From 916e0ce0997bdf3135684865eff6fadcda95752b Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 3 Aug 2011 14:13:53 -0400 Subject: remove unused imports --- nova/api/openstack/create_instance_helper.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 53e814cd5..e4b897cd6 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -14,8 +14,6 @@ # under the License. import base64 -import re -import webob from webob import exc from xml.dom import minidom -- cgit From 91e16e057f083d1a0b8dffaa00b5979c12c23edc Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 3 Aug 2011 14:30:45 -0400 Subject: fix potential runtime exception The exception could occur if a client were to create an APIRouter object. The fix relies on more established OOP patterns. --- nova/api/openstack/__init__.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 868b98a31..8d7f7a405 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -81,7 +81,10 @@ class APIRouter(base_wsgi.Router): self._setup_routes(mapper) super(APIRouter, self).__init__(mapper) - def _setup_routes(self, mapper, version): + def _setup_routes(self, mapper): + raise NotImplementedError("you must implement _setup_routes") + + def _setup_base_routes(self, mapper, version): """Routes common to all versions.""" server_members = self.server_members @@ -147,7 +150,7 @@ class APIRouterV10(APIRouter): """Define routes specific to OpenStack API V1.0.""" def _setup_routes(self, mapper): - super(APIRouterV10, self)._setup_routes(mapper, '1.0') + self._setup_base_routes(mapper, '1.0') mapper.resource("shared_ip_group", "shared_ip_groups", collection={'detail': 'GET'}, @@ -163,7 +166,7 @@ class APIRouterV11(APIRouter): """Define routes specific to OpenStack API V1.1.""" def _setup_routes(self, mapper): - super(APIRouterV11, self)._setup_routes(mapper, '1.1') + self._setup_base_routes(mapper, '1.1') image_metadata_controller = image_metadata.create_resource() mapper.resource("image_meta", "metadata", controller=image_metadata_controller, -- cgit From a5add8b30c96ad1d83eebcd9756b0f358c9cb725 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 3 Aug 2011 15:16:38 -0400 Subject: follow convention when raising exceptions --- nova/api/openstack/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 8d7f7a405..0af39c2df 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -82,7 +82,7 @@ class APIRouter(base_wsgi.Router): super(APIRouter, self).__init__(mapper) def _setup_routes(self, mapper): - raise NotImplementedError("you must implement _setup_routes") + raise NotImplementedError(_("You must implement _setup_routes.")) def _setup_base_routes(self, mapper, version): """Routes common to all versions.""" -- cgit From 634fe881223a7ea8e04b3054b39724207153be5b Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Wed, 3 Aug 2011 15:03:34 -0700 Subject: Initial version --- nova/api/openstack/contrib/security_groups.py | 489 ++++++++++++++++++++++++++ nova/api/openstack/extensions.py | 11 +- 2 files changed, 498 insertions(+), 2 deletions(-) create mode 100644 nova/api/openstack/contrib/security_groups.py (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/security_groups.py b/nova/api/openstack/contrib/security_groups.py new file mode 100644 index 000000000..39f1959e0 --- /dev/null +++ b/nova/api/openstack/contrib/security_groups.py @@ -0,0 +1,489 @@ +# Copyright 2011 OpenStack LLC. +# 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 security groups extension.""" + +import netaddr +import urllib +from webob import exc +import webob + +from nova import compute +from nova import db +from nova import exception +from nova import flags +from nova import log as logging +from nova.api.openstack import common +from nova.api.openstack import extensions +from nova.api.openstack import wsgi + + +from xml.dom import minidom + + +LOG = logging.getLogger("nova.api.contrib.security_groups") +FLAGS = flags.FLAGS + + +class SecurityGroupController(object): + """The Security group API controller for the OpenStack API.""" + + def __init__(self): + self.compute_api = compute.API() + super(SecurityGroupController, self).__init__() + + def _format_security_group_rule(self, context, rule): + r = {} + r['id'] = rule.id + r['parent_group_id'] = rule.parent_group_id + r['ip_protocol'] = rule.protocol + r['from_port'] = rule.from_port + r['to_port'] = rule.to_port + r['group'] = {} + r['ip_range'] = {} + if rule.group_id: + source_group = db.security_group_get(context, rule.group_id) + r['group'] = {'name': source_group.name, + 'tenant_id': source_group.project_id} + else: + r['ip_range'] = {'cidr': rule.cidr} + return r + + def _format_security_group(self, context, group): + g = {} + g['id'] = group.id + g['description'] = group.description + g['name'] = group.name + g['tenant_id'] = group.project_id + g['rules'] = [] + for rule in group.rules: + r = self._format_security_group_rule(context, rule) + g['rules'] += [r] + return g + + def show(self, req, id): + """Return data about the given security group.""" + context = req.environ['nova.context'] + try: + id = int(id) + security_group = db.security_group_get(context, id) + except ValueError: + msg = _("Security group id is not integer") + return exc.HTTPBadRequest(explanation=msg) + except exception.NotFound as exp: + return exc.HTTPNotFound(explanation=unicode(exp)) + + return {'security_group': self._format_security_group(context, + security_group)} + + def delete(self, req, id): + """Delete a security group.""" + context = req.environ['nova.context'] + try: + id = int(id) + security_group = db.security_group_get(context, id) + except ValueError: + msg = _("Security group id is not integer") + return exc.HTTPBadRequest(explanation=msg) + except exception.NotFound as exp: + return exc.HTTPNotFound(explanation=unicode(exp)) + + LOG.audit(_("Delete security group %s"), id, context=context) + db.security_group_destroy(context, security_group.id) + + return exc.HTTPAccepted() + + def index(self, req): + """Returns a list of security groups""" + context = req.environ['nova.context'] + + self.compute_api.ensure_default_security_group(context) + if context.is_admin: + groups = db.security_group_get_all(context) + else: + groups = db.security_group_get_by_project(context, + context.project_id) + + limited_list = common.limited(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 = req.environ['nova.context'] + if not body: + return exc.HTTPUnprocessableEntity() + + security_group = body.get('security_group', None) + + if security_group is None: + return exc.HTTPUnprocessableEntity() + + group_name = security_group.get('name', None) + group_description = security_group.get('description', None) + + self._validate_security_group_name(group_name) + self._validate_security_group_description(group_description) + + LOG.audit(_("Create Security Group %s"), group_name, context=context) + self.compute_api.ensure_default_security_group(context) + if db.security_group_exists(context, context.project_id, group_name): + msg = _('Security group %s already exists') % group_name + raise exc.HTTPBadRequest(explanation=msg) + + group = {'user_id': context.user_id, + 'project_id': context.project_id, + 'name': group_name, + 'description': group_description} + group_ref = db.security_group_create(context, group) + + return {'security_group': self._format_security_group(context, + group_ref)} + + def _validate_security_group_name(self, value): + if value is None: + msg = _("Security group name is mandatory") + raise exc.HTTPBadRequest(explanation=msg) + + if not isinstance(value, basestring): + msg = _("Security group name is not a string or unicode") + raise exc.HTTPBadRequest(explanation=msg) + + if value.strip() == '': + msg = _("Security group name is an empty string") + raise exc.HTTPBadRequest(explanation=msg) + + if len(value.strip()) > 255: + msg = _("Security group name should not be greater " + "than 255 characters") + raise exc.HTTPBadRequest(explanation=msg) + + def _validate_security_group_description(self, value): + if value is None: + msg = _("Security group description is mandatory") + raise exc.HTTPBadRequest(explanation=msg) + + if not isinstance(value, basestring): + msg = _("Security group description is not a string or unicode") + raise exc.HTTPBadRequest(explanation=msg) + + if value.strip() == '': + msg = _("Security group description is an empty string") + raise exc.HTTPBadRequest(explanation=msg) + + if len(value.strip()) > 255: + msg = _("Security group description should not be " + "greater than 255 characters") + raise exc.HTTPBadRequest(explanation=msg) + + +class SecurityGroupRulesController(SecurityGroupController): + + def create(self, req, body): + context = req.environ['nova.context'] + + if not body: + raise exc.HTTPUnprocessableEntity() + + if not 'security_group_rule' in body: + raise exc.HTTPUnprocessableEntity() + + self.compute_api.ensure_default_security_group(context) + + sg_rule = body['security_group_rule'] + parent_group_id = sg_rule.get('parent_group_id', None) + try: + parent_group_id = int(parent_group_id) + security_group = db.security_group_get(context, parent_group_id) + except ValueError: + msg = _("Parent group id is not integer") + return exc.HTTPBadRequest(explanation=msg) + except exception.NotFound as exp: + msg = _("Security group (%s) not found") % parent_group_id + return exc.HTTPNotFound(explanation=msg) + + msg = "Authorize security group ingress %s" + LOG.audit(_(msg), security_group['name'], context=context) + + try: + values = self._rule_args_to_dict(context, + to_port=sg_rule.get('to_port'), + from_port=sg_rule.get('from_port'), + parent_group_id=sg_rule.get('parent_group_id'), + ip_protocol=sg_rule.get('ip_protocol'), + cidr=sg_rule.get('cidr'), + group_id=sg_rule.get('group_id')) + except Exception as exp: + raise exc.HTTPBadRequest(explanation=unicode(exp)) + + if values is None: + msg = _("Not enough parameters to build a " + "valid rule.") + raise exc.HTTPBadRequest(explanation=msg) + + values['parent_group_id'] = security_group.id + + if self._security_group_rule_exists(security_group, values): + msg = _('This rule already exists in group %s') % parent_group_id + raise exc.HTTPBadRequest(explanation=msg) + + security_group_rule = db.security_group_rule_create(context, values) + + self.compute_api.trigger_security_group_rules_refresh(context, + security_group_id=security_group['id']) + + return {'security_group_rule': self._format_security_group_rule( + context, + security_group_rule)} + + def _security_group_rule_exists(self, security_group, values): + """Indicates whether the specified rule values are already + defined in the given security group. + """ + for rule in security_group.rules: + if 'group_id' in values: + if rule['group_id'] == values['group_id']: + return True + else: + is_duplicate = True + for key in ('cidr', 'from_port', 'to_port', 'protocol'): + if rule[key] != values[key]: + is_duplicate = False + break + if is_duplicate: + return True + return False + + def _rule_args_to_dict(self, context, to_port=None, from_port=None, + parent_group_id=None, ip_protocol=None, + cidr=None, group_id=None): + values = {} + + if group_id: + try: + parent_group_id = int(parent_group_id) + group_id = int(group_id) + except ValueError: + msg = _("Parent or group id is not integer") + raise exception.InvalidInput(reason=msg) + + if parent_group_id == group_id: + msg = _("Parent group id and group id cannot be same") + raise exception.InvalidInput(reason=msg) + + values['group_id'] = group_id + #check if groupId exists + db.security_group_get(context, group_id) + elif cidr: + # If this fails, it throws an exception. This is what we want. + try: + cidr = urllib.unquote(cidr).decode() + netaddr.IPNetwork(cidr) + except Exception: + raise exception.InvalidCidr(cidr=cidr) + values['cidr'] = cidr + else: + values['cidr'] = '0.0.0.0/0' + + if ip_protocol and from_port and to_port: + + try: + from_port = int(from_port) + to_port = int(to_port) + except ValueError: + raise exception.InvalidPortRange(from_port=from_port, + to_port=to_port) + ip_protocol = str(ip_protocol) + if ip_protocol.upper() not in ['TCP', 'UDP', 'ICMP']: + raise exception.InvalidIpProtocol(protocol=ip_protocol) + if ((min(from_port, to_port) < -1) or + (max(from_port, to_port) > 65535)): + raise exception.InvalidPortRange(from_port=from_port, + to_port=to_port) + + values['protocol'] = ip_protocol + values['from_port'] = from_port + values['to_port'] = to_port + else: + # If cidr based filtering, protocol and ports are mandatory + if 'cidr' in values: + return None + + return values + + def delete(self, req, id): + context = req.environ['nova.context'] + + self.compute_api.ensure_default_security_group(context) + try: + id = int(id) + rule = db.security_group_rule_get(context, id) + except ValueError: + msg = _("Rule id is not integer") + return exc.HTTPBadRequest(explanation=msg) + except exception.NotFound as exp: + msg = _("Rule (%s) not found") % id + return exc.HTTPNotFound(explanation=msg) + + group_id = rule.parent_group_id + self.compute_api.ensure_default_security_group(context) + + security_group = db.security_group_get(context, group_id) + if not security_group: + raise exception.SecurityGroupNotFound(security_group_id=group_id) + + msg = _("Revoke security group ingress %s") + LOG.audit(_(msg), security_group['name'], context=context) + + db.security_group_rule_destroy(context, rule['id']) + self.compute_api.trigger_security_group_rules_refresh(context, + security_group_id=security_group['id']) + + return exc.HTTPAccepted() + + +class Security_groups(extensions.ExtensionDescriptor): + def get_name(self): + return "SecurityGroups" + + def get_alias(self): + return "security_groups" + + def get_description(self): + return "Security group support" + + def get_namespace(self): + return "http://docs.openstack.org/ext/securitygroups/api/v1.1" + + def get_updated(self): + return "2011-07-21T00:00:00+00:00" + + def get_resources(self): + resources = [] + + metadata = _get_metadata() + body_serializers = { + 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, + xmlns=wsgi.XMLNS_V11), + } + serializer = wsgi.ResponseSerializer(body_serializers, None) + + body_deserializers = { + 'application/xml': SecurityGroupXMLDeserializer(), + } + deserializer = wsgi.RequestDeserializer(body_deserializers) + + res = extensions.ResourceExtension('security_groups', + controller=SecurityGroupController(), + deserializer=deserializer, + serializer=serializer) + + resources.append(res) + + body_deserializers = { + 'application/xml': SecurityGroupRulesXMLDeserializer(), + } + deserializer = wsgi.RequestDeserializer(body_deserializers) + + res = extensions.ResourceExtension('security_group_rules', + controller=SecurityGroupRulesController(), + deserializer=deserializer, + serializer=serializer) + resources.append(res) + return resources + + +class SecurityGroupXMLDeserializer(wsgi.MetadataXMLDeserializer): + """ + Deserializer to handle xml-formatted security group requests. + """ + def create(self, string): + """Deserialize an xml-formatted security group create request""" + dom = minidom.parseString(string) + security_group = {} + sg_node = self.find_first_child_named(dom, + 'security_group') + if sg_node is not None: + if sg_node.hasAttribute('name'): + security_group['name'] = sg_node.getAttribute('name') + desc_node = self.find_first_child_named(sg_node, + "description") + if desc_node: + security_group['description'] = self.extract_text(desc_node) + return {'body': {'security_group': security_group}} + + +class SecurityGroupRulesXMLDeserializer(wsgi.MetadataXMLDeserializer): + """ + Deserializer to handle xml-formatted security group requests. + """ + + def create(self, string): + """Deserialize an xml-formatted security group create request""" + dom = minidom.parseString(string) + security_group_rule = self._extract_security_group_rule(dom) + return {'body': {'security_group_rule': security_group_rule}} + + def _extract_security_group_rule(self, node): + """Marshal the security group rule attribute of a parsed request""" + sg_rule = {} + sg_rule_node = self.find_first_child_named(node, + 'security_group_rule') + if sg_rule_node is not None: + ip_protocol_node = self.find_first_child_named(sg_rule_node, + "ip_protocol") + if ip_protocol_node is not None: + sg_rule['ip_protocol'] = self.extract_text(ip_protocol_node) + + from_port_node = self.find_first_child_named(sg_rule_node, + "from_port") + if from_port_node is not None: + sg_rule['from_port'] = self.extract_text(from_port_node) + + to_port_node = self.find_first_child_named(sg_rule_node, "to_port") + if to_port_node is not None: + sg_rule['to_port'] = self.extract_text(to_port_node) + + parent_group_id_node = self.find_first_child_named(sg_rule_node, + "parent_group_id") + if parent_group_id_node is not None: + sg_rule['parent_group_id'] = self.extract_text( + parent_group_id_node) + + group_id_node = self.find_first_child_named(sg_rule_node, + "group_id") + if group_id_node is not None: + sg_rule['group_id'] = self.extract_text(group_id_node) + + cidr_node = self.find_first_child_named(sg_rule_node, "cidr") + if cidr_node is not None: + sg_rule['cidr'] = self.extract_text(cidr_node) + + return sg_rule + + +def _get_metadata(): + metadata = { + "attributes": { + "security_group": ["id", "tenant_id", "name"], + "rule": ["id", "parent_group_id"], + "security_group_rule": ["id", "parent_group_id"], + } + } + return metadata diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index cc889703e..15b3cfae4 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -265,9 +265,13 @@ class ExtensionMiddleware(base_wsgi.Middleware): for resource in ext_mgr.get_resources(): LOG.debug(_('Extended resource: %s'), resource.collection) + if resource.serializer is None: + resource.serializer = serializer + mapper.resource(resource.collection, resource.collection, controller=wsgi.Resource( - resource.controller, serializer=serializer), + resource.controller, resource.deserializer, + resource.serializer), collection=resource.collection_actions, member=resource.member_actions, parent_resource=resource.parent) @@ -460,12 +464,15 @@ class ResourceExtension(object): """Add top level resources to the OpenStack API in nova.""" def __init__(self, collection, controller, parent=None, - collection_actions={}, member_actions={}): + collection_actions={}, member_actions={}, + deserializer=None, serializer=None): self.collection = collection self.controller = controller self.parent = parent self.collection_actions = collection_actions self.member_actions = member_actions + self.deserializer = deserializer + self.serializer = serializer class ExtensionsXMLSerializer(wsgi.XMLDictSerializer): -- cgit From b94eb7bf4fd71a23cacc20def2b5a47dad053b56 Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Wed, 3 Aug 2011 16:42:23 -0700 Subject: Remove whitespaces from name and description before creating security group --- nova/api/openstack/contrib/security_groups.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/security_groups.py b/nova/api/openstack/contrib/security_groups.py index 39f1959e0..7da046b8f 100644 --- a/nova/api/openstack/contrib/security_groups.py +++ b/nova/api/openstack/contrib/security_groups.py @@ -140,6 +140,8 @@ class SecurityGroupController(object): self._validate_security_group_name(group_name) self._validate_security_group_description(group_description) + group_name = group_name.strip() + group_description = group_description.strip() LOG.audit(_("Create Security Group %s"), group_name, context=context) self.compute_api.ensure_default_security_group(context) -- cgit From 23f6ec84a93638e880f60bec00db8587b9e3e8d8 Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Wed, 3 Aug 2011 20:26:37 -0400 Subject: fix pylint W0102 errors. --- nova/api/openstack/extensions.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index cc889703e..b070dd61a 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -460,7 +460,11 @@ class ResourceExtension(object): """Add top level resources to the OpenStack API in nova.""" def __init__(self, collection, controller, parent=None, - collection_actions={}, member_actions={}): + collection_actions=None, member_actions=None): + if not collection_actions: + collection_actions = {} + if not member_actions: + member_actions = {} self.collection = collection self.controller = controller self.parent = parent -- cgit From 2fe0c5fe95487df8827db10f38065e3c8ac3800f Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Fri, 5 Aug 2011 12:09:46 -0700 Subject: Fixed review comments --- nova/api/openstack/contrib/security_groups.py | 101 ++++++++++---------------- 1 file changed, 40 insertions(+), 61 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/security_groups.py b/nova/api/openstack/contrib/security_groups.py index 7da046b8f..e2fed7965 100644 --- a/nova/api/openstack/contrib/security_groups.py +++ b/nova/api/openstack/contrib/security_groups.py @@ -45,33 +45,33 @@ class SecurityGroupController(object): super(SecurityGroupController, self).__init__() def _format_security_group_rule(self, context, rule): - r = {} - r['id'] = rule.id - r['parent_group_id'] = rule.parent_group_id - r['ip_protocol'] = rule.protocol - r['from_port'] = rule.from_port - r['to_port'] = rule.to_port - r['group'] = {} - r['ip_range'] = {} + 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: source_group = db.security_group_get(context, rule.group_id) - r['group'] = {'name': source_group.name, + sg_rule['group'] = {'name': source_group.name, 'tenant_id': source_group.project_id} else: - r['ip_range'] = {'cidr': rule.cidr} - return r + sg_rule['ip_range'] = {'cidr': rule.cidr} + return sg_rule def _format_security_group(self, context, group): - g = {} - g['id'] = group.id - g['description'] = group.description - g['name'] = group.name - g['tenant_id'] = group.project_id - g['rules'] = [] + 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: - r = self._format_security_group_rule(context, rule) - g['rules'] += [r] - return g + security_group['rules'] += [self._format_security_group_rule( + context, rule)] + return security_group def show(self, req, id): """Return data about the given security group.""" @@ -97,7 +97,7 @@ class SecurityGroupController(object): except ValueError: msg = _("Security group id is not integer") return exc.HTTPBadRequest(explanation=msg) - except exception.NotFound as exp: + except exception.SecurityGroupNotFound as exp: return exc.HTTPNotFound(explanation=unicode(exp)) LOG.audit(_("Delete security group %s"), id, context=context) @@ -138,8 +138,9 @@ class SecurityGroupController(object): group_name = security_group.get('name', None) group_description = security_group.get('description', None) - self._validate_security_group_name(group_name) - self._validate_security_group_description(group_description) + self._validate_security_group_property(group_name, "name") + self._validate_security_group_property(group_description, + "description") group_name = group_name.strip() group_description = group_description.strip() @@ -158,40 +159,21 @@ class SecurityGroupController(object): return {'security_group': self._format_security_group(context, group_ref)} - def _validate_security_group_name(self, value): - if value is None: - msg = _("Security group name is mandatory") - raise exc.HTTPBadRequest(explanation=msg) - - if not isinstance(value, basestring): - msg = _("Security group name is not a string or unicode") - raise exc.HTTPBadRequest(explanation=msg) - - if value.strip() == '': - msg = _("Security group name is an empty string") - raise exc.HTTPBadRequest(explanation=msg) - - if len(value.strip()) > 255: - msg = _("Security group name should not be greater " - "than 255 characters") - raise exc.HTTPBadRequest(explanation=msg) - - def _validate_security_group_description(self, value): - if value is None: - msg = _("Security group description is mandatory") - raise exc.HTTPBadRequest(explanation=msg) - - if not isinstance(value, basestring): - msg = _("Security group description is not a string or unicode") + def _validate_security_group_property(self, value, typ): + """ typ will be either 'name' or 'description', + depending on the caller + """ + try: + val = value.strip() + except AttributeError: + msg = _("Security group %s is not a string or unicode") % typ raise exc.HTTPBadRequest(explanation=msg) - - if value.strip() == '': - msg = _("Security group description is an empty string") + if not val: + msg = _("Security group %s cannot be empty.") % typ raise exc.HTTPBadRequest(explanation=msg) - - if len(value.strip()) > 255: - msg = _("Security group description should not be " - "greater than 255 characters") + if len(val) > 255: + msg = _("Security group %s should not be greater " + "than 255 characters.") % typ raise exc.HTTPBadRequest(explanation=msg) @@ -220,7 +202,7 @@ class SecurityGroupRulesController(SecurityGroupController): msg = _("Security group (%s) not found") % parent_group_id return exc.HTTPNotFound(explanation=msg) - msg = "Authorize security group ingress %s" + msg = _("Authorize security group ingress %s") LOG.audit(_(msg), security_group['name'], context=context) try: @@ -315,7 +297,7 @@ class SecurityGroupRulesController(SecurityGroupController): if ip_protocol.upper() not in ['TCP', 'UDP', 'ICMP']: raise exception.InvalidIpProtocol(protocol=ip_protocol) if ((min(from_port, to_port) < -1) or - (max(from_port, to_port) > 65535)): + (max(from_port, to_port) > 65535)): raise exception.InvalidPortRange(from_port=from_port, to_port=to_port) @@ -345,17 +327,14 @@ class SecurityGroupRulesController(SecurityGroupController): group_id = rule.parent_group_id self.compute_api.ensure_default_security_group(context) - security_group = db.security_group_get(context, group_id) - if not security_group: - raise exception.SecurityGroupNotFound(security_group_id=group_id) msg = _("Revoke security group ingress %s") LOG.audit(_(msg), security_group['name'], context=context) db.security_group_rule_destroy(context, rule['id']) self.compute_api.trigger_security_group_rules_refresh(context, - security_group_id=security_group['id']) + security_group_id=security_group['id']) return exc.HTTPAccepted() -- cgit From 2a329ff0734bc4413723322e289a0ac486ed7e2f Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Fri, 5 Aug 2011 12:43:27 -0700 Subject: Fixed localization review comment --- nova/api/openstack/contrib/security_groups.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/security_groups.py b/nova/api/openstack/contrib/security_groups.py index e2fed7965..d3a8e21b8 100644 --- a/nova/api/openstack/contrib/security_groups.py +++ b/nova/api/openstack/contrib/security_groups.py @@ -203,7 +203,7 @@ class SecurityGroupRulesController(SecurityGroupController): return exc.HTTPNotFound(explanation=msg) msg = _("Authorize security group ingress %s") - LOG.audit(_(msg), security_group['name'], context=context) + LOG.audit(msg, security_group['name'], context=context) try: values = self._rule_args_to_dict(context, @@ -330,7 +330,7 @@ class SecurityGroupRulesController(SecurityGroupController): security_group = db.security_group_get(context, group_id) msg = _("Revoke security group ingress %s") - LOG.audit(_(msg), security_group['name'], context=context) + LOG.audit(msg, security_group['name'], context=context) db.security_group_rule_destroy(context, rule['id']) self.compute_api.trigger_security_group_rules_refresh(context, -- cgit From d5bcc6764b418d7aa16b53b50173261385445ee8 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Tue, 9 Aug 2011 11:31:48 -0700 Subject: initial port --- nova/api/openstack/contrib/keypairs.py | 137 +++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 nova/api/openstack/contrib/keypairs.py (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/keypairs.py b/nova/api/openstack/contrib/keypairs.py new file mode 100644 index 000000000..879bd9712 --- /dev/null +++ b/nova/api/openstack/contrib/keypairs.py @@ -0,0 +1,137 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# 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. + +""" Keypair management extension""" + +from webob import exc + +from nova import db +from nova import crypto +from nova import exception +from nova.api.openstack import extensions + + +class KeypairController(object): + """ Keypair API controller for the Openstack API """ + + # TODO(ja): the keypair crud logic should be in nova.compute.API? + + def _gen_key(self): + """ + Generate a key + """ + # TODO(ja): crypto.generate_key_pair is currently a slow method + # and should probably be moved to a process pool? + private_key, public_key, fingerprint = crypto.generate_key_pair() + return {'private_key': private_key, + 'public_key': public_key, + 'fingerprint': fingerprint} + + def create(self, req, body): + """ + Create or import keypair. + + Sending key_name will generate a key and return private_key + and fingerprint. + + You can send a public_key to add an existing ssh key + + params: keypair object with: + key_name (required) - string + fingerprint (optional) - string + public_key (optional) - string + """ + + context = req.environ['nova.context'] + params = body['keypair'] + key_name = params['key_name'] + + # NOTE(ja): generation is slow, so shortcut invalid key_name exception + try: + db.key_pair_get(context, context.user_id, key_name) + raise exception.KeyPairExists(key_name=key_name) + except exception.NotFound: + pass + + keypair = {'user_id': context.user_id, + 'name': key_name} + + # import if public_key is sent + if 'public_key' in params: + keypair['public_key'] = params['public_key'] + keypair['fingerprint'] = params.get('fingerprint', None) + else: + generated_key = self._gen_key() + keypair['private_key'] = generated_key['private_key'] + keypair['public_key'] = generated_key['public_key'] + keypair['fingerprint'] = generated_key['fingerprint'] + + db.key_pair_create(context, keypair) + return {'keypair': keypair} + + def delete(self, req, name): + """ + Delete a keypair with a given name + """ + context = req.environ['nova.context'] + db.key_pair_destroy(context, context.user_id, name) + return exc.HTTPAccepted() + + def index(self, req): + """ + List of keypairs for a user + """ + context = req.environ['nova.context'] + key_pairs = db.key_pair_get_all_by_user(context, context.user_id) + rval = [] + for key_pair in key_pairs: + rval.append({ + 'name': key_pair['name'], + 'key_name': key_pair['name'], + 'fingerprint': key_pair['fingerprint'], + }) + + return {'keypairs': rval} + + +class Keypairs(extensions.ExtensionDescriptor): + + def get_name(self): + return "Keypairs" + + def get_alias(self): + return "os-keypairs" + + def get_description(self): + return "Keypair Support" + + def get_namespace(self): + return \ + "http://docs.openstack.org/ext/keypairs/api/v1.1" + + def get_updated(self): + return "2011-08-08T00:00:00+00:00" + + def get_resources(self): + resources = [] + + res = extensions.ResourceExtension( + 'os-keypairs', + KeypairController()) + + resources.append(res) + return resources -- cgit From 057b6ad650013a952f88f6e02f3e3db0164084d1 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Tue, 9 Aug 2011 13:00:13 -0700 Subject: added tests - list doesn't pass due to unicode issues --- nova/api/openstack/contrib/keypairs.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/keypairs.py b/nova/api/openstack/contrib/keypairs.py index 879bd9712..8435494e1 100644 --- a/nova/api/openstack/contrib/keypairs.py +++ b/nova/api/openstack/contrib/keypairs.py @@ -23,19 +23,20 @@ from nova import db from nova import crypto from nova import exception from nova.api.openstack import extensions - +import os +import shutil +import tempfile class KeypairController(object): """ Keypair API controller for the Openstack API """ - # TODO(ja): the keypair crud logic should be in nova.compute.API? + # TODO(ja): both this file and nova.api.ec2.cloud.py have similar logic. + # move the common keypair logic to nova.compute.API? def _gen_key(self): """ Generate a key """ - # TODO(ja): crypto.generate_key_pair is currently a slow method - # and should probably be moved to a process pool? private_key, public_key, fingerprint = crypto.generate_key_pair() return {'private_key': private_key, 'public_key': public_key, @@ -52,7 +53,6 @@ class KeypairController(object): params: keypair object with: key_name (required) - string - fingerprint (optional) - string public_key (optional) - string """ @@ -72,8 +72,14 @@ class KeypairController(object): # import if public_key is sent if 'public_key' in params: + tmpdir = tempfile.mkdtemp() + fn = os.path.join(tmpdir, 'import.pub') + with open(fn, 'w') as pub: + pub.write(params['public_key']) + fingerprint = crypto.generate_fingerprint(fn) + shutil.rmtree(tmpdir) keypair['public_key'] = params['public_key'] - keypair['fingerprint'] = params.get('fingerprint', None) + keypair['fingerprint'] = fingerprint else: generated_key = self._gen_key() keypair['private_key'] = generated_key['private_key'] @@ -83,12 +89,12 @@ class KeypairController(object): db.key_pair_create(context, keypair) return {'keypair': keypair} - def delete(self, req, name): + def delete(self, req, id): """ Delete a keypair with a given name """ context = req.environ['nova.context'] - db.key_pair_destroy(context, context.user_id, name) + db.key_pair_destroy(context, context.user_id, id) return exc.HTTPAccepted() def index(self, req): @@ -99,11 +105,11 @@ class KeypairController(object): key_pairs = db.key_pair_get_all_by_user(context, context.user_id) rval = [] for key_pair in key_pairs: - rval.append({ + rval.append({'keypair': { 'name': key_pair['name'], 'key_name': key_pair['name'], 'fingerprint': key_pair['fingerprint'], - }) + }}) return {'keypairs': rval} -- cgit From 131b2185e23567895e3f87cdfe9c2822d18910b2 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Tue, 9 Aug 2011 16:12:59 -0700 Subject: tests pass --- nova/api/openstack/contrib/keypairs.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/keypairs.py b/nova/api/openstack/contrib/keypairs.py index 8435494e1..d718846f1 100644 --- a/nova/api/openstack/contrib/keypairs.py +++ b/nova/api/openstack/contrib/keypairs.py @@ -46,29 +46,29 @@ class KeypairController(object): """ Create or import keypair. - Sending key_name will generate a key and return private_key + Sending name will generate a key and return private_key and fingerprint. You can send a public_key to add an existing ssh key params: keypair object with: - key_name (required) - string + name (required) - string public_key (optional) - string """ context = req.environ['nova.context'] params = body['keypair'] - key_name = params['key_name'] + name = params['name'] - # NOTE(ja): generation is slow, so shortcut invalid key_name exception + # NOTE(ja): generation is slow, so shortcut invalid name exception try: - db.key_pair_get(context, context.user_id, key_name) - raise exception.KeyPairExists(key_name=key_name) + db.key_pair_get(context, context.user_id, name) + raise exception.KeyPairExists(key_name=name) except exception.NotFound: pass keypair = {'user_id': context.user_id, - 'name': key_name} + 'name': name} # import if public_key is sent if 'public_key' in params: @@ -107,7 +107,7 @@ class KeypairController(object): for key_pair in key_pairs: rval.append({'keypair': { 'name': key_pair['name'], - 'key_name': key_pair['name'], + 'public_key': key_pair['public_key'], 'fingerprint': key_pair['fingerprint'], }}) -- cgit From 83ed9faa488b3ec0f1cb16e7147293c912e2fc2b Mon Sep 17 00:00:00 2001 From: Matthew Hooker Date: Tue, 9 Aug 2011 19:35:40 -0400 Subject: pep8 fix --- nova/api/openstack/contrib/floating_ips.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 52c9c6cf9..2aba1068a 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -102,7 +102,7 @@ class FloatingIPController(object): def delete(self, req, id): context = req.environ['nova.context'] ip = self.network_api.get_floating_ip(context, id) - + if 'fixed_ip' in ip: try: self.disassociate(req, id, '') -- cgit From f73b6dc8e90b763da1fe86496fc6fd6a80b99f0a Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Tue, 9 Aug 2011 17:03:24 -0700 Subject: List security groups project wise for admin users same as other users --- nova/api/openstack/contrib/security_groups.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/security_groups.py b/nova/api/openstack/contrib/security_groups.py index d3a8e21b8..1d70956dc 100644 --- a/nova/api/openstack/contrib/security_groups.py +++ b/nova/api/openstack/contrib/security_groups.py @@ -110,12 +110,8 @@ class SecurityGroupController(object): context = req.environ['nova.context'] self.compute_api.ensure_default_security_group(context) - if context.is_admin: - groups = db.security_group_get_all(context) - else: - groups = db.security_group_get_by_project(context, - context.project_id) - + groups = db.security_group_get_by_project(context, + context.project_id) limited_list = common.limited(groups, req) result = [self._format_security_group(context, group) for group in limited_list] -- cgit From 0719b038ba1548f98668991507c001eb18c82981 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Tue, 9 Aug 2011 17:22:01 -0700 Subject: import formatting - thx --- nova/api/openstack/contrib/keypairs.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/keypairs.py b/nova/api/openstack/contrib/keypairs.py index d718846f1..201648ab5 100644 --- a/nova/api/openstack/contrib/keypairs.py +++ b/nova/api/openstack/contrib/keypairs.py @@ -17,15 +17,17 @@ """ Keypair management extension""" +import os +import shutil +import tempfile + from webob import exc -from nova import db from nova import crypto +from nova import db from nova import exception from nova.api.openstack import extensions -import os -import shutil -import tempfile + class KeypairController(object): """ Keypair API controller for the Openstack API """ -- cgit From 45d6ab8ffec6ff4b26500df7049ce4092b15f00c Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 11 Aug 2011 15:30:43 -0400 Subject: fixing id parsing --- nova/api/openstack/common.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index dfdd62201..23614d598 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -169,10 +169,13 @@ def get_id_from_href(href): Returns: 123 """ - if re.match(r'\d+$', str(href)): - return int(href) try: - return int(urlparse.urlsplit(href).path.split('/')[-1]) + href = str(href) + + if re.match(r'\d+$', href): + return int(href) + else: + return int(urlparse.urlsplit(href).path.split('/')[-1]) except ValueError, e: LOG.debug(_("Error extracting id from href: %s") % href) raise ValueError(_('could not parse id from href')) -- cgit From 68161e3e224ff77e4e93d02e5fabbd9ea17b0d48 Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Thu, 11 Aug 2011 17:04:33 -0700 Subject: prefixed with os- for the newly added extensions --- nova/api/openstack/contrib/security_groups.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/security_groups.py b/nova/api/openstack/contrib/security_groups.py index 1d70956dc..6c57fbb51 100644 --- a/nova/api/openstack/contrib/security_groups.py +++ b/nova/api/openstack/contrib/security_groups.py @@ -366,7 +366,7 @@ class Security_groups(extensions.ExtensionDescriptor): } deserializer = wsgi.RequestDeserializer(body_deserializers) - res = extensions.ResourceExtension('security_groups', + res = extensions.ResourceExtension('os-security-groups', controller=SecurityGroupController(), deserializer=deserializer, serializer=serializer) @@ -378,7 +378,7 @@ class Security_groups(extensions.ExtensionDescriptor): } deserializer = wsgi.RequestDeserializer(body_deserializers) - res = extensions.ResourceExtension('security_group_rules', + res = extensions.ResourceExtension('os-security-group-rules', controller=SecurityGroupRulesController(), deserializer=deserializer, serializer=serializer) -- cgit From f95e0118d91a8f77345e4d78980e2523cb4dba56 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Fri, 12 Aug 2011 10:59:10 -0400 Subject: Fixes to the OSAPI floating API extension DELETE. Updated to use correct args for self.disassociate (don't sweep exceptions which should cause test cases to fail under the rug). Additionally updated to pass network_api.release_floating_ip the address instead of a dict. --- nova/api/openstack/contrib/floating_ips.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 2aba1068a..c07bfdf09 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -104,12 +104,9 @@ class FloatingIPController(object): ip = self.network_api.get_floating_ip(context, id) if 'fixed_ip' in ip: - try: - self.disassociate(req, id, '') - except Exception as e: - LOG.exception(_("Error disassociating fixed_ip %s"), e) + self.disassociate(req, id) - self.network_api.release_floating_ip(context, address=ip) + self.network_api.release_floating_ip(context, address=ip['address']) return {'released': { "id": ip['id'], -- cgit From 954e8e24c6b8ceb541c539ce7c26da4b35b5f0b1 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 12 Aug 2011 11:44:49 -0400 Subject: rewriting parsing --- nova/api/openstack/common.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 23614d598..b2a675653 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -169,16 +169,20 @@ def get_id_from_href(href): Returns: 123 """ + LOG.debug(_("Attempting to treat %(href)s as an integer ID.") % locals()) + + try: + return int(href) + except ValueError: + pass + + LOG.debug(_("Attempting to treat %(href)s as a URL.") % locals()) + try: - href = str(href) - - if re.match(r'\d+$', href): - return int(href) - else: - return int(urlparse.urlsplit(href).path.split('/')[-1]) - except ValueError, e: - LOG.debug(_("Error extracting id from href: %s") % href) - raise ValueError(_('could not parse id from href')) + return int(urlparse.urlsplit(href).path.split('/')[-1]) + except ValueError as error: + LOG.debug(_("Failed to parse ID from %(href)s: %(error)s") % locals()) + raise def remove_version_from_href(href): -- cgit