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 a66ae006e0a6103ee6db49ad2b8dc4506969178e Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 8 Jun 2011 15:45:23 +0000 Subject: Look for vm_mode property on images and use that if it exists to determine if image should be run in PV or HVM mode. If it doesn't exist, fall back to existing logic --- nova/compute/api.py | 6 ++- .../versions/022_add_vm_mode_to_instances.py | 43 ++++++++++++++++++++++ nova/db/sqlalchemy/models.py | 1 + nova/virt/xenapi/vmops.py | 11 ++++-- 4 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/022_add_vm_mode_to_instances.py (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 4f327fab1..5ea086088 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -166,6 +166,9 @@ class API(base.Base): os_type = None if 'properties' in image and 'os_type' in image['properties']: os_type = image['properties']['os_type'] + vm_mode = None + if 'properties' in image and 'vm_mode' in image['properties']: + vm_mode = image['properties']['vm_mode'] if kernel_id is None: kernel_id = image['properties'].get('kernel_id', None) @@ -224,7 +227,8 @@ class API(base.Base): 'locked': False, 'metadata': metadata, 'availability_zone': availability_zone, - 'os_type': os_type} + 'os_type': os_type, + 'vm_mode': vm_mode} elevated = context.elevated() instances = [] LOG.debug(_("Going to run %s instances..."), num_instances) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/022_add_vm_mode_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/022_add_vm_mode_to_instances.py new file mode 100644 index 000000000..5b6a25e41 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/022_add_vm_mode_to_instances.py @@ -0,0 +1,43 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 OpenStack LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from sqlalchemy import Column, Integer, MetaData, String, Table + +meta = MetaData() + +instances = Table('instances', meta, + Column('id', Integer(), primary_key=True, nullable=False), + ) + +instances_vm_mode = Column('vm_mode', + String(length=255, convert_unicode=False, + assert_unicode=None, unicode_error=None, + _warn_on_bytestring=False), + nullable=True) + + +def upgrade(migrate_engine): + # Upgrade operations go here. Don't create your own engine; + # bind migrate_engine to your metadata + meta.bind = migrate_engine + + instances.create_column(instances_vm_mode) + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + + instances.drop_column('vm_mode') diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 239f6e96a..612ccc93f 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -232,6 +232,7 @@ class Instance(BASE, NovaBase): locked = Column(Boolean) os_type = Column(String(255)) + vm_mode = Column(String(255)) # TODO(vish): see Ewan's email about state improvements, probably # should be in a driver base class or some such diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 32dae97c2..3b793113f 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -160,9 +160,14 @@ class VMOps(object): # Create the VM ref and attach the first disk first_vdi_ref = self._session.call_xenapi('VDI.get_by_uuid', vdis[0]['vdi_uuid']) - use_pv_kernel = VMHelper.determine_is_pv(self._session, - instance.id, first_vdi_ref, disk_image_type, - instance.os_type) + if instance.vm_mode in ('pv', 'PV'): + use_pv_kernel = True + elif instance.vm_mode in ('hv', 'HV', 'hvm', 'HVM'): + use_pv_kernel = False + else: + use_pv_kernel = VMHelper.determine_is_pv(self._session, + instance.id, first_vdi_ref, disk_image_type, + instance.os_type) vm_ref = VMHelper.create_vm(self._session, instance, kernel, ramdisk, use_pv_kernel) VMHelper.create_vbd(session=self._session, vm_ref=vm_ref, -- cgit From f20c73bbe395a93c087562966b10ade3c9f32afc Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 8 Jun 2011 22:28:28 +0000 Subject: Normalize and update database with used vm_mode --- nova/virt/xenapi/vmops.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 3b793113f..99d2dc758 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -160,14 +160,24 @@ class VMOps(object): # Create the VM ref and attach the first disk first_vdi_ref = self._session.call_xenapi('VDI.get_by_uuid', vdis[0]['vdi_uuid']) - if instance.vm_mode in ('pv', 'PV'): + + vm_mode = instance.vm_mode and instance.vm_mode.lower() + if vm_mode == 'pv': use_pv_kernel = True - elif instance.vm_mode in ('hv', 'HV', 'hvm', 'HVM'): + elif vm_mode in ('hv', 'hvm'): use_pv_kernel = False + vm_mode = 'hvm' # Normalize else: use_pv_kernel = VMHelper.determine_is_pv(self._session, instance.id, first_vdi_ref, disk_image_type, instance.os_type) + vm_mode = use_pv_kernel and 'pv' or 'hvm' + + if instance.vm_mode != vm_mode: + # Update database with normalized (or determined) value + db.instance_update(context.get_admin_context(), + instance['id'], {'vm_mode': vm_mode}) + vm_ref = VMHelper.create_vm(self._session, instance, kernel, ramdisk, use_pv_kernel) VMHelper.create_vbd(session=self._session, vm_ref=vm_ref, -- cgit From 467bda72a05a0fd9fa4d7d417da422eea04de220 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Thu, 9 Jun 2011 18:51:21 +0000 Subject: 022 migration has already been added, so make ours 023 now --- .../versions/022_add_vm_mode_to_instances.py | 43 ---------------------- .../versions/023_add_vm_mode_to_instances.py | 43 ++++++++++++++++++++++ 2 files changed, 43 insertions(+), 43 deletions(-) delete mode 100644 nova/db/sqlalchemy/migrate_repo/versions/022_add_vm_mode_to_instances.py create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/023_add_vm_mode_to_instances.py (limited to 'nova') diff --git a/nova/db/sqlalchemy/migrate_repo/versions/022_add_vm_mode_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/022_add_vm_mode_to_instances.py deleted file mode 100644 index 5b6a25e41..000000000 --- a/nova/db/sqlalchemy/migrate_repo/versions/022_add_vm_mode_to_instances.py +++ /dev/null @@ -1,43 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 OpenStack LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from sqlalchemy import Column, Integer, MetaData, String, Table - -meta = MetaData() - -instances = Table('instances', meta, - Column('id', Integer(), primary_key=True, nullable=False), - ) - -instances_vm_mode = Column('vm_mode', - String(length=255, convert_unicode=False, - assert_unicode=None, unicode_error=None, - _warn_on_bytestring=False), - nullable=True) - - -def upgrade(migrate_engine): - # Upgrade operations go here. Don't create your own engine; - # bind migrate_engine to your metadata - meta.bind = migrate_engine - - instances.create_column(instances_vm_mode) - - -def downgrade(migrate_engine): - meta.bind = migrate_engine - - instances.drop_column('vm_mode') diff --git a/nova/db/sqlalchemy/migrate_repo/versions/023_add_vm_mode_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/023_add_vm_mode_to_instances.py new file mode 100644 index 000000000..5b6a25e41 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/023_add_vm_mode_to_instances.py @@ -0,0 +1,43 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 OpenStack LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from sqlalchemy import Column, Integer, MetaData, String, Table + +meta = MetaData() + +instances = Table('instances', meta, + Column('id', Integer(), primary_key=True, nullable=False), + ) + +instances_vm_mode = Column('vm_mode', + String(length=255, convert_unicode=False, + assert_unicode=None, unicode_error=None, + _warn_on_bytestring=False), + nullable=True) + + +def upgrade(migrate_engine): + # Upgrade operations go here. Don't create your own engine; + # bind migrate_engine to your metadata + meta.bind = migrate_engine + + instances.create_column(instances_vm_mode) + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + + instances.drop_column('vm_mode') -- 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 a86523d5ae17b4e5507b14fade0a87c5434f2cac Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Fri, 10 Jun 2011 17:26:25 +0000 Subject: Fix copyright year --- .../db/sqlalchemy/migrate_repo/versions/023_add_vm_mode_to_instances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/migrate_repo/versions/023_add_vm_mode_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/023_add_vm_mode_to_instances.py index 5b6a25e41..16b9826d9 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/023_add_vm_mode_to_instances.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/023_add_vm_mode_to_instances.py @@ -1,6 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2010 OpenStack LLC. +# Copyright 2011 OpenStack LLC. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain -- cgit From 92423564aa24124b0144264d6cd1c57c78eaf5dd Mon Sep 17 00:00:00 2001 From: William Wolf Date: Fri, 10 Jun 2011 16:41:14 -0400 Subject: return body correctly as object instead of a string, with tests, also check for empty body on requests that need a body --- nova/api/openstack/server_metadata.py | 11 +++++++++-- nova/api/openstack/wsgi.py | 4 ++++ nova/tests/api/openstack/test_server_metadata.py | 25 ++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index b38b84a2a..c17d77d46 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -37,12 +37,18 @@ class Controller(object): meta_dict[key] = value return dict(metadata=meta_dict) + def _check_body(self, body): + if not body: + expl = _('No Request Body') + raise exc.HTTPBadRequest(explanation=expl) + def index(self, req, server_id): """ Returns the list of metadata for a given instance """ context = req.environ['nova.context'] return self._get_metadata(context, server_id) def create(self, req, server_id, body): + self._check_body(body) context = req.environ['nova.context'] metadata = body.get('metadata') try: @@ -51,9 +57,10 @@ class Controller(object): metadata) except quota.QuotaError as error: self._handle_quota_error(error) - return req.body + return body def update(self, req, server_id, id, body): + self._check_body(body) context = req.environ['nova.context'] if not id in body: expl = _('Request body and URI mismatch') @@ -68,7 +75,7 @@ class Controller(object): except quota.QuotaError as error: self._handle_quota_error(error) - return req.body + return body def show(self, req, server_id, id): """ Return a single metadata item """ diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index ddf4e6fa9..8c30cdeb9 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -348,16 +348,20 @@ class Resource(wsgi.Application): LOG.debug("%(method)s %(url)s" % {"method": request.method, "url": request.url}) + print "CALL" try: action, action_args, accept = self.deserializer.deserialize( request) except exception.InvalidContentType: return webob.exc.HTTPBadRequest(_("Unsupported Content-Type")) + print "CALL2" action_result = self.dispatch(request, action, action_args) + print "ACTION RESULT:", action_result, isinstance(action_result, str) #TODO(bcwaldon): find a more elegant way to pass through non-dict types if type(action_result) is dict: + print "DICT" response = self.serializer.serialize(action_result, accept) else: response = action_result diff --git a/nova/tests/api/openstack/test_server_metadata.py b/nova/tests/api/openstack/test_server_metadata.py index c4d1d4fd8..b583d40fe 100644 --- a/nova/tests/api/openstack/test_server_metadata.py +++ b/nova/tests/api/openstack/test_server_metadata.py @@ -89,6 +89,7 @@ class ServerMetaDataTest(unittest.TestCase): res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) + self.assertEqual('application/json', res.headers['Content-Type']) self.assertEqual('value1', res_dict['metadata']['key1']) def test_index_no_data(self): @@ -99,6 +100,7 @@ class ServerMetaDataTest(unittest.TestCase): res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) + self.assertEqual('application/json', res.headers['Content-Type']) self.assertEqual(0, len(res_dict['metadata'])) def test_show(self): @@ -109,6 +111,7 @@ class ServerMetaDataTest(unittest.TestCase): res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) + self.assertEqual('application/json', res.headers['Content-Type']) self.assertEqual('value5', res_dict['key5']) def test_show_meta_not_found(self): @@ -140,8 +143,19 @@ class ServerMetaDataTest(unittest.TestCase): res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) + self.assertEqual('application/json', res.headers['Content-Type']) self.assertEqual('value1', res_dict['metadata']['key1']) + def test_create_empty_body(self): + self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + return_create_instance_metadata) + req = webob.Request.blank('/v1.1/servers/1/meta') + req.environ['api.version'] = '1.1' + req.method = 'POST' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) + def test_update_item(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', return_create_instance_metadata) @@ -152,9 +166,20 @@ class ServerMetaDataTest(unittest.TestCase): req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(200, res.status_int) + self.assertEqual('application/json', res.headers['Content-Type']) res_dict = json.loads(res.body) self.assertEqual('value1', res_dict['key1']) + def test_update_item_empty_body(self): + self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + return_create_instance_metadata) + req = webob.Request.blank('/v1.1/servers/1/meta/key1') + req.environ['api.version'] = '1.1' + req.method = 'PUT' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) + def test_update_item_too_many_keys(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', return_create_instance_metadata) -- cgit From cbaa94ac255cde729bae3257da6657a114755224 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Fri, 10 Jun 2011 16:43:13 -0400 Subject: got rid of prints for debugging --- nova/api/openstack/wsgi.py | 4 ---- 1 file changed, 4 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 8c30cdeb9..ddf4e6fa9 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -348,20 +348,16 @@ class Resource(wsgi.Application): LOG.debug("%(method)s %(url)s" % {"method": request.method, "url": request.url}) - print "CALL" try: action, action_args, accept = self.deserializer.deserialize( request) except exception.InvalidContentType: return webob.exc.HTTPBadRequest(_("Unsupported Content-Type")) - print "CALL2" action_result = self.dispatch(request, action, action_args) - print "ACTION RESULT:", action_result, isinstance(action_result, str) #TODO(bcwaldon): find a more elegant way to pass through non-dict types if type(action_result) is dict: - print "DICT" response = self.serializer.serialize(action_result, accept) else: response = action_result -- cgit From 8146b92f7d81eada6408f939ef25cb5393650008 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Fri, 10 Jun 2011 18:39:58 -0400 Subject: adding support for cusom serialization methods --- nova/api/openstack/wsgi.py | 11 +++++++---- nova/tests/api/openstack/test_wsgi.py | 8 ++++---- 2 files changed, 11 insertions(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index ddf4e6fa9..e71b80e2c 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -189,7 +189,10 @@ class DictSerializer(object): def serialize(self, data, action='default'): """Find local serialization method and encode response body.""" - action_method = getattr(self, action, self.default) + if action is None: + action_method = self.default + else: + action_method = getattr(self, action, self.default) return action_method(data) def default(self, data): @@ -296,7 +299,7 @@ class ResponseSerializer(object): } self.serializers.update(serializers or {}) - def serialize(self, response_data, content_type): + def serialize(self, response_data, content_type, action='default'): """Serialize a dict into a string and wrap in a wsgi.Request object. :param response_data: dict produced by the Controller @@ -307,7 +310,7 @@ class ResponseSerializer(object): response.headers['Content-Type'] = content_type serializer = self.get_serializer(content_type) - response.body = serializer.serialize(response_data) + response.body = serializer.serialize(response_data, action) return response @@ -358,7 +361,7 @@ class Resource(wsgi.Application): #TODO(bcwaldon): find a more elegant way to pass through non-dict types if type(action_result) is dict: - response = self.serializer.serialize(action_result, accept) + response = self.serializer.serialize(action_result, accept, action) else: response = action_result diff --git a/nova/tests/api/openstack/test_wsgi.py b/nova/tests/api/openstack/test_wsgi.py index ebbdc9409..0329ab218 100644 --- a/nova/tests/api/openstack/test_wsgi.py +++ b/nova/tests/api/openstack/test_wsgi.py @@ -171,11 +171,11 @@ class XMLDeserializerTest(test.TestCase): class ResponseSerializerTest(test.TestCase): def setUp(self): class JSONSerializer(object): - def serialize(self, data): + def serialize(self, data, action='default'): return 'pew_json' class XMLSerializer(object): - def serialize(self, data): + def serialize(self, data, action='default'): return 'pew_xml' self.serializers = { @@ -211,11 +211,11 @@ class ResponseSerializerTest(test.TestCase): class RequestDeserializerTest(test.TestCase): def setUp(self): class JSONDeserializer(object): - def deserialize(self, data): + def deserialize(self, data, action='default'): return 'pew_json' class XMLDeserializer(object): - def deserialize(self, data): + def deserialize(self, data, action='default'): return 'pew_xml' self.deserializers = { -- cgit From bd31a85575ce53dfa80f414dd359b3bdb2855292 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Mon, 13 Jun 2011 08:53:34 -0400 Subject: check for none and empty string, this way empty dicts/lists will be ok --- nova/api/openstack/server_metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index c17d77d46..57666f6b7 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -38,7 +38,7 @@ class Controller(object): return dict(metadata=meta_dict) def _check_body(self, body): - if not body: + if body == None or body == "": expl = _('No Request Body') raise exc.HTTPBadRequest(explanation=expl) -- cgit From 67e11fa809c83f25af9d09eac1bbe1c69a22a311 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Mon, 13 Jun 2011 10:10:45 -0400 Subject: fixed bug 796619 --- nova/crypto.py | 3 ++- nova/tests/test_crypto.py | 69 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/crypto.py b/nova/crypto.py index bdc32482a..8d535f426 100644 --- a/nova/crypto.py +++ b/nova/crypto.py @@ -176,7 +176,8 @@ def revoke_certs_by_project(project_id): def revoke_certs_by_user_and_project(user_id, project_id): """Revoke certs for user in project.""" admin = context.get_admin_context() - for cert in db.certificate_get_all_by_user(admin, user_id, project_id): + for cert in db.certificate_get_all_by_user_and_project(admin, + user_id, project_id): revoke_cert(cert['project_id'], cert['file_name']) diff --git a/nova/tests/test_crypto.py b/nova/tests/test_crypto.py index 945d78794..1356ec8b8 100644 --- a/nova/tests/test_crypto.py +++ b/nova/tests/test_crypto.py @@ -16,7 +16,10 @@ Tests for Crypto module. """ +import mox + from nova import crypto +from nova import db from nova import test @@ -46,3 +49,69 @@ class SymmetricKeyTestCase(test.TestCase): plain = decrypt(cipher_text) self.assertEquals(plain_text, plain) + + +class RevokeCertsTest(test.TestCase): + + def test_revoke_certs_by_user_and_project(self): + user_id = 'test_user' + project_id = 2 + file_name = 'test_file' + + certificates = [{"user_id": user_id, "project_id": project_id, + "file_name": file_name}] + + self.mox.StubOutWithMock(db, 'certificate_get_all_by_user_and_project') + db.certificate_get_all_by_user_and_project(mox.IgnoreArg(), \ + user_id, project_id).AndReturn(certificates) + + self.mox.StubOutWithMock(crypto, 'revoke_cert') + crypto.revoke_cert(project_id, mox.IgnoreArg()) + + self.mox.ReplayAll() + + crypto.revoke_certs_by_user_and_project(user_id, project_id) + + self.mox.VerifyAll() + + def test_revoke_certs_by_user(self): + user_id = 'test_user' + project_id = 2 + file_name = 'test_file' + + certificates = [{"user_id": user_id, "project_id": project_id, + "file_name": file_name}] + + self.mox.StubOutWithMock(db, 'certificate_get_all_by_user') + db.certificate_get_all_by_user(mox.IgnoreArg(), \ + user_id).AndReturn(certificates) + + self.mox.StubOutWithMock(crypto, 'revoke_cert') + crypto.revoke_cert(project_id, mox.IgnoreArg()) + + self.mox.ReplayAll() + + crypto.revoke_certs_by_user(user_id) + + self.mox.VerifyAll() + + def test_revoke_certs_by_project(self): + user_id = 'test_user' + project_id = 2 + file_name = 'test_file' + + certificates = [{"user_id": user_id, "project_id": project_id, + "file_name": file_name}] + + self.mox.StubOutWithMock(db, 'certificate_get_all_by_project') + db.certificate_get_all_by_project(mox.IgnoreArg(), \ + project_id).AndReturn(certificates) + + self.mox.StubOutWithMock(crypto, 'revoke_cert') + crypto.revoke_cert(project_id, mox.IgnoreArg()) + + self.mox.ReplayAll() + + crypto.revoke_certs_by_project(project_id) + + self.mox.VerifyAll() -- cgit From 2ee3d49e6c35515b9ef9d78365c3bc0ec9236b4b Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 13 Jun 2011 11:06:25 -0400 Subject: Update xenapi/vm_utils.py so that it calls find_sr instead of get_sr. Remove the old get_sr function which by default looked for an SR named 'slices'. --- nova/virt/xenapi/vm_utils.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index b9d4346e4..cb4da7fdb 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -329,12 +329,6 @@ class VMHelper(HelperBase): 'snap': template_vdi_uuid} return template_vm_ref, template_vdi_uuids - @classmethod - def get_sr(cls, session, sr_label='slices'): - """Finds the SR named by the given name label and returns - the UUID""" - return session.call_xenapi('SR.get_by_name_label', sr_label)[0] - @classmethod def get_sr_path(cls, session): """Return the path to our storage repository @@ -790,8 +784,7 @@ class VMHelper(HelperBase): @classmethod def scan_default_sr(cls, session): """Looks for the system default SR and triggers a re-scan""" - #FIXME(sirp/mdietz): refactor scan_default_sr in there - sr_ref = cls.get_sr(session) + sr_ref = find_sr(session) session.call_xenapi('SR.scan', sr_ref) -- 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 f2ca12fc5ea236bb8940acce80065a3bcbe37d2a Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 13 Jun 2011 15:03:26 -0400 Subject: wsgi can now handle dispatching action None more elegantly --- nova/api/openstack/wsgi.py | 13 ++++++------- nova/tests/api/openstack/test_wsgi.py | 13 +++++++++++++ 2 files changed, 19 insertions(+), 7 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index e71b80e2c..385ae4625 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -60,8 +60,9 @@ class TextDeserializer(object): def deserialize(self, datastring, action='default'): """Find local deserialization method and parse request body.""" - action_method = getattr(self, action, self.default) - return action_method(datastring) + action_method = getattr(self, str(action), self.default) + output = action_method(datastring) + return output def default(self, datastring): """Default deserialization code should live here""" @@ -189,11 +190,9 @@ class DictSerializer(object): def serialize(self, data, action='default'): """Find local serialization method and encode response body.""" - if action is None: - action_method = self.default - else: - action_method = getattr(self, action, self.default) - return action_method(data) + action_method = getattr(self, str(action), self.default) + output = action_method(data) + return output def default(self, data): """Default serialization code should live here""" diff --git a/nova/tests/api/openstack/test_wsgi.py b/nova/tests/api/openstack/test_wsgi.py index 0329ab218..5ec7712dd 100644 --- a/nova/tests/api/openstack/test_wsgi.py +++ b/nova/tests/api/openstack/test_wsgi.py @@ -89,6 +89,13 @@ class DictSerializerTest(test.TestCase): serializer.default = lambda x: 'trousers' self.assertEqual(serializer.serialize({}, 'update'), 'trousers') + def test_dispatch_action_None(self): + serializer = wsgi.DictSerializer() + serializer.create = lambda x: 'pants' + serializer.default = lambda x: 'trousers' + self.assertEqual(serializer.serialize({}, None), 'trousers') + + class XMLDictSerializerTest(test.TestCase): def test_xml(self): @@ -123,6 +130,12 @@ class TextDeserializerTest(test.TestCase): deserializer.default = lambda x: 'trousers' self.assertEqual(deserializer.deserialize({}, 'update'), 'trousers') + def test_dispatch_action_None(self): + deserializer = wsgi.TextDeserializer() + deserializer.create = lambda x: 'pants' + deserializer.default = lambda x: 'trousers' + self.assertEqual(deserializer.deserialize({}, None), 'trousers') + class JSONDeserializerTest(test.TestCase): def test_json(self): -- cgit From 83df6e50fa90620dd7510e1a06d9128d4de7cb29 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 13 Jun 2011 15:08:00 -0400 Subject: removing unnecessary lines --- nova/api/openstack/wsgi.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 385ae4625..6cb73d8bf 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -61,8 +61,7 @@ class TextDeserializer(object): def deserialize(self, datastring, action='default'): """Find local deserialization method and parse request body.""" action_method = getattr(self, str(action), self.default) - output = action_method(datastring) - return output + return action_method(datastring) def default(self, datastring): """Default deserialization code should live here""" @@ -191,8 +190,7 @@ class DictSerializer(object): def serialize(self, data, action='default'): """Find local serialization method and encode response body.""" action_method = getattr(self, str(action), self.default) - output = action_method(data) - return output + return action_method(data) def default(self, data): """Default serialization code should live here""" -- cgit From bdfded59fda6716adbbcf981a45d1ed90aa23f89 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Mon, 13 Jun 2011 15:18:55 -0400 Subject: Improved tests --- nova/tests/test_crypto.py | 46 ++++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 16 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_crypto.py b/nova/tests/test_crypto.py index 1356ec8b8..8eaef3293 100644 --- a/nova/tests/test_crypto.py +++ b/nova/tests/test_crypto.py @@ -17,6 +17,7 @@ Tests for Crypto module. """ import mox +import stubout from nova import crypto from nova import db @@ -53,20 +54,31 @@ class SymmetricKeyTestCase(test.TestCase): class RevokeCertsTest(test.TestCase): + def setUp(self): + super(RevokeCertsTest, self).setUp() + self.stubs = stubout.StubOutForTesting() + + def tearDown(self): + self.stubs.UnsetAll() + super(RevokeCertsTest, self).tearDown() + def test_revoke_certs_by_user_and_project(self): user_id = 'test_user' project_id = 2 file_name = 'test_file' - certificates = [{"user_id": user_id, "project_id": project_id, - "file_name": file_name}] + def mock_certificate_get_all_by_user_and_project(context, + user_id, + project_id): + + return [{"user_id": user_id, "project_id": project_id, + "file_name": file_name}] - self.mox.StubOutWithMock(db, 'certificate_get_all_by_user_and_project') - db.certificate_get_all_by_user_and_project(mox.IgnoreArg(), \ - user_id, project_id).AndReturn(certificates) + self.stubs.Set(db, 'certificate_get_all_by_user_and_project', + mock_certificate_get_all_by_user_and_project) self.mox.StubOutWithMock(crypto, 'revoke_cert') - crypto.revoke_cert(project_id, mox.IgnoreArg()) + crypto.revoke_cert(project_id, file_name) self.mox.ReplayAll() @@ -79,12 +91,13 @@ class RevokeCertsTest(test.TestCase): project_id = 2 file_name = 'test_file' - certificates = [{"user_id": user_id, "project_id": project_id, - "file_name": file_name}] + def mock_certificate_get_all_by_user(context, user_id): + + return [{"user_id": user_id, "project_id": project_id, + "file_name": file_name}] - self.mox.StubOutWithMock(db, 'certificate_get_all_by_user') - db.certificate_get_all_by_user(mox.IgnoreArg(), \ - user_id).AndReturn(certificates) + self.stubs.Set(db, 'certificate_get_all_by_user', + mock_certificate_get_all_by_user) self.mox.StubOutWithMock(crypto, 'revoke_cert') crypto.revoke_cert(project_id, mox.IgnoreArg()) @@ -100,12 +113,13 @@ class RevokeCertsTest(test.TestCase): project_id = 2 file_name = 'test_file' - certificates = [{"user_id": user_id, "project_id": project_id, - "file_name": file_name}] + def mock_certificate_get_all_by_project(context, project_id): + + return [{"user_id": user_id, "project_id": project_id, + "file_name": file_name}] - self.mox.StubOutWithMock(db, 'certificate_get_all_by_project') - db.certificate_get_all_by_project(mox.IgnoreArg(), \ - project_id).AndReturn(certificates) + self.stubs.Set(db, 'certificate_get_all_by_project', + mock_certificate_get_all_by_project) self.mox.StubOutWithMock(crypto, 'revoke_cert') crypto.revoke_cert(project_id, mox.IgnoreArg()) -- cgit From 431b9d52f2eb325c8be90d45d102c9e238d02325 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Mon, 13 Jun 2011 15:24:02 -0400 Subject: pep8 --- nova/tests/test_crypto.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_crypto.py b/nova/tests/test_crypto.py index 8eaef3293..6c25b396e 100644 --- a/nova/tests/test_crypto.py +++ b/nova/tests/test_crypto.py @@ -67,15 +67,15 @@ class RevokeCertsTest(test.TestCase): project_id = 2 file_name = 'test_file' - def mock_certificate_get_all_by_user_and_project(context, + def mock_certificate_get_all_by_user_and_project(context, user_id, project_id): - return [{"user_id": user_id, "project_id": project_id, + return [{"user_id": user_id, "project_id": project_id, "file_name": file_name}] - self.stubs.Set(db, 'certificate_get_all_by_user_and_project', - mock_certificate_get_all_by_user_and_project) + self.stubs.Set(db, 'certificate_get_all_by_user_and_project', + mock_certificate_get_all_by_user_and_project) self.mox.StubOutWithMock(crypto, 'revoke_cert') crypto.revoke_cert(project_id, file_name) @@ -91,12 +91,12 @@ class RevokeCertsTest(test.TestCase): project_id = 2 file_name = 'test_file' - def mock_certificate_get_all_by_user(context, user_id): + def mock_certificate_get_all_by_user(context, user_id): - return [{"user_id": user_id, "project_id": project_id, + return [{"user_id": user_id, "project_id": project_id, "file_name": file_name}] - self.stubs.Set(db, 'certificate_get_all_by_user', + self.stubs.Set(db, 'certificate_get_all_by_user', mock_certificate_get_all_by_user) self.mox.StubOutWithMock(crypto, 'revoke_cert') @@ -115,10 +115,10 @@ class RevokeCertsTest(test.TestCase): def mock_certificate_get_all_by_project(context, project_id): - return [{"user_id": user_id, "project_id": project_id, + return [{"user_id": user_id, "project_id": project_id, "file_name": file_name}] - self.stubs.Set(db, 'certificate_get_all_by_project', + self.stubs.Set(db, 'certificate_get_all_by_project', mock_certificate_get_all_by_project) self.mox.StubOutWithMock(crypto, 'revoke_cert') -- cgit From bebeaa6b0bf69c0a4017d429e79174401df28550 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Mon, 13 Jun 2011 15:20:43 -0500 Subject: Removed clocksource=jiffies from PV_args. --- nova/tests/test_xenapi.py | 2 +- nova/virt/xenapi/vm_utils.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 3a175b106..d1c88287a 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -331,7 +331,7 @@ class XenAPIVMTestCase(test.TestCase): def check_vm_params_for_linux(self): self.assertEquals(self.vm['platform']['nx'], 'false') - self.assertEquals(self.vm['PV_args'], 'clocksource=jiffies') + self.assertEquals(self.vm['PV_args'], '') self.assertEquals(self.vm['PV_bootloader'], 'pygrub') # check that these are not set diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index b9d4346e4..11da221f2 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -157,7 +157,6 @@ class VMHelper(HelperBase): rec['PV_ramdisk'] = ramdisk else: # 2. Use kernel within the image - rec['PV_args'] = 'clocksource=jiffies' rec['PV_bootloader'] = 'pygrub' else: # 3. Using hardware virtualization -- cgit From 2f422747cc7ffcbbe952e9a3fb5fd1de6a417901 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Mon, 13 Jun 2011 16:41:31 -0400 Subject: Test now passes even if the rpc call does not complete on time --- nova/tests/test_cloud.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 13046f861..b491448eb 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -466,7 +466,8 @@ class CloudTestCase(test.TestCase): self.assertEqual(instance['imageId'], 'ami-00000001') self.assertEqual(instance['displayName'], 'Server 1') self.assertEqual(instance['instanceId'], 'i-00000001') - self.assertEqual(instance['instanceState']['name'], 'networking') + self.assertTrue(instance['instanceState']['name'] in + ['networking', 'scheduling']) self.assertEqual(instance['instanceType'], 'm1.small') def test_run_instances_image_state_none(self): -- cgit From d9c2a7112ba239fb64ecc76ce844caed9146a5dc Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Mon, 13 Jun 2011 20:46:25 +0000 Subject: Load table schema automatically instead of stubbing out --- .../sqlalchemy/migrate_repo/versions/023_add_vm_mode_to_instances.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/migrate_repo/versions/023_add_vm_mode_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/023_add_vm_mode_to_instances.py index 16b9826d9..2f7bfddcb 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/023_add_vm_mode_to_instances.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/023_add_vm_mode_to_instances.py @@ -18,9 +18,7 @@ from sqlalchemy import Column, Integer, MetaData, String, Table meta = MetaData() -instances = Table('instances', meta, - Column('id', Integer(), primary_key=True, nullable=False), - ) +instances = Table('instances', meta, autoload=True) instances_vm_mode = Column('vm_mode', String(length=255, convert_unicode=False, -- cgit From f3381ee03355d8800d229efb7f799df9e6c915e2 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Mon, 13 Jun 2011 21:14:26 -0400 Subject: pep8 --- nova/api/openstack/limits.py | 2 +- nova/compute/api.py | 2 +- nova/tests/scheduler/test_host_filter.py | 8 ++++---- nova/tests/scheduler/test_least_cost_scheduler.py | 8 ++++---- nova/tests/scheduler/test_zone_aware_scheduler.py | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index dc2bc6bbc..fede96e33 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -99,7 +99,7 @@ def create_resource(version='1.0'): serializers = { 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns, - metadata=metadata) + metadata=metadata), } return wsgi.Resource(controller, serializers=serializers) diff --git a/nova/compute/api.py b/nova/compute/api.py index b0949a729..5afc0480a 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -281,7 +281,7 @@ class API(base.Base): 'instance_type': instance_type, 'filter': filter_class, 'blob': zone_blob, - 'num_instances': num_instances + 'num_instances': num_instances, } rpc.cast(context, diff --git a/nova/tests/scheduler/test_host_filter.py b/nova/tests/scheduler/test_host_filter.py index 07817cc5a..10eafde08 100644 --- a/nova/tests/scheduler/test_host_filter.py +++ b/nova/tests/scheduler/test_host_filter.py @@ -133,11 +133,11 @@ class HostFilterTestCase(test.TestCase): raw = ['or', ['and', ['<', '$compute.host_memory_free', 30], - ['<', '$compute.disk_available', 300] + ['<', '$compute.disk_available', 300], ], ['and', ['>', '$compute.host_memory_free', 70], - ['>', '$compute.disk_available', 700] + ['>', '$compute.disk_available', 700], ] ] cooked = json.dumps(raw) @@ -183,12 +183,12 @@ class HostFilterTestCase(test.TestCase): self.assertTrue(hf.filter_hosts(self.zone_manager, json.dumps([]))) self.assertTrue(hf.filter_hosts(self.zone_manager, json.dumps({}))) self.assertTrue(hf.filter_hosts(self.zone_manager, json.dumps( - ['not', True, False, True, False] + ['not', True, False, True, False], ))) try: hf.filter_hosts(self.zone_manager, json.dumps( - 'not', True, False, True, False + 'not', True, False, True, False, )) self.fail("Should give KeyError") except KeyError, e: diff --git a/nova/tests/scheduler/test_least_cost_scheduler.py b/nova/tests/scheduler/test_least_cost_scheduler.py index 506fa62fb..9a5318aee 100644 --- a/nova/tests/scheduler/test_least_cost_scheduler.py +++ b/nova/tests/scheduler/test_least_cost_scheduler.py @@ -44,7 +44,7 @@ class WeightedSumTestCase(test.TestCase): hosts = [ FakeHost(1, 512 * MB, 100), FakeHost(2, 256 * MB, 400), - FakeHost(3, 512 * MB, 100) + FakeHost(3, 512 * MB, 100), ] weighted_fns = [ @@ -96,7 +96,7 @@ class LeastCostSchedulerTestCase(test.TestCase): def test_noop_cost_fn(self): FLAGS.least_cost_scheduler_cost_functions = [ - 'nova.scheduler.least_cost.noop_cost_fn' + 'nova.scheduler.least_cost.noop_cost_fn', ] FLAGS.noop_cost_fn_weight = 1 @@ -110,7 +110,7 @@ class LeastCostSchedulerTestCase(test.TestCase): def test_cost_fn_weights(self): FLAGS.least_cost_scheduler_cost_functions = [ - 'nova.scheduler.least_cost.noop_cost_fn' + 'nova.scheduler.least_cost.noop_cost_fn', ] FLAGS.noop_cost_fn_weight = 2 @@ -124,7 +124,7 @@ class LeastCostSchedulerTestCase(test.TestCase): def test_fill_first_cost_fn(self): FLAGS.least_cost_scheduler_cost_functions = [ - 'nova.scheduler.least_cost.fill_first_cost_fn' + 'nova.scheduler.least_cost.fill_first_cost_fn', ] FLAGS.fill_first_cost_fn_weight = 1 diff --git a/nova/tests/scheduler/test_zone_aware_scheduler.py b/nova/tests/scheduler/test_zone_aware_scheduler.py index 9f70b9dbc..37c6488cc 100644 --- a/nova/tests/scheduler/test_zone_aware_scheduler.py +++ b/nova/tests/scheduler/test_zone_aware_scheduler.py @@ -201,7 +201,7 @@ class ZoneAwareSchedulerTestCase(test.TestCase): 'instance_properties': {}, 'instance_type': {}, 'filter_driver': 'nova.scheduler.host_filter.AllHostsFilter', - 'blob': "Non-None blob data" + 'blob': "Non-None blob data", } result = sched.schedule_run_instance(None, 1, request_spec) -- cgit From a3ddb45464204464c93b1deb692414c44ce99376 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 14 Jun 2011 10:16:51 -0400 Subject: Created new exception for handling malformed requests Wrote tests Raise httpBadRequest on malformed request bodies --- nova/api/openstack/wsgi.py | 16 +++++++++++++--- nova/exception.py | 4 ++++ nova/tests/api/openstack/test_api.py | 21 +++++++++++++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index b0e2cab2c..7f17471c4 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -2,6 +2,7 @@ import json import webob from xml.dom import minidom +from xml.parsers.expat import ExpatError from nova import exception from nova import log as logging @@ -71,7 +72,10 @@ class TextDeserializer(object): class JSONDeserializer(TextDeserializer): def default(self, datastring): - return utils.loads(datastring) + try: + return utils.loads(datastring) + except ValueError: + raise exception.MalformedRequestBody() class XMLDeserializer(TextDeserializer): @@ -86,8 +90,12 @@ class XMLDeserializer(TextDeserializer): def default(self, datastring): plurals = set(self.metadata.get('plurals', {})) - node = minidom.parseString(datastring).childNodes[0] - return {node.nodeName: self._from_xml_node(node, plurals)} + + try: + node = minidom.parseString(datastring).childNodes[0] + return {node.nodeName: self._from_xml_node(node, plurals)} + except ExpatError: + raise exception.MalformedRequestBody() def _from_xml_node(self, node, listnames): """Convert a minidom node to a simple Python type. @@ -353,6 +361,8 @@ class Resource(wsgi.Application): request) except exception.InvalidContentType: return webob.exc.HTTPBadRequest(_("Unsupported Content-Type")) + except exception.MalformedRequestBody: + return webob.exc.HTTPBadRequest(_("Malformed request")) action_result = self.dispatch(request, action, action_args) diff --git a/nova/exception.py b/nova/exception.py index 1571dd032..ffd88fbe7 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -585,3 +585,7 @@ class InstanceExists(Duplicate): class MigrationError(NovaException): message = _("Migration error") + ": %(reason)s" + + +class MalformedRequestBody(NovaException): + message = _("Malformed message body") + ": %(reason)s" diff --git a/nova/tests/api/openstack/test_api.py b/nova/tests/api/openstack/test_api.py index c63431a45..7321c329f 100644 --- a/nova/tests/api/openstack/test_api.py +++ b/nova/tests/api/openstack/test_api.py @@ -15,6 +15,8 @@ # License for the specific language governing permissions and limitations # under the License. +import json + import webob.exc import webob.dec @@ -23,6 +25,7 @@ from webob import Request from nova import test from nova.api import openstack from nova.api.openstack import faults +from nova.tests.api.openstack import fakes class APITest(test.TestCase): @@ -31,6 +34,24 @@ class APITest(test.TestCase): # simpler version of the app than fakes.wsgi_app return openstack.FaultWrapper(inner_app) + def test_malformed_json(self): + req = webob.Request.blank('/') + req.method = 'POST' + req.body = '{' + req.headers["content-type"] = "application/json" + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_malformed_xml(self): + req = webob.Request.blank('/') + req.method = 'POST' + req.body = '' + req.headers["content-type"] = "application/xml" + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + def test_exceptions_are_converted_to_faults(self): @webob.dec.wsgify -- cgit From 70bb9494639ec26f12b71dc22052d3e5b343890f Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Tue, 14 Jun 2011 15:36:07 +0000 Subject: autoload with the appropriate engine during upgrade/downgrade --- .../migrate_repo/versions/023_add_vm_mode_to_instances.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/migrate_repo/versions/023_add_vm_mode_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/023_add_vm_mode_to_instances.py index 2f7bfddcb..0c587f569 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/023_add_vm_mode_to_instances.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/023_add_vm_mode_to_instances.py @@ -18,8 +18,6 @@ from sqlalchemy import Column, Integer, MetaData, String, Table meta = MetaData() -instances = Table('instances', meta, autoload=True) - instances_vm_mode = Column('vm_mode', String(length=255, convert_unicode=False, assert_unicode=None, unicode_error=None, @@ -32,10 +30,16 @@ def upgrade(migrate_engine): # bind migrate_engine to your metadata meta.bind = migrate_engine + instances = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + instances.create_column(instances_vm_mode) def downgrade(migrate_engine): meta.bind = migrate_engine + instances = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + instances.drop_column('vm_mode') -- cgit From 9806dacb03023d1db22e9cf833845ba8498657a3 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 14 Jun 2011 12:02:15 -0400 Subject: Added faults wrapper --- nova/api/openstack/wsgi.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 7f17471c4..affc781dc 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -5,6 +5,7 @@ from xml.dom import minidom from xml.parsers.expat import ExpatError from nova import exception +import faults from nova import log as logging from nova import utils from nova import wsgi @@ -362,7 +363,9 @@ class Resource(wsgi.Application): except exception.InvalidContentType: return webob.exc.HTTPBadRequest(_("Unsupported Content-Type")) except exception.MalformedRequestBody: - return webob.exc.HTTPBadRequest(_("Malformed request")) + explanation = _("Malformed request") + return faults.Fault(webob.exc.HTTPBadRequest( + explanation=explanation)) action_result = self.dispatch(request, action, action_args) -- cgit From e9f6e47a92090a9a7867c2a117ae6cf58db394ac Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 14 Jun 2011 12:36:46 -0400 Subject: Improved errors --- nova/api/openstack/wsgi.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index affc781dc..0b749e115 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -76,7 +76,8 @@ class JSONDeserializer(TextDeserializer): try: return utils.loads(datastring) except ValueError: - raise exception.MalformedRequestBody() + raise exception.MalformedRequestBody( + "malformed JSON in request body") class XMLDeserializer(TextDeserializer): @@ -96,7 +97,8 @@ class XMLDeserializer(TextDeserializer): node = minidom.parseString(datastring).childNodes[0] return {node.nodeName: self._from_xml_node(node, plurals)} except ExpatError: - raise exception.MalformedRequestBody() + raise exception.MalformedRequestBody( + "malformed XML in request Body") def _from_xml_node(self, node, listnames): """Convert a minidom node to a simple Python type. @@ -363,7 +365,7 @@ class Resource(wsgi.Application): except exception.InvalidContentType: return webob.exc.HTTPBadRequest(_("Unsupported Content-Type")) except exception.MalformedRequestBody: - explanation = _("Malformed request") + explanation = _("Malformed request body") return faults.Fault(webob.exc.HTTPBadRequest( explanation=explanation)) -- cgit From b44dfde77b501e7c0d84769cab3b4a1a317c738d Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Tue, 14 Jun 2011 13:14:00 -0400 Subject: Stub out the rpc call in a unit test to avoid a race condition --- nova/tests/test_cloud.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index b491448eb..afc661635 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -457,6 +457,12 @@ class CloudTestCase(test.TestCase): self.cloud.delete_key_pair(self.context, 'test') def test_run_instances(self): + # stub out the rpc call + def stub_cast(*args, **kwargs): + pass + + self.stubs.Set(rpc, 'cast', stub_cast) + kwargs = {'image_id': FLAGS.default_image, 'instance_type': FLAGS.default_instance_type, 'max_count': 1} @@ -466,8 +472,7 @@ class CloudTestCase(test.TestCase): self.assertEqual(instance['imageId'], 'ami-00000001') self.assertEqual(instance['displayName'], 'Server 1') self.assertEqual(instance['instanceId'], 'i-00000001') - self.assertTrue(instance['instanceState']['name'] in - ['networking', 'scheduling']) + self.assertEqual(instance['instanceState']['name'], 'scheduling') self.assertEqual(instance['instanceType'], 'm1.small') def test_run_instances_image_state_none(self): -- cgit From b00628c4767b440fa6123aa1683d88cd33517d21 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Tue, 14 Jun 2011 13:17:13 -0400 Subject: pep8 fix --- nova/tests/test_cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index afc661635..d2ff14f27 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -462,7 +462,7 @@ class CloudTestCase(test.TestCase): pass self.stubs.Set(rpc, 'cast', stub_cast) - + kwargs = {'image_id': FLAGS.default_image, 'instance_type': FLAGS.default_instance_type, 'max_count': 1} -- cgit From e89aad7ca0ba7ab5e9b83fa6fd9cde7fb22924bf Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Tue, 14 Jun 2011 17:22:33 +0000 Subject: Really PEP8? A tab is inferior to 2 spaces? --- nova/virt/xenapi/vmops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 624588b6b..d105cf300 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -166,7 +166,7 @@ class VMOps(object): use_pv_kernel = True elif vm_mode in ('hv', 'hvm'): use_pv_kernel = False - vm_mode = 'hvm' # Normalize + vm_mode = 'hvm' # Normalize else: use_pv_kernel = VMHelper.determine_is_pv(self._session, instance.id, first_vdi_ref, disk_image_type, -- cgit From cf66a99314d6420725e32daf6a08404c98239107 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 14 Jun 2011 13:27:28 -0400 Subject: mp fixes --- nova/api/openstack/wsgi.py | 4 ++-- nova/exception.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 0b749e115..43b51b64a 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -77,7 +77,7 @@ class JSONDeserializer(TextDeserializer): return utils.loads(datastring) except ValueError: raise exception.MalformedRequestBody( - "malformed JSON in request body") + reason=_("malformed JSON in request body")) class XMLDeserializer(TextDeserializer): @@ -98,7 +98,7 @@ class XMLDeserializer(TextDeserializer): return {node.nodeName: self._from_xml_node(node, plurals)} except ExpatError: raise exception.MalformedRequestBody( - "malformed XML in request Body") + reason=_("malformed XML in request body")) def _from_xml_node(self, node, listnames): """Convert a minidom node to a simple Python type. diff --git a/nova/exception.py b/nova/exception.py index ffd88fbe7..f3a452228 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -588,4 +588,4 @@ class MigrationError(NovaException): class MalformedRequestBody(NovaException): - message = _("Malformed message body") + ": %(reason)s" + message = _("Malformed message body: %(reason)s") -- 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 6d960ff50d4cf8e6b2dc59aff0e8dea17498a9f0 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 14 Jun 2011 15:51:22 -0400 Subject: fixed HACKING --- nova/api/openstack/wsgi.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 43b51b64a..27d19db53 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -2,10 +2,10 @@ import json import webob from xml.dom import minidom -from xml.parsers.expat import ExpatError +from xml.parsers import expat -from nova import exception import faults +from nova import exception from nova import log as logging from nova import utils from nova import wsgi @@ -96,7 +96,7 @@ class XMLDeserializer(TextDeserializer): try: node = minidom.parseString(datastring).childNodes[0] return {node.nodeName: self._from_xml_node(node, plurals)} - except ExpatError: + except expat.ExpatError: raise exception.MalformedRequestBody( reason=_("malformed XML in request body")) -- 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 From 6e2f79f5452b0a470d5001e9a0428fe90f987ac8 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 15 Jun 2011 06:40:42 -0700 Subject: None project_id now default --- nova/scheduler/api.py | 5 +++-- nova/scheduler/zone_aware_scheduler.py | 3 ++- nova/scheduler/zone_manager.py | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/scheduler/api.py b/nova/scheduler/api.py index ffe59d2c1..3b3195c2e 100644 --- a/nova/scheduler/api.py +++ b/nova/scheduler/api.py @@ -106,7 +106,8 @@ def _wrap_method(function, self): def _process(func, zone): """Worker stub for green thread pool. Give the worker an authenticated nova client and zone info.""" - nova = novaclient.OpenStack(zone.username, zone.password, zone.api_url) + nova = novaclient.OpenStack(zone.username, zone.password, None, + zone.api_url) nova.authenticate() return func(nova, zone) @@ -122,7 +123,7 @@ def call_zone_method(context, method_name, errors_to_ignore=None, results = [] for zone in db.zone_get_all(context): try: - nova = novaclient.OpenStack(zone.username, zone.password, + nova = novaclient.OpenStack(zone.username, zone.password, None, zone.api_url) nova.authenticate() except novaclient.exceptions.BadRequest, e: diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index f04defa64..69d4c6034 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -105,7 +105,8 @@ class ZoneAwareScheduler(driver.Scheduler): % locals()) nova = None try: - nova = novaclient.OpenStack(zone.username, zone.password, url) + nova = novaclient.OpenStack(zone.username, zone.password, None, + url) nova.authenticate() except novaclient.exceptions.BadRequest, e: raise exception.NotAuthorized(_("Bad credentials attempting " diff --git a/nova/scheduler/zone_manager.py b/nova/scheduler/zone_manager.py index 3f483adff..ba7403c15 100644 --- a/nova/scheduler/zone_manager.py +++ b/nova/scheduler/zone_manager.py @@ -89,7 +89,8 @@ class ZoneState(object): def _call_novaclient(zone): """Call novaclient. Broken out for testing purposes.""" - client = novaclient.OpenStack(zone.username, zone.password, zone.api_url) + client = novaclient.OpenStack(zone.username, zone.password, None, + zone.api_url) return client.zones.info()._info -- cgit From 4c54aa28a8c414752d73084e3a4094e5df79b618 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 15 Jun 2011 09:45:22 -0700 Subject: fixed up some little project_id things with new novaclient --- nova/scheduler/zone_aware_scheduler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index 69d4c6034..0ec83ec2e 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -88,7 +88,7 @@ class ZoneAwareScheduler(driver.Scheduler): instance_properties = request_spec['instance_properties'] name = instance_properties['display_name'] - image_id = instance_properties['image_id'] + image_ref = instance_properties['image_ref'] meta = instance_properties['metadata'] flavor_id = instance_type['flavorid'] reservation_id = instance_properties['reservation_id'] @@ -112,7 +112,7 @@ class ZoneAwareScheduler(driver.Scheduler): raise exception.NotAuthorized(_("Bad credentials attempting " "to talk to zone at %(url)s.") % locals()) - nova.servers.create(name, image_id, flavor_id, ipgroup, meta, files, + nova.servers.create(name, image_ref, flavor_id, ipgroup, meta, files, child_blob, reservation_id=reservation_id) def _provision_resource_from_blob(self, context, item, instance_id, -- cgit From 4b56c18bb4436c6ea76f44d2b266973f5d42817f Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 15 Jun 2011 10:21:41 -0700 Subject: don't provision to all child zones --- nova/scheduler/zone_aware_scheduler.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/scheduler/zone_aware_scheduler.py b/nova/scheduler/zone_aware_scheduler.py index 0ec83ec2e..e7bff2faa 100644 --- a/nova/scheduler/zone_aware_scheduler.py +++ b/nova/scheduler/zone_aware_scheduler.py @@ -185,7 +185,11 @@ class ZoneAwareScheduler(driver.Scheduler): if not build_plan: raise driver.NoValidHost(_('No hosts were available')) - for item in build_plan: + for num in xrange(request_spec['num_instances']): + if not build_plan: + break + + item = build_plan.pop(0) self._provision_resource(context, item, instance_id, request_spec, kwargs) -- cgit From e6eae8d21a7c261dae498f52430dbee60b28840e Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 15 Jun 2011 17:35:31 -0400 Subject: Added metadata joinedloads --- nova/db/sqlalchemy/api.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 73870d2f3..7119f43eb 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -907,6 +907,7 @@ def instance_get_all_by_host(context, host): options(joinedload_all('fixed_ip.floating_ips')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ip.network')).\ + options(joinedload('metadata')).\ options(joinedload('instance_type')).\ filter_by(host=host).\ filter_by(deleted=can_read_deleted(context)).\ @@ -922,6 +923,7 @@ def instance_get_all_by_project(context, project_id): options(joinedload_all('fixed_ip.floating_ips')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ip.network')).\ + options(joinedload('metadata')).\ options(joinedload('instance_type')).\ filter_by(project_id=project_id).\ filter_by(deleted=can_read_deleted(context)).\ @@ -937,6 +939,7 @@ def instance_get_all_by_reservation(context, reservation_id): options(joinedload_all('fixed_ip.floating_ips')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ip.network')).\ + options(joinedload('metadata')).\ options(joinedload('instance_type')).\ filter_by(reservation_id=reservation_id).\ filter_by(deleted=can_read_deleted(context)).\ @@ -946,6 +949,7 @@ def instance_get_all_by_reservation(context, reservation_id): options(joinedload_all('fixed_ip.floating_ips')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ip.network')).\ + options(joinedload('metadata')).\ options(joinedload('instance_type')).\ filter_by(project_id=context.project_id).\ filter_by(reservation_id=reservation_id).\ @@ -959,6 +963,8 @@ def instance_get_project_vpn(context, project_id): return session.query(models.Instance).\ options(joinedload_all('fixed_ip.floating_ips')).\ options(joinedload('security_groups')).\ + options(joinedload_all('fixed_ip.network')).\ + options(joinedload('metadata')).\ options(joinedload('instance_type')).\ filter_by(project_id=project_id).\ filter_by(image_ref=str(FLAGS.vpn_image_id)).\ -- cgit From 00cb4efb489cce55aeab7a530012d3615552af89 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Thu, 16 Jun 2011 21:21:01 +0000 Subject: Glance host defaults to rather than localhost --- nova/flags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/flags.py b/nova/flags.py index acfcf8d68..f6f12e3b2 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -272,7 +272,7 @@ DEFINE_string('aws_access_key_id', 'admin', 'AWS Access ID') DEFINE_string('aws_secret_access_key', 'admin', 'AWS Access Key') # NOTE(sirp): my_ip interpolation doesn't work within nested structures DEFINE_list('glance_api_servers', - ['127.0.0.1:9292'], + ['%s:9292' % _get_my_ip()], 'list of glance api servers available to nova (host:port)') DEFINE_integer('s3_port', 3333, 's3 port') DEFINE_string('s3_host', '$my_ip', 's3 host (for infrastructure)') -- cgit