diff options
32 files changed, 800 insertions, 1012 deletions
diff --git a/bin/nova-ajax-console-proxy b/bin/nova-ajax-console-proxy index 23fb42fb5..4770a8a7e 100755 --- a/bin/nova-ajax-console-proxy +++ b/bin/nova-ajax-console-proxy @@ -141,4 +141,4 @@ if __name__ == '__main__': server = wsgi.Server("AJAX Console Proxy", acp, port=acp_port) service.serve(server) service.wait() - self.conn.close() + acp.conn.close() diff --git a/doc/source/devref/index.rst b/doc/source/devref/index.rst index c1c7aa80c..6a22b0e74 100644 --- a/doc/source/devref/index.rst +++ b/doc/source/devref/index.rst @@ -35,6 +35,7 @@ Background Concepts for Nova .. toctree:: :maxdepth: 3 + threading distributed_scheduler multinic zone diff --git a/doc/source/devref/threading.rst b/doc/source/devref/threading.rst new file mode 100644 index 000000000..e499f47e1 --- /dev/null +++ b/doc/source/devref/threading.rst @@ -0,0 +1,17 @@ +Threading model +=============== + +All OpenStack services use *green thread* model of threading, implemented +through using the Python `eventlet <http://eventlet.net/>`_ and +`greenlet <http://packages.python.org/greenlet/>`_ libraries. + +Green threads use a cooperative model of threading: thread context +switches can only occur when specific eventlet or greenlet library calls are +made (e.g., sleep, certain I/O calls). From the operating system's point of +view, each OpenStack service runs in a single thread. + +The use of green threads reduces the likelihood of race conditions, but does +not completely eliminate them. In some cases, you may need to use the +``@utils.synchronized(...)`` decorator to avoid races. + + diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 4b4c0f536..fde1377db 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -391,6 +391,10 @@ class Executor(wsgi.Application): LOG.info(_('NotAuthorized raised: %s'), unicode(ex), context=context) return self._error(req, context, type(ex).__name__, unicode(ex)) + except exception.InvalidRequest as ex: + LOG.debug(_('InvalidRequest raised: %s'), unicode(ex), + context=context) + return self._error(req, context, type(ex).__name__, unicode(ex)) except Exception as ex: extra = {'environment': req.environ} LOG.exception(_('Unexpected error raised: %s'), unicode(ex), diff --git a/nova/api/ec2/apirequest.py b/nova/api/ec2/apirequest.py index 9a3e55925..61b5ba3a5 100644 --- a/nova/api/ec2/apirequest.py +++ b/nova/api/ec2/apirequest.py @@ -24,10 +24,14 @@ import datetime # TODO(termie): replace minidom with etree from xml.dom import minidom +from nova import flags from nova import log as logging +from nova import exception from nova.api.ec2 import ec2utils +from nova.api.ec2.admin import AdminController LOG = logging.getLogger("nova.api.request") +FLAGS = flags.FLAGS def _underscore_to_camelcase(str): @@ -53,6 +57,14 @@ class APIRequest(object): def invoke(self, context): try: + # Raise NotImplemented exception for Admin specific request if + # admin flag is set to false in nova.conf + if (isinstance(self.controller, AdminController) and + (not FLAGS.allow_ec2_admin_api)): + ## Raise InvalidRequest exception for EC2 Admin interface ## + LOG.exception("Unsupported API request") + raise exception.InvalidRequest() + method = getattr(self.controller, ec2utils.camelcase_to_underscore(self.action)) except AttributeError: @@ -63,7 +75,7 @@ class APIRequest(object): LOG.exception(_error) # TODO: Raise custom exception, trap in apiserver, # and reraise as 400 error. - raise Exception(_error) + raise exception.InvalidRequest() args = ec2utils.dict_from_dotted_str(self.args.items()) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 16ea74025..b2aa120e6 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -1437,10 +1437,7 @@ class CloudController(object): security_group=kwargs.get('security_group'), availability_zone=kwargs.get('placement', {}).get( 'AvailabilityZone'), - 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) + block_device_mapping=kwargs.get('block_device_mapping', {})) return self._format_run_instances(context, resv_id) def _do_instance(self, action, context, ec2_id): diff --git a/nova/api/openstack/contrib/createserverext.py b/nova/api/openstack/contrib/createserverext.py index da95164e8..ab5037304 100644 --- a/nova/api/openstack/contrib/createserverext.py +++ b/nova/api/openstack/contrib/createserverext.py @@ -21,10 +21,12 @@ from nova.api.openstack import wsgi class CreateServerController(servers.Controller): - def _build_view(self, req, instance, is_detail=False): - server = super(CreateServerController, self)._build_view(req, - instance, - is_detail) + def _build_view(self, req, instance, is_detail=False, is_create=False): + server = super(CreateServerController, self).\ + _build_view(req, + instance, + is_detail=is_detail, + is_create=is_create) if is_detail: self._build_security_groups(server['server'], instance) return server diff --git a/nova/api/openstack/contrib/security_groups.py b/nova/api/openstack/contrib/security_groups.py index 662711951..9072a34ee 100644 --- a/nova/api/openstack/contrib/security_groups.py +++ b/nova/api/openstack/contrib/security_groups.py @@ -251,7 +251,7 @@ class SecurityGroupRulesController(SecurityGroupController): cidr=None, group_id=None): values = {} - if group_id: + if group_id is not None: try: parent_group_id = int(parent_group_id) group_id = int(group_id) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 29160c5ce..b71e5df31 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -462,8 +462,7 @@ class Controller(object): 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) + block_device_mapping=block_device_mapping) except quota.QuotaError as error: self._handle_quota_error(error) except exception.InstanceTypeMemoryTooSmall as error: @@ -497,7 +496,7 @@ class Controller(object): instance['instance_type'] = inst_type instance['image_ref'] = image_href - server = self._build_view(req, instance, is_detail=True) + server = self._build_view(req, instance, is_create=True) if '_is_precooked' in server['server']: del server['server']['_is_precooked'] else: @@ -748,7 +747,7 @@ class Controller(object): return common.get_id_from_href(flavor_ref) - def _build_view(self, req, instance, is_detail=False): + def _build_view(self, req, instance, is_detail=False, is_create=False): context = req.environ['nova.context'] project_id = getattr(context, 'project_id', '') base_url = req.application_url @@ -757,7 +756,9 @@ class Controller(object): addresses_builder = views_addresses.ViewBuilder() builder = views_servers.ViewBuilder(context, addresses_builder, flavor_builder, image_builder, base_url, project_id) - return builder.build(instance, is_detail=is_detail) + return builder.build(instance, + is_detail=is_detail, + is_create=is_create) def _build_list(self, req, instances, is_detail=False): params = req.GET.copy() diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 288730efe..4a0be46c1 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -41,13 +41,15 @@ class ViewBuilder(object): self.base_url = base_url self.project_id = project_id - def build(self, inst, is_detail=False): + def build(self, inst, is_detail=False, is_create=False): """Return a dict that represenst a server.""" if inst.get('_is_precooked', False): server = dict(server=inst) else: if is_detail: server = self._build_detail(inst) + elif is_create: + server = self._build_create(inst) else: server = self._build_simple(inst) @@ -59,6 +61,12 @@ class ViewBuilder(object): """Return a simple model of a server.""" return dict(server=dict(id=inst['uuid'], name=inst['display_name'])) + def _build_create(self, inst): + """Return data that should be returned from a server create.""" + server = dict(server=dict(id=inst['uuid'])) + self._build_links(server['server'], inst) + return server + def _build_detail(self, inst): """Returns a detailed model of a server.""" vm_state = inst.get('vm_state', vm_states.BUILDING) diff --git a/nova/compute/api.py b/nova/compute/api.py index 436dc79b5..b6ba86eb3 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -45,6 +45,7 @@ LOG = logging.getLogger('nova.compute.api') FLAGS = flags.FLAGS +flags.DECLARE('enable_zone_routing', 'nova.scheduler.api') flags.DECLARE('vncproxy_topic', 'nova.vnc') flags.DEFINE_integer('find_host_timeout', 30, 'Timeout after NN seconds when looking for a host.') @@ -189,8 +190,7 @@ class API(base.Base): injected_files, admin_password, zone_blob, reservation_id, access_ip_v4, access_ip_v6, requested_networks, config_drive, - block_device_mapping, - wait_for_instances): + block_device_mapping, create_instance_here=False): """Verify all the input parameters regardless of the provisioning strategy being performed and schedule the instance(s) for creation.""" @@ -325,10 +325,18 @@ class API(base.Base): LOG.debug(_("Going to run %s instances...") % num_instances) - if wait_for_instances: - rpc_method = rpc.call - else: + if create_instance_here: + instance = self.create_db_entry_for_new_instance( + context, instance_type, image, base_options, + security_group, block_device_mapping) + # Tells scheduler we created the instance already. + base_options['id'] = instance['id'] rpc_method = rpc.cast + else: + # We need to wait for the scheduler to create the instance + # DB entries, because the instance *could* be # created in + # a child zone. + rpc_method = rpc.call # TODO(comstud): We should use rpc.multicall when we can # retrieve the full instance dictionary from the scheduler. @@ -344,6 +352,8 @@ class API(base.Base): num_instances, requested_networks, block_device_mapping, security_group) + if create_instance_here: + return ([instance], reservation_id) return (instances, reservation_id) @staticmethod @@ -534,8 +544,7 @@ class API(base.Base): 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, - wait_for_instances=True): + requested_networks=None, config_drive=None): """ Provision instances, sending instance information to the scheduler. The scheduler will determine where the instance(s) @@ -546,6 +555,13 @@ class API(base.Base): we waited for information from the scheduler or not. """ + # We can create the DB entry for the instance here if we're + # only going to create 1 instance and we're in a single + # zone deployment. This speeds up API responses for builds + # as we don't need to wait for the scheduler. + create_instance_here = (max_count == 1 and + not FLAGS.enable_zone_routing) + (instances, reservation_id) = self._create_instance( context, instance_type, image_href, kernel_id, ramdisk_id, @@ -557,10 +573,9 @@ class API(base.Base): reservation_id, access_ip_v4, access_ip_v6, requested_networks, config_drive, block_device_mapping, - wait_for_instances) + create_instance_here=create_instance_here) - if instances is None: - # wait_for_instances must have been False + if create_instance_here or instances is None: return (instances, reservation_id) inst_ret_list = [] diff --git a/nova/compute/manager.py b/nova/compute/manager.py index b1e75cd9a..ddd878664 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -470,7 +470,7 @@ class ComputeManager(manager.SchedulerDependentManager): # be fixed once we have no-db-messaging pass except: - with utils.original_exception_raised(): + with utils.save_and_reraise_exception(): _deallocate_network() def _get_instance_volume_bdms(self, context, instance_id): diff --git a/nova/context.py b/nova/context.py index de5b791c4..36d15ba08 100644 --- a/nova/context.py +++ b/nova/context.py @@ -1,5 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright 2011 OpenStack LLC. # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. @@ -20,6 +21,7 @@ import uuid +from nova import local from nova import utils @@ -51,6 +53,7 @@ class RequestContext(object): self.request_id = request_id self.auth_token = auth_token self.strategy = strategy + local.store.context = self def to_dict(self): return {'user_id': self.user_id, diff --git a/nova/exception.py b/nova/exception.py index 998fece1e..1b80a0f56 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -107,6 +107,8 @@ def wrap_exception(notifier=None, publisher_id=None, event_type=None, # TODO(sandy): Find a way to import nova.notifier.api so we don't have # to pass it in as a parameter. Otherwise we get a cyclic import of # nova.notifier.api -> nova.utils -> nova.exception :( + # TODO(johannes): Also, it would be nice to use + # utils.save_and_reraise_exception() without an import loop def inner(f): def wrapped(*args, **kw): try: @@ -206,6 +208,10 @@ class Invalid(NovaException): message = _("Unacceptable parameters.") +class InvalidRequest(Invalid): + message = _("The request is invalid.") + + class InvalidSignature(Invalid): message = _("Invalid signature %(signature)s for user %(user)s.") @@ -567,7 +573,7 @@ class NoFloatingIpsDefined(NotFound): class KeypairNotFound(NotFound): - message = _("Keypair %(keypair_name)s not found for user %(user_id)s") + message = _("Keypair %(name)s not found for user %(user_id)s") class CertificateNotFound(NotFound): diff --git a/nova/flags.py b/nova/flags.py index 20225eba5..bc4fd475d 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -474,3 +474,5 @@ DEFINE_integer('reclaim_instance_interval', 0, DEFINE_integer('zombie_instance_updated_at_window', 172800, 'Limit in seconds that a zombie instance can exist before ' 'being cleaned up.') + +DEFINE_boolean('allow_ec2_admin_api', False, 'Enable/Disable EC2 Admin API') diff --git a/nova/local.py b/nova/local.py new file mode 100644 index 000000000..19d962732 --- /dev/null +++ b/nova/local.py @@ -0,0 +1,37 @@ +# 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. + +"""Greenthread local storage of variables using weak references""" + +import weakref + +from eventlet import corolocal + + +class WeakLocal(corolocal.local): + def __getattribute__(self, attr): + rval = corolocal.local.__getattribute__(self, attr) + if rval: + rval = rval() + return rval + + def __setattr__(self, attr, value): + value = weakref.ref(value) + return corolocal.local.__setattr__(self, attr, value) + + +store = WeakLocal() diff --git a/nova/log.py b/nova/log.py index 1e04f755d..23b83ab5b 100644 --- a/nova/log.py +++ b/nova/log.py @@ -1,5 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright 2011 OpenStack LLC. # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. @@ -38,6 +39,7 @@ import traceback import nova from nova import flags +from nova import local from nova import version @@ -152,6 +154,8 @@ class NovaLogger(logging.Logger): """Extract context from any log call.""" if not extra: extra = {} + if context is None: + context = getattr(local.store, 'context', None) if context: extra.update(_dictify_context(context)) extra.update({"nova_version": version.version_string_with_vcs()}) @@ -245,11 +249,12 @@ class NovaRootLogger(NovaLogger): def setup_from_flags(self): """Setup logger from flags.""" global _filelog + if self.syslog: + self.removeHandler(self.syslog) + self.syslog = None if FLAGS.use_syslog: self.syslog = SysLogHandler(address='/dev/log') self.addHandler(self.syslog) - elif self.syslog: - self.removeHandler(self.syslog) logpath = _get_log_file_path() if logpath: self.removeHandler(self.streamlog) diff --git a/nova/scheduler/driver.py b/nova/scheduler/driver.py index 7c79d28c9..af39b2edb 100644 --- a/nova/scheduler/driver.py +++ b/nova/scheduler/driver.py @@ -155,6 +155,9 @@ class Scheduler(object): def create_instance_db_entry(self, context, request_spec): """Create instance DB entry based on request_spec""" base_options = request_spec['instance_properties'] + if base_options.get('id'): + # Instance was already created before calling scheduler + return db.instance_get(context, base_options['id']) image = request_spec['image'] instance_type = request_spec.get('instance_type') security_group = request_spec.get('security_group', 'default') diff --git a/nova/scheduler/simple.py b/nova/scheduler/simple.py index cce1509b8..6768205ad 100644 --- a/nova/scheduler/simple.py +++ b/nova/scheduler/simple.py @@ -23,7 +23,6 @@ Simple Scheduler from nova import db from nova import flags -from nova import utils from nova.scheduler import driver from nova.scheduler import chance @@ -44,9 +43,11 @@ class SimpleScheduler(chance.ChanceScheduler): availability_zone = instance_opts.get('availability_zone') - if availability_zone and context.is_admin and \ - (':' in availability_zone): - zone, host = availability_zone.split(':', 1) + zone, host = None, None + if availability_zone: + zone, _x, host = availability_zone.partition(':') + + if host and context.is_admin: service = db.service_get_by_args(context.elevated(), host, 'nova-compute') if not self.service_is_up(service): @@ -54,6 +55,9 @@ class SimpleScheduler(chance.ChanceScheduler): return host results = db.service_get_all_compute_sorted(context) + if zone: + results = [(service, cores) for (service, cores) in results + if service['availability_zone'] == zone] for result in results: (service, instance_cores) = result if instance_cores + instance_opts['vcpus'] > FLAGS.max_cores: @@ -82,15 +86,17 @@ class SimpleScheduler(chance.ChanceScheduler): host = self._schedule_instance(context, instance_ref, *_args, **_kwargs) driver.cast_to_compute_host(context, host, 'start_instance', - instance_id=intance_id, **_kwargs) + instance_id=instance_id, **_kwargs) def schedule_create_volume(self, context, volume_id, *_args, **_kwargs): """Picks a host that is up and has the fewest volumes.""" volume_ref = db.volume_get(context, volume_id) - if (volume_ref['availability_zone'] - and ':' in volume_ref['availability_zone'] - and context.is_admin): - zone, _x, host = volume_ref['availability_zone'].partition(':') + availability_zone = volume_ref.get('availability_zone') + + zone, host = None, None + if availability_zone: + zone, _x, host = availability_zone.partition(':') + if host and context.is_admin: service = db.service_get_by_args(context.elevated(), host, 'nova-volume') if not self.service_is_up(service): @@ -98,7 +104,11 @@ class SimpleScheduler(chance.ChanceScheduler): driver.cast_to_volume_host(context, host, 'create_volume', volume_id=volume_id, **_kwargs) return None + results = db.service_get_all_volume_sorted(context) + if zone: + results = [(service, gigs) for (service, gigs) in results + if service['availability_zone'] == zone] for result in results: (service, volume_gigabytes) = result if volume_gigabytes + volume_ref['size'] > FLAGS.max_gigabytes: diff --git a/nova/scheduler/zone.py b/nova/scheduler/zone.py deleted file mode 100644 index c369477f8..000000000 --- a/nova/scheduler/zone.py +++ /dev/null @@ -1,77 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2010 Openstack, LLC. -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# 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. - -""" -Availability Zone Scheduler implementation -""" - -import random - -from nova.scheduler import driver -from nova import db - - -class ZoneScheduler(driver.Scheduler): - """Implements Scheduler as a random node selector.""" - - def hosts_up_with_zone(self, context, topic, zone): - """Return the list of hosts that have a running service - for topic and availability zone (if defined). - """ - - if not zone: - return self.hosts_up(context, topic) - - services = db.service_get_all_by_topic(context, topic) - return [service.host - for service in services - if self.service_is_up(service) - and service.availability_zone == zone] - - 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') - 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/scheduler/zone_manager.py b/nova/scheduler/zone_manager.py index 3ccd5015e..f6d895a55 100644 --- a/nova/scheduler/zone_manager.py +++ b/nova/scheduler/zone_manager.py @@ -22,9 +22,8 @@ import thread import traceback import UserDict -from novaclient import v1_1 as novaclient - from eventlet import greenpool +from novaclient import v1_1 as novaclient from nova import db from nova import flags @@ -33,9 +32,13 @@ from nova import utils FLAGS = flags.FLAGS flags.DEFINE_integer('zone_db_check_interval', 60, - 'Seconds between getting fresh zone info from db.') + 'Seconds between getting fresh zone info from db.') flags.DEFINE_integer('zone_failures_to_offline', 3, - 'Number of consecutive errors before marking zone offline') + 'Number of consecutive errors before marking zone offline') +flags.DEFINE_integer('reserved_host_disk_mb', 0, + 'Amount of disk in MB to reserve for host/dom0') +flags.DEFINE_integer('reserved_host_memory_mb', 512, + 'Amount of memory in MB to reserve for host/dom0') class ZoneState(object): @@ -228,17 +231,26 @@ class ZoneManager(object): for compute in compute_nodes: all_disk = compute['local_gb'] all_ram = compute['memory_mb'] - host = compute['service']['host'] + service = compute['service'] + if not service: + LOG.warn(_("No service for compute ID %s") % compute['id']) + continue + host = service['host'] caps = self.service_states.get(host, None) - host_info_map[host] = HostInfo(host, caps=caps, - free_disk_gb=all_disk, - free_ram_mb=all_ram) + host_info = HostInfo(host, caps=caps, + free_disk_gb=all_disk, free_ram_mb=all_ram) + # Reserve resources for host/dom0 + host_info.consume_resources(FLAGS.reserved_host_disk_mb * 1024, + FLAGS.reserved_host_memory_mb) + host_info_map[host] = host_info # "Consume" resources from the host the instance resides on. instances = self._instance_get_all(context) for instance in instances: host = instance['host'] + if not host: + continue host_info = host_info_map.get(host, None) if not host_info: continue diff --git a/nova/tests/api/ec2/test_cloud.py b/nova/tests/api/ec2/test_cloud.py index c0f3d44d7..f76690831 100644 --- a/nova/tests/api/ec2/test_cloud.py +++ b/nova/tests/api/ec2/test_cloud.py @@ -1013,8 +1013,6 @@ class CloudTestCase(test.TestCase): self.assertDictListUnorderedMatch(result['blockDeviceMapping'], self._expected_bdms2, 'deviceName') - self.stubs.UnsetAll() - def test_describe_image_attribute(self): describe_image_attribute = self.cloud.describe_image_attribute @@ -1216,6 +1214,9 @@ class CloudTestCase(test.TestCase): self.stubs.UnsetAll() self.stubs.Set(fake._FakeImageService, 'show', fake_show) + # NOTE(comstud): Make 'cast' behave like a 'call' which will + # ensure that operations complete + self.stubs.Set(rpc, 'cast', rpc.call) result = run_instances(self.context, **kwargs) instance = result['instancesSet'][0] diff --git a/nova/tests/api/openstack/contrib/test_security_groups.py b/nova/tests/api/openstack/contrib/test_security_groups.py index 65e60df7f..f55ce4a55 100644 --- a/nova/tests/api/openstack/contrib/test_security_groups.py +++ b/nova/tests/api/openstack/contrib/test_security_groups.py @@ -30,31 +30,44 @@ from nova.tests.api.openstack import fakes FAKE_UUID = 'a47ae74e-ab08-447f-8eee-ffd43fc46c16' -def _get_create_request_json(body_dict): - req = webob.Request.blank('/v1.1/123/os-security-groups') - req.headers['Content-Type'] = 'application/json' - req.method = 'POST' - req.body = json.dumps(body_dict) - return req +class AttrDict(dict): + def __getattr__(self, k): + return self[k] -def _create_security_group_json(security_group): - body_dict = _create_security_group_request_dict(security_group) - request = _get_create_request_json(body_dict) - response = request.get_response(fakes.wsgi_app()) - return response +def security_group_template(**kwargs): + sg = kwargs.copy() + sg.setdefault('tenant_id', '123') + sg.setdefault('name', 'test') + sg.setdefault('description', 'test-description') + return sg -def _create_security_group_request_dict(security_group): - sg = {} - if security_group is not None: - name = security_group.get('name', None) - description = security_group.get('description', None) - if name: - sg['name'] = security_group['name'] - if description: - sg['description'] = security_group['description'] - return {'security_group': sg} +def security_group_db(security_group, id=None): + attrs = security_group.copy() + if 'tenant_id' in attrs: + attrs['project_id'] = attrs.pop('tenant_id') + if id is not None: + attrs['id'] = id + attrs.setdefault('rules', []) + attrs.setdefault('instances', []) + return AttrDict(attrs) + + +def security_group_rule_template(**kwargs): + rule = kwargs.copy() + rule.setdefault('ip_protocol', 'tcp') + rule.setdefault('from_port', 22) + rule.setdefault('to_port', 22) + rule.setdefault('parent_group_id', 2) + return rule + + +def security_group_rule_db(rule, id=None): + attrs = rule.copy() + if 'ip_protocol' in attrs: + attrs['protocol'] = attrs.pop('ip_protocol') + return AttrDict(attrs) def return_server(context, server_id): @@ -72,13 +85,11 @@ def return_server_by_uuid(context, server_uuid): def return_non_running_server(context, server_id): - return {'id': server_id, 'state': 0x02, - 'host': "localhost"} + return {'id': server_id, 'state': 0x02, 'host': "localhost"} -def return_security_group(context, project_id, group_name): - return {'id': 1, 'name': group_name, "instances": [ - {'id': 1}]} +def return_security_group_by_name(context, project_id, group_name): + return {'id': 1, 'name': group_name, "instances": [{'id': 1}]} def return_security_group_without_instances(context, project_id, group_name): @@ -89,328 +100,251 @@ def return_server_nonexistent(context, server_id): raise exception.InstanceNotFound(instance_id=server_id) +class StubExtensionManager(object): + def register(self, *args): + pass + + class TestSecurityGroups(test.TestCase): def setUp(self): super(TestSecurityGroups, self).setUp() + self.controller = security_groups.SecurityGroupController() + self.manager = security_groups.Security_groups(StubExtensionManager()) + def tearDown(self): super(TestSecurityGroups, self).tearDown() - def _create_security_group_request_dict(self, security_group): - sg = {} - if security_group is not None: - name = security_group.get('name', None) - description = security_group.get('description', None) - if name: - sg['name'] = security_group['name'] - if description: - sg['description'] = security_group['description'] - return {'security_group': sg} - - def _format_create_xml_request_body(self, body_dict): - sg = body_dict['security_group'] - body_parts = [] - body_parts.extend([ - '<?xml version="1.0" encoding="UTF-8"?>', - '<security_group xmlns="http://docs.openstack.org/ext/' - 'securitygroups/api/v1.1"', - ' name="%s">' % (sg['name'])]) - if 'description' in sg: - body_parts.append('<description>%s</description>' - % sg['description']) - body_parts.append('</security_group>') - return ''.join(body_parts) - - def _get_create_request_xml(self, body_dict): - req = webob.Request.blank('/v1.1/123/os-security-groups') - req.headers['Content-Type'] = 'application/xml' - req.content_type = 'application/xml' - req.accept = 'application/xml' - req.method = 'POST' - req.body = self._format_create_xml_request_body(body_dict) - return req - - def _create_security_group_xml(self, security_group): - body_dict = self._create_security_group_request_dict(security_group) - request = self._get_create_request_xml(body_dict) - response = request.get_response(fakes.wsgi_app()) - return response - - def _delete_security_group(self, id): - request = webob.Request.blank('/v1.1/123/os-security-groups/%s' - % id) - request.method = 'DELETE' - response = request.get_response(fakes.wsgi_app()) - return response - - def test_create_security_group_json(self): - security_group = {} - security_group['name'] = "test" - security_group['description'] = "group-description" - response = _create_security_group_json(security_group) - res_dict = json.loads(response.body) - self.assertEqual(res_dict['security_group']['name'], "test") + def test_create_security_group(self): + sg = security_group_template() + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-groups') + res_dict = self.controller.create(req, {'security_group': sg}) + self.assertEqual(res_dict['security_group']['name'], 'test') self.assertEqual(res_dict['security_group']['description'], - "group-description") - self.assertEquals(response.status_int, 200) - - def test_create_security_group_xml(self): - security_group = {} - security_group['name'] = "test" - security_group['description'] = "group-description" - response = \ - self._create_security_group_xml(security_group) - - self.assertEquals(response.status_int, 200) - dom = minidom.parseString(response.body) - sg = dom.childNodes[0] - self.assertEquals(sg.nodeName, 'security_group') - self.assertEqual(security_group['name'], sg.getAttribute('name')) - - def test_create_security_group_with_no_name_json(self): - security_group = {} - security_group['description'] = "group-description" - response = _create_security_group_json(security_group) - self.assertEquals(response.status_int, 400) - - def test_create_security_group_with_no_description_json(self): - security_group = {} - security_group['name'] = "test" - response = _create_security_group_json(security_group) - self.assertEquals(response.status_int, 400) - - def test_create_security_group_with_blank_name_json(self): - security_group = {} - security_group['name'] = "" - security_group['description'] = "group-description" - response = _create_security_group_json(security_group) - self.assertEquals(response.status_int, 400) - - def test_create_security_group_with_whitespace_name_json(self): - security_group = {} - security_group['name'] = " " - security_group['description'] = "group-description" - response = _create_security_group_json(security_group) - self.assertEquals(response.status_int, 400) - - def test_create_security_group_with_blank_description_json(self): - security_group = {} - security_group['name'] = "test" - security_group['description'] = "" - response = _create_security_group_json(security_group) - self.assertEquals(response.status_int, 400) - - def test_create_security_group_with_whitespace_description_json(self): - security_group = {} - security_group['name'] = "name" - security_group['description'] = " " - response = _create_security_group_json(security_group) - self.assertEquals(response.status_int, 400) - - def test_create_security_group_with_duplicate_name_json(self): - security_group = {} - security_group['name'] = "test" - security_group['description'] = "group-description" - response = _create_security_group_json(security_group) - - self.assertEquals(response.status_int, 200) - response = _create_security_group_json(security_group) - self.assertEquals(response.status_int, 400) - - def test_create_security_group_with_no_body_json(self): - request = _get_create_request_json(body_dict=None) - response = request.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 422) + 'test-description') + + def test_create_security_group_with_no_name(self): + sg = security_group_template() + del sg['name'] + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-groups') + self.assertRaises(webob.exc.HTTPUnprocessableEntity, + self.controller.create, req, sg) + + def test_create_security_group_with_no_description(self): + sg = security_group_template() + del sg['description'] + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-groups') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group': sg}) + + def test_create_security_group_with_blank_name(self): + sg = security_group_template(name='') + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-groups') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group': sg}) + + def test_create_security_group_with_whitespace_name(self): + sg = security_group_template(name=' ') + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-groups') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group': sg}) + + def test_create_security_group_with_blank_description(self): + sg = security_group_template(description='') + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-groups') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group': sg}) + + def test_create_security_group_with_whitespace_description(self): + sg = security_group_template(description=' ') + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-groups') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group': sg}) + + def test_create_security_group_with_duplicate_name(self): + sg = security_group_template() + + # FIXME: Stub out _get instead of creating twice + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-groups') + self.controller.create(req, {'security_group': sg}) + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-groups') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group': sg}) + + def test_create_security_group_with_no_body(self): + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-groups') + self.assertRaises(webob.exc.HTTPUnprocessableEntity, + self.controller.create, req, None) def test_create_security_group_with_no_security_group(self): - body_dict = {} - body_dict['no-securityGroup'] = None - request = _get_create_request_json(body_dict) - response = request.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 422) - - def test_create_security_group_above_255_characters_name_json(self): - security_group = {} - security_group['name'] = ("1234567890123456" - "1234567890123456789012345678901234567890" - "1234567890123456789012345678901234567890" - "1234567890123456789012345678901234567890" - "1234567890123456789012345678901234567890" - "1234567890123456789012345678901234567890" - "1234567890123456789012345678901234567890") - security_group['description'] = "group-description" - response = _create_security_group_json(security_group) - - self.assertEquals(response.status_int, 400) - - def test_create_security_group_above_255_characters_description_json(self): - security_group = {} - security_group['name'] = "test" - security_group['description'] = ("1234567890123456" - "1234567890123456789012345678901234567890" - "1234567890123456789012345678901234567890" - "1234567890123456789012345678901234567890" - "1234567890123456789012345678901234567890" - "1234567890123456789012345678901234567890" - "1234567890123456789012345678901234567890") - response = _create_security_group_json(security_group) - self.assertEquals(response.status_int, 400) - - def test_create_security_group_non_string_name_json(self): - security_group = {} - security_group['name'] = 12 - security_group['description'] = "group-description" - response = _create_security_group_json(security_group) - self.assertEquals(response.status_int, 400) - - def test_create_security_group_non_string_description_json(self): - security_group = {} - security_group['name'] = "test" - security_group['description'] = 12 - response = _create_security_group_json(security_group) - self.assertEquals(response.status_int, 400) + body = {'no-securityGroup': None} + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-groups') + self.assertRaises(webob.exc.HTTPUnprocessableEntity, + self.controller.create, req, body) + + def test_create_security_group_above_255_characters_name(self): + sg = security_group_template(name='1234567890' * 26) + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-groups') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group': sg}) + + def test_create_security_group_above_255_characters_description(self): + sg = security_group_template(description='1234567890' * 26) + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-groups') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group': sg}) + + def test_create_security_group_non_string_name(self): + sg = security_group_template(name=12) + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-groups') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group': sg}) + + def test_create_security_group_non_string_description(self): + sg = security_group_template(description=12) + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-groups') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group': sg}) def test_get_security_group_list(self): - security_group = {} - security_group['name'] = "test" - security_group['description'] = "group-description" - response = _create_security_group_json(security_group) - - req = webob.Request.blank('/v1.1/123/os-security-groups') - req.headers['Content-Type'] = 'application/json' - req.method = 'GET' - response = req.get_response(fakes.wsgi_app()) - res_dict = json.loads(response.body) - - expected = {'security_groups': [ - {'id': 1, - 'name':"default", - 'tenant_id': "123", - "description":"default", - "rules": [] - }, - ] - } - expected['security_groups'].append( - { - 'id': 2, - 'name': "test", - 'tenant_id': "123", - "description": "group-description", - "rules": [] - } - ) - self.assertEquals(response.status_int, 200) + groups = [] + for i, name in enumerate(['default', 'test']): + sg = security_group_template(id=i + 1, + name=name, + description=name + '-desc', + rules=[]) + groups.append(sg) + expected = {'security_groups': groups} + + def return_security_groups(context, project_id): + return [security_group_db(sg) for sg in groups] + + self.stubs.Set(nova.db, 'security_group_get_by_project', + return_security_groups) + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-groups') + res_dict = self.controller.index(req) + self.assertEquals(res_dict, expected) def test_get_security_group_by_id(self): - security_group = {} - security_group['name'] = "test" - security_group['description'] = "group-description" - response = _create_security_group_json(security_group) - - res_dict = json.loads(response.body) - req = webob.Request.blank('/v1.1/123/os-security-groups/%s' % - res_dict['security_group']['id']) - req.headers['Content-Type'] = 'application/json' - req.method = 'GET' - response = req.get_response(fakes.wsgi_app()) - res_dict = json.loads(response.body) + sg = security_group_template(id=2, rules=[]) - expected = { - 'security_group': { - 'id': 2, - 'name': "test", - 'tenant_id': "123", - 'description': "group-description", - 'rules': [] - } - } + def return_security_group(context, group_id): + self.assertEquals(sg['id'], group_id) + return security_group_db(sg) + + self.stubs.Set(nova.db, 'security_group_get', + return_security_group) + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-groups/2') + res_dict = self.controller.show(req, '2') + + expected = {'security_group': sg} self.assertEquals(res_dict, expected) def test_get_security_group_by_invalid_id(self): - req = webob.Request.blank('/v1.1/123/os-security-groups/invalid') - req.headers['Content-Type'] = 'application/json' - req.method = 'GET' - response = req.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 400) + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-groups/invalid') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.delete, + req, 'invalid') def test_get_security_group_by_non_existing_id(self): - req = webob.Request.blank('/v1.1/123/os-security-groups/111111111') - req.headers['Content-Type'] = 'application/json' - req.method = 'GET' - response = req.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 404) + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-groups/111111111') + self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete, + req, '111111111') def test_delete_security_group_by_id(self): - security_group = {} - security_group['name'] = "test" - security_group['description'] = "group-description" - response = _create_security_group_json(security_group) - security_group = json.loads(response.body)['security_group'] - response = self._delete_security_group(security_group['id']) - self.assertEquals(response.status_int, 202) + sg = security_group_template(id=1, rules=[]) + + self.called = False + + def security_group_destroy(context, id): + self.called = True + + def return_security_group(context, group_id): + self.assertEquals(sg['id'], group_id) + return security_group_db(sg) + + self.stubs.Set(nova.db, 'security_group_destroy', + security_group_destroy) + self.stubs.Set(nova.db, 'security_group_get', + return_security_group) + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-groups/1') + self.controller.delete(req, '1') - response = self._delete_security_group(security_group['id']) - self.assertEquals(response.status_int, 404) + self.assertTrue(self.called) def test_delete_security_group_by_invalid_id(self): - response = self._delete_security_group('invalid') - self.assertEquals(response.status_int, 400) + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-groups/invalid') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.delete, + req, 'invalid') def test_delete_security_group_by_non_existing_id(self): - response = self._delete_security_group(11111111) - self.assertEquals(response.status_int, 404) + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-groups/11111111') + self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete, + req, '11111111') def test_associate_by_non_existing_security_group_name(self): body = dict(addSecurityGroup=dict(name='non-existing')) - req = webob.Request.blank('/v1.1/123/servers/%s/action' % FAKE_UUID) - req.headers['Content-Type'] = 'application/json' - req.method = 'POST' - req.body = json.dumps(body) - response = req.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 404) + + req = fakes.HTTPRequest.blank('/v1.1/123/servers/1/action') + self.assertRaises(webob.exc.HTTPNotFound, + self.manager._addSecurityGroup, body, req, '1') + + def test_associate_by_invalid_server_id(self): + body = dict(addSecurityGroup=dict(name='test')) + + req = fakes.HTTPRequest.blank('/v1.1/123/servers/invalid/action') + self.assertRaises(webob.exc.HTTPNotFound, + self.manager._addSecurityGroup, body, req, 'invalid') def test_associate_without_body(self): - req = webob.Request.blank('/v1.1/123/servers/%s/action' % FAKE_UUID) + self.stubs.Set(nova.db, 'instance_get', return_server) body = dict(addSecurityGroup=None) - req.headers['Content-Type'] = 'application/json' - req.method = 'POST' - req.body = json.dumps(body) - response = req.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 400) + + req = fakes.HTTPRequest.blank('/v1.1/123/servers/1/action') + self.assertRaises(webob.exc.HTTPBadRequest, + self.manager._addSecurityGroup, body, req, '1') def test_associate_no_security_group_name(self): - req = webob.Request.blank('/v1.1/123/servers/%s/action' % FAKE_UUID) + self.stubs.Set(nova.db, 'instance_get', return_server) body = dict(addSecurityGroup=dict()) - req.headers['Content-Type'] = 'application/json' - req.method = 'POST' - req.body = json.dumps(body) - response = req.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 400) + + req = fakes.HTTPRequest.blank('/v1.1/123/servers/1/action') + self.assertRaises(webob.exc.HTTPBadRequest, + self.manager._addSecurityGroup, body, req, '1') def test_associate_security_group_name_with_whitespaces(self): - req = webob.Request.blank('/v1.1/123/servers/%s/action' % FAKE_UUID) + self.stubs.Set(nova.db, 'instance_get', return_server) body = dict(addSecurityGroup=dict(name=" ")) - req.headers['Content-Type'] = 'application/json' - req.method = 'POST' - req.body = json.dumps(body) - response = req.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 400) + + req = fakes.HTTPRequest.blank('/v1.1/123/servers/1/action') + self.assertRaises(webob.exc.HTTPBadRequest, + self.manager._addSecurityGroup, body, req, '1') def test_associate_non_existing_instance(self): self.stubs.Set(nova.db, 'instance_get', return_server_nonexistent) self.stubs.Set(nova.db, 'instance_get_by_uuid', return_server_nonexistent) body = dict(addSecurityGroup=dict(name="test")) - self.stubs.Set(nova.db, 'security_group_get_by_name', - return_security_group) - req = webob.Request.blank('/v1.1/123/servers/%s/action' % FAKE_UUID) - req.headers['Content-Type'] = 'application/json' - req.method = 'POST' - req.body = json.dumps(body) - response = req.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 404) + + req = fakes.HTTPRequest.blank('/v1.1/123/servers/1/action') + self.assertRaises(webob.exc.HTTPNotFound, + self.manager._addSecurityGroup, body, req, '1') def test_associate_non_running_instance(self): self.stubs.Set(nova.db, 'instance_get', return_non_running_server) @@ -419,26 +353,22 @@ class TestSecurityGroups(test.TestCase): self.stubs.Set(nova.db, 'security_group_get_by_name', return_security_group_without_instances) body = dict(addSecurityGroup=dict(name="test")) - req = webob.Request.blank('/v1.1/123/servers/%s/action' % FAKE_UUID) - req.headers['Content-Type'] = 'application/json' - req.method = 'POST' - req.body = json.dumps(body) - response = req.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 400) + + req = fakes.HTTPRequest.blank('/v1.1/123/servers/1/action') + self.assertRaises(webob.exc.HTTPBadRequest, + self.manager._addSecurityGroup, body, req, '1') def test_associate_already_associated_security_group_to_instance(self): self.stubs.Set(nova.db, 'instance_get', return_server) self.stubs.Set(nova.db, 'instance_get_by_uuid', return_server_by_uuid) self.stubs.Set(nova.db, 'security_group_get_by_name', - return_security_group) + return_security_group_by_name) body = dict(addSecurityGroup=dict(name="test")) - req = webob.Request.blank('/v1.1/123/servers/%s/action' % FAKE_UUID) - req.headers['Content-Type'] = 'application/json' - req.method = 'POST' - req.body = json.dumps(body) - response = req.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 400) + + req = fakes.HTTPRequest.blank('/v1.1/123/servers/1/action') + self.assertRaises(webob.exc.HTTPBadRequest, + self.manager._addSecurityGroup, body, req, '1') def test_associate(self): self.stubs.Set(nova.db, 'instance_get', return_server) @@ -446,104 +376,79 @@ class TestSecurityGroups(test.TestCase): return_server_by_uuid) self.mox.StubOutWithMock(nova.db, 'instance_add_security_group') nova.db.instance_add_security_group(mox.IgnoreArg(), - mox.IgnoreArg(), - mox.IgnoreArg()) + mox.IgnoreArg(), + mox.IgnoreArg()) self.stubs.Set(nova.db, 'security_group_get_by_name', return_security_group_without_instances) self.mox.ReplayAll() body = dict(addSecurityGroup=dict(name="test")) - req = webob.Request.blank('/v1.1/123/servers/%s/action' % FAKE_UUID) - req.headers['Content-Type'] = 'application/json' - req.method = 'POST' - req.body = json.dumps(body) - response = req.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 202) - - def test_associate_xml(self): - self.stubs.Set(nova.db, 'instance_get', return_server) - self.stubs.Set(nova.db, 'instance_get_by_uuid', - return_server_by_uuid) - self.mox.StubOutWithMock(nova.db, 'instance_add_security_group') - nova.db.instance_add_security_group(mox.IgnoreArg(), - mox.IgnoreArg(), - mox.IgnoreArg()) - self.stubs.Set(nova.db, 'security_group_get_by_name', - return_security_group_without_instances) - self.mox.ReplayAll() - req = webob.Request.blank('/v1.1/123/servers/%s/action' % FAKE_UUID) - req.headers['Content-Type'] = 'application/xml' - req.method = 'POST' - req.body = """<addSecurityGroup> - <name>test</name> - </addSecurityGroup>""" - response = req.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 202) + req = fakes.HTTPRequest.blank('/v1.1/123/servers/1/action') + self.manager._addSecurityGroup(body, req, '1') def test_disassociate_by_non_existing_security_group_name(self): body = dict(removeSecurityGroup=dict(name='non-existing')) - req = webob.Request.blank('/v1.1/123/servers/%s/action' % FAKE_UUID) - req.headers['Content-Type'] = 'application/json' - req.method = 'POST' - req.body = json.dumps(body) - response = req.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 404) + + req = fakes.HTTPRequest.blank('/v1.1/123/servers/1/action') + self.assertRaises(webob.exc.HTTPNotFound, + self.manager._removeSecurityGroup, body, req, '1') + + def test_disassociate_by_invalid_server_id(self): + self.stubs.Set(nova.db, 'security_group_get_by_name', + return_security_group_by_name) + body = dict(removeSecurityGroup=dict(name='test')) + + req = fakes.HTTPRequest.blank('/v1.1/123/servers/invalid/action') + self.assertRaises(webob.exc.HTTPNotFound, + self.manager._removeSecurityGroup, body, req, + 'invalid') def test_disassociate_without_body(self): - req = webob.Request.blank('/v1.1/123/servers/%s/action' % FAKE_UUID) + self.stubs.Set(nova.db, 'instance_get', return_server) body = dict(removeSecurityGroup=None) - req.headers['Content-Type'] = 'application/json' - req.method = 'POST' - req.body = json.dumps(body) - response = req.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 400) + + req = fakes.HTTPRequest.blank('/v1.1/123/servers/1/action') + self.assertRaises(webob.exc.HTTPBadRequest, + self.manager._removeSecurityGroup, body, req, '1') def test_disassociate_no_security_group_name(self): - req = webob.Request.blank('/v1.1/123/servers/%s/action' % FAKE_UUID) + self.stubs.Set(nova.db, 'instance_get', return_server) body = dict(removeSecurityGroup=dict()) - req.headers['Content-Type'] = 'application/json' - req.method = 'POST' - req.body = json.dumps(body) - response = req.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 400) + + req = fakes.HTTPRequest.blank('/v1.1/123/servers/1/action') + self.assertRaises(webob.exc.HTTPBadRequest, + self.manager._removeSecurityGroup, body, req, '1') def test_disassociate_security_group_name_with_whitespaces(self): - req = webob.Request.blank('/v1.1/123/servers/%s/action' % FAKE_UUID) + self.stubs.Set(nova.db, 'instance_get', return_server) body = dict(removeSecurityGroup=dict(name=" ")) - req.headers['Content-Type'] = 'application/json' - req.method = 'POST' - req.body = json.dumps(body) - response = req.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 400) + + req = fakes.HTTPRequest.blank('/v1.1/123/servers/1/action') + self.assertRaises(webob.exc.HTTPBadRequest, + self.manager._removeSecurityGroup, body, req, '1') def test_disassociate_non_existing_instance(self): self.stubs.Set(nova.db, 'instance_get', return_server_nonexistent) - self.stubs.Set(nova.db, 'instance_get_by_uuid', - return_server_nonexistent) - body = dict(removeSecurityGroup=dict(name="test")) self.stubs.Set(nova.db, 'security_group_get_by_name', - return_security_group) - req = webob.Request.blank('/v1.1/123/servers/%s/action' % FAKE_UUID) - req.headers['Content-Type'] = 'application/json' - req.method = 'POST' - req.body = json.dumps(body) - response = req.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 404) + return_security_group_by_name) + body = dict(removeSecurityGroup=dict(name="test")) + + req = fakes.HTTPRequest.blank('/v1.1/123/servers/1/action') + self.assertRaises(webob.exc.HTTPNotFound, + self.manager._removeSecurityGroup, body, req, '1') def test_disassociate_non_running_instance(self): self.stubs.Set(nova.db, 'instance_get', return_non_running_server) self.stubs.Set(nova.db, 'instance_get_by_uuid', return_non_running_server) self.stubs.Set(nova.db, 'security_group_get_by_name', - return_security_group) + return_security_group_by_name) body = dict(removeSecurityGroup=dict(name="test")) - req = webob.Request.blank('/v1.1/123/servers/%s/action' % FAKE_UUID) - req.headers['Content-Type'] = 'application/json' - req.method = 'POST' - req.body = json.dumps(body) - response = req.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 400) + + req = fakes.HTTPRequest.blank('/v1.1/123/servers/1/action') + self.assertRaises(webob.exc.HTTPBadRequest, + self.manager._removeSecurityGroup, body, req, '1') def test_disassociate_already_associated_security_group_to_instance(self): self.stubs.Set(nova.db, 'instance_get', return_server) @@ -552,12 +457,10 @@ class TestSecurityGroups(test.TestCase): self.stubs.Set(nova.db, 'security_group_get_by_name', return_security_group_without_instances) body = dict(removeSecurityGroup=dict(name="test")) - req = webob.Request.blank('/v1.1/123/servers/%s/action' % FAKE_UUID) - req.headers['Content-Type'] = 'application/json' - req.method = 'POST' - req.body = json.dumps(body) - response = req.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 400) + + req = fakes.HTTPRequest.blank('/v1.1/123/servers/1/action') + self.assertRaises(webob.exc.HTTPBadRequest, + self.manager._removeSecurityGroup, body, req, '1') def test_disassociate(self): self.stubs.Set(nova.db, 'instance_get', return_server) @@ -568,377 +471,242 @@ class TestSecurityGroups(test.TestCase): mox.IgnoreArg(), mox.IgnoreArg()) self.stubs.Set(nova.db, 'security_group_get_by_name', - return_security_group) + return_security_group_by_name) self.mox.ReplayAll() body = dict(removeSecurityGroup=dict(name="test")) - req = webob.Request.blank('/v1.1/123/servers/%s/action' % FAKE_UUID) - req.headers['Content-Type'] = 'application/json' - req.method = 'POST' - req.body = json.dumps(body) - response = req.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 202) - - def test_disassociate_xml(self): - self.stubs.Set(nova.db, 'instance_get', return_server) - self.stubs.Set(nova.db, 'instance_get_by_uuid', - return_server_by_uuid) - self.mox.StubOutWithMock(nova.db, 'instance_remove_security_group') - nova.db.instance_remove_security_group(mox.IgnoreArg(), - mox.IgnoreArg(), - mox.IgnoreArg()) - self.stubs.Set(nova.db, 'security_group_get_by_name', - return_security_group) - self.mox.ReplayAll() - req = webob.Request.blank('/v1.1/123/servers/%s/action' % FAKE_UUID) - req.headers['Content-Type'] = 'application/xml' - req.method = 'POST' - req.body = """<removeSecurityGroup> - <name>test</name> - </removeSecurityGroup>""" - response = req.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 202) + req = fakes.HTTPRequest.blank('/v1.1/123/servers/1/action') + self.manager._removeSecurityGroup(body, req, '1') class TestSecurityGroupRules(test.TestCase): def setUp(self): super(TestSecurityGroupRules, self).setUp() - security_group = {} - security_group['name'] = "authorize-revoke" - security_group['description'] = ("Security group created for " - " authorize-revoke testing") - response = _create_security_group_json(security_group) - security_group = json.loads(response.body) - self.parent_security_group = security_group['security_group'] - - rules = { - "security_group_rule": { - "ip_protocol": "tcp", - "from_port": "22", - "to_port": "22", - "parent_group_id": self.parent_security_group['id'], - "cidr": "10.0.0.0/24" - } - } - res = self._create_security_group_rule_json(rules) - self.assertEquals(res.status_int, 200) - self.security_group_rule = json.loads(res.body)['security_group_rule'] + + controller = security_groups.SecurityGroupController() + + sg1 = security_group_template(id=1) + sg2 = security_group_template(id=2, + name='authorize_revoke', + description='authorize-revoke testing') + db1 = security_group_db(sg1) + db2 = security_group_db(sg2) + + def return_security_group(context, group_id): + if group_id == db1['id']: + return db1 + if group_id == db2['id']: + return db2 + raise exception.NotFound() + + self.stubs.Set(nova.db, 'security_group_get', + return_security_group) + + self.parent_security_group = db2 + + self.controller = security_groups.SecurityGroupRulesController() def tearDown(self): super(TestSecurityGroupRules, self).tearDown() - def _create_security_group_rule_json(self, rules): - request = webob.Request.blank('/v1.1/123/os-security-group-rules') - request.headers['Content-Type'] = 'application/json' - request.method = 'POST' - request.body = json.dumps(rules) - response = request.get_response(fakes.wsgi_app()) - return response - - def _delete_security_group_rule(self, id): - request = webob.Request.blank('/v1.1/123/os-security-group-rules/%s' - % id) - request.method = 'DELETE' - response = request.get_response(fakes.wsgi_app()) - return response - - def test_create_by_cidr_json(self): - rules = { - "security_group_rule": { - "ip_protocol": "tcp", - "from_port": "22", - "to_port": "22", - "parent_group_id": 2, - "cidr": "10.2.3.124/24" - } - } - - response = self._create_security_group_rule_json(rules) - security_group_rule = json.loads(response.body)['security_group_rule'] - self.assertEquals(response.status_int, 200) + def test_create_by_cidr(self): + rule = security_group_rule_template(cidr='10.2.3.124/24') + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-group-rules') + res_dict = self.controller.create(req, {'security_group_rule': rule}) + + security_group_rule = res_dict['security_group_rule'] self.assertNotEquals(security_group_rule['id'], 0) self.assertEquals(security_group_rule['parent_group_id'], 2) self.assertEquals(security_group_rule['ip_range']['cidr'], "10.2.3.124/24") - def test_create_by_group_id_json(self): - rules = { - "security_group_rule": { - "ip_protocol": "tcp", - "from_port": "22", - "to_port": "22", - "group_id": "1", - "parent_group_id": "%s" - % self.parent_security_group['id'], - } - } - - response = self._create_security_group_rule_json(rules) - self.assertEquals(response.status_int, 200) - security_group_rule = json.loads(response.body)['security_group_rule'] + def test_create_by_group_id(self): + rule = security_group_rule_template(group_id='1') + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-group-rules') + res_dict = self.controller.create(req, {'security_group_rule': rule}) + + security_group_rule = res_dict['security_group_rule'] self.assertNotEquals(security_group_rule['id'], 0) self.assertEquals(security_group_rule['parent_group_id'], 2) - def test_create_add_existing_rules_json(self): - rules = { - "security_group_rule": { - "ip_protocol": "tcp", - "from_port": "22", - "to_port": "22", - "cidr": "10.0.0.0/24", - "parent_group_id": "%s" % self.parent_security_group['id'], - } - } - - response = self._create_security_group_rule_json(rules) - self.assertEquals(response.status_int, 400) - - def test_create_with_no_body_json(self): - request = webob.Request.blank('/v1.1/123/os-security-group-rules') - request.headers['Content-Type'] = 'application/json' - request.method = 'POST' - request.body = json.dumps(None) - response = request.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 422) - - def test_create_with_no_security_group_rule_in_body_json(self): - request = webob.Request.blank('/v1.1/123/os-security-group-rules') - request.headers['Content-Type'] = 'application/json' - request.method = 'POST' - body_dict = {'test': "test"} - request.body = json.dumps(body_dict) - response = request.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 422) - - def test_create_with_invalid_parent_group_id_json(self): - rules = { - "security_group_rule": { - "ip_protocol": "tcp", - "from_port": "22", - "to_port": "22", - "parent_group_id": "invalid" - } - } - - response = self._create_security_group_rule_json(rules) - self.assertEquals(response.status_int, 400) - - def test_create_with_non_existing_parent_group_id_json(self): - rules = { - "security_group_rule": { - "ip_protocol": "tcp", - "from_port": "22", - "to_port": "22", - "group_id": "invalid", - "parent_group_id": "1111111111111" - } - } - - response = self._create_security_group_rule_json(rules) - self.assertEquals(response.status_int, 404) - - def test_create_with_invalid_protocol_json(self): - rules = { - "security_group_rule": { - "ip_protocol": "invalid-protocol", - "from_port": "22", - "to_port": "22", - "cidr": "10.2.2.0/24", - "parent_group_id": "%s" % self.parent_security_group['id'], - } - } - - response = self._create_security_group_rule_json(rules) - self.assertEquals(response.status_int, 400) - - def test_create_with_no_protocol_json(self): - rules = { - "security_group_rule": { - "from_port": "22", - "to_port": "22", - "cidr": "10.2.2.0/24", - "parent_group_id": "%s" % self.parent_security_group['id'], - } - } - - response = self._create_security_group_rule_json(rules) - self.assertEquals(response.status_int, 400) - - def test_create_with_invalid_from_port_json(self): - rules = { - "security_group_rule": { - "ip_protocol": "tcp", - "from_port": "666666", - "to_port": "22", - "cidr": "10.2.2.0/24", - "parent_group_id": "%s" % self.parent_security_group['id'], - } - } - - response = self._create_security_group_rule_json(rules) - self.assertEquals(response.status_int, 400) - - def test_create_with_invalid_to_port_json(self): - rules = { - "security_group_rule": { - "ip_protocol": "tcp", - "from_port": "22", - "to_port": "666666", - "cidr": "10.2.2.0/24", - "parent_group_id": "%s" % self.parent_security_group['id'], - } - } - - response = self._create_security_group_rule_json(rules) - self.assertEquals(response.status_int, 400) - - def test_create_with_non_numerical_from_port_json(self): - rules = { - "security_group_rule": { - "ip_protocol": "tcp", - "from_port": "invalid", - "to_port": "22", - "cidr": "10.2.2.0/24", - "parent_group_id": "%s" % self.parent_security_group['id'], - } - } - - response = self._create_security_group_rule_json(rules) - self.assertEquals(response.status_int, 400) - - def test_create_with_non_numerical_to_port_json(self): - rules = { - "security_group_rule": { - "ip_protocol": "tcp", - "from_port": "22", - "to_port": "invalid", - "cidr": "10.2.2.0/24", - "parent_group_id": "%s" % self.parent_security_group['id'], - } - } - - response = self._create_security_group_rule_json(rules) - self.assertEquals(response.status_int, 400) - - def test_create_with_no_to_port_json(self): - rules = { - "security_group_rule": { - "ip_protocol": "tcp", - "from_port": "22", - "cidr": "10.2.2.0/24", - "parent_group_id": "%s" % self.parent_security_group['id'], - } - } - - response = self._create_security_group_rule_json(rules) - self.assertEquals(response.status_int, 400) - - def test_create_with_invalid_cidr_json(self): - rules = { - "security_group_rule": { - "ip_protocol": "tcp", - "from_port": "22", - "to_port": "22", - "cidr": "10.2.22222.0/24", - "parent_group_id": "%s" % self.parent_security_group['id'], - } - } - - response = self._create_security_group_rule_json(rules) - self.assertEquals(response.status_int, 400) - - def test_create_with_no_cidr_group_json(self): - rules = { - "security_group_rule": { - "ip_protocol": "tcp", - "from_port": "22", - "to_port": "22", - "parent_group_id": "%s" % self.parent_security_group['id'], - } - } - - response = self._create_security_group_rule_json(rules) - security_group_rule = json.loads(response.body)['security_group_rule'] - self.assertEquals(response.status_int, 200) + def test_create_add_existing_rules(self): + rule = security_group_rule_template(cidr='10.0.0.0/24') + + self.parent_security_group['rules'] = [security_group_rule_db(rule)] + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-group-rules') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_rule': rule}) + + def test_create_with_no_body(self): + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-group-rules') + self.assertRaises(webob.exc.HTTPUnprocessableEntity, + self.controller.create, req, None) + + def test_create_with_no_security_group_rule_in_body(self): + rules = {'test': 'test'} + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-group-rules') + self.assertRaises(webob.exc.HTTPUnprocessableEntity, + self.controller.create, req, rules) + + def test_create_with_invalid_parent_group_id(self): + rule = security_group_rule_template(parent_group_id='invalid') + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-group-rules') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_rule': rule}) + + def test_create_with_non_existing_parent_group_id(self): + rule = security_group_rule_template(group_id='invalid', + parent_group_id='1111111111111') + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-group-rules') + self.assertRaises(webob.exc.HTTPNotFound, self.controller.create, + req, {'security_group_rule': rule}) + + def test_create_with_invalid_protocol(self): + rule = security_group_rule_template(ip_protocol='invalid-protocol', + cidr='10.2.2.0/24') + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-group-rules') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_rule': rule}) + + def test_create_with_no_protocol(self): + rule = security_group_rule_template(cidr='10.2.2.0/24') + del rule['ip_protocol'] + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-group-rules') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_rule': rule}) + + def test_create_with_invalid_from_port(self): + rule = security_group_rule_template(from_port='666666', + cidr='10.2.2.0/24') + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-group-rules') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_rule': rule}) + + def test_create_with_invalid_to_port(self): + rule = security_group_rule_template(to_port='666666', + cidr='10.2.2.0/24') + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-group-rules') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_rule': rule}) + + def test_create_with_non_numerical_from_port(self): + rule = security_group_rule_template(from_port='invalid', + cidr='10.2.2.0/24') + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-group-rules') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_rule': rule}) + + def test_create_with_non_numerical_to_port(self): + rule = security_group_rule_template(to_port='invalid', + cidr='10.2.2.0/24') + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-group-rules') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_rule': rule}) + + def test_create_with_no_from_port(self): + rule = security_group_rule_template(cidr='10.2.2.0/24') + del rule['from_port'] + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-group-rules') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_rule': rule}) + + def test_create_with_no_to_port(self): + rule = security_group_rule_template(cidr='10.2.2.0/24') + del rule['to_port'] + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-group-rules') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_rule': rule}) + + def test_create_with_invalid_cidr(self): + rule = security_group_rule_template(cidr='10.2.2222.0/24') + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-group-rules') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_rule': rule}) + + def test_create_with_no_cidr_group(self): + rule = security_group_rule_template() + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-group-rules') + res_dict = self.controller.create(req, {'security_group_rule': rule}) + + security_group_rule = res_dict['security_group_rule'] self.assertNotEquals(security_group_rule['id'], 0) self.assertEquals(security_group_rule['parent_group_id'], self.parent_security_group['id']) self.assertEquals(security_group_rule['ip_range']['cidr'], "0.0.0.0/0") - def test_create_with_invalid_group_id_json(self): - rules = { - "security_group_rule": { - "ip_protocol": "tcp", - "from_port": "22", - "to_port": "22", - "group_id": "invalid", - "parent_group_id": "%s" % self.parent_security_group['id'], - } - } - - response = self._create_security_group_rule_json(rules) - self.assertEquals(response.status_int, 400) - - def test_create_with_empty_group_id_json(self): - rules = { - "security_group_rule": { - "ip_protocol": "tcp", - "from_port": "22", - "to_port": "22", - "group_id": "invalid", - "parent_group_id": "%s" % self.parent_security_group['id'], - } - } - - response = self._create_security_group_rule_json(rules) - self.assertEquals(response.status_int, 400) - - def test_create_with_invalid_group_id_json(self): - rules = { - "security_group_rule": { - "ip_protocol": "tcp", - "from_port": "22", - "to_port": "22", - "group_id": "222222", - "parent_group_id": "%s" % self.parent_security_group['id'], - } - } - - response = self._create_security_group_rule_json(rules) - self.assertEquals(response.status_int, 400) - - def test_create_rule_with_same_group_parent_id_json(self): - rules = { - "security_group_rule": { - "ip_protocol": "tcp", - "from_port": "22", - "to_port": "22", - "group_id": "%s" % self.parent_security_group['id'], - "parent_group_id": "%s" % self.parent_security_group['id'], - } - } - - response = self._create_security_group_rule_json(rules) - self.assertEquals(response.status_int, 400) + def test_create_with_invalid_group_id(self): + rule = security_group_rule_template(group_id='invalid') + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-group-rules') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_rule': rule}) + + def test_create_with_empty_group_id(self): + rule = security_group_rule_template(group_id='') + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-group-rules') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_rule': rule}) + + def test_create_with_nonexist_group_id(self): + rule = security_group_rule_template(group_id='222222') + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-group-rules') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_rule': rule}) + + def test_create_rule_with_same_group_parent_id(self): + rule = security_group_rule_template(group_id=2) + + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-group-rules') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, {'security_group_rule': rule}) def test_delete(self): - response = self._delete_security_group_rule( - self.security_group_rule['id']) - self.assertEquals(response.status_int, 202) + rule = security_group_rule_template(id=10) + + def security_group_rule_get(context, id): + return security_group_rule_db(rule) + + def security_group_rule_destroy(context, id): + pass + + self.stubs.Set(nova.db, 'security_group_rule_get', + security_group_rule_get) + self.stubs.Set(nova.db, 'security_group_rule_destroy', + security_group_rule_destroy) - response = self._delete_security_group_rule( - self.security_group_rule['id']) - self.assertEquals(response.status_int, 404) + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-group-rules/10') + self.controller.delete(req, '10') def test_delete_invalid_rule_id(self): - response = self._delete_security_group_rule('invalid') - self.assertEquals(response.status_int, 400) + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-group-rules' + + '/invalid') + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.delete, + req, 'invalid') def test_delete_non_existing_rule_id(self): - response = self._delete_security_group_rule(22222222222222) - self.assertEquals(response.status_int, 404) + req = fakes.HTTPRequest.blank('/v1.1/123/os-security-group-rules' + + '/22222222222222') + self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete, + req, '22222222222222') class TestSecurityGroupRulesXMLDeserializer(unittest.TestCase): diff --git a/nova/tests/api/openstack/contrib/test_volumes.py b/nova/tests/api/openstack/contrib/test_volumes.py index 0a3023e48..a130d1140 100644 --- a/nova/tests/api/openstack/contrib/test_volumes.py +++ b/nova/tests/api/openstack/contrib/test_volumes.py @@ -81,9 +81,6 @@ class BootFromVolumeTest(test.TestCase): self.assertEqual(res.status_int, 200) server = json.loads(res.body)['server'] self.assertEqual(FAKE_UUID, server['id']) - self.assertEqual(2, int(server['flavor']['id'])) - self.assertEqual(u'test_server', server['name']) - self.assertEqual(IMAGE_UUID, 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) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index e6e528535..31c87e630 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1354,7 +1354,7 @@ class ServersControllerCreateTest(test.TestCase): self.instance_cache_num += 1 instance = { 'id': self.instance_cache_num, - 'display_name': 'server_test', + 'display_name': inst['display_name'] or 'test', 'uuid': FAKE_UUID, 'instance_type': dict(inst_type), 'access_ip_v4': '1.2.3.4', @@ -1390,8 +1390,10 @@ class ServersControllerCreateTest(test.TestCase): request_spec['instance_properties'])) return instances - def server_update(context, id, params): - return instance_create(context, id) + def server_update(context, instance_id, params): + inst = self.instance_cache[instance_id] + inst.update(params) + return inst def fake_method(*args, **kwargs): pass @@ -1441,10 +1443,7 @@ class ServersControllerCreateTest(test.TestCase): server = self.controller.create(req, body)['server'] self.assertEqual(FLAGS.password_length, len(server['adminPass'])) - self.assertEqual('server_test', server['name']) self.assertEqual(FAKE_UUID, server['id']) - self.assertEqual('2', server['flavor']['id']) - self.assertEqual(image_uuid, server['image']['id']) def test_create_multiple_instances(self): """Test creating multiple instances but not asking for @@ -1613,12 +1612,6 @@ class ServersControllerCreateTest(test.TestCase): server = res['server'] self.assertEqual(FLAGS.password_length, len(server['adminPass'])) self.assertEqual(FAKE_UUID, server['id']) - self.assertEqual(0, server['progress']) - self.assertEqual('server_test', server['name']) - self.assertEqual(expected_flavor, server['flavor']) - self.assertEqual(expected_image, server['image']) - self.assertEqual(access_ipv4, server['accessIPv4']) - self.assertEqual(access_ipv6, server['accessIPv6']) def test_create_instance(self): # proper local hrefs must start with 'http://localhost/v1.1/' @@ -1670,13 +1663,6 @@ class ServersControllerCreateTest(test.TestCase): server = res['server'] self.assertEqual(FLAGS.password_length, len(server['adminPass'])) self.assertEqual(FAKE_UUID, server['id']) - self.assertEqual("BUILD", server["status"]) - self.assertEqual(0, server['progress']) - self.assertEqual('server_test', server['name']) - self.assertEqual(expected_flavor, server['flavor']) - self.assertEqual(expected_image, server['image']) - self.assertEqual('1.2.3.4', server['accessIPv4']) - self.assertEqual('fead::1234', server['accessIPv6']) def test_create_instance_invalid_key_name(self): image_href = 'http://localhost/v1.1/images/2' @@ -1777,7 +1763,6 @@ class ServersControllerCreateTest(test.TestCase): server = res['server'] self.assertEqual(FAKE_UUID, server['id']) - self.assertTrue(server['config_drive']) def test_create_instance_with_config_drive_as_id(self): self.config_drive = 2 @@ -1805,8 +1790,6 @@ class ServersControllerCreateTest(test.TestCase): server = res['server'] self.assertEqual(FAKE_UUID, server['id']) - self.assertTrue(server['config_drive']) - self.assertEqual(2, server['config_drive']) def test_create_instance_with_bad_config_drive(self): self.config_drive = "asdf" @@ -1859,7 +1842,6 @@ class ServersControllerCreateTest(test.TestCase): server = res['server'] self.assertEqual(FAKE_UUID, server['id']) - self.assertFalse(server['config_drive']) def test_create_instance_bad_href(self): image_href = 'asdf' @@ -1879,24 +1861,6 @@ class ServersControllerCreateTest(test.TestCase): def test_create_instance_local_href(self): image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6' flavor_ref = 'http://localhost/v1.1/flavors/3' - expected_flavor = { - "id": "3", - "links": [ - { - "rel": "bookmark", - "href": 'http://localhost/fake/flavors/3', - }, - ], - } - expected_image = { - "id": image_uuid, - "links": [ - { - "rel": "bookmark", - "href": 'http://localhost/fake/images/%s' % image_uuid, - }, - ], - } body = { 'server': { 'name': 'server_test', @@ -1912,8 +1876,7 @@ class ServersControllerCreateTest(test.TestCase): res = self.controller.create(req, body) server = res['server'] - self.assertEqual(expected_flavor, server['flavor']) - self.assertEqual(expected_image, server['image']) + self.assertEqual(FAKE_UUID, server['id']) def test_create_instance_admin_pass(self): image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6' diff --git a/nova/tests/integrated/test_servers.py b/nova/tests/integrated/test_servers.py index 36f62ac01..58aca5778 100644 --- a/nova/tests/integrated/test_servers.py +++ b/nova/tests/integrated/test_servers.py @@ -292,10 +292,6 @@ class ServersTest(integrated_helpers._IntegratedTestBase): self.assertTrue(created_server['id']) created_server_id = created_server['id'] - # Reenable when bug fixed - self.assertEqual(metadata, created_server.get('metadata')) - # Check it's there - found_server = self.api.get_server(created_server_id) self.assertEqual(created_server_id, found_server['id']) self.assertEqual(metadata, found_server.get('metadata')) diff --git a/nova/tests/scheduler/test_host_filter.py b/nova/tests/scheduler/test_host_filter.py index 96f26b23c..b5b5aadef 100644 --- a/nova/tests/scheduler/test_host_filter.py +++ b/nova/tests/scheduler/test_host_filter.py @@ -56,7 +56,8 @@ class HostFilterTestCase(test.TestCase): def setUp(self): super(HostFilterTestCase, self).setUp() default_host_filters = ['AllHostsFilter'] - self.flags(default_host_filters=default_host_filters) + self.flags(default_host_filters=default_host_filters, + reserved_host_disk_mb=0, reserved_host_memory_mb=0) self.instance_type = dict(name='tiny', memory_mb=30, vcpus=10, @@ -139,6 +140,20 @@ class HostFilterTestCase(test.TestCase): self.assertEquals('host3', just_hosts[1]) self.assertEquals('host2', just_hosts[0]) + def test_instance_type_filter_reserved_memory(self): + self.flags(reserved_host_memory_mb=2048) + hf = nova.scheduler.filters.InstanceTypeFilter() + # filter all hosts that can support 30 ram and 300 disk after + # reserving 2048 ram + cooked = hf.instance_type_to_filter(self.instance_type) + all_hosts = self._get_all_hosts() + hosts = hf.filter_hosts(all_hosts, cooked) + self.assertEquals(2, len(hosts)) + just_hosts = [host for host, hostinfo in hosts] + just_hosts.sort() + self.assertEquals('host4', just_hosts[1]) + self.assertEquals('host3', just_hosts[0]) + def test_instance_type_filter_extra_specs(self): hf = nova.scheduler.filters.InstanceTypeFilter() # filter all hosts that can support 30 ram and 300 disk diff --git a/nova/tests/scheduler/test_least_cost.py b/nova/tests/scheduler/test_least_cost.py index ba6cdb686..a45e95181 100644 --- a/nova/tests/scheduler/test_least_cost.py +++ b/nova/tests/scheduler/test_least_cost.py @@ -32,6 +32,7 @@ def scale(hostinfo): class LeastCostTestCase(test.TestCase): def setUp(self): super(LeastCostTestCase, self).setUp() + self.flags(reserved_host_disk_mb=0, reserved_host_memory_mb=0) self.zone_manager = fake_zone_manager.FakeZoneManager() diff --git a/nova/tests/scheduler/test_scheduler.py b/nova/tests/scheduler/test_scheduler.py index df1ccce61..472def879 100644 --- a/nova/tests/scheduler/test_scheduler.py +++ b/nova/tests/scheduler/test_scheduler.py @@ -21,12 +21,10 @@ Tests For Scheduler import datetime import mox -import stubout from novaclient import v1_1 as novaclient from novaclient import exceptions as novaclient_exceptions -from mox import IgnoreArg from nova import context from nova import db from nova import exception @@ -35,13 +33,10 @@ 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 @@ -84,7 +79,7 @@ def _create_volume(): """Create a test volume""" vol = {} vol['size'] = 1 - vol['availability_zone'] = 'test' + vol['availability_zone'] = 'nova' ctxt = context.get_admin_context() return db.volume_create(ctxt, vol)['id'] @@ -250,77 +245,6 @@ class SchedulerTestCase(test.TestCase): db.instance_destroy(ctxt, i_ref2['id']) -class ZoneSchedulerTestCase(test.TestCase): - """Test case for zone scheduler""" - def setUp(self): - super(ZoneSchedulerTestCase, self).setUp() - 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() - service.host = kwargs['host'] - service.disabled = False - service.deleted = False - service.report_count = 0 - service.binary = 'nova-compute' - service.topic = 'compute' - service.id = kwargs['id'] - service.availability_zone = kwargs['zone'] - service.created_at = utils.utcnow() - return service - - def test_with_two_zones(self): - scheduler = manager.SchedulerManager() - ctxt = context.RequestContext('user', 'project') - service_list = [self._create_service_model(id=1, - host='host1', - zone='zone1'), - self._create_service_model(id=2, - host='host2', - zone='zone2'), - self._create_service_model(id=3, - host='host3', - zone='zone2'), - self._create_service_model(id=4, - host='host4', - zone='zone2'), - 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) - 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': 100}}) - self.mox.ReplayAll() - scheduler.run_instance(ctxt, - 'compute', - request_spec=request_spec) - - class SimpleDriverTestCase(test.TestCase): """Test case for simple driver""" def setUp(self): @@ -444,7 +368,7 @@ class SimpleDriverTestCase(test.TestCase): compute2.kill() def test_specific_host_gets_instance_no_queue(self): - """Ensures if you set availability_zone it launches on that zone""" + """Ensures if you set zone:host it launches on that host""" compute1 = service.Service('host1', 'nova-compute', 'compute', @@ -533,6 +457,78 @@ class SimpleDriverTestCase(test.TestCase): compute1.terminate_instance(self.context, instance_ids[0]) compute1.kill() + def test_specific_zone_gets_instance_no_queue(self): + """Ensures if you set availability_zone it launches on that zone""" + self.flags(node_availability_zone='zone1') + compute1 = service.Service('host1', + 'nova-compute', + 'compute', + FLAGS.compute_manager) + compute1.start() + self.flags(node_availability_zone='zone2') + compute2 = service.Service('host2', + 'nova-compute', + 'compute', + FLAGS.compute_manager) + compute2.start() + + 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='zone1') + 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_bad_instance_zone_fails(self): + self.flags(node_availability_zone='zone1') + compute1 = service.Service('host1', + 'nova-compute', + 'compute', + FLAGS.compute_manager) + compute1.start() + request_spec = _create_request_spec(availability_zone='zone2') + try: + self.assertRaises(driver.NoValidHost, + self.scheduler.driver.schedule_run_instance, + self.context, + request_spec) + finally: + compute1.kill() + + def test_bad_volume_zone_fails(self): + self.flags(node_availability_zone='zone1') + volume1 = service.Service('host1', + 'nova-volume', + 'volume', + FLAGS.volume_manager) + volume1.start() + # uses 'nova' for zone + volume_id = _create_volume() + try: + self.assertRaises(driver.NoValidHost, + self.scheduler.driver.schedule_create_volume, + self.context, + volume_id) + finally: + db.volume_destroy(self.context, volume_id) + volume1.kill() + def test_too_many_cores_no_queue(self): """Ensures we don't go over max cores""" compute1 = service.Service('host1', diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index addb6084d..7295c9f1f 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -1409,25 +1409,6 @@ class ComputeTestCase(test.TestCase): 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 diff --git a/nova/utils.py b/nova/utils.py index 9a01a6fb8..655be744d 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -979,21 +979,27 @@ def generate_glance_url(): @contextlib.contextmanager -def original_exception_raised(): - """Run some code, then re-raise the original exception. +def save_and_reraise_exception(): + """Save current exception, run some code and then re-raise. - This is needed because when Eventlet switches greenthreads, it clears the - exception context. This means if exception handler code blocks, we'll lose - the helpful exception traceback information. + In some cases the exception context can be cleared, resulting in None + being attempted to be reraised after an exception handler is run. This + can happen when eventlet switches greenthreads or when running an + exception handler, code raises and catches and exception. In both + cases the exception context will be cleared. To work around this, we save the exception state, run handler code, and - then re-raise the original exception. + then re-raise the original exception. If another exception occurs, the + saved exception is logged and the new exception is reraised. """ type_, value, traceback = sys.exc_info() try: yield - finally: - raise type_, value, traceback + except: + LOG.exception(_('Original exception being dropped'), + exc_info=(type_, value, traceback)) + raise + raise type_, value, traceback def make_dev_path(dev, partition=None, base='/dev'): diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 2778c5bf4..296f9b0e4 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -483,6 +483,12 @@ w # 2. Attach VDI to compute worker (VBD hotplug) with vdi_attached_here(session, vdi_ref, read_only=False) as dev: # 3. Create swap partition + + # NOTE(jk0): We use a FAT32 filesystem for the Windows swap + # partition because that is what parted supports. + is_windows = instance.os_type == "windows" + fs_type = "fat32" if is_windows else "linux-swap" + dev_path = utils.make_dev_path(dev) utils.execute('parted', '--script', dev_path, 'mklabel', 'msdos', run_as_root=True) @@ -490,7 +496,7 @@ w partition_start = 0 partition_end = swap_mb utils.execute('parted', '--script', dev_path, 'mkpartfs', - 'primary', 'linux-swap', + 'primary', fs_type, str(partition_start), str(partition_end), run_as_root=True) @@ -499,7 +505,7 @@ w cls.create_vbd(session, vm_ref, vdi_ref, userdevice, bootable=False) except: - with utils.original_exception_raised(): + with utils.save_and_reraise_exception(): cls.destroy_vdi(session, vdi_ref) @classmethod |