summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2011-09-27 20:01:30 +0000
committerGerrit Code Review <review@openstack.org>2011-09-27 20:01:30 +0000
commita2646129bc9dbd9dec57bdde7f510e0ea7bbddea (patch)
tree041886cfdef32a0bb26cc51399e1e08d0c7b5f39
parent7dba1d9aa989760b190f1cf3bad2ed22bb2e2fc5 (diff)
parent0fab78825ef06310926181f6f97d377058b56b97 (diff)
downloadnova-a2646129bc9dbd9dec57bdde7f510e0ea7bbddea.tar.gz
nova-a2646129bc9dbd9dec57bdde7f510e0ea7bbddea.tar.xz
nova-a2646129bc9dbd9dec57bdde7f510e0ea7bbddea.zip
Merge "compute_api create*() and schedulers refactoring"
-rw-r--r--doc/source/devref/distributed_scheduler.rst2
-rw-r--r--nova/api/ec2/cloud.py10
-rw-r--r--nova/api/openstack/__init__.py3
-rw-r--r--nova/api/openstack/contrib/createserverext.py3
-rw-r--r--nova/api/openstack/contrib/volumes.py43
-rw-r--r--nova/api/openstack/contrib/zones.py50
-rw-r--r--nova/api/openstack/create_instance_helper.py606
-rw-r--r--nova/api/openstack/servers.py616
-rw-r--r--nova/api/openstack/zones.py48
-rw-r--r--nova/compute/api.py217
-rw-r--r--nova/compute/manager.py2
-rw-r--r--nova/flags.py3
-rw-r--r--nova/scheduler/abstract_scheduler.py75
-rw-r--r--nova/scheduler/api.py4
-rw-r--r--nova/scheduler/chance.py25
-rw-r--r--nova/scheduler/driver.py115
-rw-r--r--nova/scheduler/least_cost.py3
-rw-r--r--nova/scheduler/manager.py24
-rw-r--r--nova/scheduler/multi.py8
-rw-r--r--nova/scheduler/simple.py74
-rw-r--r--nova/scheduler/vsa.py33
-rw-r--r--nova/scheduler/zone.py28
-rw-r--r--nova/tests/api/openstack/contrib/test_createserverext.py13
-rw-r--r--nova/tests/api/openstack/contrib/test_volumes.py14
-rw-r--r--nova/tests/api/openstack/test_extensions.py3
-rw-r--r--nova/tests/api/openstack/test_server_actions.py4
-rw-r--r--nova/tests/api/openstack/test_servers.py183
-rw-r--r--nova/tests/integrated/api/client.py19
-rw-r--r--nova/tests/integrated/test_servers.py36
-rw-r--r--nova/tests/scheduler/test_abstract_scheduler.py75
-rw-r--r--nova/tests/scheduler/test_least_cost_scheduler.py2
-rw-r--r--nova/tests/scheduler/test_scheduler.py487
-rw-r--r--nova/tests/scheduler/test_vsa_scheduler.py30
-rw-r--r--nova/tests/test_compute.py153
-rw-r--r--nova/tests/test_quota.py17
35 files changed, 1774 insertions, 1254 deletions
diff --git a/doc/source/devref/distributed_scheduler.rst b/doc/source/devref/distributed_scheduler.rst
index c63e62f7f..7a631d69f 100644
--- a/doc/source/devref/distributed_scheduler.rst
+++ b/doc/source/devref/distributed_scheduler.rst
@@ -77,6 +77,8 @@ The filtering (excluding compute nodes incapable of fulfilling the request) and
Requesting a new instance
-------------------------
+(Note: The information below is out of date, as the `nova.compute.api.create_all_at_once()` functionality has merged into `nova.compute.api.create()` and the non-zone aware schedulers have been updated.)
+
Prior to the `BaseScheduler`, to request a new instance, a call was made to `nova.compute.api.create()`. The type of instance created depended on the value of the `InstanceType` record being passed in. The `InstanceType` determined the amount of disk, CPU, RAM and network required for the instance. Administrators can add new `InstanceType` records to suit their needs. For more complicated instance requests we need to go beyond the default fields in the `InstanceType` table.
`nova.compute.api.create()` performed the following actions:
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 3c8cd513f..a71ab8cf0 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -1385,7 +1385,7 @@ class CloudController(object):
if image_state != 'available':
raise exception.ApiError(_('Image must be available'))
- instances = self.compute_api.create(context,
+ (instances, resv_id) = self.compute_api.create(context,
instance_type=instance_types.get_instance_type_by_name(
kwargs.get('instance_type', None)),
image_href=self._get_image(context, kwargs['image_id'])['id'],
@@ -1400,9 +1400,11 @@ class CloudController(object):
security_group=kwargs.get('security_group'),
availability_zone=kwargs.get('placement', {}).get(
'AvailabilityZone'),
- block_device_mapping=kwargs.get('block_device_mapping', {}))
- return self._format_run_instances(context,
- reservation_id=instances[0]['reservation_id'])
+ block_device_mapping=kwargs.get('block_device_mapping', {}),
+ # NOTE(comstud): Unfortunately, EC2 requires that the
+ # instance DB entries have been created..
+ wait_for_instances=True)
+ return self._format_run_instances(context, resv_id)
def _do_instance(self, action, context, ec2_id):
instance_id = ec2utils.ec2_id_to_id(ec2_id)
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index 3b74fefc9..b517eae2c 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -139,8 +139,7 @@ class APIRouter(base_wsgi.Router):
controller=zones.create_resource(version),
collection={'detail': 'GET',
'info': 'GET',
- 'select': 'POST',
- 'boot': 'POST'})
+ 'select': 'POST'})
mapper.connect("versions", "/",
controller=versions.create_resource(version),
diff --git a/nova/api/openstack/contrib/createserverext.py b/nova/api/openstack/contrib/createserverext.py
index af7f37f13..1e13e10d4 100644
--- a/nova/api/openstack/contrib/createserverext.py
+++ b/nova/api/openstack/contrib/createserverext.py
@@ -15,7 +15,6 @@
# under the License
from nova import utils
-from nova.api.openstack import create_instance_helper as helper
from nova.api.openstack import extensions
from nova.api.openstack import servers
from nova.api.openstack import wsgi
@@ -66,7 +65,7 @@ class Createserverext(extensions.ExtensionDescriptor):
}
body_deserializers = {
- 'application/xml': helper.ServerXMLDeserializerV11(),
+ 'application/xml': servers.ServerXMLDeserializerV11(),
}
serializer = wsgi.ResponseSerializer(body_serializers,
diff --git a/nova/api/openstack/contrib/volumes.py b/nova/api/openstack/contrib/volumes.py
index 9d4254f1f..6c79a0538 100644
--- a/nova/api/openstack/contrib/volumes.py
+++ b/nova/api/openstack/contrib/volumes.py
@@ -334,47 +334,8 @@ class VolumeAttachmentController(object):
class BootFromVolumeController(servers.ControllerV11):
"""The boot from volume API controller for the Openstack API."""
- def _create_instance(self, context, instance_type, image_href, **kwargs):
- try:
- return self.compute_api.create(context, instance_type,
- image_href, **kwargs)
- except quota.QuotaError as error:
- self.helper._handle_quota_error(error)
- except exception.ImageNotFound as error:
- msg = _("Can not find requested image")
- raise faults.Fault(exc.HTTPBadRequest(explanation=msg))
-
- def create(self, req, body):
- """ Creates a new server for a given user """
- extra_values = None
- try:
-
- def get_kwargs(context, instance_type, image_href, **kwargs):
- kwargs['context'] = context
- kwargs['instance_type'] = instance_type
- kwargs['image_href'] = image_href
- return kwargs
-
- extra_values, kwargs = self.helper.create_instance(req, body,
- get_kwargs)
-
- block_device_mapping = body['server'].get('block_device_mapping')
- kwargs['block_device_mapping'] = block_device_mapping
-
- instances = self._create_instance(**kwargs)
- except faults.Fault, f:
- return f
-
- # We can only return 1 instance via the API, if we happen to
- # build more than one... instances is a list, so we'll just
- # use the first one..
- inst = instances[0]
- for key in ['instance_type', 'image_ref']:
- inst[key] = extra_values[key]
-
- server = self._build_view(req, inst, is_detail=True)
- server['server']['adminPass'] = extra_values['password']
- return server
+ def _get_block_device_mapping(self, data):
+ return data.get('block_device_mapping')
class Volumes(extensions.ExtensionDescriptor):
diff --git a/nova/api/openstack/contrib/zones.py b/nova/api/openstack/contrib/zones.py
new file mode 100644
index 000000000..5901e321e
--- /dev/null
+++ b/nova/api/openstack/contrib/zones.py
@@ -0,0 +1,50 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 OpenStack LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""The zones extension."""
+
+
+from nova import flags
+from nova import log as logging
+from nova.api.openstack import extensions
+
+
+LOG = logging.getLogger("nova.api.zones")
+FLAGS = flags.FLAGS
+
+
+class Zones(extensions.ExtensionDescriptor):
+ def get_name(self):
+ return "Zones"
+
+ def get_alias(self):
+ return "os-zones"
+
+ def get_description(self):
+ return """Enables zones-related functionality such as adding
+child zones, listing child zones, getting the capabilities of the
+local zone, and returning build plans to parent zones' schedulers"""
+
+ def get_namespace(self):
+ return "http://docs.openstack.org/ext/zones/api/v1.1"
+
+ def get_updated(self):
+ return "2011-09-21T00:00:00+00:00"
+
+ def get_resources(self):
+ # Nothing yet.
+ return []
diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py
deleted file mode 100644
index dde9187cd..000000000
--- a/nova/api/openstack/create_instance_helper.py
+++ /dev/null
@@ -1,606 +0,0 @@
-# Copyright 2011 OpenStack LLC.
-# Copyright 2011 Piston Cloud Computing, Inc.
-# 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
-
-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 common
-from nova.api.openstack import wsgi
-from nova.rpc.common import RemoteError
-
-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 exc.HTTPUnprocessableEntity()
-
- if not 'server' in body:
- raise exc.HTTPUnprocessableEntity()
-
- context = req.environ['nova.context']
- server_dict = body['server']
- password = self.controller._get_server_admin_password(server_dict)
-
- if not 'name' in server_dict:
- msg = _("Server name is not defined")
- raise exc.HTTPBadRequest(explanation=msg)
-
- name = server_dict['name']
- self._validate_server_name(name)
- name = name.strip()
-
- image_href = self.controller._image_ref_from_req_data(body)
- # If the image href was generated by nova api, strip image_href
- # down to an id and use the default glance connection params
-
- if str(image_href).startswith(req.application_url):
- image_href = image_href.split('/').pop()
- try:
- image_service, image_id = nova.image.get_image_service(context,
- image_href)
- kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(
- req, image_service, 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 exc.HTTPBadRequest(explanation=msg)
-
- personality = server_dict.get('personality')
- config_drive = server_dict.get('config_drive')
-
- injected_files = []
- if personality:
- injected_files = self._get_injected_files(personality)
-
- sg_names = []
- security_groups = server_dict.get('security_groups')
- if security_groups is not None:
- sg_names = [sg['name'] for sg in security_groups if sg.get('name')]
- if not sg_names:
- sg_names.append('default')
-
- sg_names = list(set(sg_names))
-
- requested_networks = server_dict.get('networks')
- if requested_networks is not None:
- requested_networks = self._get_requested_networks(
- requested_networks)
-
- try:
- flavor_id = self.controller._flavor_id_from_req_data(body)
- except ValueError as error:
- msg = _("Invalid flavorRef provided.")
- raise exc.HTTPBadRequest(explanation=msg)
-
- zone_blob = server_dict.get('blob')
-
- # optional openstack extensions:
- key_name = server_dict.get('key_name')
- user_data = server_dict.get('user_data')
- self._validate_user_data(user_data)
-
- availability_zone = server_dict.get('availability_zone')
- name = server_dict['name']
- self._validate_server_name(name)
- name = name.strip()
-
- reservation_id = server_dict.get('reservation_id')
- min_count = server_dict.get('min_count')
- max_count = server_dict.get('max_count')
- # min_count and max_count are optional. If they exist, they come
- # in as strings. We want to default 'min_count' to 1, and default
- # 'max_count' to be 'min_count'.
- min_count = int(min_count) if min_count else 1
- max_count = int(max_count) if max_count else min_count
- if min_count > max_count:
- min_count = max_count
-
- try:
- inst_type = \
- instance_types.get_instance_type_by_flavor_id(flavor_id)
- extra_values = {
- 'instance_type': inst_type,
- 'image_ref': image_href,
- 'config_drive': config_drive,
- '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,
- metadata=server_dict.get('metadata', {}),
- access_ip_v4=server_dict.get('accessIPv4'),
- access_ip_v6=server_dict.get('accessIPv6'),
- injected_files=injected_files,
- admin_password=password,
- zone_blob=zone_blob,
- reservation_id=reservation_id,
- min_count=min_count,
- max_count=max_count,
- requested_networks=requested_networks,
- security_group=sg_names,
- user_data=user_data,
- availability_zone=availability_zone,
- config_drive=config_drive,))
- except quota.QuotaError as error:
- self._handle_quota_error(error)
- except exception.InstanceTypeMemoryTooSmall as error:
- raise exc.HTTPBadRequest(explanation=unicode(error))
- except exception.InstanceTypeDiskTooSmall as error:
- raise exc.HTTPBadRequest(explanation=unicode(error))
- except exception.ImageNotFound as error:
- msg = _("Can not find requested image")
- raise exc.HTTPBadRequest(explanation=msg)
- except exception.FlavorNotFound as error:
- msg = _("Invalid flavorRef provided.")
- raise exc.HTTPBadRequest(explanation=msg)
- except exception.KeypairNotFound as error:
- msg = _("Invalid key_name provided.")
- raise exc.HTTPBadRequest(explanation=msg)
- except exception.SecurityGroupNotFound as error:
- raise exc.HTTPBadRequest(explanation=unicode(error))
- except RemoteError as err:
- msg = "%(err_type)s: %(err_msg)s" % \
- {'err_type': err.exc_type, 'err_msg': err.value}
- raise exc.HTTPBadRequest(explanation=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.HTTPRequestEntityTooLarge(explanation=error.message,
- headers={'Retry-After': 0})
- if error.code == "OnsetFilePathLimitExceeded":
- expl = _("Personality file path too long")
- raise exc.HTTPRequestEntityTooLarge(explanation=error.message,
- headers={'Retry-After': 0})
- if error.code == "OnsetFileContentLimitExceeded":
- expl = _("Personality file content too long")
- raise exc.HTTPRequestEntityTooLarge(explanation=error.message,
- headers={'Retry-After': 0})
- if error.code == "InstanceLimitExceeded":
- expl = _("Instance quotas have been exceeded")
- raise exc.HTTPRequestEntityTooLarge(explanation=error.message,
- headers={'Retry-After': 0})
- # 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 = ServerXMLDeserializer()
- 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(explanation=msg)
-
- if value.strip() == '':
- msg = _("Server name is an empty string")
- raise exc.HTTPBadRequest(explanation=msg)
-
- def _get_kernel_ramdisk_from_image(self, req, image_service, 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 = 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:
- ramdisk_id = None
-
- 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(FLAGS.password_length)
-
- 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(FLAGS.password_length)
- if not isinstance(password, basestring) or password == '':
- msg = _("Invalid adminPass")
- raise exc.HTTPBadRequest(explanation=msg)
- return password
-
- def _get_requested_networks(self, requested_networks):
- """
- Create a list of requested networks from the networks attribute
- """
- networks = []
- for network in requested_networks:
- try:
- network_uuid = network['uuid']
-
- if not utils.is_uuid_like(network_uuid):
- msg = _("Bad networks format: network uuid is not in"
- " proper format (%s)") % network_uuid
- raise exc.HTTPBadRequest(explanation=msg)
-
- #fixed IP address is optional
- #if the fixed IP address is not provided then
- #it will use one of the available IP address from the network
- address = network.get('fixed_ip', None)
- if address is not None and not utils.is_valid_ipv4(address):
- msg = _("Invalid fixed IP address (%s)") % address
- raise exc.HTTPBadRequest(explanation=msg)
- # check if the network id is already present in the list,
- # we don't want duplicate networks to be passed
- # at the boot time
- for id, ip in networks:
- if id == network_uuid:
- expl = _("Duplicate networks (%s) are not allowed")\
- % network_uuid
- raise exc.HTTPBadRequest(explanation=expl)
-
- networks.append((network_uuid, address))
- except KeyError as key:
- expl = _('Bad network format: missing %s') % key
- raise exc.HTTPBadRequest(explanation=expl)
- except TypeError:
- expl = _('Bad networks format')
- raise exc.HTTPBadRequest(explanation=expl)
-
- return networks
-
- def _validate_user_data(self, user_data):
- """Check if the user_data is encoded properly"""
- if not user_data:
- return
- try:
- user_data = base64.b64decode(user_data)
- except TypeError:
- expl = _('Userdata content cannot be decoded')
- raise exc.HTTPBadRequest(explanation=expl)
-
-
-class ServerXMLDeserializer(wsgi.XMLDeserializer):
- """
- Deserializer to handle xml-formatted server create requests.
-
- Handles standard server attributes as well as optional metadata
- and personality attributes
- """
-
- metadata_deserializer = common.MetadataXMLDeserializer()
-
- def create(self, string):
- """Deserialize an xml-formatted server create request"""
- dom = minidom.parseString(string)
- server = self._extract_server(dom)
- return {'body': {'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')
-
- attributes = ["name", "imageId", "flavorId", "adminPass"]
- for attr in attributes:
- if server_node.getAttribute(attr):
- server[attr] = server_node.getAttribute(attr)
-
- metadata_node = self.find_first_child_named(server_node, "metadata")
- server["metadata"] = self.metadata_deserializer.extract_metadata(
- metadata_node)
-
- server["personality"] = self._extract_personality(server_node)
-
- return server
-
- def _extract_personality(self, server_node):
- """Marshal the personality attribute of a parsed request"""
- node = self.find_first_child_named(server_node, "personality")
- personality = []
- if node is not None:
- for file_node in self.find_children_named(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
-
-
-class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer):
- """
- Deserializer to handle xml-formatted server create requests.
-
- Handles standard server attributes as well as optional metadata
- and personality attributes
- """
-
- metadata_deserializer = common.MetadataXMLDeserializer()
-
- def action(self, string):
- dom = minidom.parseString(string)
- action_node = dom.childNodes[0]
- action_name = action_node.tagName
-
- action_deserializer = {
- 'createImage': self._action_create_image,
- 'createBackup': self._action_create_backup,
- 'changePassword': self._action_change_password,
- 'reboot': self._action_reboot,
- 'rebuild': self._action_rebuild,
- 'resize': self._action_resize,
- 'confirmResize': self._action_confirm_resize,
- 'revertResize': self._action_revert_resize,
- }.get(action_name, self.default)
-
- action_data = action_deserializer(action_node)
-
- return {'body': {action_name: action_data}}
-
- def _action_create_image(self, node):
- return self._deserialize_image_action(node, ('name',))
-
- def _action_create_backup(self, node):
- attributes = ('name', 'backup_type', 'rotation')
- return self._deserialize_image_action(node, attributes)
-
- def _action_change_password(self, node):
- if not node.hasAttribute("adminPass"):
- raise AttributeError("No adminPass was specified in request")
- return {"adminPass": node.getAttribute("adminPass")}
-
- def _action_reboot(self, node):
- if not node.hasAttribute("type"):
- raise AttributeError("No reboot type was specified in request")
- return {"type": node.getAttribute("type")}
-
- def _action_rebuild(self, node):
- rebuild = {}
- if node.hasAttribute("name"):
- rebuild['name'] = node.getAttribute("name")
-
- metadata_node = self.find_first_child_named(node, "metadata")
- if metadata_node is not None:
- rebuild["metadata"] = self.extract_metadata(metadata_node)
-
- personality = self._extract_personality(node)
- if personality is not None:
- rebuild["personality"] = personality
-
- if not node.hasAttribute("imageRef"):
- raise AttributeError("No imageRef was specified in request")
- rebuild["imageRef"] = node.getAttribute("imageRef")
-
- return rebuild
-
- def _action_resize(self, node):
- if not node.hasAttribute("flavorRef"):
- raise AttributeError("No flavorRef was specified in request")
- return {"flavorRef": node.getAttribute("flavorRef")}
-
- def _action_confirm_resize(self, node):
- return None
-
- def _action_revert_resize(self, node):
- return None
-
- def _deserialize_image_action(self, node, allowed_attributes):
- data = {}
- for attribute in allowed_attributes:
- value = node.getAttribute(attribute)
- if value:
- data[attribute] = value
- metadata_node = self.find_first_child_named(node, 'metadata')
- if metadata_node is not None:
- metadata = self.metadata_deserializer.extract_metadata(
- metadata_node)
- data['metadata'] = metadata
- return data
-
- def create(self, string):
- """Deserialize an xml-formatted server create request"""
- dom = minidom.parseString(string)
- server = self._extract_server(dom)
- return {'body': {'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')
-
- attributes = ["name", "imageRef", "flavorRef", "adminPass",
- "accessIPv4", "accessIPv6"]
- for attr in attributes:
- if server_node.getAttribute(attr):
- server[attr] = server_node.getAttribute(attr)
-
- metadata_node = self.find_first_child_named(server_node, "metadata")
- if metadata_node is not None:
- server["metadata"] = self.extract_metadata(metadata_node)
-
- personality = self._extract_personality(server_node)
- if personality is not None:
- server["personality"] = personality
-
- networks = self._extract_networks(server_node)
- if networks is not None:
- server["networks"] = networks
-
- security_groups = self._extract_security_groups(server_node)
- if security_groups is not None:
- server["security_groups"] = security_groups
-
- return server
-
- def _extract_personality(self, server_node):
- """Marshal the personality attribute of a parsed request"""
- node = self.find_first_child_named(server_node, "personality")
- if node is not None:
- personality = []
- for file_node in self.find_children_named(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
- else:
- return None
-
- def _extract_networks(self, server_node):
- """Marshal the networks attribute of a parsed request"""
- node = self.find_first_child_named(server_node, "networks")
- if node is not None:
- networks = []
- for network_node in self.find_children_named(node,
- "network"):
- item = {}
- if network_node.hasAttribute("uuid"):
- item["uuid"] = network_node.getAttribute("uuid")
- if network_node.hasAttribute("fixed_ip"):
- item["fixed_ip"] = network_node.getAttribute("fixed_ip")
- networks.append(item)
- return networks
- else:
- return None
-
- def _extract_security_groups(self, server_node):
- """Marshal the security_groups attribute of a parsed request"""
- node = self.find_first_child_named(server_node, "security_groups")
- if node is not None:
- security_groups = []
- for sg_node in self.find_children_named(node, "security_group"):
- item = {}
- name_node = self.find_first_child_named(sg_node, "name")
- if name_node:
- item["name"] = self.extract_text(name_node)
- security_groups.append(item)
- return security_groups
- else:
- return None
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index e92ab9bda..4a2d0bcbc 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -1,4 +1,5 @@
# Copyright 2010 OpenStack LLC.
+# Copyright 2011 Piston Cloud Computing, Inc
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -21,18 +22,21 @@ from novaclient import exceptions as novaclient_exceptions
from lxml import etree
from webob import exc
import webob
+from xml.dom import minidom
from nova import compute
from nova import network
from nova import db
from nova import exception
from nova import flags
+from nova import image
from nova import log as logging
from nova import utils
+from nova import quota
from nova.api.openstack import common
-from nova.api.openstack import create_instance_helper as helper
from nova.api.openstack import ips
from nova.api.openstack import wsgi
+from nova.compute import instance_types
from nova.scheduler import api as scheduler_api
import nova.api.openstack
import nova.api.openstack.views.addresses
@@ -40,6 +44,7 @@ import nova.api.openstack.views.flavors
import nova.api.openstack.views.images
import nova.api.openstack.views.servers
from nova.api.openstack import xmlutil
+from nova.rpc import common as rpc_common
LOG = logging.getLogger('nova.api.openstack.servers')
@@ -73,7 +78,6 @@ class Controller(object):
def __init__(self):
self.compute_api = compute.API()
self.network_api = network.API()
- self.helper = helper.CreateInstanceHelper(self)
def index(self, req):
""" Returns a list of server names and ids for a given user """
@@ -107,11 +111,23 @@ class Controller(object):
def _action_rebuild(self, info, request, instance_id):
raise NotImplementedError()
+ def _get_block_device_mapping(self, data):
+ """Get block_device_mapping from 'server' dictionary.
+ Overidden by volumes controller.
+ """
+ return None
+
def _get_networks_for_instance(self, req, instance):
return ips._get_networks_for_instance(req.environ['nova.context'],
self.network_api,
instance)
+ def _get_block_device_mapping(self, data):
+ """Get block_device_mapping from 'server' dictionary.
+ Overidden by volumes controller.
+ """
+ return None
+
def _get_servers(self, req, is_detail):
"""Returns a list of servers, taking into account any search
options specified.
@@ -163,6 +179,181 @@ class Controller(object):
limited_list = self._limit_items(instance_list, req)
return self._build_list(req, limited_list, is_detail=is_detail)
+ def _handle_quota_error(self, error):
+ """
+ Reraise quota errors as api-specific http exceptions
+ """
+
+ code_mappings = {
+ "OnsetFileLimitExceeded":
+ _("Personality file limit exceeded"),
+ "OnsetFilePathLimitExceeded":
+ _("Personality file path too long"),
+ "OnsetFileContentLimitExceeded":
+ _("Personality file content too long"),
+ "InstanceLimitExceeded":
+ _("Instance quotas have been exceeded")}
+
+ expl = code_mappings.get(error.code)
+ if expl:
+ raise exc.HTTPRequestEntityTooLarge(explanation=expl,
+ headers={'Retry-After': 0})
+ # 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 = ServerXMLDeserializer()
+ 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(explanation=msg)
+
+ if value.strip() == '':
+ msg = _("Server name is an empty string")
+ raise exc.HTTPBadRequest(explanation=msg)
+
+ def _get_kernel_ramdisk_from_image(self, req, image_service, 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 = 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:
+ ramdisk_id = None
+
+ 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(FLAGS.password_length)
+
+ 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(FLAGS.password_length)
+ if not isinstance(password, basestring) or password == '':
+ msg = _("Invalid adminPass")
+ raise exc.HTTPBadRequest(explanation=msg)
+ return password
+
+ def _get_requested_networks(self, requested_networks):
+ """
+ Create a list of requested networks from the networks attribute
+ """
+ networks = []
+ for network in requested_networks:
+ try:
+ network_uuid = network['uuid']
+
+ if not utils.is_uuid_like(network_uuid):
+ msg = _("Bad networks format: network uuid is not in"
+ " proper format (%s)") % network_uuid
+ raise exc.HTTPBadRequest(explanation=msg)
+
+ #fixed IP address is optional
+ #if the fixed IP address is not provided then
+ #it will use one of the available IP address from the network
+ address = network.get('fixed_ip', None)
+ if address is not None and not utils.is_valid_ipv4(address):
+ msg = _("Invalid fixed IP address (%s)") % address
+ raise exc.HTTPBadRequest(explanation=msg)
+ # check if the network id is already present in the list,
+ # we don't want duplicate networks to be passed
+ # at the boot time
+ for id, ip in networks:
+ if id == network_uuid:
+ expl = _("Duplicate networks (%s) are not allowed")\
+ % network_uuid
+ raise exc.HTTPBadRequest(explanation=expl)
+
+ networks.append((network_uuid, address))
+ except KeyError as key:
+ expl = _('Bad network format: missing %s') % key
+ raise exc.HTTPBadRequest(explanation=expl)
+ except TypeError:
+ expl = _('Bad networks format')
+ raise exc.HTTPBadRequest(explanation=expl)
+
+ return networks
+
+ def _validate_user_data(self, user_data):
+ """Check if the user_data is encoded properly"""
+ if not user_data:
+ return
+ try:
+ user_data = base64.b64decode(user_data)
+ except TypeError:
+ expl = _('Userdata content cannot be decoded')
+ raise exc.HTTPBadRequest(explanation=expl)
+
@novaclient_exception_converter
@scheduler_api.redirect_handler
def show(self, req, id):
@@ -180,22 +371,172 @@ class Controller(object):
def create(self, req, body):
""" Creates a new server for a given user """
- if 'server' in body:
- body['server']['key_name'] = self._get_key_name(req, body)
- extra_values = None
- extra_values, instances = self.helper.create_instance(
- req, body, self.compute_api.create)
+ if not body:
+ raise exc.HTTPUnprocessableEntity()
+
+ if not 'server' in body:
+ raise exc.HTTPUnprocessableEntity()
+
+ body['server']['key_name'] = self._get_key_name(req, body)
+
+ context = req.environ['nova.context']
+ server_dict = body['server']
+ password = self._get_server_admin_password(server_dict)
+
+ if not 'name' in server_dict:
+ msg = _("Server name is not defined")
+ raise exc.HTTPBadRequest(explanation=msg)
+
+ name = server_dict['name']
+ self._validate_server_name(name)
+ name = name.strip()
- # We can only return 1 instance via the API, if we happen to
- # build more than one... instances is a list, so we'll just
- # use the first one..
- inst = instances[0]
- for key in ['instance_type', 'image_ref']:
- inst[key] = extra_values[key]
+ image_href = self._image_ref_from_req_data(body)
+ # If the image href was generated by nova api, strip image_href
+ # down to an id and use the default glance connection params
- server = self._build_view(req, inst, is_detail=True)
- server['server']['adminPass'] = extra_values['password']
+ if str(image_href).startswith(req.application_url):
+ image_href = image_href.split('/').pop()
+ try:
+ image_service, image_id = image.get_image_service(context,
+ image_href)
+ kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(
+ req, image_service, 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 exc.HTTPBadRequest(explanation=msg)
+
+ personality = server_dict.get('personality')
+ config_drive = server_dict.get('config_drive')
+
+ injected_files = []
+ if personality:
+ injected_files = self._get_injected_files(personality)
+
+ sg_names = []
+ security_groups = server_dict.get('security_groups')
+ if security_groups is not None:
+ sg_names = [sg['name'] for sg in security_groups if sg.get('name')]
+ if not sg_names:
+ sg_names.append('default')
+
+ sg_names = list(set(sg_names))
+
+ requested_networks = server_dict.get('networks')
+ if requested_networks is not None:
+ requested_networks = self._get_requested_networks(
+ requested_networks)
+
+ try:
+ flavor_id = self._flavor_id_from_req_data(body)
+ except ValueError as error:
+ msg = _("Invalid flavorRef provided.")
+ raise exc.HTTPBadRequest(explanation=msg)
+
+ zone_blob = server_dict.get('blob')
+
+ # optional openstack extensions:
+ key_name = server_dict.get('key_name')
+ user_data = server_dict.get('user_data')
+ self._validate_user_data(user_data)
+
+ availability_zone = server_dict.get('availability_zone')
+ name = server_dict['name']
+ self._validate_server_name(name)
+ name = name.strip()
+
+ block_device_mapping = self._get_block_device_mapping(server_dict)
+
+ # Only allow admins to specify their own reservation_ids
+ # This is really meant to allow zones to work.
+ reservation_id = server_dict.get('reservation_id')
+ if all([reservation_id is not None,
+ reservation_id != '',
+ not context.is_admin]):
+ reservation_id = None
+
+ ret_resv_id = server_dict.get('return_reservation_id', False)
+
+ min_count = server_dict.get('min_count')
+ max_count = server_dict.get('max_count')
+ # min_count and max_count are optional. If they exist, they come
+ # in as strings. We want to default 'min_count' to 1, and default
+ # 'max_count' to be 'min_count'.
+ min_count = int(min_count) if min_count else 1
+ max_count = int(max_count) if max_count else min_count
+ if min_count > max_count:
+ min_count = max_count
+
+ try:
+ inst_type = \
+ instance_types.get_instance_type_by_flavor_id(flavor_id)
+
+ (instances, resv_id) = 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,
+ metadata=server_dict.get('metadata', {}),
+ access_ip_v4=server_dict.get('accessIPv4'),
+ access_ip_v6=server_dict.get('accessIPv6'),
+ injected_files=injected_files,
+ admin_password=password,
+ zone_blob=zone_blob,
+ reservation_id=reservation_id,
+ min_count=min_count,
+ max_count=max_count,
+ requested_networks=requested_networks,
+ security_group=sg_names,
+ user_data=user_data,
+ availability_zone=availability_zone,
+ config_drive=config_drive,
+ block_device_mapping=block_device_mapping,
+ wait_for_instances=not ret_resv_id)
+ except quota.QuotaError as error:
+ self._handle_quota_error(error)
+ except exception.InstanceTypeMemoryTooSmall as error:
+ raise exc.HTTPBadRequest(explanation=unicode(error))
+ except exception.InstanceTypeDiskTooSmall as error:
+ raise exc.HTTPBadRequest(explanation=unicode(error))
+ except exception.ImageNotFound as error:
+ msg = _("Can not find requested image")
+ raise exc.HTTPBadRequest(explanation=msg)
+ except exception.FlavorNotFound as error:
+ msg = _("Invalid flavorRef provided.")
+ raise exc.HTTPBadRequest(explanation=msg)
+ except exception.KeypairNotFound as error:
+ msg = _("Invalid key_name provided.")
+ raise exc.HTTPBadRequest(explanation=msg)
+ except exception.SecurityGroupNotFound as error:
+ raise exc.HTTPBadRequest(explanation=unicode(error))
+ except rpc_common.RemoteError as err:
+ msg = "%(err_type)s: %(err_msg)s" % \
+ {'err_type': err.exc_type, 'err_msg': err.value}
+ raise exc.HTTPBadRequest(explanation=msg)
+ # Let the caller deal with unhandled exceptions.
+
+ # If the caller wanted a reservation_id, return it
+ if ret_resv_id:
+ return {'reservation_id': resv_id}
+
+ # Instances is a list
+ instance = instances[0]
+ if not instance.get('_is_precooked', False):
+ instance['instance_type'] = inst_type
+ instance['image_ref'] = image_href
+
+ server = self._build_view(req, instance, is_detail=True)
+ if '_is_precooked' in server['server']:
+ del server['server']['_is_precooked']
+ else:
+ server['server']['adminPass'] = password
return server
def _delete(self, context, id):
@@ -218,7 +559,7 @@ class Controller(object):
if 'name' in body['server']:
name = body['server']['name']
- self.helper._validate_server_name(name)
+ self._validate_server_name(name)
update_dict['display_name'] = name.strip()
if 'accessIPv4' in body['server']:
@@ -290,17 +631,17 @@ class Controller(object):
except KeyError as missing_key:
msg = _("createBackup entity requires %s attribute") % missing_key
- raise webob.exc.HTTPBadRequest(explanation=msg)
+ raise exc.HTTPBadRequest(explanation=msg)
except TypeError:
msg = _("Malformed createBackup entity")
- raise webob.exc.HTTPBadRequest(explanation=msg)
+ raise exc.HTTPBadRequest(explanation=msg)
try:
rotation = int(rotation)
except ValueError:
msg = _("createBackup attribute 'rotation' must be an integer")
- raise webob.exc.HTTPBadRequest(explanation=msg)
+ raise exc.HTTPBadRequest(explanation=msg)
# preserve link to server in image properties
server_ref = os.path.join(req.application_url,
@@ -315,7 +656,7 @@ class Controller(object):
props.update(metadata)
except ValueError:
msg = _("Invalid metadata")
- raise webob.exc.HTTPBadRequest(explanation=msg)
+ raise exc.HTTPBadRequest(explanation=msg)
image = self.compute_api.backup(context,
instance_id,
@@ -696,7 +1037,7 @@ class ControllerV10(Controller):
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)
+ return self._get_server_admin_password_old_style(server)
def _get_server_search_options(self):
"""Return server search options allowed by non-admin"""
@@ -884,11 +1225,11 @@ class ControllerV11(Controller):
except KeyError:
msg = _("createImage entity requires name attribute")
- raise webob.exc.HTTPBadRequest(explanation=msg)
+ raise exc.HTTPBadRequest(explanation=msg)
except TypeError:
msg = _("Malformed createImage entity")
- raise webob.exc.HTTPBadRequest(explanation=msg)
+ raise exc.HTTPBadRequest(explanation=msg)
# preserve link to server in image properties
server_ref = os.path.join(req.application_url,
@@ -903,7 +1244,7 @@ class ControllerV11(Controller):
props.update(metadata)
except ValueError:
msg = _("Invalid metadata")
- raise webob.exc.HTTPBadRequest(explanation=msg)
+ raise exc.HTTPBadRequest(explanation=msg)
image = self.compute_api.snapshot(context,
instance_id,
@@ -923,7 +1264,7 @@ class ControllerV11(Controller):
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)
+ return self._get_server_admin_password_new_style(server)
def _get_server_search_options(self):
"""Return server search options allowed by non-admin"""
@@ -1068,6 +1409,227 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer):
return self._to_xml(server)
+class ServerXMLDeserializer(wsgi.XMLDeserializer):
+ """
+ Deserializer to handle xml-formatted server create requests.
+
+ Handles standard server attributes as well as optional metadata
+ and personality attributes
+ """
+
+ metadata_deserializer = common.MetadataXMLDeserializer()
+
+ def create(self, string):
+ """Deserialize an xml-formatted server create request"""
+ dom = minidom.parseString(string)
+ server = self._extract_server(dom)
+ return {'body': {'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')
+
+ attributes = ["name", "imageId", "flavorId", "adminPass"]
+ for attr in attributes:
+ if server_node.getAttribute(attr):
+ server[attr] = server_node.getAttribute(attr)
+
+ metadata_node = self.find_first_child_named(server_node, "metadata")
+ server["metadata"] = self.metadata_deserializer.extract_metadata(
+ metadata_node)
+
+ server["personality"] = self._extract_personality(server_node)
+
+ return server
+
+ def _extract_personality(self, server_node):
+ """Marshal the personality attribute of a parsed request"""
+ node = self.find_first_child_named(server_node, "personality")
+ personality = []
+ if node is not None:
+ for file_node in self.find_children_named(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
+
+
+class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer):
+ """
+ Deserializer to handle xml-formatted server create requests.
+
+ Handles standard server attributes as well as optional metadata
+ and personality attributes
+ """
+
+ metadata_deserializer = common.MetadataXMLDeserializer()
+
+ def action(self, string):
+ dom = minidom.parseString(string)
+ action_node = dom.childNodes[0]
+ action_name = action_node.tagName
+
+ action_deserializer = {
+ 'createImage': self._action_create_image,
+ 'createBackup': self._action_create_backup,
+ 'changePassword': self._action_change_password,
+ 'reboot': self._action_reboot,
+ 'rebuild': self._action_rebuild,
+ 'resize': self._action_resize,
+ 'confirmResize': self._action_confirm_resize,
+ 'revertResize': self._action_revert_resize,
+ }.get(action_name, self.default)
+
+ action_data = action_deserializer(action_node)
+
+ return {'body': {action_name: action_data}}
+
+ def _action_create_image(self, node):
+ return self._deserialize_image_action(node, ('name',))
+
+ def _action_create_backup(self, node):
+ attributes = ('name', 'backup_type', 'rotation')
+ return self._deserialize_image_action(node, attributes)
+
+ def _action_change_password(self, node):
+ if not node.hasAttribute("adminPass"):
+ raise AttributeError("No adminPass was specified in request")
+ return {"adminPass": node.getAttribute("adminPass")}
+
+ def _action_reboot(self, node):
+ if not node.hasAttribute("type"):
+ raise AttributeError("No reboot type was specified in request")
+ return {"type": node.getAttribute("type")}
+
+ def _action_rebuild(self, node):
+ rebuild = {}
+ if node.hasAttribute("name"):
+ rebuild['name'] = node.getAttribute("name")
+
+ metadata_node = self.find_first_child_named(node, "metadata")
+ if metadata_node is not None:
+ rebuild["metadata"] = self.extract_metadata(metadata_node)
+
+ personality = self._extract_personality(node)
+ if personality is not None:
+ rebuild["personality"] = personality
+
+ if not node.hasAttribute("imageRef"):
+ raise AttributeError("No imageRef was specified in request")
+ rebuild["imageRef"] = node.getAttribute("imageRef")
+
+ return rebuild
+
+ def _action_resize(self, node):
+ if not node.hasAttribute("flavorRef"):
+ raise AttributeError("No flavorRef was specified in request")
+ return {"flavorRef": node.getAttribute("flavorRef")}
+
+ def _action_confirm_resize(self, node):
+ return None
+
+ def _action_revert_resize(self, node):
+ return None
+
+ def _deserialize_image_action(self, node, allowed_attributes):
+ data = {}
+ for attribute in allowed_attributes:
+ value = node.getAttribute(attribute)
+ if value:
+ data[attribute] = value
+ metadata_node = self.find_first_child_named(node, 'metadata')
+ if metadata_node is not None:
+ metadata = self.metadata_deserializer.extract_metadata(
+ metadata_node)
+ data['metadata'] = metadata
+ return data
+
+ def create(self, string):
+ """Deserialize an xml-formatted server create request"""
+ dom = minidom.parseString(string)
+ server = self._extract_server(dom)
+ return {'body': {'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')
+
+ attributes = ["name", "imageRef", "flavorRef", "adminPass",
+ "accessIPv4", "accessIPv6"]
+ for attr in attributes:
+ if server_node.getAttribute(attr):
+ server[attr] = server_node.getAttribute(attr)
+
+ metadata_node = self.find_first_child_named(server_node, "metadata")
+ if metadata_node is not None:
+ server["metadata"] = self.extract_metadata(metadata_node)
+
+ personality = self._extract_personality(server_node)
+ if personality is not None:
+ server["personality"] = personality
+
+ networks = self._extract_networks(server_node)
+ if networks is not None:
+ server["networks"] = networks
+
+ security_groups = self._extract_security_groups(server_node)
+ if security_groups is not None:
+ server["security_groups"] = security_groups
+
+ return server
+
+ def _extract_personality(self, server_node):
+ """Marshal the personality attribute of a parsed request"""
+ node = self.find_first_child_named(server_node, "personality")
+ if node is not None:
+ personality = []
+ for file_node in self.find_children_named(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
+ else:
+ return None
+
+ def _extract_networks(self, server_node):
+ """Marshal the networks attribute of a parsed request"""
+ node = self.find_first_child_named(server_node, "networks")
+ if node is not None:
+ networks = []
+ for network_node in self.find_children_named(node,
+ "network"):
+ item = {}
+ if network_node.hasAttribute("uuid"):
+ item["uuid"] = network_node.getAttribute("uuid")
+ if network_node.hasAttribute("fixed_ip"):
+ item["fixed_ip"] = network_node.getAttribute("fixed_ip")
+ networks.append(item)
+ return networks
+ else:
+ return None
+
+ def _extract_security_groups(self, server_node):
+ """Marshal the security_groups attribute of a parsed request"""
+ node = self.find_first_child_named(server_node, "security_groups")
+ if node is not None:
+ security_groups = []
+ for sg_node in self.find_children_named(node, "security_group"):
+ item = {}
+ name_node = self.find_first_child_named(sg_node, "name")
+ if name_node:
+ item["name"] = self.extract_text(name_node)
+ security_groups.append(item)
+ return security_groups
+ else:
+ return None
+
+
def create_resource(version='1.0'):
controller = {
'1.0': ControllerV10,
@@ -1107,8 +1669,8 @@ def create_resource(version='1.0'):
}
xml_deserializer = {
- '1.0': helper.ServerXMLDeserializer(),
- '1.1': helper.ServerXMLDeserializerV11(),
+ '1.0': ServerXMLDeserializer(),
+ '1.1': ServerXMLDeserializerV11(),
}[version]
body_deserializers = {
diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py
index 998498e82..95e9eaa51 100644
--- a/nova/api/openstack/zones.py
+++ b/nova/api/openstack/zones.py
@@ -25,8 +25,8 @@ 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_helper as helper
from nova.api.openstack import common
+from nova.api.openstack import servers
from nova.api.openstack import wsgi
@@ -67,7 +67,6 @@ class Controller(object):
def __init__(self):
self.compute_api = compute.API()
- self.helper = helper.CreateInstanceHelper(self)
def index(self, req):
"""Return all zones in brief"""
@@ -120,18 +119,6 @@ class Controller(object):
zone = api.zone_update(context, zone_id, body["zone"])
return dict(zone=_scrub_zone(zone))
- def boot(self, req, body):
- """Creates a new server for a given user while being Zone aware.
-
- Returns a reservation ID (a UUID).
- """
- result = None
- extra_values, result = self.helper.create_instance(req, body,
- self.compute_api.create_all_at_once)
-
- reservation_id = result
- return {'reservation_id': reservation_id}
-
@check_encryption_key
def select(self, req, body):
"""Returns a weighted list of costs to create instances
@@ -155,37 +142,8 @@ class Controller(object):
blob=cipher_text))
return cooked
- 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 self.helper._get_server_admin_password_old_style(server)
-
-
-class ControllerV11(Controller):
- """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):
- return data['server']['flavorRef']
-
def create_resource(version):
- controller = {
- '1.0': Controller,
- '1.1': ControllerV11,
- }[version]()
-
metadata = {
"attributes": {
"zone": ["id", "api_url", "name", "capabilities"],
@@ -199,8 +157,8 @@ def create_resource(version):
serializer = wsgi.ResponseSerializer(body_serializers)
body_deserializers = {
- 'application/xml': helper.ServerXMLDeserializer(),
+ 'application/xml': servers.ServerXMLDeserializer(),
}
deserializer = wsgi.RequestDeserializer(body_deserializers)
- return wsgi.Resource(controller, deserializer, serializer)
+ return wsgi.Resource(Controller(), deserializer, serializer)
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 5f5e980c3..3900e8a40 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -75,6 +75,11 @@ def generate_default_hostname(instance):
return display_name.translate(table, deletions)
+def generate_default_display_name(instance):
+ """Generate a default display name"""
+ return 'Server %s' % instance['id']
+
+
def _is_able_to_shutdown(instance, instance_id):
vm_state = instance["vm_state"]
task_state = instance["task_state"]
@@ -177,17 +182,27 @@ class API(base.Base):
self.network_api.validate_networks(context, requested_networks)
- def _check_create_parameters(self, context, instance_type,
- image_href, kernel_id=None, ramdisk_id=None,
- min_count=None, max_count=None,
- display_name='', display_description='',
- key_name=None, key_data=None, security_group='default',
- availability_zone=None, user_data=None, metadata=None,
- injected_files=None, admin_password=None, zone_blob=None,
- reservation_id=None, access_ip_v4=None, access_ip_v6=None,
- requested_networks=None, config_drive=None,):
+ def _create_instance(self, context, instance_type,
+ image_href, kernel_id, ramdisk_id,
+ min_count, max_count,
+ display_name, display_description,
+ key_name, key_data, security_group,
+ availability_zone, user_data, metadata,
+ injected_files, admin_password, zone_blob,
+ reservation_id, access_ip_v4, access_ip_v6,
+ requested_networks, config_drive,
+ block_device_mapping,
+ wait_for_instances):
"""Verify all the input parameters regardless of the provisioning
- strategy being performed."""
+ strategy being performed and schedule the instance(s) for
+ creation."""
+
+ if not metadata:
+ metadata = {}
+ if not display_description:
+ display_description = ''
+ if not security_group:
+ security_group = 'default'
if not instance_type:
instance_type = instance_types.get_default_instance_type()
@@ -198,6 +213,8 @@ class API(base.Base):
if not metadata:
metadata = {}
+ block_device_mapping = block_device_mapping or []
+
num_instances = quota.allowed_instances(context, max_count,
instance_type)
if num_instances < min_count:
@@ -303,7 +320,28 @@ class API(base.Base):
'vm_mode': vm_mode,
'root_device_name': root_device_name}
- return (num_instances, base_options, image)
+ LOG.debug(_("Going to run %s instances...") % num_instances)
+
+ if wait_for_instances:
+ rpc_method = rpc.call
+ else:
+ rpc_method = rpc.cast
+
+ # TODO(comstud): We should use rpc.multicall when we can
+ # retrieve the full instance dictionary from the scheduler.
+ # Otherwise, we could exceed the AMQP max message size limit.
+ # This would require the schedulers' schedule_run_instances
+ # methods to return an iterator vs a list.
+ instances = self._schedule_run_instance(
+ rpc_method,
+ context, base_options,
+ instance_type, zone_blob,
+ availability_zone, injected_files,
+ admin_password, image,
+ num_instances, requested_networks,
+ block_device_mapping, security_group)
+
+ return (instances, reservation_id)
@staticmethod
def _volume_size(instance_type, virtual_name):
@@ -399,10 +437,8 @@ class API(base.Base):
including any related table updates (such as security group,
etc).
- This will called by create() in the majority of situations,
- but create_all_at_once() style Schedulers may initiate the call.
- If you are changing this method, be sure to update both
- call paths.
+ This is called by the scheduler after a location for the
+ instance has been determined.
"""
elevated = context.elevated()
if security_group is None:
@@ -439,7 +475,7 @@ class API(base.Base):
updates = {}
if (not hasattr(instance, 'display_name') or
instance.display_name is None):
- updates['display_name'] = "Server %s" % instance_id
+ updates['display_name'] = generate_default_display_name(instance)
instance['display_name'] = updates['display_name']
updates['hostname'] = self.hostname_factory(instance)
updates['vm_state'] = vm_states.BUILDING
@@ -448,21 +484,23 @@ class API(base.Base):
instance = self.update(context, instance_id, **updates)
return instance
- def _ask_scheduler_to_create_instance(self, context, base_options,
- instance_type, zone_blob,
- availability_zone, injected_files,
- admin_password, image,
- instance_id=None, num_instances=1,
- requested_networks=None):
- """Send the run_instance request to the schedulers for processing."""
+ def _schedule_run_instance(self,
+ rpc_method,
+ context, base_options,
+ instance_type, zone_blob,
+ availability_zone, injected_files,
+ admin_password, image,
+ num_instances,
+ requested_networks,
+ block_device_mapping,
+ security_group):
+ """Send a run_instance request to the schedulers for processing."""
+
pid = context.project_id
uid = context.user_id
- if instance_id:
- LOG.debug(_("Casting to scheduler for %(pid)s/%(uid)s's"
- " instance %(instance_id)s (single-shot)") % locals())
- else:
- LOG.debug(_("Casting to scheduler for %(pid)s/%(uid)s's"
- " (all-at-once)") % locals())
+
+ LOG.debug(_("Sending create to scheduler for %(pid)s/%(uid)s's") %
+ locals())
request_spec = {
'image': image,
@@ -471,82 +509,41 @@ class API(base.Base):
'filter': None,
'blob': zone_blob,
'num_instances': num_instances,
+ 'block_device_mapping': block_device_mapping,
+ 'security_group': security_group,
}
- rpc.cast(context,
- FLAGS.scheduler_topic,
- {"method": "run_instance",
- "args": {"topic": FLAGS.compute_topic,
- "instance_id": instance_id,
- "request_spec": request_spec,
- "availability_zone": availability_zone,
- "admin_password": admin_password,
- "injected_files": injected_files,
- "requested_networks": requested_networks}})
-
- def create_all_at_once(self, context, instance_type,
- image_href, kernel_id=None, ramdisk_id=None,
- min_count=None, max_count=None,
- display_name='', display_description='',
- key_name=None, key_data=None, security_group='default',
- availability_zone=None, user_data=None, metadata=None,
- injected_files=None, admin_password=None, zone_blob=None,
- reservation_id=None, block_device_mapping=None,
- access_ip_v4=None, access_ip_v6=None,
- requested_networks=None, config_drive=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."""
-
- if not metadata:
- metadata = {}
-
- num_instances, base_options, image = self._check_create_parameters(
- context, instance_type,
- image_href, kernel_id, ramdisk_id,
- min_count, max_count,
- display_name, display_description,
- key_name, key_data, security_group,
- availability_zone, user_data, metadata,
- injected_files, admin_password, zone_blob,
- reservation_id, access_ip_v4, access_ip_v6,
- requested_networks, config_drive)
-
- self._ask_scheduler_to_create_instance(context, base_options,
- instance_type, zone_blob,
- availability_zone, injected_files,
- admin_password, image,
- num_instances=num_instances,
- requested_networks=requested_networks)
-
- return base_options['reservation_id']
+ return rpc_method(context,
+ FLAGS.scheduler_topic,
+ {"method": "run_instance",
+ "args": {"topic": FLAGS.compute_topic,
+ "request_spec": request_spec,
+ "admin_password": admin_password,
+ "injected_files": injected_files,
+ "requested_networks": requested_networks}})
def create(self, context, instance_type,
image_href, kernel_id=None, ramdisk_id=None,
min_count=None, max_count=None,
- display_name='', display_description='',
- key_name=None, key_data=None, security_group='default',
+ display_name=None, display_description=None,
+ key_name=None, key_data=None, security_group=None,
availability_zone=None, user_data=None, metadata=None,
injected_files=None, admin_password=None, zone_blob=None,
reservation_id=None, block_device_mapping=None,
access_ip_v4=None, access_ip_v6=None,
- requested_networks=None, config_drive=None,):
+ requested_networks=None, config_drive=None,
+ wait_for_instances=True):
"""
- Provision the instances by sending off a series of single
- instance requests to the Schedulers. This is fine for trival
- Scheduler drivers, but may remove the effectiveness of the
- more complicated drivers.
+ Provision instances, sending instance information to the
+ scheduler. The scheduler will determine where the instance(s)
+ go and will handle creating the DB entries.
- NOTE: If you change this method, be sure to change
- create_all_at_once() at the same time!
-
- Returns a list of instance dicts.
+ Returns a tuple of (instances, reservation_id) where instances
+ could be 'None' or a list of instance dicts depending on if
+ we waited for information from the scheduler or not.
"""
- if not metadata:
- metadata = {}
-
- num_instances, base_options, image = self._check_create_parameters(
+ (instances, reservation_id) = self._create_instance(
context, instance_type,
image_href, kernel_id, ramdisk_id,
min_count, max_count,
@@ -555,27 +552,25 @@ class API(base.Base):
availability_zone, user_data, metadata,
injected_files, admin_password, zone_blob,
reservation_id, access_ip_v4, access_ip_v6,
- requested_networks, config_drive)
+ requested_networks, config_drive,
+ block_device_mapping,
+ wait_for_instances)
- block_device_mapping = block_device_mapping or []
- instances = []
- LOG.debug(_("Going to run %s instances..."), num_instances)
- for num in range(num_instances):
- instance = self.create_db_entry_for_new_instance(context,
- instance_type, image,
- base_options, security_group,
- block_device_mapping, num=num)
- instances.append(instance)
- instance_id = instance['id']
-
- self._ask_scheduler_to_create_instance(context, base_options,
- instance_type, zone_blob,
- availability_zone, injected_files,
- admin_password, image,
- instance_id=instance_id,
- requested_networks=requested_networks)
-
- return [dict(x.iteritems()) for x in instances]
+ if instances is None:
+ # wait_for_instances must have been False
+ return (instances, reservation_id)
+
+ inst_ret_list = []
+ for instance in instances:
+ if instance.get('_is_precooked', False):
+ inst_ret_list.append(instance)
+ else:
+ # Scheduler only gives us the 'id'. We need to pull
+ # in the created instances from the DB
+ instance = self.db.instance_get(context, instance['id'])
+ inst_ret_list.append(dict(instance.iteritems()))
+
+ return (inst_ret_list, reservation_id)
def has_finished_migration(self, context, instance_uuid):
"""Returns true if an instance has a finished migration."""
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 0d504ab78..878a70add 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -69,8 +69,6 @@ flags.DEFINE_string('instances_path', '$state_path/instances',
'where instances are stored on disk')
flags.DEFINE_string('compute_driver', 'nova.virt.connection.get_connection',
'Driver to use for controlling virtualization')
-flags.DEFINE_string('stub_network', False,
- 'Stub network related code')
flags.DEFINE_string('console_host', socket.gethostname(),
'Console proxy host to use to connect to instances on'
'this host.')
diff --git a/nova/flags.py b/nova/flags.py
index 5dde02adf..1050f0347 100644
--- a/nova/flags.py
+++ b/nova/flags.py
@@ -432,3 +432,6 @@ DEFINE_list('monkey_patch_modules',
DEFINE_bool('allow_resize_to_same_host', False,
'Allow destination machine to match source for resize. Useful'
' when testing in environments with only one host machine.')
+
+DEFINE_string('stub_network', False,
+ 'Stub network related code')
diff --git a/nova/scheduler/abstract_scheduler.py b/nova/scheduler/abstract_scheduler.py
index b121444f2..e8712e5df 100644
--- a/nova/scheduler/abstract_scheduler.py
+++ b/nova/scheduler/abstract_scheduler.py
@@ -60,24 +60,10 @@ class AbstractScheduler(driver.Scheduler):
request_spec, kwargs):
"""Create the requested resource in this Zone."""
host = build_plan_item['hostname']
- base_options = request_spec['instance_properties']
- image = request_spec['image']
- instance_type = request_spec.get('instance_type')
-
- # TODO(sandy): I guess someone needs to add block_device_mapping
- # support at some point? Also, OS API has no concept of security
- # groups.
- instance = compute_api.API().create_db_entry_for_new_instance(context,
- instance_type, image, base_options, None, [])
-
- instance_id = instance['id']
- kwargs['instance_id'] = instance_id
-
- queue = db.queue_get_for(context, "compute", host)
- params = {"method": "run_instance", "args": kwargs}
- rpc.cast(context, queue, params)
- LOG.debug(_("Provisioning locally via compute node %(host)s")
- % locals())
+ instance = self.create_instance_db_entry(context, request_spec)
+ driver.cast_to_compute_host(context, host,
+ 'run_instance', instance_id=instance['id'], **kwargs)
+ return driver.encode_instance(instance, local=True)
def _decrypt_blob(self, blob):
"""Returns the decrypted blob or None if invalid. Broken out
@@ -112,7 +98,7 @@ class AbstractScheduler(driver.Scheduler):
files = kwargs['injected_files']
child_zone = zone_info['child_zone']
child_blob = zone_info['child_blob']
- zone = db.zone_get(context, child_zone)
+ zone = db.zone_get(context.elevated(), child_zone)
url = zone.api_url
LOG.debug(_("Forwarding instance create call to child zone %(url)s"
". ReservationID=%(reservation_id)s") % locals())
@@ -132,12 +118,13 @@ class AbstractScheduler(driver.Scheduler):
# arguments are passed as keyword arguments
# (there's a reasonable default for ipgroups in the
# novaclient call).
- nova.servers.create(name, image_ref, flavor_id,
+ instance = nova.servers.create(name, image_ref, flavor_id,
meta=meta, files=files, zone_blob=child_blob,
reservation_id=reservation_id)
+ return driver.encode_instance(instance._info, local=False)
def _provision_resource_from_blob(self, context, build_plan_item,
- instance_id, request_spec, kwargs):
+ request_spec, kwargs):
"""Create the requested resource locally or in a child zone
based on what is stored in the zone blob info.
@@ -165,21 +152,21 @@ class AbstractScheduler(driver.Scheduler):
# Valid data ... is it for us?
if 'child_zone' in host_info and 'child_blob' in host_info:
- self._ask_child_zone_to_create_instance(context, host_info,
- request_spec, kwargs)
+ instance = self._ask_child_zone_to_create_instance(context,
+ host_info, request_spec, kwargs)
else:
- self._provision_resource_locally(context, host_info, request_spec,
- kwargs)
+ instance = self._provision_resource_locally(context,
+ host_info, request_spec, kwargs)
+ return instance
- def _provision_resource(self, context, build_plan_item, instance_id,
+ def _provision_resource(self, context, build_plan_item,
request_spec, kwargs):
"""Create the requested resource in this Zone or a child zone."""
if "hostname" in build_plan_item:
- self._provision_resource_locally(context, build_plan_item,
- request_spec, kwargs)
- return
- self._provision_resource_from_blob(context, build_plan_item,
- instance_id, request_spec, kwargs)
+ return self._provision_resource_locally(context,
+ build_plan_item, request_spec, kwargs)
+ return self._provision_resource_from_blob(context,
+ build_plan_item, request_spec, kwargs)
def _adjust_child_weights(self, child_results, zones):
"""Apply the Scale and Offset values from the Zone definition
@@ -205,8 +192,7 @@ class AbstractScheduler(driver.Scheduler):
LOG.exception(_("Bad child zone scaling values "
"for Zone: %(zone_id)s") % locals())
- def schedule_run_instance(self, context, instance_id, request_spec,
- *args, **kwargs):
+ def schedule_run_instance(self, context, request_spec, *args, **kwargs):
"""This method is called from nova.compute.api to provision
an instance. However we need to look at the parameters being
passed in to see if this is a request to:
@@ -214,13 +200,16 @@ class AbstractScheduler(driver.Scheduler):
2. Use the Build Plan information in the request parameters
to simply create the instance (either in this zone or
a child zone).
+
+ returns list of instances created.
"""
# TODO(sandy): We'll have to look for richer specs at some point.
blob = request_spec.get('blob')
if blob:
- self._provision_resource(context, request_spec, instance_id,
- request_spec, kwargs)
- return None
+ instance = self._provision_resource(context,
+ request_spec, request_spec, kwargs)
+ # Caller expects a list of instances
+ return [instance]
num_instances = request_spec.get('num_instances', 1)
LOG.debug(_("Attempting to build %(num_instances)d instance(s)") %
@@ -231,16 +220,16 @@ class AbstractScheduler(driver.Scheduler):
if not build_plan:
raise driver.NoValidHost(_('No hosts were available'))
+ instances = []
for num in xrange(num_instances):
if not build_plan:
break
build_plan_item = build_plan.pop(0)
- self._provision_resource(context, build_plan_item, instance_id,
- request_spec, kwargs)
+ instance = self._provision_resource(context,
+ build_plan_item, request_spec, kwargs)
+ instances.append(instance)
- # Returning None short-circuits the routing to Compute (since
- # we've already done it here)
- return None
+ return instances
def select(self, context, request_spec, *args, **kwargs):
"""Select returns a list of weights and zone/host information
@@ -251,7 +240,7 @@ class AbstractScheduler(driver.Scheduler):
return self._schedule(context, "compute", request_spec,
*args, **kwargs)
- def schedule(self, context, topic, request_spec, *args, **kwargs):
+ def schedule(self, context, topic, method, *args, **kwargs):
"""The schedule() contract requires we return the one
best-suited host for this request.
"""
@@ -285,7 +274,7 @@ class AbstractScheduler(driver.Scheduler):
weighted_hosts = self.weigh_hosts(topic, request_spec, filtered_hosts)
# Next, tack on the host weights from the child zones
json_spec = json.dumps(request_spec)
- all_zones = db.zone_get_all(context)
+ all_zones = db.zone_get_all(context.elevated())
child_results = self._call_zone_method(context, "select",
specs=json_spec, zones=all_zones)
self._adjust_child_weights(child_results, all_zones)
diff --git a/nova/scheduler/api.py b/nova/scheduler/api.py
index 44e01aa0c..0b91edccc 100644
--- a/nova/scheduler/api.py
+++ b/nova/scheduler/api.py
@@ -65,7 +65,7 @@ def get_zone_list(context):
for item in items:
item['api_url'] = item['api_url'].replace('\\/', '/')
if not items:
- items = db.zone_get_all(context)
+ items = db.zone_get_all(context.elevated())
return items
@@ -116,7 +116,7 @@ def call_zone_method(context, method_name, errors_to_ignore=None,
pool = greenpool.GreenPool()
results = []
if zones is None:
- zones = db.zone_get_all(context)
+ zones = db.zone_get_all(context.elevated())
for zone in zones:
try:
# Do this on behalf of the user ...
diff --git a/nova/scheduler/chance.py b/nova/scheduler/chance.py
index f4461cee2..8a69f2d0f 100644
--- a/nova/scheduler/chance.py
+++ b/nova/scheduler/chance.py
@@ -29,12 +29,33 @@ from nova.scheduler import driver
class ChanceScheduler(driver.Scheduler):
"""Implements Scheduler as a random node selector."""
- def schedule(self, context, topic, *_args, **_kwargs):
+ def _schedule(self, context, topic, **kwargs):
"""Picks a host that is up at random."""
- hosts = self.hosts_up(context, topic)
+ elevated = context.elevated()
+ hosts = self.hosts_up(elevated, topic)
if not hosts:
raise driver.NoValidHost(_("Scheduler was unable to locate a host"
" for this request. Is the appropriate"
" service running?"))
return hosts[int(random.random() * len(hosts))]
+
+ def schedule(self, context, topic, method, *_args, **kwargs):
+ """Picks a host that is up at random."""
+
+ host = self._schedule(context, topic, **kwargs)
+ driver.cast_to_host(context, topic, host, method, **kwargs)
+
+ def schedule_run_instance(self, context, request_spec, *_args, **kwargs):
+ """Create and run an instance or instances"""
+ elevated = context.elevated()
+ num_instances = request_spec.get('num_instances', 1)
+ instances = []
+ for num in xrange(num_instances):
+ host = self._schedule(context, 'compute', **kwargs)
+ instance = self.create_instance_db_entry(elevated, request_spec)
+ driver.cast_to_compute_host(context, host,
+ 'run_instance', instance_id=instance['id'], **kwargs)
+ instances.append(driver.encode_instance(instance))
+
+ return instances
diff --git a/nova/scheduler/driver.py b/nova/scheduler/driver.py
index 22f4e14f9..7bf26cfdf 100644
--- a/nova/scheduler/driver.py
+++ b/nova/scheduler/driver.py
@@ -29,17 +29,94 @@ from nova import flags
from nova import log as logging
from nova import rpc
from nova import utils
+from nova.compute import api as compute_api
from nova.compute import power_state
from nova.compute import vm_states
from nova.api.ec2 import ec2utils
FLAGS = flags.FLAGS
+LOG = logging.getLogger('nova.scheduler.driver')
flags.DEFINE_integer('service_down_time', 60,
'maximum time since last checkin for up service')
flags.DECLARE('instances_path', 'nova.compute.manager')
+def cast_to_volume_host(context, host, method, update_db=True, **kwargs):
+ """Cast request to a volume host queue"""
+
+ if update_db:
+ volume_id = kwargs.get('volume_id', None)
+ if volume_id is not None:
+ now = utils.utcnow()
+ db.volume_update(context, volume_id,
+ {'host': host, 'scheduled_at': now})
+ rpc.cast(context,
+ db.queue_get_for(context, 'volume', host),
+ {"method": method, "args": kwargs})
+ LOG.debug(_("Casted '%(method)s' to volume '%(host)s'") % locals())
+
+
+def cast_to_compute_host(context, host, method, update_db=True, **kwargs):
+ """Cast request to a compute host queue"""
+
+ if update_db:
+ instance_id = kwargs.get('instance_id', None)
+ if instance_id is not None:
+ now = utils.utcnow()
+ db.instance_update(context, instance_id,
+ {'host': host, 'scheduled_at': now})
+ rpc.cast(context,
+ db.queue_get_for(context, 'compute', host),
+ {"method": method, "args": kwargs})
+ LOG.debug(_("Casted '%(method)s' to compute '%(host)s'") % locals())
+
+
+def cast_to_network_host(context, host, method, update_db=False, **kwargs):
+ """Cast request to a network host queue"""
+
+ rpc.cast(context,
+ db.queue_get_for(context, 'network', host),
+ {"method": method, "args": kwargs})
+ LOG.debug(_("Casted '%(method)s' to network '%(host)s'") % locals())
+
+
+def cast_to_host(context, topic, host, method, update_db=True, **kwargs):
+ """Generic cast to host"""
+
+ topic_mapping = {
+ "compute": cast_to_compute_host,
+ "volume": cast_to_volume_host,
+ 'network': cast_to_network_host}
+
+ func = topic_mapping.get(topic)
+ if func:
+ func(context, host, method, update_db=update_db, **kwargs)
+ else:
+ rpc.cast(context,
+ db.queue_get_for(context, topic, host),
+ {"method": method, "args": kwargs})
+ LOG.debug(_("Casted '%(method)s' to %(topic)s '%(host)s'")
+ % locals())
+
+
+def encode_instance(instance, local=True):
+ """Encode locally created instance for return via RPC"""
+ # TODO(comstud): I would love to be able to return the full
+ # instance information here, but we'll need some modifications
+ # to the RPC code to handle datetime conversions with the
+ # json encoding/decoding. We should be able to set a default
+ # json handler somehow to do it.
+ #
+ # For now, I'll just return the instance ID and let the caller
+ # do a DB lookup :-/
+ if local:
+ return dict(id=instance['id'], _is_precooked=False)
+ else:
+ instance['_is_precooked'] = True
+ return instance
+
+
class NoValidHost(exception.Error):
"""There is no valid host for the command."""
pass
@@ -55,6 +132,7 @@ class Scheduler(object):
def __init__(self):
self.zone_manager = None
+ self.compute_api = compute_api.API()
def set_zone_manager(self, zone_manager):
"""Called by the Scheduler Service to supply a ZoneManager."""
@@ -76,7 +154,20 @@ class Scheduler(object):
for service in services
if self.service_is_up(service)]
- def schedule(self, context, topic, *_args, **_kwargs):
+ def create_instance_db_entry(self, context, request_spec):
+ """Create instance DB entry based on request_spec"""
+ base_options = request_spec['instance_properties']
+ image = request_spec['image']
+ instance_type = request_spec.get('instance_type')
+ security_group = request_spec.get('security_group', 'default')
+ block_device_mapping = request_spec.get('block_device_mapping', [])
+
+ instance = self.compute_api.create_db_entry_for_new_instance(
+ context, instance_type, image, base_options,
+ security_group, block_device_mapping)
+ return instance
+
+ def schedule(self, context, topic, method, *_args, **_kwargs):
"""Must override at least this method for scheduler to work."""
raise NotImplementedError(_("Must implement a fallback schedule"))
@@ -114,10 +205,12 @@ class Scheduler(object):
volume_ref['id'],
{'status': 'migrating'})
- # Return value is necessary to send request to src
- # Check _schedule() in detail.
src = instance_ref['host']
- return src
+ cast_to_compute_host(context, src, 'live_migration',
+ update_db=False,
+ instance_id=instance_id,
+ dest=dest,
+ block_migration=block_migration)
def _live_migration_src_check(self, context, instance_ref):
"""Live migration check routine (for src host).
@@ -205,7 +298,7 @@ class Scheduler(object):
if not block_migration:
src = instance_ref['host']
ipath = FLAGS.instances_path
- logging.error(_("Cannot confirm tmpfile at %(ipath)s is on "
+ LOG.error(_("Cannot confirm tmpfile at %(ipath)s is on "
"same shared storage between %(src)s "
"and %(dest)s.") % locals())
raise
@@ -243,7 +336,7 @@ class Scheduler(object):
except rpc.RemoteError:
src = instance_ref['host']
- logging.exception(_("host %(dest)s is not compatible with "
+ LOG.exception(_("host %(dest)s is not compatible with "
"original host %(src)s.") % locals())
raise
@@ -354,6 +447,8 @@ class Scheduler(object):
dst_t = db.queue_get_for(context, FLAGS.compute_topic, dest)
src_t = db.queue_get_for(context, FLAGS.compute_topic, src)
+ filename = None
+
try:
# create tmpfile at dest host
filename = rpc.call(context, dst_t,
@@ -370,6 +465,8 @@ class Scheduler(object):
raise
finally:
- rpc.call(context, dst_t,
- {"method": 'cleanup_shared_storage_test_file',
- "args": {'filename': filename}})
+ # Should only be None for tests?
+ if filename is not None:
+ rpc.call(context, dst_t,
+ {"method": 'cleanup_shared_storage_test_file',
+ "args": {'filename': filename}})
diff --git a/nova/scheduler/least_cost.py b/nova/scheduler/least_cost.py
index 903d786cd..1a6ef990c 100644
--- a/nova/scheduler/least_cost.py
+++ b/nova/scheduler/least_cost.py
@@ -160,8 +160,7 @@ class LeastCostScheduler(base_scheduler.BaseScheduler):
weighted = []
weight_log = []
- for cost, (hostname, service) in zip(costs, hosts):
- caps = service[topic]
+ for cost, (hostname, caps) in zip(costs, hosts):
weight_log.append("%s: %s" % (hostname, "%.2f" % cost))
weight_dict = dict(weight=cost, hostname=hostname,
capabilities=caps)
diff --git a/nova/scheduler/manager.py b/nova/scheduler/manager.py
index bf18abc6c..f5a268adb 100644
--- a/nova/scheduler/manager.py
+++ b/nova/scheduler/manager.py
@@ -81,37 +81,23 @@ class SchedulerManager(manager.Manager):
"""Select a list of hosts best matching the provided specs."""
return self.driver.select(context, *args, **kwargs)
- def get_scheduler_rules(self, context=None, *args, **kwargs):
- """Ask the driver how requests should be made of it."""
- return self.driver.get_scheduler_rules(context, *args, **kwargs)
-
def _schedule(self, method, context, topic, *args, **kwargs):
"""Tries to call schedule_* method on the driver to retrieve host.
Falls back to schedule(context, topic) if method doesn't exist.
"""
driver_method = 'schedule_%s' % method
- elevated = context.elevated()
try:
real_meth = getattr(self.driver, driver_method)
- args = (elevated,) + args
+ args = (context,) + args
except AttributeError, e:
LOG.warning(_("Driver Method %(driver_method)s missing: %(e)s."
"Reverting to schedule()") % locals())
real_meth = self.driver.schedule
- args = (elevated, topic) + args
- host = real_meth(*args, **kwargs)
-
- if not host:
- LOG.debug(_("%(topic)s %(method)s handled in Scheduler")
- % locals())
- return
-
- rpc.cast(context,
- db.queue_get_for(context, topic, host),
- {"method": method,
- "args": kwargs})
- LOG.debug(_("Casted to %(topic)s %(host)s for %(method)s") % locals())
+ args = (context, topic, method) + args
+
+ # Scheduler methods are responsible for casting.
+ return real_meth(*args, **kwargs)
# NOTE (masumotok) : This method should be moved to nova.api.ec2.admin.
# Based on bexar design summit discussion,
diff --git a/nova/scheduler/multi.py b/nova/scheduler/multi.py
index b1578033c..126dbe60e 100644
--- a/nova/scheduler/multi.py
+++ b/nova/scheduler/multi.py
@@ -38,7 +38,8 @@ flags.DEFINE_string('volume_scheduler_driver',
# A mapping of methods to topics so we can figure out which driver to use.
_METHOD_MAP = {'run_instance': 'compute',
'start_instance': 'compute',
- 'create_volume': 'volume'}
+ 'create_volume': 'volume',
+ 'create_volumes': 'volume'}
class MultiScheduler(driver.Scheduler):
@@ -69,5 +70,6 @@ class MultiScheduler(driver.Scheduler):
for k, v in self.drivers.iteritems():
v.set_zone_manager(zone_manager)
- def schedule(self, context, topic, *_args, **_kwargs):
- return self.drivers[topic].schedule(context, topic, *_args, **_kwargs)
+ def schedule(self, context, topic, method, *_args, **_kwargs):
+ return self.drivers[topic].schedule(context, topic,
+ method, *_args, **_kwargs)
diff --git a/nova/scheduler/simple.py b/nova/scheduler/simple.py
index fc1b3142a..cce1509b8 100644
--- a/nova/scheduler/simple.py
+++ b/nova/scheduler/simple.py
@@ -39,47 +39,50 @@ flags.DEFINE_integer("max_networks", 1000,
class SimpleScheduler(chance.ChanceScheduler):
"""Implements Naive Scheduler that tries to find least loaded host."""
- def _schedule_instance(self, context, instance_id, *_args, **_kwargs):
+ def _schedule_instance(self, context, instance_opts, *_args, **_kwargs):
"""Picks a host that is up and has the fewest running instances."""
- instance_ref = db.instance_get(context, instance_id)
- if (instance_ref['availability_zone']
- and ':' in instance_ref['availability_zone']
- and context.is_admin):
- zone, _x, host = instance_ref['availability_zone'].partition(':')
+
+ availability_zone = instance_opts.get('availability_zone')
+
+ if availability_zone and context.is_admin and \
+ (':' in availability_zone):
+ zone, host = availability_zone.split(':', 1)
service = db.service_get_by_args(context.elevated(), host,
'nova-compute')
if not self.service_is_up(service):
raise driver.WillNotSchedule(_("Host %s is not alive") % host)
-
- # TODO(vish): this probably belongs in the manager, if we
- # can generalize this somehow
- now = utils.utcnow()
- db.instance_update(context, instance_id, {'host': host,
- 'scheduled_at': now})
return host
+
results = db.service_get_all_compute_sorted(context)
for result in results:
(service, instance_cores) = result
- if instance_cores + instance_ref['vcpus'] > FLAGS.max_cores:
+ if instance_cores + instance_opts['vcpus'] > FLAGS.max_cores:
raise driver.NoValidHost(_("All hosts have too many cores"))
if self.service_is_up(service):
- # NOTE(vish): this probably belongs in the manager, if we
- # can generalize this somehow
- now = utils.utcnow()
- db.instance_update(context,
- instance_id,
- {'host': service['host'],
- 'scheduled_at': now})
return service['host']
raise driver.NoValidHost(_("Scheduler was unable to locate a host"
" for this request. Is the appropriate"
" service running?"))
- def schedule_run_instance(self, context, instance_id, *_args, **_kwargs):
- return self._schedule_instance(context, instance_id, *_args, **_kwargs)
+ def schedule_run_instance(self, context, request_spec, *_args, **_kwargs):
+ num_instances = request_spec.get('num_instances', 1)
+ instances = []
+ for num in xrange(num_instances):
+ host = self._schedule_instance(context,
+ request_spec['instance_properties'], *_args, **_kwargs)
+ instance_ref = self.create_instance_db_entry(context,
+ request_spec)
+ driver.cast_to_compute_host(context, host, 'run_instance',
+ instance_id=instance_ref['id'], **_kwargs)
+ instances.append(driver.encode_instance(instance_ref))
+ return instances
def schedule_start_instance(self, context, instance_id, *_args, **_kwargs):
- return self._schedule_instance(context, instance_id, *_args, **_kwargs)
+ instance_ref = db.instance_get(context, instance_id)
+ host = self._schedule_instance(context, instance_ref,
+ *_args, **_kwargs)
+ driver.cast_to_compute_host(context, host, 'start_instance',
+ instance_id=intance_id, **_kwargs)
def schedule_create_volume(self, context, volume_id, *_args, **_kwargs):
"""Picks a host that is up and has the fewest volumes."""
@@ -92,13 +95,9 @@ class SimpleScheduler(chance.ChanceScheduler):
'nova-volume')
if not self.service_is_up(service):
raise driver.WillNotSchedule(_("Host %s not available") % host)
-
- # TODO(vish): this probably belongs in the manager, if we
- # can generalize this somehow
- now = utils.utcnow()
- db.volume_update(context, volume_id, {'host': host,
- 'scheduled_at': now})
- return host
+ driver.cast_to_volume_host(context, host, 'create_volume',
+ volume_id=volume_id, **_kwargs)
+ return None
results = db.service_get_all_volume_sorted(context)
for result in results:
(service, volume_gigabytes) = result
@@ -106,14 +105,9 @@ class SimpleScheduler(chance.ChanceScheduler):
raise driver.NoValidHost(_("All hosts have too many "
"gigabytes"))
if self.service_is_up(service):
- # NOTE(vish): this probably belongs in the manager, if we
- # can generalize this somehow
- now = utils.utcnow()
- db.volume_update(context,
- volume_id,
- {'host': service['host'],
- 'scheduled_at': now})
- return service['host']
+ driver.cast_to_volume_host(context, service['host'],
+ 'create_volume', volume_id=volume_id, **_kwargs)
+ return None
raise driver.NoValidHost(_("Scheduler was unable to locate a host"
" for this request. Is the appropriate"
" service running?"))
@@ -127,7 +121,9 @@ class SimpleScheduler(chance.ChanceScheduler):
if instance_count >= FLAGS.max_networks:
raise driver.NoValidHost(_("All hosts have too many networks"))
if self.service_is_up(service):
- return service['host']
+ driver.cast_to_network_host(context, service['host'],
+ 'set_network_host', **_kwargs)
+ return None
raise driver.NoValidHost(_("Scheduler was unable to locate a host"
" for this request. Is the appropriate"
" service running?"))
diff --git a/nova/scheduler/vsa.py b/nova/scheduler/vsa.py
index 6962dd86b..da4475004 100644
--- a/nova/scheduler/vsa.py
+++ b/nova/scheduler/vsa.py
@@ -195,8 +195,6 @@ class VsaScheduler(simple.SimpleScheduler):
'display_description': vol['description'],
'volume_type_id': vol['volume_type_id'],
'metadata': dict(to_vsa_id=vsa_id),
- 'host': vol['host'],
- 'scheduled_at': now
}
size = vol['size']
@@ -205,12 +203,10 @@ class VsaScheduler(simple.SimpleScheduler):
LOG.debug(_("Provision volume %(name)s of size %(size)s GB on "\
"host %(host)s"), locals())
- volume_ref = db.volume_create(context, options)
- rpc.cast(context,
- db.queue_get_for(context, "volume", vol['host']),
- {"method": "create_volume",
- "args": {"volume_id": volume_ref['id'],
- "snapshot_id": None}})
+ volume_ref = db.volume_create(context.elevated(), options)
+ driver.cast_to_volume_host(context, vol['host'],
+ 'create_volume', volume_id=volume_ref['id'],
+ snapshot_id=None)
def _check_host_enforcement(self, context, availability_zone):
if (availability_zone
@@ -274,7 +270,6 @@ class VsaScheduler(simple.SimpleScheduler):
def schedule_create_volumes(self, context, request_spec,
availability_zone=None, *_args, **_kwargs):
"""Picks hosts for hosting multiple volumes."""
-
num_volumes = request_spec.get('num_volumes')
LOG.debug(_("Attempting to spawn %(num_volumes)d volume(s)") %
locals())
@@ -291,7 +286,8 @@ class VsaScheduler(simple.SimpleScheduler):
for vol in volume_params:
self._provision_volume(context, vol, vsa_id, availability_zone)
- except:
+ except Exception:
+ LOG.exception(_("Error creating volumes"))
if vsa_id:
db.vsa_update(context, vsa_id, dict(status=VsaState.FAILED))
@@ -310,10 +306,9 @@ class VsaScheduler(simple.SimpleScheduler):
host = self._check_host_enforcement(context,
volume_ref['availability_zone'])
if host:
- now = utils.utcnow()
- db.volume_update(context, volume_id, {'host': host,
- 'scheduled_at': now})
- return host
+ driver.cast_to_volume_host(context, host, 'create_volume',
+ volume_id=volume_id, **_kwargs)
+ return None
volume_type_id = volume_ref['volume_type_id']
if volume_type_id:
@@ -344,18 +339,16 @@ class VsaScheduler(simple.SimpleScheduler):
try:
(host, qos_cap) = self._select_hosts(request_spec, all_hosts=hosts)
- except:
+ except Exception:
+ LOG.exception(_("Error creating volume"))
if volume_ref['to_vsa_id']:
db.vsa_update(context, volume_ref['to_vsa_id'],
dict(status=VsaState.FAILED))
raise
if host:
- now = utils.utcnow()
- db.volume_update(context, volume_id, {'host': host,
- 'scheduled_at': now})
- self._consume_resource(qos_cap, volume_ref['size'], -1)
- return host
+ driver.cast_to_volume_host(context, host, 'create_volume',
+ volume_id=volume_id, **_kwargs)
def _consume_full_drive(self, qos_values, direction):
qos_values['FullDrive']['NumFreeDrives'] += direction
diff --git a/nova/scheduler/zone.py b/nova/scheduler/zone.py
index 44d5a166f..c369477f8 100644
--- a/nova/scheduler/zone.py
+++ b/nova/scheduler/zone.py
@@ -35,7 +35,7 @@ class ZoneScheduler(driver.Scheduler):
for topic and availability zone (if defined).
"""
- if zone is None:
+ if not zone:
return self.hosts_up(context, topic)
services = db.service_get_all_by_topic(context, topic)
@@ -44,16 +44,34 @@ class ZoneScheduler(driver.Scheduler):
if self.service_is_up(service)
and service.availability_zone == zone]
- def schedule(self, context, topic, *_args, **_kwargs):
+ def _schedule(self, context, topic, request_spec, **kwargs):
"""Picks a host that is up at random in selected
availability zone (if defined).
"""
- zone = _kwargs.get('availability_zone')
- hosts = self.hosts_up_with_zone(context, topic, zone)
+ zone = kwargs.get('availability_zone')
+ if not zone and request_spec:
+ zone = request_spec['instance_properties'].get(
+ 'availability_zone')
+ hosts = self.hosts_up_with_zone(context.elevated(), topic, zone)
if not hosts:
raise driver.NoValidHost(_("Scheduler was unable to locate a host"
" for this request. Is the appropriate"
" service running?"))
-
return hosts[int(random.random() * len(hosts))]
+
+ def schedule(self, context, topic, method, *_args, **kwargs):
+ host = self._schedule(context, topic, None, **kwargs)
+ driver.cast_to_host(context, topic, host, method, **kwargs)
+
+ def schedule_run_instance(self, context, request_spec, *_args, **kwargs):
+ """Builds and starts instances on selected hosts"""
+ num_instances = request_spec.get('num_instances', 1)
+ instances = []
+ for num in xrange(num_instances):
+ host = self._schedule(context, 'compute', request_spec, **kwargs)
+ instance = self.create_instance_db_entry(context, request_spec)
+ driver.cast_to_compute_host(context, host,
+ 'run_instance', instance_id=instance['id'], **kwargs)
+ instances.append(driver.encode_instance(instance))
+ return instances
diff --git a/nova/tests/api/openstack/contrib/test_createserverext.py b/nova/tests/api/openstack/contrib/test_createserverext.py
index d2fac26c7..9921b54a3 100644
--- a/nova/tests/api/openstack/contrib/test_createserverext.py
+++ b/nova/tests/api/openstack/contrib/test_createserverext.py
@@ -25,6 +25,7 @@ import webob
from nova import db
from nova import exception
from nova import flags
+from nova import rpc
from nova import test
import nova.api.openstack
from nova.tests.api.openstack import fakes
@@ -118,13 +119,15 @@ class CreateserverextTest(test.TestCase):
if 'user_data' in kwargs:
self.user_data = kwargs['user_data']
- return [{'id': '1234', 'display_name': 'fakeinstance',
+ resv_id = None
+
+ return ([{'id': '1234', 'display_name': 'fakeinstance',
'uuid': FAKE_UUID,
'user_id': 'fake',
'project_id': 'fake',
'created_at': "",
'updated_at': "",
- 'progress': 0}]
+ 'progress': 0}], resv_id)
def set_admin_password(self, *args, **kwargs):
pass
@@ -133,10 +136,9 @@ class CreateserverextTest(test.TestCase):
self.stubs.Set(nova.compute, 'API',
self._make_stub_method(compute_api))
self.stubs.Set(
- nova.api.openstack.create_instance_helper.CreateInstanceHelper,
+ nova.api.openstack.servers.Controller,
'_get_kernel_ramdisk_from_image',
self._make_stub_method((1, 1)))
-
return compute_api
def _setup_mock_network_api(self):
@@ -399,7 +401,8 @@ class CreateserverextTest(test.TestCase):
self._setup_mock_network_api()
body_dict = self._create_security_group_request_dict(security_groups)
request = self._get_create_request_json(body_dict)
- response = request.get_response(fakes.wsgi_app())
+ compute_api, response = \
+ self._run_create_instance_with_mock_compute_api(request)
self.assertEquals(response.status_int, 202)
def test_get_server_by_id_verify_security_groups_json(self):
diff --git a/nova/tests/api/openstack/contrib/test_volumes.py b/nova/tests/api/openstack/contrib/test_volumes.py
index f61e25d12..9a24b9a54 100644
--- a/nova/tests/api/openstack/contrib/test_volumes.py
+++ b/nova/tests/api/openstack/contrib/test_volumes.py
@@ -29,8 +29,12 @@ FLAGS = flags.FLAGS
def fake_compute_api_create(cls, context, instance_type, image_href, **kwargs):
+ global _block_device_mapping_seen
+ _block_device_mapping_seen = kwargs.get('block_device_mapping')
+
inst_type = instance_types.get_instance_type_by_flavor_id(2)
- return [{'id': 1,
+ resv_id = None
+ return ([{'id': 1,
'display_name': 'test_server',
'uuid': fake_gen_uuid(),
'instance_type': dict(inst_type),
@@ -42,7 +46,7 @@ def fake_compute_api_create(cls, context, instance_type, image_href, **kwargs):
'created_at': datetime.datetime(2010, 10, 10, 12, 0, 0),
'updated_at': datetime.datetime(2010, 11, 11, 11, 0, 0),
'progress': 0
- }]
+ }], resv_id)
def fake_get_instance_nw_info(cls, context, instance):
@@ -73,6 +77,8 @@ class BootFromVolumeTest(test.TestCase):
delete_on_termination=False,
)]
))
+ global _block_device_mapping_seen
+ _block_device_mapping_seen = None
req = webob.Request.blank('/v1.1/fake/os-volumes_boot')
req.method = 'POST'
req.body = json.dumps(body)
@@ -85,3 +91,7 @@ class BootFromVolumeTest(test.TestCase):
self.assertEqual(u'test_server', server['name'])
self.assertEqual(3, int(server['image']['id']))
self.assertEqual(FLAGS.password_length, len(server['adminPass']))
+ self.assertEqual(len(_block_device_mapping_seen), 1)
+ self.assertEqual(_block_device_mapping_seen[0]['volume_id'], 1)
+ self.assertEqual(_block_device_mapping_seen[0]['device_name'],
+ '/dev/vda')
diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py
index a5c6fe65a..4f66f5405 100644
--- a/nova/tests/api/openstack/test_extensions.py
+++ b/nova/tests/api/openstack/test_extensions.py
@@ -87,6 +87,7 @@ class ExtensionControllerTest(test.TestCase):
self.ext_list = [
"Createserverext",
"DeferredDelete",
+ "DiskConfig",
"FlavorExtraSpecs",
"FlavorExtraData",
"Floating_ips",
@@ -102,7 +103,7 @@ class ExtensionControllerTest(test.TestCase):
"VirtualInterfaces",
"Volumes",
"VolumeTypes",
- "DiskConfig",
+ "Zones",
]
self.ext_list.sort()
diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py
index 3811fcf0f..04b603237 100644
--- a/nova/tests/api/openstack/test_server_actions.py
+++ b/nova/tests/api/openstack/test_server_actions.py
@@ -9,7 +9,7 @@ from nova import context
from nova import utils
from nova import exception
from nova import flags
-from nova.api.openstack import create_instance_helper
+from nova.api.openstack import servers
from nova.compute import vm_states
from nova.compute import instance_types
import nova.db.api
@@ -971,7 +971,7 @@ class ServerActionsTestV11(test.TestCase):
class TestServerActionXMLDeserializerV11(test.TestCase):
def setUp(self):
- self.deserializer = create_instance_helper.ServerXMLDeserializerV11()
+ self.deserializer = servers.ServerXMLDeserializerV11()
def tearDown(self):
pass
diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py
index 29be7a812..fa67db688 100644
--- a/nova/tests/api/openstack/test_servers.py
+++ b/nova/tests/api/openstack/test_servers.py
@@ -33,7 +33,6 @@ from nova import flags
from nova import test
from nova import utils
import nova.api.openstack
-from nova.api.openstack import create_instance_helper
from nova.api.openstack import servers
from nova.api.openstack import xmlutil
import nova.compute.api
@@ -1562,10 +1561,15 @@ class ServersTest(test.TestCase):
def _setup_for_create_instance(self):
"""Shared implementation for tests below that create instance"""
+
+ self.instance_cache_num = 0
+ self.instance_cache = {}
+
def instance_create(context, inst):
inst_type = instance_types.get_instance_type_by_flavor_id(3)
image_ref = 'http://localhost/images/2'
- return {'id': 1,
+ self.instance_cache_num += 1
+ instance = {'id': self.instance_cache_num,
'display_name': 'server_test',
'uuid': FAKE_UUID,
'instance_type': dict(inst_type),
@@ -1574,11 +1578,32 @@ class ServersTest(test.TestCase):
'image_ref': image_ref,
'user_id': 'fake',
'project_id': 'fake',
+ 'reservation_id': inst['reservation_id'],
"created_at": datetime.datetime(2010, 10, 10, 12, 0, 0),
"updated_at": datetime.datetime(2010, 11, 11, 11, 0, 0),
"config_drive": self.config_drive,
"progress": 0
}
+ self.instance_cache[instance['id']] = instance
+ return instance
+
+ def instance_get(context, instance_id):
+ """Stub for compute/api create() pulling in instance after
+ scheduling
+ """
+ return self.instance_cache[instance_id]
+
+ def rpc_call_wrapper(context, topic, msg):
+ """Stub out the scheduler creating the instance entry"""
+ if topic == FLAGS.scheduler_topic and \
+ msg['method'] == 'run_instance':
+ request_spec = msg['args']['request_spec']
+ num_instances = request_spec.get('num_instances', 1)
+ instances = []
+ for x in xrange(num_instances):
+ instances.append(instance_create(context,
+ request_spec['instance_properties']))
+ return instances
def server_update(context, id, params):
return instance_create(context, id)
@@ -1601,18 +1626,20 @@ class ServersTest(test.TestCase):
self.stubs.Set(nova.db.api, 'project_get_networks',
project_get_networks)
self.stubs.Set(nova.db.api, 'instance_create', instance_create)
+ self.stubs.Set(nova.db.api, 'instance_get', instance_get)
self.stubs.Set(nova.rpc, 'cast', fake_method)
- self.stubs.Set(nova.rpc, 'call', fake_method)
+ self.stubs.Set(nova.rpc, 'call', rpc_call_wrapper)
self.stubs.Set(nova.db.api, 'instance_update', server_update)
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.create_instance_helper.CreateInstanceHelper,
- "_get_kernel_ramdisk_from_image", kernel_ramdisk_mapping)
+ servers.Controller,
+ "_get_kernel_ramdisk_from_image",
+ kernel_ramdisk_mapping)
self.stubs.Set(nova.compute.api.API, "_find_host", find_host)
- def _test_create_instance_helper(self):
+ def _test_create_instance(self):
self._setup_for_create_instance()
body = dict(server=dict(
@@ -1636,7 +1663,7 @@ class ServersTest(test.TestCase):
self.assertEqual(FAKE_UUID, server['uuid'])
def test_create_instance(self):
- self._test_create_instance_helper()
+ self._test_create_instance()
def test_create_instance_has_uuid(self):
"""Tests at the db-layer instead of API layer since that's where the
@@ -1648,51 +1675,134 @@ class ServersTest(test.TestCase):
expected = FAKE_UUID
self.assertEqual(instance['uuid'], expected)
- def test_create_instance_via_zones(self):
- """Server generated ReservationID"""
+ def test_create_multiple_instances(self):
+ """Test creating multiple instances but not asking for
+ reservation_id
+ """
self._setup_for_create_instance()
- self.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')
+ image_href = 'http://localhost/v1.1/123/images/2'
+ flavor_ref = 'http://localhost/123/flavors/3'
+ body = {
+ 'server': {
+ 'min_count': 2,
+ 'name': 'server_test',
+ 'imageRef': image_href,
+ 'flavorRef': flavor_ref,
+ 'metadata': {'hello': 'world',
+ 'open': 'stack'},
+ 'personality': []
+ }
+ }
+
+ req = webob.Request.blank('/v1.1/123/servers')
req.method = 'POST'
req.body = json.dumps(body)
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 202)
+ body = json.loads(res.body)
+ self.assertIn('server', body)
- reservation_id = json.loads(res.body)['reservation_id']
- self.assertEqual(res.status_int, 200)
+ def test_create_multiple_instances_resv_id_return(self):
+ """Test creating multiple instances with asking for
+ reservation_id
+ """
+ self._setup_for_create_instance()
+
+ image_href = 'http://localhost/v1.1/123/images/2'
+ flavor_ref = 'http://localhost/123/flavors/3'
+ body = {
+ 'server': {
+ 'min_count': 2,
+ 'name': 'server_test',
+ 'imageRef': image_href,
+ 'flavorRef': flavor_ref,
+ 'metadata': {'hello': 'world',
+ 'open': 'stack'},
+ 'personality': [],
+ 'return_reservation_id': True
+ }
+ }
+
+ req = webob.Request.blank('/v1.1/123/servers')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 202)
+ body = json.loads(res.body)
+ reservation_id = body.get('reservation_id')
self.assertNotEqual(reservation_id, "")
self.assertNotEqual(reservation_id, None)
self.assertTrue(len(reservation_id) > 1)
- def test_create_instance_via_zones_with_resid(self):
- """User supplied ReservationID"""
+ def test_create_instance_with_user_supplied_reservation_id(self):
+ """Non-admin supplied reservation_id should be ignored."""
self._setup_for_create_instance()
- self.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')
+ image_href = 'http://localhost/v1.1/123/images/2'
+ flavor_ref = 'http://localhost/123/flavors/3'
+ body = {
+ 'server': {
+ 'name': 'server_test',
+ 'imageRef': image_href,
+ 'flavorRef': flavor_ref,
+ 'metadata': {'hello': 'world',
+ 'open': 'stack'},
+ 'personality': [],
+ 'reservation_id': 'myresid',
+ 'return_reservation_id': True
+ }
+ }
+
+ req = webob.Request.blank('/v1.1/123/servers')
req.method = 'POST'
req.body = json.dumps(body)
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app())
+ self.assertEqual(res.status_int, 202)
+ res_body = json.loads(res.body)
+ self.assertIn('reservation_id', res_body)
+ self.assertNotEqual(res_body['reservation_id'], 'myresid')
+ def test_create_instance_with_admin_supplied_reservation_id(self):
+ """Admin supplied reservation_id should be honored."""
+ self._setup_for_create_instance()
+
+ image_href = 'http://localhost/v1.1/123/images/2'
+ flavor_ref = 'http://localhost/123/flavors/3'
+ body = {
+ 'server': {
+ 'name': 'server_test',
+ 'imageRef': image_href,
+ 'flavorRef': flavor_ref,
+ 'metadata': {'hello': 'world',
+ 'open': 'stack'},
+ 'personality': [],
+ 'reservation_id': 'myresid',
+ 'return_reservation_id': True
+ }
+ }
+
+ req = webob.Request.blank('/v1.1/123/servers')
+ req.method = 'POST'
+ req.body = json.dumps(body)
+ req.headers["content-type"] = "application/json"
+
+ context = nova.context.RequestContext('testuser', 'testproject',
+ is_admin=True)
+ res = req.get_response(fakes.wsgi_app(fake_auth_context=context))
+ self.assertEqual(res.status_int, 202)
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()
+ self._test_create_instance()
def test_create_instance_no_name(self):
self._setup_for_create_instance()
@@ -2782,7 +2892,7 @@ class TestServerStatus(test.TestCase):
class TestServerCreateRequestXMLDeserializerV10(unittest.TestCase):
def setUp(self):
- self.deserializer = create_instance_helper.ServerXMLDeserializer()
+ self.deserializer = servers.ServerXMLDeserializer()
def test_minimal_request(self):
serial_request = """
@@ -3068,7 +3178,7 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase):
def setUp(self):
super(TestServerCreateRequestXMLDeserializerV11, self).setUp()
- self.deserializer = create_instance_helper.ServerXMLDeserializerV11()
+ self.deserializer = servers.ServerXMLDeserializerV11()
def test_minimal_request(self):
serial_request = """
@@ -3543,10 +3653,12 @@ class TestServerInstanceCreation(test.TestCase):
else:
self.injected_files = None
- return [{'id': '1234', 'display_name': 'fakeinstance',
+ resv_id = None
+
+ return ([{'id': '1234', 'display_name': 'fakeinstance',
'user_id': 'fake',
'project_id': 'fake',
- 'uuid': FAKE_UUID}]
+ 'uuid': FAKE_UUID}], resv_id)
def set_admin_password(self, *args, **kwargs):
pass
@@ -3559,8 +3671,9 @@ class TestServerInstanceCreation(test.TestCase):
compute_api = MockComputeAPI()
self.stubs.Set(nova.compute, 'API', make_stub_method(compute_api))
self.stubs.Set(
- nova.api.openstack.create_instance_helper.CreateInstanceHelper,
- '_get_kernel_ramdisk_from_image', make_stub_method((1, 1)))
+ servers.Controller,
+ '_get_kernel_ramdisk_from_image',
+ make_stub_method((1, 1)))
return compute_api
def _create_personality_request_dict(self, personality_files):
@@ -3821,8 +3934,8 @@ class TestGetKernelRamdiskFromImage(test.TestCase):
@staticmethod
def _get_k_r(image_meta):
"""Rebinding function to a shorter name for convenience"""
- kernel_id, ramdisk_id = create_instance_helper.CreateInstanceHelper. \
- _do_get_kernel_ramdisk_from_image(image_meta)
+ kernel_id, ramdisk_id = servers.Controller.\
+ _do_get_kernel_ramdisk_from_image(image_meta)
return kernel_id, ramdisk_id
diff --git a/nova/tests/integrated/api/client.py b/nova/tests/integrated/api/client.py
index 67c35fe6b..c942b0108 100644
--- a/nova/tests/integrated/api/client.py
+++ b/nova/tests/integrated/api/client.py
@@ -16,6 +16,7 @@
import json
import httplib
+import urllib
import urlparse
from nova import log as logging
@@ -100,7 +101,7 @@ class TestOpenStackClient(object):
relative_url = parsed_url.path
if parsed_url.query:
- relative_url = relative_url + parsed_url.query
+ relative_url = relative_url + "?" + parsed_url.query
LOG.info(_("Doing %(method)s on %(relative_url)s") % locals())
if body:
LOG.info(_("Body: %s") % body)
@@ -205,12 +206,24 @@ class TestOpenStackClient(object):
def get_server(self, server_id):
return self.api_get('/servers/%s' % server_id)['server']
- def get_servers(self, detail=True):
+ def get_servers(self, detail=True, search_opts=None):
rel_url = '/servers/detail' if detail else '/servers'
+
+ if search_opts is not None:
+ qparams = {}
+ for opt, val in search_opts.iteritems():
+ qparams[opt] = val
+ if qparams:
+ query_string = "?%s" % urllib.urlencode(qparams)
+ rel_url += query_string
return self.api_get(rel_url)['servers']
def post_server(self, server):
- return self.api_post('/servers', server)['server']
+ response = self.api_post('/servers', server)
+ if 'reservation_id' in response:
+ return response
+ else:
+ return response['server']
def put_server(self, server_id, server):
return self.api_put('/servers/%s' % server_id, server)
diff --git a/nova/tests/integrated/test_servers.py b/nova/tests/integrated/test_servers.py
index ca3eb7340..4bfad93b1 100644
--- a/nova/tests/integrated/test_servers.py
+++ b/nova/tests/integrated/test_servers.py
@@ -438,6 +438,42 @@ class ServersTest(integrated_helpers._IntegratedTestBase):
# Cleanup
self._delete_server(server_id)
+ def test_create_multiple_servers(self):
+ """Creates multiple servers and checks for reservation_id"""
+
+ # Create 2 servers, setting 'return_reservation_id, which should
+ # return a reservation_id
+ server = self._build_minimal_create_server_request()
+ server['min_count'] = 2
+ server['return_reservation_id'] = True
+ post = {'server': server}
+ response = self.api.post_server(post)
+ self.assertIn('reservation_id', response)
+ reservation_id = response['reservation_id']
+ self.assertNotIn(reservation_id, ['', None])
+
+ # Create 1 more server, which should not return a reservation_id
+ server = self._build_minimal_create_server_request()
+ post = {'server': server}
+ created_server = self.api.post_server(post)
+ self.assertTrue(created_server['id'])
+ created_server_id = created_server['id']
+
+ # lookup servers created by the first request.
+ servers = self.api.get_servers(detail=True,
+ search_opts={'reservation_id': reservation_id})
+ server_map = dict((server['id'], server) for server in servers)
+ found_server = server_map.get(created_server_id)
+ # The server from the 2nd request should not be there.
+ self.assertEqual(found_server, None)
+ # Should have found 2 servers.
+ self.assertEqual(len(server_map), 2)
+
+ # Cleanup
+ self._delete_server(created_server_id)
+ for server_id in server_map.iterkeys():
+ self._delete_server(server_id)
+
if __name__ == "__main__":
unittest.main()
diff --git a/nova/tests/scheduler/test_abstract_scheduler.py b/nova/tests/scheduler/test_abstract_scheduler.py
index 5549ea453..da25f1544 100644
--- a/nova/tests/scheduler/test_abstract_scheduler.py
+++ b/nova/tests/scheduler/test_abstract_scheduler.py
@@ -20,6 +20,7 @@ import json
import nova.db
+from nova import context
from nova import exception
from nova import rpc
from nova import test
@@ -102,7 +103,7 @@ def fake_empty_call_zone_method(context, method, specs, zones):
was_called = False
-def fake_provision_resource(context, item, instance_id, request_spec, kwargs):
+def fake_provision_resource(context, item, request_spec, kwargs):
global was_called
was_called = True
@@ -118,8 +119,7 @@ def fake_provision_resource_locally(context, build_plan, request_spec, kwargs):
was_called = True
-def fake_provision_resource_from_blob(context, item, instance_id,
- request_spec, kwargs):
+def fake_provision_resource_from_blob(context, item, request_spec, kwargs):
global was_called
was_called = True
@@ -185,7 +185,7 @@ class AbstractSchedulerTestCase(test.TestCase):
zm = FakeZoneManager()
sched.set_zone_manager(zm)
- fake_context = {}
+ fake_context = context.RequestContext('user', 'project')
build_plan = sched.select(fake_context,
{'instance_type': {'memory_mb': 512},
'num_instances': 4})
@@ -229,9 +229,10 @@ class AbstractSchedulerTestCase(test.TestCase):
zm = FakeEmptyZoneManager()
sched.set_zone_manager(zm)
- fake_context = {}
+ fake_context = context.RequestContext('user', 'project')
+ request_spec = {}
self.assertRaises(driver.NoValidHost, sched.schedule_run_instance,
- fake_context, 1,
+ fake_context, request_spec,
dict(host_filter=None, instance_type={}))
def test_schedule_do_not_schedule_with_hint(self):
@@ -250,8 +251,8 @@ class AbstractSchedulerTestCase(test.TestCase):
'blob': "Non-None blob data",
}
- result = sched.schedule_run_instance(None, 1, request_spec)
- self.assertEquals(None, result)
+ instances = sched.schedule_run_instance(None, request_spec)
+ self.assertTrue(instances)
self.assertTrue(was_called)
def test_provision_resource_local(self):
@@ -263,7 +264,7 @@ class AbstractSchedulerTestCase(test.TestCase):
fake_provision_resource_locally)
request_spec = {'hostname': "foo"}
- sched._provision_resource(None, request_spec, 1, request_spec, {})
+ sched._provision_resource(None, request_spec, request_spec, {})
self.assertTrue(was_called)
def test_provision_resource_remote(self):
@@ -275,7 +276,7 @@ class AbstractSchedulerTestCase(test.TestCase):
fake_provision_resource_from_blob)
request_spec = {}
- sched._provision_resource(None, request_spec, 1, request_spec, {})
+ sched._provision_resource(None, request_spec, request_spec, {})
self.assertTrue(was_called)
def test_provision_resource_from_blob_empty(self):
@@ -285,7 +286,7 @@ class AbstractSchedulerTestCase(test.TestCase):
request_spec = {}
self.assertRaises(abstract_scheduler.InvalidBlob,
sched._provision_resource_from_blob,
- None, {}, 1, {}, {})
+ None, {}, {}, {})
def test_provision_resource_from_blob_with_local_blob(self):
"""
@@ -303,20 +304,21 @@ class AbstractSchedulerTestCase(test.TestCase):
# return fake instances
return {'id': 1, 'uuid': 'f874093c-7b17-49c0-89c3-22a5348497f9'}
- def fake_rpc_cast(*args, **kwargs):
+ def fake_cast_to_compute_host(*args, **kwargs):
pass
self.stubs.Set(sched, '_decrypt_blob',
fake_decrypt_blob_returns_local_info)
+ self.stubs.Set(driver, 'cast_to_compute_host',
+ fake_cast_to_compute_host)
self.stubs.Set(compute_api.API,
'create_db_entry_for_new_instance',
fake_create_db_entry_for_new_instance)
- self.stubs.Set(rpc, 'cast', fake_rpc_cast)
build_plan_item = {'blob': "Non-None blob data"}
request_spec = {'image': {}, 'instance_properties': {}}
- sched._provision_resource_from_blob(None, build_plan_item, 1,
+ sched._provision_resource_from_blob(None, build_plan_item,
request_spec, {})
self.assertTrue(was_called)
@@ -335,7 +337,7 @@ class AbstractSchedulerTestCase(test.TestCase):
request_spec = {'blob': "Non-None blob data"}
- sched._provision_resource_from_blob(None, request_spec, 1,
+ sched._provision_resource_from_blob(None, request_spec,
request_spec, {})
self.assertTrue(was_called)
@@ -352,7 +354,7 @@ class AbstractSchedulerTestCase(test.TestCase):
request_spec = {'child_blob': True, 'child_zone': True}
- sched._provision_resource_from_blob(None, request_spec, 1,
+ sched._provision_resource_from_blob(None, request_spec,
request_spec, {})
self.assertTrue(was_called)
@@ -386,7 +388,7 @@ class AbstractSchedulerTestCase(test.TestCase):
zm.service_states = {}
sched.set_zone_manager(zm)
- fake_context = {}
+ fake_context = context.RequestContext('user', 'project')
build_plan = sched.select(fake_context,
{'instance_type': {'memory_mb': 512},
'num_instances': 4})
@@ -394,6 +396,45 @@ class AbstractSchedulerTestCase(test.TestCase):
# 0 from local zones, 12 from remotes
self.assertEqual(12, len(build_plan))
+ def test_run_instance_non_admin(self):
+ """Test creating an instance locally using run_instance, passing
+ a non-admin context. DB actions should work."""
+ sched = FakeAbstractScheduler()
+
+ def fake_cast_to_compute_host(*args, **kwargs):
+ pass
+
+ def fake_zone_get_all_zero(context):
+ # make sure this is called with admin context, even though
+ # we're using user context below
+ self.assertTrue(context.is_admin)
+ return []
+
+ self.stubs.Set(driver, 'cast_to_compute_host',
+ fake_cast_to_compute_host)
+ self.stubs.Set(sched, '_call_zone_method', fake_call_zone_method)
+ self.stubs.Set(nova.db, 'zone_get_all', fake_zone_get_all_zero)
+
+ zm = FakeZoneManager()
+ sched.set_zone_manager(zm)
+
+ fake_context = context.RequestContext('user', 'project')
+
+ request_spec = {
+ 'image': {'properties': {}},
+ 'security_group': [],
+ 'instance_properties': {
+ 'project_id': fake_context.project_id,
+ 'user_id': fake_context.user_id},
+ 'instance_type': {'memory_mb': 256},
+ 'filter_driver': 'nova.scheduler.host_filter.AllHostsFilter'
+ }
+
+ instances = sched.schedule_run_instance(fake_context, request_spec)
+ self.assertEqual(len(instances), 1)
+ self.assertFalse(instances[0].get('_is_precooked', False))
+ nova.db.instance_destroy(fake_context, instances[0]['id'])
+
class BaseSchedulerTestCase(test.TestCase):
"""Test case for Base Scheduler."""
diff --git a/nova/tests/scheduler/test_least_cost_scheduler.py b/nova/tests/scheduler/test_least_cost_scheduler.py
index af58de527..b8847a2bf 100644
--- a/nova/tests/scheduler/test_least_cost_scheduler.py
+++ b/nova/tests/scheduler/test_least_cost_scheduler.py
@@ -134,7 +134,7 @@ class LeastCostSchedulerTestCase(test.TestCase):
expected = []
for idx, (hostname, services) in enumerate(hosts):
- caps = copy.deepcopy(services["compute"])
+ caps = copy.deepcopy(services)
# Costs are normalized so over 10 hosts, each host with increasing
# free ram will cost 1/N more. Since the lowest cost host has some
# free ram, we add in the 1/N for the base_cost
diff --git a/nova/tests/scheduler/test_scheduler.py b/nova/tests/scheduler/test_scheduler.py
index 3a10e9287..77db3520b 100644
--- a/nova/tests/scheduler/test_scheduler.py
+++ b/nova/tests/scheduler/test_scheduler.py
@@ -35,10 +35,13 @@ from nova import service
from nova import test
from nova import rpc
from nova import utils
+from nova.db.sqlalchemy import models
from nova.scheduler import api
from nova.scheduler import driver
from nova.scheduler import manager
from nova.scheduler import multi
+from nova.scheduler.simple import SimpleScheduler
+from nova.scheduler.zone import ZoneScheduler
from nova.compute import power_state
from nova.compute import vm_states
@@ -53,17 +56,86 @@ FAKE_UUID_NOT_FOUND = 'ffffffff-ffff-ffff-ffff-ffffffffffff'
FAKE_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
-class FakeContext(object):
- auth_token = None
+def _create_instance_dict(**kwargs):
+ """Create a dictionary for a test instance"""
+ inst = {}
+ # NOTE(jk0): If an integer is passed as the image_ref, the image
+ # service will use the default image service (in this case, the fake).
+ inst['image_ref'] = '1'
+ inst['reservation_id'] = 'r-fakeres'
+ inst['user_id'] = kwargs.get('user_id', 'admin')
+ inst['project_id'] = kwargs.get('project_id', 'fake')
+ inst['instance_type_id'] = '1'
+ if 'host' in kwargs:
+ inst['host'] = kwargs.get('host')
+ inst['vcpus'] = kwargs.get('vcpus', 1)
+ inst['memory_mb'] = kwargs.get('memory_mb', 20)
+ inst['local_gb'] = kwargs.get('local_gb', 30)
+ inst['vm_state'] = kwargs.get('vm_state', vm_states.ACTIVE)
+ inst['power_state'] = kwargs.get('power_state', power_state.RUNNING)
+ inst['task_state'] = kwargs.get('task_state', None)
+ inst['availability_zone'] = kwargs.get('availability_zone', None)
+ inst['ami_launch_index'] = 0
+ inst['launched_on'] = kwargs.get('launched_on', 'dummy')
+ return inst
+
+
+def _create_volume():
+ """Create a test volume"""
+ vol = {}
+ vol['size'] = 1
+ vol['availability_zone'] = 'test'
+ ctxt = context.get_admin_context()
+ return db.volume_create(ctxt, vol)['id']
+
+
+def _create_instance(**kwargs):
+ """Create a test instance"""
+ ctxt = context.get_admin_context()
+ return db.instance_create(ctxt, _create_instance_dict(**kwargs))
+
+
+def _create_instance_from_spec(spec):
+ return _create_instance(**spec['instance_properties'])
+
+
+def _create_request_spec(**kwargs):
+ return dict(instance_properties=_create_instance_dict(**kwargs))
+
+
+def _fake_cast_to_compute_host(context, host, method, **kwargs):
+ global _picked_host
+ _picked_host = host
+
+
+def _fake_cast_to_volume_host(context, host, method, **kwargs):
+ global _picked_host
+ _picked_host = host
+
+
+def _fake_create_instance_db_entry(simple_self, context, request_spec):
+ instance = _create_instance_from_spec(request_spec)
+ global instance_ids
+ instance_ids.append(instance['id'])
+ return instance
+
+
+class FakeContext(context.RequestContext):
+ def __init__(self, *args, **kwargs):
+ super(FakeContext, self).__init__('user', 'project', **kwargs)
class TestDriver(driver.Scheduler):
"""Scheduler Driver for Tests"""
- def schedule(context, topic, *args, **kwargs):
- return 'fallback_host'
+ def schedule(self, context, topic, method, *args, **kwargs):
+ host = 'fallback_host'
+ driver.cast_to_host(context, topic, host, method, **kwargs)
- def schedule_named_method(context, topic, num):
- return 'named_host'
+ def schedule_named_method(self, context, num=None):
+ topic = 'topic'
+ host = 'named_host'
+ method = 'named_method'
+ driver.cast_to_host(context, topic, host, method, num=num)
class SchedulerTestCase(test.TestCase):
@@ -89,31 +161,16 @@ class SchedulerTestCase(test.TestCase):
return db.service_get(ctxt, s_ref['id'])
- def _create_instance(self, **kwargs):
- """Create a test instance"""
- ctxt = context.get_admin_context()
- inst = {}
- inst['user_id'] = 'admin'
- inst['project_id'] = kwargs.get('project_id', 'fake')
- inst['host'] = kwargs.get('host', 'dummy')
- inst['vcpus'] = kwargs.get('vcpus', 1)
- inst['memory_mb'] = kwargs.get('memory_mb', 10)
- inst['local_gb'] = kwargs.get('local_gb', 20)
- inst['vm_state'] = kwargs.get('vm_state', vm_states.ACTIVE)
- inst['power_state'] = kwargs.get('power_state', power_state.RUNNING)
- inst['task_state'] = kwargs.get('task_state', None)
- return db.instance_create(ctxt, inst)
-
def test_fallback(self):
scheduler = manager.SchedulerManager()
self.mox.StubOutWithMock(rpc, 'cast', use_mock_anything=True)
ctxt = context.get_admin_context()
rpc.cast(ctxt,
- 'topic.fallback_host',
+ 'fake_topic.fallback_host',
{'method': 'noexist',
'args': {'num': 7}})
self.mox.ReplayAll()
- scheduler.noexist(ctxt, 'topic', num=7)
+ scheduler.noexist(ctxt, 'fake_topic', num=7)
def test_named_method(self):
scheduler = manager.SchedulerManager()
@@ -173,8 +230,8 @@ class SchedulerTestCase(test.TestCase):
scheduler = manager.SchedulerManager()
ctxt = context.get_admin_context()
s_ref = self._create_compute_service()
- i_ref1 = self._create_instance(project_id='p-01', host=s_ref['host'])
- i_ref2 = self._create_instance(project_id='p-02', vcpus=3,
+ i_ref1 = _create_instance(project_id='p-01', host=s_ref['host'])
+ i_ref2 = _create_instance(project_id='p-02', vcpus=3,
host=s_ref['host'])
result = scheduler.show_host_resources(ctxt, s_ref['host'])
@@ -197,7 +254,10 @@ class ZoneSchedulerTestCase(test.TestCase):
"""Test case for zone scheduler"""
def setUp(self):
super(ZoneSchedulerTestCase, self).setUp()
- self.flags(scheduler_driver='nova.scheduler.zone.ZoneScheduler')
+ self.flags(
+ scheduler_driver='nova.scheduler.multi.MultiScheduler',
+ compute_scheduler_driver='nova.scheduler.zone.ZoneScheduler',
+ volume_scheduler_driver='nova.scheduler.zone.ZoneScheduler')
def _create_service_model(self, **kwargs):
service = db.sqlalchemy.models.Service()
@@ -214,7 +274,7 @@ class ZoneSchedulerTestCase(test.TestCase):
def test_with_two_zones(self):
scheduler = manager.SchedulerManager()
- ctxt = context.get_admin_context()
+ ctxt = context.RequestContext('user', 'project')
service_list = [self._create_service_model(id=1,
host='host1',
zone='zone1'),
@@ -230,66 +290,53 @@ class ZoneSchedulerTestCase(test.TestCase):
self._create_service_model(id=5,
host='host5',
zone='zone2')]
+
+ request_spec = _create_request_spec(availability_zone='zone1')
+
+ fake_instance = _create_instance_dict(
+ **request_spec['instance_properties'])
+ fake_instance['id'] = 100
+ fake_instance['uuid'] = FAKE_UUID
+
self.mox.StubOutWithMock(db, 'service_get_all_by_topic')
+ self.mox.StubOutWithMock(db, 'instance_update')
+ # Assumes we're testing with MultiScheduler
+ compute_sched_driver = scheduler.driver.drivers['compute']
+ self.mox.StubOutWithMock(compute_sched_driver,
+ 'create_instance_db_entry')
+ self.mox.StubOutWithMock(rpc, 'cast', use_mock_anything=True)
+
arg = IgnoreArg()
db.service_get_all_by_topic(arg, arg).AndReturn(service_list)
- self.mox.StubOutWithMock(rpc, 'cast', use_mock_anything=True)
- rpc.cast(ctxt,
+ compute_sched_driver.create_instance_db_entry(arg,
+ request_spec).AndReturn(fake_instance)
+ db.instance_update(arg, 100, {'host': 'host1', 'scheduled_at': arg})
+ rpc.cast(arg,
'compute.host1',
{'method': 'run_instance',
- 'args': {'instance_id': 'i-ffffffff',
- 'availability_zone': 'zone1'}})
+ 'args': {'instance_id': 100}})
self.mox.ReplayAll()
scheduler.run_instance(ctxt,
'compute',
- instance_id='i-ffffffff',
- availability_zone='zone1')
+ request_spec=request_spec)
class SimpleDriverTestCase(test.TestCase):
"""Test case for simple driver"""
def setUp(self):
super(SimpleDriverTestCase, self).setUp()
+ simple_scheduler = 'nova.scheduler.simple.SimpleScheduler'
self.flags(connection_type='fake',
- stub_network=True,
- max_cores=4,
- max_gigabytes=4,
- network_manager='nova.network.manager.FlatManager',
- volume_driver='nova.volume.driver.FakeISCSIDriver',
- scheduler_driver='nova.scheduler.simple.SimpleScheduler')
+ stub_network=True,
+ max_cores=4,
+ max_gigabytes=4,
+ network_manager='nova.network.manager.FlatManager',
+ volume_driver='nova.volume.driver.FakeISCSIDriver',
+ scheduler_driver='nova.scheduler.multi.MultiScheduler',
+ compute_scheduler_driver=simple_scheduler,
+ volume_scheduler_driver=simple_scheduler)
self.scheduler = manager.SchedulerManager()
self.context = context.get_admin_context()
- self.user_id = 'fake'
- self.project_id = 'fake'
-
- def _create_instance(self, **kwargs):
- """Create a test instance"""
- inst = {}
- # NOTE(jk0): If an integer is passed as the image_ref, the image
- # service will use the default image service (in this case, the fake).
- inst['image_ref'] = '1'
- inst['reservation_id'] = 'r-fakeres'
- inst['user_id'] = self.user_id
- inst['project_id'] = self.project_id
- inst['instance_type_id'] = '1'
- inst['vcpus'] = kwargs.get('vcpus', 1)
- inst['ami_launch_index'] = 0
- inst['availability_zone'] = kwargs.get('availability_zone', None)
- inst['host'] = kwargs.get('host', 'dummy')
- inst['memory_mb'] = kwargs.get('memory_mb', 20)
- inst['local_gb'] = kwargs.get('local_gb', 30)
- inst['launched_on'] = kwargs.get('launghed_on', 'dummy')
- inst['vm_state'] = kwargs.get('vm_state', vm_states.ACTIVE)
- inst['task_state'] = kwargs.get('task_state', None)
- inst['power_state'] = kwargs.get('power_state', power_state.RUNNING)
- return db.instance_create(self.context, inst)['id']
-
- def _create_volume(self):
- """Create a test volume"""
- vol = {}
- vol['size'] = 1
- vol['availability_zone'] = 'test'
- return db.volume_create(self.context, vol)['id']
def _create_compute_service(self, **kwargs):
"""Create a compute service."""
@@ -369,14 +416,30 @@ class SimpleDriverTestCase(test.TestCase):
'compute',
FLAGS.compute_manager)
compute2.start()
- instance_id1 = self._create_instance()
- compute1.run_instance(self.context, instance_id1)
- instance_id2 = self._create_instance()
- host = self.scheduler.driver.schedule_run_instance(self.context,
- instance_id2)
- self.assertEqual(host, 'host2')
- compute1.terminate_instance(self.context, instance_id1)
- db.instance_destroy(self.context, instance_id2)
+
+ global instance_ids
+ instance_ids = []
+ instance_ids.append(_create_instance()['id'])
+ compute1.run_instance(self.context, instance_ids[0])
+
+ self.stubs.Set(SimpleScheduler,
+ 'create_instance_db_entry', _fake_create_instance_db_entry)
+ global _picked_host
+ _picked_host = None
+ self.stubs.Set(driver,
+ 'cast_to_compute_host', _fake_cast_to_compute_host)
+
+ request_spec = _create_request_spec()
+ instances = self.scheduler.driver.schedule_run_instance(
+ self.context, request_spec)
+
+ self.assertEqual(_picked_host, 'host2')
+ self.assertEqual(len(instance_ids), 2)
+ self.assertEqual(len(instances), 1)
+ self.assertEqual(instances[0].get('_is_precooked', False), False)
+
+ compute1.terminate_instance(self.context, instance_ids[0])
+ compute2.terminate_instance(self.context, instance_ids[1])
compute1.kill()
compute2.kill()
@@ -392,14 +455,27 @@ class SimpleDriverTestCase(test.TestCase):
'compute',
FLAGS.compute_manager)
compute2.start()
- instance_id1 = self._create_instance()
- compute1.run_instance(self.context, instance_id1)
- instance_id2 = self._create_instance(availability_zone='nova:host1')
- host = self.scheduler.driver.schedule_run_instance(self.context,
- instance_id2)
- self.assertEqual('host1', host)
- compute1.terminate_instance(self.context, instance_id1)
- db.instance_destroy(self.context, instance_id2)
+
+ global instance_ids
+ instance_ids = []
+ instance_ids.append(_create_instance()['id'])
+ compute1.run_instance(self.context, instance_ids[0])
+
+ self.stubs.Set(SimpleScheduler,
+ 'create_instance_db_entry', _fake_create_instance_db_entry)
+ global _picked_host
+ _picked_host = None
+ self.stubs.Set(driver,
+ 'cast_to_compute_host', _fake_cast_to_compute_host)
+
+ request_spec = _create_request_spec(availability_zone='nova:host1')
+ instances = self.scheduler.driver.schedule_run_instance(
+ self.context, request_spec)
+ self.assertEqual(_picked_host, 'host1')
+ self.assertEqual(len(instance_ids), 2)
+
+ compute1.terminate_instance(self.context, instance_ids[0])
+ compute1.terminate_instance(self.context, instance_ids[1])
compute1.kill()
compute2.kill()
@@ -414,12 +490,21 @@ class SimpleDriverTestCase(test.TestCase):
delta = datetime.timedelta(seconds=FLAGS.service_down_time * 2)
past = now - delta
db.service_update(self.context, s1['id'], {'updated_at': past})
- instance_id2 = self._create_instance(availability_zone='nova:host1')
+
+ global instance_ids
+ instance_ids = []
+ self.stubs.Set(SimpleScheduler,
+ 'create_instance_db_entry', _fake_create_instance_db_entry)
+ global _picked_host
+ _picked_host = None
+ self.stubs.Set(driver,
+ 'cast_to_compute_host', _fake_cast_to_compute_host)
+
+ request_spec = _create_request_spec(availability_zone='nova:host1')
self.assertRaises(driver.WillNotSchedule,
self.scheduler.driver.schedule_run_instance,
self.context,
- instance_id2)
- db.instance_destroy(self.context, instance_id2)
+ request_spec)
compute1.kill()
def test_will_schedule_on_disabled_host_if_specified_no_queue(self):
@@ -430,11 +515,22 @@ class SimpleDriverTestCase(test.TestCase):
compute1.start()
s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute')
db.service_update(self.context, s1['id'], {'disabled': True})
- instance_id2 = self._create_instance(availability_zone='nova:host1')
- host = self.scheduler.driver.schedule_run_instance(self.context,
- instance_id2)
- self.assertEqual('host1', host)
- db.instance_destroy(self.context, instance_id2)
+
+ global instance_ids
+ instance_ids = []
+ self.stubs.Set(SimpleScheduler,
+ 'create_instance_db_entry', _fake_create_instance_db_entry)
+ global _picked_host
+ _picked_host = None
+ self.stubs.Set(driver,
+ 'cast_to_compute_host', _fake_cast_to_compute_host)
+
+ request_spec = _create_request_spec(availability_zone='nova:host1')
+ instances = self.scheduler.driver.schedule_run_instance(
+ self.context, request_spec)
+ self.assertEqual(_picked_host, 'host1')
+ self.assertEqual(len(instance_ids), 1)
+ compute1.terminate_instance(self.context, instance_ids[0])
compute1.kill()
def test_too_many_cores_no_queue(self):
@@ -452,17 +548,17 @@ class SimpleDriverTestCase(test.TestCase):
instance_ids1 = []
instance_ids2 = []
for index in xrange(FLAGS.max_cores):
- instance_id = self._create_instance()
+ instance_id = _create_instance()['id']
compute1.run_instance(self.context, instance_id)
instance_ids1.append(instance_id)
- instance_id = self._create_instance()
+ instance_id = _create_instance()['id']
compute2.run_instance(self.context, instance_id)
instance_ids2.append(instance_id)
- instance_id = self._create_instance()
+ request_spec = _create_request_spec()
self.assertRaises(driver.NoValidHost,
self.scheduler.driver.schedule_run_instance,
self.context,
- instance_id)
+ request_spec)
for instance_id in instance_ids1:
compute1.terminate_instance(self.context, instance_id)
for instance_id in instance_ids2:
@@ -481,13 +577,19 @@ class SimpleDriverTestCase(test.TestCase):
'nova-volume',
'volume',
FLAGS.volume_manager)
+
+ global _picked_host
+ _picked_host = None
+ self.stubs.Set(driver,
+ 'cast_to_volume_host', _fake_cast_to_volume_host)
+
volume2.start()
- volume_id1 = self._create_volume()
+ volume_id1 = _create_volume()
volume1.create_volume(self.context, volume_id1)
- volume_id2 = self._create_volume()
- host = self.scheduler.driver.schedule_create_volume(self.context,
- volume_id2)
- self.assertEqual(host, 'host2')
+ volume_id2 = _create_volume()
+ self.scheduler.driver.schedule_create_volume(self.context,
+ volume_id2)
+ self.assertEqual(_picked_host, 'host2')
volume1.delete_volume(self.context, volume_id1)
db.volume_destroy(self.context, volume_id2)
@@ -514,17 +616,30 @@ class SimpleDriverTestCase(test.TestCase):
compute2.kill()
def test_least_busy_host_gets_instance(self):
- """Ensures the host with less cores gets the next one"""
+ """Ensures the host with less cores gets the next one w/ Simple"""
compute1 = self.start_service('compute', host='host1')
compute2 = self.start_service('compute', host='host2')
- instance_id1 = self._create_instance()
- compute1.run_instance(self.context, instance_id1)
- instance_id2 = self._create_instance()
- host = self.scheduler.driver.schedule_run_instance(self.context,
- instance_id2)
- self.assertEqual(host, 'host2')
- compute1.terminate_instance(self.context, instance_id1)
- db.instance_destroy(self.context, instance_id2)
+
+ global instance_ids
+ instance_ids = []
+ instance_ids.append(_create_instance()['id'])
+ compute1.run_instance(self.context, instance_ids[0])
+
+ self.stubs.Set(SimpleScheduler,
+ 'create_instance_db_entry', _fake_create_instance_db_entry)
+ global _picked_host
+ _picked_host = None
+ self.stubs.Set(driver,
+ 'cast_to_compute_host', _fake_cast_to_compute_host)
+
+ request_spec = _create_request_spec()
+ instances = self.scheduler.driver.schedule_run_instance(
+ self.context, request_spec)
+ self.assertEqual(_picked_host, 'host2')
+ self.assertEqual(len(instance_ids), 2)
+
+ compute1.terminate_instance(self.context, instance_ids[0])
+ compute2.terminate_instance(self.context, instance_ids[1])
compute1.kill()
compute2.kill()
@@ -532,41 +647,64 @@ class SimpleDriverTestCase(test.TestCase):
"""Ensures if you set availability_zone it launches on that zone"""
compute1 = self.start_service('compute', host='host1')
compute2 = self.start_service('compute', host='host2')
- instance_id1 = self._create_instance()
- compute1.run_instance(self.context, instance_id1)
- instance_id2 = self._create_instance(availability_zone='nova:host1')
- host = self.scheduler.driver.schedule_run_instance(self.context,
- instance_id2)
- self.assertEqual('host1', host)
- compute1.terminate_instance(self.context, instance_id1)
- db.instance_destroy(self.context, instance_id2)
+
+ global instance_ids
+ instance_ids = []
+ instance_ids.append(_create_instance()['id'])
+ compute1.run_instance(self.context, instance_ids[0])
+
+ self.stubs.Set(SimpleScheduler,
+ 'create_instance_db_entry', _fake_create_instance_db_entry)
+ global _picked_host
+ _picked_host = None
+ self.stubs.Set(driver,
+ 'cast_to_compute_host', _fake_cast_to_compute_host)
+
+ request_spec = _create_request_spec(availability_zone='nova:host1')
+ instances = self.scheduler.driver.schedule_run_instance(
+ self.context, request_spec)
+ self.assertEqual(_picked_host, 'host1')
+ self.assertEqual(len(instance_ids), 2)
+
+ compute1.terminate_instance(self.context, instance_ids[0])
+ compute1.terminate_instance(self.context, instance_ids[1])
compute1.kill()
compute2.kill()
- def test_wont_sechedule_if_specified_host_is_down(self):
+ def test_wont_schedule_if_specified_host_is_down(self):
compute1 = self.start_service('compute', host='host1')
s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute')
now = utils.utcnow()
delta = datetime.timedelta(seconds=FLAGS.service_down_time * 2)
past = now - delta
db.service_update(self.context, s1['id'], {'updated_at': past})
- instance_id2 = self._create_instance(availability_zone='nova:host1')
+ request_spec = _create_request_spec(availability_zone='nova:host1')
self.assertRaises(driver.WillNotSchedule,
self.scheduler.driver.schedule_run_instance,
self.context,
- instance_id2)
- db.instance_destroy(self.context, instance_id2)
+ request_spec)
compute1.kill()
def test_will_schedule_on_disabled_host_if_specified(self):
compute1 = self.start_service('compute', host='host1')
s1 = db.service_get_by_args(self.context, 'host1', 'nova-compute')
db.service_update(self.context, s1['id'], {'disabled': True})
- instance_id2 = self._create_instance(availability_zone='nova:host1')
- host = self.scheduler.driver.schedule_run_instance(self.context,
- instance_id2)
- self.assertEqual('host1', host)
- db.instance_destroy(self.context, instance_id2)
+
+ global instance_ids
+ instance_ids = []
+ self.stubs.Set(SimpleScheduler,
+ 'create_instance_db_entry', _fake_create_instance_db_entry)
+ global _picked_host
+ _picked_host = None
+ self.stubs.Set(driver,
+ 'cast_to_compute_host', _fake_cast_to_compute_host)
+
+ request_spec = _create_request_spec(availability_zone='nova:host1')
+ instances = self.scheduler.driver.schedule_run_instance(
+ self.context, request_spec)
+ self.assertEqual(_picked_host, 'host1')
+ self.assertEqual(len(instance_ids), 1)
+ compute1.terminate_instance(self.context, instance_ids[0])
compute1.kill()
def test_too_many_cores(self):
@@ -576,18 +714,30 @@ class SimpleDriverTestCase(test.TestCase):
instance_ids1 = []
instance_ids2 = []
for index in xrange(FLAGS.max_cores):
- instance_id = self._create_instance()
+ instance_id = _create_instance()['id']
compute1.run_instance(self.context, instance_id)
instance_ids1.append(instance_id)
- instance_id = self._create_instance()
+ instance_id = _create_instance()['id']
compute2.run_instance(self.context, instance_id)
instance_ids2.append(instance_id)
- instance_id = self._create_instance()
+
+ def _create_instance_db_entry(simple_self, context, request_spec):
+ self.fail(_("Shouldn't try to create DB entry when at "
+ "max cores"))
+ self.stubs.Set(SimpleScheduler,
+ 'create_instance_db_entry', _create_instance_db_entry)
+
+ global _picked_host
+ _picked_host = None
+ self.stubs.Set(driver,
+ 'cast_to_compute_host', _fake_cast_to_compute_host)
+
+ request_spec = _create_request_spec()
+
self.assertRaises(driver.NoValidHost,
self.scheduler.driver.schedule_run_instance,
self.context,
- instance_id)
- db.instance_destroy(self.context, instance_id)
+ request_spec)
for instance_id in instance_ids1:
compute1.terminate_instance(self.context, instance_id)
for instance_id in instance_ids2:
@@ -599,12 +749,18 @@ class SimpleDriverTestCase(test.TestCase):
"""Ensures the host with less gigabytes gets the next one"""
volume1 = self.start_service('volume', host='host1')
volume2 = self.start_service('volume', host='host2')
- volume_id1 = self._create_volume()
+
+ global _picked_host
+ _picked_host = None
+ self.stubs.Set(driver,
+ 'cast_to_volume_host', _fake_cast_to_volume_host)
+
+ volume_id1 = _create_volume()
volume1.create_volume(self.context, volume_id1)
- volume_id2 = self._create_volume()
- host = self.scheduler.driver.schedule_create_volume(self.context,
- volume_id2)
- self.assertEqual(host, 'host2')
+ volume_id2 = _create_volume()
+ self.scheduler.driver.schedule_create_volume(self.context,
+ volume_id2)
+ self.assertEqual(_picked_host, 'host2')
volume1.delete_volume(self.context, volume_id1)
db.volume_destroy(self.context, volume_id2)
volume1.kill()
@@ -617,13 +773,13 @@ class SimpleDriverTestCase(test.TestCase):
volume_ids1 = []
volume_ids2 = []
for index in xrange(FLAGS.max_gigabytes):
- volume_id = self._create_volume()
+ volume_id = _create_volume()
volume1.create_volume(self.context, volume_id)
volume_ids1.append(volume_id)
- volume_id = self._create_volume()
+ volume_id = _create_volume()
volume2.create_volume(self.context, volume_id)
volume_ids2.append(volume_id)
- volume_id = self._create_volume()
+ volume_id = _create_volume()
self.assertRaises(driver.NoValidHost,
self.scheduler.driver.schedule_create_volume,
self.context,
@@ -636,13 +792,13 @@ class SimpleDriverTestCase(test.TestCase):
volume2.kill()
def test_scheduler_live_migration_with_volume(self):
- """scheduler_live_migration() works correctly as expected.
+ """schedule_live_migration() works correctly as expected.
Also, checks instance state is changed from 'running' -> 'migrating'.
"""
- instance_id = self._create_instance()
+ instance_id = _create_instance(host='dummy')['id']
i_ref = db.instance_get(self.context, instance_id)
dic = {'instance_id': instance_id, 'size': 1}
v_ref = db.volume_create(self.context, dic)
@@ -680,7 +836,8 @@ class SimpleDriverTestCase(test.TestCase):
def test_live_migration_src_check_instance_not_running(self):
"""The instance given by instance_id is not running."""
- instance_id = self._create_instance(power_state=power_state.NOSTATE)
+ instance_id = _create_instance(
+ power_state=power_state.NOSTATE)['id']
i_ref = db.instance_get(self.context, instance_id)
try:
@@ -695,7 +852,7 @@ class SimpleDriverTestCase(test.TestCase):
def test_live_migration_src_check_volume_node_not_alive(self):
"""Raise exception when volume node is not alive."""
- instance_id = self._create_instance()
+ instance_id = _create_instance()['id']
i_ref = db.instance_get(self.context, instance_id)
dic = {'instance_id': instance_id, 'size': 1}
v_ref = db.volume_create(self.context, {'instance_id': instance_id,
@@ -715,7 +872,7 @@ class SimpleDriverTestCase(test.TestCase):
def test_live_migration_src_check_compute_node_not_alive(self):
"""Confirms src-compute node is alive."""
- instance_id = self._create_instance()
+ instance_id = _create_instance()['id']
i_ref = db.instance_get(self.context, instance_id)
t = utils.utcnow() - datetime.timedelta(10)
s_ref = self._create_compute_service(created_at=t, updated_at=t,
@@ -730,7 +887,7 @@ class SimpleDriverTestCase(test.TestCase):
def test_live_migration_src_check_works_correctly(self):
"""Confirms this method finishes with no error."""
- instance_id = self._create_instance()
+ instance_id = _create_instance()['id']
i_ref = db.instance_get(self.context, instance_id)
s_ref = self._create_compute_service(host=i_ref['host'])
@@ -743,7 +900,7 @@ class SimpleDriverTestCase(test.TestCase):
def test_live_migration_dest_check_not_alive(self):
"""Confirms exception raises in case dest host does not exist."""
- instance_id = self._create_instance()
+ instance_id = _create_instance()['id']
i_ref = db.instance_get(self.context, instance_id)
t = utils.utcnow() - datetime.timedelta(10)
s_ref = self._create_compute_service(created_at=t, updated_at=t,
@@ -758,7 +915,7 @@ class SimpleDriverTestCase(test.TestCase):
def test_live_migration_dest_check_service_same_host(self):
"""Confirms exceptioin raises in case dest and src is same host."""
- instance_id = self._create_instance()
+ instance_id = _create_instance()['id']
i_ref = db.instance_get(self.context, instance_id)
s_ref = self._create_compute_service(host=i_ref['host'])
@@ -771,9 +928,9 @@ class SimpleDriverTestCase(test.TestCase):
def test_live_migration_dest_check_service_lack_memory(self):
"""Confirms exception raises when dest doesn't have enough memory."""
- instance_id = self._create_instance()
- instance_id2 = self._create_instance(host='somewhere',
- memory_mb=12)
+ instance_id = _create_instance()['id']
+ instance_id2 = _create_instance(host='somewhere',
+ memory_mb=12)['id']
i_ref = db.instance_get(self.context, instance_id)
s_ref = self._create_compute_service(host='somewhere')
@@ -787,9 +944,9 @@ class SimpleDriverTestCase(test.TestCase):
def test_block_migration_dest_check_service_lack_disk(self):
"""Confirms exception raises when dest doesn't have enough disk."""
- instance_id = self._create_instance()
- instance_id2 = self._create_instance(host='somewhere',
- local_gb=70)
+ instance_id = _create_instance()['id']
+ instance_id2 = _create_instance(host='somewhere',
+ local_gb=70)['id']
i_ref = db.instance_get(self.context, instance_id)
s_ref = self._create_compute_service(host='somewhere')
@@ -803,7 +960,7 @@ class SimpleDriverTestCase(test.TestCase):
def test_live_migration_dest_check_service_works_correctly(self):
"""Confirms method finishes with no error."""
- instance_id = self._create_instance()
+ instance_id = _create_instance()['id']
i_ref = db.instance_get(self.context, instance_id)
s_ref = self._create_compute_service(host='somewhere',
memory_mb_used=5)
@@ -821,7 +978,7 @@ class SimpleDriverTestCase(test.TestCase):
dest = 'dummydest'
# mocks for live_migration_common_check()
- instance_id = self._create_instance()
+ instance_id = _create_instance()['id']
i_ref = db.instance_get(self.context, instance_id)
t1 = utils.utcnow() - datetime.timedelta(10)
s_ref = self._create_compute_service(created_at=t1, updated_at=t1,
@@ -855,7 +1012,7 @@ class SimpleDriverTestCase(test.TestCase):
def test_live_migration_common_check_service_different_hypervisor(self):
"""Original host and dest host has different hypervisor type."""
dest = 'dummydest'
- instance_id = self._create_instance()
+ instance_id = _create_instance(host='dummy')['id']
i_ref = db.instance_get(self.context, instance_id)
# compute service for destination
@@ -880,7 +1037,7 @@ class SimpleDriverTestCase(test.TestCase):
def test_live_migration_common_check_service_different_version(self):
"""Original host and dest host has different hypervisor version."""
dest = 'dummydest'
- instance_id = self._create_instance()
+ instance_id = _create_instance(host='dummy')['id']
i_ref = db.instance_get(self.context, instance_id)
# compute service for destination
@@ -904,10 +1061,10 @@ class SimpleDriverTestCase(test.TestCase):
db.service_destroy(self.context, s_ref2['id'])
def test_live_migration_common_check_checking_cpuinfo_fail(self):
- """Raise excetion when original host doen't have compatible cpu."""
+ """Raise exception when original host doesn't have compatible cpu."""
dest = 'dummydest'
- instance_id = self._create_instance()
+ instance_id = _create_instance(host='dummy')['id']
i_ref = db.instance_get(self.context, instance_id)
# compute service for destination
@@ -927,7 +1084,7 @@ class SimpleDriverTestCase(test.TestCase):
self.mox.ReplayAll()
try:
- self.scheduler.driver._live_migration_common_check(self.context,
+ driver._live_migration_common_check(self.context,
i_ref,
dest,
False)
@@ -1021,7 +1178,6 @@ class FakeResource(object):
class ZoneRedirectTest(test.TestCase):
def setUp(self):
super(ZoneRedirectTest, self).setUp()
- self.stubs = stubout.StubOutForTesting()
self.stubs.Set(db, 'zone_get_all', zone_get_all)
self.stubs.Set(db, 'instance_get_by_uuid',
@@ -1029,7 +1185,6 @@ class ZoneRedirectTest(test.TestCase):
self.flags(enable_zone_routing=True)
def tearDown(self):
- self.stubs.UnsetAll()
super(ZoneRedirectTest, self).tearDown()
def test_trap_found_locally(self):
@@ -1257,12 +1412,10 @@ class FakeNovaClientZones(object):
class CallZoneMethodTest(test.TestCase):
def setUp(self):
super(CallZoneMethodTest, self).setUp()
- self.stubs = stubout.StubOutForTesting()
self.stubs.Set(db, 'zone_get_all', zone_get_all)
self.stubs.Set(novaclient, 'Client', FakeNovaClientZones)
def tearDown(self):
- self.stubs.UnsetAll()
super(CallZoneMethodTest, self).tearDown()
def test_call_zone_method(self):
diff --git a/nova/tests/scheduler/test_vsa_scheduler.py b/nova/tests/scheduler/test_vsa_scheduler.py
index 37964f00d..802946e1b 100644
--- a/nova/tests/scheduler/test_vsa_scheduler.py
+++ b/nova/tests/scheduler/test_vsa_scheduler.py
@@ -22,6 +22,7 @@ from nova import db
from nova import exception
from nova import flags
from nova import log as logging
+from nova import rpc
from nova import test
from nova import utils
from nova.volume import volume_types
@@ -37,6 +38,10 @@ scheduled_volume = {}
global_volume = {}
+def fake_rpc_cast(*args, **kwargs):
+ pass
+
+
class FakeVsaLeastUsedScheduler(
vsa_sched.VsaSchedulerLeastUsedHost):
# No need to stub anything at the moment
@@ -170,12 +175,10 @@ class VsaSchedulerTestCase(test.TestCase):
LOG.debug(_("Test: provision vol %(name)s on host %(host)s"),
locals())
LOG.debug(_("\t vol=%(vol)s"), locals())
- pass
def _fake_vsa_update(self, context, vsa_id, values):
LOG.debug(_("Test: VSA update request: vsa_id=%(vsa_id)s "\
"values=%(values)s"), locals())
- pass
def _fake_volume_create(self, context, options):
LOG.debug(_("Test: Volume create: %s"), options)
@@ -196,7 +199,6 @@ class VsaSchedulerTestCase(test.TestCase):
"values=%(values)s"), locals())
global scheduled_volume
scheduled_volume = {'id': volume_id, 'host': values['host']}
- pass
def _fake_service_get_by_args(self, context, host, binary):
return "service"
@@ -209,7 +211,6 @@ class VsaSchedulerTestCase(test.TestCase):
def setUp(self, sched_class=None):
super(VsaSchedulerTestCase, self).setUp()
- self.stubs = stubout.StubOutForTesting()
self.context = context.get_admin_context()
if sched_class is None:
@@ -220,6 +221,7 @@ class VsaSchedulerTestCase(test.TestCase):
self.host_num = 10
self.drive_type_num = 5
+ self.stubs.Set(rpc, 'cast', fake_rpc_cast)
self.stubs.Set(self.sched,
'_get_service_states', self._fake_get_service_states)
self.stubs.Set(self.sched,
@@ -234,8 +236,6 @@ class VsaSchedulerTestCase(test.TestCase):
def tearDown(self):
for name in self.created_types_lst:
volume_types.purge(self.context, name)
-
- self.stubs.UnsetAll()
super(VsaSchedulerTestCase, self).tearDown()
def test_vsa_sched_create_volumes_simple(self):
@@ -333,6 +333,8 @@ class VsaSchedulerTestCase(test.TestCase):
self.stubs.Set(self.sched,
'_get_service_states', self._fake_get_service_states)
self.stubs.Set(nova.db, 'volume_create', self._fake_volume_create)
+ self.stubs.Set(nova.db, 'volume_update', self._fake_volume_update)
+ self.stubs.Set(rpc, 'cast', fake_rpc_cast)
self.sched.schedule_create_volumes(self.context,
request_spec,
@@ -467,10 +469,9 @@ class VsaSchedulerTestCase(test.TestCase):
self.stubs.Set(self.sched,
'service_is_up', self._fake_service_is_up_True)
- host = self.sched.schedule_create_volume(self.context,
- 123, availability_zone=None)
+ self.sched.schedule_create_volume(self.context,
+ 123, availability_zone=None)
- self.assertEqual(host, 'host_3')
self.assertEqual(scheduled_volume['id'], 123)
self.assertEqual(scheduled_volume['host'], 'host_3')
@@ -514,10 +515,9 @@ class VsaSchedulerTestCase(test.TestCase):
global_volume['volume_type_id'] = volume_type['id']
global_volume['size'] = 0
- host = self.sched.schedule_create_volume(self.context,
- 123, availability_zone=None)
+ self.sched.schedule_create_volume(self.context,
+ 123, availability_zone=None)
- self.assertEqual(host, 'host_2')
self.assertEqual(scheduled_volume['id'], 123)
self.assertEqual(scheduled_volume['host'], 'host_2')
@@ -529,7 +529,6 @@ class VsaSchedulerTestCaseMostAvail(VsaSchedulerTestCase):
FakeVsaMostAvailCapacityScheduler())
def tearDown(self):
- self.stubs.UnsetAll()
super(VsaSchedulerTestCaseMostAvail, self).tearDown()
def test_vsa_sched_create_single_volume(self):
@@ -558,10 +557,9 @@ class VsaSchedulerTestCaseMostAvail(VsaSchedulerTestCase):
global_volume['volume_type_id'] = volume_type['id']
global_volume['size'] = 0
- host = self.sched.schedule_create_volume(self.context,
- 123, availability_zone=None)
+ self.sched.schedule_create_volume(self.context,
+ 123, availability_zone=None)
- self.assertEqual(host, 'host_9')
self.assertEqual(scheduled_volume['id'], 123)
self.assertEqual(scheduled_volume['host'], 'host_9')
diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py
index 86ffc18a6..4da551c93 100644
--- a/nova/tests/test_compute.py
+++ b/nova/tests/test_compute.py
@@ -28,6 +28,7 @@ from nova import db
from nova import exception
from nova import flags
from nova import log as logging
+from nova.scheduler import driver as scheduler_driver
from nova import rpc
from nova import test
from nova import utils
@@ -56,6 +57,38 @@ class FakeTime(object):
self.counter += t
+orig_rpc_call = rpc.call
+orig_rpc_cast = rpc.cast
+
+
+def rpc_call_wrapper(context, topic, msg, do_cast=True):
+ """Stub out the scheduler creating the instance entry"""
+ if topic == FLAGS.scheduler_topic and \
+ msg['method'] == 'run_instance':
+ request_spec = msg['args']['request_spec']
+ scheduler = scheduler_driver.Scheduler
+ num_instances = request_spec.get('num_instances', 1)
+ instances = []
+ for x in xrange(num_instances):
+ instance = scheduler().create_instance_db_entry(
+ context, request_spec)
+ encoded = scheduler_driver.encode_instance(instance)
+ instances.append(encoded)
+ return instances
+ else:
+ if do_cast:
+ orig_rpc_cast(context, topic, msg)
+ else:
+ return orig_rpc_call(context, topic, msg)
+
+
+def rpc_cast_wrapper(context, topic, msg):
+ """Stub out the scheduler creating the instance entry in
+ the reservation_id case.
+ """
+ rpc_call_wrapper(context, topic, msg, do_cast=True)
+
+
def nop_report_driver_status(self):
pass
@@ -80,6 +113,8 @@ class ComputeTestCase(test.TestCase):
'properties': {'kernel_id': 1, 'ramdisk_id': 1}}
self.stubs.Set(fake_image._FakeImageService, 'show', fake_show)
+ self.stubs.Set(rpc, 'call', rpc_call_wrapper)
+ self.stubs.Set(rpc, 'cast', rpc_cast_wrapper)
def _create_instance(self, params=None):
"""Create a test instance"""
@@ -142,7 +177,7 @@ class ComputeTestCase(test.TestCase):
"""Verify that an instance cannot be created without a display_name."""
cases = [dict(), dict(display_name=None)]
for instance in cases:
- ref = self.compute_api.create(self.context,
+ (ref, resv_id) = self.compute_api.create(self.context,
instance_types.get_default_instance_type(), None, **instance)
try:
self.assertNotEqual(ref[0]['display_name'], None)
@@ -152,7 +187,7 @@ class ComputeTestCase(test.TestCase):
def test_create_instance_associates_security_groups(self):
"""Make sure create associates security groups"""
group = self._create_group()
- ref = self.compute_api.create(
+ (ref, resv_id) = self.compute_api.create(
self.context,
instance_type=instance_types.get_default_instance_type(),
image_href=None,
@@ -212,7 +247,7 @@ class ComputeTestCase(test.TestCase):
('<}\x1fh\x10e\x08l\x02l\x05o\x12!{>', 'hello'),
('hello_server', 'hello-server')]
for display_name, hostname in cases:
- ref = self.compute_api.create(self.context,
+ (ref, resv_id) = self.compute_api.create(self.context,
instance_types.get_default_instance_type(), None,
display_name=display_name)
try:
@@ -224,7 +259,7 @@ class ComputeTestCase(test.TestCase):
"""Make sure destroying disassociates security groups"""
group = self._create_group()
- ref = self.compute_api.create(
+ (ref, resv_id) = self.compute_api.create(
self.context,
instance_type=instance_types.get_default_instance_type(),
image_href=None,
@@ -240,7 +275,7 @@ class ComputeTestCase(test.TestCase):
"""Make sure destroying security groups disassociates instances"""
group = self._create_group()
- ref = self.compute_api.create(
+ (ref, resv_id) = self.compute_api.create(
self.context,
instance_type=instance_types.get_default_instance_type(),
image_href=None,
@@ -1398,6 +1433,84 @@ class ComputeTestCase(test.TestCase):
'swap'),
swap_size)
+ def test_reservation_id_one_instance(self):
+ """Verify building an instance has a reservation_id that
+ matches return value from create"""
+ (refs, resv_id) = self.compute_api.create(self.context,
+ instance_types.get_default_instance_type(), None)
+ try:
+ self.assertEqual(len(refs), 1)
+ self.assertEqual(refs[0]['reservation_id'], resv_id)
+ finally:
+ db.instance_destroy(self.context, refs[0]['id'])
+
+ def test_reservation_ids_two_instances(self):
+ """Verify building 2 instances at once results in a
+ reservation_id being returned equal to reservation id set
+ in both instances
+ """
+ (refs, resv_id) = self.compute_api.create(self.context,
+ instance_types.get_default_instance_type(), None,
+ min_count=2, max_count=2)
+ try:
+ self.assertEqual(len(refs), 2)
+ self.assertNotEqual(resv_id, None)
+ finally:
+ for instance in refs:
+ self.assertEqual(instance['reservation_id'], resv_id)
+ db.instance_destroy(self.context, instance['id'])
+
+ def test_reservation_ids_two_instances_no_wait(self):
+ """Verify building 2 instances at once without waiting for
+ instance IDs results in a reservation_id being returned equal
+ to reservation id set in both instances
+ """
+ (refs, resv_id) = self.compute_api.create(self.context,
+ instance_types.get_default_instance_type(), None,
+ min_count=2, max_count=2, wait_for_instances=False)
+ try:
+ self.assertEqual(refs, None)
+ self.assertNotEqual(resv_id, None)
+ finally:
+ instances = self.compute_api.get_all(self.context,
+ search_opts={'reservation_id': resv_id})
+ self.assertEqual(len(instances), 2)
+ for instance in instances:
+ self.assertEqual(instance['reservation_id'], resv_id)
+ db.instance_destroy(self.context, instance['id'])
+
+ def test_create_with_specified_reservation_id(self):
+ """Verify building instances with a specified
+ reservation_id results in the correct reservation_id
+ being set
+ """
+
+ # We need admin context to be able to specify our own
+ # reservation_ids.
+ context = self.context.elevated()
+ # 1 instance
+ (refs, resv_id) = self.compute_api.create(context,
+ instance_types.get_default_instance_type(), None,
+ min_count=1, max_count=1, reservation_id='meow')
+ try:
+ self.assertEqual(len(refs), 1)
+ self.assertEqual(resv_id, 'meow')
+ finally:
+ self.assertEqual(refs[0]['reservation_id'], resv_id)
+ db.instance_destroy(self.context, refs[0]['id'])
+
+ # 2 instances
+ (refs, resv_id) = self.compute_api.create(context,
+ instance_types.get_default_instance_type(), None,
+ min_count=2, max_count=2, reservation_id='woof')
+ try:
+ self.assertEqual(len(refs), 2)
+ self.assertEqual(resv_id, 'woof')
+ finally:
+ for instance in refs:
+ self.assertEqual(instance['reservation_id'], resv_id)
+ db.instance_destroy(self.context, instance['id'])
+
class ComputeTestMinRamMinDisk(test.TestCase):
def setUp(self):
@@ -1405,6 +1518,8 @@ class ComputeTestMinRamMinDisk(test.TestCase):
self.compute = utils.import_object(FLAGS.compute_manager)
self.compute_api = compute.API()
self.context = context.RequestContext('fake', 'fake')
+ self.stubs.Set(rpc, 'call', rpc_call_wrapper)
+ self.stubs.Set(rpc, 'cast', rpc_cast_wrapper)
self.fake_image = {
'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1}}
@@ -1425,10 +1540,9 @@ class ComputeTestMinRamMinDisk(test.TestCase):
# Now increase the inst_type memory and make sure all is fine.
inst_type['memory_mb'] = 2
- ref = self.compute_api.create(self.context, inst_type, None)
- self.assertTrue(ref)
-
- db.instance_destroy(self.context, ref[0]['id'])
+ (refs, resv_id) = self.compute_api.create(self.context,
+ inst_type, None)
+ db.instance_destroy(self.context, refs[0]['id'])
def test_create_with_too_little_disk(self):
"""Test an instance type with too little disk space"""
@@ -1447,10 +1561,9 @@ class ComputeTestMinRamMinDisk(test.TestCase):
# Now increase the inst_type disk space and make sure all is fine.
inst_type['local_gb'] = 2
- ref = self.compute_api.create(self.context, inst_type, None)
- self.assertTrue(ref)
-
- db.instance_destroy(self.context, ref[0]['id'])
+ (refs, resv_id) = self.compute_api.create(self.context,
+ inst_type, None)
+ db.instance_destroy(self.context, refs[0]['id'])
def test_create_just_enough_ram_and_disk(self):
"""Test an instance type with just enough ram and disk space"""
@@ -1466,10 +1579,9 @@ class ComputeTestMinRamMinDisk(test.TestCase):
return img
self.stubs.Set(fake_image._FakeImageService, 'show', fake_show)
- ref = self.compute_api.create(self.context, inst_type, None)
- self.assertTrue(ref)
-
- db.instance_destroy(self.context, ref[0]['id'])
+ (refs, resv_id) = self.compute_api.create(self.context,
+ inst_type, None)
+ db.instance_destroy(self.context, refs[0]['id'])
def test_create_with_no_ram_and_disk_reqs(self):
"""Test an instance type with no min_ram or min_disk"""
@@ -1482,7 +1594,6 @@ class ComputeTestMinRamMinDisk(test.TestCase):
return copy(self.fake_image)
self.stubs.Set(fake_image._FakeImageService, 'show', fake_show)
- ref = self.compute_api.create(self.context, inst_type, None)
- self.assertTrue(ref)
-
- db.instance_destroy(self.context, ref[0]['id'])
+ (refs, resv_id) = self.compute_api.create(self.context,
+ inst_type, None)
+ db.instance_destroy(self.context, refs[0]['id'])
diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py
index f4b481ebe..35d48dd62 100644
--- a/nova/tests/test_quota.py
+++ b/nova/tests/test_quota.py
@@ -21,9 +21,11 @@ from nova import context
from nova import db
from nova import flags
from nova import quota
+from nova import rpc
from nova import test
from nova import volume
from nova.compute import instance_types
+from nova.scheduler import driver as scheduler_driver
FLAGS = flags.FLAGS
@@ -51,6 +53,21 @@ class QuotaTestCase(test.TestCase):
self.context = context.RequestContext(self.user_id,
self.project_id,
True)
+ orig_rpc_call = rpc.call
+
+ def rpc_call_wrapper(context, topic, msg):
+ """Stub out the scheduler creating the instance entry"""
+ if topic == FLAGS.scheduler_topic and \
+ msg['method'] == 'run_instance':
+ scheduler = scheduler_driver.Scheduler
+ instance = scheduler().create_instance_db_entry(
+ context,
+ msg['args']['request_spec'])
+ return [scheduler_driver.encode_instance(instance)]
+ else:
+ return orig_rpc_call(context, topic, msg)
+
+ self.stubs.Set(rpc, 'call', rpc_call_wrapper)
def _create_instance(self, cores=2):
"""Create a test instance"""