From 19a4ddaf157ebb388cce37ddc142dfad304b8cf0 Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Fri, 12 Aug 2011 16:48:13 -0700 Subject: Added add securitygroup to instance and remove securitygroup from instance functionality --- nova/api/openstack/contrib/security_groups.py | 199 +++++++++++++++++++++++--- nova/api/openstack/create_instance_helper.py | 30 +++- 2 files changed, 210 insertions(+), 19 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/security_groups.py b/nova/api/openstack/contrib/security_groups.py index 6c57fbb51..a104a42e4 100644 --- a/nova/api/openstack/contrib/security_groups.py +++ b/nova/api/openstack/contrib/security_groups.py @@ -25,10 +25,11 @@ from nova import db from nova import exception from nova import flags from nova import log as logging +from nova import rpc from nova.api.openstack import common from nova.api.openstack import extensions from nova.api.openstack import wsgi - +from nova.compute import power_state from xml.dom import minidom @@ -73,33 +74,28 @@ class SecurityGroupController(object): context, rule)] return security_group - def show(self, req, id): - """Return data about the given security group.""" - context = req.environ['nova.context'] + def _get_security_group(self, context, id): 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) + msg = _("Security group id should be integer") + raise exc.HTTPBadRequest(explanation=msg) except exception.NotFound as exp: - return exc.HTTPNotFound(explanation=unicode(exp)) + raise exc.HTTPNotFound(explanation=unicode(exp)) + return security_group + def show(self, req, id): + """Return data about the given security group.""" + context = req.environ['nova.context'] + security_group = self._get_security_group(context, id) 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.SecurityGroupNotFound as exp: - return exc.HTTPNotFound(explanation=unicode(exp)) - + security_group = self._get_security_group(context, id) LOG.audit(_("Delete security group %s"), id, context=context) db.security_group_destroy(context, security_group.id) @@ -172,6 +168,135 @@ class SecurityGroupController(object): "than 255 characters.") % typ raise exc.HTTPBadRequest(explanation=msg) + def associate(self, req, id, body): + context = req.environ['nova.context'] + + if not body: + raise exc.HTTPUnprocessableEntity() + + if not 'security_group_associate' in body: + raise exc.HTTPUnprocessableEntity() + + security_group = self._get_security_group(context, id) + + servers = body['security_group_associate'].get('servers') + + if not servers: + msg = _("No servers found") + return exc.HTTPBadRequest(explanation=msg) + + hosts = set() + for server in servers: + if server['id']: + try: + # check if the server exists + inst = db.instance_get(context, server['id']) + #check if the security group is assigned to the server + if self._is_security_group_associated_to_server( + security_group, inst['id']): + msg = _("Security group %s is already associated with" + " the instance %s") % (security_group['id'], + server['id']) + raise exc.HTTPBadRequest(explanation=msg) + + #check if the instance is in running state + if inst['state'] != power_state.RUNNING: + msg = _("Server %s is not in the running state")\ + % server['id'] + raise exc.HTTPBadRequest(explanation=msg) + + hosts.add(inst['host']) + except exception.InstanceNotFound as exp: + return exc.HTTPNotFound(explanation=unicode(exp)) + + # Associate security group with the server in the db + for server in servers: + if server['id']: + db.instance_add_security_group(context.elevated(), + server['id'], + security_group['id']) + + for host in hosts: + rpc.cast(context, + db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "refresh_security_group_rules", + "args": {"security_group_id": security_group['id']}}) + + return exc.HTTPAccepted() + + def _is_security_group_associated_to_server(self, security_group, + instance_id): + if not security_group: + return False + + instances = security_group.get('instances') + if not instances: + return False + + inst_id = None + for inst_id in (instance['id'] for instance in instances \ + if instance_id == instance['id']): + return True + + return False + + def disassociate(self, req, id, body): + context = req.environ['nova.context'] + + if not body: + raise exc.HTTPUnprocessableEntity() + + if not 'security_group_disassociate' in body: + raise exc.HTTPUnprocessableEntity() + + security_group = self._get_security_group(context, id) + + servers = body['security_group_disassociate'].get('servers') + + if not servers: + msg = _("No servers found") + return exc.HTTPBadRequest(explanation=msg) + + hosts = set() + for server in servers: + if server['id']: + try: + # check if the instance exists + inst = db.instance_get(context, server['id']) + # Check if the security group is not associated + # with the instance + if not self._is_security_group_associated_to_server( + security_group, inst['id']): + msg = _("Security group %s is not associated with the" + "instance %s") % (security_group['id'], + server['id']) + raise exc.HTTPBadRequest(explanation=msg) + + #check if the instance is in running state + if inst['state'] != power_state.RUNNING: + msg = _("Server %s is not in the running state")\ + % server['id'] + raise exp.HTTPBadRequest(explanation=msg) + + hosts.add(inst['host']) + except exception.InstanceNotFound as exp: + return exc.HTTPNotFound(explanation=unicode(exp)) + + # Disassociate security group from the server + for server in servers: + if server['id']: + db.instance_remove_security_group(context.elevated(), + server['id'], + security_group['id']) + + for host in hosts: + rpc.cast(context, + db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "refresh_security_group_rules", + "args": {"security_group_id": security_group['id']}}) + + return exc.HTTPAccepted() + class SecurityGroupRulesController(SecurityGroupController): @@ -226,9 +351,9 @@ class SecurityGroupRulesController(SecurityGroupController): 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']) + security_group_id=security_group['id']) - return {'security_group_rule': self._format_security_group_rule( + return {"security_group_rule": self._format_security_group_rule( context, security_group_rule)} @@ -368,6 +493,10 @@ class Security_groups(extensions.ExtensionDescriptor): res = extensions.ResourceExtension('os-security-groups', controller=SecurityGroupController(), + member_actions={ + 'associate': 'POST', + 'disassociate': 'POST' + }, deserializer=deserializer, serializer=serializer) @@ -405,6 +534,40 @@ class SecurityGroupXMLDeserializer(wsgi.MetadataXMLDeserializer): security_group['description'] = self.extract_text(desc_node) return {'body': {'security_group': security_group}} + def _get_servers(self, node): + servers_dict = {'servers': []} + if node is not None: + servers_node = self.find_first_child_named(node, + 'servers') + if servers_node is not None: + for server_node in self.find_children_named(servers_node, + "server"): + servers_dict['servers'].append( + {"id": self.extract_text(server_node)}) + return servers_dict + + def associate(self, string): + """Deserialize an xml-formatted security group associate request""" + dom = minidom.parseString(string) + node = self.find_first_child_named(dom, + 'security_group_associate') + result = {'body': {}} + if node: + result['body']['security_group_associate'] = \ + self._get_servers(node) + return result + + def disassociate(self, string): + """Deserialize an xml-formatted security group disassociate request""" + dom = minidom.parseString(string) + node = self.find_first_child_named(dom, + 'security_group_disassociate') + result = {'body': {}} + if node: + result['body']['security_group_disassociate'] = \ + self._get_servers(node) + return result + class SecurityGroupRulesXMLDeserializer(wsgi.MetadataXMLDeserializer): """ diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 1425521a9..4ceb972c0 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -111,6 +111,16 @@ class CreateInstanceHelper(object): if personality: injected_files = self._get_injected_files(personality) + sg_names = [] + security_groups = server_dict.get('security_groups') + if security_groups: + sg_names = [sg['name'] for sg in security_groups if sg.get('name')] + if not sg_names: + sg_names.append('default') + + sg_names = list(set(sg_names)) + LOG.debug(sg_names) + try: flavor_id = self.controller._flavor_id_from_req_data(body) except ValueError as error: @@ -161,7 +171,8 @@ class CreateInstanceHelper(object): zone_blob=zone_blob, reservation_id=reservation_id, min_count=min_count, - max_count=max_count)) + max_count=max_count, + security_group=sg_names)) except quota.QuotaError as error: self._handle_quota_error(error) except exception.ImageNotFound as error: @@ -170,6 +181,8 @@ class CreateInstanceHelper(object): except exception.FlavorNotFound as error: msg = _("Invalid flavorRef provided.") raise exc.HTTPBadRequest(explanation=msg) + except exception.SecurityGroupNotFound as error: + raise exc.HTTPBadRequest(explanation=unicode(error)) # Let the caller deal with unhandled exceptions. def _handle_quota_error(self, error): @@ -454,6 +467,8 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer): if personality is not None: server["personality"] = personality + server["security_groups"] = self._extract_security_groups(server_node) + return server def _extract_personality(self, server_node): @@ -470,3 +485,16 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer): return personality else: return None + + def _extract_security_groups(self, server_node): + """Marshal the security_groups attribute of a parsed request""" + node = self.find_first_child_named(server_node, "security_groups") + security_groups = [] + if node is not None: + for sg_node in self.find_children_named(node, "security_group"): + item = {} + name_node = self.find_first_child_named(sg_node, "name") + if name_node: + item["name"] = self.extract_text(name_node) + security_groups.append(item) + return security_groups -- cgit From 83b45a371665fd069fc7e372628f82874258fd08 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Tue, 16 Aug 2011 00:31:54 -0700 Subject: redux of floating ip api --- nova/api/openstack/contrib/floating_ips.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 44b35c385..722320534 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -78,11 +78,14 @@ class FloatingIPController(object): def index(self, req): context = req.environ['nova.context'] - floating_ips = self.network_api.list_floating_ips(context) + try: + floating_ips = self.network_api.list_floating_ips(context) + except exception.FloatingIpNotFoundForProject: + floating_ips = [] return _translate_floating_ips_view(floating_ips) - def create(self, req): + def create(self, req, body): context = req.environ['nova.context'] try: @@ -95,9 +98,7 @@ class FloatingIPController(object): else: raise - return {'allocated': { - "id": ip['id'], - "floating_ip": ip['address']}} + return _translate_floating_ip_view(ip) def delete(self, req, id): context = req.environ['nova.context'] @@ -125,26 +126,22 @@ class FloatingIPController(object): except rpc.RemoteError: raise - return {'associated': - { - "floating_ip_id": id, - "floating_ip": floating_ip, - "fixed_ip": fixed_ip}} + floating_ip = self.network_api.get_floating_ip(context, id) + return _translate_floating_ip_view(floating_ip) def disassociate(self, req, id, body=None): """ POST /floating_ips/{id}/disassociate """ context = req.environ['nova.context'] floating_ip = self.network_api.get_floating_ip(context, id) address = floating_ip['address'] - fixed_ip = floating_ip['fixed_ip']['address'] try: self.network_api.disassociate_floating_ip(context, address) except rpc.RemoteError: raise - return {'disassociated': {'floating_ip': address, - 'fixed_ip': fixed_ip}} + floating_ip = self.network_api.get_floating_ip(context, id) + return _translate_floating_ip_view(floating_ip) def _get_ip_by_id(self, context, value): """Checks that value is id and then returns its address.""" -- cgit From fb43ea94e81e5eec51b73c2aab4a8a38cdf71361 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Tue, 16 Aug 2011 11:46:22 -0700 Subject: make delete more consistant --- nova/api/openstack/contrib/floating_ips.py | 10 +++++----- 1 file changed, 5 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 722320534..1276c0118 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -105,13 +105,13 @@ class FloatingIPController(object): ip = self.network_api.get_floating_ip(context, id) if 'fixed_ip' in ip: - self.disassociate(req, id) + try: + self.disassociate(req, id) + except exception.ApiError: + LOG.warn("disassociate failure %s", id) self.network_api.release_floating_ip(context, address=ip['address']) - - return {'released': { - "id": ip['id'], - "floating_ip": ip['address']}} + return exc.HTTPAccepted() def associate(self, req, id, body): """ /floating_ips/{id}/associate fixed ip in body """ -- cgit From 83177757632b381d42cc5107fe7d1cba8830a10a Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 16 Aug 2011 16:59:36 -0700 Subject: all tests passing --- 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 1276c0118..751b27c9f 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -85,7 +85,7 @@ class FloatingIPController(object): return _translate_floating_ips_view(floating_ips) - def create(self, req, body): + def create(self, req, body=None): context = req.environ['nova.context'] try: -- cgit From 4f3a33859c350ff13b2fd94e33de4f10a7f93bc1 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 17 Aug 2011 10:05:01 -0700 Subject: fix some naming inconsistencies, make associate/disassociate PUTs --- nova/api/openstack/contrib/floating_ips.py | 35 ++++++++++++------------------ 1 file changed, 14 insertions(+), 21 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 751b27c9f..af3eee16a 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -102,45 +102,38 @@ class FloatingIPController(object): def delete(self, req, id): context = req.environ['nova.context'] - ip = self.network_api.get_floating_ip(context, id) + floating_ip = self.network_api.get_floating_ip(context, id) - if 'fixed_ip' in ip: - try: - self.disassociate(req, id) - except exception.ApiError: - LOG.warn("disassociate failure %s", id) + if 'fixed_ip' in floating_ip: + self.network_api.disassociate_floating_ip(context, floating_ip['address']) - self.network_api.release_floating_ip(context, address=ip['address']) + self.network_api.release_floating_ip(context, address=floating_ip['address']) return exc.HTTPAccepted() def associate(self, req, id, body): - """ /floating_ips/{id}/associate fixed ip in body """ + """PUT /floating_ips/{id}/associate fixed ip in body """ context = req.environ['nova.context'] floating_ip = self._get_ip_by_id(context, id) - fixed_ip = body['associate_address']['fixed_ip'] + fixed_ip = body['floating_ip']['fixed_ip'] - try: - self.network_api.associate_floating_ip(context, - floating_ip, fixed_ip) - except rpc.RemoteError: - raise + self.network_api.associate_floating_ip(context, + floating_ip, fixed_ip) floating_ip = self.network_api.get_floating_ip(context, id) return _translate_floating_ip_view(floating_ip) def disassociate(self, req, id, body=None): - """ POST /floating_ips/{id}/disassociate """ + """PUT /floating_ips/{id}/disassociate """ context = req.environ['nova.context'] floating_ip = self.network_api.get_floating_ip(context, id) address = floating_ip['address'] - try: + # no-op if this ip is already disassociated + if 'fixed_ip' in floating_ip: self.network_api.disassociate_floating_ip(context, address) - except rpc.RemoteError: - raise + floating_ip = self.network_api.get_floating_ip(context, id) - floating_ip = self.network_api.get_floating_ip(context, id) return _translate_floating_ip_view(floating_ip) def _get_ip_by_id(self, context, value): @@ -170,8 +163,8 @@ class Floating_ips(extensions.ExtensionDescriptor): res = extensions.ResourceExtension('os-floating-ips', FloatingIPController(), member_actions={ - 'associate': 'POST', - 'disassociate': 'POST'}) + 'associate': 'PUT', + 'disassociate': 'PUT'}) resources.append(res) return resources -- cgit From 65d7db1136557b7af1f0b9413bacc8fc59e7211f Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 17 Aug 2011 10:23:44 -0700 Subject: pep8 fix --- nova/api/openstack/contrib/floating_ips.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index af3eee16a..2f5fdd001 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -105,9 +105,11 @@ class FloatingIPController(object): floating_ip = self.network_api.get_floating_ip(context, id) if 'fixed_ip' in floating_ip: - self.network_api.disassociate_floating_ip(context, floating_ip['address']) + self.network_api.disassociate_floating_ip(context, + floating_ip['address']) - self.network_api.release_floating_ip(context, address=floating_ip['address']) + self.network_api.release_floating_ip(context, + address=floating_ip['address']) return exc.HTTPAccepted() def associate(self, req, id, body): -- cgit From a4379a342798016a9dc40761561c996093945d87 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 17 Aug 2011 16:03:03 -0400 Subject: Updated server create XML deserializer to account for accessIPv4 and accessIPv6 --- nova/api/openstack/create_instance_helper.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 4e1da549e..5ba8afe97 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -443,7 +443,8 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer): server = {} server_node = self.find_first_child_named(node, 'server') - attributes = ["name", "imageRef", "flavorRef", "adminPass"] + attributes = ["name", "imageRef", "flavorRef", "adminPass", + "accessIPv4", "accessIPv6"] for attr in attributes: if server_node.getAttribute(attr): server[attr] = server_node.getAttribute(attr) -- cgit From af333cc72e753a4a28d0deb20369076df7bf09e3 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 18 Aug 2011 10:53:01 -0400 Subject: Added accessIPv4 and accessIPv6 to servers view builder Updated compute api to handle accessIPv4 and 6 --- nova/api/openstack/create_instance_helper.py | 2 ++ nova/api/openstack/views/servers.py | 4 ++++ 2 files changed, 6 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 5ba8afe97..332d5d9bb 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -157,6 +157,8 @@ class CreateInstanceHelper(object): key_name=key_name, key_data=key_data, metadata=server_dict.get('metadata', {}), + access_ip_v4=server_dict.get('accessIPv4'), + access_ip_v6=server_dict.get('accessIPv6'), injected_files=injected_files, admin_password=password, zone_blob=zone_blob, diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index edc328129..3b91c037a 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -182,6 +182,10 @@ class ViewBuilderV11(ViewBuilder): def _build_extra(self, response, inst): self._build_links(response, inst) response['uuid'] = inst['uuid'] + if inst.get('access_ip_v4'): + response['accessIPv4'] = inst['access_ip_v4'] + if inst.get('access_ip_v6'): + response['accessIPv6'] = inst['access_ip_v6'] def _build_links(self, response, inst): href = self.generate_href(inst["id"]) -- cgit From 9b5416e8afc115fabb76664a65b6d33e9ba89b7f Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 18 Aug 2011 11:05:59 -0400 Subject: Updated ServersXMLSerializer to allow accessIPv4 and accessIPv6 in XML responses --- nova/api/openstack/servers.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 335ecad86..f06ee6b62 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -837,6 +837,10 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): node.setAttribute('created', str(server['created'])) node.setAttribute('updated', str(server['updated'])) node.setAttribute('status', server['status']) + if 'accessIPv4' in server: + node.setAttribute('accessIPv4', str(server['accessIPv4'])) + if 'accessIPv6' in server: + node.setAttribute('accessIPv6', str(server['accessIPv6'])) if 'progress' in server: node.setAttribute('progress', str(server['progress'])) -- cgit From 155d640d3d53bcf76daa0ff0ae67ac5dbbe3022a Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 18 Aug 2011 12:19:47 -0400 Subject: Fixed issue where accessIP was added in none detail responses --- nova/api/openstack/views/servers.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 3b91c037a..8b3a1e221 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -143,6 +143,12 @@ class ViewBuilderV11(ViewBuilder): response['server']['progress'] = 100 elif response['server']['status'] == "BUILD": response['server']['progress'] = 0 + + if inst.get('access_ip_v4'): + response['server']['accessIPv4'] = inst['access_ip_v4'] + if inst.get('access_ip_v6'): + response['server']['accessIPv6'] = inst['access_ip_v6'] + return response def _build_image(self, response, inst): @@ -182,10 +188,6 @@ class ViewBuilderV11(ViewBuilder): def _build_extra(self, response, inst): self._build_links(response, inst) response['uuid'] = inst['uuid'] - if inst.get('access_ip_v4'): - response['accessIPv4'] = inst['access_ip_v4'] - if inst.get('access_ip_v6'): - response['accessIPv6'] = inst['access_ip_v6'] def _build_links(self, response, inst): href = self.generate_href(inst["id"]) -- cgit From cca07a461d6c826a9dcc902b7b88afe602377756 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 18 Aug 2011 16:27:49 -0400 Subject: updated PUT to severs/id to handle accessIPv4 and accessIPv6 --- nova/api/openstack/servers.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index f06ee6b62..df55d981a 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -163,7 +163,7 @@ class Controller(object): @scheduler_api.redirect_handler def update(self, req, id, body): - """Update server name then pass on to version-specific controller""" + """Update server then pass on to version-specific controller""" if len(req.body) == 0: raise exc.HTTPUnprocessableEntity() @@ -178,6 +178,14 @@ class Controller(object): self.helper._validate_server_name(name) update_dict['display_name'] = name.strip() + if 'accessIPv4' in body['server']: + access_ipv4 = body['server']['accessIPv4'] + update_dict['access_ip_v4'] = access_ipv4.strip() + + if 'accessIPv6' in body['server']: + access_ipv6 = body['server']['accessIPv6'] + update_dict['access_ip_v6'] = access_ipv6.strip() + try: self.compute_api.update(ctxt, id, **update_dict) except exception.NotFound: -- cgit From c75e132786a65501477f77efa1bc9147b7763c31 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Fri, 19 Aug 2011 15:55:56 -0400 Subject: Finished changing ServerXMLSerializationTest to use XML validation and lxml --- nova/api/openstack/schemas/v1.1/server.rng | 50 +++++++++++++++++++++++ nova/api/openstack/schemas/v1.1/servers.rng | 6 +++ nova/api/openstack/schemas/v1.1/servers_index.rng | 12 ++++++ 3 files changed, 68 insertions(+) create mode 100644 nova/api/openstack/schemas/v1.1/server.rng create mode 100644 nova/api/openstack/schemas/v1.1/servers.rng create mode 100644 nova/api/openstack/schemas/v1.1/servers_index.rng (limited to 'nova/api') diff --git a/nova/api/openstack/schemas/v1.1/server.rng b/nova/api/openstack/schemas/v1.1/server.rng new file mode 100644 index 000000000..dbd169a83 --- /dev/null +++ b/nova/api/openstack/schemas/v1.1/server.rng @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nova/api/openstack/schemas/v1.1/servers.rng b/nova/api/openstack/schemas/v1.1/servers.rng new file mode 100644 index 000000000..4e2bb8853 --- /dev/null +++ b/nova/api/openstack/schemas/v1.1/servers.rng @@ -0,0 +1,6 @@ + + + + + diff --git a/nova/api/openstack/schemas/v1.1/servers_index.rng b/nova/api/openstack/schemas/v1.1/servers_index.rng new file mode 100644 index 000000000..768f0912d --- /dev/null +++ b/nova/api/openstack/schemas/v1.1/servers_index.rng @@ -0,0 +1,12 @@ + + + + + + + + + + + -- cgit From 5366332a84b89bc5a056bd7f43e528a908e8d188 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Fri, 19 Aug 2011 13:15:42 -0700 Subject: incorporate feedback from brian waldon and brian lamar. Move associate/disassociate to server actions --- nova/api/openstack/contrib/floating_ips.py | 69 +++++++++++++++++++----------- 1 file changed, 45 insertions(+), 24 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 2f5fdd001..b305ebdcb 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -15,8 +15,9 @@ # 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 webob import exc +import webob +from nova import compute from nova import exception from nova import log as logging from nova import network @@ -71,7 +72,7 @@ class FloatingIPController(object): try: floating_ip = self.network_api.get_floating_ip(context, id) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + return faults.Fault(webob.exc.HTTPNotFound()) return _translate_floating_ip_view(floating_ip) @@ -110,40 +111,49 @@ class FloatingIPController(object): self.network_api.release_floating_ip(context, address=floating_ip['address']) - return exc.HTTPAccepted() + return webob.exc.HTTPAccepted() - def associate(self, req, id, body): - """PUT /floating_ips/{id}/associate fixed ip in body """ + def _get_ip_by_id(self, context, value): + """Checks that value is id and then returns its address.""" + return self.network_api.get_floating_ip(context, value)['address'] + + +class Floating_ips(extensions.ExtensionDescriptor): + def __init__(self): + self.compute_api = compute.API() + self.network_api = network.API() + super(Floating_ips, self).__init__() + + def _add_floating_ip(self, input_dict, req, instance_id): + """Associate floating_ip to an instance.""" context = req.environ['nova.context'] - floating_ip = self._get_ip_by_id(context, id) - fixed_ip = body['floating_ip']['fixed_ip'] + try: + address = input_dict['addFloatingIp']['address'] + except KeyError: + msg = _("Address not specified") + raise webob.exc.HTTPBadRequest(explanation=msg) - self.network_api.associate_floating_ip(context, - floating_ip, fixed_ip) + self.compute_api.associate_floating_ip(context, instance_id, address) - floating_ip = self.network_api.get_floating_ip(context, id) - return _translate_floating_ip_view(floating_ip) + return webob.Response(status_int=202) - def disassociate(self, req, id, body=None): - """PUT /floating_ips/{id}/disassociate """ + def _remove_floating_ip(self, input_dict, req, instance_id): + """Dissociate floating_ip from an instance.""" context = req.environ['nova.context'] - floating_ip = self.network_api.get_floating_ip(context, id) - address = floating_ip['address'] - # no-op if this ip is already disassociated + try: + address = input_dict['removeFloatingIp']['address'] + except KeyError: + msg = _("Address not specified") + raise webob.exc.HTTPBadRequest(explanation=msg) + + floating_ip = self.network_api.get_floating_ip_by_ip(context, address) if 'fixed_ip' in floating_ip: self.network_api.disassociate_floating_ip(context, address) - floating_ip = self.network_api.get_floating_ip(context, id) - - return _translate_floating_ip_view(floating_ip) - - def _get_ip_by_id(self, context, value): - """Checks that value is id and then returns its address.""" - return self.network_api.get_floating_ip(context, value)['address'] + return webob.Response(status_int=202) -class Floating_ips(extensions.ExtensionDescriptor): def get_name(self): return "Floating_ips" @@ -170,3 +180,14 @@ class Floating_ips(extensions.ExtensionDescriptor): resources.append(res) return resources + + def get_actions(self): + """Return the actions the extension adds, as required by contract.""" + actions = [ + extensions.ActionExtension("servers", "addFloatingIp", + self._add_floating_ip), + extensions.ActionExtension("servers", "removeFloatingIp", + self._remove_floating_ip), + ] + + return actions -- cgit From 468893c667c7ce6cddb9d62906dfcb807fcd6da1 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Fri, 19 Aug 2011 13:25:33 -0700 Subject: a few tweaks - remove unused member functions, add comment --- nova/api/openstack/contrib/floating_ips.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index b305ebdcb..3b400807a 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -80,6 +80,7 @@ class FloatingIPController(object): context = req.environ['nova.context'] try: + # FIXME - why does self.network_api.list_floating_ips raise this? floating_ips = self.network_api.list_floating_ips(context) except exception.FloatingIpNotFoundForProject: floating_ips = [] @@ -174,9 +175,7 @@ class Floating_ips(extensions.ExtensionDescriptor): res = extensions.ResourceExtension('os-floating-ips', FloatingIPController(), - member_actions={ - 'associate': 'PUT', - 'disassociate': 'PUT'}) + member_actions={}) resources.append(res) return resources -- cgit From ce4ac4be2b813a8f025a9f2891fbc1ed4101c496 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Fri, 19 Aug 2011 13:31:49 -0700 Subject: tweak to comment --- 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 3b400807a..0f27f2f27 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -80,7 +80,7 @@ class FloatingIPController(object): context = req.environ['nova.context'] try: - # FIXME - why does self.network_api.list_floating_ips raise this? + # FIXME(ja) - why does self.network_api.list_floating_ips raise? floating_ips = self.network_api.list_floating_ips(context) except exception.FloatingIpNotFoundForProject: floating_ips = [] -- cgit From 5f6cd490425d8d91870de1b4a492a6cb34502bcb Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Fri, 19 Aug 2011 16:36:20 -0400 Subject: Updated accessIPv4 and accessIPv6 to always be in a servers response --- nova/api/openstack/views/servers.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 8b3a1e221..d2c1b0ba1 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -144,10 +144,8 @@ class ViewBuilderV11(ViewBuilder): elif response['server']['status'] == "BUILD": response['server']['progress'] = 0 - if inst.get('access_ip_v4'): - response['server']['accessIPv4'] = inst['access_ip_v4'] - if inst.get('access_ip_v6'): - response['server']['accessIPv6'] = inst['access_ip_v6'] + response['server']['accessIPv4'] = inst.get('access_ip_v4') or "" + response['server']['accessIPv6'] = inst.get('access_ip_v6') or "" return response -- cgit From 9b65cdf0b2d5cc7ed7adcaca0dde4d6e2a10bf95 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Fri, 19 Aug 2011 14:16:57 -0700 Subject: better handle malformed input, and add associated tests --- nova/api/openstack/contrib/floating_ips.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 0f27f2f27..40086f778 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -131,6 +131,9 @@ class Floating_ips(extensions.ExtensionDescriptor): try: address = input_dict['addFloatingIp']['address'] + except TypeError: + msg = _("Missing parameter dict") + raise webob.exc.HTTPBadRequest(explanation=msg) except KeyError: msg = _("Address not specified") raise webob.exc.HTTPBadRequest(explanation=msg) @@ -145,6 +148,9 @@ class Floating_ips(extensions.ExtensionDescriptor): try: address = input_dict['removeFloatingIp']['address'] + except TypeError: + msg = _("Missing parameter dict") + raise webob.exc.HTTPBadRequest(explanation=msg) except KeyError: msg = _("Address not specified") raise webob.exc.HTTPBadRequest(explanation=msg) -- cgit From bb989133196744779527e36cba22a76bd44e533b Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Sat, 20 Aug 2011 15:38:13 -0700 Subject: add/remove security groups to/from the servers as server actions --- nova/api/openstack/contrib/security_groups.py | 248 +++++++++----------------- 1 file changed, 81 insertions(+), 167 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/security_groups.py b/nova/api/openstack/contrib/security_groups.py index a104a42e4..1fd64f3b8 100644 --- a/nova/api/openstack/contrib/security_groups.py +++ b/nova/api/openstack/contrib/security_groups.py @@ -168,135 +168,6 @@ class SecurityGroupController(object): "than 255 characters.") % typ raise exc.HTTPBadRequest(explanation=msg) - def associate(self, req, id, body): - context = req.environ['nova.context'] - - if not body: - raise exc.HTTPUnprocessableEntity() - - if not 'security_group_associate' in body: - raise exc.HTTPUnprocessableEntity() - - security_group = self._get_security_group(context, id) - - servers = body['security_group_associate'].get('servers') - - if not servers: - msg = _("No servers found") - return exc.HTTPBadRequest(explanation=msg) - - hosts = set() - for server in servers: - if server['id']: - try: - # check if the server exists - inst = db.instance_get(context, server['id']) - #check if the security group is assigned to the server - if self._is_security_group_associated_to_server( - security_group, inst['id']): - msg = _("Security group %s is already associated with" - " the instance %s") % (security_group['id'], - server['id']) - raise exc.HTTPBadRequest(explanation=msg) - - #check if the instance is in running state - if inst['state'] != power_state.RUNNING: - msg = _("Server %s is not in the running state")\ - % server['id'] - raise exc.HTTPBadRequest(explanation=msg) - - hosts.add(inst['host']) - except exception.InstanceNotFound as exp: - return exc.HTTPNotFound(explanation=unicode(exp)) - - # Associate security group with the server in the db - for server in servers: - if server['id']: - db.instance_add_security_group(context.elevated(), - server['id'], - security_group['id']) - - for host in hosts: - rpc.cast(context, - db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "refresh_security_group_rules", - "args": {"security_group_id": security_group['id']}}) - - return exc.HTTPAccepted() - - def _is_security_group_associated_to_server(self, security_group, - instance_id): - if not security_group: - return False - - instances = security_group.get('instances') - if not instances: - return False - - inst_id = None - for inst_id in (instance['id'] for instance in instances \ - if instance_id == instance['id']): - return True - - return False - - def disassociate(self, req, id, body): - context = req.environ['nova.context'] - - if not body: - raise exc.HTTPUnprocessableEntity() - - if not 'security_group_disassociate' in body: - raise exc.HTTPUnprocessableEntity() - - security_group = self._get_security_group(context, id) - - servers = body['security_group_disassociate'].get('servers') - - if not servers: - msg = _("No servers found") - return exc.HTTPBadRequest(explanation=msg) - - hosts = set() - for server in servers: - if server['id']: - try: - # check if the instance exists - inst = db.instance_get(context, server['id']) - # Check if the security group is not associated - # with the instance - if not self._is_security_group_associated_to_server( - security_group, inst['id']): - msg = _("Security group %s is not associated with the" - "instance %s") % (security_group['id'], - server['id']) - raise exc.HTTPBadRequest(explanation=msg) - - #check if the instance is in running state - if inst['state'] != power_state.RUNNING: - msg = _("Server %s is not in the running state")\ - % server['id'] - raise exp.HTTPBadRequest(explanation=msg) - - hosts.add(inst['host']) - except exception.InstanceNotFound as exp: - return exc.HTTPNotFound(explanation=unicode(exp)) - - # Disassociate security group from the server - for server in servers: - if server['id']: - db.instance_remove_security_group(context.elevated(), - server['id'], - security_group['id']) - - for host in hosts: - rpc.cast(context, - db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "refresh_security_group_rules", - "args": {"security_group_id": security_group['id']}}) - - return exc.HTTPAccepted() - class SecurityGroupRulesController(SecurityGroupController): @@ -461,6 +332,11 @@ class SecurityGroupRulesController(SecurityGroupController): class Security_groups(extensions.ExtensionDescriptor): + + def __init__(self): + self.compute_api = compute.API() + super(Security_groups, self).__init__() + def get_name(self): return "SecurityGroups" @@ -476,6 +352,82 @@ class Security_groups(extensions.ExtensionDescriptor): def get_updated(self): return "2011-07-21T00:00:00+00:00" + def _addSecurityGroup(self, input_dict, req, instance_id): + context = req.environ['nova.context'] + + try: + body = input_dict['addSecurityGroup'] + group_name = body['name'] + instance_id = int(instance_id) + except ValueError: + msg = _("Server id should be integer") + raise exc.HTTPBadRequest(explanation=msg) + except TypeError: + msg = _("Missing parameter dict") + raise webob.exc.HTTPBadRequest(explanation=msg) + except KeyError: + msg = _("Security group not specified") + raise webob.exc.HTTPBadRequest(explanation=msg) + + if not group_name or group_name.strip() == '': + msg = _("Security group name cannot be empty") + raise webob.exc.HTTPBadRequest(explanation=msg) + + try: + self.compute_api.add_security_group(context, instance_id, + group_name) + except exception.SecurityGroupNotFound as exp: + return exc.HTTPNotFound(explanation=unicode(exp)) + except exception.InstanceNotFound as exp: + return exc.HTTPNotFound(explanation=unicode(exp)) + except exception.Invalid as exp: + return exc.HTTPBadRequest(explanation=unicode(exp)) + + return exc.HTTPAccepted() + + def _removeSecurityGroup(self, input_dict, req, instance_id): + context = req.environ['nova.context'] + + try: + body = input_dict['removeSecurityGroup'] + group_name = body['name'] + instance_id = int(instance_id) + except ValueError: + msg = _("Server id should be integer") + raise exc.HTTPBadRequest(explanation=msg) + except TypeError: + msg = _("Missing parameter dict") + raise webob.exc.HTTPBadRequest(explanation=msg) + except KeyError: + msg = _("Security group not specified") + raise webob.exc.HTTPBadRequest(explanation=msg) + + if not group_name or group_name.strip() == '': + msg = _("Security group name cannot be empty") + raise webob.exc.HTTPBadRequest(explanation=msg) + + try: + self.compute_api.remove_security_group(context, instance_id, + group_name) + except exception.SecurityGroupNotFound as exp: + return exc.HTTPNotFound(explanation=unicode(exp)) + except exception.InstanceNotFound as exp: + return exc.HTTPNotFound(explanation=unicode(exp)) + except exception.Invalid as exp: + return exc.HTTPBadRequest(explanation=unicode(exp)) + + return exc.HTTPAccepted() + + def get_actions(self): + """Return the actions the extensions adds""" + actions = [ + extensions.ActionExtension("servers", "addSecurityGroup", + self._addSecurityGroup), + extensions.ActionExtension("servers", "removeSecurityGroup", + self._removeSecurityGroup) + ] + return actions + def get_resources(self): resources = [] @@ -493,10 +445,6 @@ class Security_groups(extensions.ExtensionDescriptor): res = extensions.ResourceExtension('os-security-groups', controller=SecurityGroupController(), - member_actions={ - 'associate': 'POST', - 'disassociate': 'POST' - }, deserializer=deserializer, serializer=serializer) @@ -534,40 +482,6 @@ class SecurityGroupXMLDeserializer(wsgi.MetadataXMLDeserializer): security_group['description'] = self.extract_text(desc_node) return {'body': {'security_group': security_group}} - def _get_servers(self, node): - servers_dict = {'servers': []} - if node is not None: - servers_node = self.find_first_child_named(node, - 'servers') - if servers_node is not None: - for server_node in self.find_children_named(servers_node, - "server"): - servers_dict['servers'].append( - {"id": self.extract_text(server_node)}) - return servers_dict - - def associate(self, string): - """Deserialize an xml-formatted security group associate request""" - dom = minidom.parseString(string) - node = self.find_first_child_named(dom, - 'security_group_associate') - result = {'body': {}} - if node: - result['body']['security_group_associate'] = \ - self._get_servers(node) - return result - - def disassociate(self, string): - """Deserialize an xml-formatted security group disassociate request""" - dom = minidom.parseString(string) - node = self.find_first_child_named(dom, - 'security_group_disassociate') - result = {'body': {}} - if node: - result['body']['security_group_disassociate'] = \ - self._get_servers(node) - return result - class SecurityGroupRulesXMLDeserializer(wsgi.MetadataXMLDeserializer): """ -- cgit