nova/nova/api/openstack/compute/legacy_v2/contrib/cloudpipe.py

162 lines
6.0 KiB
Python

# Copyright 2011 OpenStack Foundation
#
# 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."""
from oslo_config import cfg
from oslo_utils import fileutils
from webob import exc
from nova.api.openstack import extensions
from nova.cloudpipe import pipelib
from nova import compute
from nova.compute import utils as compute_utils
from nova.compute import vm_states
from nova import exception
from nova.i18n import _
from nova import network
from nova import utils
CONF = cfg.CONF
CONF.import_opt('keys_path', 'nova.crypto')
authorize = extensions.extension_authorizer('compute', 'cloudpipe')
class CloudpipeController(object):
"""Handle creating and listing cloudpipe instances."""
def __init__(self):
self.compute_api = compute.API()
self.network_api = network.API()
self.cloudpipe = pipelib.CloudPipe()
self.setup()
def setup(self):
"""Ensure the keychains and folders exist."""
# NOTE(vish): One of the drawbacks of doing this in the api is
# the keys will only be on the api node that launched
# the cloudpipe.
fileutils.ensure_tree(CONF.keys_path)
def _get_all_cloudpipes(self, context):
"""Get all cloudpipes."""
instances = self.compute_api.get_all(context,
search_opts={'deleted': False},
want_objects=True)
return [instance for instance in instances
if pipelib.is_vpn_image(instance.image_ref)
and instance.vm_state != vm_states.DELETED]
def _get_cloudpipe_for_project(self, context):
"""Get the cloudpipe instance for a project from context."""
cloudpipes = self._get_all_cloudpipes(context) or [None]
return cloudpipes[0]
def _vpn_dict(self, context, project_id, instance):
elevated = context.elevated()
rv = {'project_id': project_id}
if not instance:
rv['state'] = 'pending'
return rv
rv['instance_id'] = instance.uuid
rv['created_at'] = utils.isotime(instance.created_at)
nw_info = compute_utils.get_nw_info_for_instance(instance)
if not nw_info:
return rv
vif = nw_info[0]
ips = [ip for ip in vif.fixed_ips() if ip['version'] == 4]
if ips:
rv['internal_ip'] = ips[0]['address']
# NOTE(vish): Currently network_api.get does an owner check on
# project_id. This is probably no longer necessary
# but rather than risk changes in the db layer,
# we are working around it here by changing the
# project_id in the context. This can be removed
# if we remove the project_id check in the db.
elevated.project_id = project_id
network = self.network_api.get(elevated, vif['network']['id'])
if network:
vpn_ip = network['vpn_public_address']
vpn_port = network['vpn_public_port']
rv['public_ip'] = vpn_ip
rv['public_port'] = vpn_port
if vpn_ip and vpn_port:
if utils.vpn_ping(vpn_ip, vpn_port):
rv['state'] = 'running'
else:
rv['state'] = 'down'
else:
rv['state'] = 'invalid'
return rv
def create(self, req, body):
"""Create a new cloudpipe instance, if none exists.
Parameters: {cloudpipe: {'project_id': ''}}
"""
context = req.environ['nova.context']
authorize(context)
params = body.get('cloudpipe', {})
project_id = params.get('project_id', context.project_id)
# NOTE(vish): downgrade to project context. Note that we keep
# the same token so we can still talk to glance
context.project_id = project_id
context.user_id = 'project-vpn'
context.is_admin = False
context.roles = []
instance = self._get_cloudpipe_for_project(context)
if not instance:
try:
result = self.cloudpipe.launch_vpn_instance(context)
instance = result[0][0]
except exception.NoMoreNetworks:
msg = _("Unable to claim IP for VPN instances, ensure it "
"isn't running, and try again in a few minutes")
raise exc.HTTPBadRequest(explanation=msg)
return {'instance_id': instance.uuid}
def index(self, req):
"""List running cloudpipe instances."""
context = req.environ['nova.context']
authorize(context)
vpns = [self._vpn_dict(context, x['project_id'], x)
for x in self._get_all_cloudpipes(context)]
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:00Z"
def get_resources(self):
resources = []
res = extensions.ResourceExtension('os-cloudpipe',
CloudpipeController())
resources.append(res)
return resources