diff options
83 files changed, 887 insertions, 410 deletions
diff --git a/HACKING.rst b/HACKING.rst index 35493e55b..54e3b3275 100644 --- a/HACKING.rst +++ b/HACKING.rst @@ -29,6 +29,18 @@ General mylist = Foo().list() # OKAY, does not shadow built-in +- Use the "not in" operator for collection membership evaluation. Example:: + + if not X in Y: # BAD, hard to understand + pass + + if X not in Y: # OKAY, intuitive + pass + + if not (X in Y or X is Z): # OKAY, still better than all those 'not's + pass + + Imports ------- - Do not import objects, only modules (*) diff --git a/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-req.json b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-req.json new file mode 100644 index 000000000..63fc8738b --- /dev/null +++ b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-req.json @@ -0,0 +1,6 @@ +{ + "extra_specs": { + "key1": "value1", + "key2": "value2" + } +}
\ No newline at end of file diff --git a/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-req.xml b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-req.xml new file mode 100644 index 000000000..95c1daab9 --- /dev/null +++ b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-req.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<extra_specs> + <key1>value1</key1> + <key2>value2</key2> +</extra_specs>
\ No newline at end of file diff --git a/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-resp.json b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-resp.json new file mode 100644 index 000000000..63fc8738b --- /dev/null +++ b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-resp.json @@ -0,0 +1,6 @@ +{ + "extra_specs": { + "key1": "value1", + "key2": "value2" + } +}
\ No newline at end of file diff --git a/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-resp.xml b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-resp.xml new file mode 100644 index 000000000..06b01a9fc --- /dev/null +++ b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-resp.xml @@ -0,0 +1,5 @@ +<?xml version='1.0' encoding='UTF-8'?> +<extra_specs> + <key2>value2</key2> + <key1>value1</key1> +</extra_specs>
\ No newline at end of file diff --git a/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-get-resp.json b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-get-resp.json new file mode 100644 index 000000000..e71755fe6 --- /dev/null +++ b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-get-resp.json @@ -0,0 +1,3 @@ +{ + "key1": "value1" +}
\ No newline at end of file diff --git a/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-get-resp.xml b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-get-resp.xml new file mode 100644 index 000000000..d57579ba6 --- /dev/null +++ b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-get-resp.xml @@ -0,0 +1,2 @@ +<?xml version='1.0' encoding='UTF-8'?> +<extra_spec key="key1">value1</extra_spec>
\ No newline at end of file diff --git a/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-list-resp.json b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-list-resp.json new file mode 100644 index 000000000..63fc8738b --- /dev/null +++ b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-list-resp.json @@ -0,0 +1,6 @@ +{ + "extra_specs": { + "key1": "value1", + "key2": "value2" + } +}
\ No newline at end of file diff --git a/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-list-resp.xml b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-list-resp.xml new file mode 100644 index 000000000..06b01a9fc --- /dev/null +++ b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-list-resp.xml @@ -0,0 +1,5 @@ +<?xml version='1.0' encoding='UTF-8'?> +<extra_specs> + <key2>value2</key2> + <key1>value1</key1> +</extra_specs>
\ No newline at end of file diff --git a/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-req.json b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-req.json new file mode 100644 index 000000000..a40d79e32 --- /dev/null +++ b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-req.json @@ -0,0 +1,3 @@ +{ + "key1": "new_value1" +}
\ No newline at end of file diff --git a/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-req.xml b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-req.xml new file mode 100644 index 000000000..b7ae6732b --- /dev/null +++ b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-req.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="UTF-8"?> + <key1>new_value1</key1>
\ No newline at end of file diff --git a/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-resp.json b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-resp.json new file mode 100644 index 000000000..a40d79e32 --- /dev/null +++ b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-resp.json @@ -0,0 +1,3 @@ +{ + "key1": "new_value1" +}
\ No newline at end of file diff --git a/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-resp.xml b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-resp.xml new file mode 100644 index 000000000..13208ad7c --- /dev/null +++ b/doc/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-resp.xml @@ -0,0 +1,2 @@ +<?xml version='1.0' encoding='UTF-8'?> +<extra_spec key="key1">new_value1</extra_spec>
\ No newline at end of file diff --git a/etc/nova/rootwrap.d/compute.filters b/etc/nova/rootwrap.d/compute.filters index e1113a9e7..9562a23aa 100644 --- a/etc/nova/rootwrap.d/compute.filters +++ b/etc/nova/rootwrap.d/compute.filters @@ -174,3 +174,9 @@ vgs: CommandFilter, /sbin/vgs, root # nova/virt/baremetal/volume_driver.py: 'tgtadm', '--lld', 'iscsi', ... tgtadm: CommandFilter, /usr/sbin/tgtadm, root + +# nova/utils.py:read_file_as_root: 'cat', file_path +# (called from nova/virt/disk/vfs/localfs.py:VFSLocalFS.read_file) +read_passwd: RegExpFilter, cat, root, cat, (/var|/usr)?/tmp/openstack-vfs-localfs[^/]+/etc/passwd +read_shadow: RegExpFilter, cat, root, cat, (/var|/usr)?/tmp/openstack-vfs-localfs[^/]+/etc/shadow + diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 48b0f632f..b66b15852 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -280,7 +280,7 @@ class CloudController(object): host_services = {} for service in enabled_services: zone_hosts.setdefault(service['availability_zone'], []) - if not service['host'] in zone_hosts[service['availability_zone']]: + if service['host'] not in zone_hosts[service['availability_zone']]: zone_hosts[service['availability_zone']].append( service['host']) @@ -407,7 +407,7 @@ class CloudController(object): def describe_key_pairs(self, context, key_name=None, **kwargs): key_pairs = self.keypair_api.get_key_pairs(context, context.user_id) - if not key_name is None: + if key_name is not None: key_pairs = [x for x in key_pairs if x['name'] in key_name] #If looking for non existent key pair @@ -527,7 +527,7 @@ class CloudController(object): def _rule_args_to_dict(self, context, kwargs): rules = [] - if not 'groups' in kwargs and not 'ip_ranges' in kwargs: + if 'groups' not in kwargs and 'ip_ranges' not in kwargs: rule = self._rule_dict_last_step(context, **kwargs) if rule: rules.append(rule) @@ -991,18 +991,22 @@ class CloudController(object): def describe_instances(self, context, **kwargs): # Optional DescribeInstances argument instance_id = kwargs.get('instance_id', None) + filters = kwargs.get('filter', None) instances = self._enforce_valid_instance_ids(context, instance_id) return self._format_describe_instances(context, instance_id=instance_id, - instance_cache=instances) + instance_cache=instances, + filter=filters) def describe_instances_v6(self, context, **kwargs): # Optional DescribeInstancesV6 argument instance_id = kwargs.get('instance_id', None) + filters = kwargs.get('filter', None) instances = self._enforce_valid_instance_ids(context, instance_id) return self._format_describe_instances(context, instance_id=instance_id, instance_cache=instances, + filter=filters, use_v6=True) def _format_describe_instances(self, context, **kwargs): @@ -1545,11 +1549,11 @@ class CloudController(object): if attribute != 'launchPermission': raise exception.EC2APIError(_('attribute not supported: %s') % attribute) - if not 'user_group' in kwargs: + if 'user_group' not in kwargs: raise exception.EC2APIError(_('user or group not specified')) if len(kwargs['user_group']) != 1 and kwargs['user_group'][0] != 'all': raise exception.EC2APIError(_('only group "all" is supported')) - if not operation_type in ['add', 'remove']: + if operation_type not in ['add', 'remove']: msg = _('operation_type must be add or remove') raise exception.EC2APIError(msg) LOG.audit(_("Updating image %s publicity"), image_id, context=context) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index d812cef18..a76b74324 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -102,7 +102,7 @@ class APIMapper(routes.Mapper): class ProjectMapper(APIMapper): def resource(self, member_name, collection_name, **kwargs): - if not ('parent_resource' in kwargs): + if 'parent_resource' not in kwargs: kwargs['path_prefix'] = '{project_id}/' else: parent_resource = kwargs['parent_resource'] diff --git a/nova/api/openstack/compute/contrib/aggregates.py b/nova/api/openstack/compute/contrib/aggregates.py index 91d138be4..84b0358a3 100644 --- a/nova/api/openstack/compute/contrib/aggregates.py +++ b/nova/api/openstack/compute/contrib/aggregates.py @@ -106,7 +106,7 @@ class AggregateController(object): raise exc.HTTPBadRequest for key in updates.keys(): - if not key in ["name", "availability_zone"]: + if key not in ["name", "availability_zone"]: raise exc.HTTPBadRequest try: diff --git a/nova/api/openstack/compute/contrib/availability_zone.py b/nova/api/openstack/compute/contrib/availability_zone.py index 6cde5ca64..98c508bd7 100644 --- a/nova/api/openstack/compute/contrib/availability_zone.py +++ b/nova/api/openstack/compute/contrib/availability_zone.py @@ -110,7 +110,7 @@ class AvailabilityZoneController(wsgi.Controller): host_services = {} for service in enabled_services: zone_hosts.setdefault(service['availability_zone'], []) - if not service['host'] in zone_hosts[service['availability_zone']]: + if service['host'] not in zone_hosts[service['availability_zone']]: zone_hosts[service['availability_zone']].append( service['host']) diff --git a/nova/api/openstack/compute/contrib/flavorextraspecs.py b/nova/api/openstack/compute/contrib/flavorextraspecs.py index 84f157b6a..12cc7d9ed 100644 --- a/nova/api/openstack/compute/contrib/flavorextraspecs.py +++ b/nova/api/openstack/compute/contrib/flavorextraspecs.py @@ -84,7 +84,7 @@ class FlavorExtraSpecsController(object): context = req.environ['nova.context'] authorize(context) self._check_body(body) - if not id in body: + if id not in body: expl = _('Request body and URI mismatch') raise exc.HTTPBadRequest(explanation=expl) if len(body) > 1: diff --git a/nova/api/openstack/compute/contrib/floating_ips_bulk.py b/nova/api/openstack/compute/contrib/floating_ips_bulk.py index f5b8d24dd..db506a15d 100644 --- a/nova/api/openstack/compute/contrib/floating_ips_bulk.py +++ b/nova/api/openstack/compute/contrib/floating_ips_bulk.py @@ -80,13 +80,13 @@ class FloatingIPBulkController(object): context = req.environ['nova.context'] authorize(context) - if not 'floating_ips_bulk_create' in body: + if 'floating_ips_bulk_create' not in body: raise webob.exc.HTTPUnprocessableEntity() params = body['floating_ips_bulk_create'] LOG.debug(params) - if not 'ip_range' in params: + if 'ip_range' not in params: raise webob.exc.HTTPUnprocessableEntity() ip_range = params['ip_range'] diff --git a/nova/api/openstack/compute/contrib/simple_tenant_usage.py b/nova/api/openstack/compute/contrib/simple_tenant_usage.py index 8502e93c4..2313c00ac 100644 --- a/nova/api/openstack/compute/contrib/simple_tenant_usage.py +++ b/nova/api/openstack/compute/contrib/simple_tenant_usage.py @@ -159,7 +159,7 @@ class SimpleTenantUsageController(object): info['uptime'] = delta.days * 24 * 3600 + delta.seconds - if not info['tenant_id'] in rval: + if info['tenant_id'] not in rval: summary = {} summary['tenant_id'] = info['tenant_id'] if detailed: diff --git a/nova/api/openstack/compute/image_metadata.py b/nova/api/openstack/compute/image_metadata.py index 1a467f3a7..7e78d6324 100644 --- a/nova/api/openstack/compute/image_metadata.py +++ b/nova/api/openstack/compute/image_metadata.py @@ -76,7 +76,7 @@ class Controller(object): expl = _('Incorrect request body format') raise exc.HTTPBadRequest(explanation=expl) - if not id in meta: + if id not in meta: expl = _('Request body and URI mismatch') raise exc.HTTPBadRequest(explanation=expl) if len(meta) > 1: @@ -105,7 +105,7 @@ class Controller(object): def delete(self, req, image_id, id): context = req.environ['nova.context'] image = self._get_image(context, image_id) - if not id in image['properties']: + if id not in image['properties']: msg = _("Invalid metadata key") raise exc.HTTPNotFound(explanation=msg) image['properties'].pop(id) diff --git a/nova/api/openstack/compute/servers.py b/nova/api/openstack/compute/servers.py index 93a07ec3f..ac4ebd293 100644 --- a/nova/api/openstack/compute/servers.py +++ b/nova/api/openstack/compute/servers.py @@ -751,7 +751,7 @@ class Controller(wsgi.Controller): server_dict = body['server'] password = self._get_server_admin_password(server_dict) - if not 'name' in server_dict: + if 'name' not in server_dict: msg = _("Server name is not defined") raise exc.HTTPBadRequest(explanation=msg) @@ -911,6 +911,8 @@ class Controller(wsgi.Controller): except exception.ImageNotFound as error: msg = _("Can not find requested image") raise exc.HTTPBadRequest(explanation=msg) + except exception.ImageNotActive as error: + raise exc.HTTPBadRequest(explanation=unicode(error)) except exception.FlavorNotFound as error: msg = _("Invalid flavorRef provided.") raise exc.HTTPBadRequest(explanation=msg) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index a6f255081..f68eff2a7 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -150,7 +150,7 @@ class Request(webob.Request): Does not do any body introspection, only checks header """ - if not "Content-Type" in self.headers: + if "Content-Type" not in self.headers: return None content_type = self.content_type diff --git a/nova/availability_zones.py b/nova/availability_zones.py index 711eee1fa..97faccc9f 100644 --- a/nova/availability_zones.py +++ b/nova/availability_zones.py @@ -71,7 +71,7 @@ def get_availability_zones(context): available_zones = [] for zone in [service['availability_zone'] for service in enabled_services]: - if not zone in available_zones: + if zone not in available_zones: available_zones.append(zone) not_available_zones = [] diff --git a/nova/common/memorycache.py b/nova/common/memorycache.py index f77b3f51a..86057b6ae 100644 --- a/nova/common/memorycache.py +++ b/nova/common/memorycache.py @@ -70,7 +70,7 @@ class Client(object): def add(self, key, value, time=0, min_compress_len=0): """Sets the value for a key if it doesn't exist.""" - if not self.get(key) is None: + if self.get(key) is not None: return False return self.set(key, value, time, min_compress_len) diff --git a/nova/compute/vm_mode.py b/nova/compute/vm_mode.py index 26e5ad8a0..cc1ca6978 100644 --- a/nova/compute/vm_mode.py +++ b/nova/compute/vm_mode.py @@ -52,7 +52,7 @@ def get_from_instance(instance): if mode == "hv": mode = HVM - if not mode in ALL: + if mode not in ALL: raise exception.Invalid("Unknown vm mode '%s'" % mode) return mode diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 61f27f31c..ad7e4f21f 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1244,7 +1244,7 @@ def fixed_ip_get_by_network_host(context, network_id, host): first() if not result: - raise exception.FixedIpNotFoundForNetworkHost(network_uuid=network_id, + raise exception.FixedIpNotFoundForNetworkHost(network_id=network_id, host=host) return result diff --git a/nova/db/sqlalchemy/session.py b/nova/db/sqlalchemy/session.py index eb5d8016f..28ec613c5 100644 --- a/nova/db/sqlalchemy/session.py +++ b/nova/db/sqlalchemy/session.py @@ -228,8 +228,10 @@ from eventlet import db_pool from eventlet import greenthread try: import MySQLdb + from MySQLdb.constants import CLIENT as mysql_client_constants except ImportError: MySQLdb = None + mysql_client_constants = None from sqlalchemy.exc import DisconnectionError, OperationalError, IntegrityError import sqlalchemy.interfaces import sqlalchemy.orm @@ -482,9 +484,21 @@ def create_engine(sql_connection): 'user': connection_dict.username, 'min_size': CONF.sql_min_pool_size, 'max_size': CONF.sql_max_pool_size, - 'max_idle': CONF.sql_idle_timeout} - creator = db_pool.ConnectionPool(MySQLdb, **pool_args) - engine_args['creator'] = creator.create + 'max_idle': CONF.sql_idle_timeout, + 'client_flag': mysql_client_constants.FOUND_ROWS} + + pool = db_pool.ConnectionPool(MySQLdb, **pool_args) + + def creator(): + conn = pool.create() + if isinstance(conn, tuple): + # NOTE(belliott) eventlet >= 0.10 returns a tuple + now, now, conn = conn + + return conn + + engine_args['creator'] = creator + else: engine_args['pool_size'] = CONF.sql_max_pool_size if CONF.sql_max_overflow is not None: @@ -550,6 +564,10 @@ class Session(sqlalchemy.orm.session.Session): def flush(self, *args, **kwargs): return super(Session, self).flush(*args, **kwargs) + @wrap_db_error + def execute(self, *args, **kwargs): + return super(Session, self).execute(*args, **kwargs) + def get_maker(engine, autocommit=True, expire_on_commit=False): """Return a SQLAlchemy sessionmaker using the given engine.""" diff --git a/nova/image/glance.py b/nova/image/glance.py index 1a6bba62f..6eac96c35 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -485,6 +485,8 @@ def get_remote_image_service(context, image_href): :returns: a tuple of the form (image_service, image_id) """ + # Calling out to another service may take a while, so lets log this + LOG.debug(_("fetching image %s from glance") % image_href) #NOTE(bcwaldon): If image_href doesn't look like a URI, assume its a # standalone image ID if '/' not in str(image_href): diff --git a/nova/scheduler/driver.py b/nova/scheduler/driver.py index 16714a5ff..01bef4185 100644 --- a/nova/scheduler/driver.py +++ b/nova/scheduler/driver.py @@ -27,6 +27,7 @@ from nova.compute import power_state from nova.compute import rpcapi as compute_rpcapi from nova.compute import utils as compute_utils from nova.compute import vm_states +from nova.conductor import api as conductor_api from nova import db from nova import exception from nova import notifications @@ -66,6 +67,7 @@ def handle_schedule_error(context, ex, instance_uuid, request_spec): notifications.send_update(context, old_ref, new_ref, service="scheduler") compute_utils.add_instance_fault_from_exc(context, + conductor_api.LocalAPI(), new_ref, ex, sys.exc_info()) properties = request_spec.get('instance_properties', {}) diff --git a/nova/scheduler/filters/trusted_filter.py b/nova/scheduler/filters/trusted_filter.py index 302d2b3a8..14f1a37b0 100644 --- a/nova/scheduler/filters/trusted_filter.py +++ b/nova/scheduler/filters/trusted_filter.py @@ -269,7 +269,7 @@ class ComputeAttestationCache(object): def get_host_attestation(self, host): """Check host's trust level.""" - if not host in self.compute_nodes: + if host not in self.compute_nodes: self._init_cache_entry(host) if not self._cache_valid(host): self._update_cache() diff --git a/nova/scheduler/manager.py b/nova/scheduler/manager.py index 23e64cd7c..e6bf1a293 100644 --- a/nova/scheduler/manager.py +++ b/nova/scheduler/manager.py @@ -26,6 +26,7 @@ import sys from nova.compute import rpcapi as compute_rpcapi from nova.compute import utils as compute_utils from nova.compute import vm_states +from nova.conductor import api as conductor_api import nova.context from nova import db from nova import exception @@ -190,6 +191,7 @@ class SchedulerManager(manager.Manager): notifications.send_update(context, old_ref, new_ref, service="scheduler") compute_utils.add_instance_fault_from_exc(context, + conductor_api.LocalAPI(), new_ref, ex, sys.exc_info()) payload = dict(request_spec=request_spec, diff --git a/nova/service.py b/nova/service.py index 2daceba80..3d556a202 100644 --- a/nova/service.py +++ b/nova/service.py @@ -621,7 +621,7 @@ class WSGIService(object): """ fl = '%s_manager' % self.name - if not fl in CONF: + if fl not in CONF: return None manager_class_name = CONF.get(fl, None) diff --git a/nova/tests/api/ec2/test_cloud.py b/nova/tests/api/ec2/test_cloud.py index a00dceff1..c60a0148e 100644 --- a/nova/tests/api/ec2/test_cloud.py +++ b/nova/tests/api/ec2/test_cloud.py @@ -824,6 +824,19 @@ class CloudTestCase(test.TestCase): self.cloud.describe_instances, self.context, instance_id=[instance_id]) + def test_describe_instances_with_filters(self): + # Makes sure describe_instances works and filters results. + filters = {'filter': [{'name': 'test', + 'value': ['a', 'b']}, + {'name': 'another_test', + 'value': 'a string'}]} + + self._stub_instance_get_with_fixed_ips('get_all') + self._stub_instance_get_with_fixed_ips('get') + + result = self.cloud.describe_instances(self.context, **filters) + self.assertEqual(result, {'reservationSet': []}) + def test_describe_instances_sorting(self): # Makes sure describe_instances works and is sorted as expected. self.flags(use_ipv6=True) diff --git a/nova/tests/api/openstack/compute/contrib/test_flavors_extra_specs.py b/nova/tests/api/openstack/compute/contrib/test_flavors_extra_specs.py index 5328ec2ee..a3745d573 100644 --- a/nova/tests/api/openstack/compute/contrib/test_flavors_extra_specs.py +++ b/nova/tests/api/openstack/compute/contrib/test_flavors_extra_specs.py @@ -18,10 +18,9 @@ import webob from nova.api.openstack.compute.contrib import flavorextraspecs -from nova.api.openstack import wsgi +import nova.db from nova import test from nova.tests.api.openstack import fakes -import nova.wsgi def return_create_flavor_extra_specs(context, flavor_id, extra_specs): @@ -174,14 +173,6 @@ class FlavorsExtraSpecsXMLSerializerTest(test.TestCase): text = serializer.serialize(dict(extra_specs={"key1": "value1"})) self.assertEqual(text, expected) - def test_deserializer(self): - deserializer = wsgi.XMLDeserializer() - expected = dict(extra_specs={"key1": "value1"}) - intext = ("<?xml version='1.0' encoding='UTF-8'?>\n" - '<extra_specs><key1>value1</key1></extra_specs>') - result = deserializer.deserialize(intext)['body'] - self.assertEqual(result, expected) - def test_show_update_serializer(self): serializer = flavorextraspecs.ExtraSpecTemplate() expected = ("<?xml version='1.0' encoding='UTF-8'?>\n" diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index 03fc87ac5..3ef98b902 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -372,7 +372,7 @@ def create_info_cache(nw_cache): def get_fake_uuid(token=0): - if not token in FAKE_UUIDS: + if token not in FAKE_UUIDS: FAKE_UUIDS[token] = str(uuid.uuid4()) return FAKE_UUIDS[token] @@ -399,12 +399,12 @@ def fake_instance_get_all_by_filters(num_servers=5, **kwargs): server = stub_instance(id=i + 1, uuid=uuid, **kwargs) servers_list.append(server) - if not marker is None and uuid == marker: + if marker is not None and uuid == marker: found_marker = True servers_list = [] - if not marker is None and not found_marker: + if marker is not None and not found_marker: raise exc.MarkerNotFound(marker=marker) - if not limit is None: + if limit is not None: servers_list = servers_list[:limit] return servers_list return _return_servers diff --git a/nova/tests/compute/test_compute.py b/nova/tests/compute/test_compute.py index 7b284779e..6bd2c3cac 100644 --- a/nova/tests/compute/test_compute.py +++ b/nova/tests/compute/test_compute.py @@ -2067,7 +2067,7 @@ class ComputeTestCase(BaseTestCase): self.compute.terminate_instance(self.context, instance=new_instance) def test_prep_resize_instance_migration_error_on_none_host(self): - """Ensure prep_resize raise a migration error if destination host is + """Ensure prep_resize raises a migration error if destination host is not defined """ instance = jsonutils.to_primitive(self._create_fake_instance()) @@ -2532,7 +2532,7 @@ class ComputeTestCase(BaseTestCase): db.instance_destroy(c, inst_uuid) def test_post_live_migration_no_shared_storage_working_correctly(self): - """Confirm post_live_migration() works as expected correctly + """Confirm post_live_migration() works correctly as expected for non shared storage migration. """ # Create stubs @@ -3431,7 +3431,7 @@ class ComputeTestCase(BaseTestCase): db.instance_destroy(c, inst_uuid) def test_rebuild_with_wrong_shared_storage(self): - """Confirm evacuate scenario updates host.""" + """Confirm evacuate scenario does not update host.""" # creating testdata c = self.context.elevated() diff --git a/nova/tests/compute/test_resource_tracker.py b/nova/tests/compute/test_resource_tracker.py index 53d92a13f..eaa0df5bf 100644 --- a/nova/tests/compute/test_resource_tracker.py +++ b/nova/tests/compute/test_resource_tracker.py @@ -391,7 +391,7 @@ class BaseTrackerTestCase(BaseTestCase): if tracker is None: tracker = self.tracker - if not field in tracker.compute_node: + if field not in tracker.compute_node: raise test.TestingException( "'%(field)s' not in compute node." % locals()) x = tracker.compute_node[field] diff --git a/nova/tests/fakeguestfs.py b/nova/tests/fakeguestfs.py index 33ca49c33..ff006db68 100644 --- a/nova/tests/fakeguestfs.py +++ b/nova/tests/fakeguestfs.py @@ -50,7 +50,7 @@ class GuestFS(object): self.mounts.append((options, device, mntpoint)) def mkdir_p(self, path): - if not path in self.files: + if path not in self.files: self.files[path] = { "isdir": True, "gid": 100, @@ -59,7 +59,7 @@ class GuestFS(object): } def read_file(self, path): - if not path in self.files: + if path not in self.files: self.files[path] = { "isdir": False, "content": "Hello World", @@ -71,7 +71,7 @@ class GuestFS(object): return self.files[path]["content"] def write(self, path, content): - if not path in self.files: + if path not in self.files: self.files[path] = { "isdir": False, "content": "Hello World", @@ -83,7 +83,7 @@ class GuestFS(object): self.files[path]["content"] = content def write_append(self, path, content): - if not path in self.files: + if path not in self.files: self.files[path] = { "isdir": False, "content": "Hello World", @@ -95,13 +95,13 @@ class GuestFS(object): self.files[path]["content"] = self.files[path]["content"] + content def stat(self, path): - if not path in self.files: + if path not in self.files: raise RuntimeError("No such file: " + path) return self.files[path]["mode"] def chown(self, uid, gid, path): - if not path in self.files: + if path not in self.files: raise RuntimeError("No such file: " + path) if uid != -1: @@ -110,7 +110,7 @@ class GuestFS(object): self.files[path]["gid"] = gid def chmod(self, mode, path): - if not path in self.files: + if path not in self.files: raise RuntimeError("No such file: " + path) self.files[path]["mode"] = mode diff --git a/nova/tests/integrated/api/client.py b/nova/tests/integrated/api/client.py index a072b3128..958a5500b 100644 --- a/nova/tests/integrated/api/client.py +++ b/nova/tests/integrated/api/client.py @@ -155,7 +155,7 @@ class TestOpenStackClient(object): LOG.debug(_("%(relative_uri)s => code %(http_status)s") % locals()) if check_response_status: - if not http_status in check_response_status: + if http_status not in check_response_status: if http_status == 404: raise OpenStackApiNotFoundException(response=response) elif http_status == 401: diff --git a/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-req.json.tpl b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-req.json.tpl new file mode 100644 index 000000000..dd858e76c --- /dev/null +++ b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-req.json.tpl @@ -0,0 +1,6 @@ +{ + "extra_specs": { + "key1": "%(value1)s", + "key2": "%(value2)s" + } +} diff --git a/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-req.xml.tpl b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-req.xml.tpl new file mode 100644 index 000000000..c94595cad --- /dev/null +++ b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-req.xml.tpl @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<extra_specs> + <key1>%(value1)s</key1> + <key2>%(value2)s</key2> +</extra_specs> diff --git a/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-resp.json.tpl b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-resp.json.tpl new file mode 100644 index 000000000..dd858e76c --- /dev/null +++ b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-resp.json.tpl @@ -0,0 +1,6 @@ +{ + "extra_specs": { + "key1": "%(value1)s", + "key2": "%(value2)s" + } +} diff --git a/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-resp.xml.tpl b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-resp.xml.tpl new file mode 100644 index 000000000..1008b5bb0 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-create-resp.xml.tpl @@ -0,0 +1,5 @@ +<?xml version='1.0' encoding='UTF-8'?> +<extra_specs> + <key2>%(value2)s</key2> + <key1>%(value1)s</key1> +</extra_specs> diff --git a/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-get-resp.json.tpl b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-get-resp.json.tpl new file mode 100644 index 000000000..adfa77008 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-get-resp.json.tpl @@ -0,0 +1,3 @@ +{ + "key1": "%(value1)s" +} diff --git a/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-get-resp.xml.tpl b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-get-resp.xml.tpl new file mode 100644 index 000000000..e3de59a34 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-get-resp.xml.tpl @@ -0,0 +1,2 @@ +<?xml version='1.0' encoding='UTF-8'?> +<extra_spec key="key1">%(value1)s</extra_spec> diff --git a/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-list-resp.json.tpl b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-list-resp.json.tpl new file mode 100644 index 000000000..dd858e76c --- /dev/null +++ b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-list-resp.json.tpl @@ -0,0 +1,6 @@ +{ + "extra_specs": { + "key1": "%(value1)s", + "key2": "%(value2)s" + } +} diff --git a/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-list-resp.xml.tpl b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-list-resp.xml.tpl new file mode 100644 index 000000000..1008b5bb0 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-list-resp.xml.tpl @@ -0,0 +1,5 @@ +<?xml version='1.0' encoding='UTF-8'?> +<extra_specs> + <key2>%(value2)s</key2> + <key1>%(value1)s</key1> +</extra_specs> diff --git a/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-req.json.tpl b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-req.json.tpl new file mode 100644 index 000000000..adfa77008 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-req.json.tpl @@ -0,0 +1,3 @@ +{ + "key1": "%(value1)s" +} diff --git a/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-req.xml.tpl b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-req.xml.tpl new file mode 100644 index 000000000..6421e5959 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-req.xml.tpl @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="UTF-8"?> + <key1>%(value1)s</key1> diff --git a/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-resp.json.tpl b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-resp.json.tpl new file mode 100644 index 000000000..adfa77008 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-resp.json.tpl @@ -0,0 +1,3 @@ +{ + "key1": "%(value1)s" +} diff --git a/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-resp.xml.tpl b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-resp.xml.tpl new file mode 100644 index 000000000..e3de59a34 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-flavor-extra-specs/flavor-extra-specs-update-resp.xml.tpl @@ -0,0 +1,2 @@ +<?xml version='1.0' encoding='UTF-8'?> +<extra_spec key="key1">%(value1)s</extra_spec> diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py index f17dc025f..90e9a806e 100644 --- a/nova/tests/integrated/integrated_helpers.py +++ b/nova/tests/integrated/integrated_helpers.py @@ -58,7 +58,7 @@ def generate_new_element(items, prefix, numeric=False): candidate = prefix + generate_random_numeric(8) else: candidate = prefix + generate_random_alphanumeric(8) - if not candidate in items: + if candidate not in items: return candidate LOG.debug("Random collision on %s" % candidate) diff --git a/nova/tests/integrated/test_api_samples.py b/nova/tests/integrated/test_api_samples.py index 8343aa347..21a23d94e 100644 --- a/nova/tests/integrated/test_api_samples.py +++ b/nova/tests/integrated/test_api_samples.py @@ -380,7 +380,6 @@ class ApiSamplesTrap(ApiSampleTestBase): do_not_approve_additions.append('os-config-drive') do_not_approve_additions.append('os-create-server-ext') do_not_approve_additions.append('os-flavor-access') - do_not_approve_additions.append('os-flavor-extra-specs') do_not_approve_additions.append('os-floating-ip-dns') do_not_approve_additions.append('os-hypervisors') do_not_approve_additions.append('os-networks') @@ -2714,6 +2713,61 @@ class InstanceUsageAuditLogJsonTest(ApiSampleTestBase): class InstanceUsageAuditLogXmlTest(InstanceUsageAuditLogJsonTest): + ctype = "xml" + + +class FlavorExtraSpecsSampleJsonTests(ApiSampleTestBase): + extension_name = ("nova.api.openstack.compute.contrib.flavorextraspecs." + "Flavorextraspecs") + + def _flavor_extra_specs_create(self): + subs = {'value1': 'value1', + 'value2': 'value2' + } + response = self._do_post('flavors/1/os-extra_specs', + 'flavor-extra-specs-create-req', subs) + self.assertEqual(response.status, 200) + return self._verify_response('flavor-extra-specs-create-resp', + subs, response) + + def test_flavor_extra_specs_get(self): + subs = {'value1': 'value1'} + self._flavor_extra_specs_create() + response = self._do_get('flavors/1/os-extra_specs/key1') + self.assertEqual(response.status, 200) + return self._verify_response('flavor-extra-specs-get-resp', + subs, response) + + def test_flavor_extra_specs_list(self): + subs = {'value1': 'value1', + 'value2': 'value2' + } + self._flavor_extra_specs_create() + response = self._do_get('flavors/1/os-extra_specs') + self.assertEqual(response.status, 200) + return self._verify_response('flavor-extra-specs-list-resp', + subs, response) + + def test_flavor_extra_specs_create(self): + return self._flavor_extra_specs_create() + + def test_flavor_extra_specs_update(self): + subs = {'value1': 'new_value1'} + self._flavor_extra_specs_create() + response = self._do_put('flavors/1/os-extra_specs/key1', + 'flavor-extra-specs-update-req', subs) + self.assertEqual(response.status, 200) + return self._verify_response('flavor-extra-specs-update-resp', + subs, response) + + def test_flavor_extra_specs_delete(self): + self._flavor_extra_specs_create() + response = self._do_delete('flavors/1/os-extra_specs/key1') + self.assertEqual(response.status, 200) + self.assertEqual(response.read(), '') + + +class FlavorExtraSpecsSampleXmlTests(FlavorExtraSpecsSampleJsonTests): ctype = 'xml' diff --git a/nova/tests/scheduler/test_chance_scheduler.py b/nova/tests/scheduler/test_chance_scheduler.py index 76fba900d..dcbe86f75 100644 --- a/nova/tests/scheduler/test_chance_scheduler.py +++ b/nova/tests/scheduler/test_chance_scheduler.py @@ -25,6 +25,7 @@ import mox from nova.compute import rpcapi as compute_rpcapi from nova.compute import utils as compute_utils from nova.compute import vm_states +from nova.conductor import api as conductor_api from nova import context from nova import db from nova import exception @@ -134,7 +135,8 @@ class ChanceSchedulerTestCase(test_scheduler.SchedulerTestCase): {'vm_state': vm_states.ERROR, 'task_state': None}).AndReturn(({}, {})) compute_utils.add_instance_fault_from_exc(ctxt, - new_ref, mox.IsA(exception.NoValidHost), mox.IgnoreArg()) + mox.IsA(conductor_api.LocalAPI), new_ref, + mox.IsA(exception.NoValidHost), mox.IgnoreArg()) self.mox.ReplayAll() self.driver.schedule_run_instance( diff --git a/nova/tests/scheduler/test_filter_scheduler.py b/nova/tests/scheduler/test_filter_scheduler.py index 2bd2cb85b..ff3a00f22 100644 --- a/nova/tests/scheduler/test_filter_scheduler.py +++ b/nova/tests/scheduler/test_filter_scheduler.py @@ -21,6 +21,7 @@ import mox from nova.compute import instance_types from nova.compute import utils as compute_utils from nova.compute import vm_states +from nova.conductor import api as conductor_api from nova import context from nova import db from nova import exception @@ -62,7 +63,8 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase): uuid, {'vm_state': vm_states.ERROR, 'task_state': None}).AndReturn(({}, {})) compute_utils.add_instance_fault_from_exc(fake_context, - new_ref, mox.IsA(exception.NoValidHost), mox.IgnoreArg()) + mox.IsA(conductor_api.LocalAPI), new_ref, + mox.IsA(exception.NoValidHost), mox.IgnoreArg()) self.mox.ReplayAll() sched.schedule_run_instance( fake_context, request_spec, None, None, None, None, {}) @@ -92,7 +94,8 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase): uuid, {'vm_state': vm_states.ERROR, 'task_state': None}).AndReturn(({}, {})) compute_utils.add_instance_fault_from_exc(fake_context, - new_ref, mox.IsA(exception.NoValidHost), mox.IgnoreArg()) + mox.IsA(conductor_api.LocalAPI), new_ref, + mox.IsA(exception.NoValidHost), mox.IgnoreArg()) self.mox.ReplayAll() sched.schedule_run_instance( fake_context, request_spec, None, None, None, None, {}) diff --git a/nova/tests/scheduler/test_scheduler.py b/nova/tests/scheduler/test_scheduler.py index eb4c3864f..142d8ea0e 100644 --- a/nova/tests/scheduler/test_scheduler.py +++ b/nova/tests/scheduler/test_scheduler.py @@ -26,10 +26,12 @@ from nova.compute import power_state from nova.compute import rpcapi as compute_rpcapi from nova.compute import utils as compute_utils from nova.compute import vm_states +from nova.conductor import api as conductor_api from nova import context from nova import db from nova import exception from nova.openstack.common import jsonutils +from nova.openstack.common.notifier import api as notifier from nova.openstack.common import rpc from nova.scheduler import driver from nova.scheduler import manager @@ -187,7 +189,8 @@ class SchedulerManagerTestCase(test.TestCase): fake_instance_uuid, {"vm_state": vm_states.ERROR, "task_state": None}).AndReturn((inst, inst)) - compute_utils.add_instance_fault_from_exc(self.context, new_ref, + compute_utils.add_instance_fault_from_exc(self.context, + mox.IsA(conductor_api.LocalAPI), new_ref, mox.IsA(exception.NoValidHost), mox.IgnoreArg()) self.mox.ReplayAll() @@ -221,7 +224,8 @@ class SchedulerManagerTestCase(test.TestCase): fake_instance_uuid, {"vm_state": vm_states.ACTIVE, "task_state": None}).AndReturn( (inst, inst)) - compute_utils.add_instance_fault_from_exc(self.context, new_ref, + compute_utils.add_instance_fault_from_exc(self.context, + mox.IsA(conductor_api.LocalAPI), new_ref, mox.IsA(exception.NoValidHost), mox.IgnoreArg()) self.mox.ReplayAll() @@ -258,7 +262,8 @@ class SchedulerManagerTestCase(test.TestCase): fake_instance_uuid, {"vm_state": vm_states.ERROR, "task_state": None}).AndReturn((inst, inst)) - compute_utils.add_instance_fault_from_exc(self.context, new_ref, + compute_utils.add_instance_fault_from_exc(self.context, + mox.IsA(conductor_api.LocalAPI), new_ref, mox.IsA(test.TestingException), mox.IgnoreArg()) self.mox.ReplayAll() @@ -266,6 +271,25 @@ class SchedulerManagerTestCase(test.TestCase): self.assertRaises(test.TestingException, self.manager.prep_resize, **kwargs) + def test_set_vm_state_and_notify_adds_instance_fault(self): + request = {'instance_properties': {'uuid': 'fake-uuid'}} + updates = {'vm_state': 'foo'} + fake_inst = {'uuid': 'fake-uuid'} + + self.mox.StubOutWithMock(db, 'instance_update_and_get_original') + self.mox.StubOutWithMock(db, 'instance_fault_create') + self.mox.StubOutWithMock(notifier, 'notify') + db.instance_update_and_get_original(self.context, 'fake-uuid', + updates).AndReturn((None, + fake_inst)) + db.instance_fault_create(self.context, mox.IgnoreArg()) + notifier.notify(self.context, mox.IgnoreArg(), 'scheduler.foo', + notifier.ERROR, mox.IgnoreArg()) + self.mox.ReplayAll() + + self.manager._set_vm_state_and_notify('foo', {'vm_state': 'foo'}, + self.context, None, request) + class SchedulerTestCase(test.TestCase): """Test case for base scheduler driver class.""" @@ -620,6 +644,24 @@ class SchedulerTestCase(test.TestCase): block_migration=block_migration, disk_over_commit=disk_over_commit) + def test_handle_schedule_error_adds_instance_fault(self): + instance = {'uuid': 'fake-uuid'} + self.mox.StubOutWithMock(db, 'instance_update_and_get_original') + self.mox.StubOutWithMock(db, 'instance_fault_create') + self.mox.StubOutWithMock(notifier, 'notify') + db.instance_update_and_get_original(self.context, instance['uuid'], + mox.IgnoreArg()).AndReturn( + (None, instance)) + db.instance_fault_create(self.context, mox.IgnoreArg()) + notifier.notify(self.context, mox.IgnoreArg(), + 'scheduler.run_instance', + notifier.ERROR, mox.IgnoreArg()) + self.mox.ReplayAll() + + driver.handle_schedule_error(self.context, + exception.NoValidHost('test'), + instance['uuid'], {}) + class SchedulerDriverBaseTestCase(SchedulerTestCase): """Test cases for base scheduler driver class methods diff --git a/nova/tests/test_db_api.py b/nova/tests/test_db_api.py index 4485be4f9..684f9fded 100644 --- a/nova/tests/test_db_api.py +++ b/nova/tests/test_db_api.py @@ -1510,6 +1510,25 @@ class MigrationTestCase(test.TestCase): self.assertEqual(migration['instance_uuid'], instance['uuid']) +class TestFixedIPGetByNetworkHost(test.TestCase): + def test_not_found_exception(self): + ctxt = context.get_admin_context() + + self.assertRaises( + exception.FixedIpNotFoundForNetworkHost, + db.fixed_ip_get_by_network_host, + ctxt, 1, 'ignore') + + def test_fixed_ip_found(self): + ctxt = context.get_admin_context() + db.fixed_ip_create(ctxt, dict(network_id=1, host='host')) + + fip = db.fixed_ip_get_by_network_host(ctxt, 1, 'host') + + self.assertEquals(1, fip['network_id']) + self.assertEquals('host', fip['host']) + + class TestIpAllocation(test.TestCase): def setUp(self): diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 5a90f3348..6e7986b0e 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -3848,7 +3848,7 @@ class IptablesFirewallTestCase(test.TestCase): in_rules = filter(lambda l: not l.startswith('#'), self.in_rules) for rule in in_rules: - if not 'nova' in rule: + if 'nova' not in rule: self.assertTrue(rule in self.out_rules, 'Rule went missing: %s' % rule) @@ -4249,7 +4249,7 @@ class LibvirtUtilsTestCase(test.TestCase): def test_pick_disk_driver_name(self): type_map = {'kvm': ([True, 'qemu'], [False, 'qemu'], [None, 'qemu']), 'qemu': ([True, 'qemu'], [False, 'qemu'], [None, 'qemu']), - 'xen': ([True, 'phy'], [False, 'file'], [None, 'file']), + 'xen': ([True, 'phy'], [False, 'tap'], [None, 'tap']), 'uml': ([True, None], [False, None], [None, None]), 'lxc': ([True, None], [False, None], [None, None])} diff --git a/nova/tests/test_sqlalchemy.py b/nova/tests/test_sqlalchemy.py index f79d607f8..5c7f4450b 100644 --- a/nova/tests/test_sqlalchemy.py +++ b/nova/tests/test_sqlalchemy.py @@ -22,8 +22,14 @@ try: except ImportError: MySQLdb = None +from sqlalchemy import Column, MetaData, Table, UniqueConstraint +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy import DateTime, Integer + from nova import context +from nova.db.sqlalchemy import models from nova.db.sqlalchemy import session +from nova import exception from nova import test @@ -64,3 +70,60 @@ class DbPoolTestCase(test.TestCase): self.assertEqual(info['kwargs']['max_idle'], 11) self.assertEqual(info['kwargs']['min_size'], 21) self.assertEqual(info['kwargs']['max_size'], 42) + + +BASE = declarative_base() +_TABLE_NAME = '__tmp__test__tmp__' + + +class TmpTable(BASE, models.NovaBase): + __tablename__ = _TABLE_NAME + id = Column(Integer, primary_key=True) + foo = Column(Integer) + + +class SessionErrorWrapperTestCase(test.TestCase): + def setUp(self): + super(SessionErrorWrapperTestCase, self).setUp() + meta = MetaData() + meta.bind = session.get_engine() + test_table = Table(_TABLE_NAME, meta, + Column('id', Integer, primary_key=True, + nullable=False), + Column('deleted', Integer, default=0), + Column('deleted_at', DateTime), + Column('updated_at', DateTime), + Column('created_at', DateTime), + Column('foo', Integer), + UniqueConstraint('foo', name='uniq_foo')) + test_table.create() + + def tearDown(self): + super(SessionErrorWrapperTestCase, self).tearDown() + meta = MetaData() + meta.bind = session.get_engine() + test_table = Table(_TABLE_NAME, meta, autoload=True) + test_table.drop() + + def test_flush_wrapper(self): + tbl = TmpTable() + tbl.update({'foo': 10}) + tbl.save() + + tbl2 = TmpTable() + tbl2.update({'foo': 10}) + self.assertRaises(exception.DBDuplicateEntry, tbl2.save) + + def test_execute_wrapper(self): + _session = session.get_session() + with _session.begin(): + for i in [10, 20]: + tbl = TmpTable() + tbl.update({'foo': i}) + tbl.save(session=_session) + + method = _session.query(TmpTable).\ + filter_by(foo=10).\ + update + self.assertRaises(exception.DBDuplicateEntry, + method, {'foo': 20}) diff --git a/nova/tests/test_virt_disk_vfs_localfs.py b/nova/tests/test_virt_disk_vfs_localfs.py index af4571dd2..096a75964 100644 --- a/nova/tests/test_virt_disk_vfs_localfs.py +++ b/nova/tests/test_virt_disk_vfs_localfs.py @@ -46,7 +46,7 @@ def fake_execute(*args, **kwargs): elif args[0] == "chown": owner = args[1] path = args[2] - if not path in files: + if path not in files: raise Exception("No such file: " + path) sep = owner.find(':') @@ -72,7 +72,7 @@ def fake_execute(*args, **kwargs): elif args[0] == "chgrp": group = args[1] path = args[2] - if not path in files: + if path not in files: raise Exception("No such file: " + path) if group == "users": @@ -83,13 +83,13 @@ def fake_execute(*args, **kwargs): elif args[0] == "chmod": mode = args[1] path = args[2] - if not path in files: + if path not in files: raise Exception("No such file: " + path) files[path]["mode"] = int(mode, 8) elif args[0] == "cat": path = args[1] - if not path in files: + if path not in files: files[path] = { "content": "Hello World", "gid": 100, @@ -104,7 +104,7 @@ def fake_execute(*args, **kwargs): else: path = args[1] append = False - if not path in files: + if path not in files: files[path] = { "content": "Hello World", "gid": 100, diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 067e28a13..c480d5c5f 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -19,7 +19,6 @@ import ast import base64 import contextlib -import cPickle as pickle import functools import os import re @@ -48,6 +47,7 @@ from nova.virt.xenapi import agent from nova.virt.xenapi import driver as xenapi_conn from nova.virt.xenapi import fake as xenapi_fake from nova.virt.xenapi import host +from nova.virt.xenapi.imageupload import glance from nova.virt.xenapi import pool from nova.virt.xenapi import pool_states from nova.virt.xenapi import vm_utils @@ -431,15 +431,29 @@ class XenAPIVMTestCase(stubs.XenAPITestBase): {'task_state': task_states.IMAGE_UPLOADING, 'expected_state': task_states.IMAGE_PENDING_UPLOAD}}] func_call_matcher = matchers.FunctionCallMatcher(expected_calls) + image_id = "my_snapshot_id" stubs.stubout_instance_snapshot(self.stubs) stubs.stubout_is_snapshot(self.stubs) # Stubbing out firewall driver as previous stub sets alters # xml rpc result parsing stubs.stubout_firewall_driver(self.stubs, self.conn) + instance = self._create_instance() - image_id = "my_snapshot_id" + self.fake_upload_called = False + + def fake_image_upload(_self, ctx, session, inst, vdi_uuids, + img_id): + self.fake_upload_called = True + self.assertEqual(ctx, self.context) + self.assertEqual(inst, instance) + self.assertTrue(isinstance(vdi_uuids, list)) + self.assertEqual(img_id, image_id) + + self.stubs.Set(glance.GlanceStore, 'upload_image', + fake_image_upload) + self.conn.snapshot(self.context, instance, image_id, func_call_matcher.call) @@ -469,6 +483,8 @@ class XenAPIVMTestCase(stubs.XenAPITestBase): name_label = vdi_rec["name_label"] self.assert_(not name_label.endswith('snapshot')) + self.assertTrue(self.fake_upload_called) + def create_vm_record(self, conn, os_type, name): instances = conn.list_instances() self.assertEquals(instances, [name]) @@ -579,7 +595,7 @@ class XenAPIVMTestCase(stubs.XenAPITestBase): def _check_vdis(self, start_list, end_list): for vdi_ref in end_list: - if not vdi_ref in start_list: + if vdi_ref not in start_list: vdi_rec = xenapi_fake.get_record('VDI', vdi_ref) # If the cache is turned on then the base disk will be # there even after the cleanup @@ -1933,7 +1949,7 @@ class XenAPIDom0IptablesFirewallTestCase(stubs.XenAPITestBase): in_rules = filter(lambda l: not l.startswith('#'), self._in_rules) for rule in in_rules: - if not 'nova' in rule: + if 'nova' not in rule: self.assertTrue(rule in self._out_rules, 'Rule went missing: %s' % rule) @@ -2574,54 +2590,6 @@ class SwapXapiHostTestCase(test.TestCase): "http://someserver", 'otherserver')) -class VmUtilsTestCase(test.TestCase): - """Unit tests for xenapi utils.""" - - def test_upload_image(self): - def fake_instance_system_metadata_get(context, uuid): - return dict(image_a=1, image_b=2, image_c='c', d='d') - - def fake_get_sr_path(session): - return "foo" - - class FakeInstance(dict): - def __init__(self): - super(FakeInstance, self).__init__({ - 'auto_disk_config': 'auto disk config', - 'os_type': 'os type'}) - - def __missing__(self, item): - return "whatever" - - class FakeSession(object): - def call_plugin(session_self, service, command, kwargs): - self.kwargs = kwargs - - def call_plugin_serialized(session_self, service, command, *args, - **kwargs): - self.kwargs = kwargs - - def fake_dumps(thing): - return thing - - self.stubs.Set(db, "instance_system_metadata_get", - fake_instance_system_metadata_get) - self.stubs.Set(vm_utils, "get_sr_path", fake_get_sr_path) - self.stubs.Set(pickle, "dumps", fake_dumps) - - ctx = context.get_admin_context() - - instance = FakeInstance() - session = FakeSession() - vm_utils.upload_image(ctx, session, instance, "vmi uuids", "image id") - - actual = self.kwargs['properties'] - # Inheritance happens in another place, now - expected = dict(auto_disk_config='auto disk config', - os_type='os type') - self.assertEquals(expected, actual) - - class XenAPILiveMigrateTestCase(stubs.XenAPITestBase): """Unit tests for live_migration.""" def setUp(self): diff --git a/nova/tests/virt/xenapi/imageupload/__init__.py b/nova/tests/virt/xenapi/imageupload/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/nova/tests/virt/xenapi/imageupload/__init__.py diff --git a/nova/tests/virt/xenapi/imageupload/test_glance.py b/nova/tests/virt/xenapi/imageupload/test_glance.py new file mode 100644 index 000000000..b0518228d --- /dev/null +++ b/nova/tests/virt/xenapi/imageupload/test_glance.py @@ -0,0 +1,74 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import mox + +from nova import context +from nova import test +from nova.virt.xenapi.imageupload import glance +from nova.virt.xenapi import vm_utils + + +class TestGlanceStore(test.TestCase): + def setUp(self): + super(TestGlanceStore, self).setUp() + self.store = glance.GlanceStore() + self.mox = mox.Mox() + + def tearDown(self): + super(TestGlanceStore, self).tearDown() + + def test_upload_image(self): + glance_host = '0.1.2.3' + glance_port = 8143 + glance_use_ssl = False + sr_path = '/fake/sr/path' + self.flags(glance_host=glance_host) + self.flags(glance_port=glance_port) + self.flags(glance_api_insecure=glance_use_ssl) + + def fake_get_sr_path(*_args, **_kwargs): + return sr_path + + self.stubs.Set(vm_utils, 'get_sr_path', fake_get_sr_path) + + ctx = context.RequestContext('user', 'project', auth_token='foobar') + properties = { + 'auto_disk_config': True, + 'os_type': 'default', + } + image_id = 'fake_image_uuid' + vdi_uuids = ['fake_vdi_uuid'] + instance = {'uuid': 'blah'} + instance.update(properties) + + params = {'vdi_uuids': vdi_uuids, + 'image_id': image_id, + 'glance_host': glance_host, + 'glance_port': glance_port, + 'glance_use_ssl': glance_use_ssl, + 'sr_path': sr_path, + 'auth_token': 'foobar', + 'properties': properties} + session = self.mox.CreateMockAnything() + session.call_plugin_serialized('glance', 'upload_vhd', **params) + self.mox.ReplayAll() + + self.store.upload_image(ctx, session, instance, vdi_uuids, image_id) + + self.mox.VerifyAll() diff --git a/nova/utils.py b/nova/utils.py index 52d4868c9..de9879393 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -628,7 +628,7 @@ class DynamicLoopingCall(LoopingCallBase): if not self._running: break - if not periodic_interval_max is None: + if periodic_interval_max is not None: idle = min(idle, periodic_interval_max) LOG.debug(_('Periodic task processor sleeping for %.02f ' 'seconds'), idle) diff --git a/nova/virt/baremetal/driver.py b/nova/virt/baremetal/driver.py index 9904fdcd4..631a9a8c4 100644 --- a/nova/virt/baremetal/driver.py +++ b/nova/virt/baremetal/driver.py @@ -140,7 +140,7 @@ class BareMetalDriver(driver.ComputeDriver): keyval[0] = keyval[0].strip() keyval[1] = keyval[1].strip() extra_specs[keyval[0]] = keyval[1] - if not 'cpu_arch' in extra_specs: + if 'cpu_arch' not in extra_specs: LOG.warning( _('cpu_arch is not found in instance_type_extra_specs')) extra_specs['cpu_arch'] = '' diff --git a/nova/virt/baremetal/volume_driver.py b/nova/virt/baremetal/volume_driver.py index 0a05dfedd..e92325b97 100644 --- a/nova/virt/baremetal/volume_driver.py +++ b/nova/virt/baremetal/volume_driver.py @@ -210,7 +210,7 @@ class LibvirtVolumeDriver(VolumeDriver): def _volume_driver_method(self, method_name, connection_info, *args, **kwargs): driver_type = connection_info.get('driver_volume_type') - if not driver_type in self.volume_drivers: + if driver_type not in self.volume_drivers: raise exception.VolumeDriverNotFound(driver_type=driver_type) driver = self.volume_drivers[driver_type] method = getattr(driver, method_name) diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 04eeded72..5a5bb7b13 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -125,7 +125,7 @@ class FakeDriver(driver.ComputeDriver): self.instances[name] = fake_instance def snapshot(self, context, instance, name, update_task_state): - if not instance['name'] in self.instances: + if instance['name'] not in self.instances: raise exception.InstanceNotRunning(instance_id=instance['uuid']) update_task_state(task_state=task_states.IMAGE_UPLOADING) @@ -209,7 +209,7 @@ class FakeDriver(driver.ComputeDriver): def attach_volume(self, connection_info, instance, mountpoint): """Attach the disk to the instance at mountpoint using info.""" instance_name = instance['name'] - if not instance_name in self._mounts: + if instance_name not in self._mounts: self._mounts[instance_name] = {} self._mounts[instance_name][mountpoint] = connection_info return True diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 9ed7a054c..585074f1f 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -673,7 +673,7 @@ class LibvirtDriver(driver.ComputeDriver): def volume_driver_method(self, method_name, connection_info, *args, **kwargs): driver_type = connection_info.get('driver_volume_type') - if not driver_type in self.volume_drivers: + if driver_type not in self.volume_drivers: raise exception.VolumeDriverNotFound(driver_type=driver_type) driver = self.volume_drivers[driver_type] method = getattr(driver, method_name) diff --git a/nova/virt/libvirt/firewall.py b/nova/virt/libvirt/firewall.py index 3323b8f1d..5b712cf42 100644 --- a/nova/virt/libvirt/firewall.py +++ b/nova/virt/libvirt/firewall.py @@ -29,11 +29,7 @@ LOG = logging.getLogger(__name__) CONF = cfg.CONF CONF.import_opt('use_ipv6', 'nova.netconf') -try: - import libvirt -except ImportError: - LOG.warn(_("Libvirt module could not be loaded. NWFilterFirewall will " - "not work correctly.")) +libvirt = None class NWFilterFirewall(base_firewall.FirewallDriver): @@ -47,6 +43,13 @@ class NWFilterFirewall(base_firewall.FirewallDriver): def __init__(self, virtapi, get_connection, **kwargs): super(NWFilterFirewall, self).__init__(virtapi) + global libvirt + if libvirt is None: + try: + libvirt = __import__('libvirt') + except ImportError: + LOG.warn(_("Libvirt module could not be loaded. " + "NWFilterFirewall will not work correctly.")) self._libvirt_get_connection = get_connection self.static_filters_configured = False self.handle_security_groups = False diff --git a/nova/virt/libvirt/imagecache.py b/nova/virt/libvirt/imagecache.py index 8f677b482..ea7bded95 100644 --- a/nova/virt/libvirt/imagecache.py +++ b/nova/virt/libvirt/imagecache.py @@ -305,7 +305,7 @@ class ImageCacheManager(object): backing_path = os.path.join(CONF.instances_path, CONF.base_dir_name, backing_file) - if not backing_path in inuse_images: + if backing_path not in inuse_images: inuse_images.append(backing_path) if backing_path in self.unexplained_images: @@ -464,7 +464,7 @@ class ImageCacheManager(object): # _verify_checksum returns True if the checksum is ok, and None if # there is no checksum file checksum_result = self._verify_checksum(img_id, base_file) - if not checksum_result is None: + if checksum_result is not None: image_bad = not checksum_result # Give other threads a chance to run @@ -555,7 +555,7 @@ class ImageCacheManager(object): # Elements remaining in unexplained_images might be in use inuse_backing_images = self._list_backing_images() for backing_path in inuse_backing_images: - if not backing_path in self.active_base_files: + if backing_path not in self.active_base_files: self.active_base_files.append(backing_path) # Anything left is an unknown base image diff --git a/nova/virt/libvirt/utils.py b/nova/virt/libvirt/utils.py index bd4ec685c..88110055c 100644 --- a/nova/virt/libvirt/utils.py +++ b/nova/virt/libvirt/utils.py @@ -275,7 +275,7 @@ def pick_disk_driver_name(is_block_dev=False): if is_block_dev: return "phy" else: - return "file" + return "tap" elif CONF.libvirt_type in ('kvm', 'qemu'): return "qemu" else: diff --git a/nova/virt/libvirt/volume.py b/nova/virt/libvirt/volume.py index f9a948fb5..d02db22f3 100644 --- a/nova/virt/libvirt/volume.py +++ b/nova/virt/libvirt/volume.py @@ -47,19 +47,19 @@ CONF = cfg.CONF CONF.register_opts(volume_opts) -class LibvirtVolumeDriver(object): +class LibvirtBaseVolumeDriver(object): """Base class for volume drivers.""" - def __init__(self, connection): + def __init__(self, connection, is_block_dev): self.connection = connection + self.is_block_dev = is_block_dev def connect_volume(self, connection_info, mount_device): """Connect the volume. Returns xml for libvirt.""" + conf = vconfig.LibvirtConfigGuestDisk() - conf.source_type = "block" - conf.driver_name = virtutils.pick_disk_driver_name(is_block_dev=True) + conf.driver_name = virtutils.pick_disk_driver_name(self.is_block_dev) conf.driver_format = "raw" conf.driver_cache = "none" - conf.source_path = connection_info['data']['device_path'] conf.target_dev = mount_device conf.target_bus = "virtio" conf.serial = connection_info.get('serial') @@ -70,37 +70,49 @@ class LibvirtVolumeDriver(object): pass -class LibvirtFakeVolumeDriver(LibvirtVolumeDriver): - """Driver to attach Network volumes to libvirt.""" +class LibvirtVolumeDriver(LibvirtBaseVolumeDriver): + """Class for volumes backed by local file.""" + def __init__(self, connection): + super(LibvirtVolumeDriver, + self).__init__(connection, is_block_dev=True) def connect_volume(self, connection_info, mount_device): - conf = vconfig.LibvirtConfigGuestDisk() + """Connect the volume to a local device.""" + conf = super(LibvirtVolumeDriver, + self).connect_volume(connection_info, mount_device) + conf.source_type = "block" + conf.source_path = connection_info['data']['device_path'] + return conf + + +class LibvirtFakeVolumeDriver(LibvirtBaseVolumeDriver): + """Driver to attach fake volumes to libvirt.""" + def __init__(self, connection): + super(LibvirtFakeVolumeDriver, + self).__init__(connection, is_block_dev=True) + + def connect_volume(self, connection_info, mount_device): + """Connect the volume to a fake device.""" + conf = super(LibvirtFakeVolumeDriver, + self).connect_volume(connection_info, mount_device) conf.source_type = "network" - conf.driver_name = "qemu" - conf.driver_format = "raw" - conf.driver_cache = "none" conf.source_protocol = "fake" conf.source_host = "fake" - conf.target_dev = mount_device - conf.target_bus = "virtio" - conf.serial = connection_info.get('serial') return conf -class LibvirtNetVolumeDriver(LibvirtVolumeDriver): +class LibvirtNetVolumeDriver(LibvirtBaseVolumeDriver): """Driver to attach Network volumes to libvirt.""" + def __init__(self, connection): + super(LibvirtNetVolumeDriver, + self).__init__(connection, is_block_dev=False) def connect_volume(self, connection_info, mount_device): - conf = vconfig.LibvirtConfigGuestDisk() + conf = super(LibvirtNetVolumeDriver, + self).connect_volume(connection_info, mount_device) conf.source_type = "network" - conf.driver_name = virtutils.pick_disk_driver_name(is_block_dev=False) - conf.driver_format = "raw" - conf.driver_cache = "none" conf.source_protocol = connection_info['driver_volume_type'] conf.source_host = connection_info['data']['name'] - conf.target_dev = mount_device - conf.target_bus = "virtio" - conf.serial = connection_info.get('serial') netdisk_properties = connection_info['data'] auth_enabled = netdisk_properties.get('auth_enabled') if (conf.source_protocol == 'rbd' and @@ -118,8 +130,11 @@ class LibvirtNetVolumeDriver(LibvirtVolumeDriver): return conf -class LibvirtISCSIVolumeDriver(LibvirtVolumeDriver): +class LibvirtISCSIVolumeDriver(LibvirtBaseVolumeDriver): """Driver to attach Network volumes to libvirt.""" + def __init__(self, connection): + super(LibvirtISCSIVolumeDriver, + self).__init__(connection, is_block_dev=False) def _run_iscsiadm(self, iscsi_properties, iscsi_command, **kwargs): check_exit_code = kwargs.pop('check_exit_code', 0) @@ -141,6 +156,9 @@ class LibvirtISCSIVolumeDriver(LibvirtVolumeDriver): @lockutils.synchronized('connect_volume', 'nova-') def connect_volume(self, connection_info, mount_device): """Attach the volume to instance_name.""" + conf = super(LibvirtISCSIVolumeDriver, + self).connect_volume(connection_info, mount_device) + iscsi_properties = connection_info['data'] # NOTE(vish): If we are on the same host as nova volume, the # discovery makes the target so we don't need to @@ -204,15 +222,15 @@ class LibvirtISCSIVolumeDriver(LibvirtVolumeDriver): "(after %(tries)s rescans)") % locals()) - connection_info['data']['device_path'] = host_device - sup = super(LibvirtISCSIVolumeDriver, self) - return sup.connect_volume(connection_info, mount_device) + conf.source_type = "block" + conf.source_path = host_device + return conf @lockutils.synchronized('connect_volume', 'nova-') def disconnect_volume(self, connection_info, mount_device): """Detach the volume from instance_name.""" - sup = super(LibvirtISCSIVolumeDriver, self) - sup.disconnect_volume(connection_info, mount_device) + super(LibvirtISCSIVolumeDriver, + self).disconnect_volume(connection_info, mount_device) iscsi_properties = connection_info['data'] # NOTE(vish): Only disconnect from the target if no luns from the # target are in use. diff --git a/nova/virt/libvirt/volume_nfs.py b/nova/virt/libvirt/volume_nfs.py index b5083937d..70bb8c38f 100644 --- a/nova/virt/libvirt/volume_nfs.py +++ b/nova/virt/libvirt/volume_nfs.py @@ -38,27 +38,24 @@ CONF = cfg.CONF CONF.register_opts(volume_opts) -class NfsVolumeDriver(volume.LibvirtVolumeDriver): +class NfsVolumeDriver(volume.LibvirtBaseVolumeDriver): """Class implements libvirt part of volume driver for NFS.""" - def __init__(self, *args, **kwargs): - """Create back-end to nfs and check connection.""" - super(NfsVolumeDriver, self).__init__(*args, **kwargs) + def __init__(self, connection): + """Create back-end to nfs.""" + super(NfsVolumeDriver, + self).__init__(connection, is_block_dev=False) def connect_volume(self, connection_info, mount_device): """Connect the volume. Returns xml for libvirt.""" + conf = super(NfsVolumeDriver, + self).connect_volume(connection_info, mount_device) path = self._ensure_mounted(connection_info['data']['export']) path = os.path.join(path, connection_info['data']['name']) - connection_info['data']['device_path'] = path - conf = super(NfsVolumeDriver, self).connect_volume(connection_info, - mount_device) conf.source_type = 'file' + conf.source_path = path return conf - def disconnect_volume(self, connection_info, mount_device): - """Disconnect the volume.""" - pass - def _ensure_mounted(self, nfs_export): """ @type nfs_export: string diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index 666e46754..bdadfbc38 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -89,7 +89,7 @@ def reset(): def reset_table(table): - if not table in _CLASSES: + if table not in _CLASSES: return _db_content[table] = {} @@ -417,7 +417,7 @@ class SessionBase(object): def VBD_add_to_other_config(self, _1, vbd_ref, key, value): db_ref = _db_content['VBD'][vbd_ref] - if not 'other_config' in db_ref: + if 'other_config' not in db_ref: db_ref['other_config'] = {} if key in db_ref['other_config']: raise Failure(['MAP_DUPLICATE_KEY', 'VBD', 'other_config', @@ -426,7 +426,7 @@ class SessionBase(object): def VBD_get_other_config(self, _1, vbd_ref): db_ref = _db_content['VBD'][vbd_ref] - if not 'other_config' in db_ref: + if 'other_config' not in db_ref: return {} return db_ref['other_config'] @@ -497,14 +497,14 @@ class SessionBase(object): def VM_remove_from_xenstore_data(self, _1, vm_ref, key): db_ref = _db_content['VM'][vm_ref] - if not 'xenstore_data' in db_ref: + if 'xenstore_data' not in db_ref: return if key in db_ref['xenstore_data']: del db_ref['xenstore_data'][key] def VM_add_to_xenstore_data(self, _1, vm_ref, key, value): db_ref = _db_content['VM'][vm_ref] - if not 'xenstore_data' in db_ref: + if 'xenstore_data' not in db_ref: db_ref['xenstore_data'] = {} db_ref['xenstore_data'][key] = value @@ -513,14 +513,14 @@ class SessionBase(object): def VDI_remove_from_other_config(self, _1, vdi_ref, key): db_ref = _db_content['VDI'][vdi_ref] - if not 'other_config' in db_ref: + if 'other_config' not in db_ref: return if key in db_ref['other_config']: del db_ref['other_config'][key] def VDI_add_to_other_config(self, _1, vdi_ref, key, value): db_ref = _db_content['VDI'][vdi_ref] - if not 'other_config' in db_ref: + if 'other_config' not in db_ref: db_ref['other_config'] = {} if key in db_ref['other_config']: raise Failure(['MAP_DUPLICATE_KEY', 'VDI', 'other_config', diff --git a/nova/virt/xenapi/imageupload/__init__.py b/nova/virt/xenapi/imageupload/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/nova/virt/xenapi/imageupload/__init__.py diff --git a/nova/virt/xenapi/imageupload/glance.py b/nova/virt/xenapi/imageupload/glance.py new file mode 100644 index 000000000..adc06f65b --- /dev/null +++ b/nova/virt/xenapi/imageupload/glance.py @@ -0,0 +1,54 @@ +# Copyright 2013 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. + +from nova.image import glance +from nova.openstack.common import cfg +import nova.openstack.common.log as logging +from nova.virt.xenapi import vm_utils + +LOG = logging.getLogger(__name__) + +CONF = cfg.CONF + + +class GlanceStore(object): + + def upload_image(self, context, session, instance, vdi_uuids, image_id): + """Requests that the Glance plugin bundle the specified VDIs and + push them into Glance using the specified human-friendly name. + """ + # NOTE(sirp): Currently we only support uploading images as VHD, there + # is no RAW equivalent (yet) + LOG.debug(_("Asking xapi to upload to glance %(vdi_uuids)s as" + " ID %(image_id)s"), locals(), instance=instance) + + glance_api_servers = glance.get_api_servers() + glance_host, glance_port, glance_use_ssl = glance_api_servers.next() + + properties = { + 'auto_disk_config': instance['auto_disk_config'], + 'os_type': instance['os_type'] or CONF.default_os_type, + } + + params = {'vdi_uuids': vdi_uuids, + 'image_id': image_id, + 'glance_host': glance_host, + 'glance_port': glance_port, + 'glance_use_ssl': glance_use_ssl, + 'sr_path': vm_utils.get_sr_path(session), + 'auth_token': getattr(context, 'auth_token', None), + 'properties': properties} + + session.call_plugin_serialized('glance', 'upload_vhd', **params) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 582a9320a..6c4c3d4bc 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -715,35 +715,6 @@ def _find_cached_image(session, image_id, sr_ref): return cached_images.get(image_id) -def upload_image(context, session, instance, vdi_uuids, image_id): - """Requests that the Glance plugin bundle the specified VDIs and - push them into Glance using the specified human-friendly name. - """ - # NOTE(sirp): Currently we only support uploading images as VHD, there - # is no RAW equivalent (yet) - LOG.debug(_("Asking xapi to upload %(vdi_uuids)s as" - " ID %(image_id)s"), locals(), instance=instance) - - glance_api_servers = glance.get_api_servers() - glance_host, glance_port, glance_use_ssl = glance_api_servers.next() - - properties = { - 'auto_disk_config': instance['auto_disk_config'], - 'os_type': instance['os_type'] or CONF.default_os_type, - } - - params = {'vdi_uuids': vdi_uuids, - 'image_id': image_id, - 'glance_host': glance_host, - 'glance_port': glance_port, - 'glance_use_ssl': glance_use_ssl, - 'sr_path': get_sr_path(session), - 'auth_token': getattr(context, 'auth_token', None), - 'properties': properties} - - session.call_plugin_serialized('glance', 'upload_vhd', **params) - - def resize_disk(session, instance, vdi_ref, instance_type): # Copy VDI over to something we can resize # NOTE(jerdfelt): Would be nice to just set vdi_ref to read/write @@ -1589,7 +1560,7 @@ def _find_iso_sr(session): if not sr_rec['content_type'] == 'iso': LOG.debug(_("ISO: not iso content")) continue - if not 'i18n-key' in sr_rec['other_config']: + if 'i18n-key' not in sr_rec['other_config']: LOG.debug(_("ISO: iso content_type, no 'i18n-key' key")) continue if not sr_rec['other_config']['i18n-key'] == 'local-storage-iso': diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 4a8372cda..8d3a3bed2 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -59,7 +59,10 @@ xenapi_vmops_opts = [ 'to go to running state'), cfg.StrOpt('xenapi_vif_driver', default='nova.virt.xenapi.vif.XenAPIBridgeDriver', - help='The XenAPI VIF driver using XenServer Network APIs.') + help='The XenAPI VIF driver using XenServer Network APIs.'), + cfg.StrOpt('xenapi_image_upload_handler', + default='nova.virt.xenapi.imageupload.glance.GlanceStore', + help='Object Store Driver used to handle image uploads.'), ] CONF = cfg.CONF @@ -161,6 +164,11 @@ class VMOps(object): self.vif_driver = vif_impl(xenapi_session=self._session) self.default_root_dev = '/dev/sda' + msg = _("Importing image upload handler: %s") + LOG.debug(msg % CONF.xenapi_image_upload_handler) + self.image_upload_handler = importutils.import_object( + CONF.xenapi_image_upload_handler) + @property def agent_enabled(self): return not CONF.xenapi_disable_agent @@ -661,9 +669,11 @@ class VMOps(object): coalesce together, so, we must wait for this coalescing to occur to get a stable representation of the data on disk. - 3. Push-to-glance: Once coalesced, we call a plugin on the XenServer - that will bundle the VHDs together and then push the bundle into - Glance. + 3. Push-to-data-store: Once coalesced, we call a plugin on the + XenServer that will bundle the VHDs together and then push the + bundle. Depending on the configured value of + 'xenapi_image_upload_handler', image data may be pushed to + Glance or the specified data store. """ vm_ref = self._get_vm_opaque_ref(instance) @@ -674,8 +684,11 @@ class VMOps(object): update_task_state) as vdi_uuids: update_task_state(task_state=task_states.IMAGE_UPLOADING, expected_state=task_states.IMAGE_PENDING_UPLOAD) - vm_utils.upload_image( - context, self._session, instance, vdi_uuids, image_id) + self.image_upload_handler.upload_image(context, + self._session, + instance, + vdi_uuids, + image_id) LOG.debug(_("Finished snapshot and upload for VM"), instance=instance) diff --git a/openstack-common.conf b/openstack-common.conf index a0b14e651..f9d38ea1d 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -1,7 +1,7 @@ [DEFAULT] # The list of modules to copy from openstack-common -modules=cfg,cliutils,context,excutils,eventlet_backdoor,fileutils,gettextutils,importutils,iniparser,jsonutils,local,lockutils,log,network_utils,notifier,plugin,policy,rootwrap,setup,timeutils,rpc,uuidutils +modules=cfg,cliutils,context,excutils,eventlet_backdoor,fileutils,gettextutils,importutils,iniparser,jsonutils,local,lockutils,log,network_utils,notifier,plugin,policy,rootwrap,setup,timeutils,rpc,uuidutils,install_venv_common # The base module to hold the copy of openstack.common base=nova diff --git a/tools/install_venv.py b/tools/install_venv.py index b1ceb74f0..abf56ea0e 100644 --- a/tools/install_venv.py +++ b/tools/install_venv.py @@ -1,4 +1,3 @@ - # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the @@ -6,6 +5,7 @@ # All Rights Reserved. # # Copyright 2010 OpenStack, LLC +# Copyright 2013 IBM Corp. # # 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 @@ -19,186 +19,10 @@ # License for the specific language governing permissions and limitations # under the License. -"""Installation script for Nova's development virtualenv.""" - -import optparse import os -import subprocess import sys - -ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) -VENV = os.path.join(ROOT, '.venv') -PIP_REQUIRES = os.path.join(ROOT, 'tools', 'pip-requires') -TEST_REQUIRES = os.path.join(ROOT, 'tools', 'test-requires') -PY_VERSION = "python%s.%s" % (sys.version_info[0], sys.version_info[1]) - - -def die(message, *args): - print >> sys.stderr, message % args - sys.exit(1) - - -def check_python_version(): - if sys.version_info < (2, 6): - die("Need Python Version >= 2.6") - - -def run_command_with_code(cmd, redirect_output=True, check_exit_code=True): - """Runs a command in an out-of-process shell. - - Returns the output of that command. Working directory is ROOT. - """ - if redirect_output: - stdout = subprocess.PIPE - else: - stdout = None - - proc = subprocess.Popen(cmd, cwd=ROOT, stdout=stdout) - output = proc.communicate()[0] - if check_exit_code and proc.returncode != 0: - die('Command "%s" failed.\n%s', ' '.join(cmd), output) - return (output, proc.returncode) - - -def run_command(cmd, redirect_output=True, check_exit_code=True): - return run_command_with_code(cmd, redirect_output, check_exit_code)[0] - - -class Distro(object): - - def check_cmd(self, cmd): - return bool(run_command(['which', cmd], check_exit_code=False).strip()) - - def install_virtualenv(self): - if self.check_cmd('virtualenv'): - return - - if self.check_cmd('easy_install'): - print 'Installing virtualenv via easy_install...', - if run_command(['easy_install', 'virtualenv']): - print 'Succeeded' - return - else: - print 'Failed' - - die('ERROR: virtualenv not found.\n\nNova development' - ' requires virtualenv, please install it using your' - ' favorite package management tool') - - def post_process(self): - """Any distribution-specific post-processing gets done here. - - In particular, this is useful for applying patches to code inside - the venv. - """ - pass - - -class Fedora(Distro): - """This covers all Fedora-based distributions. - - Includes: Fedora, RHEL, CentOS, Scientific Linux""" - - def check_pkg(self, pkg): - return run_command_with_code(['rpm', '-q', pkg], - check_exit_code=False)[1] == 0 - - def yum_install(self, pkg, **kwargs): - print "Attempting to install '%s' via yum" % pkg - run_command(['sudo', 'yum', 'install', '-y', pkg], **kwargs) - - def apply_patch(self, originalfile, patchfile): - run_command(['patch', originalfile, patchfile]) - - def install_virtualenv(self): - if self.check_cmd('virtualenv'): - return - - if not self.check_pkg('python-virtualenv'): - self.yum_install('python-virtualenv', check_exit_code=False) - - super(Fedora, self).install_virtualenv() - - def post_process(self): - """Workaround for a bug in eventlet. - - This currently affects RHEL6.1, but the fix can safely be - applied to all RHEL and Fedora distributions. - - This can be removed when the fix is applied upstream. - - Nova: https://bugs.launchpad.net/nova/+bug/884915 - Upstream: https://bitbucket.org/which_linden/eventlet/issue/89 - """ - - # Install "patch" program if it's not there - if not self.check_pkg('patch'): - self.yum_install('patch') - - # Apply the eventlet patch - self.apply_patch(os.path.join(VENV, 'lib', PY_VERSION, 'site-packages', - 'eventlet/green/subprocess.py'), - 'contrib/redhat-eventlet.patch') - - -def get_distro(): - if (os.path.exists('/etc/fedora-release') or - os.path.exists('/etc/redhat-release')): - return Fedora() - else: - return Distro() - - -def check_dependencies(): - get_distro().install_virtualenv() - - -def create_virtualenv(venv=VENV, no_site_packages=True): - """Creates the virtual environment and installs PIP. - - Creates the virtual environment and installs PIP only into the - virtual environment. - """ - print 'Creating venv...', - if no_site_packages: - run_command(['virtualenv', '-q', '--no-site-packages', VENV]) - else: - run_command(['virtualenv', '-q', VENV]) - print 'done.' - print 'Installing pip in virtualenv...', - if not run_command(['tools/with_venv.sh', 'easy_install', - 'pip>1.0']).strip(): - die("Failed to install pip.") - print 'done.' - - -def pip_install(*args): - run_command(['tools/with_venv.sh', - 'pip', 'install', '--upgrade'] + list(args), - redirect_output=False) - - -def install_dependencies(venv=VENV): - print 'Installing dependencies with pip (this can take a while)...' - - # First things first, make sure our venv has the latest pip and distribute. - # NOTE: we keep pip at version 1.1 since the most recent version causes - # the .venv creation to fail. See: - # https://bugs.launchpad.net/nova/+bug/1047120 - pip_install('pip==1.1') - pip_install('distribute') - - # Install greenlet by hand - just listing it in the requires file does not - # get it in stalled in the right order - pip_install('greenlet') - - pip_install('-r', PIP_REQUIRES) - pip_install('-r', TEST_REQUIRES) - - -def post_process(): - get_distro().post_process() +import install_venv_common as install_venv def print_help(): @@ -223,22 +47,21 @@ def print_help(): print help -def parse_args(): - """Parses command-line arguments.""" - parser = optparse.OptionParser() - parser.add_option("-n", "--no-site-packages", dest="no_site_packages", - default=False, action="store_true", - help="Do not inherit packages from global Python install") - return parser.parse_args() - - def main(argv): - (options, args) = parse_args() - check_python_version() - check_dependencies() - create_virtualenv(no_site_packages=options.no_site_packages) - install_dependencies() - post_process() + root = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + venv = os.path.join(root, '.venv') + pip_requires = os.path.join(root, 'tools', 'pip-requires') + test_requires = os.path.join(root, 'tools', 'test-requires') + py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1]) + project = 'Nova' + install = install_venv.InstallVenv(root, venv, pip_requires, test_requires, + py_version, project) + options = install.parse_args(argv) + install.check_python_version() + install.check_dependencies() + install.create_virtualenv(no_site_packages=options.no_site_packages) + install.install_dependencies() + install.post_process() print_help() if __name__ == '__main__': diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py new file mode 100644 index 000000000..b15011a00 --- /dev/null +++ b/tools/install_venv_common.py @@ -0,0 +1,225 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 OpenStack, LLC +# Copyright 2013 IBM Corp. +# +# 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. + +"""Provides methods needed by installation script for OpenStack development +virtual environments. + +Synced in from openstack-common +""" + +import os +import subprocess +import sys + +from nova.openstack.common import cfg + + +class InstallVenv(object): + + def __init__(self, root, venv, pip_requires, test_requires, py_version, + project): + self.root = root + self.venv = venv + self.pip_requires = pip_requires + self.test_requires = test_requires + self.py_version = py_version + self.project = project + + def die(self, message, *args): + print >> sys.stderr, message % args + sys.exit(1) + + def check_python_version(self): + if sys.version_info < (2, 6): + self.die("Need Python Version >= 2.6") + + def run_command_with_code(self, cmd, redirect_output=True, + check_exit_code=True): + """Runs a command in an out-of-process shell. + + Returns the output of that command. Working directory is ROOT. + """ + if redirect_output: + stdout = subprocess.PIPE + else: + stdout = None + + proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout) + output = proc.communicate()[0] + if check_exit_code and proc.returncode != 0: + self.die('Command "%s" failed.\n%s', ' '.join(cmd), output) + return (output, proc.returncode) + + def run_command(self, cmd, redirect_output=True, check_exit_code=True): + return self.run_command_with_code(cmd, redirect_output, + check_exit_code)[0] + + def get_distro(self): + if (os.path.exists('/etc/fedora-release') or + os.path.exists('/etc/redhat-release')): + return Fedora(self.root, self.venv, self.pip_requires, + self.test_requires, self.py_version, self.project) + else: + return Distro(self.root, self.venv, self.pip_requires, + self.test_requires, self.py_version, self.project) + + def check_dependencies(self): + self.get_distro().install_virtualenv() + + def create_virtualenv(self, no_site_packages=True): + """Creates the virtual environment and installs PIP. + + Creates the virtual environment and installs PIP only into the + virtual environment. + """ + if not os.path.isdir(self.venv): + print 'Creating venv...', + if no_site_packages: + self.run_command(['virtualenv', '-q', '--no-site-packages', + self.venv]) + else: + self.run_command(['virtualenv', '-q', self.venv]) + print 'done.' + print 'Installing pip in virtualenv...', + if not self.run_command(['tools/with_venv.sh', 'easy_install', + 'pip>1.0']).strip(): + self.die("Failed to install pip.") + print 'done.' + else: + print "venv already exists..." + pass + + def pip_install(self, *args): + self.run_command(['tools/with_venv.sh', + 'pip', 'install', '--upgrade'] + list(args), + redirect_output=False) + + def install_dependencies(self): + print 'Installing dependencies with pip (this can take a while)...' + + # First things first, make sure our venv has the latest pip and + # distribute. + # NOTE: we keep pip at version 1.1 since the most recent version causes + # the .venv creation to fail. See: + # https://bugs.launchpad.net/nova/+bug/1047120 + self.pip_install('pip==1.1') + self.pip_install('distribute') + + # Install greenlet by hand - just listing it in the requires file does + # not + # get it installed in the right order + self.pip_install('greenlet') + + self.pip_install('-r', self.pip_requires) + self.pip_install('-r', self.test_requires) + + def post_process(self): + self.get_distro().post_process() + + def parse_args(self, argv): + """Parses command-line arguments.""" + cli_opts = [ + cfg.BoolOpt('no-site-packages', + default=False, + short='n', + help="Do not inherit packages from global Python" + "install"), + ] + CLI = cfg.ConfigOpts() + CLI.register_cli_opts(cli_opts) + CLI(argv[1:]) + return CLI + + +class Distro(InstallVenv): + + def check_cmd(self, cmd): + return bool(self.run_command(['which', cmd], + check_exit_code=False).strip()) + + def install_virtualenv(self): + if self.check_cmd('virtualenv'): + return + + if self.check_cmd('easy_install'): + print 'Installing virtualenv via easy_install...', + if self.run_command(['easy_install', 'virtualenv']): + print 'Succeeded' + return + else: + print 'Failed' + + self.die('ERROR: virtualenv not found.\n\n%s development' + ' requires virtualenv, please install it using your' + ' favorite package management tool' % self.project) + + def post_process(self): + """Any distribution-specific post-processing gets done here. + + In particular, this is useful for applying patches to code inside + the venv. + """ + pass + + +class Fedora(Distro): + """This covers all Fedora-based distributions. + + Includes: Fedora, RHEL, CentOS, Scientific Linux + """ + + def check_pkg(self, pkg): + return self.run_command_with_code(['rpm', '-q', pkg], + check_exit_code=False)[1] == 0 + + def yum_install(self, pkg, **kwargs): + print "Attempting to install '%s' via yum" % pkg + self.run_command(['sudo', 'yum', 'install', '-y', pkg], **kwargs) + + def apply_patch(self, originalfile, patchfile): + self.run_command(['patch', originalfile, patchfile]) + + def install_virtualenv(self): + if self.check_cmd('virtualenv'): + return + + if not self.check_pkg('python-virtualenv'): + self.yum_install('python-virtualenv', check_exit_code=False) + + super(Fedora, self).install_virtualenv() + + def post_process(self): + """Workaround for a bug in eventlet. + + This currently affects RHEL6.1, but the fix can safely be + applied to all RHEL and Fedora distributions. + + This can be removed when the fix is applied upstream. + + Nova: https://bugs.launchpad.net/nova/+bug/884915 + Upstream: https://bitbucket.org/which_linden/eventlet/issue/89 + """ + + # Install "patch" program if it's not there + if not self.check_pkg('patch'): + self.yum_install('patch') + + # Apply the eventlet patch + self.apply_patch(os.path.join(self.venv, 'lib', self.py_version, + 'site-packages', + 'eventlet/green/subprocess.py'), + 'contrib/redhat-eventlet.patch') |