diff options
| author | Todd Willey <xtoddx@gmail.com> | 2011-12-19 13:03:37 -0500 |
|---|---|---|
| committer | Todd Willey <xtoddx@gmail.com> | 2012-01-03 15:05:46 -0500 |
| commit | 6f0ef4240fc42f3bf4e7b59cd83997edddb3c985 (patch) | |
| tree | 9d0ffa80c421e1b2fa6ba77d93a9100be58c87f4 /nova/api | |
| parent | 02f1b50fb21e6f314d69bc7888bee1c7dabaf9af (diff) | |
| download | nova-6f0ef4240fc42f3bf4e7b59cd83997edddb3c985.tar.gz nova-6f0ef4240fc42f3bf4e7b59cd83997edddb3c985.tar.xz nova-6f0ef4240fc42f3bf4e7b59cd83997edddb3c985.zip | |
Add cloudpipe/vpn api to openstack api contrib.
blueprint cloudpipe-extension
Updates:
2011-12-19 #1: * Remove unused imports
* return uuid as the instance_id
* change state with bad config to "invalid"
* whitespace cleanup
* change top-level key on index to "cloudpipes"
2011-12-22 #1: * add serializer
* change post body to be cloudpipe/project_id
* change admin api method
2011-12-23 #1: * Change extension namespace
2011-12-23 #2: * Fix failing extension test
2011-12-23 #3: * Add xtoddx@gmail.com to .mailmap
2011-12-27 #1: * pep-8
2012-01-02 #1: * fix test stubs to not cause later test failures
2012-01-03 #1: * fix test self.app to not traverse middlewares
* don't use not in for a single item list
Change-Id: I5710f8cea710fa09e5405c30d565144a7c10e112
Diffstat (limited to 'nova/api')
| -rw-r--r-- | nova/api/openstack/v2/contrib/cloudpipe.py | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/nova/api/openstack/v2/contrib/cloudpipe.py b/nova/api/openstack/v2/contrib/cloudpipe.py new file mode 100644 index 000000000..33c23c1d0 --- /dev/null +++ b/nova/api/openstack/v2/contrib/cloudpipe.py @@ -0,0 +1,183 @@ +# Copyright 2011 Openstack, LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Connect your vlan to the world.""" + +import os + +from nova.api.openstack import wsgi +from nova.api.openstack import xmlutil +from nova.api.openstack.v2 import extensions +from nova.auth import manager +from nova.cloudpipe import pipelib +from nova import compute +from nova.compute import vm_states +from nova import db +from nova import exception +from nova import flags +from nova import log as logging +from nova import utils + + +FLAGS = flags.FLAGS +LOG = logging.getLogger("nova.api.openstack.v2.contrib.cloudpipe") + + +class CloudpipeController(object): + """Handle creating and listing cloudpipe instances.""" + + def __init__(self): + self.compute_api = compute.API() + self.auth_manager = manager.AuthManager() + self.cloudpipe = pipelib.CloudPipe() + self.setup() + + def setup(self): + """Ensure the keychains and folders exist.""" + # TODO(todd): this was copyed from api.ec2.cloud + # FIXME(ja): this should be moved to a nova-manage command, + # if not setup throw exceptions instead of running + # Create keys folder, if it doesn't exist + if not os.path.exists(FLAGS.keys_path): + os.makedirs(FLAGS.keys_path) + # Gen root CA, if we don't have one + root_ca_path = os.path.join(FLAGS.ca_path, FLAGS.ca_file) + if not os.path.exists(root_ca_path): + genrootca_sh_path = os.path.join(os.path.dirname(__file__), + os.path.pardir, + os.path.pardir, + 'CA', + 'genrootca.sh') + + start = os.getcwd() + if not os.path.exists(FLAGS.ca_path): + os.makedirs(FLAGS.ca_path) + os.chdir(FLAGS.ca_path) + # TODO(vish): Do this with M2Crypto instead + utils.runthis(_("Generating root CA: %s"), "sh", genrootca_sh_path) + os.chdir(start) + + def _get_cloudpipe_for_project(self, context, project_id): + """Get the cloudpipe instance for a project ID.""" + # NOTE(todd): this should probably change to compute_api.get_all + # or db.instance_get_project_vpn + for instance in db.instance_get_all_by_project(context, project_id): + if (instance['image_id'] == str(FLAGS.vpn_image_id) + and instance['vm_state'] != vm_states.DELETED): + return instance + + def _vpn_dict(self, project, vpn_instance): + rv = {'project_id': project.id, + 'public_ip': project.vpn_ip, + 'public_port': project.vpn_port} + if vpn_instance: + rv['instance_id'] = vpn_instance['uuid'] + rv['created_at'] = utils.isotime(vpn_instance['created_at']) + address = vpn_instance.get('fixed_ip', None) + if address: + rv['internal_ip'] = address['address'] + if project.vpn_ip and project.vpn_port: + if utils.vpn_ping(project.vpn_ip, project.vpn_port): + rv['state'] = 'running' + else: + rv['state'] = 'down' + else: + rv['state'] = 'invalid' + else: + rv['state'] = 'pending' + return rv + + def create(self, req, body): + """Create a new cloudpipe instance, if none exists. + + Parameters: {cloudpipe: {project_id: XYZ}} + """ + + ctxt = req.environ['nova.context'] + params = body.get('cloudpipe', {}) + project_id = params.get('project_id', ctxt.project_id) + instance = self._get_cloudpipe_for_project(ctxt, project_id) + if not instance: + proj = self.auth_manager.get_project(project_id) + user_id = proj.project_manager_id + try: + self.cloudpipe.launch_vpn_instance(project_id, user_id) + except db.NoMoreNetworks: + msg = _("Unable to claim IP for VPN instances, ensure it " + "isn't running, and try again in a few minutes") + raise exception.ApiError(msg) + instance = self._get_cloudpipe_for_project(ctxt, proj) + return {'instance_id': instance['uuid']} + + def index(self, req): + """Show admins the list of running cloudpipe instances.""" + context = req.environ['nova.context'] + vpns = [] + # TODO(todd): could use compute_api.get_all with admin context? + for project in self.auth_manager.get_projects(): + instance = self._get_cloudpipe_for_project(context, project.id) + vpns.append(self._vpn_dict(project, instance)) + return {'cloudpipes': vpns} + + +class Cloudpipe(extensions.ExtensionDescriptor): + """Adds actions to create cloudpipe instances. + + When running with the Vlan network mode, you need a mechanism to route + from the public Internet to your vlans. This mechanism is known as a + cloudpipe. + + At the time of creating this class, only OpenVPN is supported. Support for + a SSH Bastion host is forthcoming. + """ + + name = "Cloudpipe" + alias = "os-cloudpipe" + namespace = "http://docs.openstack.org/compute/ext/cloudpipe/api/v1.1" + updated = "2011-12-16T00:00:00+00:00" + admin_only = True + + def get_resources(self): + resources = [] + body_serializers = { + 'application/xml': CloudpipeSerializer(), + } + serializer = wsgi.ResponseSerializer(body_serializers) + res = extensions.ResourceExtension('os-cloudpipe', + CloudpipeController(), + serializer=serializer) + resources.append(res) + return resources + + +class CloudpipeSerializer(xmlutil.XMLTemplateSerializer): + def index(self): + return CloudpipesTemplate() + + def default(self): + return CloudpipeTemplate() + + +class CloudpipeTemplate(xmlutil.TemplateBuilder): + def construct(self): + return xmlutil.MasterTemplate(xmlutil.make_flat_dict('cloudpipe'), 1) + + +class CloudpipesTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('cloudpipes') + elem = xmlutil.make_flat_dict('cloudpipe', selector='cloudpipes', + subselector='cloudpipe') + root.append(elem) + return xmlutil.MasterTemplate(root, 1) |
