summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorTodd Willey <xtoddx@gmail.com>2011-12-19 13:03:37 -0500
committerTodd Willey <xtoddx@gmail.com>2012-01-03 15:05:46 -0500
commit6f0ef4240fc42f3bf4e7b59cd83997edddb3c985 (patch)
tree9d0ffa80c421e1b2fa6ba77d93a9100be58c87f4 /nova/api
parent02f1b50fb21e6f314d69bc7888bee1c7dabaf9af (diff)
downloadnova-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.py183
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)