From c9b4bf8f3eb3bdb51b51b98b6f283415229c2e0e Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 30 May 2011 11:02:55 -0700 Subject: first pass at reservation id support --- nova/api/openstack/zones.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'nova') diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index 29b7b2279..7b495cecf 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -114,6 +114,13 @@ class Controller(common.OpenstackController): zone = api.zone_update(context, zone_id, env["zone"]) return dict(zone=_scrub_zone(zone)) + def boot(self, req): + """Creates a new server for a given user while being Zone aware.""" + reservation_id = \ + common.create(req, self.compute_api.create_all_at_once) + + return {'reservation': {'reservation_id': reservation_id}} + @check_encryption_key def select(self, req): """Returns a weighted list of costs to create instances -- cgit From d428a8e4f9dc5291cae105e13a02e993cca19350 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 30 May 2011 15:38:29 -0700 Subject: regular boot working again --- nova/api/openstack/__init__.py | 7 +- nova/api/openstack/common.py | 3 +- nova/api/openstack/servers.py | 233 ++--------------------------------------- nova/api/openstack/zones.py | 22 +++- nova/compute/api.py | 20 ++-- 5 files changed, 47 insertions(+), 238 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index ce9e0b7ed..6862abd39 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -98,8 +98,11 @@ class APIRouter(wsgi.Router): server_members['inject_network_info'] = 'POST' mapper.resource("zone", "zones", controller=zones.Controller(), - collection={'detail': 'GET', 'info': 'GET', - 'select': 'POST'}) + collection={'detail': 'GET', + 'info': 'GET', + 'select': 'POST', + 'boot': 'POST' + }) mapper.resource("user", "users", controller=users.Controller(), collection={'detail': 'GET'}) diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 32cd689ca..32a948f2d 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -23,12 +23,11 @@ import webob from nova import exception from nova import flags from nova import log as logging +from nova import utils from nova import wsgi LOG = logging.getLogger('nova.api.openstack.common') - - FLAGS = flags.FLAGS diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 0ffb66763..cbf284d60 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -23,16 +23,15 @@ from nova import compute from nova import exception from nova import flags from nova import log as logging -from nova import quota from nova import utils from nova.api.openstack import common +from nova.api.openstack import create_instance_controller as controller from nova.api.openstack import faults import nova.api.openstack.views.addresses import nova.api.openstack.views.flavors import nova.api.openstack.views.images import nova.api.openstack.views.servers from nova.auth import manager as auth_manager -from nova.compute import instance_types import nova.api.openstack from nova.scheduler import api as scheduler_api @@ -41,7 +40,7 @@ LOG = logging.getLogger('nova.api.openstack.servers') FLAGS = flags.FLAGS -class Controller(common.OpenstackController): +class Controller(controller.OpenstackCreateInstanceController): """ The Server API controller for the OpenStack API """ _serialization_metadata = { @@ -64,7 +63,6 @@ class Controller(common.OpenstackController): def __init__(self): self.compute_api = compute.API() - self._image_service = utils.import_object(FLAGS.image_service) super(Controller, self).__init__() def index(self, req): @@ -124,89 +122,18 @@ class Controller(common.OpenstackController): def create(self, req): """ Creates a new server for a given user """ - env = self._deserialize_create(req) - if not env: - return faults.Fault(exc.HTTPUnprocessableEntity()) - - context = req.environ['nova.context'] - - password = self._get_server_admin_password(env['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'] - - requested_image_id = self._image_id_from_req_data(env) - try: - image_id = common.get_image_id_from_image_hash(self._image_service, - context, requested_image_id) - except: - msg = _("Can not find requested image") - return faults.Fault(exc.HTTPBadRequest(msg)) - - kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image( - req, image_id) - - personality = env['server'].get('personality') - injected_files = [] - if personality: - injected_files = self._get_injected_files(personality) - - flavor_id = self._flavor_id_from_req_data(env) - - if not 'name' in env['server']: - msg = _("Server name is not defined") - return exc.HTTPBadRequest(msg) - - zone_blob = env['server'].get('blob') - name = env['server']['name'] - self._validate_server_name(name) - name = name.strip() - - try: - inst_type = \ - instance_types.get_instance_type_by_flavor_id(flavor_id) - (inst,) = self.compute_api.create( - 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=env['server'].get('metadata', {}), - injected_files=injected_files, - admin_password=password, - zone_blob=zone_blob) - except quota.QuotaError as error: - self._handle_quota_error(error) - - inst['instance_type'] = inst_type - inst['image_id'] = requested_image_id + extra_values, instances = \ + self.create_instance(req, self.compute_api.create) + (inst, ) = instances + for key in ['instance_type', 'image_id']: + inst[key] = extra_values[key] + builder = self._get_view_builder(req) server = builder.build(inst, is_detail=True) - server['server']['adminPass'] = password + server['server']['adminPass'] = extra_values['password'] return server - def _deserialize_create(self, request): - """ - Deserialize a create request - - Overrides normal behavior in the case of xml content - """ - if request.content_type == "application/xml": - deserializer = ServerCreateRequestXMLDeserializer() - return deserializer.deserialize(request.body) - else: - return self._deserialize(request.body, request.get_content_type()) - def _get_injected_files(self, personality): """ Create a list of injected files from the personality attribute @@ -235,22 +162,6 @@ class Controller(common.OpenstackController): injected_files.append((path, contents)) return injected_files - def _handle_quota_error(self, error): - """ - Reraise quota errors as api-specific http exceptions - """ - if error.code == "OnsetFileLimitExceeded": - expl = _("Personality file limit exceeded") - raise exc.HTTPBadRequest(explanation=expl) - if error.code == "OnsetFilePathLimitExceeded": - expl = _("Personality file path too long") - raise exc.HTTPBadRequest(explanation=expl) - if error.code == "OnsetFileContentLimitExceeded": - expl = _("Personality file content too long") - raise exc.HTTPBadRequest(explanation=expl) - # if the original error is okay, just reraise it - raise error - def _get_server_admin_password(self, server): """ Determine the admin password for a server on creation """ return utils.generate_password(16) @@ -552,45 +463,6 @@ class Controller(common.OpenstackController): error=item.error)) return dict(actions=actions) - def _get_kernel_ramdisk_from_image(self, req, image_id): - """Fetch an image from the ImageService, then if present, return the - associated kernel and ramdisk image IDs. - """ - context = req.environ['nova.context'] - image_meta = self._image_service.show(context, image_id) - # NOTE(sirp): extracted to a separate method to aid unit-testing, the - # new method doesn't need a request obj or an ImageService stub - kernel_id, ramdisk_id = self._do_get_kernel_ramdisk_from_image( - image_meta) - return kernel_id, ramdisk_id - - @staticmethod - def _do_get_kernel_ramdisk_from_image(image_meta): - """Given an ImageService image_meta, return kernel and ramdisk image - ids if present. - - This is only valid for `ami` style images. - """ - image_id = image_meta['id'] - if image_meta['status'] != 'active': - raise exception.ImageUnacceptable(image_id=image_id, - reason=_("status is not active")) - - if image_meta.get('container_format') != 'ami': - return None, None - - try: - kernel_id = image_meta['properties']['kernel_id'] - except KeyError: - raise exception.KernelNotFoundForImage(image_id=image_id) - - try: - ramdisk_id = image_meta['properties']['ramdisk_id'] - except KeyError: - raise exception.RamdiskNotFoundForImage(image_id=image_id) - - return kernel_id, ramdisk_id - class ControllerV10(Controller): def _image_id_from_req_data(self, data): @@ -727,92 +599,5 @@ class ControllerV11(Controller): response.empty_body = True return response - def _get_server_admin_password(self, server): - """ Determine the admin password for a server on creation """ - password = server.get('adminPass') - if password is None: - return utils.generate_password(16) - if not isinstance(password, basestring) or password == '': - msg = _("Invalid adminPass") - raise exc.HTTPBadRequest(msg) - return password - def get_default_xmlns(self, req): return common.XML_NS_V11 - - -class ServerCreateRequestXMLDeserializer(object): - """ - Deserializer to handle xml-formatted server create requests. - - Handles standard server attributes as well as optional metadata - and personality attributes - """ - - def deserialize(self, string): - """Deserialize an xml-formatted server create request""" - dom = minidom.parseString(string) - server = self._extract_server(dom) - return {'server': server} - - def _extract_server(self, node): - """Marshal the server attribute of a parsed request""" - server = {} - server_node = self._find_first_child_named(node, 'server') - for attr in ["name", "imageId", "flavorId", "imageRef", "flavorRef"]: - if server_node.getAttribute(attr): - server[attr] = server_node.getAttribute(attr) - metadata = self._extract_metadata(server_node) - if metadata is not None: - server["metadata"] = metadata - personality = self._extract_personality(server_node) - if personality is not None: - server["personality"] = personality - return server - - def _extract_metadata(self, server_node): - """Marshal the metadata attribute of a parsed request""" - metadata_node = self._find_first_child_named(server_node, "metadata") - if metadata_node is None: - return None - metadata = {} - for meta_node in self._find_children_named(metadata_node, "meta"): - key = meta_node.getAttribute("key") - metadata[key] = self._extract_text(meta_node) - return metadata - - def _extract_personality(self, server_node): - """Marshal the personality attribute of a parsed request""" - personality_node = \ - self._find_first_child_named(server_node, "personality") - if personality_node is None: - return None - personality = [] - for file_node in self._find_children_named(personality_node, "file"): - item = {} - if file_node.hasAttribute("path"): - item["path"] = file_node.getAttribute("path") - item["contents"] = self._extract_text(file_node) - personality.append(item) - return personality - - 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: - if node.nodeName == name: - return node - return None - - def _find_children_named(self, parent, name): - """Return all of a nodes children who have the given name""" - for node in parent.childNodes: - if node.nodeName == name: - yield node - - def _extract_text(self, node): - """Get the text field contained by the given node""" - if len(node.childNodes) == 1: - child = node.childNodes[0] - if child.nodeType == child.TEXT_NODE: - return child.nodeValue - return "" diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index 7b495cecf..51ce315dc 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -21,7 +21,7 @@ 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 create_instance_controller as controller from nova.scheduler import api @@ -57,7 +57,7 @@ def check_encryption_key(func): return wrapped -class Controller(common.OpenstackController): +class Controller(controller.OpenstackCreateInstanceController): _serialization_metadata = { 'application/xml': { @@ -97,17 +97,20 @@ class Controller(common.OpenstackController): return dict(zone=_scrub_zone(zone)) def delete(self, req, id): + """Delete a child zone entry.""" zone_id = int(id) api.zone_delete(req.environ['nova.context'], zone_id) return {} def create(self, req): + """Create a child zone entry.""" context = req.environ['nova.context'] env = self._deserialize(req.body, req.get_content_type()) zone = api.zone_create(context, env["zone"]) return dict(zone=_scrub_zone(zone)) def update(self, req, id): + """Update a child zone entry.""" context = req.environ['nova.context'] env = self._deserialize(req.body, req.get_content_type()) zone_id = int(id) @@ -115,11 +118,14 @@ class Controller(common.OpenstackController): return dict(zone=_scrub_zone(zone)) def boot(self, req): - """Creates a new server for a given user while being Zone aware.""" + """Creates a new server for a given user while being Zone aware. + + Returns a reservation ID (a UUID). + """ reservation_id = \ common.create(req, self.compute_api.create_all_at_once) - return {'reservation': {'reservation_id': reservation_id}} + return {'reservation_id': reservation_id} @check_encryption_key def select(self, req): @@ -144,3 +150,11 @@ class Controller(common.OpenstackController): cooked.append(dict(weight=entry['weight'], blob=cipher_text)) return cooked + + # Assume OS 1.0 functionality for these overrides. + + def _image_id_from_req_data(self, data): + return data['server']['imageId'] + + def _flavor_id_from_req_data(self, data): + return data['server']['flavorId'] diff --git a/nova/compute/api.py b/nova/compute/api.py index 52cff3d56..fc369ccd2 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -140,7 +140,8 @@ class API(base.Base): display_name='', display_description='', key_name=None, key_data=None, security_group='default', availability_zone=None, user_data=None, metadata={}, - injected_files=None, admin_password=None, zone_blob=None): + injected_files=None, admin_password=None, zone_blob=None, + reservation_id=None): """Verify all the input parameters regardless of the provisioning strategy being performed.""" @@ -205,8 +206,11 @@ class API(base.Base): key_pair = db.key_pair_get(context, context.user_id, key_name) key_data = key_pair['public_key'] + if reservation_id is None: + reservation_id = utils.generate_uid('r') + base_options = { - 'reservation_id': utils.generate_uid('r'), + 'reservation_id': reservation_id, 'image_id': image_id, 'kernel_id': kernel_id or '', 'ramdisk_id': ramdisk_id or '', @@ -305,7 +309,8 @@ class API(base.Base): display_name='', display_description='', key_name=None, key_data=None, security_group='default', availability_zone=None, user_data=None, metadata={}, - injected_files=None, admin_password=None, zone_blob=None): + injected_files=None, admin_password=None, zone_blob=None, + reservation_id=None): """Provision the instances by passing the whole request to the Scheduler for execution. Returns a Reservation ID related to the creation of all of these instances.""" @@ -317,7 +322,8 @@ class API(base.Base): display_name, display_description, key_name, key_data, security_group, availability_zone, user_data, metadata, - injected_files, admin_password, zone_blob) + injected_files, admin_password, zone_blob, + reservation_id) self._ask_scheduler_to_create_instance(context, base_options, instance_type, zone_blob, @@ -333,7 +339,8 @@ class API(base.Base): display_name='', display_description='', key_name=None, key_data=None, security_group='default', availability_zone=None, user_data=None, metadata={}, - injected_files=None, admin_password=None, zone_blob=None): + injected_files=None, admin_password=None, zone_blob=None, + reservation_id=None): """ Provision the instances by sending off a series of single instance requests to the Schedulers. This is fine for trival @@ -351,7 +358,8 @@ class API(base.Base): display_name, display_description, key_name, key_data, security_group, availability_zone, user_data, metadata, - injected_files, admin_password, zone_blob) + injected_files, admin_password, zone_blob, + reservation_id) instances = [] LOG.debug(_("Going to run %s instances..."), num_instances) -- cgit From 318e307c268bb554d24ba441b2484790f2a08798 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 30 May 2011 15:38:45 -0700 Subject: regular boot working again --- nova/api/openstack/create_instance_controller.py | 291 +++++++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 nova/api/openstack/create_instance_controller.py (limited to 'nova') diff --git a/nova/api/openstack/create_instance_controller.py b/nova/api/openstack/create_instance_controller.py new file mode 100644 index 000000000..52c1e444e --- /dev/null +++ b/nova/api/openstack/create_instance_controller.py @@ -0,0 +1,291 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 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. + +import re +from urlparse import urlparse +from webob import exc +from xml.dom import minidom + +import webob + +from nova import exception +from nova import flags +from nova import log as logging +from nova import quota +from nova import utils +from nova import wsgi + +from nova.compute import instance_types +from nova.api.openstack import common +from nova.api.openstack import faults +from nova.auth import manager as auth_manager + + +LOG = logging.getLogger('nova.api.openstack.create_instance_controller') +FLAGS = flags.FLAGS + + +class OpenstackCreateInstanceController(common.OpenstackController): + """This is the base class for OS API Controllers that + are capable of creating instances (currently Servers and Zones). + + Once we stabilize the Zones portion of the API we may be able + to move this code back into servers.py + """ + + def __init__(self): + """We need the image service to create an instance.""" + self._image_service = utils.import_object(FLAGS.image_service) + super(OpenstackCreateInstanceController, self).__init__() + + def create_instance(self, req, create_method): + """Creates a new server for the given user. The approach + used depends on the create_method. For example, the standard + POST /server call uses compute.api.create(), while + POST /zones/server uses compute.api.create_all_at_once(). + + The problem is, both approaches return different values (i.e. + [instance dicts] vs. reservation_id). So the handling of the + return type from this method is left to the caller. + """ + env = self._deserialize_create(req) + if not env: + return faults.Fault(exc.HTTPUnprocessableEntity()) + + context = req.environ['nova.context'] + + password = self._get_server_admin_password(env['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'] + + requested_image_id = self._image_id_from_req_data(env) + try: + image_id = common.get_image_id_from_image_hash(self._image_service, + context, requested_image_id) + except: + msg = _("Can not find requested image") + return faults.Fault(exc.HTTPBadRequest(msg)) + + kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image( + req, image_id) + + personality = env['server'].get('personality') + injected_files = [] + if personality: + injected_files = self._get_injected_files(personality) + + flavor_id = self._flavor_id_from_req_data(env) + + if not 'name' in env['server']: + msg = _("Server name is not defined") + return exc.HTTPBadRequest(msg) + + zone_blob = env['server'].get('blob') + reservation_id = env['server'].get('reservation_id') + name = env['server']['name'] + self._validate_server_name(name) + name = name.strip() + + inst_type = instance_types.get_instance_type_by_flavor_id(flavor_id) + extra_values = { + 'instance_type': inst_type, + 'image_id': requested_image_id, + 'password': password + } + + try: + 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=env['server'].get('metadata', {}), + injected_files=injected_files, + admin_password=password, + zone_blob=zone_blob, + reservation_id=reservation_id + ) + ) + except quota.QuotaError as error: + self._handle_quota_error(error) + + # Let the caller deal with unhandled exceptions. + + def _handle_quota_error(self, error): + """ + Reraise quota errors as api-specific http exceptions + """ + if error.code == "OnsetFileLimitExceeded": + expl = _("Personality file limit exceeded") + raise exc.HTTPBadRequest(explanation=expl) + if error.code == "OnsetFilePathLimitExceeded": + expl = _("Personality file path too long") + raise exc.HTTPBadRequest(explanation=expl) + if error.code == "OnsetFileContentLimitExceeded": + expl = _("Personality file content too long") + raise exc.HTTPBadRequest(explanation=expl) + # if the original error is okay, just reraise it + raise error + + def _deserialize_create(self, request): + """ + Deserialize a create request + + Overrides normal behavior in the case of xml content + """ + if request.content_type == "application/xml": + deserializer = ServerCreateRequestXMLDeserializer() + return deserializer.deserialize(request.body) + else: + return self._deserialize(request.body, request.get_content_type()) + + def _get_server_admin_password(self, server): + """ Determine the admin password for a server on creation """ + password = server.get('adminPass') + if password is None: + return utils.generate_password(16) + if not isinstance(password, basestring) or password == '': + msg = _("Invalid adminPass") + raise exc.HTTPBadRequest(msg) + return password + + def _get_kernel_ramdisk_from_image(self, req, image_id): + """Fetch an image from the ImageService, then if present, return the + associated kernel and ramdisk image IDs. + """ + context = req.environ['nova.context'] + image_meta = self._image_service.show(context, image_id) + # NOTE(sirp): extracted to a separate method to aid unit-testing, the + # new method doesn't need a request obj or an ImageService stub + kernel_id, ramdisk_id = self._do_get_kernel_ramdisk_from_image( + image_meta) + return kernel_id, ramdisk_id + + @staticmethod + def _do_get_kernel_ramdisk_from_image(image_meta): + """Given an ImageService image_meta, return kernel and ramdisk image + ids if present. + + This is only valid for `ami` style images. + """ + image_id = image_meta['id'] + if image_meta['status'] != 'active': + raise exception.ImageUnacceptable(image_id=image_id, + reason=_("status is not active")) + + if image_meta.get('container_format') != 'ami': + return None, None + + try: + kernel_id = image_meta['properties']['kernel_id'] + except KeyError: + raise exception.KernelNotFoundForImage(image_id=image_id) + + try: + ramdisk_id = image_meta['properties']['ramdisk_id'] + except KeyError: + raise exception.RamdiskNotFoundForImage(image_id=image_id) + + return kernel_id, ramdisk_id + + +class ServerCreateRequestXMLDeserializer(object): + """ + Deserializer to handle xml-formatted server create requests. + + Handles standard server attributes as well as optional metadata + and personality attributes + """ + + def deserialize(self, string): + """Deserialize an xml-formatted server create request""" + dom = minidom.parseString(string) + server = self._extract_server(dom) + return {'server': server} + + def _extract_server(self, node): + """Marshal the server attribute of a parsed request""" + server = {} + server_node = self._find_first_child_named(node, 'server') + for attr in ["name", "imageId", "flavorId", "imageRef", "flavorRef"]: + if server_node.getAttribute(attr): + server[attr] = server_node.getAttribute(attr) + metadata = self._extract_metadata(server_node) + if metadata is not None: + server["metadata"] = metadata + personality = self._extract_personality(server_node) + if personality is not None: + server["personality"] = personality + return server + + def _extract_metadata(self, server_node): + """Marshal the metadata attribute of a parsed request""" + metadata_node = self._find_first_child_named(server_node, "metadata") + if metadata_node is None: + return None + metadata = {} + for meta_node in self._find_children_named(metadata_node, "meta"): + key = meta_node.getAttribute("key") + metadata[key] = self._extract_text(meta_node) + return metadata + + def _extract_personality(self, server_node): + """Marshal the personality attribute of a parsed request""" + personality_node = \ + self._find_first_child_named(server_node, "personality") + if personality_node is None: + return None + personality = [] + for file_node in self._find_children_named(personality_node, "file"): + item = {} + if file_node.hasAttribute("path"): + item["path"] = file_node.getAttribute("path") + item["contents"] = self._extract_text(file_node) + personality.append(item) + return personality + + 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: + if node.nodeName == name: + return node + return None + + def _find_children_named(self, parent, name): + """Return all of a nodes children who have the given name""" + for node in parent.childNodes: + if node.nodeName == name: + yield node + + def _extract_text(self, node): + """Get the text field contained by the given node""" + if len(node.childNodes) == 1: + child = node.childNodes[0] + if child.nodeType == child.TEXT_NODE: + return child.nodeValue + return "" -- cgit From 544ec189a7fddc4b4491774b62071a4884e8e895 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 30 May 2011 16:18:11 -0700 Subject: zone-boot working --- nova/api/openstack/create_instance_controller.py | 15 +++++++++++++++ nova/api/openstack/servers.py | 15 --------------- nova/api/openstack/zones.py | 12 +++++++++--- 3 files changed, 24 insertions(+), 18 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_controller.py b/nova/api/openstack/create_instance_controller.py index 52c1e444e..1c4098a08 100644 --- a/nova/api/openstack/create_instance_controller.py +++ b/nova/api/openstack/create_instance_controller.py @@ -52,6 +52,12 @@ class OpenstackCreateInstanceController(common.OpenstackController): self._image_service = utils.import_object(FLAGS.image_service) super(OpenstackCreateInstanceController, self).__init__() + def _image_id_from_req_data(self, data): + raise NotImplementedError() + + def _flavor_id_from_req_data(self, data): + raise NotImplementedError() + def create_instance(self, req, create_method): """Creates a new server for the given user. The approach used depends on the create_method. For example, the standard @@ -164,6 +170,15 @@ class OpenstackCreateInstanceController(common.OpenstackController): else: return self._deserialize(request.body, request.get_content_type()) + def _validate_server_name(self, value): + if not isinstance(value, basestring): + msg = _("Server name is not a string or unicode") + raise exc.HTTPBadRequest(msg) + + if value.strip() == '': + msg = _("Server name is an empty string") + raise exc.HTTPBadRequest(msg) + def _get_server_admin_password(self, server): """ Determine the admin password for a server on creation """ password = server.get('adminPass') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index cbf284d60..6e86c2956 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -73,12 +73,6 @@ class Controller(controller.OpenstackCreateInstanceController): """ Returns a list of server details for a given user """ return self._items(req, is_detail=True) - def _image_id_from_req_data(self, data): - raise NotImplementedError() - - def _flavor_id_from_req_data(self, data): - raise NotImplementedError() - def _get_view_builder(self, req): raise NotImplementedError() @@ -193,15 +187,6 @@ class Controller(controller.OpenstackCreateInstanceController): return exc.HTTPNoContent() - def _validate_server_name(self, value): - if not isinstance(value, basestring): - msg = _("Server name is not a string or unicode") - raise exc.HTTPBadRequest(msg) - - if value.strip() == '': - msg = _("Server name is an empty string") - raise exc.HTTPBadRequest(msg) - def _parse_update(self, context, id, inst_dict, update_dict): pass diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index 51ce315dc..91531aa97 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -21,9 +21,11 @@ from nova import db from nova import exception from nova import flags from nova import log as logging -from nova.api.openstack import create_instance_controller as controller + +from nova.compute import api as compute from nova.scheduler import api +from nova.api.openstack import create_instance_controller as controller FLAGS = flags.FLAGS @@ -64,6 +66,10 @@ class Controller(controller.OpenstackCreateInstanceController): "attributes": { "zone": ["id", "api_url", "name", "capabilities"]}}} + def __init__(self): + self.compute_api = compute.API() + super(Controller, self).__init__() + def index(self, req): """Return all zones in brief""" # Ask the ZoneManager in the Scheduler for most recent data, @@ -122,8 +128,8 @@ class Controller(controller.OpenstackCreateInstanceController): Returns a reservation ID (a UUID). """ - reservation_id = \ - common.create(req, self.compute_api.create_all_at_once) + extra_values, reservation_id = \ + self.create_instance(req, self.compute_api.create_all_at_once) return {'reservation_id': reservation_id} -- cgit From fccc653376ec03e2f8d4e91449a18d62cd87902f Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Tue, 31 May 2011 06:29:38 -0700 Subject: tests passing again --- nova/api/openstack/create_instance_controller.py | 31 +++++++++--------------- nova/api/openstack/servers.py | 27 ++++++++++++++++----- nova/api/openstack/zones.py | 12 ++++++--- nova/tests/api/openstack/test_servers.py | 4 ++- 4 files changed, 44 insertions(+), 30 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_controller.py b/nova/api/openstack/create_instance_controller.py index 1c4098a08..ca076a218 100644 --- a/nova/api/openstack/create_instance_controller.py +++ b/nova/api/openstack/create_instance_controller.py @@ -46,7 +46,7 @@ class OpenstackCreateInstanceController(common.OpenstackController): Once we stabilize the Zones portion of the API we may be able to move this code back into servers.py """ - + def __init__(self): """We need the image service to create an instance.""" self._image_service = utils.import_object(FLAGS.image_service) @@ -58,19 +58,22 @@ class OpenstackCreateInstanceController(common.OpenstackController): def _flavor_id_from_req_data(self, data): raise NotImplementedError() + def _get_server_admin_password(self, server): + raise NotImplementedError() + def create_instance(self, req, create_method): """Creates a new server for the given user. The approach used depends on the create_method. For example, the standard - POST /server call uses compute.api.create(), while + POST /server call uses compute.api.create(), while POST /zones/server uses compute.api.create_all_at_once(). The problem is, both approaches return different values (i.e. [instance dicts] vs. reservation_id). So the handling of the - return type from this method is left to the caller. + return type from this method is left to the caller. """ env = self._deserialize_create(req) if not env: - return faults.Fault(exc.HTTPUnprocessableEntity()) + return (None, faults.Fault(exc.HTTPUnprocessableEntity())) context = req.environ['nova.context'] @@ -90,7 +93,7 @@ class OpenstackCreateInstanceController(common.OpenstackController): context, requested_image_id) except: msg = _("Can not find requested image") - return faults.Fault(exc.HTTPBadRequest(msg)) + return (None, faults.Fault(exc.HTTPBadRequest(msg))) kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image( req, image_id) @@ -104,14 +107,14 @@ class OpenstackCreateInstanceController(common.OpenstackController): if not 'name' in env['server']: msg = _("Server name is not defined") - return exc.HTTPBadRequest(msg) - - zone_blob = env['server'].get('blob') - reservation_id = env['server'].get('reservation_id') + return (None, exc.HTTPBadRequest(msg)) name = env['server']['name'] self._validate_server_name(name) name = name.strip() + zone_blob = env['server'].get('blob') + reservation_id = env['server'].get('reservation_id') + inst_type = instance_types.get_instance_type_by_flavor_id(flavor_id) extra_values = { 'instance_type': inst_type, @@ -179,16 +182,6 @@ class OpenstackCreateInstanceController(common.OpenstackController): msg = _("Server name is an empty string") raise exc.HTTPBadRequest(msg) - def _get_server_admin_password(self, server): - """ Determine the admin password for a server on creation """ - password = server.get('adminPass') - if password is None: - return utils.generate_password(16) - if not isinstance(password, basestring) or password == '': - msg = _("Invalid adminPass") - raise exc.HTTPBadRequest(msg) - return password - def _get_kernel_ramdisk_from_image(self, req, image_id): """Fetch an image from the ImageService, then if present, return the associated kernel and ramdisk image IDs. diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 6e86c2956..223e1a191 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -116,13 +116,17 @@ class Controller(controller.OpenstackCreateInstanceController): def create(self, req): """ Creates a new server for a given user """ - extra_values, instances = \ + extra_values, result = \ self.create_instance(req, self.compute_api.create) + if extra_values is None: + return result # a Fault. + + instances = result (inst, ) = instances for key in ['instance_type', 'image_id']: inst[key] = extra_values[key] - + builder = self._get_view_builder(req) server = builder.build(inst, is_detail=True) server['server']['adminPass'] = extra_values['password'] @@ -156,10 +160,6 @@ class Controller(controller.OpenstackCreateInstanceController): injected_files.append((path, contents)) return injected_files - def _get_server_admin_password(self, server): - """ Determine the admin password for a server on creation """ - return utils.generate_password(16) - @scheduler_api.redirect_handler def update(self, req, id): """ Updates the server name or password """ @@ -491,6 +491,10 @@ class ControllerV10(Controller): response.empty_body = True return response + def _get_server_admin_password(self, server): + """ Determine the admin password for a server on creation """ + return utils.generate_password(16) + class ControllerV11(Controller): def _image_id_from_req_data(self, data): @@ -586,3 +590,14 @@ class ControllerV11(Controller): def get_default_xmlns(self, req): return common.XML_NS_V11 + + def _get_server_admin_password(self, server): + """ Determine the admin password for a server on creation """ + password = server.get('adminPass') + + if password is None: + return utils.generate_password(16) + if not isinstance(password, basestring) or password == '': + msg = _("Invalid adminPass") + raise exc.HTTPBadRequest(msg) + return password diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index 91531aa97..acd01a1ff 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -26,6 +26,7 @@ from nova.compute import api as compute from nova.scheduler import api from nova.api.openstack import create_instance_controller as controller +from nova.api.openstack import common FLAGS = flags.FLAGS @@ -125,12 +126,15 @@ class Controller(controller.OpenstackCreateInstanceController): def boot(self, req): """Creates a new server for a given user while being Zone aware. - + Returns a reservation ID (a UUID). """ - extra_values, reservation_id = \ + extra_values, result = \ self.create_instance(req, self.compute_api.create_all_at_once) + if extra_values is None: + return result # a Fault. + reservation_id = result return {'reservation_id': reservation_id} @check_encryption_key @@ -156,8 +160,8 @@ class Controller(controller.OpenstackCreateInstanceController): cooked.append(dict(weight=entry['weight'], blob=cipher_text)) return cooked - - # Assume OS 1.0 functionality for these overrides. + + # Assume OS 1.0 functionality for these overrides. def _image_id_from_req_data(self, data): return data['server']['imageId'] diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index fbde5c9ce..7bad28ca8 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -31,6 +31,7 @@ from nova import flags from nova import test import nova.api.openstack from nova.api.openstack import servers +from nova.api.openstack import create_instance_controller import nova.compute.api from nova.compute import instance_types from nova.compute import power_state @@ -1380,7 +1381,8 @@ class ServersTest(test.TestCase): class TestServerCreateRequestXMLDeserializer(unittest.TestCase): def setUp(self): - self.deserializer = servers.ServerCreateRequestXMLDeserializer() + self.deserializer = \ + create_instance_controller.ServerCreateRequestXMLDeserializer() def test_minimal_request(self): serial_request = """ -- cgit From beb6bf93d0bab5b50c6f0af90758e21cc68187ab Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Tue, 31 May 2011 14:56:04 -0700 Subject: basic zone-boot test in place --- nova/api/openstack/create_instance_controller.py | 3 ++- nova/api/openstack/servers.py | 4 ---- nova/api/openstack/zones.py | 1 + nova/tests/api/openstack/test_servers.py | 27 ++++++++++++++++++++++-- 4 files changed, 28 insertions(+), 7 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_controller.py b/nova/api/openstack/create_instance_controller.py index ca076a218..c79638bd9 100644 --- a/nova/api/openstack/create_instance_controller.py +++ b/nova/api/openstack/create_instance_controller.py @@ -59,7 +59,8 @@ class OpenstackCreateInstanceController(common.OpenstackController): raise NotImplementedError() def _get_server_admin_password(self, server): - raise NotImplementedError() + """ Determine the admin password for a server on creation """ + return utils.generate_password(16) def create_instance(self, req, create_method): """Creates a new server for the given user. The approach diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 223e1a191..67b3fd23f 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -491,10 +491,6 @@ class ControllerV10(Controller): response.empty_body = True return response - def _get_server_admin_password(self, server): - """ Determine the admin password for a server on creation """ - return utils.generate_password(16) - class ControllerV11(Controller): def _image_id_from_req_data(self, data): diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index acd01a1ff..687978b08 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -129,6 +129,7 @@ class Controller(controller.OpenstackCreateInstanceController): Returns a reservation ID (a UUID). """ + print "************** IN ZONE BOOT" extra_values, result = \ self.create_instance(req, self.compute_api.create_all_at_once) if extra_values is None: diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 7bad28ca8..9d12097c8 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -218,7 +218,6 @@ class ServersTest(test.TestCase): }, ] - print res_dict['server'] self.assertEqual(res_dict['server']['links'], expected_links) def test_get_server_by_id_with_addresses_xml(self): @@ -484,7 +483,9 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.db.api, 'queue_get_for', queue_get_for) self.stubs.Set(nova.network.manager.VlanManager, 'allocate_fixed_ip', fake_method) - self.stubs.Set(nova.api.openstack.servers.Controller, + self.stubs.Set( + nova.api.openstack.create_instance_controller.\ + OpenstackCreateInstanceController, "_get_kernel_ramdisk_from_image", kernel_ramdisk_mapping) self.stubs.Set(nova.api.openstack.common, "get_image_id_from_image_hash", image_id_from_hash) @@ -515,6 +516,28 @@ class ServersTest(test.TestCase): def test_create_instance(self): self._test_create_instance_helper() + def test_create_instance_via_zones(self): + """Server generated ReservationID""" + self._setup_for_create_instance() + FLAGS.allow_admin_api = True + + body = dict(server=dict( + name='server_test', imageId=3, flavorId=2, + metadata={'hello': 'world', 'open': 'stack'}, + personality={})) + req = webob.Request.blank('/v1.0/zones/boot') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + + res = req.get_response(fakes.wsgi_app()) + + reservation_id = json.loads(res.body)['reservation_id'] + self.assertEqual(res.status_int, 200) + self.assertNotEqual(reservation_id, "") + self.assertNotEqual(reservation_id, None) + self.assertTrue(len(reservation_id) > 1) + def test_create_instance_no_key_pair(self): fakes.stub_out_key_pair_funcs(self.stubs, have_key_pair=False) self._test_create_instance_helper() -- cgit From db68508e1468e9d2d3469f2ea6a9ec577d1190bc Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 1 Jun 2011 05:36:41 -0700 Subject: added /zones/boot reservation id tests --- nova/tests/api/openstack/test_servers.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 9d12097c8..3d5f92dea 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -538,6 +538,26 @@ class ServersTest(test.TestCase): self.assertNotEqual(reservation_id, None) self.assertTrue(len(reservation_id) > 1) + def test_create_instance_via_zones_with_resid(self): + """User supplied ReservationID""" + self._setup_for_create_instance() + FLAGS.allow_admin_api = True + + body = dict(server=dict( + name='server_test', imageId=3, flavorId=2, + metadata={'hello': 'world', 'open': 'stack'}, + personality={}, reservation_id='myresid')) + req = webob.Request.blank('/v1.0/zones/boot') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + + res = req.get_response(fakes.wsgi_app()) + + reservation_id = json.loads(res.body)['reservation_id'] + self.assertEqual(res.status_int, 200) + self.assertEqual(reservation_id, "myresid") + def test_create_instance_no_key_pair(self): fakes.stub_out_key_pair_funcs(self.stubs, have_key_pair=False) self._test_create_instance_helper() -- cgit From 3bf3255f91aab28aa6915a2836dad77f17312e03 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 1 Jun 2011 11:52:33 -0700 Subject: basic reservation id support to GET /servers --- nova/api/openstack/servers.py | 34 +++++----------------------------- nova/api/openstack/zones.py | 1 - 2 files changed, 5 insertions(+), 30 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 67b3fd23f..2bfcbac81 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -87,7 +87,11 @@ class Controller(controller.OpenstackCreateInstanceController): builder - the response model builder """ - instance_list = self.compute_api.get_all(req.environ['nova.context']) + reservation_id = req.str_GET.get('reservation_id') + LOG.exception(_(" ************* RESERVATION ID %s"), reservation_id) + instance_list = self.compute_api.get_all( + req.environ['nova.context'], + reservation_id=reservation_id) limited_list = self._limit_items(instance_list, req) builder = self._get_view_builder(req) servers = [builder.build(inst, is_detail)['server'] @@ -132,34 +136,6 @@ class Controller(controller.OpenstackCreateInstanceController): server['server']['adminPass'] = extra_values['password'] return server - def _get_injected_files(self, personality): - """ - Create a list of injected files from the personality attribute - - At this time, injected_files must be formatted as a list of - (file_path, file_content) pairs for compatibility with the - underlying compute service. - """ - injected_files = [] - - for item in personality: - try: - path = item['path'] - contents = item['contents'] - except KeyError as key: - expl = _('Bad personality format: missing %s') % key - raise exc.HTTPBadRequest(explanation=expl) - except TypeError: - expl = _('Bad personality format') - raise exc.HTTPBadRequest(explanation=expl) - try: - contents = base64.b64decode(contents) - except TypeError: - expl = _('Personality content for %s cannot be decoded') % path - raise exc.HTTPBadRequest(explanation=expl) - injected_files.append((path, contents)) - return injected_files - @scheduler_api.redirect_handler def update(self, req, id): """ Updates the server name or password """ diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index 687978b08..acd01a1ff 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -129,7 +129,6 @@ class Controller(controller.OpenstackCreateInstanceController): Returns a reservation ID (a UUID). """ - print "************** IN ZONE BOOT" extra_values, result = \ self.create_instance(req, self.compute_api.create_all_at_once) if extra_values is None: -- cgit From b05dcdc69387ecd54e40063e66355961d39b4430 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 1 Jun 2011 12:39:31 -0700 Subject: reservation id's properly forwarded to child zones on create --- nova/api/openstack/create_instance_controller.py | 30 ++++++++++++++++++++++++ nova/scheduler/zone_aware_scheduler.py | 6 +++-- 2 files changed, 34 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_controller.py b/nova/api/openstack/create_instance_controller.py index c79638bd9..786d74e37 100644 --- a/nova/api/openstack/create_instance_controller.py +++ b/nova/api/openstack/create_instance_controller.py @@ -116,6 +116,8 @@ class OpenstackCreateInstanceController(common.OpenstackController): zone_blob = env['server'].get('blob') reservation_id = env['server'].get('reservation_id') + LOG.exception("******* CREATE_INSTANCE RES_ID=%s of %s" % (reservation_id, env)) + inst_type = instance_types.get_instance_type_by_flavor_id(flavor_id) extra_values = { 'instance_type': inst_type, @@ -221,6 +223,34 @@ class OpenstackCreateInstanceController(common.OpenstackController): raise exception.RamdiskNotFoundForImage(image_id=image_id) return kernel_id, ramdisk_id + + def _get_injected_files(self, personality): + """ + Create a list of injected files from the personality attribute + + At this time, injected_files must be formatted as a list of + (file_path, file_content) pairs for compatibility with the + underlying compute service. + """ + injected_files = [] + + for item in personality: + try: + path = item['path'] + contents = item['contents'] + except KeyError as key: + expl = _('Bad personality format: missing %s') % key + raise exc.HTTPBadRequest(explanation=expl) + except TypeError: + expl = _('Bad personality format') + raise exc.HTTPBadRequest(explanation=expl) + try: + contents = base64.b64decode(contents) + except TypeError: + expl = _('Personality content for %s cannot be decoded') % path + raise exc.HTTPBadRequest(explanation=expl) + injected_files.append((path, contents)) + return injected_files class ServerCreateRequestXMLDeserializer(object): diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index 35ffdbde1..4b96f9877 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -90,6 +90,7 @@ class ZoneAwareScheduler(driver.Scheduler): image_id = instance_properties['image_id'] meta = instance_properties['metadata'] flavor_id = instance_type['flavorid'] + reservation_id = instance_properties['reservation_id'] files = kwargs['injected_files'] ipgroup = None # Not supported in OS API ... yet @@ -98,7 +99,8 @@ class ZoneAwareScheduler(driver.Scheduler): child_blob = zone_info['child_blob'] zone = db.zone_get(context, child_zone) url = zone.api_url - LOG.debug(_("Forwarding instance create call to child zone %(url)s") + LOG.debug(_("Forwarding instance create call to child zone %(url)s" + ". ReservationID=%(reservation_id)s") % locals()) nova = None try: @@ -109,7 +111,7 @@ class ZoneAwareScheduler(driver.Scheduler): "to talk to zone at %(url)s.") % locals()) nova.servers.create(name, image_id, flavor_id, ipgroup, meta, files, - child_blob) + child_blob, reservation_id=reservation_id) def _provision_resource_from_blob(self, context, item, instance_id, request_spec, kwargs): -- cgit From cf464dc7f2093ea3d1f831915ce22f54f0d1c90a Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 1 Jun 2011 17:35:49 -0700 Subject: list --reservation now works across zones --- nova/api/openstack/create_instance_controller.py | 2 -- nova/api/openstack/views/servers.py | 12 +++++++++--- nova/compute/api.py | 22 ++++++++++++++++++++-- nova/scheduler/api.py | 8 +++++--- 4 files changed, 34 insertions(+), 10 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_controller.py b/nova/api/openstack/create_instance_controller.py index 786d74e37..edb1a5007 100644 --- a/nova/api/openstack/create_instance_controller.py +++ b/nova/api/openstack/create_instance_controller.py @@ -116,8 +116,6 @@ class OpenstackCreateInstanceController(common.OpenstackController): zone_blob = env['server'].get('blob') reservation_id = env['server'].get('reservation_id') - LOG.exception("******* CREATE_INSTANCE RES_ID=%s of %s" % (reservation_id, env)) - inst_type = instance_types.get_instance_type_by_flavor_id(flavor_id) extra_values = { 'instance_type': inst_type, diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 0be468edc..0ee461dde 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -41,10 +41,13 @@ class ViewBuilder(object): def build(self, inst, is_detail): """Return a dict that represenst a server.""" - if is_detail: - server = self._build_detail(inst) + if inst.get('_is_precooked', False): + server = dict(server=inst) else: - server = self._build_simple(inst) + if is_detail: + server = self._build_detail(inst) + else: + server = self._build_simple(inst) self._build_extra(server, inst) @@ -78,6 +81,9 @@ class ViewBuilder(object): ctxt = nova.context.get_admin_context() compute_api = nova.compute.API() + + # TODO(sandy): Could be a bug here since the instance ID + # may have come from another Zone. if compute_api.has_finished_migration(ctxt, inst['id']): inst_dict['status'] = 'RESIZE-CONFIRM' diff --git a/nova/compute/api.py b/nova/compute/api.py index fc369ccd2..f9e76ffbc 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -543,6 +543,25 @@ class API(base.Base): """ return self.get(context, instance_id) + def get_all_across_zones(self, context, reservation_id): + """Get all instances with this reservation_id, across + all available Zones (if any). + """ + instances = self.db.instance_get_all_by_reservation( + context, reservation_id) + + children = scheduler_api.call_zone_method(context, "list", + novaclient_collection_name="servers", + reservation_id=reservation_id) + + for zone, servers in children: + for server in servers: + LOG.debug("**** INSTANCE= %s" % server._info) + # Results are ready to send to user. No need to scrub. + server._info['_is_precooked'] = True + instances.append(server._info) + return instances + def get_all(self, context, project_id=None, reservation_id=None, fixed_ip=None): """Get all instances filtered by one of the given parameters. @@ -552,8 +571,7 @@ class API(base.Base): """ if reservation_id is not None: - return self.db.instance_get_all_by_reservation( - context, reservation_id) + return self.get_all_across_zones(context, reservation_id) if fixed_ip is not None: return self.db.fixed_ip_get_instance(context, fixed_ip) diff --git a/nova/scheduler/api.py b/nova/scheduler/api.py index de0660713..0f423655e 100644 --- a/nova/scheduler/api.py +++ b/nova/scheduler/api.py @@ -118,7 +118,8 @@ def _process(func, zone): return func(nova, zone) -def call_zone_method(context, method, errors_to_ignore=None, *args, **kwargs): +def call_zone_method(context, method_name, errors_to_ignore=None, + novaclient_collection_name='zones', *args, **kwargs): """Returns a list of (zone, call_result) objects.""" if not isinstance(errors_to_ignore, (list, tuple)): # This will also handle the default None @@ -138,11 +139,12 @@ def call_zone_method(context, method, errors_to_ignore=None, *args, **kwargs): #TODO (dabo) - add logic for failure counts per zone, # with escalation after a given number of failures. continue - zone_method = getattr(nova.zones, method) + novaclient_collection = getattr(nova, novaclient_collection_name) + collection_method = getattr(novaclient_collection, method_name) def _error_trap(*args, **kwargs): try: - return zone_method(*args, **kwargs) + return collection_method(*args, **kwargs) except Exception as e: if type(e) in errors_to_ignore: return None -- cgit From e0d2dde5d370d76cd8ff55e47dbbf749be43a4c9 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 1 Jun 2011 17:49:49 -0700 Subject: tests all passing again --- nova/api/openstack/create_instance_controller.py | 5 +++-- nova/api/openstack/servers.py | 1 - nova/compute/api.py | 1 - nova/scheduler/api.py | 5 +---- nova/tests/test_scheduler.py | 8 +------- 5 files changed, 5 insertions(+), 15 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_controller.py b/nova/api/openstack/create_instance_controller.py index edb1a5007..3a8bbb3c9 100644 --- a/nova/api/openstack/create_instance_controller.py +++ b/nova/api/openstack/create_instance_controller.py @@ -15,13 +15,14 @@ # License for the specific language governing permissions and limitations # under the License. +import base64 import re +import webob + from urlparse import urlparse from webob import exc from xml.dom import minidom -import webob - from nova import exception from nova import flags from nova import log as logging diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 2bfcbac81..e5b04db43 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -88,7 +88,6 @@ class Controller(controller.OpenstackCreateInstanceController): builder - the response model builder """ reservation_id = req.str_GET.get('reservation_id') - LOG.exception(_(" ************* RESERVATION ID %s"), reservation_id) instance_list = self.compute_api.get_all( req.environ['nova.context'], reservation_id=reservation_id) diff --git a/nova/compute/api.py b/nova/compute/api.py index f9e76ffbc..9cb572720 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -556,7 +556,6 @@ class API(base.Base): for zone, servers in children: for server in servers: - LOG.debug("**** INSTANCE= %s" % server._info) # Results are ready to send to user. No need to scrub. server._info['_is_precooked'] = True instances.append(server._info) diff --git a/nova/scheduler/api.py b/nova/scheduler/api.py index 0f423655e..432f22b90 100644 --- a/nova/scheduler/api.py +++ b/nova/scheduler/api.py @@ -148,10 +148,7 @@ def call_zone_method(context, method_name, errors_to_ignore=None, except Exception as e: if type(e) in errors_to_ignore: return None - # TODO (dabo) - want to be able to re-raise here. - # Returning a string now; raising was causing issues. - # raise e - return "ERROR", "%s" % e + raise e res = pool.spawn(_error_trap, *args, **kwargs) results.append((zone, res)) diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py index 54b3f80fb..5d8f34efd 100644 --- a/nova/tests/test_scheduler.py +++ b/nova/tests/test_scheduler.py @@ -1109,10 +1109,4 @@ class CallZoneMethodTest(test.TestCase): def test_call_zone_method_generates_exception(self): context = {} method = 'raises_exception' - results = api.call_zone_method(context, method) - - # FIXME(sirp): for now the _error_trap code is catching errors and - # converting them to a ("ERROR", "string") tuples. The code (and this - # test) should eventually handle real exceptions. - expected = [(1, ('ERROR', 'testing'))] - self.assertEqual(expected, results) + self.assertRaises(Exception, api.call_zone_method, context, method) -- cgit From d31ad6211956e69644894490ce37f6c3e8ea5e6e Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 1 Jun 2011 17:53:45 -0700 Subject: pep8 and all that --- nova/api/openstack/create_instance_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_controller.py b/nova/api/openstack/create_instance_controller.py index 3a8bbb3c9..0ab262b6e 100644 --- a/nova/api/openstack/create_instance_controller.py +++ b/nova/api/openstack/create_instance_controller.py @@ -222,7 +222,7 @@ class OpenstackCreateInstanceController(common.OpenstackController): raise exception.RamdiskNotFoundForImage(image_id=image_id) return kernel_id, ramdisk_id - + def _get_injected_files(self, personality): """ Create a list of injected files from the personality attribute -- cgit From 970415346b356f03f9d6152bfd4744b94bb59bbd Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 1 Jun 2011 18:17:04 -0700 Subject: Little cleanups --- nova/api/openstack/views/servers.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 0ee461dde..84086b3b2 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -49,7 +49,7 @@ class ViewBuilder(object): else: server = self._build_simple(inst) - self._build_extra(server, inst) + self._build_extra(server, inst) return server @@ -82,8 +82,6 @@ class ViewBuilder(object): ctxt = nova.context.get_admin_context() compute_api = nova.compute.API() - # TODO(sandy): Could be a bug here since the instance ID - # may have come from another Zone. if compute_api.has_finished_migration(ctxt, inst['id']): inst_dict['status'] = 'RESIZE-CONFIRM' -- cgit From 3fb467e44b5e5715e364c6c616998e54d7f20f92 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 2 Jun 2011 07:57:35 -0700 Subject: get_all with reservation id across zone tests --- nova/compute/api.py | 16 ---------- nova/scheduler/api.py | 7 ----- nova/scheduler/driver.py | 7 ----- nova/scheduler/zone_aware_scheduler.py | 6 ---- nova/tests/api/openstack/test_servers.py | 50 ++++++++++++++++++++++++++++++-- 5 files changed, 47 insertions(+), 39 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 9cb572720..b99d1d0a3 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -377,22 +377,6 @@ class API(base.Base): return [dict(x.iteritems()) for x in instances] - def smart_create(self, *args, **kwargs): - """ - Ask the scheduler if we should do single shot instance requests - or all-at-once. - - Cache this information on first request and act accordingly. - """ - - if API.should_create_all_at_once == None: - API.should_create_all_at_once = \ - scheduler_api.should_create_all_at_once(context) - - if API.should_create_all_at_once: - return self.create_all_at_once(*args, **kwargs) - return self.create(*args, **kwargs) - def has_finished_migration(self, context, instance_id): """Returns true if an instance has a finished migration.""" try: diff --git a/nova/scheduler/api.py b/nova/scheduler/api.py index 432f22b90..789993890 100644 --- a/nova/scheduler/api.py +++ b/nova/scheduler/api.py @@ -87,13 +87,6 @@ def select(context, specs=None): params={"request_spec": specs}) -def should_create_all_at_once(context): - """Returns a tuple of rules for how instances should - be created given the current Scheduler driver being used.""" - return _call_scheduler('should_create_all_at_once', context=context, - params={}) - - def update_service_capabilities(context, service_name, host, capabilities): """Send an update to all the scheduler services informing them of the capabilities of this service.""" diff --git a/nova/scheduler/driver.py b/nova/scheduler/driver.py index 237e31c04..2094e3565 100644 --- a/nova/scheduler/driver.py +++ b/nova/scheduler/driver.py @@ -72,13 +72,6 @@ class Scheduler(object): for service in services if self.service_is_up(service)] - def should_create_all_at_once(self, context=None, *args, **kwargs): - """ - Does this driver prefer single-shot requests or all-at-once? - By default, prefer single-shot. - """ - return False - def schedule(self, context, topic, *_args, **_kwargs): """Must override at least this method for scheduler to work.""" raise NotImplementedError(_("Must implement a fallback schedule")) diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index 4b96f9877..5e8d63038 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -159,12 +159,6 @@ class ZoneAwareScheduler(driver.Scheduler): self._provision_resource_from_blob(context, item, instance_id, request_spec, kwargs) - def should_create_all_at_once(self, context=None, *args, **kwargs): - """ - This driver prefers all-at-once requests. - """ - return True - def schedule_run_instance(self, context, instance_id, request_spec, *args, **kwargs): """This method is called from nova.compute.api to provision diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 3d5f92dea..ae3fad2dc 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -36,6 +36,7 @@ import nova.compute.api from nova.compute import instance_types from nova.compute import power_state import nova.db.api +import nova.scheduler.api from nova.db.sqlalchemy.models import Instance from nova.db.sqlalchemy.models import InstanceMetadata import nova.rpc @@ -68,6 +69,26 @@ def return_servers(context, user_id=1): return [stub_instance(i, user_id) for i in xrange(5)] +def return_servers_by_reservation(context, reservation_id=""): + return [stub_instance(i, reservation_id) for i in xrange(5)] + + +def return_servers_from_child_zones(*args, **kwargs): + class Server(object): + pass + + zones = [] + for zone in xrange(3): + servers = [] + for server_id in xrange(5): + server = Server() + server._info = stub_instance(server_id, reservation_id="child") + servers.append(server) + + zones.append(("Zone%d" % zone, servers)) + return zones + + def return_security_group(context, instance_id, security_group_id): pass @@ -81,7 +102,7 @@ def instance_address(context, instance_id): def stub_instance(id, user_id=1, private_address=None, public_addresses=None, - host=None, power_state=0): + host=None, power_state=0, reservation_id=""): metadata = [] metadata.append(InstanceMetadata(key='seq', value=id)) @@ -93,6 +114,11 @@ def stub_instance(id, user_id=1, private_address=None, public_addresses=None, if host is not None: host = str(host) + # ReservationID isn't sent back, hack it in there. + server_name = "server%s" % id + if reservation_id != "": + server_name = "reservation_%s" % (reservation_id, ) + instance = { "id": id, "admin_pass": "", @@ -113,13 +139,13 @@ def stub_instance(id, user_id=1, private_address=None, public_addresses=None, "host": host, "instance_type": dict(inst_type), "user_data": "", - "reservation_id": "", + "reservation_id": reservation_id, "mac_address": "", "scheduled_at": datetime.datetime.now(), "launched_at": datetime.datetime.now(), "terminated_at": datetime.datetime.now(), "availability_zone": "", - "display_name": "server%s" % id, + "display_name": server_name, "display_description": "", "locked": False, "metadata": metadata} @@ -364,6 +390,24 @@ class ServersTest(test.TestCase): self.assertEqual(s.get('imageId', None), None) i += 1 + def test_get_server_list_with_reservation_id(self): + self.stubs.Set(nova.db.api, 'instance_get_all_by_reservation', + return_servers_by_reservation) + self.stubs.Set(nova.scheduler.api, 'call_zone_method', + return_servers_from_child_zones) + req = webob.Request.blank('/v1.0/servers/detail?reservation_id=foo') + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + + i = 0 + for s in res_dict['servers']: + print "SERVER", s + if '_is_precooked' in s: + self.assertEqual(s.get('reservation_id'), 'child') + else: + self.assertEqual(s.get('name'), 'server%d' % i) + i += 1 + def test_get_server_list_v1_1(self): req = webob.Request.blank('/v1.1/servers') res = req.get_response(fakes.wsgi_app()) -- cgit From 983bff090da0f09f944dd4152173a4586866a895 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 2 Jun 2011 08:05:46 -0700 Subject: more tests (empty responses) --- nova/tests/api/openstack/test_servers.py | 45 +++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index ae3fad2dc..ba76b6691 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -73,6 +73,14 @@ def return_servers_by_reservation(context, reservation_id=""): return [stub_instance(i, reservation_id) for i in xrange(5)] +def return_servers_by_reservation_empty(context, reservation_id=""): + return [] + + +def return_servers_from_child_zones_empty(*args, **kwargs): + return [] + + def return_servers_from_child_zones(*args, **kwargs): class Server(object): pass @@ -395,19 +403,54 @@ class ServersTest(test.TestCase): return_servers_by_reservation) self.stubs.Set(nova.scheduler.api, 'call_zone_method', return_servers_from_child_zones) + req = webob.Request.blank('/v1.0/servers?reservation_id=foo') + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + + i = 0 + for s in res_dict['servers']: + if '_is_precooked' in s: + self.assertEqual(s.get('reservation_id'), 'child') + else: + self.assertEqual(s.get('name'), 'server%d' % i) + i += 1 + + def test_get_server_list_with_reservation_id_empty(self): + self.stubs.Set(nova.db.api, 'instance_get_all_by_reservation', + return_servers_by_reservation_empty) + self.stubs.Set(nova.scheduler.api, 'call_zone_method', + return_servers_from_child_zones_empty) req = webob.Request.blank('/v1.0/servers/detail?reservation_id=foo') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) i = 0 for s in res_dict['servers']: - print "SERVER", s if '_is_precooked' in s: self.assertEqual(s.get('reservation_id'), 'child') else: self.assertEqual(s.get('name'), 'server%d' % i) i += 1 + def test_get_server_list_with_reservation_id_details(self): + self.stubs.Set(nova.db.api, 'instance_get_all_by_reservation', + return_servers_by_reservation) + self.stubs.Set(nova.scheduler.api, 'call_zone_method', + return_servers_from_child_zones) + req = webob.Request.blank('/v1.0/servers/detail?reservation_id=foo') + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + + i = 0 + for s in res_dict['servers']: + if '_is_precooked' in s: + self.assertEqual(s.get('reservation_id'), 'child') + else: + self.assertEqual(s.get('name'), 'server%d' % i) + i += 1 + + + def test_get_server_list_v1_1(self): req = webob.Request.blank('/v1.1/servers') res = req.get_response(fakes.wsgi_app()) -- cgit From 810b580cb41b076b083ace1c4670c13b2f16c5a5 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 9 Jun 2011 16:19:24 -0700 Subject: forgot some debugging statements --- nova/api/openstack/create_instance_controller.py | 8 -------- nova/api/openstack/servers.py | 3 --- nova/compute/api.py | 1 - nova/image/__init__.py | 2 -- 4 files changed, 14 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_controller.py b/nova/api/openstack/create_instance_controller.py index 613a33b25..cffd944f7 100644 --- a/nova/api/openstack/create_instance_controller.py +++ b/nova/api/openstack/create_instance_controller.py @@ -76,7 +76,6 @@ class OpenstackCreateInstanceController(object): [instance dicts] vs. reservation_id). So the handling of the return type from this method is left to the caller. """ - print "************************ A" if not body: return (None, faults.Fault(exc.HTTPUnprocessableEntity())) @@ -84,7 +83,6 @@ class OpenstackCreateInstanceController(object): password = self._get_server_admin_password(body['server']) - print "************************ B" key_name = None key_data = None key_pairs = auth_manager.AuthManager.get_key_pairs(context) @@ -93,15 +91,11 @@ class OpenstackCreateInstanceController(object): key_name = key_pair['name'] key_data = key_pair['public_key'] - print "************************ C" image_href = self._image_ref_from_req_data(body) try: - print "************************ Ca" image_service, image_id = nova.image.get_image_service(image_href) - print "************************ Cb" kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image( req, image_id) - print "************************ Ce" images = set([str(x['id']) for x in image_service.index(context)]) assert str(image_id) in images except Exception, e: @@ -109,7 +103,6 @@ class OpenstackCreateInstanceController(object): locals()) return (None, faults.Fault(exc.HTTPBadRequest(msg))) - print "************************ D" personality = body['server'].get('personality') injected_files = [] @@ -118,7 +111,6 @@ class OpenstackCreateInstanceController(object): flavor_id = self._flavor_id_from_req_data(body) - print "************************ E" if not 'name' in body['server']: msg = _("Server name is not defined") return (None, exc.HTTPBadRequest(msg)) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 23bb1c869..387b0343a 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -110,14 +110,11 @@ class Controller(base_controller.OpenstackCreateInstanceController): def create(self, req, body): """ Creates a new server for a given user """ - print "************************ 1" extra_values, result = \ self.create_instance(req, body, self.compute_api.create) - print "************************ 2" if extra_values is None: return result # a Fault. - print "************************ 3" instances = result (inst, ) = instances diff --git a/nova/compute/api.py b/nova/compute/api.py index 09ac7a2c6..c7db167c1 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -361,7 +361,6 @@ class API(base.Base): for num in range(num_instances): instance = self.create_db_entry_for_new_instance(context, base_options, security_groups, num=num) - print "*********** INSTANCE = ", instance instances.append(instance) instance_id = instance['id'] diff --git a/nova/image/__init__.py b/nova/image/__init__.py index bde600f70..93d83df24 100644 --- a/nova/image/__init__.py +++ b/nova/image/__init__.py @@ -84,12 +84,10 @@ def get_image_service(image_href): :returns: a tuple of the form (image_service, image_id) """ - print "******** XX" image_href = image_href or 0 if str(image_href).isdigit(): return (get_default_image_service(), int(image_href)) - print "******** X" (glance_client, image_id) = get_glance_client(image_href) image_service = nova.image.glance.GlanceImageService(glance_client) return (image_service, image_id) -- cgit From 1261d1340631206c8d47c6373ebd783e75f389ac Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Fri, 10 Jun 2011 05:27:05 -0700 Subject: fixed reraise in trap_error --- nova/scheduler/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/scheduler/api.py b/nova/scheduler/api.py index 789993890..ffe59d2c1 100644 --- a/nova/scheduler/api.py +++ b/nova/scheduler/api.py @@ -141,7 +141,7 @@ def call_zone_method(context, method_name, errors_to_ignore=None, except Exception as e: if type(e) in errors_to_ignore: return None - raise e + raise res = pool.spawn(_error_trap, *args, **kwargs) results.append((zone, res)) -- cgit From db3280e5177df92484bf0a52b5f6ed89dfea63dd Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 13 Jun 2011 09:39:58 -0700 Subject: zones image_id/image_href support for 1.0/1.1 --- nova/api/openstack/__init__.py | 8 ++++---- nova/api/openstack/create_instance_controller.py | 2 -- nova/api/openstack/servers.py | 2 -- nova/api/openstack/zones.py | 19 +++++++++++-------- 4 files changed, 15 insertions(+), 16 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 4650445d7..e0ae55105 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -81,7 +81,7 @@ class APIRouter(base_wsgi.Router): self._setup_routes(mapper) super(APIRouter, self).__init__(mapper) - def _setup_routes(self, mapper): + def _setup_routes(self, mapper, version='1.0'): server_members = self.server_members server_members['action'] = 'POST' if FLAGS.allow_admin_api: @@ -99,7 +99,7 @@ class APIRouter(base_wsgi.Router): server_members['inject_network_info'] = 'POST' mapper.resource("zone", "zones", - controller=zones.create_resource(), + controller=zones.create_resource(version), collection={'detail': 'GET', 'info': 'GET', 'select': 'POST', @@ -126,7 +126,7 @@ class APIRouterV10(APIRouter): """Define routes specific to OpenStack API V1.0.""" def _setup_routes(self, mapper): - super(APIRouterV10, self)._setup_routes(mapper) + super(APIRouterV10, self)._setup_routes(mapper, version='1.0') mapper.resource("server", "servers", controller=servers.create_resource('1.0'), collection={'detail': 'GET'}, @@ -162,7 +162,7 @@ class APIRouterV11(APIRouter): """Define routes specific to OpenStack API V1.1.""" def _setup_routes(self, mapper): - super(APIRouterV11, self)._setup_routes(mapper) + super(APIRouterV11, self)._setup_routes(mapper, version='1.1') mapper.resource("server", "servers", controller=servers.create_resource('1.1'), collection={'detail': 'GET'}, diff --git a/nova/api/openstack/create_instance_controller.py b/nova/api/openstack/create_instance_controller.py index cffd944f7..2d807470a 100644 --- a/nova/api/openstack/create_instance_controller.py +++ b/nova/api/openstack/create_instance_controller.py @@ -19,7 +19,6 @@ import base64 import re import webob -from urlparse import urlparse from webob import exc from xml.dom import minidom @@ -31,7 +30,6 @@ from nova import quota from nova import utils from nova.compute import instance_types -from nova.api.openstack import common from nova.api.openstack import faults from nova.api.openstack import wsgi from nova.auth import manager as auth_manager diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 387b0343a..9799c3dea 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -17,7 +17,6 @@ import base64 import traceback from webob import exc -from xml.dom import minidom from nova import compute from nova import exception @@ -32,7 +31,6 @@ import nova.api.openstack.views.flavors import nova.api.openstack.views.images import nova.api.openstack.views.servers from nova.api.openstack import wsgi -from nova.auth import manager as auth_manager import nova.api.openstack from nova.scheduler import api as scheduler_api diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index 250848165..91b063cad 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -69,8 +69,9 @@ class Controller(controller.OpenstackCreateInstanceController): doing that (shared with Servers). """ - def __init__(self): + def __init__(self, version): self.compute_api = compute.API() + self.version = version super(Controller, self).__init__() def index(self, req): @@ -160,16 +161,18 @@ class Controller(controller.OpenstackCreateInstanceController): blob=cipher_text)) return cooked - # Assume OS 1.0 functionality for these overrides. - - def _image_id_from_req_data(self, data): - return data['server']['imageId'] + def _image_ref_from_req_data(self, data): + if self.version == '1.0': + return data['server']['imageId'] + return data['server']['imageRef'] def _flavor_id_from_req_data(self, data): - return data['server']['flavorId'] + if self.version == '1.0': + return data['server']['flavorId'] + return data['server']['flavorRef'] -def create_resource(): +def create_resource(version): metadata = { "attributes": { "zone": ["id", "api_url", "name", "capabilities"], @@ -185,5 +188,5 @@ def create_resource(): 'application/xml': controller.ServerXMLDeserializer(), } - return wsgi.Resource(Controller(), serializers=serializers, + return wsgi.Resource(Controller(version), serializers=serializers, deserializers=deserializers) -- cgit From e7e501a1a77f01247d84fa88275e858a338c6c95 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 13 Jun 2011 10:59:58 -0700 Subject: removed yucky None return types --- nova/api/openstack/create_instance_controller.py | 16 ++++++++++++---- nova/api/openstack/servers.py | 11 +++++++---- nova/api/openstack/zones.py | 8 +++++--- 3 files changed, 24 insertions(+), 11 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/create_instance_controller.py b/nova/api/openstack/create_instance_controller.py index 2d807470a..90f2542d9 100644 --- a/nova/api/openstack/create_instance_controller.py +++ b/nova/api/openstack/create_instance_controller.py @@ -39,6 +39,14 @@ LOG = logging.getLogger('nova.api.openstack.create_instance_controller') FLAGS = flags.FLAGS +class CreateFault(exception.NovaException): + message = _("Invalid parameters given to create_instance.") + + def __init__(self, fault): + self.fault = fault + super(CreateFault, self).__init__() + + class OpenstackCreateInstanceController(object): """This is the base class for OS API Controllers that are capable of creating instances (currently Servers and Zones). @@ -75,7 +83,7 @@ class OpenstackCreateInstanceController(object): return type from this method is left to the caller. """ if not body: - return (None, faults.Fault(exc.HTTPUnprocessableEntity())) + raise faults.Fault(exc.HTTPUnprocessableEntity()) context = req.environ['nova.context'] @@ -99,7 +107,7 @@ class OpenstackCreateInstanceController(object): except Exception, e: msg = _("Cannot find requested image %(image_href)s: %(e)s" % locals()) - return (None, faults.Fault(exc.HTTPBadRequest(msg))) + raise faults.Fault(exc.HTTPBadRequest(msg)) personality = body['server'].get('personality') @@ -111,7 +119,7 @@ class OpenstackCreateInstanceController(object): if not 'name' in body['server']: msg = _("Server name is not defined") - return (None, exc.HTTPBadRequest(msg)) + raise exc.HTTPBadRequest(msg) zone_blob = body['server'].get('blob') name = body['server']['name'] @@ -150,7 +158,7 @@ class OpenstackCreateInstanceController(object): self._handle_quota_error(error) except exception.ImageNotFound as error: msg = _("Can not find requested image") - return faults.Fault(exc.HTTPBadRequest(msg)) + raise faults.Fault(exc.HTTPBadRequest(msg)) # Let the caller deal with unhandled exceptions. diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 9799c3dea..1b18c4ecb 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -108,10 +108,13 @@ class Controller(base_controller.OpenstackCreateInstanceController): def create(self, req, body): """ Creates a new server for a given user """ - extra_values, result = \ - self.create_instance(req, body, self.compute_api.create) - if extra_values is None: - return result # a Fault. + extra_values = None + result = None + try: + extra_values, result = \ + self.create_instance(req, body, self.compute_api.create) + except faults.Fault, f: + return f instances = result diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index 91b063cad..7ccb8555b 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -130,10 +130,12 @@ class Controller(controller.OpenstackCreateInstanceController): Returns a reservation ID (a UUID). """ - extra_values, result = self.create_instance(req, body, + result = None + try: + extra_values, result = self.create_instance(req, body, self.compute_api.create_all_at_once) - if extra_values is None: - return result # a Fault. + except faults.Fault, f: + return f reservation_id = result return {'reservation_id': reservation_id} -- cgit From a3282ac30255a63f166947a052af0fcda4992621 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Tue, 14 Jun 2011 11:40:15 -0700 Subject: refactored out controller base class to use aggregation over inheritance --- nova/api/openstack/create_instance_controller.py | 344 ---------------------- nova/api/openstack/create_instance_helper.py | 346 +++++++++++++++++++++++ nova/api/openstack/servers.py | 33 ++- nova/api/openstack/zones.py | 48 ++-- nova/tests/api/openstack/test_servers.py | 14 +- 5 files changed, 404 insertions(+), 381 deletions(-) delete mode 100644 nova/api/openstack/create_instance_controller.py create mode 100644 nova/api/openstack/create_instance_helper.py (limited to 'nova') diff --git a/nova/api/openstack/create_instance_controller.py b/nova/api/openstack/create_instance_controller.py deleted file mode 100644 index 90f2542d9..000000000 --- a/nova/api/openstack/create_instance_controller.py +++ /dev/null @@ -1,344 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 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. - -import base64 -import re -import webob - -from webob import exc -from xml.dom import minidom - -from nova import exception -from nova import flags -from nova import log as logging -import nova.image -from nova import quota -from nova import utils - -from nova.compute import instance_types -from nova.api.openstack import faults -from nova.api.openstack import wsgi -from nova.auth import manager as auth_manager - - -LOG = logging.getLogger('nova.api.openstack.create_instance_controller') -FLAGS = flags.FLAGS - - -class CreateFault(exception.NovaException): - message = _("Invalid parameters given to create_instance.") - - def __init__(self, fault): - self.fault = fault - super(CreateFault, self).__init__() - - -class OpenstackCreateInstanceController(object): - """This is the base class for OS API Controllers that - are capable of creating instances (currently Servers and Zones). - - Once we stabilize the Zones portion of the API we may be able - to move this code back into servers.py - """ - - def __init__(self): - """We need the image service to create an instance.""" - self._image_service = utils.import_object(FLAGS.image_service) - super(OpenstackCreateInstanceController, self).__init__() - - # Default to the 1.0 naming scheme. - - def _image_ref_from_req_data(self, data): - return data['server']['imageId'] - - def _flavor_id_from_req_data(self, data): - return data['server']['flavorId'] - - def _get_server_admin_password(self, server): - """ Determine the admin password for a server on creation """ - return utils.generate_password(16) - - def create_instance(self, req, body, create_method): - """Creates a new server for the given user. The approach - used depends on the create_method. For example, the standard - POST /server call uses compute.api.create(), while - POST /zones/server uses compute.api.create_all_at_once(). - - The problem is, both approaches return different values (i.e. - [instance dicts] vs. reservation_id). So the handling of the - return type from this method is left to the caller. - """ - if not body: - raise faults.Fault(exc.HTTPUnprocessableEntity()) - - context = req.environ['nova.context'] - - password = self._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._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(msg)) - - personality = body['server'].get('personality') - - injected_files = [] - if personality: - injected_files = self._get_injected_files(personality) - - flavor_id = self._flavor_id_from_req_data(body) - - if not 'name' in body['server']: - msg = _("Server name is not defined") - raise exc.HTTPBadRequest(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') - - 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 - ) - ) - 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(msg)) - - # Let the caller deal with unhandled exceptions. - - def _handle_quota_error(self, error): - """ - Reraise quota errors as api-specific http exceptions - """ - if error.code == "OnsetFileLimitExceeded": - expl = _("Personality file limit exceeded") - raise exc.HTTPBadRequest(explanation=expl) - if error.code == "OnsetFilePathLimitExceeded": - expl = _("Personality file path too long") - raise exc.HTTPBadRequest(explanation=expl) - if error.code == "OnsetFileContentLimitExceeded": - expl = _("Personality file content too long") - raise exc.HTTPBadRequest(explanation=expl) - # if the original error is okay, just reraise it - raise error - - def _deserialize_create(self, request): - """ - Deserialize a create request - - Overrides normal behavior in the case of xml content - """ - if request.content_type == "application/xml": - deserializer = ServerCreateRequestXMLDeserializer() - return deserializer.deserialize(request.body) - else: - return self._deserialize(request.body, request.get_content_type()) - - def _validate_server_name(self, value): - if not isinstance(value, basestring): - msg = _("Server name is not a string or unicode") - raise exc.HTTPBadRequest(msg) - - if value.strip() == '': - msg = _("Server name is an empty string") - raise exc.HTTPBadRequest(msg) - - def _get_kernel_ramdisk_from_image(self, req, image_id): - """Fetch an image from the ImageService, then if present, return the - associated kernel and ramdisk image IDs. - """ - context = req.environ['nova.context'] - image_meta = self._image_service.show(context, image_id) - # NOTE(sirp): extracted to a separate method to aid unit-testing, the - # new method doesn't need a request obj or an ImageService stub - kernel_id, ramdisk_id = self._do_get_kernel_ramdisk_from_image( - image_meta) - return kernel_id, ramdisk_id - - @staticmethod - def _do_get_kernel_ramdisk_from_image(image_meta): - """Given an ImageService image_meta, return kernel and ramdisk image - ids if present. - - This is only valid for `ami` style images. - """ - image_id = image_meta['id'] - if image_meta['status'] != 'active': - raise exception.ImageUnacceptable(image_id=image_id, - reason=_("status is not active")) - - if image_meta.get('container_format') != 'ami': - return None, None - - try: - kernel_id = image_meta['properties']['kernel_id'] - except KeyError: - raise exception.KernelNotFoundForImage(image_id=image_id) - - try: - ramdisk_id = image_meta['properties']['ramdisk_id'] - except KeyError: - raise exception.RamdiskNotFoundForImage(image_id=image_id) - - return kernel_id, ramdisk_id - - def _get_injected_files(self, personality): - """ - Create a list of injected files from the personality attribute - - At this time, injected_files must be formatted as a list of - (file_path, file_content) pairs for compatibility with the - underlying compute service. - """ - injected_files = [] - - for item in personality: - try: - path = item['path'] - contents = item['contents'] - except KeyError as key: - expl = _('Bad personality format: missing %s') % key - raise exc.HTTPBadRequest(explanation=expl) - except TypeError: - expl = _('Bad personality format') - raise exc.HTTPBadRequest(explanation=expl) - try: - contents = base64.b64decode(contents) - except TypeError: - expl = _('Personality content for %s cannot be decoded') % path - raise exc.HTTPBadRequest(explanation=expl) - injected_files.append((path, contents)) - return injected_files - - -class ServerXMLDeserializer(wsgi.XMLDeserializer): - """ - Deserializer to handle xml-formatted server create requests. - - Handles standard server attributes as well as optional metadata - and personality attributes - """ - - def create(self, string): - """Deserialize an xml-formatted server create request""" - dom = minidom.parseString(string) - server = self._extract_server(dom) - return {'server': server} - - def _extract_server(self, node): - """Marshal the server attribute of a parsed request""" - server = {} - server_node = self._find_first_child_named(node, 'server') - for attr in ["name", "imageId", "flavorId", "imageRef", "flavorRef"]: - if server_node.getAttribute(attr): - server[attr] = server_node.getAttribute(attr) - metadata = self._extract_metadata(server_node) - if metadata is not None: - server["metadata"] = metadata - personality = self._extract_personality(server_node) - if personality is not None: - server["personality"] = personality - return server - - def _extract_metadata(self, server_node): - """Marshal the metadata attribute of a parsed request""" - metadata_node = self._find_first_child_named(server_node, "metadata") - if metadata_node is None: - return None - metadata = {} - for meta_node in self._find_children_named(metadata_node, "meta"): - key = meta_node.getAttribute("key") - metadata[key] = self._extract_text(meta_node) - return metadata - - def _extract_personality(self, server_node): - """Marshal the personality attribute of a parsed request""" - personality_node = \ - self._find_first_child_named(server_node, "personality") - if personality_node is None: - return None - personality = [] - for file_node in self._find_children_named(personality_node, "file"): - item = {} - if file_node.hasAttribute("path"): - item["path"] = file_node.getAttribute("path") - item["contents"] = self._extract_text(file_node) - personality.append(item) - return personality - - 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: - if node.nodeName == name: - return node - return None - - def _find_children_named(self, parent, name): - """Return all of a nodes children who have the given name""" - for node in parent.childNodes: - if node.nodeName == name: - yield node - - def _extract_text(self, node): - """Get the text field contained by the given node""" - if len(node.childNodes) == 1: - child = node.childNodes[0] - if child.nodeType == child.TEXT_NODE: - return child.nodeValue - return "" diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py new file mode 100644 index 000000000..fbc6318ef --- /dev/null +++ b/nova/api/openstack/create_instance_helper.py @@ -0,0 +1,346 @@ +# 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. + +import base64 +import re +import webob + +from webob import exc +from xml.dom import minidom + +from nova import exception +from nova import flags +from nova import log as logging +import nova.image +from nova import quota +from nova import utils + +from nova.compute import instance_types +from nova.api.openstack import faults +from nova.api.openstack import wsgi +from nova.auth import manager as auth_manager + + +LOG = logging.getLogger('nova.api.openstack.create_instance_helper') +FLAGS = flags.FLAGS + + +class CreateFault(exception.NovaException): + message = _("Invalid parameters given to create_instance.") + + def __init__(self, fault): + self.fault = fault + super(CreateFault, self).__init__() + + +class CreateInstanceHelper(object): + """This is the base class for OS API Controllers that + are capable of creating instances (currently Servers and Zones). + + Once we stabilize the Zones portion of the API we may be able + to move this code back into servers.py + """ + + def __init__(self, controller): + """We need the image service to create an instance.""" + self.controller = controller + self._image_service = utils.import_object(FLAGS.image_service) + super(CreateInstanceHelper, self).__init__() + + def create_instance(self, req, body, create_method): + """Creates a new server for the given user. The approach + used depends on the create_method. For example, the standard + POST /server call uses compute.api.create(), while + POST /zones/server uses compute.api.create_all_at_once(). + + The problem is, both approaches return different values (i.e. + [instance dicts] vs. reservation_id). So the handling of the + return type from this method is left to the caller. + """ + 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(msg)) + + personality = body['server'].get('personality') + + injected_files = [] + if personality: + injected_files = self._get_injected_files(personality) + + 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(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') + + 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 + ) + ) + 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(msg)) + + # Let the caller deal with unhandled exceptions. + + def _handle_quota_error(self, error): + """ + Reraise quota errors as api-specific http exceptions + """ + if error.code == "OnsetFileLimitExceeded": + expl = _("Personality file limit exceeded") + raise exc.HTTPBadRequest(explanation=expl) + if error.code == "OnsetFilePathLimitExceeded": + expl = _("Personality file path too long") + raise exc.HTTPBadRequest(explanation=expl) + if error.code == "OnsetFileContentLimitExceeded": + expl = _("Personality file content too long") + raise exc.HTTPBadRequest(explanation=expl) + # if the original error is okay, just reraise it + raise error + + def _deserialize_create(self, request): + """ + Deserialize a create request + + Overrides normal behavior in the case of xml content + """ + if request.content_type == "application/xml": + deserializer = ServerCreateRequestXMLDeserializer() + return deserializer.deserialize(request.body) + else: + return self._deserialize(request.body, request.get_content_type()) + + def _validate_server_name(self, value): + if not isinstance(value, basestring): + msg = _("Server name is not a string or unicode") + raise exc.HTTPBadRequest(msg) + + if value.strip() == '': + msg = _("Server name is an empty string") + raise exc.HTTPBadRequest(msg) + + def _get_kernel_ramdisk_from_image(self, req, image_id): + """Fetch an image from the ImageService, then if present, return the + associated kernel and ramdisk image IDs. + """ + context = req.environ['nova.context'] + image_meta = self._image_service.show(context, image_id) + # NOTE(sirp): extracted to a separate method to aid unit-testing, the + # new method doesn't need a request obj or an ImageService stub + kernel_id, ramdisk_id = self._do_get_kernel_ramdisk_from_image( + image_meta) + return kernel_id, ramdisk_id + + @staticmethod + def _do_get_kernel_ramdisk_from_image(image_meta): + """Given an ImageService image_meta, return kernel and ramdisk image + ids if present. + + This is only valid for `ami` style images. + """ + image_id = image_meta['id'] + if image_meta['status'] != 'active': + raise exception.ImageUnacceptable(image_id=image_id, + reason=_("status is not active")) + + if image_meta.get('container_format') != 'ami': + return None, None + + try: + kernel_id = image_meta['properties']['kernel_id'] + except KeyError: + raise exception.KernelNotFoundForImage(image_id=image_id) + + try: + ramdisk_id = image_meta['properties']['ramdisk_id'] + except KeyError: + raise exception.RamdiskNotFoundForImage(image_id=image_id) + + return kernel_id, ramdisk_id + + def _get_injected_files(self, personality): + """ + Create a list of injected files from the personality attribute + + At this time, injected_files must be formatted as a list of + (file_path, file_content) pairs for compatibility with the + underlying compute service. + """ + injected_files = [] + + for item in personality: + try: + path = item['path'] + contents = item['contents'] + except KeyError as key: + expl = _('Bad personality format: missing %s') % key + raise exc.HTTPBadRequest(explanation=expl) + except TypeError: + expl = _('Bad personality format') + raise exc.HTTPBadRequest(explanation=expl) + try: + contents = base64.b64decode(contents) + except TypeError: + expl = _('Personality content for %s cannot be decoded') % path + raise exc.HTTPBadRequest(explanation=expl) + injected_files.append((path, contents)) + return injected_files + + def _get_server_admin_password_old_style(self, server): + """ Determine the admin password for a server on creation """ + return utils.generate_password(16) + + def _get_server_admin_password_new_style(self, server): + """ Determine the admin password for a server on creation """ + password = server.get('adminPass') + + if password is None: + return utils.generate_password(16) + if not isinstance(password, basestring) or password == '': + msg = _("Invalid adminPass") + raise exc.HTTPBadRequest(msg) + return password + + +class ServerXMLDeserializer(wsgi.XMLDeserializer): + """ + Deserializer to handle xml-formatted server create requests. + + Handles standard server attributes as well as optional metadata + and personality attributes + """ + + def create(self, string): + """Deserialize an xml-formatted server create request""" + dom = minidom.parseString(string) + server = self._extract_server(dom) + return {'server': server} + + def _extract_server(self, node): + """Marshal the server attribute of a parsed request""" + server = {} + server_node = self._find_first_child_named(node, 'server') + for attr in ["name", "imageId", "flavorId", "imageRef", "flavorRef"]: + if server_node.getAttribute(attr): + server[attr] = server_node.getAttribute(attr) + metadata = self._extract_metadata(server_node) + if metadata is not None: + server["metadata"] = metadata + personality = self._extract_personality(server_node) + if personality is not None: + server["personality"] = personality + return server + + def _extract_metadata(self, server_node): + """Marshal the metadata attribute of a parsed request""" + metadata_node = self._find_first_child_named(server_node, "metadata") + if metadata_node is None: + return None + metadata = {} + for meta_node in self._find_children_named(metadata_node, "meta"): + key = meta_node.getAttribute("key") + metadata[key] = self._extract_text(meta_node) + return metadata + + def _extract_personality(self, server_node): + """Marshal the personality attribute of a parsed request""" + personality_node = \ + self._find_first_child_named(server_node, "personality") + if personality_node is None: + return None + personality = [] + for file_node in self._find_children_named(personality_node, "file"): + item = {} + if file_node.hasAttribute("path"): + item["path"] = file_node.getAttribute("path") + item["contents"] = self._extract_text(file_node) + personality.append(item) + return personality + + 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: + if node.nodeName == name: + return node + return None + + def _find_children_named(self, parent, name): + """Return all of a nodes children who have the given name""" + for node in parent.childNodes: + if node.nodeName == name: + yield node + + def _extract_text(self, node): + """Get the text field contained by the given node""" + if len(node.childNodes) == 1: + child = node.childNodes[0] + if child.nodeType == child.TEXT_NODE: + return child.nodeValue + return "" diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 1b18c4ecb..5c967c40f 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -24,7 +24,7 @@ from nova import flags from nova import log as logging from nova import utils from nova.api.openstack import common -from nova.api.openstack import create_instance_controller as base_controller +from nova.api.openstack import create_instance_helper as helper from nova.api.openstack import faults import nova.api.openstack.views.addresses import nova.api.openstack.views.flavors @@ -39,11 +39,12 @@ LOG = logging.getLogger('nova.api.openstack.servers') FLAGS = flags.FLAGS -class Controller(base_controller.OpenstackCreateInstanceController): +class Controller(object): """ The Server API controller for the OpenStack API """ def __init__(self): self.compute_api = compute.API() + self.helper = helper.CreateInstanceHelper(self) super(Controller, self).__init__() def index(self, req): @@ -111,8 +112,8 @@ class Controller(base_controller.OpenstackCreateInstanceController): extra_values = None result = None try: - extra_values, result = \ - self.create_instance(req, body, self.compute_api.create) + extra_values, result = self.helper.create_instance( + req, body, self.compute_api.create) except faults.Fault, f: return f @@ -141,7 +142,7 @@ class Controller(base_controller.OpenstackCreateInstanceController): if 'name' in body['server']: name = body['server']['name'] - self._validate_server_name(name) + self.helper._validate_server_name(name) update_dict['display_name'] = name.strip() self._parse_update(ctxt, id, body, update_dict) @@ -403,6 +404,13 @@ class Controller(base_controller.OpenstackCreateInstanceController): class ControllerV10(Controller): + + def _image_ref_from_req_data(self, data): + return data['server']['imageId'] + + def _flavor_id_from_req_data(self, data): + return data['server']['flavorId'] + def _get_view_builder(self, req): addresses_builder = nova.api.openstack.views.addresses.ViewBuilderV10() return nova.api.openstack.views.servers.ViewBuilderV10( @@ -453,6 +461,10 @@ class ControllerV10(Controller): response.empty_body = True return response + def _get_server_admin_password(self, server): + """ Determine the admin password for a server on creation """ + return self.helper._get_server_admin_password_old_style(server) + class ControllerV11(Controller): def _image_ref_from_req_data(self, data): @@ -567,14 +579,7 @@ class ControllerV11(Controller): def _get_server_admin_password(self, server): """ Determine the admin password for a server on creation """ - password = server.get('adminPass') - - if password is None: - return utils.generate_password(16) - if not isinstance(password, basestring) or password == '': - msg = _("Invalid adminPass") - raise exc.HTTPBadRequest(msg) - return password + return self.helper._get_server_admin_password_new_style(server) def create_resource(version='1.0'): @@ -610,7 +615,7 @@ def create_resource(version='1.0'): } deserializers = { - 'application/xml': base_controller.ServerXMLDeserializer(), + 'application/xml': helper.ServerXMLDeserializer(), } return wsgi.Resource(controller, serializers=serializers, diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index 7ccb8555b..c34360e01 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -25,8 +25,9 @@ from nova import log as logging from nova.compute import api as compute from nova.scheduler import api -from nova.api.openstack import create_instance_controller as controller +from nova.api.openstack import create_instance_helper as helper from nova.api.openstack import common +from nova.api.openstack import faults from nova.api.openstack import wsgi @@ -62,16 +63,12 @@ def check_encryption_key(func): return wrapped -class Controller(controller.OpenstackCreateInstanceController): - """Controller for Zone resources. Since we can also create instances - via /zone/boot, this controller is derived from - OpenstackCreateInstanceController, which contains all the logic for - doing that (shared with Servers). - """ +class Controller(object): + """Controller for Zone resources.""" - def __init__(self, version): + def __init__(self): self.compute_api = compute.API() - self.version = version + self.helper = helper.CreateInstanceHelper(self) super(Controller, self).__init__() def index(self, req): @@ -132,7 +129,7 @@ class Controller(controller.OpenstackCreateInstanceController): """ result = None try: - extra_values, result = self.create_instance(req, body, + extra_values, result = self.helper.create_instance(req, body, self.compute_api.create_all_at_once) except faults.Fault, f: return f @@ -164,17 +161,36 @@ class Controller(controller.OpenstackCreateInstanceController): return cooked def _image_ref_from_req_data(self, data): - if self.version == '1.0': - return data['server']['imageId'] + return data['server']['imageId'] + + def _flavor_id_from_req_data(self, data): + return data['server']['flavorId'] + + def _get_server_admin_password(self, server): + """ Determine the admin password for a server on creation """ + return self.helper._get_server_admin_password_old_style(server) + + +class ControllerV11(object): + """Controller for 1.1 Zone resources.""" + + def _get_server_admin_password(self, server): + """ Determine the admin password for a server on creation """ + return self.helper._get_server_admin_password_new_style(server) + + def _image_ref_from_req_data(self, data): return data['server']['imageRef'] def _flavor_id_from_req_data(self, data): - if self.version == '1.0': - return data['server']['flavorId'] return data['server']['flavorRef'] def create_resource(version): + controller = { + '1.0': Controller, + '1.1': ControllerV11, + }[version]() + metadata = { "attributes": { "zone": ["id", "api_url", "name", "capabilities"], @@ -187,8 +203,8 @@ def create_resource(version): } deserializers = { - 'application/xml': controller.ServerXMLDeserializer(), + 'application/xml': helper.ServerXMLDeserializer(), } - return wsgi.Resource(Controller(version), serializers=serializers, + return wsgi.Resource(controller, serializers=serializers, deserializers=deserializers) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 529ca83c5..8357df594 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -31,7 +31,7 @@ from nova import test from nova import utils import nova.api.openstack from nova.api.openstack import servers -from nova.api.openstack import create_instance_controller +from nova.api.openstack import create_instance_helper import nova.compute.api from nova.compute import instance_types from nova.compute import power_state @@ -570,8 +570,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.network.manager.VlanManager, 'allocate_fixed_ip', fake_method) self.stubs.Set( - nova.api.openstack.create_instance_controller.\ - OpenstackCreateInstanceController, + nova.api.openstack.create_instance_helper.CreateInstanceHelper, "_get_kernel_ramdisk_from_image", kernel_ramdisk_mapping) self.stubs.Set(nova.compute.api.API, "_find_host", find_host) @@ -1531,7 +1530,7 @@ class ServersTest(test.TestCase): class TestServerCreateRequestXMLDeserializer(unittest.TestCase): def setUp(self): - self.deserializer = create_instance_controller.ServerXMLDeserializer() + self.deserializer = create_instance_helper.ServerXMLDeserializer() def test_minimal_request(self): serial_request = """ @@ -1863,7 +1862,8 @@ class TestServerInstanceCreation(test.TestCase): compute_api = MockComputeAPI() self.stubs.Set(nova.compute, 'API', make_stub_method(compute_api)) - self.stubs.Set(nova.api.openstack.servers.Controller, + self.stubs.Set( + nova.api.openstack.create_instance_helper.CreateInstanceHelper, '_get_kernel_ramdisk_from_image', make_stub_method((1, 1))) return compute_api @@ -2119,6 +2119,6 @@ class TestGetKernelRamdiskFromImage(test.TestCase): @staticmethod def _get_k_r(image_meta): """Rebinding function to a shorter name for convenience""" - kernel_id, ramdisk_id = \ - servers.Controller._do_get_kernel_ramdisk_from_image(image_meta) + kernel_id, ramdisk_id = create_instance_helper.CreateInstanceHelper. \ + _do_get_kernel_ramdisk_from_image(image_meta) return kernel_id, ramdisk_id -- cgit From b331ae15cfaa0bfbe06bb4b1947f12e56033c333 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Tue, 14 Jun 2011 11:55:43 -0700 Subject: version passing cleanup --- nova/api/openstack/__init__.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index e0ae55105..a22889e83 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -81,7 +81,9 @@ class APIRouter(base_wsgi.Router): self._setup_routes(mapper) super(APIRouter, self).__init__(mapper) - def _setup_routes(self, mapper, version='1.0'): + def _setup_routes(self, mapper, version): + """Routes common to all versions.""" + server_members = self.server_members server_members['action'] = 'POST' if FLAGS.allow_admin_api: @@ -98,14 +100,6 @@ class APIRouter(base_wsgi.Router): server_members['reset_network'] = 'POST' server_members['inject_network_info'] = 'POST' - mapper.resource("zone", "zones", - controller=zones.create_resource(version), - collection={'detail': 'GET', - 'info': 'GET', - 'select': 'POST', - 'boot': 'POST' - }) - mapper.resource("user", "users", controller=users.create_resource(), collection={'detail': 'GET'}) @@ -114,11 +108,19 @@ class APIRouter(base_wsgi.Router): controller=accounts.create_resource(), collection={'detail': 'GET'}) - mapper.resource("console", "consoles", + mapper.resource("console", "consoles", controller=consoles.create_resource(), parent_resource=dict(member_name='server', collection_name='servers')) + mapper.resource("zone", "zones", + controller=zones.create_resource(version), + collection={'detail': 'GET', + 'info': 'GET', + 'select': 'POST', + 'boot': 'POST' + }) + super(APIRouter, self).__init__(mapper) @@ -126,7 +128,7 @@ class APIRouterV10(APIRouter): """Define routes specific to OpenStack API V1.0.""" def _setup_routes(self, mapper): - super(APIRouterV10, self)._setup_routes(mapper, version='1.0') + super(APIRouterV10, self)._setup_routes(mapper, '1.0') mapper.resource("server", "servers", controller=servers.create_resource('1.0'), collection={'detail': 'GET'}, @@ -162,7 +164,7 @@ class APIRouterV11(APIRouter): """Define routes specific to OpenStack API V1.1.""" def _setup_routes(self, mapper): - super(APIRouterV11, self)._setup_routes(mapper, version='1.1') + super(APIRouterV11, self)._setup_routes(mapper, '1.1') mapper.resource("server", "servers", controller=servers.create_resource('1.1'), collection={'detail': 'GET'}, -- cgit From 60a89dda55258bd7212e09e2113dca92ebd67a08 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Tue, 14 Jun 2011 12:34:10 -0700 Subject: duplicate routes moved to base class --- nova/api/openstack/__init__.py | 54 ++++++++++++++++-------------------------- 1 file changed, 21 insertions(+), 33 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index a22889e83..ddd9580d7 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -108,11 +108,6 @@ class APIRouter(base_wsgi.Router): controller=accounts.create_resource(), collection={'detail': 'GET'}) - mapper.resource("console", "consoles", - controller=consoles.create_resource(), - parent_resource=dict(member_name='server', - collection_name='servers')) - mapper.resource("zone", "zones", controller=zones.create_resource(version), collection={'detail': 'GET', @@ -121,6 +116,27 @@ class APIRouter(base_wsgi.Router): 'boot': 'POST' }) + mapper.resource("console", "consoles", + controller=consoles.create_resource(), + parent_resource=dict(member_name='server', + collection_name='servers')) + + mapper.resource("server", "servers", + controller=servers.create_resource(version), + collection={'detail': 'GET'}, + member=self.server_members) + + mapper.resource("image", "images", + controller=images.create_resource(version), + collection={'detail': 'GET'}) + + mapper.resource("limit", "limits", + controller=limits.create_resource(version)) + + mapper.resource("flavor", "flavors", + controller=flavors.create_resource(version), + collection={'detail': 'GET'}) + super(APIRouter, self).__init__(mapper) @@ -129,19 +145,10 @@ class APIRouterV10(APIRouter): def _setup_routes(self, mapper): super(APIRouterV10, self)._setup_routes(mapper, '1.0') - mapper.resource("server", "servers", - controller=servers.create_resource('1.0'), - collection={'detail': 'GET'}, - member=self.server_members) - mapper.resource("image", "images", controller=images.create_resource('1.0'), collection={'detail': 'GET'}) - mapper.resource("flavor", "flavors", - controller=flavors.create_resource('1.0'), - collection={'detail': 'GET'}) - mapper.resource("shared_ip_group", "shared_ip_groups", collection={'detail': 'GET'}, controller=shared_ip_groups.create_resource()) @@ -151,9 +158,6 @@ class APIRouterV10(APIRouter): parent_resource=dict(member_name='server', collection_name='servers')) - mapper.resource("limit", "limits", - controller=limits.create_resource('1.0')) - mapper.resource("ip", "ips", controller=ips.create_resource(), collection=dict(public='GET', private='GET'), parent_resource=dict(member_name='server', @@ -165,15 +169,6 @@ class APIRouterV11(APIRouter): def _setup_routes(self, mapper): super(APIRouterV11, self)._setup_routes(mapper, '1.1') - mapper.resource("server", "servers", - controller=servers.create_resource('1.1'), - collection={'detail': 'GET'}, - member=self.server_members) - - mapper.resource("image", "images", - controller=images.create_resource('1.1'), - collection={'detail': 'GET'}) - mapper.resource("image_meta", "meta", controller=image_metadata.create_resource(), parent_resource=dict(member_name='image', @@ -183,10 +178,3 @@ class APIRouterV11(APIRouter): controller=server_metadata.create_resource(), parent_resource=dict(member_name='server', collection_name='servers')) - - mapper.resource("flavor", "flavors", - controller=flavors.create_resource('1.1'), - collection={'detail': 'GET'}) - - mapper.resource("limit", "limits", - controller=limits.create_resource('1.1')) -- cgit From 0ce3e2af1b2d48d53c7ae6f59caca745946c6198 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Tue, 14 Jun 2011 12:59:16 -0700 Subject: removed extra init calls --- nova/api/openstack/servers.py | 1 - nova/api/openstack/zones.py | 1 - 2 files changed, 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 5c967c40f..798fdd7f7 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -45,7 +45,6 @@ class Controller(object): def __init__(self): self.compute_api = compute.API() self.helper = helper.CreateInstanceHelper(self) - super(Controller, self).__init__() def index(self, req): """ Returns a list of server names and ids for a given user """ diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index c34360e01..8864f825b 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -69,7 +69,6 @@ class Controller(object): def __init__(self): self.compute_api = compute.API() self.helper = helper.CreateInstanceHelper(self) - super(Controller, self).__init__() def index(self, req): """Return all zones in brief""" -- cgit