From 6cbd1d860d6a3fe96417391c21fb79b1750ecdcf Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Tue, 19 Jul 2011 15:35:54 -0700 Subject: Added a new extension instead of directly making changes to OS V1.1. API --- nova/api/openstack/contrib/createserverext.py | 234 ++++++++++++++++++++++++++ nova/api/openstack/create_instance_helper.py | 51 +----- nova/api/openstack/extensions.py | 9 +- nova/api/openstack/servers.py | 35 ++-- 4 files changed, 262 insertions(+), 67 deletions(-) create mode 100644 nova/api/openstack/contrib/createserverext.py (limited to 'nova/api') diff --git a/nova/api/openstack/contrib/createserverext.py b/nova/api/openstack/contrib/createserverext.py new file mode 100644 index 000000000..e8fe9afad --- /dev/null +++ b/nova/api/openstack/contrib/createserverext.py @@ -0,0 +1,234 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# +# 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 webob import exc + +from nova import exception +import nova.image +from nova import network +from nova import quota +from nova import rpc + +from nova.api.openstack import create_instance_helper as helper +from nova.api.openstack import extensions +from nova.api.openstack import faults +from nova.compute import instance_types +from nova.api.openstack import servers +from nova.api.openstack import wsgi +from nova.auth import manager as auth_manager + + +class CreateInstanceHelperEx(helper.CreateInstanceHelper): + def __init__(self, controller): + super(CreateInstanceHelperEx, self).__init__(controller) + + def create_instance(self, req, body, create_method): + """Creates a new server for the given user as per + the network information if it is provided + """ + if not body: + raise faults.Fault(exc.HTTPUnprocessableEntity()) + + context = req.environ['nova.context'] + + password = self.controller._get_server_admin_password(body['server']) + + key_name = None + key_data = None + key_pairs = auth_manager.AuthManager.get_key_pairs(context) + if key_pairs: + key_pair = key_pairs[0] + key_name = key_pair['name'] + key_data = key_pair['public_key'] + + image_href = self.controller._image_ref_from_req_data(body) + try: + image_service, image_id = nova.image.get_image_service(image_href) + kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image( + req, image_id) + images = set([str(x['id']) for x in image_service.index(context)]) + assert str(image_id) in images + except Exception, e: + msg = _("Cannot find requested image %(image_href)s: %(e)s" % + locals()) + raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + + personality = body['server'].get('personality') + + injected_files = [] + if personality: + injected_files = self._get_injected_files(personality) + + requested_networks = body['server'].get('networks') + + if requested_networks is not None: + requested_networks = self._get_requested_networks( + requested_networks) + + flavor_id = self.controller._flavor_id_from_req_data(body) + + if not 'name' in body['server']: + msg = _("Server name is not defined") + raise exc.HTTPBadRequest(explanation=msg) + + zone_blob = body['server'].get('blob') + name = body['server']['name'] + self._validate_server_name(name) + name = name.strip() + + reservation_id = body['server'].get('reservation_id') + min_count = body['server'].get('min_count') + max_count = body['server'].get('max_count') + # min_count and max_count are optional. If they exist, they come + # in as strings. We want to default 'min_count' to 1, and default + # 'max_count' to be 'min_count'. + min_count = int(min_count) if min_count else 1 + max_count = int(max_count) if max_count else min_count + if min_count > max_count: + min_count = max_count + + try: + inst_type = \ + instance_types.get_instance_type_by_flavor_id(flavor_id) + extra_values = { + 'instance_type': inst_type, + 'image_ref': image_href, + 'password': password} + + return (extra_values, + create_method(context, + inst_type, + image_id, + kernel_id=kernel_id, + ramdisk_id=ramdisk_id, + display_name=name, + display_description=name, + key_name=key_name, + key_data=key_data, + metadata=body['server'].get('metadata', {}), + injected_files=injected_files, + admin_password=password, + zone_blob=zone_blob, + reservation_id=reservation_id, + min_count=min_count, + max_count=max_count, + requested_networks=requested_networks)) + except quota.QuotaError as error: + self._handle_quota_error(error) + except exception.ImageNotFound as error: + msg = _("Can not find requested image") + raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + except rpc.RemoteError as err: + msg = "%(err_type)s: %(err_msg)s" % \ + {'err_type': err.exc_type, 'err_msg': err.value} + raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + + # Let the caller deal with unhandled exceptions. + + def _get_requested_networks(self, requested_networks): + """ + Create a list of requested networks from the networks attribute + """ + networks = [] + for network in requested_networks: + try: + network_id = network['id'] + network_id = int(network_id) + #fixed IP address is optional + #if the fixed IP address is not provided then + #it will use one of the available IP address from the network + fixed_ip = network.get('fixed_ip', None) + if fixed_ip is not None: + self._validate_fixed_ip(fixed_ip) + # check if the network id is already present in the list, + # we don't want duplicate networks to be passed + # at the boot time + for id, ip in networks: + if id == network_id: + expl = _("Duplicate networks (%s) are not allowed")\ + % network_id + raise faults.Fault(exc.HTTPBadRequest( + explanation=expl)) + + networks.append((network_id, fixed_ip)) + except KeyError as key: + expl = _('Bad network format: missing %s') % key + raise faults.Fault(exc.HTTPBadRequest(explanation=expl)) + except ValueError: + expl = _("Bad networks format: network id should " + "be integer (%s)") % network_id + raise faults.Fault(exc.HTTPBadRequest(explanation=expl)) + except TypeError: + expl = _('Bad networks format') + raise exc.HTTPBadRequest(explanation=expl) + + return networks + + +class CreateServerExtController(servers.ControllerV11): + """This is the controller for the extended version + of the create server OS V1.1 + """ + def __init__(self): + super(CreateServerExtController, self).__init__() + self.helper = CreateInstanceHelperEx(self) + + +class Createserverext(extensions.ExtensionDescriptor): + """The servers create ext + + Exposes addFixedIp and removeFixedIp actions on servers. + + """ + def get_name(self): + return "Createserverext" + + def get_alias(self): + return "os-servers-create-ext" + + def get_description(self): + return "Extended support to the Create Server v1.1 API" + + def get_namespace(self): + return "http://docs.openstack.org/ext/serverscreateext/api/v1.1" + + def get_updated(self): + return "2011-07-19T00:00:00+00:00" + + def get_resources(self): + resources = [] + + headers_serializer = servers.HeadersSerializer() + metadata = servers._get_metadata() + body_serializers = { + 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, + xmlns=wsgi.XMLNS_V11), + } + + body_deserializers = { + 'application/xml': helper.ServerXMLDeserializer(), + } + + serializer = wsgi.ResponseSerializer(body_serializers, + headers_serializer) + deserializer = wsgi.RequestDeserializer(body_deserializers) + + res = extensions.ResourceExtension('os-servers-create-ext', + controller=CreateServerExtController(), + deserializer=deserializer, + serializer=serializer) + resources.append(res) + + return resources diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 32b1b2f7c..8579c45df 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -25,7 +25,6 @@ from nova import flags from nova import log as logging import nova.image from nova import quota -from nova import rpc from nova import utils from nova.compute import instance_types @@ -102,12 +101,6 @@ class CreateInstanceHelper(object): if personality: injected_files = self._get_injected_files(personality) - requested_networks = body['server'].get('networks') - - if requested_networks is not None: - requested_networks = self._get_requested_networks( - requested_networks) - flavor_id = self.controller._flavor_id_from_req_data(body) if not 'name' in body['server']: @@ -154,17 +147,12 @@ class CreateInstanceHelper(object): zone_blob=zone_blob, reservation_id=reservation_id, min_count=min_count, - max_count=max_count, - requested_networks=requested_networks)) + max_count=max_count)) except quota.QuotaError as error: self._handle_quota_error(error) except exception.ImageNotFound as error: msg = _("Can not find requested image") raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) - except rpc.RemoteError as err: - msg = "%(err_type)s: %(err_msg)s" % \ - {'err_type': err.exc_type, 'err_msg': err.value} - raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) # Let the caller deal with unhandled exceptions. @@ -296,42 +284,6 @@ class CreateInstanceHelper(object): raise exc.HTTPBadRequest(explanation=msg) return password - def _get_requested_networks(self, requested_networks): - """ - Create a list of requested networks from the networks attribute - """ - networks = [] - for network in requested_networks: - try: - network_id = network['id'] - network_id = int(network_id) - #fixed IP address is optional - #if the fixed IP address is not provided then - #it will use one of the available IP address from the network - fixed_ip = network.get('fixed_ip', None) - if fixed_ip is not None: - self._validate_fixed_ip(fixed_ip) - # check if the network id is already present in the list, - # we don't want duplicate networks to be passed - # at the boot time - for id, ip in networks: - if id == network_id: - expl = _("Duplicate networks (%s) are not allowed")\ - % network_id - raise faults.Fault(exc.HTTPBadRequest( - explanation=expl)) - - networks.append((network_id, fixed_ip)) - except KeyError as key: - expl = _('Bad network format: missing %s') % key - raise faults.Fault(exc.HTTPBadRequest(explanation=expl)) - except ValueError: - expl = _("Bad networks format: network id should " - "be integer (%s)") % network_id - raise faults.Fault(exc.HTTPBadRequest(explanation=expl)) - - return networks - class ServerXMLDeserializer(wsgi.XMLDeserializer): """ @@ -411,7 +363,6 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): def _find_first_child_named(self, parent, name): """Search a nodes children for the first child with a given name""" for node in parent.childNodes: - LOG.debug(node.nodeName) if node.nodeName == name: return node return None diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index da06ecd15..dec66d8a4 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -263,7 +263,9 @@ class ExtensionMiddleware(base_wsgi.Middleware): LOG.debug(_('Extended resource: %s'), resource.collection) mapper.resource(resource.collection, resource.collection, - controller=wsgi.Resource(resource.controller), + controller=wsgi.Resource(resource.controller, + resource.deserializer, + resource.serializer), collection=resource.collection_actions, member=resource.member_actions, parent_resource=resource.parent) @@ -456,9 +458,12 @@ 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 diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 93f8e832c..6a28f7bf1 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -618,21 +618,7 @@ def create_resource(version='1.0'): '1.1': ControllerV11, }[version]() - metadata = { - "attributes": { - "server": ["id", "imageId", "name", "flavorId", "hostId", - "status", "progress", "adminPass", "flavorRef", - "imageRef"], - "link": ["rel", "type", "href"], - }, - "dict_collections": { - "metadata": {"item_name": "meta", "item_key": "key"}, - }, - "list_collections": { - "public": {"item_name": "ip", "item_key": "addr"}, - "private": {"item_name": "ip", "item_key": "addr"}, - }, - } + metadata = _get_metadata() xmlns = { '1.0': wsgi.XMLNS_V10, @@ -654,3 +640,22 @@ def create_resource(version='1.0'): deserializer = wsgi.RequestDeserializer(body_deserializers) return wsgi.Resource(controller, deserializer, serializer) + + +def _get_metadata(): + metadata = { + "attributes": { + "server": ["id", "imageId", "name", "flavorId", "hostId", + "status", "progress", "adminPass", "flavorRef", + "imageRef"], + "link": ["rel", "type", "href"], + }, + "dict_collections": { + "metadata": {"item_name": "meta", "item_key": "key"}, + }, + "list_collections": { + "public": {"item_name": "ip", "item_key": "addr"}, + "private": {"item_name": "ip", "item_key": "addr"}, + }, + } + return metadata -- cgit