diff options
50 files changed, 562 insertions, 405 deletions
diff --git a/doc/api_samples/os-volumes/snapshot-create-resp.json b/doc/api_samples/os-volumes/snapshot-create-resp.json index 1a14bea01..a8dd57d84 100644 --- a/doc/api_samples/os-volumes/snapshot-create-resp.json +++ b/doc/api_samples/os-volumes/snapshot-create-resp.json @@ -6,27 +6,6 @@ "id": 100, "size": 100, "status": "available", - "volumeId": { - "attach_status": "attached", - "availability_zone": "fakeaz", - "created_at": "1999-01-01T01:01:01.000000", - "display_description": "displaydesc", - "display_name": "displayname", - "host": "fakehost", - "id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c", - "instance_uuid": "fakeuuid", - "mountpoint": "/", - "name": "vol name", - "project_id": "fakeproject", - "size": 1, - "snapshot_id": null, - "status": "fakestatus", - "user_id": "fakeuser", - "volume_metadata": [], - "volume_type": { - "name": "vol_type_name" - }, - "volume_type_id": "fakevoltype" - } + "volumeId": "521752a6-acf6-4b2d-bc7a-119f9148cd8c" } }
\ No newline at end of file diff --git a/doc/api_samples/os-volumes/snapshot-create-resp.xml b/doc/api_samples/os-volumes/snapshot-create-resp.xml index ad815f723..654bf3d34 100644 --- a/doc/api_samples/os-volumes/snapshot-create-resp.xml +++ b/doc/api_samples/os-volumes/snapshot-create-resp.xml @@ -1,2 +1,2 @@ <?xml version='1.0' encoding='UTF-8'?> -<snapshot status="available" displayDescription="Daily backup" displayName="snap-001" volumeId="{'instance_uuid': 'fakeuuid', 'status': 'fakestatus', 'user_id': 'fakeuser', 'name': 'vol name', 'display_name': 'displayname', 'availability_zone': 'fakeaz', 'created_at': datetime.datetime(1999, 1, 1, 1, 1, 1), 'attach_status': 'attached', 'display_description': 'displaydesc', 'host': 'fakehost', 'volume_type_id': 'fakevoltype', 'volume_metadata': [], 'volume_type': {'name': 'vol_type_name'}, 'snapshot_id': None, 'mountpoint': '/', 'project_id': 'fakeproject', 'id': u'521752a6-acf6-4b2d-bc7a-119f9148cd8c', 'size': 1}" id="100" createdAt="2013-02-25 16:27:36.840121" size="100"/>
\ No newline at end of file +<snapshot status="available" displayDescription="Daily backup" displayName="snap-001" volumeId="521752a6-acf6-4b2d-bc7a-119f9148cd8c" id="100" createdAt="2013-02-25 16:27:36.840121" size="100"/>
\ No newline at end of file diff --git a/nova/api/openstack/compute/contrib/disk_config.py b/nova/api/openstack/compute/contrib/disk_config.py index 2c4d24332..f2b906144 100644 --- a/nova/api/openstack/compute/contrib/disk_config.py +++ b/nova/api/openstack/compute/contrib/disk_config.py @@ -21,7 +21,7 @@ from webob import exc from nova.api.openstack import extensions from nova.api.openstack import wsgi from nova.api.openstack import xmlutil -from nova import utils +from nova.openstack.common import strutils ALIAS = 'OS-DCF' XMLNS_DCF = "http://docs.openstack.org/compute/ext/disk_config/api/v1.1" @@ -65,7 +65,7 @@ class ImageDiskConfigController(wsgi.Controller): metadata = image['metadata'] if INTERNAL_DISK_CONFIG in metadata: raw_value = metadata[INTERNAL_DISK_CONFIG] - value = utils.bool_from_str(raw_value) + value = strutils.bool_from_string(raw_value) image[API_DISK_CONFIG] = disk_config_to_api(value) @wsgi.extends diff --git a/nova/api/openstack/compute/contrib/evacuate.py b/nova/api/openstack/compute/contrib/evacuate.py index 587e231d1..7eee99ed1 100644 --- a/nova/api/openstack/compute/contrib/evacuate.py +++ b/nova/api/openstack/compute/contrib/evacuate.py @@ -21,6 +21,7 @@ from nova.api.openstack import wsgi from nova import compute from nova import exception from nova.openstack.common import log as logging +from nova.openstack.common import strutils from nova import utils LOG = logging.getLogger(__name__) @@ -47,7 +48,7 @@ class Controller(wsgi.Controller): evacuate_body = body["evacuate"] host = evacuate_body["host"] - on_shared_storage = utils.bool_from_str( + on_shared_storage = strutils.bool_from_string( evacuate_body["onSharedStorage"]) password = None diff --git a/nova/api/openstack/compute/contrib/flavormanage.py b/nova/api/openstack/compute/contrib/flavormanage.py index c36b40125..086c541dc 100644 --- a/nova/api/openstack/compute/contrib/flavormanage.py +++ b/nova/api/openstack/compute/contrib/flavormanage.py @@ -58,18 +58,20 @@ class FlavorManageController(wsgi.Controller): vals = body['flavor'] name = vals['name'] flavorid = vals.get('id') - memory_mb = vals.get('ram') + memory = vals.get('ram') vcpus = vals.get('vcpus') root_gb = vals.get('disk') - ephemeral_gb = vals.get('OS-FLV-EXT-DATA:ephemeral') - swap = vals.get('swap') - rxtx_factor = vals.get('rxtx_factor') + ephemeral_gb = vals.get('OS-FLV-EXT-DATA:ephemeral', 0) + swap = vals.get('swap', 0) + rxtx_factor = vals.get('rxtx_factor', 1.0) is_public = vals.get('os-flavor-access:is_public', True) try: - flavor = flavors.create(name, memory_mb, vcpus, - root_gb, ephemeral_gb, flavorid, - swap, rxtx_factor, is_public) + flavor = flavors.create(name, memory, vcpus, root_gb, + ephemeral_gb=ephemeral_gb, + flavorid=flavorid, swap=swap, + rxtx_factor=rxtx_factor, + is_public=is_public) req.cache_db_flavor(flavor) except (exception.InstanceTypeExists, exception.InstanceTypeIdExists) as err: diff --git a/nova/api/openstack/compute/contrib/volumes.py b/nova/api/openstack/compute/contrib/volumes.py index 5b2097365..ea1fb1f21 100644 --- a/nova/api/openstack/compute/contrib/volumes.py +++ b/nova/api/openstack/compute/contrib/volumes.py @@ -25,6 +25,7 @@ from nova.api.openstack import xmlutil from nova import compute from nova import exception from nova.openstack.common import log as logging +from nova.openstack.common import strutils from nova.openstack.common import uuidutils from nova import utils from nova import volume @@ -624,7 +625,7 @@ class SnapshotController(wsgi.Controller): msg = _("Invalid value '%s' for force.") % force raise exception.InvalidParameterValue(err=msg) - if utils.bool_from_str(force): + if strutils.bool_from_string(force): new_snapshot = self.volume_api.create_snapshot_force(context, vol, snapshot.get('display_name'), diff --git a/nova/api/openstack/compute/flavors.py b/nova/api/openstack/compute/flavors.py index 42b627cf4..744fe5e93 100644 --- a/nova/api/openstack/compute/flavors.py +++ b/nova/api/openstack/compute/flavors.py @@ -23,6 +23,7 @@ from nova.api.openstack import wsgi from nova.api.openstack import xmlutil from nova.compute import flavors from nova import exception +from nova.openstack.common import strutils def make_flavor(elem, detailed=False): @@ -91,25 +92,20 @@ class Controller(wsgi.Controller): return self._view_builder.show(req, flavor) - def _get_is_public(self, req): + def _parse_is_public(self, is_public): """Parse is_public into something usable.""" - is_public = req.params.get('is_public', None) if is_public is None: # preserve default value of showing only public flavors return True - elif is_public is True or \ - is_public.lower() in ['t', 'true', 'yes', '1']: - return True - elif is_public is False or \ - is_public.lower() in ['f', 'false', 'no', '0']: - return False - elif is_public.lower() == 'none': - # value to match all flavors, ignore is_public + elif is_public == 'none': return None else: - msg = _('Invalid is_public filter [%s]') % req.params['is_public'] - raise webob.exc.HTTPBadRequest(explanation=msg) + try: + return strutils.bool_from_string(is_public, strict=True) + except ValueError: + msg = _('Invalid is_public filter [%s]') % is_public + raise webob.exc.HTTPBadRequest(explanation=msg) def _get_flavors(self, req): """Helper function that returns a list of flavor dicts.""" @@ -118,7 +114,8 @@ class Controller(wsgi.Controller): context = req.environ['nova.context'] if context.is_admin: # Only admin has query access to all flavor types - filters['is_public'] = self._get_is_public(req) + filters['is_public'] = self._parse_is_public( + req.params.get('is_public', None)) else: filters['is_public'] = True filters['disabled'] = False diff --git a/nova/api/openstack/compute/servers.py b/nova/api/openstack/compute/servers.py index 30f8e6fe8..cb3f7ad49 100644 --- a/nova/api/openstack/compute/servers.py +++ b/nova/api/openstack/compute/servers.py @@ -33,6 +33,7 @@ from nova import exception from nova.openstack.common import importutils from nova.openstack.common import log as logging from nova.openstack.common.rpc import common as rpc_common +from nova.openstack.common import strutils from nova.openstack.common import timeutils from nova.openstack.common import uuidutils from nova import utils @@ -185,7 +186,8 @@ class CommonDeserializer(wsgi.MetadataXMLDeserializer): res_id = server_node.getAttribute('return_reservation_id') if res_id: - server['return_reservation_id'] = utils.bool_from_str(res_id) + server['return_reservation_id'] = \ + strutils.bool_from_string(res_id) scheduler_hints = self._extract_scheduler_hints(server_node) if scheduler_hints: @@ -253,7 +255,7 @@ class CommonDeserializer(wsgi.MetadataXMLDeserializer): for attr in attributes: value = child.getAttribute(attr) if value: - mapping[attr] = utils.bool_from_str(value) + mapping[attr] = strutils.bool_from_string(value) block_device_mapping.append(mapping) return block_device_mapping else: @@ -826,7 +828,7 @@ class Controller(wsgi.Controller): for bdm in block_device_mapping: self._validate_device_name(bdm["device_name"]) if 'delete_on_termination' in bdm: - bdm['delete_on_termination'] = utils.bool_from_str( + bdm['delete_on_termination'] = strutils.bool_from_string( bdm['delete_on_termination']) ret_resv_id = False @@ -991,7 +993,7 @@ class Controller(wsgi.Controller): update_dict['access_ip_v6'] = access_ipv6.strip() if 'auto_disk_config' in body['server']: - auto_disk_config = utils.bool_from_str( + auto_disk_config = strutils.bool_from_string( body['server']['auto_disk_config']) update_dict['auto_disk_config'] = auto_disk_config diff --git a/nova/cmd/all.py b/nova/cmd/all.py index a6426ed33..f510069b6 100644 --- a/nova/cmd/all.py +++ b/nova/cmd/all.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation diff --git a/nova/cmd/api.py b/nova/cmd/api.py index 811171afc..a7f6313b0 100644 --- a/nova/cmd/api.py +++ b/nova/cmd/api.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the diff --git a/nova/cmd/api_ec2.py b/nova/cmd/api_ec2.py index 2d78c58e5..2b3b942c8 100644 --- a/nova/cmd/api_ec2.py +++ b/nova/cmd/api_ec2.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the diff --git a/nova/cmd/api_metadata.py b/nova/cmd/api_metadata.py index b2acee33c..ab235df57 100644 --- a/nova/cmd/api_metadata.py +++ b/nova/cmd/api_metadata.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the diff --git a/nova/cmd/api_os_compute.py b/nova/cmd/api_os_compute.py index c4cb7982e..196abfef5 100644 --- a/nova/cmd/api_os_compute.py +++ b/nova/cmd/api_os_compute.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the diff --git a/nova/cmd/baremetal_manage.py b/nova/cmd/baremetal_manage.py index 4b659db31..e1224664a 100644 --- a/nova/cmd/baremetal_manage.py +++ b/nova/cmd/baremetal_manage.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright (c) 2011 X.commerce, a business unit of eBay Inc. diff --git a/nova/cmd/cells.py b/nova/cmd/cells.py index 9e6fae402..35fa9b64f 100644 --- a/nova/cmd/cells.py +++ b/nova/cmd/cells.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright (c) 2012 Rackspace Hosting diff --git a/nova/cmd/cert.py b/nova/cmd/cert.py index 668d8b0a4..1338b8dc8 100644 --- a/nova/cmd/cert.py +++ b/nova/cmd/cert.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 OpenStack Foundation diff --git a/nova/cmd/clear_rabbit_queues.py b/nova/cmd/clear_rabbit_queues.py index 6748949ae..c15c2b058 100644 --- a/nova/cmd/clear_rabbit_queues.py +++ b/nova/cmd/clear_rabbit_queues.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright (c) 2011 OpenStack Foundation diff --git a/nova/cmd/compute.py b/nova/cmd/compute.py index 644462dde..0aae286a4 100644 --- a/nova/cmd/compute.py +++ b/nova/cmd/compute.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the diff --git a/nova/cmd/conductor.py b/nova/cmd/conductor.py index ba1ef2032..b9723f2d2 100644 --- a/nova/cmd/conductor.py +++ b/nova/cmd/conductor.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2012 IBM Corp. diff --git a/nova/cmd/console.py b/nova/cmd/console.py index 2aa099f0c..4fdc090f6 100644 --- a/nova/cmd/console.py +++ b/nova/cmd/console.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright (c) 2010 OpenStack Foundation diff --git a/nova/cmd/consoleauth.py b/nova/cmd/consoleauth.py index ea28a2faf..130f7ad65 100644 --- a/nova/cmd/consoleauth.py +++ b/nova/cmd/consoleauth.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright (c) 2012 OpenStack Foundation diff --git a/nova/cmd/dhcpbridge.py b/nova/cmd/dhcpbridge.py index 10ccfbd1d..faf49805d 100644 --- a/nova/cmd/dhcpbridge.py +++ b/nova/cmd/dhcpbridge.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the diff --git a/nova/cmd/manage.py b/nova/cmd/manage.py index 604528160..599b9a299 100644 --- a/nova/cmd/manage.py +++ b/nova/cmd/manage.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright (c) 2011 X.commerce, a business unit of eBay Inc. @@ -887,8 +886,9 @@ class InstanceTypeCommands(object): """Creates instance types / flavors.""" try: flavors.create(name, memory, vcpus, root_gb, - ephemeral_gb, flavorid, swap, rxtx_factor, - is_public) + ephemeral_gb=ephemeral_gb, flavorid=flavorid, + swap=swap, rxtx_factor=rxtx_factor, + is_public=is_public) except exception.InvalidInput as e: print _("Must supply valid parameters to create instance_type") print e diff --git a/nova/cmd/network.py b/nova/cmd/network.py index 72eac2a19..1af0c0d7d 100644 --- a/nova/cmd/network.py +++ b/nova/cmd/network.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the diff --git a/nova/cmd/novncproxy.py b/nova/cmd/novncproxy.py index cf70b83c5..449aea76e 100644 --- a/nova/cmd/novncproxy.py +++ b/nova/cmd/novncproxy.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright (c) 2012 OpenStack Foundation diff --git a/nova/cmd/objectstore.py b/nova/cmd/objectstore.py index eb8257f9c..12e08beb8 100644 --- a/nova/cmd/objectstore.py +++ b/nova/cmd/objectstore.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python + # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the diff --git a/nova/cmd/rpc_zmq_receiver.py b/nova/cmd/rpc_zmq_receiver.py index a587af689..f4f5214e3 100644 --- a/nova/cmd/rpc_zmq_receiver.py +++ b/nova/cmd/rpc_zmq_receiver.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack Foundation diff --git a/nova/cmd/scheduler.py b/nova/cmd/scheduler.py index 40486f5db..b8e6b99aa 100644 --- a/nova/cmd/scheduler.py +++ b/nova/cmd/scheduler.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the diff --git a/nova/cmd/spicehtml5proxy.py b/nova/cmd/spicehtml5proxy.py index 190e7e77a..8e005bcad 100644 --- a/nova/cmd/spicehtml5proxy.py +++ b/nova/cmd/spicehtml5proxy.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright (c) 2012 OpenStack Foundation diff --git a/nova/cmd/xvpvncproxy.py b/nova/cmd/xvpvncproxy.py index 0f62e2083..7d0c87627 100644 --- a/nova/cmd/xvpvncproxy.py +++ b/nova/cmd/xvpvncproxy.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright (c) 2010 OpenStack Foundation diff --git a/nova/compute/api.py b/nova/compute/api.py index d95e65f38..aa0ae7d96 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -53,6 +53,7 @@ from nova import notifications from nova.openstack.common import excutils from nova.openstack.common import jsonutils from nova.openstack.common import log as logging +from nova.openstack.common import strutils from nova.openstack.common import timeutils from nova.openstack.common import uuidutils import nova.policy @@ -439,7 +440,7 @@ class API(base.Base): if value is not None: if prop_type == 'bool': - value = utils.bool_from_str(value) + value = strutils.bool_from_string(value) return value diff --git a/nova/compute/flavors.py b/nova/compute/flavors.py index c4ce550eb..2958769e1 100644 --- a/nova/compute/flavors.py +++ b/nova/compute/flavors.py @@ -30,6 +30,7 @@ from nova import db from nova import exception from nova.openstack.common.db import exception as db_exc from nova.openstack.common import log as logging +from nova.openstack.common import strutils from nova import utils instance_type_opts = [ @@ -65,18 +66,11 @@ system_metadata_instance_type_props = { } -def create(name, memory, vcpus, root_gb, ephemeral_gb=None, flavorid=None, - swap=None, rxtx_factor=None, is_public=True): +def create(name, memory, vcpus, root_gb, ephemeral_gb=0, flavorid=None, + swap=0, rxtx_factor=1.0, is_public=True): """Creates instance types.""" - - if flavorid is None or flavorid == '': + if not flavorid: flavorid = uuid.uuid4() - if swap is None: - swap = 0 - if rxtx_factor is None: - rxtx_factor = 1.0 - if ephemeral_gb is None: - ephemeral_gb = 0 kwargs = { 'memory_mb': memory, @@ -96,13 +90,23 @@ def create(name, memory, vcpus, root_gb, ephemeral_gb=None, flavorid=None, msg = _("names can only contain [a-zA-Z0-9_.- ]") raise exception.InvalidInput(reason=msg) - # ensure some attributes are integers and greater than or equal to 0 - for option in ['memory_mb', 'vcpus', 'root_gb', 'ephemeral_gb', 'swap']: + # Some attributes are positive ( > 0) integers + for option in ['memory_mb', 'vcpus']: + try: + kwargs[option] = int(kwargs[option]) + assert kwargs[option] > 0 + except (ValueError, AssertionError): + msg = _("'%s' argument must be greater than 0") % option + raise exception.InvalidInput(reason=msg) + + # Some attributes are non-negative ( >= 0) integers + for option in ['root_gb', 'ephemeral_gb', 'swap']: try: kwargs[option] = int(kwargs[option]) assert kwargs[option] >= 0 except (ValueError, AssertionError): - msg = _("'%s' argument must be a positive integer") % option + msg = _("'%s' argument must be greater than or equal" + " to 0") % option raise exception.InvalidInput(reason=msg) # rxtx_factor should be a positive float @@ -113,14 +117,6 @@ def create(name, memory, vcpus, root_gb, ephemeral_gb=None, flavorid=None, msg = _("'rxtx_factor' argument must be a positive float") raise exception.InvalidInput(reason=msg) - # some value are required to be nonzero, not just positive - for option in ['memory_mb', 'vcpus']: - try: - assert kwargs[option] > 0 - except AssertionError: - msg = _("'%s' argument must be greater than 0") % option - raise exception.InvalidInput(reason=msg) - kwargs['name'] = name # NOTE(vish): Internally, flavorid is stored as a string but it comes # in through json as an integer, so we convert it here. @@ -130,7 +126,7 @@ def create(name, memory, vcpus, root_gb, ephemeral_gb=None, flavorid=None, if not utils.is_valid_boolstr(is_public): msg = _("is_public must be a boolean") raise exception.InvalidInput(reason=msg) - kwargs['is_public'] = utils.bool_from_str(is_public) + kwargs['is_public'] = strutils.bool_from_string(is_public) try: return db.instance_type_create(context.get_admin_context(), kwargs) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 730ca641c..1fc273bec 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1914,14 +1914,23 @@ def instance_get_floating_address(context, instance_id): @require_context def instance_floating_address_get_all(context, instance_uuid): - fixed_ips = fixed_ip_get_by_instance(context, instance_uuid) + if not uuidutils.is_uuid_like(instance_uuid): + raise exception.InvalidUUID(uuid=instance_uuid) + + fixed_ip_ids = model_query(context, models.FixedIp.id, + base_model=models.FixedIp).\ + filter_by(instance_uuid=instance_uuid).\ + all() + if not fixed_ip_ids: + raise exception.FixedIpNotFoundForInstance(instance_uuid=instance_uuid) - floating_ips = [] - for fixed_ip in fixed_ips: - _floating_ips = floating_ip_get_by_fixed_ip_id(context, fixed_ip['id']) - floating_ips += _floating_ips + fixed_ip_ids = [fixed_ip_id.id for fixed_ip_id in fixed_ip_ids] - return floating_ips + floating_ips = model_query(context, models.FloatingIp.address, + base_model=models.FloatingIp).\ + filter(models.FloatingIp.fixed_ip_id.in_(fixed_ip_ids)).\ + all() + return [floating_ip.address for floating_ip in floating_ips] @require_admin_context diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index c4a22f4c5..f10bc8c32 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -552,6 +552,7 @@ class ProviderFirewallRule(BASE, NovaBase): class KeyPair(BASE, NovaBase): """Represents a public key pair for ssh.""" __tablename__ = 'key_pairs' + __table_args__ = (schema.UniqueConstraint("name", "user_id"), ) id = Column(Integer, primary_key=True) name = Column(String(255)) diff --git a/nova/network/api.py b/nova/network/api.py index 3091a8fa1..26bf03446 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -484,7 +484,7 @@ class API(base.Base): def _get_floating_ip_addresses(self, context, instance): floating_ips = self.db.instance_floating_address_get_all(context, instance['uuid']) - return [floating_ip['address'] for floating_ip in floating_ips] + return floating_ips @wrap_check_policy def migrate_instance_start(self, context, instance, migration): diff --git a/nova/network/manager.py b/nova/network/manager.py index e34233ea9..5d58edb97 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -69,6 +69,7 @@ from nova.openstack.common import jsonutils from nova.openstack.common import log as logging from nova.openstack.common import periodic_task from nova.openstack.common.rpc import common as rpc_common +from nova.openstack.common import strutils from nova.openstack.common import timeutils from nova.openstack.common import uuidutils from nova import quota @@ -1029,10 +1030,11 @@ class NetworkManager(manager.Manager): else: kwargs["network_size"] = CONF.network_size - kwargs["multi_host"] = (CONF.multi_host - if kwargs["multi_host"] is None - else - utils.bool_from_str(kwargs["multi_host"])) + kwargs["multi_host"] = ( + CONF.multi_host + if kwargs["multi_host"] is None + else strutils.bool_from_string(kwargs["multi_host"])) + kwargs["vlan_start"] = kwargs.get("vlan_start") or CONF.vlan_start kwargs["vpn_start"] = kwargs.get("vpn_start") or CONF.vpn_start kwargs["dns1"] = kwargs["dns1"] or CONF.flat_network_dns diff --git a/nova/openstack/common/strutils.py b/nova/openstack/common/strutils.py new file mode 100644 index 000000000..cdf70cb20 --- /dev/null +++ b/nova/openstack/common/strutils.py @@ -0,0 +1,150 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack Foundation. +# 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. + +""" +System-level utilities and helper functions. +""" + +import sys + +from nova.openstack.common.gettextutils import _ + + +TRUE_STRINGS = ('1', 't', 'true', 'on', 'y', 'yes') +FALSE_STRINGS = ('0', 'f', 'false', 'off', 'n', 'no') + + +def int_from_bool_as_string(subject): + """ + Interpret a string as a boolean and return either 1 or 0. + + Any string value in: + + ('True', 'true', 'On', 'on', '1') + + is interpreted as a boolean True. + + Useful for JSON-decoded stuff and config file parsing + """ + return bool_from_string(subject) and 1 or 0 + + +def bool_from_string(subject, strict=False): + """ + Interpret a string as a boolean. + + A case-insensitive match is performed such that strings matching 't', + 'true', 'on', 'y', 'yes', or '1' are considered True and, when + `strict=False`, anything else is considered False. + + Useful for JSON-decoded stuff and config file parsing. + + If `strict=True`, unrecognized values, including None, will raise a + ValueError which is useful when parsing values passed in from an API call. + Strings yielding False are 'f', 'false', 'off', 'n', 'no', or '0'. + """ + if not isinstance(subject, basestring): + subject = str(subject) + + lowered = subject.strip().lower() + + if lowered in TRUE_STRINGS: + return True + elif lowered in FALSE_STRINGS: + return False + elif strict: + acceptable = ', '.join( + "'%s'" % s for s in sorted(TRUE_STRINGS + FALSE_STRINGS)) + msg = _("Unrecognized value '%(val)s', acceptable values are:" + " %(acceptable)s") % {'val': subject, + 'acceptable': acceptable} + raise ValueError(msg) + else: + return False + + +def safe_decode(text, incoming=None, errors='strict'): + """ + Decodes incoming str using `incoming` if they're + not already unicode. + + :param incoming: Text's current encoding + :param errors: Errors handling policy. See here for valid + values http://docs.python.org/2/library/codecs.html + :returns: text or a unicode `incoming` encoded + representation of it. + :raises TypeError: If text is not an isntance of basestring + """ + if not isinstance(text, basestring): + raise TypeError("%s can't be decoded" % type(text)) + + if isinstance(text, unicode): + return text + + if not incoming: + incoming = (sys.stdin.encoding or + sys.getdefaultencoding()) + + try: + return text.decode(incoming, errors) + except UnicodeDecodeError: + # Note(flaper87) If we get here, it means that + # sys.stdin.encoding / sys.getdefaultencoding + # didn't return a suitable encoding to decode + # text. This happens mostly when global LANG + # var is not set correctly and there's no + # default encoding. In this case, most likely + # python will use ASCII or ANSI encoders as + # default encodings but they won't be capable + # of decoding non-ASCII characters. + # + # Also, UTF-8 is being used since it's an ASCII + # extension. + return text.decode('utf-8', errors) + + +def safe_encode(text, incoming=None, + encoding='utf-8', errors='strict'): + """ + Encodes incoming str/unicode using `encoding`. If + incoming is not specified, text is expected to + be encoded with current python's default encoding. + (`sys.getdefaultencoding`) + + :param incoming: Text's current encoding + :param encoding: Expected encoding for text (Default UTF-8) + :param errors: Errors handling policy. See here for valid + values http://docs.python.org/2/library/codecs.html + :returns: text or a bytestring `encoding` encoded + representation of it. + :raises TypeError: If text is not an isntance of basestring + """ + if not isinstance(text, basestring): + raise TypeError("%s can't be encoded" % type(text)) + + if not incoming: + incoming = (sys.stdin.encoding or + sys.getdefaultencoding()) + + if isinstance(text, unicode): + return text.encode(encoding, errors) + elif text and encoding != incoming: + # Decode text before encoding it with `encoding` + text = safe_decode(text, incoming, errors) + return text.encode(encoding, errors) + + return text diff --git a/nova/tests/api/openstack/compute/contrib/test_snapshots.py b/nova/tests/api/openstack/compute/contrib/test_snapshots.py index a890abe6f..8e042adf8 100644 --- a/nova/tests/api/openstack/compute/contrib/test_snapshots.py +++ b/nova/tests/api/openstack/compute/contrib/test_snapshots.py @@ -67,6 +67,8 @@ class SnapshotApiTest(test.TestCase): snapshot['display_name']) self.assertEqual(resp_dict['snapshot']['displayDescription'], snapshot['display_description']) + self.assertEqual(resp_dict['snapshot']['volumeId'], + snapshot['volume_id']) def test_snapshot_create_force(self): snapshot = {"volume_id": 12, @@ -88,6 +90,8 @@ class SnapshotApiTest(test.TestCase): snapshot['display_name']) self.assertEqual(resp_dict['snapshot']['displayDescription'], snapshot['display_description']) + self.assertEqual(resp_dict['snapshot']['volumeId'], + snapshot['volume_id']) # Test invalid force paramter snapshot = {"volume_id": 12, diff --git a/nova/tests/api/openstack/compute/test_flavors.py b/nova/tests/api/openstack/compute/test_flavors.py index 45eeeb34b..13206b6f8 100644 --- a/nova/tests/api/openstack/compute/test_flavors.py +++ b/nova/tests/api/openstack/compute/test_flavors.py @@ -762,3 +762,37 @@ class DisabledFlavorsWithRealDBTest(test.TestCase): self.req, self.disabled_type['flavorid'])['flavor'] self.assertEqual(flavor['name'], self.disabled_type['name']) + + +class ParseIsPublicTest(test.TestCase): + def setUp(self): + super(ParseIsPublicTest, self).setUp() + self.controller = flavors.Controller() + + def assertPublic(self, expected, is_public): + self.assertIs(expected, self.controller._parse_is_public(is_public), + '%s did not return %s' % (is_public, expected)) + + def test_None(self): + self.assertPublic(True, None) + + def test_truthy(self): + self.assertPublic(True, True) + self.assertPublic(True, 't') + self.assertPublic(True, 'true') + self.assertPublic(True, 'yes') + self.assertPublic(True, '1') + + def test_falsey(self): + self.assertPublic(False, False) + self.assertPublic(False, 'f') + self.assertPublic(False, 'false') + self.assertPublic(False, 'no') + self.assertPublic(False, '0') + + def test_string_none(self): + self.assertPublic(None, 'none') + + def test_other(self): + self.assertRaises( + webob.exc.HTTPBadRequest, self.assertPublic, None, 'other') diff --git a/nova/tests/api/openstack/compute/test_servers.py b/nova/tests/api/openstack/compute/test_servers.py index 2744d620f..5d2118564 100644 --- a/nova/tests/api/openstack/compute/test_servers.py +++ b/nova/tests/api/openstack/compute/test_servers.py @@ -2436,7 +2436,7 @@ class ServersControllerCreateTest(test.TestCase): {'device_name': 'foo3', 'delete_on_termination': 'invalid'}, {'device_name': 'foo4', 'delete_on_termination': 0}, {'device_name': 'foo5', 'delete_on_termination': False}] - expected_dbm = [ + expected_bdm = [ {'device_name': 'foo1', 'delete_on_termination': True}, {'device_name': 'foo2', 'delete_on_termination': True}, {'device_name': 'foo3', 'delete_on_termination': False}, @@ -2446,7 +2446,7 @@ class ServersControllerCreateTest(test.TestCase): old_create = compute_api.API.create def create(*args, **kwargs): - self.assertEqual(kwargs['block_device_mapping'], expected_dbm) + self.assertEqual(expected_bdm, kwargs['block_device_mapping']) return old_create(*args, **kwargs) self.stubs.Set(compute_api.API, 'create', create) diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index 0a78020bd..24566d4d0 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -611,8 +611,8 @@ def stub_snapshot(id, **kwargs): return snapshot -def stub_snapshot_create(self, context, volume_id, name, description): - return stub_snapshot(100, volume_id=volume_id, display_name=name, +def stub_snapshot_create(self, context, volume, name, description): + return stub_snapshot(100, volume_id=volume['id'], display_name=name, display_description=description) diff --git a/nova/tests/compute/test_compute.py b/nova/tests/compute/test_compute.py index d185c6867..9560fc78c 100644 --- a/nova/tests/compute/test_compute.py +++ b/nova/tests/compute/test_compute.py @@ -6050,11 +6050,8 @@ class ComputeAPITestCase(BaseTestCase): name = 'test_resize_new_flavor' flavorid = 11 - memory_mb = 128 - root_gb = 0 - vcpus = 1 - flavors.create(name, memory_mb, vcpus, root_gb, 0, - flavorid, 0, 1.0, True) + flavors.create(name, 128, 1, 0, ephemeral_gb=0, flavorid=flavorid, + swap=0, rxtx_factor=1.0, is_public=True) flavors.destroy(name) self.assertRaises(exception.FlavorNotFound, self.compute_api.resize, self.context, instance, flavorid) @@ -6082,11 +6079,8 @@ class ComputeAPITestCase(BaseTestCase): name = 'test_resize_with_big_mem' flavorid = 11 - memory_mb = 102400 - root_gb = 0 - vcpus = 1 - flavors.create(name, memory_mb, vcpus, root_gb, 0, - flavorid, 0, 1.0, True) + flavors.create(name, 102400, 1, 0, ephemeral_gb=0, flavorid=flavorid, + swap=0, rxtx_factor=1.0, is_public=True) self.assertRaises(exception.TooManyInstances, self.compute_api.resize, self.context, instance, flavorid) @@ -6096,11 +6090,9 @@ class ComputeAPITestCase(BaseTestCase): def test_resize_revert_deleted_flavor_fails(self): orig_name = 'test_resize_revert_orig_flavor' orig_flavorid = 11 - memory_mb = 128 - root_gb = 0 - vcpus = 1 - flavors.create(orig_name, memory_mb, vcpus, root_gb, 0, - orig_flavorid, 0, 1.0, True) + flavors.create(orig_name, 128, 1, 0, ephemeral_gb=0, + flavorid=orig_flavorid, swap=0, rxtx_factor=1.0, + is_public=True) instance = self._create_fake_instance(type_name=orig_name) instance = db.instance_get_by_uuid(self.context, instance['uuid']) diff --git a/nova/tests/integrated/api_samples/os-volumes/snapshot-create-resp.json.tpl b/nova/tests/integrated/api_samples/os-volumes/snapshot-create-resp.json.tpl index 73cd02d9d..ddffd97a3 100644 --- a/nova/tests/integrated/api_samples/os-volumes/snapshot-create-resp.json.tpl +++ b/nova/tests/integrated/api_samples/os-volumes/snapshot-create-resp.json.tpl @@ -6,27 +6,6 @@ "id": 100, "size": 100, "status": "available", - "volumeId": { - "attach_status": "attached", - "availability_zone": "fakeaz", - "created_at": "%(timestamp)s", - "display_description": "displaydesc", - "display_name": "displayname", - "host": "fakehost", - "id": "%(uuid)s", - "instance_uuid": "fakeuuid", - "mountpoint": "/", - "name": "vol name", - "project_id": "fakeproject", - "size": 1, - "snapshot_id": null, - "status": "fakestatus", - "user_id": "fakeuser", - "volume_metadata": [], - "volume_type": { - "name": "vol_type_name" - }, - "volume_type_id": "fakevoltype" - } + "volumeId": "%(uuid)s" } } diff --git a/nova/tests/integrated/api_samples/os-volumes/snapshot-create-resp.xml.tpl b/nova/tests/integrated/api_samples/os-volumes/snapshot-create-resp.xml.tpl index aa713311f..d75ae7ddd 100644 --- a/nova/tests/integrated/api_samples/os-volumes/snapshot-create-resp.xml.tpl +++ b/nova/tests/integrated/api_samples/os-volumes/snapshot-create-resp.xml.tpl @@ -1,2 +1,2 @@ <?xml version='1.0' encoding='UTF-8'?> -<snapshot status="available" displayDescription="%(description)s" displayName="%(snapshot_name)s" volumeId="{'instance_uuid': 'fakeuuid', 'status': 'fakestatus', 'user_id': 'fakeuser', 'name': 'vol name', 'display_name': 'displayname', 'availability_zone': 'fakeaz', 'created_at': datetime.datetime(1999, 1, 1, 1, 1, 1), 'attach_status': 'attached', 'display_description': 'displaydesc', 'host': 'fakehost', 'volume_type_id': 'fakevoltype', 'volume_metadata': [], 'volume_type': {'name': 'vol_type_name'}, 'snapshot_id': None, 'mountpoint': '/', 'project_id': 'fakeproject', 'id': u'521752a6-acf6-4b2d-bc7a-119f9148cd8c', 'size': 1}" id="100" createdAt="%(timestamp)s" size="100"/> +<snapshot status="available" displayDescription="%(description)s" displayName="%(snapshot_name)s" volumeId="521752a6-acf6-4b2d-bc7a-119f9148cd8c" id="100" createdAt="%(timestamp)s" size="100"/> diff --git a/nova/tests/test_db_api.py b/nova/tests/test_db_api.py index bf7cc003a..13a1dedff 100644 --- a/nova/tests/test_db_api.py +++ b/nova/tests/test_db_api.py @@ -1024,6 +1024,34 @@ class DbApiTestCase(DbTestCase): self.assertEqual(db.network_in_use_on_host(ctxt, 1, 'foo'), True) self.assertEqual(db.network_in_use_on_host(ctxt, 1, 'bar'), False) + def test_instance_floating_address_get_all(self): + ctxt = context.get_admin_context() + + instance1 = db.instance_create(ctxt, {'host': 'h1', 'hostname': 'n1'}) + instance2 = db.instance_create(ctxt, {'host': 'h2', 'hostname': 'n2'}) + + fixed_addresses = ['1.1.1.1', '1.1.1.2', '1.1.1.3'] + float_addresses = ['2.1.1.1', '2.1.1.2', '2.1.1.3'] + instance_uuids = [instance1['uuid'], instance1['uuid'], + instance2['uuid']] + + for fixed_addr, float_addr, instance_uuid in zip(fixed_addresses, + float_addresses, + instance_uuids): + db.fixed_ip_create(ctxt, {'address': fixed_addr, + 'instance_uuid': instance_uuid}) + fixed_id = db.fixed_ip_get_by_address(ctxt, fixed_addr)['id'] + db.floating_ip_create(ctxt, + {'address': float_addr, + 'fixed_ip_id': fixed_id}) + + real_float_addresses = \ + db.instance_floating_address_get_all(ctxt, instance_uuids[0]) + self.assertEqual(set(float_addresses[:2]), set(real_float_addresses)) + real_float_addresses = \ + db.instance_floating_address_get_all(ctxt, instance_uuids[2]) + self.assertEqual(set([float_addresses[2]]), set(real_float_addresses)) + def test_get_vol_mapping_non_admin(self): ref = db.ec2_volume_create(self.context, 'fake-uuid') ec2_id = db.get_ec2_volume_id_by_uuid(self.context, 'fake-uuid') @@ -1100,67 +1128,6 @@ class DbApiTestCase(DbTestCase): _compare(bw_usages[2], expected_bw_usages[2]) timeutils.clear_time_override() - def test_key_pair_create(self): - ctxt = context.get_admin_context() - values = {'name': 'test_keypair', 'public_key': 'test-public-key', - 'user_id': 'test_user_id', 'fingerprint': 'test_fingerprint'} - keypair = db.key_pair_create(ctxt, values) - self.assertNotEqual(None, keypair) - for name, value in values.iteritems(): - self.assertEqual(keypair.get(name), value) - - def test_key_pair_create_with_duplicate_name(self): - ctxt = context.get_admin_context() - values = {'name': 'test_keypair', 'public_key': 'test-public-key', - 'user_id': 'test_user_id', 'fingerprint': 'test_fingerprint'} - keypair = db.key_pair_create(ctxt, values) - self.assertRaises(exception.KeyPairExists, - db.key_pair_create, ctxt, values) - - def test_admin_get_deleted_keypair(self): - # Test deleted keypair can be read by admin user. - ctxt = context.get_admin_context() - values = {'name': 'test_keypair', 'public_key': 'test-public-key', - 'user_id': 'test_user_id', 'fingerprint': 'test_fingerprint'} - keypair = db.key_pair_create(ctxt, values) - db.key_pair_destroy(ctxt, keypair['user_id'], keypair['name']) - - # Raise exception when read_deleted is 'no'. - self.assertRaises(exception.KeypairNotFound, db.key_pair_get, ctxt, - keypair['user_id'], keypair['name']) - ctxt = ctxt.elevated(read_deleted='yes') - db_keypair = db.key_pair_get(ctxt, keypair['user_id'], - keypair['name']) - self.assertEqual(db_keypair['name'], keypair['name']) - self.assertEqual(db_keypair['deleted'], keypair['id']) - - def test_admin_get_all_keypairs_including_deleted(self): - # Test all deleted/non-deleted keypairs can be read by admin user. - ctxt = context.get_admin_context() - keypair1_values = {'name': 'test_keypair1', - 'public_key': 'test-public-key1', - 'user_id': 'test_user_id', - 'fingerprint': 'test_fingerprint1'} - keypair2_values = {'name': 'test_keypair2', - 'public_key': 'test-public-key2', - 'user_id': 'test_user_id', - 'fingerprint': 'test_fingerprint2'} - keypair1 = db.key_pair_create(ctxt, keypair1_values) - keypair2 = db.key_pair_create(ctxt, keypair2_values) - db.key_pair_destroy(ctxt, keypair1['user_id'], keypair1['name']) - db.key_pair_destroy(ctxt, keypair2['user_id'], keypair2['name']) - # Returns non-deleted keypairs. - result = db.key_pair_get_all_by_user(ctxt, keypair1['user_id']) - self.assertEqual(result, []) - ctxt = ctxt.elevated(read_deleted='yes') - # Returns deleted and non-deleted keypairs. - db_keypairs = db.key_pair_get_all_by_user(ctxt, keypair1['user_id']) - expected_deleted_ids = [keypair1['id'], keypair2['id']] - expected_keypair_names = [keypair1['name'], keypair2['name']] - for keypair in db_keypairs: - self.assertTrue(keypair['name'] in expected_keypair_names) - self.assertTrue(keypair['deleted'] in expected_deleted_ids) - def _get_fake_aggr_values(): return {'name': 'fake_aggregate'} @@ -3604,6 +3571,114 @@ class VirtualInterfaceTestCase(test.TestCase, ModelsObjectComparatorMixin): self._assertEqualListsOfObjects(vifs, real_vifs) +class KeyPairTestCase(test.TestCase, ModelsObjectComparatorMixin): + def setUp(self): + super(KeyPairTestCase, self).setUp() + self.ctxt = context.get_admin_context() + + def _create_key_pair(self, values): + return db.key_pair_create(self.ctxt, values) + + def test_key_pair_create(self): + param = { + 'name': 'test_1', + 'user_id': 'test_user_id_1', + 'public_key': 'test_public_key_1', + 'fingerprint': 'test_fingerprint_1' + } + key_pair = self._create_key_pair(param) + + self.assertTrue(key_pair['id'] is not None) + ignored_keys = ['deleted', 'created_at', 'updated_at', + 'deleted_at', 'id'] + self._assertEqualObjects(key_pair, param, ignored_keys) + + def test_key_pair_create_with_duplicate_name(self): + params = {'name': 'test_name', 'user_id': 'test_user_id'} + self._create_key_pair(params) + self.assertRaises(exception.KeyPairExists, self._create_key_pair, + params) + + def test_key_pair_get(self): + params = [ + {'name': 'test_1', 'user_id': 'test_user_id_1'}, + {'name': 'test_2', 'user_id': 'test_user_id_2'}, + {'name': 'test_3', 'user_id': 'test_user_id_3'} + ] + key_pairs = [self._create_key_pair(p) for p in params] + + for key in key_pairs: + real_key = db.key_pair_get(self.ctxt, key['user_id'], key['name']) + self._assertEqualObjects(key, real_key) + + def test_key_pair_get_no_results(self): + param = {'name': 'test_1', 'user_id': 'test_user_id_1'} + self.assertRaises(exception.KeypairNotFound, db.key_pair_get, + self.ctxt, param['user_id'], param['name']) + + def test_key_pair_get_deleted(self): + param = {'name': 'test_1', 'user_id': 'test_user_id_1'} + key_pair_created = self._create_key_pair(param) + + db.key_pair_destroy(self.ctxt, param['user_id'], param['name']) + self.assertRaises(exception.KeypairNotFound, db.key_pair_get, + self.ctxt, param['user_id'], param['name']) + + ctxt = self.ctxt.elevated(read_deleted='yes') + key_pair_deleted = db.key_pair_get(ctxt, param['user_id'], + param['name']) + ignored_keys = ['deleted', 'created_at', 'updated_at', 'deleted_at'] + self._assertEqualObjects(key_pair_deleted, key_pair_created, + ignored_keys) + self.assertEqual(key_pair_deleted['deleted'], key_pair_deleted['id']) + + def test_key_pair_get_all_by_user(self): + params = [ + {'name': 'test_1', 'user_id': 'test_user_id_1'}, + {'name': 'test_2', 'user_id': 'test_user_id_1'}, + {'name': 'test_3', 'user_id': 'test_user_id_2'} + ] + key_pairs_user_1 = [self._create_key_pair(p) for p in params + if p['user_id'] == 'test_user_id_1'] + key_pairs_user_2 = [self._create_key_pair(p) for p in params + if p['user_id'] == 'test_user_id_2'] + + real_keys_1 = db.key_pair_get_all_by_user(self.ctxt, 'test_user_id_1') + real_keys_2 = db.key_pair_get_all_by_user(self.ctxt, 'test_user_id_2') + + self._assertEqualListsOfObjects(key_pairs_user_1, real_keys_1) + self._assertEqualListsOfObjects(key_pairs_user_2, real_keys_2) + + def test_key_pair_count_by_user(self): + params = [ + {'name': 'test_1', 'user_id': 'test_user_id_1'}, + {'name': 'test_2', 'user_id': 'test_user_id_1'}, + {'name': 'test_3', 'user_id': 'test_user_id_2'} + ] + for p in params: + self._create_key_pair(p) + + count_1 = db.key_pair_count_by_user(self.ctxt, 'test_user_id_1') + self.assertEqual(count_1, 2) + + count_2 = db.key_pair_count_by_user(self.ctxt, 'test_user_id_2') + self.assertEqual(count_2, 1) + + def test_key_pair_destroy(self): + param = {'name': 'test_1', 'user_id': 'test_user_id_1'} + self._create_key_pair(param) + + db.key_pair_destroy(self.ctxt, param['user_id'], param['name']) + self.assertRaises(exception.KeypairNotFound, db.key_pair_get, + self.ctxt, param['user_id'], param['name']) + + def test_key_pair_destroy_no_such_key(self): + param = {'name': 'test_1', 'user_id': 'test_user_id_1'} + self.assertRaises(exception.KeypairNotFound, + db.key_pair_destroy, self.ctxt, + param['user_id'], param['name']) + + class ArchiveTestCase(test.TestCase): def setUp(self): diff --git a/nova/tests/test_instance_types.py b/nova/tests/test_instance_types.py index 5afa4005f..e20845f1d 100644 --- a/nova/tests/test_instance_types.py +++ b/nova/tests/test_instance_types.py @@ -51,119 +51,6 @@ class InstanceTypeTestCase(test.TestCase): """return first instance type name.""" return flavors.get_all_types().keys()[0] - def test_instance_type_create(self): - # Ensure instance types can be created. - name = 'Instance create test' - flavor_id = '512' - - original_list = flavors.get_all_types() - - # create new type and make sure values stick - inst_type = flavors.create(name, 256, 1, 120, - flavorid=flavor_id) - self.assertEqual(inst_type['flavorid'], flavor_id) - self.assertEqual(inst_type['name'], name) - self.assertEqual(inst_type['memory_mb'], 256) - self.assertEqual(inst_type['vcpus'], 1) - self.assertEqual(inst_type['root_gb'], 120) - self.assertEqual(inst_type['ephemeral_gb'], 0) - self.assertEqual(inst_type['swap'], 0) - self.assertEqual(inst_type['rxtx_factor'], 1.0) - - # make sure new type shows up in list - new_list = flavors.get_all_types() - self.assertNotEqual(len(original_list), len(new_list), - 'instance type was not created') - - def test_instance_type_create_then_delete(self): - # Ensure instance types can be created. - name = 'Small Flavor' - flavorid = 'flavor1' - - original_list = flavors.get_all_types() - - # create new type and make sure values stick - inst_type = flavors.create(name, 256, 1, 120, 100, flavorid) - inst_type_id = inst_type['id'] - self.assertEqual(inst_type['flavorid'], flavorid) - self.assertEqual(inst_type['name'], name) - self.assertEqual(inst_type['memory_mb'], 256) - self.assertEqual(inst_type['vcpus'], 1) - self.assertEqual(inst_type['root_gb'], 120) - self.assertEqual(inst_type['ephemeral_gb'], 100) - self.assertEqual(inst_type['swap'], 0) - self.assertEqual(inst_type['rxtx_factor'], 1.0) - - # make sure new type shows up in list - new_list = flavors.get_all_types() - self.assertNotEqual(len(original_list), len(new_list), - 'instance type was not created') - - flavors.destroy(name) - self.assertRaises(exception.InstanceTypeNotFound, - flavors.get_instance_type, inst_type_id) - - # deleted instance should not be in list anymoer - new_list = flavors.get_all_types() - self.assertEqual(original_list, new_list) - - def test_instance_type_create_without_flavorid(self): - name = 'Small Flavor' - inst_type = flavors.create(name, 256, 1, 120, 100) - self.assertNotEqual(inst_type['flavorid'], None) - self.assertEqual(inst_type['name'], name) - self.assertEqual(inst_type['memory_mb'], 256) - self.assertEqual(inst_type['vcpus'], 1) - self.assertEqual(inst_type['root_gb'], 120) - self.assertEqual(inst_type['ephemeral_gb'], 100) - self.assertEqual(inst_type['swap'], 0) - self.assertEqual(inst_type['rxtx_factor'], 1.0) - - def test_instance_type_create_with_empty_flavorid(self): - # Ensure that auto-generated uuid is assigned. - name = 'Empty String ID Flavor' - flavorid = '' - inst_type = flavors.create(name, 256, 1, 120, 100, flavorid) - self.assertEqual(len(inst_type['flavorid']), 36) - self.assertEqual(inst_type['name'], name) - self.assertEqual(inst_type['memory_mb'], 256) - self.assertEqual(inst_type['vcpus'], 1) - self.assertEqual(inst_type['root_gb'], 120) - self.assertEqual(inst_type['ephemeral_gb'], 100) - self.assertEqual(inst_type['swap'], 0) - self.assertEqual(inst_type['rxtx_factor'], 1.0) - - def test_instance_type_create_with_custom_rxtx_factor(self): - name = 'Custom RXTX Factor' - inst_type = flavors.create(name, 256, 1, 120, 100, - rxtx_factor=9.9) - self.assertNotEqual(inst_type['flavorid'], None) - self.assertEqual(inst_type['name'], name) - self.assertEqual(inst_type['memory_mb'], 256) - self.assertEqual(inst_type['vcpus'], 1) - self.assertEqual(inst_type['root_gb'], 120) - self.assertEqual(inst_type['ephemeral_gb'], 100) - self.assertEqual(inst_type['swap'], 0) - self.assertEqual(inst_type['rxtx_factor'], 9.9) - - def test_instance_type_create_with_special_characters(self): - # Ensure instance types raises InvalidInput for invalid characters. - name = "foo.bar!@#$%^-test_name" - flavorid = "flavor1" - self.assertRaises(exception.InvalidInput, flavors.create, - name, 256, 1, 120, 100, flavorid) - - def test_instance_type_create_with_long_flavor_name(self): - # Flavor name with 255 characters or less is valid. - name = 'a' * 255 - inst_type = flavors.create(name, 64, 1, 120, flavorid=11) - self.assertEqual(inst_type['name'], name) - - # Flavor name which is more than 255 characters will cause error. - name = 'a' * 256 - self.assertRaises(exception.InvalidInput, flavors.create, - name, 64, 1, 120, flavorid=11) - def test_add_instance_type_access(self): user_id = 'fake' project_id = 'fake' @@ -233,55 +120,12 @@ class InstanceTypeTestCase(test.TestCase): inst_types = flavors.get_all_types() self.assertEqual(total_instance_types, len(inst_types)) - def test_invalid_create_args_should_fail(self): - # Ensures that instance type creation fails with invalid args. - invalid_sigs = [ - (('Zero memory', 0, 1, 10, 20, 'flavor1'), {}), - (('Negative memory', -256, 1, 10, 20, 'flavor1'), {}), - (('Non-integer memory', 'asdf', 1, 10, 20, 'flavor1'), {}), - - (('Zero vcpus', 256, 0, 10, 20, 'flavor1'), {}), - (('Negative vcpus', 256, -1, 10, 20, 'flavor1'), {}), - (('Non-integer vcpus', 256, 'a', 10, 20, 'flavor1'), {}), - - (('Negative storage', 256, 1, -1, 20, 'flavor1'), {}), - (('Non-integer storage', 256, 1, 'a', 20, 'flavor1'), {}), - - (('Negative swap', 256, 1, 10, 20, 'flavor1'), {'swap': -1}), - (('Non-integer swap', 256, 1, 10, 20, 'flavor1'), {'swap': -1}), - - (('Negative rxtx_factor', 256, 1, 10, 20, 'f1'), - {'rxtx_factor': -1}), - (('Non-integer rxtx_factor', 256, 1, 10, 20, 'f1'), - {'rxtx_factor': "d"}), - ] - - for (args, kwargs) in invalid_sigs: - self.assertRaises(exception.InvalidInput, - flavors.create, *args, **kwargs) - def test_non_existent_inst_type_shouldnt_delete(self): # Ensures that instance type creation fails with invalid args. self.assertRaises(exception.InstanceTypeNotFoundByName, flavors.destroy, 'unknown_flavor') - def test_duplicate_names_fail(self): - # Ensures that name duplicates raise InstanceTypeCreateFailed. - name = 'some_name' - flavors.create(name, 256, 1, 120, 200, 'flavor1') - self.assertRaises(exception.InstanceTypeExists, - flavors.create, - name, 256, 1, 120, 200, 'flavor2') - - def test_duplicate_flavorids_fail(self): - # Ensures that flavorid duplicates raise InstanceTypeCreateFailed. - flavorid = 'flavor1' - flavors.create('name one', 256, 1, 120, 200, flavorid) - self.assertRaises(exception.InstanceTypeIdExists, - flavors.create, - 'name two', 256, 1, 120, 200, flavorid) - def test_will_not_destroy_with_no_name(self): # Ensure destroy said path of no name raises error. self.assertRaises(exception.InstanceTypeNotFoundByName, @@ -452,3 +296,143 @@ class InstanceTypeFilteringTest(test.TestCase): filters = dict(min_memory_mb=16384, min_root_gb=80) expected = ['m1.xlarge'] self.assertFilterResults(filters, expected) + + +class CreateInstanceTypeTest(test.TestCase): + + def assertInvalidInput(self, *create_args, **create_kwargs): + self.assertRaises(exception.InvalidInput, flavors.create, + *create_args, **create_kwargs) + + def test_name_with_special_characters(self): + # Names can contain [a-zA-Z0-9_.- ] + flavors.create('_foo.bar-123', 64, 1, 120) + + # Ensure instance types raises InvalidInput for invalid characters. + self.assertInvalidInput('foobar#', 64, 1, 120) + + def test_name_length_checks(self): + MAX_LEN = 255 + + # Flavor name with 255 characters or less is valid. + flavors.create('a' * MAX_LEN, 64, 1, 120) + + # Flavor name which is more than 255 characters will cause error. + self.assertInvalidInput('a' * (MAX_LEN + 1), 64, 1, 120) + + # Flavor name which is empty should cause an error + self.assertInvalidInput('', 64, 1, 120) + + def test_memory_must_be_positive_integer(self): + self.assertInvalidInput('flavor1', 'foo', 1, 120) + self.assertInvalidInput('flavor1', -1, 1, 120) + self.assertInvalidInput('flavor1', 0, 1, 120) + flavors.create('flavor1', 1, 1, 120) + + def test_vcpus_must_be_positive_integer(self): + self.assertInvalidInput('flavor`', 64, 'foo', 120) + self.assertInvalidInput('flavor1', 64, -1, 120) + self.assertInvalidInput('flavor1', 64, 0, 120) + flavors.create('flavor1', 64, 1, 120) + + def test_root_gb_must_be_nonnegative_integer(self): + self.assertInvalidInput('flavor1', 64, 1, 'foo') + self.assertInvalidInput('flavor1', 64, 1, -1) + flavors.create('flavor1', 64, 1, 0) + flavors.create('flavor2', 64, 1, 120) + + def test_swap_must_be_nonnegative_integer(self): + self.assertInvalidInput('flavor1', 64, 1, 120, swap='foo') + self.assertInvalidInput('flavor1', 64, 1, 120, swap=-1) + flavors.create('flavor1', 64, 1, 120, swap=0) + flavors.create('flavor2', 64, 1, 120, swap=1) + + def test_rxtx_factor_must_be_positive_float(self): + self.assertInvalidInput('flavor1', 64, 1, 120, rxtx_factor='foo') + self.assertInvalidInput('flavor1', 64, 1, 120, rxtx_factor=-1.0) + self.assertInvalidInput('flavor1', 64, 1, 120, rxtx_factor=0.0) + + flavor = flavors.create('flavor1', 64, 1, 120, rxtx_factor=1.0) + self.assertEqual(1.0, flavor['rxtx_factor']) + + flavor = flavors.create('flavor2', 64, 1, 120, rxtx_factor=1.1) + self.assertEqual(1.1, flavor['rxtx_factor']) + + def test_is_public_must_be_valid_bool_string(self): + self.assertInvalidInput('flavor1', 64, 1, 120, is_public='foo') + + flavors.create('flavor1', 64, 1, 120, is_public='TRUE') + flavors.create('flavor2', 64, 1, 120, is_public='False') + flavors.create('flavor3', 64, 1, 120, is_public='Yes') + flavors.create('flavor4', 64, 1, 120, is_public='No') + flavors.create('flavor5', 64, 1, 120, is_public='Y') + flavors.create('flavor6', 64, 1, 120, is_public='N') + flavors.create('flavor7', 64, 1, 120, is_public='1') + flavors.create('flavor8', 64, 1, 120, is_public='0') + flavors.create('flavor9', 64, 1, 120, is_public='true') + + def test_flavorid_populated(self): + flavor1 = flavors.create('flavor1', 64, 1, 120) + self.assertIsNot(None, flavor1['flavorid']) + + flavor2 = flavors.create('flavor2', 64, 1, 120, flavorid='') + self.assertIsNot(None, flavor2['flavorid']) + + flavor3 = flavors.create('flavor3', 64, 1, 120, flavorid='foo') + self.assertEqual('foo', flavor3['flavorid']) + + def test_default_values(self): + flavor1 = flavors.create('flavor1', 64, 1, 120) + + self.assertIsNot(None, flavor1['flavorid']) + self.assertEqual(flavor1['ephemeral_gb'], 0) + self.assertEqual(flavor1['swap'], 0) + self.assertEqual(flavor1['rxtx_factor'], 1.0) + + def test_basic_create(self): + # Ensure instance types can be created. + original_list = flavors.get_all_types() + + # Create new type and make sure values stick + flavor = flavors.create('flavor', 64, 1, 120) + self.assertEqual(flavor['name'], 'flavor') + self.assertEqual(flavor['memory_mb'], 64) + self.assertEqual(flavor['vcpus'], 1) + self.assertEqual(flavor['root_gb'], 120) + + # Ensure new type shows up in list + new_list = flavors.get_all_types() + self.assertNotEqual(len(original_list), len(new_list), + 'flavor was not created') + + def test_create_then_delete(self): + original_list = flavors.get_all_types() + + flavor = flavors.create('flavor', 64, 1, 120) + + # Ensure new type shows up in list + new_list = flavors.get_all_types() + self.assertNotEqual(len(original_list), len(new_list), + 'instance type was not created') + + flavors.destroy('flavor') + self.assertRaises(exception.InstanceTypeNotFound, + flavors.get_instance_type, flavor['id']) + + # Deleted instance should not be in list anymore + new_list = flavors.get_all_types() + self.assertEqual(original_list, new_list) + + def test_duplicate_names_fail(self): + # Ensures that name duplicates raise InstanceTypeCreateFailed. + flavors.create('flavor', 256, 1, 120, 200, 'flavor1') + self.assertRaises(exception.InstanceTypeExists, + flavors.create, + 'flavor', 64, 1, 120) + + def test_duplicate_flavorids_fail(self): + # Ensures that flavorid duplicates raise InstanceTypeCreateFailed. + flavors.create('flavor1', 64, 1, 120, flavorid='flavorid') + self.assertRaises(exception.InstanceTypeIdExists, + flavors.create, + 'flavor2', 64, 1, 120, flavorid='flavorid') diff --git a/nova/tests/test_utils.py b/nova/tests/test_utils.py index 6b7af48fc..39db03f3e 100644 --- a/nova/tests/test_utils.py +++ b/nova/tests/test_utils.py @@ -276,28 +276,6 @@ class GenericUtilsTestCase(test.TestCase): hostname = "<}\x1fh\x10e\x08l\x02l\x05o\x12!{>" self.assertEqual("hello", utils.sanitize_hostname(hostname)) - def test_bool_from_str(self): - self.assertTrue(utils.bool_from_str('1')) - self.assertTrue(utils.bool_from_str('2')) - self.assertTrue(utils.bool_from_str('-1')) - self.assertTrue(utils.bool_from_str('true')) - self.assertTrue(utils.bool_from_str('True')) - self.assertTrue(utils.bool_from_str('tRuE')) - self.assertTrue(utils.bool_from_str('yes')) - self.assertTrue(utils.bool_from_str('Yes')) - self.assertTrue(utils.bool_from_str('YeS')) - self.assertTrue(utils.bool_from_str('y')) - self.assertTrue(utils.bool_from_str('Y')) - self.assertFalse(utils.bool_from_str('False')) - self.assertFalse(utils.bool_from_str('false')) - self.assertFalse(utils.bool_from_str('no')) - self.assertFalse(utils.bool_from_str('No')) - self.assertFalse(utils.bool_from_str('n')) - self.assertFalse(utils.bool_from_str('N')) - self.assertFalse(utils.bool_from_str('0')) - self.assertFalse(utils.bool_from_str(None)) - self.assertFalse(utils.bool_from_str('junk')) - def test_read_cached_file(self): self.mox.StubOutWithMock(os.path, "getmtime") os.path.getmtime(mox.IgnoreArg()).AndReturn(1) diff --git a/nova/utils.py b/nova/utils.py index 3af79a6df..3020781c8 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -597,19 +597,6 @@ def parse_server_string(server_str): return ('', '') -def bool_from_str(val): - """Convert a string representation of a bool into a bool value.""" - - if not val: - return False - try: - return True if int(val) else False - except ValueError: - return val.lower() == 'true' or \ - val.lower() == 'yes' or \ - val.lower() == 'y' - - def is_int_like(val): """Check if a value looks like an int.""" try: diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 30cee234a..4cd2b2ab5 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -45,6 +45,7 @@ from nova.image import glance from nova.openstack.common import excutils from nova.openstack.common import log as logging from nova.openstack.common import processutils +from nova.openstack.common import strutils from nova import utils from nova.virt import configdrive from nova.virt.disk import api as disk @@ -998,7 +999,7 @@ def _create_image(context, session, instance, name_label, image_id, elif cache_images == 'some': sys_meta = utils.metadata_to_dict(instance['system_metadata']) try: - cache = utils.bool_from_str(sys_meta['image_cache_in_nova']) + cache = strutils.bool_from_string(sys_meta['image_cache_in_nova']) except KeyError: cache = False elif cache_images == 'none': @@ -1091,7 +1092,8 @@ def _image_uses_bittorrent(context, instance): elif xenapi_torrent_images == 'some': sys_meta = utils.metadata_to_dict(instance['system_metadata']) try: - bittorrent = utils.bool_from_str(sys_meta['image_bittorrent']) + bittorrent = strutils.bool_from_string( + sys_meta['image_bittorrent']) except KeyError: pass elif xenapi_torrent_images == 'none': diff --git a/openstack-common.conf b/openstack-common.conf index 4412c09ac..ad48a188a 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -25,6 +25,7 @@ module=policy module=processutils module=rootwrap module=rpc +module=strutils module=timeutils module=uuidutils module=version |