diff options
Diffstat (limited to 'nova/api/openstack/compute')
16 files changed, 550 insertions, 21 deletions
diff --git a/nova/api/openstack/compute/contrib/agents.py b/nova/api/openstack/compute/contrib/agents.py index c05eb4ef2f..70c6874dd8 100644 --- a/nova/api/openstack/compute/contrib/agents.py +++ b/nova/api/openstack/compute/contrib/agents.py @@ -97,7 +97,7 @@ class AgentController(object): md5hash = para['md5hash'] version = para['version'] except (TypeError, KeyError) as ex: - msg = _("Invalid request body: %s") % unicode(ex) + msg = _("Invalid request body: %s") % ex raise webob.exc.HTTPBadRequest(explanation=msg) try: @@ -115,7 +115,7 @@ class AgentController(object): agent.md5hash = md5hash agent.save() except ValueError as ex: - msg = _("Invalid request body: %s") % unicode(ex) + msg = _("Invalid request body: %s") % ex raise webob.exc.HTTPBadRequest(explanation=msg) except exception.AgentBuildNotFound as ex: raise webob.exc.HTTPNotFound(explanation=ex.format_message()) @@ -153,7 +153,7 @@ class AgentController(object): url = agent['url'] md5hash = agent['md5hash'] except (TypeError, KeyError) as ex: - msg = _("Invalid request body: %s") % unicode(ex) + msg = _("Invalid request body: %s") % ex raise webob.exc.HTTPBadRequest(explanation=msg) try: diff --git a/nova/api/openstack/compute/contrib/cloudpipe.py b/nova/api/openstack/compute/contrib/cloudpipe.py index 0f05f59c5c..abae9bf06e 100644 --- a/nova/api/openstack/compute/contrib/cloudpipe.py +++ b/nova/api/openstack/compute/contrib/cloudpipe.py @@ -81,8 +81,8 @@ class CloudpipeController(object): if pipelib.is_vpn_image(instance['image_ref']) and instance['vm_state'] != vm_states.DELETED] - def _get_cloudpipe_for_project(self, context, project_id): - """Get the cloudpipe instance for a project ID.""" + 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] @@ -143,7 +143,7 @@ class CloudpipeController(object): context.user_id = 'project-vpn' context.is_admin = False context.roles = [] - instance = self._get_cloudpipe_for_project(context, project_id) + instance = self._get_cloudpipe_for_project(context) if not instance: try: result = self.cloudpipe.launch_vpn_instance(context) diff --git a/nova/api/openstack/compute/contrib/cloudpipe_update.py b/nova/api/openstack/compute/contrib/cloudpipe_update.py index 662915ba8e..a00229894a 100644 --- a/nova/api/openstack/compute/contrib/cloudpipe_update.py +++ b/nova/api/openstack/compute/contrib/cloudpipe_update.py @@ -53,7 +53,7 @@ class CloudpipeUpdateController(wsgi.Controller): network.vpn_public_port = vpn_port network.save() except (TypeError, KeyError, ValueError) as ex: - msg = _("Invalid request body: %s") % unicode(ex) + msg = _("Invalid request body: %s") % ex raise webob.exc.HTTPBadRequest(explanation=msg) return webob.Response(status_int=202) diff --git a/nova/api/openstack/compute/contrib/security_group_default_rules.py b/nova/api/openstack/compute/contrib/security_group_default_rules.py index 30ed7d094f..08dabca889 100644 --- a/nova/api/openstack/compute/contrib/security_group_default_rules.py +++ b/nova/api/openstack/compute/contrib/security_group_default_rules.py @@ -13,6 +13,7 @@ # under the License. from xml.dom import minidom +import six import webob from webob import exc @@ -119,7 +120,7 @@ class SecurityGroupDefaultRulesController(sg.SecurityGroupControllerBase): ip_protocol=sg_rule.get('ip_protocol'), cidr=sg_rule.get('cidr')) except Exception as exp: - raise exc.HTTPBadRequest(explanation=unicode(exp)) + raise exc.HTTPBadRequest(explanation=six.text_type(exp)) if values is None: msg = _('Not enough parameters to build a valid rule.') diff --git a/nova/api/openstack/compute/contrib/security_groups.py b/nova/api/openstack/compute/contrib/security_groups.py index 43ba3ca8b1..52fa1e6c3d 100644 --- a/nova/api/openstack/compute/contrib/security_groups.py +++ b/nova/api/openstack/compute/contrib/security_groups.py @@ -20,6 +20,7 @@ import contextlib from xml.dom import minidom from oslo.serialization import jsonutils +import six import webob from webob import exc @@ -390,7 +391,7 @@ class SecurityGroupRulesController(SecurityGroupControllerBase): cidr=sg_rule.get('cidr'), group_id=sg_rule.get('group_id')) except Exception as exp: - raise exc.HTTPBadRequest(explanation=unicode(exp)) + raise exc.HTTPBadRequest(explanation=six.text_type(exp)) if new_rule is None: msg = _("Not enough parameters to build a valid rule.") diff --git a/nova/api/openstack/compute/contrib/simple_tenant_usage.py b/nova/api/openstack/compute/contrib/simple_tenant_usage.py index c1b13fd792..8b0c1d6c3b 100644 --- a/nova/api/openstack/compute/contrib/simple_tenant_usage.py +++ b/nova/api/openstack/compute/contrib/simple_tenant_usage.py @@ -17,6 +17,7 @@ import datetime import iso8601 from oslo.utils import timeutils +import six import six.moves.urllib.parse as urlparse from webob import exc @@ -55,7 +56,7 @@ def parse_strtime(dstr, fmt): try: return timeutils.parse_strtime(dstr, fmt) except (TypeError, ValueError) as e: - raise exception.InvalidStrTime(reason=unicode(e)) + raise exception.InvalidStrTime(reason=six.text_type(e)) class SimpleTenantUsageTemplate(xmlutil.TemplateBuilder): diff --git a/nova/api/openstack/compute/plugins/__init__.py b/nova/api/openstack/compute/plugins/__init__.py index 73857e2541..71cbe00e9d 100644 --- a/nova/api/openstack/compute/plugins/__init__.py +++ b/nova/api/openstack/compute/plugins/__init__.py @@ -48,7 +48,7 @@ class LoadedExtensionInfo(object): ' '.join(extension.__doc__.strip().split())) LOG.debug('Ext version: %i', extension.version) except AttributeError as ex: - LOG.exception(_("Exception loading extension: %s"), unicode(ex)) + LOG.exception(_("Exception loading extension: %s"), ex) return False return True diff --git a/nova/api/openstack/compute/plugins/v3/baremetal_nodes.py b/nova/api/openstack/compute/plugins/v3/baremetal_nodes.py new file mode 100644 index 0000000000..3582e88090 --- /dev/null +++ b/nova/api/openstack/compute/plugins/v3/baremetal_nodes.py @@ -0,0 +1,173 @@ +# Copyright (c) 2013 NTT DOCOMO, INC. +# Copyright 2014 IBM Corporation. +# All Rights Reserved. +# +# 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. + +"""The bare-metal admin extension.""" + +from oslo.config import cfg +from oslo.utils import importutils +import webob + +from nova.api.openstack import extensions +from nova.api.openstack import wsgi +from nova.i18n import _ + +ironic_client = importutils.try_import('ironicclient.client') + +CONF = cfg.CONF +ALIAS = "os-baremetal-nodes" +authorize = extensions.extension_authorizer('compute', 'v3:' + ALIAS) + +node_fields = ['id', 'cpus', 'local_gb', 'memory_mb', 'pm_address', + 'pm_user', 'service_host', 'terminal_port', 'instance_uuid'] + +node_ext_fields = ['uuid', 'task_state', 'updated_at', 'pxe_config_path'] + +interface_fields = ['id', 'address', 'datapath_id', 'port_no'] + +CONF.import_opt('api_version', + 'nova.virt.ironic.driver', + group='ironic') +CONF.import_opt('api_endpoint', + 'nova.virt.ironic.driver', + group='ironic') +CONF.import_opt('admin_username', + 'nova.virt.ironic.driver', + group='ironic') +CONF.import_opt('admin_password', + 'nova.virt.ironic.driver', + group='ironic') +CONF.import_opt('admin_tenant_name', + 'nova.virt.ironic.driver', + group='ironic') +CONF.import_opt('compute_driver', 'nova.virt.driver') + + +def _interface_dict(interface_ref): + d = {} + for f in interface_fields: + d[f] = interface_ref.get(f) + return d + + +def _get_ironic_client(): + """return an Ironic client.""" + # TODO(NobodyCam): Fix insecure setting + kwargs = {'os_username': CONF.ironic.admin_username, + 'os_password': CONF.ironic.admin_password, + 'os_auth_url': CONF.ironic.admin_url, + 'os_tenant_name': CONF.ironic.admin_tenant_name, + 'os_service_type': 'baremetal', + 'os_endpoint_type': 'public', + 'insecure': 'true', + 'ironic_url': CONF.ironic.api_endpoint} + icli = ironic_client.get_client(CONF.ironic.api_version, **kwargs) + return icli + + +def _no_ironic_proxy(cmd): + raise webob.exc.HTTPBadRequest( + explanation=_("Command Not supported. Please use Ironic " + "command %(cmd)s to perform this " + "action.") % {'cmd': cmd}) + + +class BareMetalNodeController(wsgi.Controller): + """The Bare-Metal Node API controller for the OpenStack API.""" + + def _node_dict(self, node_ref): + d = {} + for f in node_fields: + d[f] = node_ref.get(f) + for f in node_ext_fields: + d[f] = node_ref.get(f) + return d + + @extensions.expected_errors(404) + def index(self, req): + context = req.environ['nova.context'] + authorize(context) + nodes = [] + # proxy command to Ironic + icli = _get_ironic_client() + ironic_nodes = icli.node.list(detail=True) + for inode in ironic_nodes: + node = {'id': inode.uuid, + 'interfaces': [], + 'host': 'IRONIC MANAGED', + 'task_state': inode.provision_state, + 'cpus': inode.properties['cpus'], + 'memory_mb': inode.properties['memory_mb'], + 'disk_gb': inode.properties['local_gb']} + nodes.append(node) + return {'nodes': nodes} + + @extensions.expected_errors(404) + def show(self, req, id): + context = req.environ['nova.context'] + authorize(context) + # proxy command to Ironic + icli = _get_ironic_client() + inode = icli.node.get(id) + iports = icli.node.list_ports(id) + node = {'id': inode.uuid, + 'interfaces': [], + 'host': 'IRONIC MANAGED', + 'task_state': inode.provision_state, + 'cpus': inode.properties['cpus'], + 'memory_mb': inode.properties['memory_mb'], + 'disk_gb': inode.properties['local_gb'], + 'instance_uuid': inode.instance_uuid} + for port in iports: + node['interfaces'].append({'address': port.address}) + return {'node': node} + + @extensions.expected_errors(400) + def create(self, req, body): + _no_ironic_proxy("port-create") + + @extensions.expected_errors(400) + def delete(self, req, id): + _no_ironic_proxy("port-create") + + @wsgi.action('add_interface') + @extensions.expected_errors(400) + def _add_interface(self, req, id, body): + _no_ironic_proxy("port-create") + + @wsgi.action('remove_interface') + @extensions.expected_errors(400) + def _remove_interface(self, req, id, body): + _no_ironic_proxy("port-delete") + + +class BareMetalNodes(extensions.V3APIExtensionBase): + """Admin-only bare-metal node administration.""" + + name = "BareMetalNodes" + alias = ALIAS + version = 1 + + def get_resources(self): + resource = [extensions.ResourceExtension(ALIAS, + BareMetalNodeController(), + member_actions={"action": "POST"})] + return resource + + def get_controller_extensions(self): + """It's an abstract function V3APIExtensionBase and the extension + will not be loaded without it. + """ + return [] diff --git a/nova/api/openstack/compute/plugins/v3/block_device_mapping.py b/nova/api/openstack/compute/plugins/v3/block_device_mapping.py index ef54f064df..b0794a63cb 100644 --- a/nova/api/openstack/compute/plugins/v3/block_device_mapping.py +++ b/nova/api/openstack/compute/plugins/v3/block_device_mapping.py @@ -53,6 +53,10 @@ class BlockDeviceMapping(extensions.V3APIExtensionBase): 'is not allowed in the same request.') raise exc.HTTPBadRequest(explanation=expl) + if not isinstance(bdm, list): + msg = _('block_device_mapping_v2 must be a list') + raise exc.HTTPBadRequest(explanation=msg) + try: block_device_mapping = [ block_device.BlockDeviceDict.from_api(bdm_dict) diff --git a/nova/api/openstack/compute/plugins/v3/block_device_mapping_v1.py b/nova/api/openstack/compute/plugins/v3/block_device_mapping_v1.py index 187ab61b84..f9c5b2b906 100644 --- a/nova/api/openstack/compute/plugins/v3/block_device_mapping_v1.py +++ b/nova/api/openstack/compute/plugins/v3/block_device_mapping_v1.py @@ -54,6 +54,10 @@ class BlockDeviceMappingV1(extensions.V3APIExtensionBase): 'is not allowed in the same request.') raise exc.HTTPBadRequest(explanation=expl) + if not isinstance(block_device_mapping, list): + msg = _('block_device_mapping must be a list') + raise exc.HTTPBadRequest(explanation=msg) + for bdm in block_device_mapping: try: block_device.validate_device_name(bdm.get("device_name")) diff --git a/nova/api/openstack/compute/plugins/v3/cloudpipe.py b/nova/api/openstack/compute/plugins/v3/cloudpipe.py index a0eb192829..6d57d15c72 100644 --- a/nova/api/openstack/compute/plugins/v3/cloudpipe.py +++ b/nova/api/openstack/compute/plugins/v3/cloudpipe.py @@ -18,8 +18,10 @@ from oslo.config import cfg from oslo.utils import timeutils from webob import exc +from nova.api.openstack.compute.schemas.v3 import cloudpipe from nova.api.openstack import extensions from nova.api.openstack import wsgi +from nova.api import validation from nova.cloudpipe import pipelib from nova import compute from nova.compute import utils as compute_utils @@ -27,6 +29,7 @@ from nova.compute import vm_states from nova import exception from nova.i18n import _ from nova import network +from nova import objects from nova.openstack.common import fileutils from nova import utils @@ -61,8 +64,8 @@ class CloudpipeController(wsgi.Controller): if pipelib.is_vpn_image(instance['image_ref']) and instance['vm_state'] != vm_states.DELETED] - def _get_cloudpipe_for_project(self, context, project_id): - """Get the cloudpipe instance for a project ID.""" + 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] @@ -123,7 +126,7 @@ class CloudpipeController(wsgi.Controller): context.user_id = 'project-vpn' context.is_admin = False context.roles = [] - instance = self._get_cloudpipe_for_project(context, project_id) + instance = self._get_cloudpipe_for_project(context) if not instance: try: result = self.cloudpipe.launch_vpn_instance(context) @@ -143,6 +146,30 @@ class CloudpipeController(wsgi.Controller): for x in self._get_all_cloudpipes(context)] return {'cloudpipes': vpns} + @wsgi.response(202) + @extensions.expected_errors(400) + @validation.schema(cloudpipe.update) + def update(self, req, id, body): + """Configure cloudpipe parameters for the project.""" + + context = req.environ['nova.context'] + authorize(context) + + if id != "configure-project": + msg = _("Unknown action %s") % id + raise exc.HTTPBadRequest(explanation=msg) + + project_id = context.project_id + networks = objects.NetworkList.get_by_project(context, project_id) + + params = body['configure_project'] + vpn_ip = params['vpn_ip'] + vpn_port = params['vpn_port'] + for nw in networks: + nw.vpn_public_address = vpn_ip + nw.vpn_public_port = vpn_port + nw.save() + class Cloudpipe(extensions.V3APIExtensionBase): """Adds actions to create cloudpipe instances. diff --git a/nova/api/openstack/compute/plugins/v3/security_groups.py b/nova/api/openstack/compute/plugins/v3/security_groups.py index 38020abf9b..e48750842a 100644 --- a/nova/api/openstack/compute/plugins/v3/security_groups.py +++ b/nova/api/openstack/compute/plugins/v3/security_groups.py @@ -15,9 +15,12 @@ # under the License. """The security groups extension.""" +import contextlib from oslo.serialization import jsonutils +from webob import exc +from nova.api.openstack import common from nova.api.openstack.compute.schemas.v3 import security_groups as \ schema_security_groups from nova.api.openstack import extensions @@ -25,10 +28,13 @@ from nova.api.openstack import wsgi from nova import compute from nova.compute import api as compute_api from nova import exception +from nova.i18n import _ from nova.network.security_group import neutron_driver from nova.network.security_group import openstack_driver +from nova.openstack.common import log as logging +LOG = logging.getLogger(__name__) ALIAS = 'os-security-groups' ATTRIBUTE_NAME = 'security_groups' authorize = extensions.extension_authorizer('compute', 'v3:' + ALIAS) @@ -41,6 +47,263 @@ def _authorize_context(req): return context +@contextlib.contextmanager +def translate_exceptions(): + """Translate nova exceptions to http exceptions.""" + try: + yield + except exception.Invalid as exp: + msg = exp.format_message() + raise exc.HTTPBadRequest(explanation=msg) + except exception.SecurityGroupNotFound as exp: + msg = exp.format_message() + raise exc.HTTPNotFound(explanation=msg) + except exception.InstanceNotFound as exp: + msg = exp.format_message() + raise exc.HTTPNotFound(explanation=msg) + except exception.SecurityGroupLimitExceeded as exp: + msg = exp.format_message() + raise exc.HTTPForbidden(explanation=msg) + except exception.NoUniqueMatch as exp: + msg = exp.format_message() + raise exc.HTTPConflict(explanation=msg) + + +class SecurityGroupControllerBase(wsgi.Controller): + """Base class for Security Group controllers.""" + + def __init__(self): + self.security_group_api = ( + openstack_driver.get_openstack_security_group_driver()) + self.compute_api = compute.API( + security_group_api=self.security_group_api) + + def _format_security_group_rule(self, context, rule, group_rule_data=None): + """Return a secuity group rule in desired API response format. + + If group_rule_data is passed in that is used rather than querying + for it. + """ + sg_rule = {} + sg_rule['id'] = rule['id'] + sg_rule['parent_group_id'] = rule['parent_group_id'] + sg_rule['ip_protocol'] = rule['protocol'] + sg_rule['from_port'] = rule['from_port'] + sg_rule['to_port'] = rule['to_port'] + sg_rule['group'] = {} + sg_rule['ip_range'] = {} + if rule['group_id']: + with translate_exceptions(): + try: + source_group = self.security_group_api.get( + context, id=rule['group_id']) + except exception.SecurityGroupNotFound: + # NOTE(arosen): There is a possible race condition that can + # occur here if two api calls occur concurrently: one that + # lists the security groups and another one that deletes a + # security group rule that has a group_id before the + # group_id is fetched. To handle this if + # SecurityGroupNotFound is raised we return None instead + # of the rule and the caller should ignore the rule. + LOG.debug("Security Group ID %s does not exist", + rule['group_id']) + return + sg_rule['group'] = {'name': source_group.get('name'), + 'tenant_id': source_group.get('project_id')} + elif group_rule_data: + sg_rule['group'] = group_rule_data + else: + sg_rule['ip_range'] = {'cidr': rule['cidr']} + return sg_rule + + def _format_security_group(self, context, group): + security_group = {} + security_group['id'] = group['id'] + security_group['description'] = group['description'] + security_group['name'] = group['name'] + security_group['tenant_id'] = group['project_id'] + security_group['rules'] = [] + for rule in group['rules']: + formatted_rule = self._format_security_group_rule(context, rule) + if formatted_rule: + security_group['rules'] += [formatted_rule] + return security_group + + def _from_body(self, body, key): + if not body: + raise exc.HTTPBadRequest( + explanation=_("The request body can't be empty")) + value = body.get(key, None) + if value is None: + raise exc.HTTPBadRequest( + explanation=_("Missing parameter %s") % key) + return value + + +class SecurityGroupController(SecurityGroupControllerBase): + """The Security group API controller for the OpenStack API.""" + + def show(self, req, id): + """Return data about the given security group.""" + context = _authorize_context(req) + + with translate_exceptions(): + id = self.security_group_api.validate_id(id) + security_group = self.security_group_api.get(context, None, id, + map_exception=True) + + return {'security_group': self._format_security_group(context, + security_group)} + + @wsgi.response(202) + def delete(self, req, id): + """Delete a security group.""" + context = _authorize_context(req) + + with translate_exceptions(): + id = self.security_group_api.validate_id(id) + security_group = self.security_group_api.get(context, None, id, + map_exception=True) + self.security_group_api.destroy(context, security_group) + + def index(self, req): + """Returns a list of security groups.""" + context = _authorize_context(req) + + search_opts = {} + search_opts.update(req.GET) + + with translate_exceptions(): + project_id = context.project_id + raw_groups = self.security_group_api.list(context, + project=project_id, + search_opts=search_opts) + + limited_list = common.limited(raw_groups, req) + result = [self._format_security_group(context, group) + for group in limited_list] + + return {'security_groups': + list(sorted(result, + key=lambda k: (k['tenant_id'], k['name'])))} + + def create(self, req, body): + """Creates a new security group.""" + context = _authorize_context(req) + + security_group = self._from_body(body, 'security_group') + + group_name = security_group.get('name', None) + group_description = security_group.get('description', None) + + with translate_exceptions(): + self.security_group_api.validate_property(group_name, 'name', None) + self.security_group_api.validate_property(group_description, + 'description', None) + group_ref = self.security_group_api.create_security_group( + context, group_name, group_description) + + return {'security_group': self._format_security_group(context, + group_ref)} + + def update(self, req, id, body): + """Update a security group.""" + context = _authorize_context(req) + + with translate_exceptions(): + id = self.security_group_api.validate_id(id) + security_group = self.security_group_api.get(context, None, id, + map_exception=True) + + security_group_data = self._from_body(body, 'security_group') + group_name = security_group_data.get('name', None) + group_description = security_group_data.get('description', None) + + with translate_exceptions(): + self.security_group_api.validate_property(group_name, 'name', None) + self.security_group_api.validate_property(group_description, + 'description', None) + group_ref = self.security_group_api.update_security_group( + context, security_group, group_name, group_description) + + return {'security_group': self._format_security_group(context, + group_ref)} + + +class ServerSecurityGroupController(SecurityGroupControllerBase): + + def index(self, req, server_id): + """Returns a list of security groups for the given instance.""" + context = _authorize_context(req) + + self.security_group_api.ensure_default(context) + + with translate_exceptions(): + instance = self.compute_api.get(context, server_id) + groups = self.security_group_api.get_instance_security_groups( + context, instance['uuid'], True) + + result = [self._format_security_group(context, group) + for group in groups] + + return {'security_groups': + list(sorted(result, + key=lambda k: (k['tenant_id'], k['name'])))} + + +class SecurityGroupActionController(wsgi.Controller): + def __init__(self, *args, **kwargs): + super(SecurityGroupActionController, self).__init__(*args, **kwargs) + self.security_group_api = ( + openstack_driver.get_openstack_security_group_driver()) + self.compute_api = compute.API( + security_group_api=self.security_group_api) + + def _parse(self, body, action): + try: + body = body[action] + group_name = body['name'] + except TypeError: + msg = _("Missing parameter dict") + raise exc.HTTPBadRequest(explanation=msg) + except KeyError: + msg = _("Security group not specified") + raise exc.HTTPBadRequest(explanation=msg) + + if not group_name or group_name.strip() == '': + msg = _("Security group name cannot be empty") + raise exc.HTTPBadRequest(explanation=msg) + + return group_name + + def _invoke(self, method, context, id, group_name): + with translate_exceptions(): + instance = self.compute_api.get(context, id) + method(context, instance, group_name) + + @wsgi.response(202) + @wsgi.action('addSecurityGroup') + def _addSecurityGroup(self, req, id, body): + context = req.environ['nova.context'] + authorize(context) + + group_name = self._parse(body, 'addSecurityGroup') + + return self._invoke(self.security_group_api.add_to_instance, + context, id, group_name) + + @wsgi.response(202) + @wsgi.action('removeSecurityGroup') + def _removeSecurityGroup(self, req, id, body): + context = req.environ['nova.context'] + authorize(context) + + group_name = self._parse(body, 'removeSecurityGroup') + + return self._invoke(self.security_group_api.remove_from_instance, + context, id, group_name) + + class SecurityGroupsOutputController(wsgi.Controller): def __init__(self, *args, **kwargs): super(SecurityGroupsOutputController, self).__init__(*args, **kwargs) @@ -115,12 +378,20 @@ class SecurityGroups(extensions.V3APIExtensionBase): version = 1 def get_controller_extensions(self): - controller = SecurityGroupsOutputController() - output = extensions.ControllerExtension(self, 'servers', controller) - return [output] + secgrp_output_ext = extensions.ControllerExtension( + self, 'servers', SecurityGroupsOutputController()) + secgrp_act_ext = extensions.ControllerExtension( + self, 'servers', SecurityGroupActionController()) + return [secgrp_output_ext, secgrp_act_ext] def get_resources(self): - return [] + secgrp_ext = extensions.ResourceExtension('os-security-groups', + SecurityGroupController()) + server_secgrp_ext = extensions.ResourceExtension( + 'os-security-groups', + controller=ServerSecurityGroupController(), + parent=dict(member_name='server', collection_name='servers')) + return [secgrp_ext, server_secgrp_ext] # NOTE(gmann): This function is not supposed to use 'body_deprecated_param' # parameter as this is placed to handle scheduler_hint extension for V2.1. diff --git a/nova/api/openstack/compute/plugins/v3/servers.py b/nova/api/openstack/compute/plugins/v3/servers.py index 767eef1adc..5aef949c1f 100644 --- a/nova/api/openstack/compute/plugins/v3/servers.py +++ b/nova/api/openstack/compute/plugins/v3/servers.py @@ -549,7 +549,7 @@ class ServersController(wsgi.Controller): 'err_msg': err.value} raise exc.HTTPBadRequest(explanation=msg) except UnicodeDecodeError as error: - msg = "UnicodeError: %s" % unicode(error) + msg = "UnicodeError: %s" % error raise exc.HTTPBadRequest(explanation=msg) except (exception.ImageNotActive, exception.FlavorDiskTooSmall, diff --git a/nova/api/openstack/compute/plugins/v3/simple_tenant_usage.py b/nova/api/openstack/compute/plugins/v3/simple_tenant_usage.py index f672b484d3..a79153baed 100644 --- a/nova/api/openstack/compute/plugins/v3/simple_tenant_usage.py +++ b/nova/api/openstack/compute/plugins/v3/simple_tenant_usage.py @@ -17,6 +17,7 @@ import datetime import iso8601 from oslo.utils import timeutils +import six import six.moves.urllib.parse as urlparse from webob import exc @@ -37,7 +38,7 @@ def parse_strtime(dstr, fmt): try: return timeutils.parse_strtime(dstr, fmt) except (TypeError, ValueError) as e: - raise exception.InvalidStrTime(reason=unicode(e)) + raise exception.InvalidStrTime(reason=six.text_type(e)) class SimpleTenantUsageController(object): diff --git a/nova/api/openstack/compute/schemas/v3/cloudpipe.py b/nova/api/openstack/compute/schemas/v3/cloudpipe.py new file mode 100644 index 0000000000..d4e0183772 --- /dev/null +++ b/nova/api/openstack/compute/schemas/v3/cloudpipe.py @@ -0,0 +1,39 @@ +# Copyright 2014 IBM Corporation. All rights reserved. +# +# 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. + +from nova.api.validation import parameter_types + + +update = { + 'type': 'object', + 'properties': { + 'configure_project': { + 'type': 'object', + 'properties': { + 'vpn_ip': { + 'type': 'string', + 'oneOf': [ + {'format': 'ipv4'}, + {'format': 'ipv6'} + ], + }, + 'vpn_port': parameter_types.tcp_udp_port, + }, + 'required': ['vpn_ip', 'vpn_port'], + 'additionalProperties': False, + }, + }, + 'required': ['configure_project'], + 'additionalProperties': False, +} diff --git a/nova/api/openstack/compute/servers.py b/nova/api/openstack/compute/servers.py index 92a0102238..7ac660226b 100644 --- a/nova/api/openstack/compute/servers.py +++ b/nova/api/openstack/compute/servers.py @@ -857,6 +857,9 @@ class Controller(wsgi.Controller): legacy_bdm = True if self.ext_mgr.is_loaded('os-volumes'): block_device_mapping = server_dict.get('block_device_mapping', []) + if not isinstance(block_device_mapping, list): + msg = _('block_device_mapping must be a list') + raise exc.HTTPBadRequest(explanation=msg) for bdm in block_device_mapping: try: block_device.validate_device_name(bdm.get("device_name")) @@ -879,6 +882,10 @@ class Controller(wsgi.Controller): 'is not allowed in the same request.') raise exc.HTTPBadRequest(explanation=expl) + if not isinstance(block_device_mapping_v2, list): + msg = _('block_device_mapping_v2 must be a list') + raise exc.HTTPBadRequest(explanation=msg) + # Assume legacy format legacy_bdm = not bool(block_device_mapping_v2) @@ -980,7 +987,7 @@ class Controller(wsgi.Controller): 'err_msg': err.value} raise exc.HTTPBadRequest(explanation=msg) except UnicodeDecodeError as error: - msg = "UnicodeError: %s" % unicode(error) + msg = "UnicodeError: %s" % error raise exc.HTTPBadRequest(explanation=msg) except (exception.ImageNotActive, exception.FlavorDiskTooSmall, |