summaryrefslogtreecommitdiff
path: root/nova/api/openstack/compute
diff options
context:
space:
mode:
Diffstat (limited to 'nova/api/openstack/compute')
-rw-r--r--nova/api/openstack/compute/contrib/agents.py6
-rw-r--r--nova/api/openstack/compute/contrib/cloudpipe.py6
-rw-r--r--nova/api/openstack/compute/contrib/cloudpipe_update.py2
-rw-r--r--nova/api/openstack/compute/contrib/security_group_default_rules.py3
-rw-r--r--nova/api/openstack/compute/contrib/security_groups.py3
-rw-r--r--nova/api/openstack/compute/contrib/simple_tenant_usage.py3
-rw-r--r--nova/api/openstack/compute/plugins/__init__.py2
-rw-r--r--nova/api/openstack/compute/plugins/v3/baremetal_nodes.py173
-rw-r--r--nova/api/openstack/compute/plugins/v3/block_device_mapping.py4
-rw-r--r--nova/api/openstack/compute/plugins/v3/block_device_mapping_v1.py4
-rw-r--r--nova/api/openstack/compute/plugins/v3/cloudpipe.py33
-rw-r--r--nova/api/openstack/compute/plugins/v3/security_groups.py279
-rw-r--r--nova/api/openstack/compute/plugins/v3/servers.py2
-rw-r--r--nova/api/openstack/compute/plugins/v3/simple_tenant_usage.py3
-rw-r--r--nova/api/openstack/compute/schemas/v3/cloudpipe.py39
-rw-r--r--nova/api/openstack/compute/servers.py9
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,