From 2a6f97940f71c056b4bfb0cd9a86f5d676abc4e1 Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Mon, 11 Jul 2011 13:34:39 -0700 Subject: add optional parameter networks to the Create server OS API --- bin/nova-manage | 6 +- nova/api/openstack/create_instance_helper.py | 55 +++++++++++- nova/compute/api.py | 34 ++++++-- nova/compute/manager.py | 4 +- nova/db/api.py | 32 +++++++ nova/db/sqlalchemy/api.py | 125 ++++++++++++++++++++++++++- nova/exception.py | 26 ++++++ nova/network/api.py | 9 ++ nova/network/manager.py | 125 ++++++++++++++++++++++----- nova/utils.py | 16 ++++ 10 files changed, 394 insertions(+), 38 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 7dfe91698..53e3d5c93 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -615,12 +615,14 @@ class NetworkCommands(object): def list(self): """List all created networks""" - print "%-18s\t%-15s\t%-15s\t%-15s" % (_('network'), + print "%-5s\t%-18s\t%-15s\t%-15s\t%-15s" % (_('id'), + _('network'), _('netmask'), _('start address'), 'DNS') for network in db.network_get_all(context.get_admin_context()): - print "%-18s\t%-15s\t%-15s\t%-15s" % (network.cidr, + print "%-5s\t%-18s\t%-15s\t%-15s\t%-15s" % (network.id, + network.cidr, network.netmask, network.dhcp_start, network.dns) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 1066713a3..839aa9fb9 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -32,7 +32,6 @@ from nova.api.openstack import faults from nova.api.openstack import wsgi from nova.auth import manager as auth_manager - LOG = logging.getLogger('nova.api.openstack.create_instance_helper') FLAGS = flags.FLAGS @@ -102,6 +101,15 @@ class CreateInstanceHelper(object): if personality: injected_files = self._get_injected_files(personality) + requested_networks = body['server'].get('networks') + + if requested_networks is not None: + if len(requested_networks) == 0: + msg = _("No networks found") + raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + requested_networks = self._get_requested_networks( + requested_networks) + flavor_id = self.controller._flavor_id_from_req_data(body) if not 'name' in body['server']: @@ -148,12 +156,17 @@ class CreateInstanceHelper(object): zone_blob=zone_blob, reservation_id=reservation_id, min_count=min_count, - max_count=max_count)) + max_count=max_count, + requested_networks=requested_networks)) except quota.QuotaError as error: self._handle_quota_error(error) except exception.ImageNotFound as error: msg = _("Can not find requested image") raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + except exception.NovaException as ex: + LOG.error(ex) + msg = _("Failed to create server: %s") % ex + raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) # Let the caller deal with unhandled exceptions. @@ -276,6 +289,44 @@ class CreateInstanceHelper(object): raise exc.HTTPBadRequest(explanation=msg) return password + def _get_requested_networks(self, requested_networks): + """ + Create a list of requested networks from the networks attribute + """ + networks = [] + for network in requested_networks: + try: + network_id = network['id'] + network_id = int(network_id) + #fixed IP address is optional + #if the fixed IP address is not provided then + #it will used one of the available IP address from the network + fixed_ip = network.get('fixed_ip', None) + + # check if the network id is already present in the list, + # we don't want duplicate networks to be passed + # at the boot time + for id, ip in networks: + if id == network_id: + expl = _("Duplicate networks (%s) are not allowed")\ + % network_id + raise faults.Fault(exc.HTTPBadRequest( + explanation=expl)) + + networks.append((network_id, fixed_ip)) + except KeyError as key: + expl = _('Bad network format: missing %s') % key + raise faults.Fault(exc.HTTPBadRequest(explanation=expl)) + except ValueError: + expl = _("Bad networks format: network id should " + "be integer (%s)") % network_id + raise faults.Fault(exc.HTTPBadRequest(explanation=expl)) + except TypeError: + expl = _('Bad networks format') + raise faults.Fault(exc.HTTPBadRequest(explanation=expl)) + + return networks + class ServerXMLDeserializer(wsgi.XMLDeserializer): """ diff --git a/nova/compute/api.py b/nova/compute/api.py index 28459dc75..273ff285d 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -95,6 +95,7 @@ class API(base.Base): if not network_api: network_api = network.API() self.network_api = network_api + self.network_manager = utils.import_object(FLAGS.network_manager) if not volume_api: volume_api = volume.API() self.volume_api = volume_api @@ -142,6 +143,16 @@ class API(base.Base): LOG.warn(msg) raise quota.QuotaError(msg, "MetadataLimitExceeded") + def _check_requested_networks(self, context, requested_networks): + """ Check if the networks requested belongs to the project + and the fixed IP address for each network provided is within + same the network block + """ + if requested_networks is None: + return + + self.network_api.validate_networks(context, requested_networks) + def _check_create_parameters(self, context, instance_type, image_href, kernel_id=None, ramdisk_id=None, min_count=None, max_count=None, @@ -149,7 +160,7 @@ class API(base.Base): key_name=None, key_data=None, security_group='default', availability_zone=None, user_data=None, metadata={}, injected_files=None, admin_password=None, zone_blob=None, - reservation_id=None): + reservation_id=None, requested_networks=None): """Verify all the input parameters regardless of the provisioning strategy being performed.""" @@ -176,6 +187,7 @@ class API(base.Base): self._check_metadata_properties_quota(context, metadata) self._check_injected_file_quota(context, injected_files) + self._check_requested_networks(context, requested_networks) (image_service, image_id) = nova.image.get_image_service(image_href) image = image_service.show(context, image_id) @@ -315,7 +327,8 @@ class API(base.Base): instance_type, zone_blob, availability_zone, injected_files, admin_password, - instance_id=None, num_instances=1): + instance_id=None, num_instances=1, + requested_networks=None): """Send the run_instance request to the schedulers for processing.""" pid = context.project_id uid = context.user_id @@ -343,7 +356,8 @@ class API(base.Base): "request_spec": request_spec, "availability_zone": availability_zone, "admin_password": admin_password, - "injected_files": injected_files}}) + "injected_files": injected_files, + "requested_networks": requested_networks}}) def create_all_at_once(self, context, instance_type, image_href, kernel_id=None, ramdisk_id=None, @@ -381,7 +395,8 @@ class API(base.Base): key_name=None, key_data=None, security_group='default', availability_zone=None, user_data=None, metadata={}, injected_files=None, admin_password=None, zone_blob=None, - reservation_id=None, block_device_mapping=None): + reservation_id=None, block_device_mapping=None, + requested_networks=None): """ Provision the instances by sending off a series of single instance requests to the Schedulers. This is fine for trival @@ -402,7 +417,7 @@ class API(base.Base): key_name, key_data, security_group, availability_zone, user_data, metadata, injected_files, admin_password, zone_blob, - reservation_id) + reservation_id, requested_networks) instances = [] LOG.debug(_("Going to run %s instances..."), num_instances) @@ -414,10 +429,11 @@ class API(base.Base): instance_id = instance['id'] self._ask_scheduler_to_create_instance(context, base_options, - instance_type, zone_blob, - availability_zone, injected_files, - admin_password, - instance_id=instance_id) + instance_type, zone_blob, + availability_zone, injected_files, + admin_password, + instance_id=instance_id, + requested_networks=requested_networks) return [dict(x.iteritems()) for x in instances] diff --git a/nova/compute/manager.py b/nova/compute/manager.py index bbbddde0a..f77728034 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -269,6 +269,7 @@ class ComputeManager(manager.SchedulerDependentManager): context = context.elevated() instance = self.db.instance_get(context, instance_id) instance.injected_files = kwargs.get('injected_files', []) + requested_networks = kwargs.get('requested_networks', None) instance.admin_pass = kwargs.get('admin_password', None) if instance['name'] in self.driver.list_instances(): raise exception.Error(_("Instance has already been created")) @@ -291,7 +292,8 @@ class ComputeManager(manager.SchedulerDependentManager): # will eventually also need to save the address here. if not FLAGS.stub_network: network_info = self.network_api.allocate_for_instance(context, - instance, vpn=is_vpn) + instance, vpn=is_vpn, + requested_networks=requested_networks) LOG.debug(_("instance network_info: |%s|"), network_info) self.network_manager.setup_compute_network(context, instance_id) diff --git a/nova/db/api.py b/nova/db/api.py index b7c5700e5..ca904738d 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -341,6 +341,14 @@ def fixed_ip_associate_pool(context, network_id, instance_id): return IMPL.fixed_ip_associate_pool(context, network_id, instance_id) +def fixed_ip_associate_by_address(context, network_id, instance_id, address): + """check if the address is free and is in the network + and it is not associated to any instance. + """ + return IMPL.fixed_ip_associate_by_address(context, network_id, + instance_id, address) + + def fixed_ip_create(context, values): """Create a fixed ip from the values dictionary.""" return IMPL.fixed_ip_create(context, values) @@ -400,6 +408,13 @@ def fixed_ip_update(context, address, values): return IMPL.fixed_ip_update(context, address, values) +def fixed_ip_validate_by_network_address(context, network_id, + address): + """validates if the address belongs to the network""" + return IMPL.fixed_ip_validate_by_network_address(context, network_id, + address) + + #################### @@ -689,7 +704,14 @@ def network_get_all(context): return IMPL.network_get_all(context) +def network_get_requested_networks(context, requested_networks): + """Return all defined networks.""" + return IMPL.network_get_requested_networks(context, requested_networks) + + # pylint: disable=C0103 + + def network_get_associated_fixed_ips(context, network_id): """Get all network's ips that have been associated.""" return IMPL.network_get_associated_fixed_ips(context, network_id) @@ -1228,6 +1250,16 @@ def project_get_networks(context, project_id, associate=True): return IMPL.project_get_networks(context, project_id, associate) +def project_get_requested_networks(context, requested_networks): + """Return the network associated with the project. + + If associate is true, it will attempt to associate a new + network if one is not found, otherwise it returns None. + + """ + return IMPL.project_get_requested_networks(context, requested_networks) + + def project_get_networks_v6(context, project_id): return IMPL.project_get_networks_v6(context, project_id) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index ffd009513..d5cfc6099 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -251,7 +251,7 @@ def service_get_all_network_sorted(context): session = get_session() with session.begin(): topic = 'network' - label = 'network_count' + label = 'network_' subq = session.query(models.Network.host, func.count(models.Network.id).label(label)).\ filter_by(deleted=False).\ @@ -684,6 +684,40 @@ def fixed_ip_associate_pool(context, network_id, instance_id): return fixed_ip_ref['address'] +@require_admin_context +def fixed_ip_associate_by_address(context, network_id, instance_id, + address): + if address is None: + return fixed_ip_associate_pool(context, network_id, instance_id) + + session = get_session() + with session.begin(): + fixed_ip_ref = session.query(models.FixedIp).\ + filter_by(reserved=False).\ + filter_by(deleted=False).\ + filter_by(network_id=network_id).\ + filter_by(address=address).\ + with_lockmode('update').\ + first() + # NOTE(vish): if with_lockmode isn't supported, as in sqlite, + # then this has concurrency issues + if fixed_ip_ref is None: + raise exception.FixedIpNotFoundForNetwork(address=address, + network_id=network_id) + if fixed_ip_ref.instance is not None: + raise exception.FixedIpAlreadyInUse(address=address) + + if not fixed_ip_ref.network: + fixed_ip_ref.network = network_get(context, + network_id, + session=session) + fixed_ip_ref.instance = instance_get(context, + instance_id, + session=session) + session.add(fixed_ip_ref) + return fixed_ip_ref['address'] + + @require_context def fixed_ip_create(_context, values): fixed_ip_ref = models.FixedIp() @@ -771,6 +805,26 @@ def fixed_ip_get_by_address(context, address, session=None): return result +@require_context +def fixed_ip_validate_by_network_address(context, network_id, + address): + session = get_session() + fixed_ip_ref = session.query(models.FixedIp).\ + filter_by(address=address).\ + filter_by(reserved=False).\ + filter_by(network_id=network_id).\ + filter_by(deleted=can_read_deleted(context)).\ + first() + + if fixed_ip_ref is None: + raise exception.FixedIpNotFoundForNetwork(address=address, + network_id=network_id) + if fixed_ip_ref.instance is not None: + raise exception.FixedIpAlreadyInUse(address=address) + + return fixed_ip_ref + + @require_context def fixed_ip_get_by_instance(context, instance_id): session = get_session() @@ -1613,6 +1667,39 @@ def network_get_all(context): return result +@require_admin_context +def network_get_requested_networks(context, requested_networks): + session = get_session() + + network_ids = [] + for id, fixed_ip in requested_networks: + network_ids.append(id) + + result = session.query(models.Network).\ + filter(models.Network.id.in_(network_ids)).\ + filter_by(deleted=False).all() + if not result: + raise exception.NoNetworksFound() + + #check if host is set to all of the networks + # returned in the result + for network in result: + if network['host'] is None: + raise exception.NetworkHostNotSet(network_id=network['id']) + + #check if the result contains all the networks + #we are looking for + for network_id in network_ids: + found = False + for network in result: + if network['id'] == network_id: + found = True + break + if not found: + raise exception.NetworkNotFound(network_id=network_id) + + return result + # NOTE(vish): pylint complains because of the long method name, but # it fits with the names of the rest of the methods # pylint: disable=C0103 @@ -2727,6 +2814,42 @@ def project_get_networks(context, project_id, associate=True): return result +@require_context +def project_get_requested_networks(context, requested_networks): + session = get_session() + + network_ids = [] + for id, fixed_ip in requested_networks: + network_ids.append(id) + + result = session.query(models.Network).\ + filter(models.Network.id.in_(network_ids)).\ + filter_by(deleted=False).\ + filter_by(project_id=context.project_id).all() + + if not result: + raise exception.NoNetworksFound() + + #check if host is set to all of the networks + # returned in the result + for network in result: + if network['host'] is None: + raise exception.NetworkHostNotSet(network_id=network['id']) + + #check if the result contains all the networks + #we are looking for + for network_id in network_ids: + found = False + for network in result: + if network['id'] == network_id: + found = True + break + if not found: + raise exception.NetworkNotFoundForProject(network_id=network_id, + project_id=context.project_id) + return result + + @require_context def project_get_networks_v6(context, project_id): return project_get_networks(context, project_id) diff --git a/nova/exception.py b/nova/exception.py index a6776b64f..a1803d4f6 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -142,6 +142,10 @@ class Invalid(NovaException): message = _("Unacceptable parameters.") +class AlreadyInUse(NovaException): + message = _("Already is in use.") + + class InvalidSignature(Invalid): message = _("Invalid signature %(signature)s for user %(user)s.") @@ -361,6 +365,15 @@ class NoNetworksFound(NotFound): message = _("No networks defined.") +class NetworkNotFoundForProject(NotFound): + message = _("Either Network %(network_id)s is not present or " + "is not assigned to the project %(project_id)s.") + + +class NetworkHostNotSet(NovaException): + message = _("Host is not set to the network (%(network_id)s).") + + class DatastoreNotFound(NotFound): message = _("Could not find the datastore reference(s) which the VM uses.") @@ -385,6 +398,19 @@ class FixedIpNotFoundForHost(FixedIpNotFound): message = _("Host %(host)s has zero fixed ips.") +class FixedIpNotFoundForNetwork(FixedIpNotFound): + message = _("Fixed IP address (%(address)s) does not exist in " + "network (%(network_id)s).") + + +class FixedIpAlreadyInUse(AlreadyInUse): + message = _("Fixed IP address %(address)s is already in use.") + + +class FixedIpInvalid(Invalid): + message = _("Fixed IP address %(address)s is invalid.") + + class NoMoreFixedIps(Error): message = _("Zero fixed ips available.") diff --git a/nova/network/api.py b/nova/network/api.py index b2b96082b..0993038a7 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -169,3 +169,12 @@ class API(base.Base): return rpc.call(context, FLAGS.network_topic, {'method': 'get_instance_nw_info', 'args': args}) + + def validate_networks(self, context, requested_networks): + """validate the networks passed at the time of creating + the server + """ + args = {'networks': requested_networks} + return rpc.call(context, FLAGS.network_topic, + {'method': 'validate_networks', + 'args': args}) diff --git a/nova/network/manager.py b/nova/network/manager.py index d42bc8c4e..746b2ecc2 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -124,11 +124,18 @@ class RPCAllocateFixedIP(object): used since they share code to RPC.call allocate_fixed_ip on the correct network host to configure dnsmasq """ - def _allocate_fixed_ips(self, context, instance_id, networks): + def _allocate_fixed_ips(self, context, instance_id, networks, + requested_networks=None): """Calls allocate_fixed_ip once for each network.""" green_pool = greenpool.GreenPool() - for network in networks: + address = None + if requested_networks is not None: + for id, fixed_ip in requested_networks: + if (network['id'] == id): + address = fixed_ip + break + if network['host'] != self.host: # need to call allocate_fixed_ip to correct network host topic = self.db.queue_get_for(context, FLAGS.network_topic, @@ -136,23 +143,25 @@ class RPCAllocateFixedIP(object): args = {} args['instance_id'] = instance_id args['network_id'] = network['id'] - + args['address'] = address green_pool.spawn_n(rpc.call, context, topic, {'method': '_rpc_allocate_fixed_ip', 'args': args}) else: # i am the correct host, run here - self.allocate_fixed_ip(context, instance_id, network) + self.allocate_fixed_ip(context, instance_id, network, + address=address) # wait for all of the allocates (if any) to finish green_pool.waitall() - def _rpc_allocate_fixed_ip(self, context, instance_id, network_id): + def _rpc_allocate_fixed_ip(self, context, instance_id, network_id, + address=None): """Sits in between _allocate_fixed_ips and allocate_fixed_ip to perform network lookup on the far side of rpc. """ network = self.db.network_get(context, network_id) - self.allocate_fixed_ip(context, instance_id, network) + self.allocate_fixed_ip(context, instance_id, network, address=address) class FloatingIP(object): @@ -185,6 +194,7 @@ class FloatingIP(object): """ instance_id = kwargs.get('instance_id') project_id = kwargs.get('project_id') + requested_networks = kwargs.get('requested_networks') LOG.debug(_("floating IP allocation for instance |%s|"), instance_id, context=context) # call the next inherited class's allocate_for_instance() @@ -339,16 +349,21 @@ class NetworkManager(manager.SchedulerDependentManager): networks = self.db.network_get_all(context) for network in networks: host = network['host'] - if not host: + if host: # return so worker will only grab 1 (to help scale flatter) return self.set_network_host(context, network['id']) - def _get_networks_for_instance(self, context, instance_id, project_id): + def _get_networks_for_instance(self, context, instance_id, project_id, + requested_networks=None): """Determine & return which networks an instance should connect to.""" # TODO(tr3buchet) maybe this needs to be updated in the future if # there is a better way to determine which networks # a non-vlan instance should connect to - networks = self.db.network_get_all(context) + if requested_networks: + networks = self.db.network_get_requested_networks(context, + requested_networks) + else: + networks = self.db.network_get_all(context) # return only networks which are not vlan networks and have host set return [network for network in networks if @@ -362,13 +377,19 @@ class NetworkManager(manager.SchedulerDependentManager): instance_id = kwargs.pop('instance_id') project_id = kwargs.pop('project_id') type_id = kwargs.pop('instance_type_id') + requested_networks = kwargs.pop('requested_networks') + LOG.debug(requested_networks) admin_context = context.elevated() LOG.debug(_("network allocations for instance %s"), instance_id, context=context) - networks = self._get_networks_for_instance(admin_context, instance_id, - project_id) + networks = self._get_networks_for_instance(admin_context, + instance_id, + project_id, + requested_networks) self._allocate_mac_addresses(context, instance_id, networks) - self._allocate_fixed_ips(admin_context, instance_id, networks) + self._allocate_fixed_ips(admin_context, instance_id, + networks, + requested_networks=requested_networks) return self.get_instance_nw_info(context, instance_id, type_id) def deallocate_for_instance(self, context, **kwargs): @@ -486,9 +507,11 @@ class NetworkManager(manager.SchedulerDependentManager): # with a network, or a cluster of computes with a network # and use that network here with a method like # network_get_by_compute_host - address = self.db.fixed_ip_associate_pool(context.elevated(), + address = kwargs.get('address', None) + address = self.db.fixed_ip_associate_by_address(context.elevated(), network['id'], - instance_id) + instance_id, + address) vif = self.db.virtual_interface_get_by_instance_and_network(context, instance_id, network['id']) @@ -637,7 +660,8 @@ class NetworkManager(manager.SchedulerDependentManager): 'address': address, 'reserved': reserved}) - def _allocate_fixed_ips(self, context, instance_id, networks): + def _allocate_fixed_ips(self, context, instance_id, networks, + requested_networks=None): """Calls allocate_fixed_ip once for each network.""" raise NotImplementedError() @@ -653,6 +677,25 @@ class NetworkManager(manager.SchedulerDependentManager): """ raise NotImplementedError() + def validate_networks(self, context, networks): + """check if the networks exists and host + is set to each network. + """ + if networks is None: + return + + result = self.db.network_get_requested_networks(context, networks) + for network_id, fixed_ip in networks: + # check if the fixed IP address is valid and + # it actually belongs to the network + if fixed_ip is not None: + if not utils.is_valid_ipv4(fixed_ip): + raise exception.FixedIpInvalid(address=fixed_ip) + + self.db.fixed_ip_validate_by_network_address(context, + network_id, + fixed_ip) + class FlatManager(NetworkManager): """Basic network where no vlans are used. @@ -684,10 +727,18 @@ class FlatManager(NetworkManager): timeout_fixed_ips = False - def _allocate_fixed_ips(self, context, instance_id, networks): + def _allocate_fixed_ips(self, context, instance_id, networks, + requested_networks=None): """Calls allocate_fixed_ip once for each network.""" for network in networks: - self.allocate_fixed_ip(context, instance_id, network) + address = None + if requested_networks is not None: + for id, fixed_ip in requested_networks: + if (network['id'] == id): + address = fixed_ip + break + self.allocate_fixed_ip(context, instance_id, + network, address=address) def deallocate_fixed_ip(self, context, address, **kwargs): """Returns a fixed ip to the pool.""" @@ -741,11 +792,13 @@ class FlatDHCPManager(FloatingIP, RPCAllocateFixedIP, NetworkManager): self.driver.ensure_bridge(network['bridge'], network['bridge_interface']) - def allocate_fixed_ip(self, context, instance_id, network): + def allocate_fixed_ip(self, context, instance_id, network, + address=None): """Allocate flat_network fixed_ip, then setup dhcp for this network.""" address = super(FlatDHCPManager, self).allocate_fixed_ip(context, instance_id, - network) + network, + address) if not FLAGS.fake_network: self.driver.update_dhcp(context, network['id']) @@ -794,15 +847,17 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): def allocate_fixed_ip(self, context, instance_id, network, **kwargs): """Gets a fixed ip from the pool.""" + address = kwargs.get('address', None) if kwargs.get('vpn', None): address = network['vpn_private_address'] self.db.fixed_ip_associate(context, address, instance_id) else: - address = self.db.fixed_ip_associate_pool(context, + address = self.db.fixed_ip_associate_by_address(context, network['id'], - instance_id) + instance_id, + address) vif = self.db.virtual_interface_get_by_instance_and_network(context, instance_id, network['id']) @@ -826,10 +881,15 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): network['bridge'], network['bridge_interface']) - def _get_networks_for_instance(self, context, instance_id, project_id): + def _get_networks_for_instance(self, context, instance_id, project_id, + requested_networks=None): """Determine which networks an instance should connect to.""" # get networks associated with project - networks = self.db.project_get_networks(context, project_id) + if requested_networks is not None: + networks = self.db.project_get_requested_networks(context, + requested_networks) + else: + networks = self.db.project_get_networks(context, project_id) # return only networks which have host set return [network for network in networks if network['host']] @@ -878,6 +938,25 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): if(FLAGS.use_ipv6): self.driver.update_ra(context, network_id) + def validate_networks(self, context, networks): + """check if the networks exists and host + is set to each network. + """ + if networks is None: + return + + result = self.db.project_get_requested_networks(context, networks) + for network_id, fixed_ip in networks: + # check if the fixed IP address is valid and + # it actually belongs to the network + if fixed_ip is not None: + if not utils.is_valid_ipv4(fixed_ip): + raise exception.FixedIpInvalid(address=fixed_ip) + + self.db.fixed_ip_validate_by_network_address(context, + network_id, + fixed_ip) + @property def _bottom_reserved_ips(self): """Number of reserved ips at the bottom of the range.""" diff --git a/nova/utils.py b/nova/utils.py index 8784a227d..22b3a29bf 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -775,6 +775,22 @@ def bool_from_str(val): return val.lower() == 'true' +def is_valid_ipv4(address): + """valid the address strictly as per format xxx.xxx.xxx.xxx. + where xxx is a value between 0 and 255. + """ + parts = address.split(".") + if len(parts) != 4: + return False + for item in parts: + try: + if not 0 <= int(item) <= 255: + return False + except ValueError: + return False + return True + + class Bootstrapper(object): """Provides environment bootstrapping capabilities for entry points.""" -- cgit From e30a9e1516e44d839ebd5b41586a32e99c47b8c9 Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Mon, 11 Jul 2011 14:25:51 -0700 Subject: unknowingly made these changes, reverting to original --- nova/network/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/network/manager.py b/nova/network/manager.py index 746b2ecc2..dd847ac28 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -349,7 +349,7 @@ class NetworkManager(manager.SchedulerDependentManager): networks = self.db.network_get_all(context) for network in networks: host = network['host'] - if host: + if not host: # return so worker will only grab 1 (to help scale flatter) return self.set_network_host(context, network['id']) -- cgit From bf30f9c1d65053aba29ffff8d8d9a3810455a082 Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Mon, 11 Jul 2011 16:02:46 -0700 Subject: fixed all failed unit test cases --- nova/compute/api.py | 8 +++++--- nova/tests/test_network.py | 5 +++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index ddcb23db5..fd7df4560 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -366,7 +366,8 @@ class API(base.Base): key_name=None, key_data=None, security_group='default', availability_zone=None, user_data=None, metadata={}, injected_files=None, admin_password=None, zone_blob=None, - reservation_id=None, block_device_mapping=None): + reservation_id=None, block_device_mapping=None, + requested_networks=None): """Provision the instances by passing the whole request to the Scheduler for execution. Returns a Reservation ID related to the creation of all of these instances.""" @@ -378,13 +379,14 @@ class API(base.Base): key_name, key_data, security_group, availability_zone, user_data, metadata, injected_files, admin_password, zone_blob, - reservation_id) + reservation_id, requested_networks) self._ask_scheduler_to_create_instance(context, base_options, instance_type, zone_blob, availability_zone, injected_files, admin_password, - num_instances=num_instances) + num_instances=num_instances, + requested_networks=requested_networks) return base_options['reservation_id'] diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index 6d5166019..30e1c0512 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -211,12 +211,13 @@ class VlanNetworkTestCase(test.TestCase): self.network.allocate_fixed_ip(None, 0, network, vpn=True) def test_allocate_fixed_ip(self): - self.mox.StubOutWithMock(db, 'fixed_ip_associate_pool') + self.mox.StubOutWithMock(db, 'fixed_ip_associate_by_address') self.mox.StubOutWithMock(db, 'fixed_ip_update') self.mox.StubOutWithMock(db, 'virtual_interface_get_by_instance_and_network') - db.fixed_ip_associate_pool(mox.IgnoreArg(), + db.fixed_ip_associate_by_address(mox.IgnoreArg(), + mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()).AndReturn('192.168.0.1') db.fixed_ip_update(mox.IgnoreArg(), -- cgit From 51834c2141bdbc283b9d165372be08eb6b9409ca Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Tue, 12 Jul 2011 10:36:55 -0700 Subject: Allowed empty networks, handled RemoteError properly, implemented xml format for networks and fixed broken unit test cases --- nova/api/openstack/create_instance_helper.py | 29 ++++++++++++++++++++++------ nova/network/manager.py | 4 ++-- nova/tests/api/openstack/test_servers.py | 14 ++++++++++++++ 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index ef2a8393f..5e1c8d8d9 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -25,6 +25,7 @@ from nova import flags from nova import log as logging import nova.image from nova import quota +from nova import rpc from nova import utils from nova.compute import instance_types @@ -104,9 +105,6 @@ class CreateInstanceHelper(object): requested_networks = body['server'].get('networks') if requested_networks is not None: - if len(requested_networks) == 0: - msg = _("No networks found") - raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) requested_networks = self._get_requested_networks( requested_networks) @@ -163,9 +161,9 @@ class CreateInstanceHelper(object): except exception.ImageNotFound as error: msg = _("Can not find requested image") raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) - except exception.NovaException as ex: - LOG.error(ex) - msg = _("Failed to create server: %s") % ex + except rpc.RemoteError as err: + LOG.error(err) + msg = _("%s:%s") % (err.exc_type, err.value) raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) # Let the caller deal with unhandled exceptions. @@ -355,6 +353,9 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): personality = self._extract_personality(server_node) if personality is not None: server["personality"] = personality + networks = self._extract_networks(server_node) + if networks is not None: + server["networks"] = networks return server def _extract_metadata(self, server_node): @@ -383,6 +384,22 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): personality.append(item) return personality + def _extract_networks(self, server_node): + """Marshal the networks attribute of a parsed request""" + networks_node = \ + self._find_first_child_named(server_node, "networks") + if networks_node is None: + return None + networks = [] + for network_node in self._find_children_named(networks_node, "network"): + item = {} + if network_node.hasAttribute("id"): + item["id"] = network_node.getAttribute("id") + if network_node.hasAttribute("fixed_ip"): + item["fixed_ip"] = network_node.getAttribute("fixed_ip") + networks.append(item) + return networks + def _find_first_child_named(self, parent, name): """Search a nodes children for the first child with a given name""" for node in parent.childNodes: diff --git a/nova/network/manager.py b/nova/network/manager.py index dd847ac28..90ff81374 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -359,7 +359,7 @@ class NetworkManager(manager.SchedulerDependentManager): # TODO(tr3buchet) maybe this needs to be updated in the future if # there is a better way to determine which networks # a non-vlan instance should connect to - if requested_networks: + if requested_networks is not None and len(requested_networks) != 0: networks = self.db.network_get_requested_networks(context, requested_networks) else: @@ -885,7 +885,7 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): requested_networks=None): """Determine which networks an instance should connect to.""" # get networks associated with project - if requested_networks is not None: + if requested_networks is not None and len(requested_networks) != 0: networks = self.db.project_get_requested_networks(context, requested_networks) else: diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 8e82a7edc..3bac184d5 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1902,6 +1902,20 @@ b25zLiINCg0KLVJpY2hhcmQgQmFjaA==""", self.assertEquals(request['body']["server"]["imageRef"], "http://localhost:8774/v1.1/images/1") + def test_request_with_empty_networks(self): + serial_request = """ + + +""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageId": "1", + "flavorId": "1", + "networks": [], + }} + self.assertEquals(request['body'], expected) class TestServerInstanceCreation(test.TestCase): -- cgit From 2be9a4e19449f9cf37f62f3f6e380de3e7ca0d38 Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Tue, 12 Jul 2011 11:28:06 -0700 Subject: added xml deserialization unit test cases and fixe some pep errors --- nova/api/openstack/create_instance_helper.py | 3 +- nova/tests/api/openstack/test_servers.py | 144 +++++++++++++++++++++++++++ 2 files changed, 146 insertions(+), 1 deletion(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 5e1c8d8d9..86fa8becc 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -391,7 +391,8 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): if networks_node is None: return None networks = [] - for network_node in self._find_children_named(networks_node, "network"): + for network_node in self._find_children_named(networks_node, + "network"): item = {} if network_node.hasAttribute("id"): item["id"] = network_node.getAttribute("id") diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 3bac184d5..cde6bd310 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1917,6 +1917,150 @@ b25zLiINCg0KLVJpY2hhcmQgQmFjaA==""", }} self.assertEquals(request['body'], expected) + def test_request_with_one_network(self): + serial_request = """ + + + + +""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageId": "1", + "flavorId": "1", + "networks": [{"id": "1", "fixed_ip": "10.0.1.12"}], + }} + self.assertEquals(request['body'], expected) + + def test_request_with_two_networks(self): + serial_request = """ + + + + + +""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageId": "1", + "flavorId": "1", + "networks": [{"id": "1", "fixed_ip": "10.0.1.12"}, + {"id": "2", "fixed_ip": "10.0.2.12"}], + }} + self.assertEquals(request['body'], expected) + + def test_request_with_second_network_node_ignored(self): + serial_request = """ + + + + + + + +""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageId": "1", + "flavorId": "1", + "networks": [{"id": "1", "fixed_ip": "10.0.1.12"}], + }} + self.assertEquals(request['body'], expected) + + def test_request_with_one_network_missing_id(self): + serial_request = """ + + + + +""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageId": "1", + "flavorId": "1", + "networks": [{"fixed_ip": "10.0.1.12"}], + }} + self.assertEquals(request['body'], expected) + + def test_request_with_one_network_missing_fixed_ip(self): + serial_request = """ + + + + +""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageId": "1", + "flavorId": "1", + "networks": [{"id": "1"}], + }} + self.assertEquals(request['body'], expected) + + def test_request_with_one_network_empty_id(self): + serial_request = """ + + + + + """ + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageId": "1", + "flavorId": "1", + "networks": [{"id": "", "fixed_ip": "10.0.1.12"}], + }} + self.assertEquals(request['body'], expected) + + def test_request_with_one_network_empty_fixed_ip(self): + serial_request = """ + + + + + """ + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageId": "1", + "flavorId": "1", + "networks": [{"id": "1", "fixed_ip": ""}], + }} + self.assertEquals(request['body'], expected) + + def test_request_with_networks_duplicate_ids(self): + serial_request = """ + + + + + + """ + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageId": "1", + "flavorId": "1", + "networks": [{"id": "1", "fixed_ip": "10.0.1.12"}, + {"id": "1", "fixed_ip": "10.0.2.12"}], + }} + self.assertEquals(request['body'], expected) + + class TestServerInstanceCreation(test.TestCase): def setUp(self): -- cgit From 6e75e608cc7260317f014e57ba070b152f83d0e7 Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Tue, 12 Jul 2011 17:46:03 -0700 Subject: added unit test cases and minor changes (localization fix and added fixed_ip validation) --- nova/api/openstack/create_instance_helper.py | 18 ++- nova/tests/api/openstack/test_servers.py | 200 ++++++++++++++++++++++++++- 2 files changed, 212 insertions(+), 6 deletions(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 86fa8becc..5eef0ae00 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -162,8 +162,7 @@ class CreateInstanceHelper(object): msg = _("Can not find requested image") raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) except rpc.RemoteError as err: - LOG.error(err) - msg = _("%s:%s") % (err.exc_type, err.value) + msg = _("%(err.exc_type)s:%(err.value)s") raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) # Let the caller deal with unhandled exceptions. @@ -205,6 +204,15 @@ class CreateInstanceHelper(object): msg = _("Server name is an empty string") raise exc.HTTPBadRequest(explanation=msg) + def _validate_fixed_ip(self, value): + if not isinstance(value, basestring): + msg = _("Fixed IP is not a string or unicode") + raise exc.HTTPBadRequest(explanation=msg) + + if value.strip() == '': + msg = _("Fixed IP is an empty string") + raise exc.HTTPBadRequest(explanation=msg) + def _get_kernel_ramdisk_from_image(self, req, image_id): """Fetch an image from the ImageService, then if present, return the associated kernel and ramdisk image IDs. @@ -298,9 +306,10 @@ class CreateInstanceHelper(object): network_id = int(network_id) #fixed IP address is optional #if the fixed IP address is not provided then - #it will used one of the available IP address from the network + #it will use one of the available IP address from the network fixed_ip = network.get('fixed_ip', None) - + if fixed_ip is not None: + self._validate_fixed_ip(fixed_ip) # check if the network id is already present in the list, # we don't want duplicate networks to be passed # at the boot time @@ -404,6 +413,7 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): def _find_first_child_named(self, parent, name): """Search a nodes children for the first child with a given name""" for node in parent.childNodes: + LOG.debug(node.nodeName) if node.nodeName == name: return node return None diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index cde6bd310..f70ad41cc 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -588,6 +588,11 @@ class ServersTest(test.TestCase): def project_get_networks(context, user_id): return dict(id='1', host='localhost') + def project_get_requested_networks(context, requested_networks): + return [{'id': 1, 'host': 'localhost1'}, + {'id': 2, 'host': 'localhost2'}, + ] + def queue_get_for(context, *args): return 'network_topic' @@ -599,6 +604,8 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.db.api, 'project_get_networks', project_get_networks) + self.stubs.Set(nova.db.api, 'project_get_requested_networks', + project_get_requested_networks) self.stubs.Set(nova.db.api, 'instance_create', instance_create) self.stubs.Set(nova.rpc, 'cast', fake_method) self.stubs.Set(nova.rpc, 'call', fake_method) @@ -901,6 +908,29 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) + def test_create_instance_whitespace_name(self): + self._setup_for_create_instance() + + body = { + 'server': { + 'name': ' ', + 'imageId': 3, + 'flavorId': 1, + 'metadata': { + 'hello': 'world', + 'open': 'stack', + }, + 'personality': {}, + }, + } + + req = webob.Request.blank('/v1.0/servers') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + def test_update_no_body(self): req = webob.Request.blank('/v1.0/servers/1') req.method = 'PUT' @@ -2078,18 +2108,24 @@ class TestServerInstanceCreation(test.TestCase): FLAGS.allow_admin_api = self.allow_admin super(TestServerInstanceCreation, self).tearDown() - def _setup_mock_compute_api_for_personality(self): + def _setup_mock_compute_api(self): class MockComputeAPI(nova.compute.API): def __init__(self): self.injected_files = None + self.networks = None def create(self, *args, **kwargs): if 'injected_files' in kwargs: self.injected_files = kwargs['injected_files'] else: self.injected_files = None + + if 'requested_networks' in kwargs: + self.networks = kwargs['requested_networks'] + else: + self.networks = None return [{'id': '1234', 'display_name': 'fakeinstance', 'uuid': FAKE_UUID}] @@ -2120,6 +2156,18 @@ class TestServerInstanceCreation(test.TestCase): server['personality'] = personalities return {'server': server} + def _create_networks_request_dict(self, networks): + server = {} + server['name'] = 'new-server-test' + server['imageId'] = 1 + server['flavorId'] = 1 + if networks is not None: + network_list = [] + for id, fixed_ip in networks: + network_list.append({'id': id, 'fixed_ip': fixed_ip}) + server['networks'] = network_list + return {'server': server} + def _get_create_request_json(self, body_dict): req = webob.Request.blank('/v1.0/servers') req.headers['Content-Type'] = 'application/json' @@ -2128,7 +2176,7 @@ class TestServerInstanceCreation(test.TestCase): return req def _run_create_instance_with_mock_compute_api(self, request): - compute_api = self._setup_mock_compute_api_for_personality() + compute_api = self._setup_mock_compute_api() response = request.get_response(fakes.wsgi_app()) return compute_api, response @@ -2153,6 +2201,14 @@ class TestServerInstanceCreation(test.TestCase): item = (file['path'], file['contents']) body_parts.append('%s' % item) body_parts.append('') + if 'networks' in server: + networks = server['networks'] + body_parts.append('') + for network in networks: + item = (network['id'], network['fixed_ip']) + body_parts.append('' + % item) + body_parts.append('') body_parts.append('') return ''.join(body_parts) @@ -2178,6 +2234,20 @@ class TestServerInstanceCreation(test.TestCase): self._run_create_instance_with_mock_compute_api(request) return request, response, compute_api.injected_files + def _create_instance_with_networks_json(self, networks): + body_dict = self._create_networks_request_dict(networks) + request = self._get_create_request_json(body_dict) + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + return request, response, compute_api.networks + + def _create_instance_with_networks_xml(self, networks): + body_dict = self._create_networks_request_dict(networks) + request = self._get_create_request_xml(body_dict) + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + return request, response, compute_api.networks + def test_create_instance_with_no_personality(self): request, response, injected_files = \ self._create_instance_with_personality_json(personality=None) @@ -2312,6 +2382,132 @@ class TestServerInstanceCreation(test.TestCase): self.assertEquals(server.nodeName, 'server') self.assertEqual(16, len(server.getAttribute('adminPass'))) + def test_create_instance_with_no_networks(self): + request, response, networks = \ + self._create_instance_with_networks_json(networks=None) + self.assertEquals(response.status_int, 200) + self.assertEquals(networks, None) + + def test_create_instance_with_no_networks_xml(self): + request, response, networks = \ + self._create_instance_with_networks_xml(networks=None) + self.assertEquals(response.status_int, 200) + self.assertEquals(networks, None) + + def test_create_instance_with_one_network(self): + id = 1 + fixed_ip = '10.0.1.12' + networks = [(id, fixed_ip)] + request, response, networks = \ + self._create_instance_with_networks_json(networks) + self.assertEquals(response.status_int, 200) + self.assertEquals(networks, [(id, fixed_ip)]) + + def test_create_instance_with_one_network_xml(self): + id = 1 + fixed_ip = '10.0.1.12' + networks = [(id, fixed_ip)] + request, response, networks = \ + self._create_instance_with_networks_xml(networks) + self.assertEquals(response.status_int, 200) + self.assertEquals(networks, [(id, fixed_ip)]) + + def test_create_instance_with_two_networks(self): + networks = [(1, '10.0.1.12'), (2, '10.0.2.12')] + request, response, networks = \ + self._create_instance_with_networks_json(networks) + self.assertEquals(response.status_int, 200) + self.assertEquals(networks, [(1, '10.0.1.12'), (2, '10.0.2.12')]) + + def test_create_instance_with_two_networks_xml(self): + networks = [(1, '10.0.1.12'), (2, '10.0.2.12')] + request, response, networks = \ + self._create_instance_with_networks_xml(networks) + self.assertEquals(response.status_int, 200) + self.assertEquals(networks, [(1, '10.0.1.12'), (2, '10.0.2.12')]) + + def test_create_instance_with_duplicate_networks(self): + networks = [(1, '10.0.1.12'), (1, '10.0.2.12')] + request, response, networks = \ + self._create_instance_with_networks_json(networks) + self.assertEquals(response.status_int, 400) + self.assertEquals(networks, None) + + def test_create_instance_with_duplicate_networks_xml(self): + networks = [(1, '10.0.1.12'), (1, '10.0.2.12')] + request, response, networks = \ + self._create_instance_with_networks_xml(networks) + self.assertEquals(response.status_int, 400) + self.assertEquals(networks, None) + + def test_create_instance_with_network_no_id(self): + networks = [(1, '10.0.1.12')] + body_dict = self._create_networks_request_dict(networks) + del body_dict['server']['networks'][0]['id'] + request = self._get_create_request_json(body_dict) + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + self.assertEquals(response.status_int, 400) + self.assertEquals(compute_api.networks, None) + + def test_create_instance_with_network_no_id_xml(self): + networks = [(1, '10.0.1.12')] + body_dict = self._create_networks_request_dict(networks) + request = self._get_create_request_xml(body_dict) + request.body = request.body.replace(' id="1"', '') + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + self.assertEquals(response.status_int, 400) + self.assertEquals(compute_api.networks, None) + + def test_create_instance_with_network_invalid_id(self): + networks = [('asd123', '10.0.1.12')] + request, response, networks = \ + self._create_instance_with_networks_json(networks) + self.assertEquals(response.status_int, 400) + self.assertEquals(networks, None) + + def test_create_instance_with_network_invalid_id_xml(self): + networks = [('asd123', '10.0.1.12')] + request, response, networks = \ + self._create_instance_with_networks_xml(networks) + self.assertEquals(response.status_int, 400) + self.assertEquals(networks, None) + + def test_create_instance_with_network_empty_fixed_ip(self): + networks = [('1', '')] + request, response, networks = \ + self._create_instance_with_networks_json(networks) + self.assertEquals(response.status_int, 400) + self.assertEquals(networks, None) + + def test_create_instance_with_network_empty_fixed_ip_xml(self): + networks = [('1', '')] + request, response, networks = \ + self._create_instance_with_networks_xml(networks) + self.assertEquals(response.status_int, 400) + self.assertEquals(networks, None) + + def test_create_instance_with_network_no_fixed_ip(self): + networks = [(1, '10.0.1.12')] + body_dict = self._create_networks_request_dict(networks) + del body_dict['server']['networks'][0]['fixed_ip'] + request = self._get_create_request_json(body_dict) + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + self.assertEquals(response.status_int, 200) + self.assertEquals(compute_api.networks, [(1, None)]) + + def test_create_instance_with_network_no_fixed_ip_xml(self): + networks = [(1, '10.0.1.12')] + body_dict = self._create_networks_request_dict(networks) + request = self._get_create_request_xml(body_dict) + request.body = request.body.replace(' fixed_ip="10.0.1.12"', '') + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + self.assertEquals(response.status_int, 200) + self.assertEquals(compute_api.networks, [(1, None)]) + class TestGetKernelRamdiskFromImage(test.TestCase): """ -- cgit From 0b905cc45b940579c6fc7363ecf5657d10a7aeed Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Wed, 13 Jul 2011 12:07:05 -0700 Subject: added unit testcases for validating the requested networks --- nova/tests/test_network.py | 120 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index 30e1c0512..d6c363481 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -16,6 +16,7 @@ # under the License. from nova import db +from nova import exception from nova import flags from nova import log as logging from nova import test @@ -183,6 +184,65 @@ class FlatNetworkTestCase(test.TestCase): 'netmask': '255.255.255.0'}] self.assertDictListMatch(nw[1]['ips'], check) + def test_validate_networks(self): + self.mox.StubOutWithMock(db, 'network_get_requested_networks') + self.mox.StubOutWithMock(db, "fixed_ip_validate_by_network_address") + + requested_networks = [(1, "192.168.0.100")] + db.network_get_requested_networks(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks) + db.fixed_ip_validate_by_network_address(mox.IgnoreArg(), + mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(fixed_ips[0]) + + self.mox.ReplayAll() + self.network.validate_networks(None, requested_networks) + + def test_validate_networks_none_requested_networks(self): + self.network.validate_networks(None, None) + + def test_validate_networks_empty_requested_networks(self): + requested_networks = [] + self.mox.StubOutWithMock(db, 'network_get_requested_networks') + db.network_get_requested_networks(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks) + self.mox.ReplayAll() + + self.network.validate_networks(None, requested_networks) + + def test_validate_networks_invalid_fixed_ip(self): + self.mox.StubOutWithMock(db, 'network_get_requested_networks') + requested_networks = [(1, "192.168.0.100.1")] + db.network_get_requested_networks(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks) + self.mox.ReplayAll() + + self.assertRaises(exception.FixedIpInvalid, + self.network.validate_networks, None, + requested_networks) + + def test_validate_networks_empty_fixed_ip(self): + self.mox.StubOutWithMock(db, 'network_get_requested_networks') + + requested_networks = [(1, "")] + db.network_get_requested_networks(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks) + self.mox.ReplayAll() + + self.assertRaises(exception.FixedIpInvalid, + self.network.validate_networks, + None, requested_networks) + + def test_validate_networks_none_fixed_ip(self): + self.mox.StubOutWithMock(db, 'network_get_requested_networks') + + requested_networks = [(1, None)] + db.network_get_requested_networks(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks) + self.mox.ReplayAll() + + self.network.validate_networks(None, requested_networks) + class VlanNetworkTestCase(test.TestCase): def setUp(self): @@ -239,3 +299,63 @@ class VlanNetworkTestCase(test.TestCase): self.assertRaises(ValueError, self.network.create_networks, None, num_networks=100, vlan_start=1, cidr='192.168.0.1/24', network_size=100) + + def test_validate_networks(self): + self.mox.StubOutWithMock(db, 'project_get_requested_networks') + self.mox.StubOutWithMock(db, "fixed_ip_validate_by_network_address") + + requested_networks = [(1, "192.168.0.100")] + db.project_get_requested_networks(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks) + db.fixed_ip_validate_by_network_address(mox.IgnoreArg(), + mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(fixed_ips[0]) + self.mox.ReplayAll() + + self.network.validate_networks(None, requested_networks) + + def test_validate_networks_none_requested_networks(self): + self.network.validate_networks(None, None) + + def test_validate_networks_empty_requested_networks(self): + requested_networks = [] + self.mox.StubOutWithMock(db, 'project_get_requested_networks') + db.project_get_requested_networks(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks) + self.mox.ReplayAll() + + self.network.validate_networks(None, requested_networks) + + def test_validate_networks_invalid_fixed_ip(self): + self.mox.StubOutWithMock(db, 'project_get_requested_networks') + + requested_networks = [(1, "192.168.0.100.1")] + db.project_get_requested_networks(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks) + self.mox.ReplayAll() + + self.assertRaises(exception.FixedIpInvalid, + self.network.validate_networks, + None, requested_networks) + + def test_validate_networks_empty_fixed_ip(self): + self.mox.StubOutWithMock(db, 'project_get_requested_networks') + + requested_networks = [(1, "")] + db.project_get_requested_networks(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks) + self.mox.ReplayAll() + + self.assertRaises(exception.FixedIpInvalid, + self.network.validate_networks, + None, requested_networks) + + def test_validate_networks_none_fixed_ip(self): + self.mox.StubOutWithMock(db, 'project_get_requested_networks') + + requested_networks = [(1, None)] + db.project_get_requested_networks(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks) + self.mox.ReplayAll() + + self.network.validate_networks(None, requested_networks) -- cgit From 2ecbdd46d48bafbeb451875ba6e7f67276d83602 Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Wed, 13 Jul 2011 13:57:50 -0700 Subject: Minor fixes --- nova/api/openstack/create_instance_helper.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 5eef0ae00..0337dd5bb 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -162,7 +162,8 @@ class CreateInstanceHelper(object): msg = _("Can not find requested image") raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) except rpc.RemoteError as err: - msg = _("%(err.exc_type)s:%(err.value)s") + msg = "%(err_type)s: %(err_msg)s" % \ + {'err_type': err.exc_type, 'err_msg': err.value} raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) # Let the caller deal with unhandled exceptions. -- cgit From 0655f97b2cce1e28485ddb4c37a854a65cbbc276 Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Thu, 14 Jul 2011 15:53:16 -0700 Subject: added integrated unit testcases and minor fixes --- nova/api/openstack/create_instance_helper.py | 3 - nova/network/manager.py | 6 +- nova/tests/__init__.py | 3 +- nova/tests/api/openstack/test_servers.py | 7 + nova/tests/integrated/test_servers.py | 218 +++++++++++++++++++++++++++ nova/tests/test_network.py | 8 - 6 files changed, 231 insertions(+), 14 deletions(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 0337dd5bb..32b1b2f7c 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -329,9 +329,6 @@ class CreateInstanceHelper(object): expl = _("Bad networks format: network id should " "be integer (%s)") % network_id raise faults.Fault(exc.HTTPBadRequest(explanation=expl)) - except TypeError: - expl = _('Bad networks format') - raise faults.Fault(exc.HTTPBadRequest(explanation=expl)) return networks diff --git a/nova/network/manager.py b/nova/network/manager.py index e665bb1fc..b2b9335a9 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -614,6 +614,7 @@ class NetworkManager(manager.SchedulerDependentManager): net['gateway'] = str(project_net[1]) net['broadcast'] = str(project_net.broadcast) net['dhcp_start'] = str(project_net[2]) + net['project_id'] = kwargs['project_id'] if num_networks > 1: net['label'] = '%s_%d' % (label, index) else: @@ -705,7 +706,7 @@ class NetworkManager(manager.SchedulerDependentManager): """check if the networks exists and host is set to each network. """ - if networks is None: + if networks is None or len(networks) == 0: return result = self.db.network_get_requested_networks(context, networks) @@ -965,10 +966,11 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): """check if the networks exists and host is set to each network. """ - if networks is None: + if networks is None or len(networks) == 0: return result = self.db.project_get_requested_networks(context, networks) + for network_id, fixed_ip in networks: # check if the fixed IP address is valid and # it actually belongs to the network diff --git a/nova/tests/__init__.py b/nova/tests/__init__.py index e4ed75d37..44ce2e635 100644 --- a/nova/tests/__init__.py +++ b/nova/tests/__init__.py @@ -66,7 +66,8 @@ def setup(): bridge=FLAGS.flat_network_bridge, bridge_interface=bridge_interface, vpn_start=FLAGS.vpn_start, - vlan_start=FLAGS.vlan_start) + vlan_start=FLAGS.vlan_start, + project_id="openstack") for net in db.network_get_all(ctxt): network.set_network_host(ctxt, net['id']) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 52af4c17a..702dcb7a8 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -2516,6 +2516,13 @@ class TestServerInstanceCreation(test.TestCase): self.assertEquals(response.status_int, 400) self.assertEquals(networks, None) + def test_create_instance_with_network_non_string_fixed_ip(self): + networks = [('1', 12345)] + request, response, networks = \ + self._create_instance_with_networks_json(networks) + self.assertEquals(response.status_int, 400) + self.assertEquals(networks, None) + def test_create_instance_with_network_empty_fixed_ip_xml(self): networks = [('1', '')] request, response, networks = \ diff --git a/nova/tests/integrated/test_servers.py b/nova/tests/integrated/test_servers.py index fcb517cf5..08522f720 100644 --- a/nova/tests/integrated/test_servers.py +++ b/nova/tests/integrated/test_servers.py @@ -179,6 +179,224 @@ class ServersTest(integrated_helpers._IntegratedTestBase): # Cleanup self._delete_server(created_server_id) + def test_create_server_with_one_networks(self): + """Creates a server with networks.""" + + # Build the server data gradually, checking errors along the way + server = self._build_minimal_create_server_request() + + network_list = [{'id': 1, 'fixed_ip': '10.0.0.3'}] + server['networks'] = network_list + + post = {'server': server} + created_server = self.api.post_server(post) + LOG.debug("created_server: %s" % created_server) + self.assertTrue(created_server['id']) + created_server_id = created_server['id'] + + # Check it's there + found_server = self.api.get_server(created_server_id) + self.assertEqual(created_server_id, found_server['id']) + + # Cleanup + self._delete_server(created_server_id) + + def test_create_server_with_two_networks(self): + """Creates a server with networks.""" + + # Build the server data gradually, checking errors along the way + server = self._build_minimal_create_server_request() + + network_list = [{'id': 1, 'fixed_ip': '10.0.0.3'}, + {'id': 2, 'fixed_ip': '10.0.0.12'}] + + server['networks'] = network_list + + post = {'server': server} + created_server = self.api.post_server(post) + LOG.debug("created_server: %s" % created_server) + self.assertTrue(created_server['id']) + created_server_id = created_server['id'] + + # Check it's there + found_server = self.api.get_server(created_server_id) + self.assertEqual(created_server_id, found_server['id']) + + # Cleanup + self._delete_server(created_server_id) + + def test_create_server_with_empty_networks(self): + """Creates a server with networks.""" + + # Build the server data gradually, checking errors along the way + server = self._build_minimal_create_server_request() + + network_list = [] + server['networks'] = network_list + + post = {'server': server} + created_server = self.api.post_server(post) + LOG.debug("created_server: %s" % created_server) + self.assertTrue(created_server['id']) + created_server_id = created_server['id'] + + # Check it's there + found_server = self.api.get_server(created_server_id) + self.assertEqual(created_server_id, found_server['id']) + + # Cleanup + self._delete_server(created_server_id) + + def test_create_server_with_networks_invalid_id(self): + """Creates a server with networks.""" + + # Build the server data gradually, checking errors along the way + server = self._build_minimal_create_server_request() + + network_list = [{'id': "invalid", 'fixed_ip': '10.0.0.3'}] + server['networks'] = network_list + + post = {'server': server} + self.assertRaises(client.OpenStackApiException, + self.api.post_server, post) + + def test_create_server_with_networks_no_id(self): + """Creates a server with networks.""" + + # Build the server data gradually, checking errors along the way + server = self._build_minimal_create_server_request() + + network_list = [{'fixed_ip': '10.0.0.3'}] + server['networks'] = network_list + + post = {'server': server} + self.assertRaises(client.OpenStackApiException, + self.api.post_server, post) + + def test_create_server_with_networks_non_exists_id(self): + """Creates a server with networks.""" + + # Build the server data gradually, checking errors along the way + server = self._build_minimal_create_server_request() + + network_list = [{'id': 3, 'fixed_ip': '10.0.0.3'}] + server['networks'] = network_list + + post = {'server': server} + self.assertRaises(client.OpenStackApiException, + self.api.post_server, post) + + def test_create_server_with_networks_not_found_for_project(self): + """Creates a server with networks.""" + + # Build the server data gradually, checking errors along the way + server = self._build_minimal_create_server_request() + + network_list = [{'id': 1, 'fixed_ip': '10.0.0.3'}, + {'id': 3, 'fixed_ip': '10.0.0.12'}] + server['networks'] = network_list + + post = {'server': server} + self.assertRaises(client.OpenStackApiException, + self.api.post_server, post) + + def test_create_server_with_networks_invalid_fixed_ip(self): + """Creates a server with networks.""" + + # Build the server data gradually, checking errors along the way + server = self._build_minimal_create_server_request() + + network_list = [{'id': 1, 'fixed_ip': '10.0.0.3.0'}] + server['networks'] = network_list + + post = {'server': server} + self.assertRaises(client.OpenStackApiException, + self.api.post_server, post) + + def test_create_server_with_networks_non_existing_fixed_ip(self): + """Creates a server with metadata.""" + + # Build the server data gradually, checking errors along the way + server = self._build_minimal_create_server_request() + + network_list = [{'id': 1, 'fixed_ip': '10.100.0.3'}] + server['networks'] = network_list + + post = {'server': server} + self.assertRaises(client.OpenStackApiException, + self.api.post_server, post) + + + def test_create_server_with_networks_empty_fixed_ip(self): + """Creates a server with networks.""" + + # Build the server data gradually, checking errors along the way + server = self._build_minimal_create_server_request() + + network_list = [{'id': 1, 'fixed_ip': ''}] + server['networks'] = network_list + + post = {'server': server} + self.assertRaises(client.OpenStackApiException, + self.api.post_server, post) + + def test_create_server_with_networks_non_string_fixed_ip(self): + """Creates a server with networks.""" + + # Build the server data gradually, checking errors along the way + server = self._build_minimal_create_server_request() + + network_list = [{'id': 1, 'fixed_ip': 12}] + server['networks'] = network_list + + post = {'server': server} + self.assertRaises(client.OpenStackApiException, + self.api.post_server, post) + + def test_create_server_with_networks_none_fixed_ip(self): + """Creates a server with networks.""" + + # Build the server data gradually, checking errors along the way + server = self._build_minimal_create_server_request() + + network_list = [{'id': 1, 'fixed_ip': None}] + server['networks'] = network_list + + post = {'server': server} + created_server = self.api.post_server(post) + LOG.debug("created_server: %s" % created_server) + self.assertTrue(created_server['id']) + created_server_id = created_server['id'] + + # Check it's there + found_server = self.api.get_server(created_server_id) + self.assertEqual(created_server_id, found_server['id']) + + # Cleanup + self._delete_server(created_server_id) + + def test_create_server_with_networks_no_fixed_ip(self): + """Creates a server with networks.""" + + # Build the server data gradually, checking errors along the way + server = self._build_minimal_create_server_request() + + network_list = [{'id': 1}] + server['networks'] = network_list + + post = {'server': server} + created_server = self.api.post_server(post) + LOG.debug("created_server: %s" % created_server) + self.assertTrue(created_server['id']) + created_server_id = created_server['id'] + + # Check it's there + found_server = self.api.get_server(created_server_id) + self.assertEqual(created_server_id, found_server['id']) + + # Cleanup + self._delete_server(created_server_id) + def test_create_and_rebuild_server(self): """Rebuild a server.""" diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index 63317b05a..fd4408831 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -203,9 +203,6 @@ class FlatNetworkTestCase(test.TestCase): def test_validate_networks_empty_requested_networks(self): requested_networks = [] - self.mox.StubOutWithMock(db, 'network_get_requested_networks') - db.network_get_requested_networks(mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn(networks) self.mox.ReplayAll() self.network.validate_networks(None, requested_networks) @@ -319,11 +316,6 @@ class VlanNetworkTestCase(test.TestCase): def test_validate_networks_empty_requested_networks(self): requested_networks = [] - self.mox.StubOutWithMock(db, 'project_get_requested_networks') - db.project_get_requested_networks(mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn(networks) - self.mox.ReplayAll() - self.network.validate_networks(None, requested_networks) def test_validate_networks_invalid_fixed_ip(self): -- cgit From b4fba58f2785936f68a712d1cdd1d5c34f6a7c22 Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Thu, 14 Jul 2011 16:40:05 -0700 Subject: mistakenly commited this code into my branch, reverting it to original from trunk --- nova/rpc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/rpc.py b/nova/rpc.py index 055f8220e..e2771ca88 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -279,7 +279,7 @@ class FanoutAdapterConsumer(AdapterConsumer): # them when done, so they're not left around on restart. # Also, we're the only one that should be consuming. exclusive # implies auto_delete, so we'll just set that.. - self.exclusive = False + self.exclusive = True LOG.info(_('Created "%(exchange)s" fanout exchange ' 'with "%(key)s" routing key'), dict(exchange=self.exchange, key=self.routing_key)) -- cgit From 6cbd1d860d6a3fe96417391c21fb79b1750ecdcf Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Tue, 19 Jul 2011 15:35:54 -0700 Subject: Added a new extension instead of directly making changes to OS V1.1. API --- nova/api/openstack/contrib/createserverext.py | 234 ++++++++++++++++++++++++++ nova/api/openstack/create_instance_helper.py | 51 +----- nova/api/openstack/extensions.py | 9 +- nova/api/openstack/servers.py | 35 ++-- 4 files changed, 262 insertions(+), 67 deletions(-) create mode 100644 nova/api/openstack/contrib/createserverext.py diff --git a/nova/api/openstack/contrib/createserverext.py b/nova/api/openstack/contrib/createserverext.py new file mode 100644 index 000000000..e8fe9afad --- /dev/null +++ b/nova/api/openstack/contrib/createserverext.py @@ -0,0 +1,234 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# 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 webob import exc + +from nova import exception +import nova.image +from nova import network +from nova import quota +from nova import rpc + +from nova.api.openstack import create_instance_helper as helper +from nova.api.openstack import extensions +from nova.api.openstack import faults +from nova.compute import instance_types +from nova.api.openstack import servers +from nova.api.openstack import wsgi +from nova.auth import manager as auth_manager + + +class CreateInstanceHelperEx(helper.CreateInstanceHelper): + def __init__(self, controller): + super(CreateInstanceHelperEx, self).__init__(controller) + + def create_instance(self, req, body, create_method): + """Creates a new server for the given user as per + the network information if it is provided + """ + if not body: + raise faults.Fault(exc.HTTPUnprocessableEntity()) + + context = req.environ['nova.context'] + + password = self.controller._get_server_admin_password(body['server']) + + key_name = None + key_data = None + key_pairs = auth_manager.AuthManager.get_key_pairs(context) + if key_pairs: + key_pair = key_pairs[0] + key_name = key_pair['name'] + key_data = key_pair['public_key'] + + image_href = self.controller._image_ref_from_req_data(body) + try: + image_service, image_id = nova.image.get_image_service(image_href) + kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image( + req, image_id) + images = set([str(x['id']) for x in image_service.index(context)]) + assert str(image_id) in images + except Exception, e: + msg = _("Cannot find requested image %(image_href)s: %(e)s" % + locals()) + raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + + personality = body['server'].get('personality') + + injected_files = [] + if personality: + injected_files = self._get_injected_files(personality) + + requested_networks = body['server'].get('networks') + + if requested_networks is not None: + requested_networks = self._get_requested_networks( + requested_networks) + + flavor_id = self.controller._flavor_id_from_req_data(body) + + if not 'name' in body['server']: + msg = _("Server name is not defined") + raise exc.HTTPBadRequest(explanation=msg) + + zone_blob = body['server'].get('blob') + name = body['server']['name'] + self._validate_server_name(name) + name = name.strip() + + reservation_id = body['server'].get('reservation_id') + min_count = body['server'].get('min_count') + max_count = body['server'].get('max_count') + # min_count and max_count are optional. If they exist, they come + # in as strings. We want to default 'min_count' to 1, and default + # 'max_count' to be 'min_count'. + min_count = int(min_count) if min_count else 1 + max_count = int(max_count) if max_count else min_count + if min_count > max_count: + min_count = max_count + + try: + inst_type = \ + instance_types.get_instance_type_by_flavor_id(flavor_id) + extra_values = { + 'instance_type': inst_type, + 'image_ref': image_href, + 'password': password} + + return (extra_values, + create_method(context, + inst_type, + image_id, + kernel_id=kernel_id, + ramdisk_id=ramdisk_id, + display_name=name, + display_description=name, + key_name=key_name, + key_data=key_data, + metadata=body['server'].get('metadata', {}), + injected_files=injected_files, + admin_password=password, + zone_blob=zone_blob, + reservation_id=reservation_id, + min_count=min_count, + max_count=max_count, + requested_networks=requested_networks)) + except quota.QuotaError as error: + self._handle_quota_error(error) + except exception.ImageNotFound as error: + msg = _("Can not find requested image") + raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + except rpc.RemoteError as err: + msg = "%(err_type)s: %(err_msg)s" % \ + {'err_type': err.exc_type, 'err_msg': err.value} + raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + + # Let the caller deal with unhandled exceptions. + + def _get_requested_networks(self, requested_networks): + """ + Create a list of requested networks from the networks attribute + """ + networks = [] + for network in requested_networks: + try: + network_id = network['id'] + network_id = int(network_id) + #fixed IP address is optional + #if the fixed IP address is not provided then + #it will use one of the available IP address from the network + fixed_ip = network.get('fixed_ip', None) + if fixed_ip is not None: + self._validate_fixed_ip(fixed_ip) + # check if the network id is already present in the list, + # we don't want duplicate networks to be passed + # at the boot time + for id, ip in networks: + if id == network_id: + expl = _("Duplicate networks (%s) are not allowed")\ + % network_id + raise faults.Fault(exc.HTTPBadRequest( + explanation=expl)) + + networks.append((network_id, fixed_ip)) + except KeyError as key: + expl = _('Bad network format: missing %s') % key + raise faults.Fault(exc.HTTPBadRequest(explanation=expl)) + except ValueError: + expl = _("Bad networks format: network id should " + "be integer (%s)") % network_id + raise faults.Fault(exc.HTTPBadRequest(explanation=expl)) + except TypeError: + expl = _('Bad networks format') + raise exc.HTTPBadRequest(explanation=expl) + + return networks + + +class CreateServerExtController(servers.ControllerV11): + """This is the controller for the extended version + of the create server OS V1.1 + """ + def __init__(self): + super(CreateServerExtController, self).__init__() + self.helper = CreateInstanceHelperEx(self) + + +class Createserverext(extensions.ExtensionDescriptor): + """The servers create ext + + Exposes addFixedIp and removeFixedIp actions on servers. + + """ + def get_name(self): + return "Createserverext" + + def get_alias(self): + return "os-servers-create-ext" + + def get_description(self): + return "Extended support to the Create Server v1.1 API" + + def get_namespace(self): + return "http://docs.openstack.org/ext/serverscreateext/api/v1.1" + + def get_updated(self): + return "2011-07-19T00:00:00+00:00" + + def get_resources(self): + resources = [] + + headers_serializer = servers.HeadersSerializer() + metadata = servers._get_metadata() + body_serializers = { + 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, + xmlns=wsgi.XMLNS_V11), + } + + body_deserializers = { + 'application/xml': helper.ServerXMLDeserializer(), + } + + serializer = wsgi.ResponseSerializer(body_serializers, + headers_serializer) + deserializer = wsgi.RequestDeserializer(body_deserializers) + + res = extensions.ResourceExtension('os-servers-create-ext', + controller=CreateServerExtController(), + deserializer=deserializer, + serializer=serializer) + resources.append(res) + + return resources diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 32b1b2f7c..8579c45df 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -25,7 +25,6 @@ from nova import flags from nova import log as logging import nova.image from nova import quota -from nova import rpc from nova import utils from nova.compute import instance_types @@ -102,12 +101,6 @@ class CreateInstanceHelper(object): if personality: injected_files = self._get_injected_files(personality) - requested_networks = body['server'].get('networks') - - if requested_networks is not None: - requested_networks = self._get_requested_networks( - requested_networks) - flavor_id = self.controller._flavor_id_from_req_data(body) if not 'name' in body['server']: @@ -154,17 +147,12 @@ class CreateInstanceHelper(object): zone_blob=zone_blob, reservation_id=reservation_id, min_count=min_count, - max_count=max_count, - requested_networks=requested_networks)) + max_count=max_count)) except quota.QuotaError as error: self._handle_quota_error(error) except exception.ImageNotFound as error: msg = _("Can not find requested image") raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) - except rpc.RemoteError as err: - msg = "%(err_type)s: %(err_msg)s" % \ - {'err_type': err.exc_type, 'err_msg': err.value} - raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) # Let the caller deal with unhandled exceptions. @@ -296,42 +284,6 @@ class CreateInstanceHelper(object): raise exc.HTTPBadRequest(explanation=msg) return password - def _get_requested_networks(self, requested_networks): - """ - Create a list of requested networks from the networks attribute - """ - networks = [] - for network in requested_networks: - try: - network_id = network['id'] - network_id = int(network_id) - #fixed IP address is optional - #if the fixed IP address is not provided then - #it will use one of the available IP address from the network - fixed_ip = network.get('fixed_ip', None) - if fixed_ip is not None: - self._validate_fixed_ip(fixed_ip) - # check if the network id is already present in the list, - # we don't want duplicate networks to be passed - # at the boot time - for id, ip in networks: - if id == network_id: - expl = _("Duplicate networks (%s) are not allowed")\ - % network_id - raise faults.Fault(exc.HTTPBadRequest( - explanation=expl)) - - networks.append((network_id, fixed_ip)) - except KeyError as key: - expl = _('Bad network format: missing %s') % key - raise faults.Fault(exc.HTTPBadRequest(explanation=expl)) - except ValueError: - expl = _("Bad networks format: network id should " - "be integer (%s)") % network_id - raise faults.Fault(exc.HTTPBadRequest(explanation=expl)) - - return networks - class ServerXMLDeserializer(wsgi.XMLDeserializer): """ @@ -411,7 +363,6 @@ class ServerXMLDeserializer(wsgi.XMLDeserializer): def _find_first_child_named(self, parent, name): """Search a nodes children for the first child with a given name""" for node in parent.childNodes: - LOG.debug(node.nodeName) if node.nodeName == name: return node return None diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index da06ecd15..dec66d8a4 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -263,7 +263,9 @@ class ExtensionMiddleware(base_wsgi.Middleware): LOG.debug(_('Extended resource: %s'), resource.collection) mapper.resource(resource.collection, resource.collection, - controller=wsgi.Resource(resource.controller), + controller=wsgi.Resource(resource.controller, + resource.deserializer, + resource.serializer), collection=resource.collection_actions, member=resource.member_actions, parent_resource=resource.parent) @@ -456,9 +458,12 @@ class ResourceExtension(object): """Add top level resources to the OpenStack API in nova.""" def __init__(self, collection, controller, parent=None, - collection_actions={}, member_actions={}): + collection_actions={}, member_actions={}, + deserializer=None, serializer=None): self.collection = collection self.controller = controller self.parent = parent self.collection_actions = collection_actions self.member_actions = member_actions + self.deserializer = deserializer + self.serializer = serializer diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 93f8e832c..6a28f7bf1 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -618,21 +618,7 @@ def create_resource(version='1.0'): '1.1': ControllerV11, }[version]() - metadata = { - "attributes": { - "server": ["id", "imageId", "name", "flavorId", "hostId", - "status", "progress", "adminPass", "flavorRef", - "imageRef"], - "link": ["rel", "type", "href"], - }, - "dict_collections": { - "metadata": {"item_name": "meta", "item_key": "key"}, - }, - "list_collections": { - "public": {"item_name": "ip", "item_key": "addr"}, - "private": {"item_name": "ip", "item_key": "addr"}, - }, - } + metadata = _get_metadata() xmlns = { '1.0': wsgi.XMLNS_V10, @@ -654,3 +640,22 @@ def create_resource(version='1.0'): deserializer = wsgi.RequestDeserializer(body_deserializers) return wsgi.Resource(controller, deserializer, serializer) + + +def _get_metadata(): + metadata = { + "attributes": { + "server": ["id", "imageId", "name", "flavorId", "hostId", + "status", "progress", "adminPass", "flavorRef", + "imageRef"], + "link": ["rel", "type", "href"], + }, + "dict_collections": { + "metadata": {"item_name": "meta", "item_key": "key"}, + }, + "list_collections": { + "public": {"item_name": "ip", "item_key": "addr"}, + "private": {"item_name": "ip", "item_key": "addr"}, + }, + } + return metadata -- cgit From 6b47f87c9e22fa09cedc3e48b7c8dcf52b5d016a Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Tue, 19 Jul 2011 17:35:44 -0700 Subject: Fixed broken unit testcases after adding extension and minor code refactoring --- nova/api/openstack/contrib/createserverext.py | 11 +- nova/api/openstack/create_instance_helper.py | 9 - .../api/openstack/contrib/test_createserverext.py | 637 +++++++++++++++++++++ nova/tests/api/openstack/test_servers.py | 151 ----- nova/tests/integrated/test_servers.py | 216 ------- 5 files changed, 647 insertions(+), 377 deletions(-) create mode 100644 nova/tests/api/openstack/contrib/test_createserverext.py diff --git a/nova/api/openstack/contrib/createserverext.py b/nova/api/openstack/contrib/createserverext.py index e8fe9afad..78dba425a 100644 --- a/nova/api/openstack/contrib/createserverext.py +++ b/nova/api/openstack/contrib/createserverext.py @@ -137,6 +137,15 @@ class CreateInstanceHelperEx(helper.CreateInstanceHelper): # Let the caller deal with unhandled exceptions. + def _validate_fixed_ip(self, value): + if not isinstance(value, basestring): + msg = _("Fixed IP is not a string or unicode") + raise exc.HTTPBadRequest(explanation=msg) + + if value.strip() == '': + msg = _("Fixed IP is an empty string") + raise exc.HTTPBadRequest(explanation=msg) + def _get_requested_networks(self, requested_networks): """ Create a list of requested networks from the networks attribute @@ -202,7 +211,7 @@ class Createserverext(extensions.ExtensionDescriptor): return "Extended support to the Create Server v1.1 API" def get_namespace(self): - return "http://docs.openstack.org/ext/serverscreateext/api/v1.1" + return "http://docs.openstack.org/ext/createserverext/api/v1.1" def get_updated(self): return "2011-07-19T00:00:00+00:00" diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 8579c45df..fba0cb8ba 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -193,15 +193,6 @@ class CreateInstanceHelper(object): msg = _("Server name is an empty string") raise exc.HTTPBadRequest(explanation=msg) - def _validate_fixed_ip(self, value): - if not isinstance(value, basestring): - msg = _("Fixed IP is not a string or unicode") - raise exc.HTTPBadRequest(explanation=msg) - - if value.strip() == '': - msg = _("Fixed IP is an empty string") - raise exc.HTTPBadRequest(explanation=msg) - def _get_kernel_ramdisk_from_image(self, req, image_id): """Fetch an image from the ImageService, then if present, return the associated kernel and ramdisk image IDs. diff --git a/nova/tests/api/openstack/contrib/test_createserverext.py b/nova/tests/api/openstack/contrib/test_createserverext.py new file mode 100644 index 000000000..f2aa136e2 --- /dev/null +++ b/nova/tests/api/openstack/contrib/test_createserverext.py @@ -0,0 +1,637 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010-2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import base64 +import json +import unittest +from xml.dom import minidom + +import stubout +import webob + +from nova import exception +from nova import flags +from nova import test +from nova import utils +import nova.api.openstack +from nova.api.openstack import servers +from nova.api.openstack.contrib import createserverext +import nova.compute.api + +import nova.scheduler.api +import nova.image.fake +import nova.rpc +from nova.tests.api.openstack import fakes + +FLAGS = flags.FLAGS +FLAGS.verbose = True + +FAKE_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' + + +def fake_gen_uuid(): + return FAKE_UUID + + +def return_server_by_id(context, id): + return stub_instance(id) + + +def return_server_by_uuid(context, uuid): + id = 1 + return stub_instance(id, uuid=uuid) + + +def return_virtual_interface_by_instance(interfaces): + def _return_virtual_interface_by_instance(context, instance_id): + return interfaces + return _return_virtual_interface_by_instance + + +def return_virtual_interface_instance_nonexistant(interfaces): + def _return_virtual_interface_by_instance(context, instance_id): + raise exception.InstanceNotFound(instance_id=instance_id) + return _return_virtual_interface_by_instance + + +def return_server_with_addresses(private, public): + def _return_server(context, id): + return stub_instance(id, private_address=private, + public_addresses=public) + return _return_server + + +def return_server_with_interfaces(interfaces): + def _return_server(context, id): + return stub_instance(id, interfaces=interfaces) + return _return_server + + +def return_server_with_power_state(power_state): + def _return_server(context, id): + return stub_instance(id, power_state=power_state) + return _return_server + + +def return_servers(context, user_id=1): + return [stub_instance(i, user_id) for i in xrange(5)] + + +def return_servers_by_reservation(context, reservation_id=""): + return [stub_instance(i, reservation_id) for i in xrange(5)] + + +def return_servers_by_reservation_empty(context, reservation_id=""): + return [] + + +def return_servers_from_child_zones_empty(*args, **kwargs): + return [] + + +def return_servers_from_child_zones(*args, **kwargs): + class Server(object): + pass + + zones = [] + for zone in xrange(3): + servers = [] + for server_id in xrange(5): + server = Server() + server._info = stub_instance(server_id, reservation_id="child") + servers.append(server) + + zones.append(("Zone%d" % zone, servers)) + return zones + + +def return_security_group(context, instance_id, security_group_id): + pass + + +def instance_update(context, instance_id, kwargs): + return stub_instance(instance_id) + + +def instance_addresses(context, instance_id): + return None + + +def stub_instance(id, user_id=1, private_address=None, public_addresses=None, + host=None, power_state=0, reservation_id="", + uuid=FAKE_UUID, interfaces=None): + metadata = [] + metadata.append(InstanceMetadata(key='seq', value=id)) + + if interfaces is None: + interfaces = [] + + inst_type = instance_types.get_instance_type_by_flavor_id(1) + + if public_addresses is None: + public_addresses = list() + + if host is not None: + host = str(host) + + # ReservationID isn't sent back, hack it in there. + server_name = "server%s" % id + if reservation_id != "": + server_name = "reservation_%s" % (reservation_id, ) + + instance = { + "id": int(id), + "admin_pass": "", + "user_id": user_id, + "project_id": "", + "image_ref": "10", + "kernel_id": "", + "ramdisk_id": "", + "launch_index": 0, + "key_name": "", + "key_data": "", + "state": power_state, + "state_description": "", + "memory_mb": 0, + "vcpus": 0, + "local_gb": 0, + "hostname": "", + "host": host, + "instance_type": dict(inst_type), + "user_data": "", + "reservation_id": reservation_id, + "mac_address": "", + "scheduled_at": utils.utcnow(), + "launched_at": utils.utcnow(), + "terminated_at": utils.utcnow(), + "availability_zone": "", + "display_name": server_name, + "display_description": "", + "locked": False, + "metadata": metadata, + "uuid": uuid, + "virtual_interfaces": interfaces} + + instance["fixed_ips"] = { + "address": private_address, + "floating_ips": [{"address":ip} for ip in public_addresses]} + + return instance + + +def fake_compute_api(cls, req, id): + return True + + +def find_host(self, context, instance_id): + return "nova" + + +class MockSetAdminPassword(object): + def __init__(self): + self.instance_id = None + self.password = None + + def __call__(self, context, instance_id, password): + self.instance_id = instance_id + self.password = password + + +class CreateserverextTest(test.TestCase): + + def setUp(self): + super(CreateserverextTest, self).setUp() + self.controller = createserverext.CreateServerExtController() + self.stubs = stubout.StubOutForTesting() + fakes.FakeAuthManager.auth_data = {} + fakes.FakeAuthDatabase.data = {} + fakes.stub_out_auth(self.stubs) + fakes.stub_out_image_service(self.stubs) + fakes.stub_out_key_pair_funcs(self.stubs) + self.allow_admin = FLAGS.allow_admin_api + + def tearDown(self): + self.stubs.UnsetAll() + FLAGS.allow_admin_api = self.allow_admin + super(CreateserverextTest, self).tearDown() + + def _setup_mock_compute_api(self): + + class MockComputeAPI(nova.compute.API): + + def __init__(self): + self.injected_files = None + self.networks = None + + def create(self, *args, **kwargs): + if 'injected_files' in kwargs: + self.injected_files = kwargs['injected_files'] + else: + self.injected_files = None + + if 'requested_networks' in kwargs: + self.networks = kwargs['requested_networks'] + else: + self.networks = None + return [{'id': '1234', 'display_name': 'fakeinstance', + 'uuid': FAKE_UUID}] + + def set_admin_password(self, *args, **kwargs): + pass + + def make_stub_method(canned_return): + def stub_method(*args, **kwargs): + return canned_return + return stub_method + + compute_api = MockComputeAPI() + self.stubs.Set(nova.compute, 'API', make_stub_method(compute_api)) + self.stubs.Set( + createserverext.CreateInstanceHelperEx, + '_get_kernel_ramdisk_from_image', make_stub_method((1, 1))) + return compute_api + + def _create_personality_request_dict(self, personality_files): + server = {} + server['name'] = 'new-server-test' + server['imageRef'] = 1 + server['flavorRef'] = 1 + if personality_files is not None: + personalities = [] + for path, contents in personality_files: + personalities.append({'path': path, 'contents': contents}) + server['personality'] = personalities + return {'server': server} + + def _create_networks_request_dict(self, networks): + server = {} + server['name'] = 'new-server-test' + server['imageRef'] = 1 + server['flavorRef'] = 1 + if networks is not None: + network_list = [] + for id, fixed_ip in networks: + network_list.append({'id': id, 'fixed_ip': fixed_ip}) + server['networks'] = network_list + return {'server': server} + + def _get_create_request_json(self, body_dict): + req = webob.Request.blank('/v1.1/os-servers-create-ext') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + req.body = json.dumps(body_dict) + return req + + def _run_create_instance_with_mock_compute_api(self, request): + compute_api = self._setup_mock_compute_api() + response = request.get_response(fakes.wsgi_app()) + return compute_api, response + + def _format_xml_request_body(self, body_dict): + server = body_dict['server'] + body_parts = [] + body_parts.extend([ + '', + '' % ( + server['name'], server['imageRef'], server['flavorRef'])]) + if 'metadata' in server: + metadata = server['metadata'] + body_parts.append('') + for item in metadata.iteritems(): + body_parts.append('%s' % item) + body_parts.append('') + if 'personality' in server: + personalities = server['personality'] + body_parts.append('') + for file in personalities: + item = (file['path'], file['contents']) + body_parts.append('%s' % item) + body_parts.append('') + if 'networks' in server: + networks = server['networks'] + body_parts.append('') + for network in networks: + item = (network['id'], network['fixed_ip']) + body_parts.append('' + % item) + body_parts.append('') + body_parts.append('') + return ''.join(body_parts) + + def _get_create_request_xml(self, body_dict): + req = webob.Request.blank('/v1.1/os-servers-create-ext') + req.content_type = 'application/xml' + req.accept = 'application/xml' + req.method = 'POST' + req.body = self._format_xml_request_body(body_dict) + return req + + def _create_instance_with_personality_json(self, personality): + body_dict = self._create_personality_request_dict(personality) + request = self._get_create_request_json(body_dict) + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + return request, response, compute_api.injected_files + + def _create_instance_with_personality_xml(self, personality): + body_dict = self._create_personality_request_dict(personality) + request = self._get_create_request_xml(body_dict) + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + return request, response, compute_api.injected_files + + def _create_instance_with_networks_json(self, networks): + body_dict = self._create_networks_request_dict(networks) + request = self._get_create_request_json(body_dict) + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + return request, response, compute_api.networks + + def _create_instance_with_networks_xml(self, networks): + body_dict = self._create_networks_request_dict(networks) + request = self._get_create_request_xml(body_dict) + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + return request, response, compute_api.networks + + def test_create_instance_with_no_personality(self): + request, response, injected_files = \ + self._create_instance_with_personality_json(personality=None) + self.assertEquals(response.status_int, 200) + self.assertEquals(injected_files, []) + + def test_create_instance_with_no_personality_xml(self): + request, response, injected_files = \ + self._create_instance_with_personality_xml(personality=None) + self.assertEquals(response.status_int, 200) + self.assertEquals(injected_files, []) + + def test_create_instance_with_personality(self): + path = '/my/file/path' + contents = '#!/bin/bash\necho "Hello, World!"\n' + b64contents = base64.b64encode(contents) + personality = [(path, b64contents)] + request, response, injected_files = \ + self._create_instance_with_personality_json(personality) + self.assertEquals(response.status_int, 200) + self.assertEquals(injected_files, [(path, contents)]) + + def test_create_instance_with_personality_xml(self): + path = '/my/file/path' + contents = '#!/bin/bash\necho "Hello, World!"\n' + b64contents = base64.b64encode(contents) + personality = [(path, b64contents)] + request, response, injected_files = \ + self._create_instance_with_personality_xml(personality) + self.assertEquals(response.status_int, 200) + self.assertEquals(injected_files, [(path, contents)]) + + def test_create_instance_with_personality_no_path(self): + personality = [('/remove/this/path', + base64.b64encode('my\n\file\ncontents'))] + body_dict = self._create_personality_request_dict(personality) + del body_dict['server']['personality'][0]['path'] + request = self._get_create_request_json(body_dict) + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + self.assertEquals(response.status_int, 400) + self.assertEquals(compute_api.injected_files, None) + + def _test_create_instance_with_personality_no_path_xml(self): + personality = [('/remove/this/path', + base64.b64encode('my\n\file\ncontents'))] + body_dict = self._create_personality_request_dict(personality) + request = self._get_create_request_xml(body_dict) + request.body = request.body.replace(' path="/remove/this/path"', '') + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + self.assertEquals(response.status_int, 400) + self.assertEquals(compute_api.injected_files, None) + + def test_create_instance_with_personality_no_contents(self): + personality = [('/test/path', + base64.b64encode('remove\nthese\ncontents'))] + body_dict = self._create_personality_request_dict(personality) + del body_dict['server']['personality'][0]['contents'] + request = self._get_create_request_json(body_dict) + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + self.assertEquals(response.status_int, 400) + self.assertEquals(compute_api.injected_files, None) + + def test_create_instance_with_personality_not_a_list(self): + personality = [('/test/path', base64.b64encode('test\ncontents\n'))] + body_dict = self._create_personality_request_dict(personality) + body_dict['server']['personality'] = \ + body_dict['server']['personality'][0] + request = self._get_create_request_json(body_dict) + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + self.assertEquals(response.status_int, 400) + self.assertEquals(compute_api.injected_files, None) + + def test_create_instance_with_personality_with_non_b64_content(self): + path = '/my/file/path' + contents = '#!/bin/bash\necho "Oh no!"\n' + personality = [(path, contents)] + request, response, injected_files = \ + self._create_instance_with_personality_json(personality) + self.assertEquals(response.status_int, 400) + self.assertEquals(injected_files, None) + + def test_create_instance_with_null_personality(self): + personality = None + body_dict = self._create_personality_request_dict(personality) + body_dict['server']['personality'] = None + request = self._get_create_request_json(body_dict) + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + self.assertEquals(response.status_int, 200) + + def test_create_instance_with_three_personalities(self): + files = [ + ('/etc/sudoers', 'ALL ALL=NOPASSWD: ALL\n'), + ('/etc/motd', 'Enjoy your root access!\n'), + ('/etc/dovecot.conf', 'dovecot\nconfig\nstuff\n'), + ] + personality = [] + for path, content in files: + personality.append((path, base64.b64encode(content))) + request, response, injected_files = \ + self._create_instance_with_personality_json(personality) + self.assertEquals(response.status_int, 200) + self.assertEquals(injected_files, files) + + def test_create_instance_personality_empty_content(self): + path = '/my/file/path' + contents = '' + personality = [(path, contents)] + request, response, injected_files = \ + self._create_instance_with_personality_json(personality) + self.assertEquals(response.status_int, 200) + self.assertEquals(injected_files, [(path, contents)]) + + def test_create_instance_admin_pass_json(self): + request, response, dummy = \ + self._create_instance_with_personality_json(None) + self.assertEquals(response.status_int, 200) + response = json.loads(response.body) + self.assertTrue('adminPass' in response['server']) + self.assertEqual(16, len(response['server']['adminPass'])) + + def test_create_instance_admin_pass_xml(self): + request, response, dummy = \ + self._create_instance_with_personality_xml(None) + self.assertEquals(response.status_int, 200) + dom = minidom.parseString(response.body) + server = dom.childNodes[0] + self.assertEquals(server.nodeName, 'server') + self.assertEqual(16, len(server.getAttribute('adminPass'))) + + def test_create_instance_with_no_networks(self): + request, response, networks = \ + self._create_instance_with_networks_json(networks=None) + self.assertEquals(response.status_int, 200) + self.assertEquals(networks, None) + + def test_create_instance_with_no_networks_xml(self): + request, response, networks = \ + self._create_instance_with_networks_xml(networks=None) + self.assertEquals(response.status_int, 200) + self.assertEquals(networks, None) + + def test_create_instance_with_one_network(self): + id = 1 + fixed_ip = '10.0.1.12' + networks = [(id, fixed_ip)] + request, response, networks = \ + self._create_instance_with_networks_json(networks) + self.assertEquals(response.status_int, 200) + self.assertEquals(networks, [(id, fixed_ip)]) + + def test_create_instance_with_one_network_xml(self): + id = 1 + fixed_ip = '10.0.1.12' + networks = [(id, fixed_ip)] + request, response, networks = \ + self._create_instance_with_networks_xml(networks) + self.assertEquals(response.status_int, 200) + self.assertEquals(networks, [(id, fixed_ip)]) + + def test_create_instance_with_two_networks(self): + networks = [(1, '10.0.1.12'), (2, '10.0.2.12')] + request, response, networks = \ + self._create_instance_with_networks_json(networks) + self.assertEquals(response.status_int, 200) + self.assertEquals(networks, [(1, '10.0.1.12'), (2, '10.0.2.12')]) + + def test_create_instance_with_two_networks_xml(self): + networks = [(1, '10.0.1.12'), (2, '10.0.2.12')] + request, response, networks = \ + self._create_instance_with_networks_xml(networks) + self.assertEquals(response.status_int, 200) + self.assertEquals(networks, [(1, '10.0.1.12'), (2, '10.0.2.12')]) + + def test_create_instance_with_duplicate_networks(self): + networks = [(1, '10.0.1.12'), (1, '10.0.2.12')] + request, response, networks = \ + self._create_instance_with_networks_json(networks) + self.assertEquals(response.status_int, 400) + self.assertEquals(networks, None) + + def test_create_instance_with_duplicate_networks_xml(self): + networks = [(1, '10.0.1.12'), (1, '10.0.2.12')] + request, response, networks = \ + self._create_instance_with_networks_xml(networks) + self.assertEquals(response.status_int, 400) + self.assertEquals(networks, None) + + def test_create_instance_with_network_no_id(self): + networks = [(1, '10.0.1.12')] + body_dict = self._create_networks_request_dict(networks) + del body_dict['server']['networks'][0]['id'] + request = self._get_create_request_json(body_dict) + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + self.assertEquals(response.status_int, 400) + self.assertEquals(compute_api.networks, None) + + def test_create_instance_with_network_no_id_xml(self): + networks = [(1, '10.0.1.12')] + body_dict = self._create_networks_request_dict(networks) + request = self._get_create_request_xml(body_dict) + request.body = request.body.replace(' id="1"', '') + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + self.assertEquals(response.status_int, 400) + self.assertEquals(compute_api.networks, None) + + def test_create_instance_with_network_invalid_id(self): + networks = [('asd123', '10.0.1.12')] + request, response, networks = \ + self._create_instance_with_networks_json(networks) + self.assertEquals(response.status_int, 400) + self.assertEquals(networks, None) + + def test_create_instance_with_network_invalid_id_xml(self): + networks = [('asd123', '10.0.1.12')] + request, response, networks = \ + self._create_instance_with_networks_xml(networks) + self.assertEquals(response.status_int, 400) + self.assertEquals(networks, None) + + def test_create_instance_with_network_empty_fixed_ip(self): + networks = [('1', '')] + request, response, networks = \ + self._create_instance_with_networks_json(networks) + self.assertEquals(response.status_int, 400) + self.assertEquals(networks, None) + + def test_create_instance_with_network_non_string_fixed_ip(self): + networks = [('1', 12345)] + request, response, networks = \ + self._create_instance_with_networks_json(networks) + self.assertEquals(response.status_int, 400) + self.assertEquals(networks, None) + + def test_create_instance_with_network_empty_fixed_ip_xml(self): + networks = [('1', '')] + request, response, networks = \ + self._create_instance_with_networks_xml(networks) + self.assertEquals(response.status_int, 400) + self.assertEquals(networks, None) + + def test_create_instance_with_network_no_fixed_ip(self): + networks = [(1, '10.0.1.12')] + body_dict = self._create_networks_request_dict(networks) + del body_dict['server']['networks'][0]['fixed_ip'] + request = self._get_create_request_json(body_dict) + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + self.assertEquals(response.status_int, 200) + self.assertEquals(compute_api.networks, [(1, None)]) + + def test_create_instance_with_network_no_fixed_ip_xml(self): + networks = [(1, '10.0.1.12')] + body_dict = self._create_networks_request_dict(networks) + request = self._get_create_request_xml(body_dict) + request.body = request.body.replace(' fixed_ip="10.0.1.12"', '') + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + self.assertEquals(response.status_int, 200) + self.assertEquals(compute_api.networks, [(1, None)]) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index b2f1a8ef0..ec7dedafc 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -2324,10 +2324,6 @@ class TestServerInstanceCreation(test.TestCase): else: self.injected_files = None - if 'requested_networks' in kwargs: - self.networks = kwargs['requested_networks'] - else: - self.networks = None return [{'id': '1234', 'display_name': 'fakeinstance', 'uuid': FAKE_UUID}] @@ -2436,20 +2432,6 @@ class TestServerInstanceCreation(test.TestCase): self._run_create_instance_with_mock_compute_api(request) return request, response, compute_api.injected_files - def _create_instance_with_networks_json(self, networks): - body_dict = self._create_networks_request_dict(networks) - request = self._get_create_request_json(body_dict) - compute_api, response = \ - self._run_create_instance_with_mock_compute_api(request) - return request, response, compute_api.networks - - def _create_instance_with_networks_xml(self, networks): - body_dict = self._create_networks_request_dict(networks) - request = self._get_create_request_xml(body_dict) - compute_api, response = \ - self._run_create_instance_with_mock_compute_api(request) - return request, response, compute_api.networks - def test_create_instance_with_no_personality(self): request, response, injected_files = \ self._create_instance_with_personality_json(personality=None) @@ -2584,139 +2566,6 @@ class TestServerInstanceCreation(test.TestCase): self.assertEquals(server.nodeName, 'server') self.assertEqual(16, len(server.getAttribute('adminPass'))) - def test_create_instance_with_no_networks(self): - request, response, networks = \ - self._create_instance_with_networks_json(networks=None) - self.assertEquals(response.status_int, 200) - self.assertEquals(networks, None) - - def test_create_instance_with_no_networks_xml(self): - request, response, networks = \ - self._create_instance_with_networks_xml(networks=None) - self.assertEquals(response.status_int, 200) - self.assertEquals(networks, None) - - def test_create_instance_with_one_network(self): - id = 1 - fixed_ip = '10.0.1.12' - networks = [(id, fixed_ip)] - request, response, networks = \ - self._create_instance_with_networks_json(networks) - self.assertEquals(response.status_int, 200) - self.assertEquals(networks, [(id, fixed_ip)]) - - def test_create_instance_with_one_network_xml(self): - id = 1 - fixed_ip = '10.0.1.12' - networks = [(id, fixed_ip)] - request, response, networks = \ - self._create_instance_with_networks_xml(networks) - self.assertEquals(response.status_int, 200) - self.assertEquals(networks, [(id, fixed_ip)]) - - def test_create_instance_with_two_networks(self): - networks = [(1, '10.0.1.12'), (2, '10.0.2.12')] - request, response, networks = \ - self._create_instance_with_networks_json(networks) - self.assertEquals(response.status_int, 200) - self.assertEquals(networks, [(1, '10.0.1.12'), (2, '10.0.2.12')]) - - def test_create_instance_with_two_networks_xml(self): - networks = [(1, '10.0.1.12'), (2, '10.0.2.12')] - request, response, networks = \ - self._create_instance_with_networks_xml(networks) - self.assertEquals(response.status_int, 200) - self.assertEquals(networks, [(1, '10.0.1.12'), (2, '10.0.2.12')]) - - def test_create_instance_with_duplicate_networks(self): - networks = [(1, '10.0.1.12'), (1, '10.0.2.12')] - request, response, networks = \ - self._create_instance_with_networks_json(networks) - self.assertEquals(response.status_int, 400) - self.assertEquals(networks, None) - - def test_create_instance_with_duplicate_networks_xml(self): - networks = [(1, '10.0.1.12'), (1, '10.0.2.12')] - request, response, networks = \ - self._create_instance_with_networks_xml(networks) - self.assertEquals(response.status_int, 400) - self.assertEquals(networks, None) - - def test_create_instance_with_network_no_id(self): - networks = [(1, '10.0.1.12')] - body_dict = self._create_networks_request_dict(networks) - del body_dict['server']['networks'][0]['id'] - request = self._get_create_request_json(body_dict) - compute_api, response = \ - self._run_create_instance_with_mock_compute_api(request) - self.assertEquals(response.status_int, 400) - self.assertEquals(compute_api.networks, None) - - def test_create_instance_with_network_no_id_xml(self): - networks = [(1, '10.0.1.12')] - body_dict = self._create_networks_request_dict(networks) - request = self._get_create_request_xml(body_dict) - request.body = request.body.replace(' id="1"', '') - compute_api, response = \ - self._run_create_instance_with_mock_compute_api(request) - self.assertEquals(response.status_int, 400) - self.assertEquals(compute_api.networks, None) - - def test_create_instance_with_network_invalid_id(self): - networks = [('asd123', '10.0.1.12')] - request, response, networks = \ - self._create_instance_with_networks_json(networks) - self.assertEquals(response.status_int, 400) - self.assertEquals(networks, None) - - def test_create_instance_with_network_invalid_id_xml(self): - networks = [('asd123', '10.0.1.12')] - request, response, networks = \ - self._create_instance_with_networks_xml(networks) - self.assertEquals(response.status_int, 400) - self.assertEquals(networks, None) - - def test_create_instance_with_network_empty_fixed_ip(self): - networks = [('1', '')] - request, response, networks = \ - self._create_instance_with_networks_json(networks) - self.assertEquals(response.status_int, 400) - self.assertEquals(networks, None) - - def test_create_instance_with_network_non_string_fixed_ip(self): - networks = [('1', 12345)] - request, response, networks = \ - self._create_instance_with_networks_json(networks) - self.assertEquals(response.status_int, 400) - self.assertEquals(networks, None) - - def test_create_instance_with_network_empty_fixed_ip_xml(self): - networks = [('1', '')] - request, response, networks = \ - self._create_instance_with_networks_xml(networks) - self.assertEquals(response.status_int, 400) - self.assertEquals(networks, None) - - def test_create_instance_with_network_no_fixed_ip(self): - networks = [(1, '10.0.1.12')] - body_dict = self._create_networks_request_dict(networks) - del body_dict['server']['networks'][0]['fixed_ip'] - request = self._get_create_request_json(body_dict) - compute_api, response = \ - self._run_create_instance_with_mock_compute_api(request) - self.assertEquals(response.status_int, 200) - self.assertEquals(compute_api.networks, [(1, None)]) - - def test_create_instance_with_network_no_fixed_ip_xml(self): - networks = [(1, '10.0.1.12')] - body_dict = self._create_networks_request_dict(networks) - request = self._get_create_request_xml(body_dict) - request.body = request.body.replace(' fixed_ip="10.0.1.12"', '') - compute_api, response = \ - self._run_create_instance_with_mock_compute_api(request) - self.assertEquals(response.status_int, 200) - self.assertEquals(compute_api.networks, [(1, None)]) - class TestGetKernelRamdiskFromImage(test.TestCase): """ diff --git a/nova/tests/integrated/test_servers.py b/nova/tests/integrated/test_servers.py index 33ffa7cf1..4e8e85c7b 100644 --- a/nova/tests/integrated/test_servers.py +++ b/nova/tests/integrated/test_servers.py @@ -179,222 +179,6 @@ class ServersTest(integrated_helpers._IntegratedTestBase): # Cleanup self._delete_server(created_server_id) - def test_create_server_with_one_networks(self): - """Creates a server with networks.""" - - # Build the server data gradually, checking errors along the way - server = self._build_minimal_create_server_request() - - network_list = [{'id': 1, 'fixed_ip': '10.0.0.3'}] - server['networks'] = network_list - - post = {'server': server} - created_server = self.api.post_server(post) - LOG.debug("created_server: %s" % created_server) - self.assertTrue(created_server['id']) - created_server_id = created_server['id'] - - # Check it's there - found_server = self.api.get_server(created_server_id) - self.assertEqual(created_server_id, found_server['id']) - - # Cleanup - self._delete_server(created_server_id) - - def test_create_server_with_two_networks(self): - """Creates a server with networks.""" - - # Build the server data gradually, checking errors along the way - server = self._build_minimal_create_server_request() - - network_list = [{'id': 1, 'fixed_ip': '10.0.0.3'}, - {'id': 2, 'fixed_ip': '10.0.0.12'}] - server['networks'] = network_list - - post = {'server': server} - created_server = self.api.post_server(post) - LOG.debug("created_server: %s" % created_server) - self.assertTrue(created_server['id']) - created_server_id = created_server['id'] - - # Check it's there - found_server = self.api.get_server(created_server_id) - self.assertEqual(created_server_id, found_server['id']) - - # Cleanup - self._delete_server(created_server_id) - - def test_create_server_with_empty_networks(self): - """Creates a server with networks.""" - - # Build the server data gradually, checking errors along the way - server = self._build_minimal_create_server_request() - - network_list = [] - server['networks'] = network_list - - post = {'server': server} - created_server = self.api.post_server(post) - LOG.debug("created_server: %s" % created_server) - self.assertTrue(created_server['id']) - created_server_id = created_server['id'] - - # Check it's there - found_server = self.api.get_server(created_server_id) - self.assertEqual(created_server_id, found_server['id']) - - # Cleanup - self._delete_server(created_server_id) - - def test_create_server_with_networks_invalid_id(self): - """Creates a server with networks.""" - - # Build the server data gradually, checking errors along the way - server = self._build_minimal_create_server_request() - - network_list = [{'id': "invalid", 'fixed_ip': '10.0.0.3'}] - server['networks'] = network_list - - post = {'server': server} - self.assertRaises(client.OpenStackApiException, - self.api.post_server, post) - - def test_create_server_with_networks_no_id(self): - """Creates a server with networks.""" - - # Build the server data gradually, checking errors along the way - server = self._build_minimal_create_server_request() - - network_list = [{'fixed_ip': '10.0.0.3'}] - server['networks'] = network_list - - post = {'server': server} - self.assertRaises(client.OpenStackApiException, - self.api.post_server, post) - - def test_create_server_with_networks_non_exists_id(self): - """Creates a server with networks.""" - - # Build the server data gradually, checking errors along the way - server = self._build_minimal_create_server_request() - - network_list = [{'id': 3, 'fixed_ip': '10.0.0.3'}] - server['networks'] = network_list - - post = {'server': server} - self.assertRaises(client.OpenStackApiException, - self.api.post_server, post) - - def test_create_server_with_networks_not_found_for_project(self): - """Creates a server with networks.""" - - # Build the server data gradually, checking errors along the way - server = self._build_minimal_create_server_request() - - network_list = [{'id': 1, 'fixed_ip': '10.0.0.3'}, - {'id': 3, 'fixed_ip': '10.0.0.12'}] - server['networks'] = network_list - - post = {'server': server} - self.assertRaises(client.OpenStackApiException, - self.api.post_server, post) - - def test_create_server_with_networks_invalid_fixed_ip(self): - """Creates a server with networks.""" - - # Build the server data gradually, checking errors along the way - server = self._build_minimal_create_server_request() - - network_list = [{'id': 1, 'fixed_ip': '10.0.0.3.0'}] - server['networks'] = network_list - - post = {'server': server} - self.assertRaises(client.OpenStackApiException, - self.api.post_server, post) - - def test_create_server_with_networks_non_existing_fixed_ip(self): - """Creates a server with metadata.""" - - # Build the server data gradually, checking errors along the way - server = self._build_minimal_create_server_request() - - network_list = [{'id': 1, 'fixed_ip': '10.100.0.3'}] - server['networks'] = network_list - - post = {'server': server} - self.assertRaises(client.OpenStackApiException, - self.api.post_server, post) - - def test_create_server_with_networks_empty_fixed_ip(self): - """Creates a server with networks.""" - - # Build the server data gradually, checking errors along the way - server = self._build_minimal_create_server_request() - - network_list = [{'id': 1, 'fixed_ip': ''}] - server['networks'] = network_list - - post = {'server': server} - self.assertRaises(client.OpenStackApiException, - self.api.post_server, post) - - def test_create_server_with_networks_non_string_fixed_ip(self): - """Creates a server with networks.""" - - # Build the server data gradually, checking errors along the way - server = self._build_minimal_create_server_request() - - network_list = [{'id': 1, 'fixed_ip': 12}] - server['networks'] = network_list - - post = {'server': server} - self.assertRaises(client.OpenStackApiException, - self.api.post_server, post) - - def test_create_server_with_networks_none_fixed_ip(self): - """Creates a server with networks.""" - - # Build the server data gradually, checking errors along the way - server = self._build_minimal_create_server_request() - - network_list = [{'id': 1, 'fixed_ip': None}] - server['networks'] = network_list - - post = {'server': server} - created_server = self.api.post_server(post) - LOG.debug("created_server: %s" % created_server) - self.assertTrue(created_server['id']) - created_server_id = created_server['id'] - - # Check it's there - found_server = self.api.get_server(created_server_id) - self.assertEqual(created_server_id, found_server['id']) - - # Cleanup - self._delete_server(created_server_id) - - def test_create_server_with_networks_no_fixed_ip(self): - """Creates a server with networks.""" - - # Build the server data gradually, checking errors along the way - server = self._build_minimal_create_server_request() - - network_list = [{'id': 1}] - server['networks'] = network_list - - post = {'server': server} - created_server = self.api.post_server(post) - LOG.debug("created_server: %s" % created_server) - self.assertTrue(created_server['id']) - created_server_id = created_server['id'] - - # Check it's there - found_server = self.api.get_server(created_server_id) - self.assertEqual(created_server_id, found_server['id']) - - # Cleanup - self._delete_server(created_server_id) - def test_create_and_rebuild_server(self): """Rebuild a server.""" -- cgit From c8de39d52d48fc14b851cdd5de8b0d356f3291dc Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Tue, 19 Jul 2011 17:38:51 -0700 Subject: Reverted to original code, after network binding to project code is in integration code for testing new extension will be added --- nova/tests/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nova/tests/__init__.py b/nova/tests/__init__.py index 44ce2e635..e4ed75d37 100644 --- a/nova/tests/__init__.py +++ b/nova/tests/__init__.py @@ -66,8 +66,7 @@ def setup(): bridge=FLAGS.flat_network_bridge, bridge_interface=bridge_interface, vpn_start=FLAGS.vpn_start, - vlan_start=FLAGS.vlan_start, - project_id="openstack") + vlan_start=FLAGS.vlan_start) for net in db.network_get_all(ctxt): network.set_network_host(ctxt, net['id']) -- cgit From 038565bdc735ff7a227a39d2ee21df0e8194929b Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Tue, 19 Jul 2011 18:24:44 -0700 Subject: Modified alias ^Cd minor fixes --- bin/nova-manage | 2 +- nova/api/openstack/contrib/createserverext.py | 4 ++-- nova/network/manager.py | 1 - nova/tests/api/openstack/contrib/test_createserverext.py | 4 ++-- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 47bf81008..7c2dd4d6d 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -621,7 +621,7 @@ class NetworkCommands(object): def list(self): """List all created networks""" - print "%-5s\%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s" % (_('id'), + print "%-5s\t%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s" % (_('id'), _('network'), _('netmask'), _('start address'), diff --git a/nova/api/openstack/contrib/createserverext.py b/nova/api/openstack/contrib/createserverext.py index 78dba425a..2f8b4389f 100644 --- a/nova/api/openstack/contrib/createserverext.py +++ b/nova/api/openstack/contrib/createserverext.py @@ -205,7 +205,7 @@ class Createserverext(extensions.ExtensionDescriptor): return "Createserverext" def get_alias(self): - return "os-servers-create-ext" + return "os-create-server-ext" def get_description(self): return "Extended support to the Create Server v1.1 API" @@ -234,7 +234,7 @@ class Createserverext(extensions.ExtensionDescriptor): headers_serializer) deserializer = wsgi.RequestDeserializer(body_deserializers) - res = extensions.ResourceExtension('os-servers-create-ext', + res = extensions.ResourceExtension('os-create-server-ext', controller=CreateServerExtController(), deserializer=deserializer, serializer=serializer) diff --git a/nova/network/manager.py b/nova/network/manager.py index 349faa84d..27ea669b4 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -614,7 +614,6 @@ class NetworkManager(manager.SchedulerDependentManager): net['gateway'] = str(project_net[1]) net['broadcast'] = str(project_net.broadcast) net['dhcp_start'] = str(project_net[2]) - net['project_id'] = kwargs['project_id'] if num_networks > 1: net['label'] = '%s_%d' % (label, index) else: diff --git a/nova/tests/api/openstack/contrib/test_createserverext.py b/nova/tests/api/openstack/contrib/test_createserverext.py index f2aa136e2..5d1687ee9 100644 --- a/nova/tests/api/openstack/contrib/test_createserverext.py +++ b/nova/tests/api/openstack/contrib/test_createserverext.py @@ -290,7 +290,7 @@ class CreateserverextTest(test.TestCase): return {'server': server} def _get_create_request_json(self, body_dict): - req = webob.Request.blank('/v1.1/os-servers-create-ext') + req = webob.Request.blank('/v1.1/os-create-server-ext') req.headers['Content-Type'] = 'application/json' req.method = 'POST' req.body = json.dumps(body_dict) @@ -334,7 +334,7 @@ class CreateserverextTest(test.TestCase): return ''.join(body_parts) def _get_create_request_xml(self, body_dict): - req = webob.Request.blank('/v1.1/os-servers-create-ext') + req = webob.Request.blank('/v1.1/os-create-server-ext') req.content_type = 'application/xml' req.accept = 'application/xml' req.method = 'POST' -- cgit From ae1fd7e73890d361d0bd6ad764118c1e5601cdca Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Thu, 21 Jul 2011 11:25:02 -0700 Subject: pep8 --- nova/network/manager.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/nova/network/manager.py b/nova/network/manager.py index c9a68af68..bafa1c8e8 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -409,9 +409,8 @@ class NetworkManager(manager.SchedulerDependentManager): LOG.debug(_("network allocations for instance %s"), instance_id, context=context) networks = self._get_networks_for_instance(admin_context, - instance_id, - project_id, - requested_networks=requested_networks) + instance_id, project_id, + requested_networks=requested_networks) self._allocate_mac_addresses(context, instance_id, networks) self._allocate_fixed_ips(admin_context, instance_id, host, networks, vpn=vpn, @@ -928,7 +927,7 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): requested_networks) else: networks = self.db.project_get_networks(context, project_id) - return networks; + return networks def create_networks(self, context, **kwargs): """Create networks based on parameters.""" -- cgit From 6c25483c08965204e8a4199ec600098fe09ad87c Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Wed, 27 Jul 2011 15:02:00 -0700 Subject: added unit testcase to increase code coverage --- nova/tests/api/openstack/contrib/test_createserverext.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/nova/tests/api/openstack/contrib/test_createserverext.py b/nova/tests/api/openstack/contrib/test_createserverext.py index 4866f29fe..4db15d2f1 100644 --- a/nova/tests/api/openstack/contrib/test_createserverext.py +++ b/nova/tests/api/openstack/contrib/test_createserverext.py @@ -222,6 +222,16 @@ class CreateserverextTest(test.TestCase): self._run_create_instance_with_mock_compute_api(request) self.assertEquals(response.status_int, 422) + def test_create_instance_with_no_server_name(self): + server = {} + server['imageRef'] = 1 + server['flavorRef'] = 1 + body_dict = {'server': server} + request = self._get_create_request_json(body_dict) + compute_api, response = \ + self._run_create_instance_with_mock_compute_api(request) + self.assertEquals(response.status_int, 400) + def test_create_instance_with_no_personality(self): request, response, injected_files = \ self._create_instance_with_personality_json(personality=None) -- cgit From b0ffe14da11addd2e1fcf14325b9bcf4c8cd3512 Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Wed, 27 Jul 2011 15:14:13 -0700 Subject: Reverting to original code --- nova/rpc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/rpc.py b/nova/rpc.py index 055f8220e..e2771ca88 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -279,7 +279,7 @@ class FanoutAdapterConsumer(AdapterConsumer): # them when done, so they're not left around on restart. # Also, we're the only one that should be consuming. exclusive # implies auto_delete, so we'll just set that.. - self.exclusive = False + self.exclusive = True LOG.info(_('Created "%(exchange)s" fanout exchange ' 'with "%(key)s" routing key'), dict(exchange=self.exchange, key=self.routing_key)) -- cgit From 3335a91c3c53513cc35e3f39a59975b33524950b Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Fri, 29 Jul 2011 18:45:51 -0700 Subject: Fixed review comments: Put parsing logic of network information in create_instance_helper module and refactored unit testcases as per the changed code. --- nova/api/openstack/contrib/createserverext.py | 213 +----------- nova/api/openstack/create_instance_helper.py | 83 ++++- nova/compute/api.py | 1 - nova/network/manager.py | 2 +- .../api/openstack/contrib/test_createserverext.py | 359 +-------------------- nova/tests/api/openstack/test_extensions.py | 6 - nova/tests/api/openstack/test_servers.py | 229 ++++++++++--- 7 files changed, 265 insertions(+), 628 deletions(-) diff --git a/nova/api/openstack/contrib/createserverext.py b/nova/api/openstack/contrib/createserverext.py index b5c06920c..79c017b42 100644 --- a/nova/api/openstack/contrib/createserverext.py +++ b/nova/api/openstack/contrib/createserverext.py @@ -13,220 +13,11 @@ # 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 webob import exc - -from nova import exception -import nova.image -from nova import network -from nova import quota -from nova import rpc from nova.api.openstack import create_instance_helper as helper from nova.api.openstack import extensions -from nova.compute import instance_types from nova.api.openstack import servers from nova.api.openstack import wsgi -from nova.auth import manager as auth_manager - - -class CreateInstanceHelperEx(helper.CreateInstanceHelper): - def __init__(self, controller): - super(CreateInstanceHelperEx, self).__init__(controller) - - def create_instance(self, req, body, create_method): - """Creates a new server for the given user as per - the network information if it is provided - """ - if not body: - raise exc.HTTPUnprocessableEntity() - - if not 'server' in body: - raise exc.HTTPUnprocessableEntity() - - context = req.environ['nova.context'] - - password = self.controller._get_server_admin_password(body['server']) - - key_name = None - key_data = None - key_pairs = auth_manager.AuthManager.get_key_pairs(context) - if key_pairs: - key_pair = key_pairs[0] - key_name = key_pair['name'] - key_data = key_pair['public_key'] - - image_href = self.controller._image_ref_from_req_data(body) - try: - image_service, image_id = nova.image.get_image_service(image_href) - kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image( - req, image_id) - images = set([str(x['id']) for x in image_service.index(context)]) - assert str(image_id) in images - except Exception, e: - msg = _("Cannot find requested image %(image_href)s: %(e)s" % - locals()) - raise exc.HTTPBadRequest(explanation=msg) - - personality = body['server'].get('personality') - - injected_files = [] - if personality: - injected_files = self._get_injected_files(personality) - - requested_networks = body['server'].get('networks') - - if requested_networks is not None: - requested_networks = self._get_requested_networks( - requested_networks) - - flavor_id = self.controller._flavor_id_from_req_data(body) - - if not 'name' in body['server']: - msg = _("Server name is not defined") - raise exc.HTTPBadRequest(explanation=msg) - - zone_blob = body['server'].get('blob') - name = body['server']['name'] - self._validate_server_name(name) - name = name.strip() - - reservation_id = body['server'].get('reservation_id') - min_count = body['server'].get('min_count') - max_count = body['server'].get('max_count') - # min_count and max_count are optional. If they exist, they come - # in as strings. We want to default 'min_count' to 1, and default - # 'max_count' to be 'min_count'. - min_count = int(min_count) if min_count else 1 - max_count = int(max_count) if max_count else min_count - if min_count > max_count: - min_count = max_count - - try: - inst_type = \ - instance_types.get_instance_type_by_flavor_id(flavor_id) - extra_values = { - 'instance_type': inst_type, - 'image_ref': image_href, - 'password': password} - - return (extra_values, - create_method(context, - inst_type, - image_id, - kernel_id=kernel_id, - ramdisk_id=ramdisk_id, - display_name=name, - display_description=name, - key_name=key_name, - key_data=key_data, - metadata=body['server'].get('metadata', {}), - injected_files=injected_files, - admin_password=password, - zone_blob=zone_blob, - reservation_id=reservation_id, - min_count=min_count, - max_count=max_count, - requested_networks=requested_networks)) - except quota.QuotaError as error: - self._handle_quota_error(error) - except exception.ImageNotFound as error: - msg = _("Can not find requested image") - raise exc.HTTPBadRequest(explanation=msg) - except rpc.RemoteError as err: - msg = "%(err_type)s: %(err_msg)s" % \ - {'err_type': err.exc_type, 'err_msg': err.value} - raise exc.HTTPBadRequest(explanation=msg) - - # Let the caller deal with unhandled exceptions. - - def _validate_fixed_ip(self, value): - if not isinstance(value, basestring): - msg = _("Fixed IP is not a string or unicode") - raise exc.HTTPBadRequest(explanation=msg) - - if value.strip() == '': - msg = _("Fixed IP is an empty string") - raise exc.HTTPBadRequest(explanation=msg) - - def _get_requested_networks(self, requested_networks): - """ - Create a list of requested networks from the networks attribute - """ - networks = [] - for network in requested_networks: - try: - network_id = network['id'] - network_id = int(network_id) - #fixed IP address is optional - #if the fixed IP address is not provided then - #it will use one of the available IP address from the network - fixed_ip = network.get('fixed_ip', None) - if fixed_ip is not None: - self._validate_fixed_ip(fixed_ip) - # check if the network id is already present in the list, - # we don't want duplicate networks to be passed - # at the boot time - for id, ip in networks: - if id == network_id: - expl = _("Duplicate networks (%s) are not allowed")\ - % network_id - raise exc.HTTPBadRequest(explanation=expl) - - networks.append((network_id, fixed_ip)) - except KeyError as key: - expl = _('Bad network format: missing %s') % key - raise exc.HTTPBadRequest(explanation=expl) - except ValueError: - expl = _("Bad networks format: network id should " - "be integer (%s)") % network_id - raise exc.HTTPBadRequest(explanation=expl) - except TypeError: - expl = _('Bad networks format') - raise exc.HTTPBadRequest(explanation=expl) - - return networks - - -class CreateServerExtController(servers.ControllerV11): - """This is the controller for the extended version - of the create server OS V1.1 - """ - def __init__(self): - super(CreateServerExtController, self).__init__() - self.helper = CreateInstanceHelperEx(self) - - -class ServerXMLDeserializer(helper.ServerXMLDeserializer): - """ - Deserializer to handle xml-formatted server create requests. - - Handles networks element - """ - def _extract_server(self, node): - """Marshal the server attribute of a parsed request""" - server = super(ServerXMLDeserializer, self)._extract_server(node) - server_node = self.find_first_child_named(node, 'server') - networks = self._extract_networks(server_node) - if networks is not None: - server["networks"] = networks - return server - - def _extract_networks(self, server_node): - """Marshal the networks attribute of a parsed request""" - networks_node = \ - self.find_first_child_named(server_node, "networks") - if networks_node is None: - return None - networks = [] - for network_node in self.find_children_named(networks_node, - "network"): - item = {} - if network_node.hasAttribute("id"): - item["id"] = network_node.getAttribute("id") - if network_node.hasAttribute("fixed_ip"): - item["fixed_ip"] = network_node.getAttribute("fixed_ip") - networks.append(item) - return networks class Createserverext(extensions.ExtensionDescriptor): @@ -261,7 +52,7 @@ class Createserverext(extensions.ExtensionDescriptor): } body_deserializers = { - 'application/xml': ServerXMLDeserializer(), + 'application/xml': helper.ServerXMLDeserializer(), } serializer = wsgi.ResponseSerializer(body_serializers, @@ -269,7 +60,7 @@ class Createserverext(extensions.ExtensionDescriptor): deserializer = wsgi.RequestDeserializer(body_deserializers) res = extensions.ResourceExtension('os-create-server-ext', - controller=CreateServerExtController(), + controller=servers.ControllerV11(), deserializer=deserializer, serializer=serializer) resources.append(res) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 04fbeda73..c7cc88313 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -30,6 +30,7 @@ from nova import utils from nova.compute import instance_types from nova.api.openstack import wsgi +from nova.rpc.common import RemoteError LOG = logging.getLogger('nova.api.openstack.create_instance_helper') FLAGS = flags.FLAGS @@ -106,6 +107,12 @@ class CreateInstanceHelper(object): if personality: injected_files = self._get_injected_files(personality) + requested_networks = server_dict.get('networks') + + if requested_networks is not None: + requested_networks = self._get_requested_networks( + requested_networks) + try: flavor_id = self.controller._flavor_id_from_req_data(body) except ValueError as error: @@ -156,7 +163,8 @@ class CreateInstanceHelper(object): zone_blob=zone_blob, reservation_id=reservation_id, min_count=min_count, - max_count=max_count)) + max_count=max_count, + requested_networks=requested_networks)) except quota.QuotaError as error: self._handle_quota_error(error) except exception.ImageNotFound as error: @@ -165,6 +173,10 @@ class CreateInstanceHelper(object): except exception.FlavorNotFound as error: msg = _("Invalid flavorRef provided.") raise exc.HTTPBadRequest(explanation=msg) + except RemoteError as err: + msg = "%(err_type)s: %(err_msg)s" % \ + {'err_type': err.exc_type, 'err_msg': err.value} + raise exc.HTTPBadRequest(explanation=msg) # Let the caller deal with unhandled exceptions. def _handle_quota_error(self, error): @@ -286,6 +298,53 @@ class CreateInstanceHelper(object): raise exc.HTTPBadRequest(explanation=msg) return password + def _validate_fixed_ip(self, value): + if not isinstance(value, basestring): + msg = _("Fixed IP is not a string or unicode") + raise exc.HTTPBadRequest(explanation=msg) + + if value.strip() == '': + msg = _("Fixed IP is an empty string") + raise exc.HTTPBadRequest(explanation=msg) + + def _get_requested_networks(self, requested_networks): + """ + Create a list of requested networks from the networks attribute + """ + networks = [] + for network in requested_networks: + try: + network_id = network['id'] + network_id = int(network_id) + #fixed IP address is optional + #if the fixed IP address is not provided then + #it will use one of the available IP address from the network + fixed_ip = network.get('fixed_ip', None) + if fixed_ip is not None: + self._validate_fixed_ip(fixed_ip) + # check if the network id is already present in the list, + # we don't want duplicate networks to be passed + # at the boot time + for id, ip in networks: + if id == network_id: + expl = _("Duplicate networks (%s) are not allowed")\ + % network_id + raise exc.HTTPBadRequest(explanation=expl) + + networks.append((network_id, fixed_ip)) + except KeyError as key: + expl = _('Bad network format: missing %s') % key + raise exc.HTTPBadRequest(explanation=expl) + except ValueError: + expl = _("Bad networks format: network id should " + "be integer (%s)") % network_id + raise exc.HTTPBadRequest(explanation=expl) + except TypeError: + expl = _('Bad networks format') + raise exc.HTTPBadRequest(explanation=expl) + + return networks + class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): """ @@ -317,6 +376,10 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): server["personality"] = self._extract_personality(server_node) + networks = self._extract_networks(server_node) + if networks: + server["networks"] = networks + return server def _extract_personality(self, server_node): @@ -331,3 +394,21 @@ class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): item["contents"] = self.extract_text(file_node) personality.append(item) return personality + + def _extract_networks(self, server_node): + """Marshal the networks attribute of a parsed request""" + networks_node = \ + self.find_first_child_named(server_node, "networks") + if networks_node is None: + return None + networks = [] + if networks_node is not None: + for network_node in self.find_children_named(networks_node, + "network"): + item = {} + if network_node.hasAttribute("id"): + item["id"] = network_node.getAttribute("id") + if network_node.hasAttribute("fixed_ip"): + item["fixed_ip"] = network_node.getAttribute("fixed_ip") + networks.append(item) + return networks diff --git a/nova/compute/api.py b/nova/compute/api.py index 818527809..ea7ba0697 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -96,7 +96,6 @@ class API(base.Base): if not network_api: network_api = network.API() self.network_api = network_api - self.network_manager = utils.import_object(FLAGS.network_manager) if not volume_api: volume_api = volume.API() self.volume_api = volume_api diff --git a/nova/network/manager.py b/nova/network/manager.py index 62a6732d5..655d5800a 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -407,7 +407,7 @@ class NetworkManager(manager.SchedulerDependentManager): host = kwargs.pop('host') project_id = kwargs.pop('project_id') type_id = kwargs.pop('instance_type_id') - requested_networks = kwargs.pop('requested_networks') + requested_networks = kwargs.get('requested_networks') vpn = kwargs.pop('vpn') admin_context = context.elevated() LOG.debug(_("network allocations for instance %s"), instance_id, diff --git a/nova/tests/api/openstack/contrib/test_createserverext.py b/nova/tests/api/openstack/contrib/test_createserverext.py index 4db15d2f1..5a033c761 100644 --- a/nova/tests/api/openstack/contrib/test_createserverext.py +++ b/nova/tests/api/openstack/contrib/test_createserverext.py @@ -48,7 +48,6 @@ class CreateserverextTest(test.TestCase): def setUp(self): super(CreateserverextTest, self).setUp() - self.controller = createserverext.CreateServerExtController() self.stubs = stubout.StubOutForTesting() fakes.FakeAuthManager.auth_data = {} fakes.FakeAuthDatabase.data = {} @@ -82,8 +81,8 @@ class CreateserverextTest(test.TestCase): self.networks = None return [{'id': '1234', 'display_name': 'fakeinstance', 'uuid': FAKE_UUID, - 'created_at': "2010-10-10T12:00:00Z", - 'updated_at': "2010-11-11T11:00:00Z"}] + 'created_at': "", + 'updated_at': ""}] def set_admin_password(self, *args, **kwargs): pass @@ -96,22 +95,10 @@ class CreateserverextTest(test.TestCase): compute_api = MockComputeAPI() self.stubs.Set(nova.compute, 'API', make_stub_method(compute_api)) self.stubs.Set( - createserverext.CreateInstanceHelperEx, + nova.api.openstack.create_instance_helper.CreateInstanceHelper, '_get_kernel_ramdisk_from_image', make_stub_method((1, 1))) return compute_api - def _create_personality_request_dict(self, personality_files): - server = {} - server['name'] = 'new-server-test' - server['imageRef'] = 1 - server['flavorRef'] = 1 - if personality_files is not None: - personalities = [] - for path, contents in personality_files: - personalities.append({'path': path, 'contents': contents}) - server['personality'] = personalities - return {'server': server} - def _create_networks_request_dict(self, networks): server = {} server['name'] = 'new-server-test' @@ -176,20 +163,6 @@ class CreateserverextTest(test.TestCase): req.body = self._format_xml_request_body(body_dict) return req - def _create_instance_with_personality_json(self, personality): - body_dict = self._create_personality_request_dict(personality) - request = self._get_create_request_json(body_dict) - compute_api, response = \ - self._run_create_instance_with_mock_compute_api(request) - return request, response, compute_api.injected_files - - def _create_instance_with_personality_xml(self, personality): - body_dict = self._create_personality_request_dict(personality) - request = self._get_create_request_xml(body_dict) - compute_api, response = \ - self._run_create_instance_with_mock_compute_api(request) - return request, response, compute_api.injected_files - def _create_instance_with_networks_json(self, networks): body_dict = self._create_networks_request_dict(networks) request = self._get_create_request_json(body_dict) @@ -204,168 +177,6 @@ class CreateserverextTest(test.TestCase): self._run_create_instance_with_mock_compute_api(request) return request, response, compute_api.networks - def test_create_instance_with_no_server_element(self): - server = {} - server['name'] = 'new-server-test' - server['imageRef'] = 1 - server['flavorRef'] = 1 - body_dict = {'no-server': server} - request = self._get_create_request_json(body_dict) - compute_api, response = \ - self._run_create_instance_with_mock_compute_api(request) - self.assertEquals(response.status_int, 422) - - def test_create_instance_with_no_request_body(self): - body_dict = None - request = self._get_create_request_json(body_dict) - compute_api, response = \ - self._run_create_instance_with_mock_compute_api(request) - self.assertEquals(response.status_int, 422) - - def test_create_instance_with_no_server_name(self): - server = {} - server['imageRef'] = 1 - server['flavorRef'] = 1 - body_dict = {'server': server} - request = self._get_create_request_json(body_dict) - compute_api, response = \ - self._run_create_instance_with_mock_compute_api(request) - self.assertEquals(response.status_int, 400) - - def test_create_instance_with_no_personality(self): - request, response, injected_files = \ - self._create_instance_with_personality_json(personality=None) - self.assertEquals(response.status_int, 200) - self.assertEquals(injected_files, []) - - def test_create_instance_with_no_personality_xml(self): - request, response, injected_files = \ - self._create_instance_with_personality_xml(personality=None) - self.assertEquals(response.status_int, 200) - self.assertEquals(injected_files, []) - - def test_create_instance_with_personality(self): - path = '/my/file/path' - contents = '#!/bin/bash\necho "Hello, World!"\n' - b64contents = base64.b64encode(contents) - personality = [(path, b64contents)] - request, response, injected_files = \ - self._create_instance_with_personality_json(personality) - self.assertEquals(response.status_int, 200) - self.assertEquals(injected_files, [(path, contents)]) - - def test_create_instance_with_personality_xml(self): - path = '/my/file/path' - contents = '#!/bin/bash\necho "Hello, World!"\n' - b64contents = base64.b64encode(contents) - personality = [(path, b64contents)] - request, response, injected_files = \ - self._create_instance_with_personality_xml(personality) - self.assertEquals(response.status_int, 200) - self.assertEquals(injected_files, [(path, contents)]) - - def test_create_instance_with_personality_no_path(self): - personality = [('/remove/this/path', - base64.b64encode('my\n\file\ncontents'))] - body_dict = self._create_personality_request_dict(personality) - del body_dict['server']['personality'][0]['path'] - request = self._get_create_request_json(body_dict) - compute_api, response = \ - self._run_create_instance_with_mock_compute_api(request) - self.assertEquals(response.status_int, 400) - self.assertEquals(compute_api.injected_files, None) - - def _test_create_instance_with_personality_no_path_xml(self): - personality = [('/remove/this/path', - base64.b64encode('my\n\file\ncontents'))] - body_dict = self._create_personality_request_dict(personality) - request = self._get_create_request_xml(body_dict) - request.body = request.body.replace(' path="/remove/this/path"', '') - compute_api, response = \ - self._run_create_instance_with_mock_compute_api(request) - self.assertEquals(response.status_int, 400) - self.assertEquals(compute_api.injected_files, None) - - def test_create_instance_with_personality_no_contents(self): - personality = [('/test/path', - base64.b64encode('remove\nthese\ncontents'))] - body_dict = self._create_personality_request_dict(personality) - del body_dict['server']['personality'][0]['contents'] - request = self._get_create_request_json(body_dict) - compute_api, response = \ - self._run_create_instance_with_mock_compute_api(request) - self.assertEquals(response.status_int, 400) - self.assertEquals(compute_api.injected_files, None) - - def test_create_instance_with_personality_not_a_list(self): - personality = [('/test/path', base64.b64encode('test\ncontents\n'))] - body_dict = self._create_personality_request_dict(personality) - body_dict['server']['personality'] = \ - body_dict['server']['personality'][0] - request = self._get_create_request_json(body_dict) - compute_api, response = \ - self._run_create_instance_with_mock_compute_api(request) - self.assertEquals(response.status_int, 400) - self.assertEquals(compute_api.injected_files, None) - - def test_create_instance_with_personality_with_non_b64_content(self): - path = '/my/file/path' - contents = '#!/bin/bash\necho "Oh no!"\n' - personality = [(path, contents)] - request, response, injected_files = \ - self._create_instance_with_personality_json(personality) - self.assertEquals(response.status_int, 400) - self.assertEquals(injected_files, None) - - def test_create_instance_with_null_personality(self): - personality = None - body_dict = self._create_personality_request_dict(personality) - body_dict['server']['personality'] = None - request = self._get_create_request_json(body_dict) - compute_api, response = \ - self._run_create_instance_with_mock_compute_api(request) - self.assertEquals(response.status_int, 200) - - def test_create_instance_with_three_personalities(self): - files = [ - ('/etc/sudoers', 'ALL ALL=NOPASSWD: ALL\n'), - ('/etc/motd', 'Enjoy your root access!\n'), - ('/etc/dovecot.conf', 'dovecot\nconfig\nstuff\n'), - ] - personality = [] - for path, content in files: - personality.append((path, base64.b64encode(content))) - request, response, injected_files = \ - self._create_instance_with_personality_json(personality) - self.assertEquals(response.status_int, 200) - self.assertEquals(injected_files, files) - - def test_create_instance_personality_empty_content(self): - path = '/my/file/path' - contents = '' - personality = [(path, contents)] - request, response, injected_files = \ - self._create_instance_with_personality_json(personality) - self.assertEquals(response.status_int, 200) - self.assertEquals(injected_files, [(path, contents)]) - - def test_create_instance_admin_pass_json(self): - request, response, dummy = \ - self._create_instance_with_personality_json(None) - self.assertEquals(response.status_int, 200) - response = json.loads(response.body) - self.assertTrue('adminPass' in response['server']) - self.assertEqual(16, len(response['server']['adminPass'])) - - def test_create_instance_admin_pass_xml(self): - request, response, dummy = \ - self._create_instance_with_personality_xml(None) - self.assertEquals(response.status_int, 200) - dom = minidom.parseString(response.body) - server = dom.childNodes[0] - self.assertEquals(server.nodeName, 'server') - self.assertEqual(16, len(server.getAttribute('adminPass'))) - def test_create_instance_with_no_networks(self): request, response, networks = \ self._create_instance_with_networks_json(networks=None) @@ -498,167 +309,3 @@ class CreateserverextTest(test.TestCase): self._run_create_instance_with_mock_compute_api(request) self.assertEquals(response.status_int, 200) self.assertEquals(compute_api.networks, [(1, None)]) - - -class TestServerCreateRequestXMLDeserializer(unittest.TestCase): - - def setUp(self): - self.deserializer = createserverext.ServerXMLDeserializer() - - def test_request_with_empty_networks(self): - serial_request = """ - - -""" - request = self.deserializer.deserialize(serial_request, 'create') - expected = {"server": { - "name": "new-server-test", - "imageId": "1", - "flavorId": "1", - "networks": [], - }} - self.assertEquals(request['body'], expected) - - def test_request_with_one_network(self): - serial_request = """ - - - - -""" - request = self.deserializer.deserialize(serial_request, 'create') - expected = {"server": { - "name": "new-server-test", - "imageId": "1", - "flavorId": "1", - "networks": [{"id": "1", "fixed_ip": "10.0.1.12"}], - }} - self.assertEquals(request['body'], expected) - - def test_request_with_two_networks(self): - serial_request = """ - - - - - -""" - request = self.deserializer.deserialize(serial_request, 'create') - expected = {"server": { - "name": "new-server-test", - "imageId": "1", - "flavorId": "1", - "networks": [{"id": "1", "fixed_ip": "10.0.1.12"}, - {"id": "2", "fixed_ip": "10.0.2.12"}], - }} - self.assertEquals(request['body'], expected) - - def test_request_with_second_network_node_ignored(self): - serial_request = """ - - - - - - - -""" - request = self.deserializer.deserialize(serial_request, 'create') - expected = {"server": { - "name": "new-server-test", - "imageId": "1", - "flavorId": "1", - "networks": [{"id": "1", "fixed_ip": "10.0.1.12"}], - }} - self.assertEquals(request['body'], expected) - - def test_request_with_one_network_missing_id(self): - serial_request = """ - - - - -""" - request = self.deserializer.deserialize(serial_request, 'create') - expected = {"server": { - "name": "new-server-test", - "imageId": "1", - "flavorId": "1", - "networks": [{"fixed_ip": "10.0.1.12"}], - }} - self.assertEquals(request['body'], expected) - - def test_request_with_one_network_missing_fixed_ip(self): - serial_request = """ - - - - -""" - request = self.deserializer.deserialize(serial_request, 'create') - expected = {"server": { - "name": "new-server-test", - "imageId": "1", - "flavorId": "1", - "networks": [{"id": "1"}], - }} - self.assertEquals(request['body'], expected) - - def test_request_with_one_network_empty_id(self): - serial_request = """ - - - - - """ - request = self.deserializer.deserialize(serial_request, 'create') - expected = {"server": { - "name": "new-server-test", - "imageId": "1", - "flavorId": "1", - "networks": [{"id": "", "fixed_ip": "10.0.1.12"}], - }} - self.assertEquals(request['body'], expected) - - def test_request_with_one_network_empty_fixed_ip(self): - serial_request = """ - - - - - """ - request = self.deserializer.deserialize(serial_request, 'create') - expected = {"server": { - "name": "new-server-test", - "imageId": "1", - "flavorId": "1", - "networks": [{"id": "1", "fixed_ip": ""}], - }} - self.assertEquals(request['body'], expected) - - def test_request_with_networks_duplicate_ids(self): - serial_request = """ - - - - - - """ - request = self.deserializer.deserialize(serial_request, 'create') - expected = {"server": { - "name": "new-server-test", - "imageId": "1", - "flavorId": "1", - "networks": [{"id": "1", "fixed_ip": "10.0.1.12"}, - {"id": "1", "fixed_ip": "10.0.2.12"}], - }} - self.assertEquals(request['body'], expected) diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index e71a5f109..50f4d0c09 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -30,10 +30,6 @@ from nova.api.openstack import wsgi from nova.tests.api.openstack import fakes -from nova import log as logging - -LOG = logging.getLogger('nova.tests.api.openstack.test_extensions') - FLAGS = flags.FLAGS NS = "{http://docs.openstack.org/compute/api/v1.1}" ATOMNS = "{http://www.w3.org/2005/Atom}" @@ -102,8 +98,6 @@ class ExtensionControllerTest(test.TestCase): data = json.loads(response.body) names = [x['name'] for x in data['extensions']] names.sort() - LOG.info("%%%%%%%%%%%%%%%%%%%%%%%%%") - LOG.info(names) self.assertEqual(names, ["Createserverext", "FlavorExtraSpecs", "Floating_ips", "Fox In Socks", "Hosts", "Multinic", "Volumes"]) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 418931989..759a3f9cb 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1125,11 +1125,6 @@ class ServersTest(test.TestCase): def project_get_networks(context, user_id): return dict(id='1', host='localhost') - def project_get_requested_networks(context, requested_networks): - return [{'id': 1, 'host': 'localhost1'}, - {'id': 2, 'host': 'localhost2'}, - ] - def queue_get_for(context, *args): return 'network_topic' @@ -1141,8 +1136,6 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.db.api, 'project_get_networks', project_get_networks) - self.stubs.Set(nova.db.api, 'project_get_requested_networks', - project_get_requested_networks) self.stubs.Set(nova.db.api, 'instance_create', instance_create) self.stubs.Set(nova.rpc, 'cast', fake_method) self.stubs.Set(nova.rpc, 'call', fake_method) @@ -1295,29 +1288,6 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 422) - def test_create_instance_whitespace_name(self): - self._setup_for_create_instance() - - body = { - 'server': { - 'name': ' ', - 'imageId': 3, - 'flavorId': 1, - 'metadata': { - 'hello': 'world', - 'open': 'stack', - }, - 'personality': {}, - }, - } - - req = webob.Request.blank('/v1.0/servers') - req.method = 'POST' - req.body = json.dumps(body) - req.headers["content-type"] = "application/json" - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 400) - def test_create_instance_v1_1(self): self._setup_for_create_instance() @@ -2844,6 +2814,181 @@ class TestServerCreateRequestXMLDeserializerV11(unittest.TestCase): } self.assertEquals(request['body'], expected) + def test_request_with_empty_networks(self): + serial_request = """ + + +""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageId": "1", + "flavorId": "1", + "metadata": {}, + "personality": [], + }} + self.assertEquals(request['body'], expected) + + def test_request_with_one_network(self): + serial_request = """ + + + + +""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageId": "1", + "flavorId": "1", + "personality": [], + "metadata": {}, + "networks": [{"id": "1", "fixed_ip": "10.0.1.12"}], + }} + self.assertEquals(request['body'], expected) + + def test_request_with_two_networks(self): + serial_request = """ + + + + + +""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageId": "1", + "flavorId": "1", + "metadata": {}, + "personality": [], + "networks": [{"id": "1", "fixed_ip": "10.0.1.12"}, + {"id": "2", "fixed_ip": "10.0.2.12"}], + }} + self.assertEquals(request['body'], expected) + + def test_request_with_second_network_node_ignored(self): + serial_request = """ + + + + + + + +""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageId": "1", + "flavorId": "1", + "metadata": {}, + "personality": [], + "networks": [{"id": "1", "fixed_ip": "10.0.1.12"}], + }} + self.assertEquals(request['body'], expected) + + def test_request_with_one_network_missing_id(self): + serial_request = """ + + + + +""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageId": "1", + "flavorId": "1", + "metadata": {}, + "personality": [], + "networks": [{"fixed_ip": "10.0.1.12"}], + }} + self.assertEquals(request['body'], expected) + + def test_request_with_one_network_missing_fixed_ip(self): + serial_request = """ + + + + +""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageId": "1", + "flavorId": "1", + "metadata": {}, + "personality": [], + "networks": [{"id": "1"}], + }} + self.assertEquals(request['body'], expected) + + def test_request_with_one_network_empty_id(self): + serial_request = """ + + + + + """ + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageId": "1", + "flavorId": "1", + "metadata": {}, + "personality": [], + "networks": [{"id": "", "fixed_ip": "10.0.1.12"}], + }} + self.assertEquals(request['body'], expected) + + def test_request_with_one_network_empty_fixed_ip(self): + serial_request = """ + + + + + """ + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageId": "1", + "flavorId": "1", + "metadata": {}, + "personality": [], + "networks": [{"id": "1", "fixed_ip": ""}], + }} + self.assertEquals(request['body'], expected) + + def test_request_with_networks_duplicate_ids(self): + serial_request = """ + + + + + + """ + request = self.deserializer.deserialize(serial_request, 'create') + expected = {"server": { + "name": "new-server-test", + "imageId": "1", + "flavorId": "1", + "metadata": {}, + "personality": [], + "networks": [{"id": "1", "fixed_ip": "10.0.1.12"}, + {"id": "1", "fixed_ip": "10.0.2.12"}], + }} + self.assertEquals(request['body'], expected) + class TextAddressesXMLSerialization(test.TestCase): @@ -2908,7 +3053,7 @@ class TestServerInstanceCreation(test.TestCase): fakes.stub_out_image_service(self.stubs) fakes.stub_out_key_pair_funcs(self.stubs) - def _setup_mock_compute_api(self): + def _setup_mock_compute_api_for_personality(self): class MockComputeAPI(nova.compute.API): @@ -2952,18 +3097,6 @@ class TestServerInstanceCreation(test.TestCase): server['personality'] = personalities return {'server': server} - def _create_networks_request_dict(self, networks): - server = {} - server['name'] = 'new-server-test' - server['imageId'] = 1 - server['flavorId'] = 1 - if networks is not None: - network_list = [] - for id, fixed_ip in networks: - network_list.append({'id': id, 'fixed_ip': fixed_ip}) - server['networks'] = network_list - return {'server': server} - def _get_create_request_json(self, body_dict): req = webob.Request.blank('/v1.0/servers') req.headers['Content-Type'] = 'application/json' @@ -2972,7 +3105,7 @@ class TestServerInstanceCreation(test.TestCase): return req def _run_create_instance_with_mock_compute_api(self, request): - compute_api = self._setup_mock_compute_api() + compute_api = self._setup_mock_compute_api_for_personality() response = request.get_response(fakes.wsgi_app()) return compute_api, response @@ -2997,14 +3130,6 @@ class TestServerInstanceCreation(test.TestCase): item = (file['path'], file['contents']) body_parts.append('%s' % item) body_parts.append('') - if 'networks' in server: - networks = server['networks'] - body_parts.append('') - for network in networks: - item = (network['id'], network['fixed_ip']) - body_parts.append('' - % item) - body_parts.append('') body_parts.append('') return ''.join(body_parts) -- cgit From aee7778549904dc89fbd792ee60924932621a720 Mon Sep 17 00:00:00 2001 From: Ryu Ishimoto Date: Fri, 5 Aug 2011 15:02:29 +0900 Subject: Added migration to add uuid to virtual interfaces. Added uuid column to models --- .../versions/037_add_uuid_to_virtual_interfaces.py | 44 ++++++++++++++++++++++ nova/db/sqlalchemy/models.py | 2 + 2 files changed, 46 insertions(+) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/037_add_uuid_to_virtual_interfaces.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/037_add_uuid_to_virtual_interfaces.py b/nova/db/sqlalchemy/migrate_repo/versions/037_add_uuid_to_virtual_interfaces.py new file mode 100644 index 000000000..0f542cbec --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/037_add_uuid_to_virtual_interfaces.py @@ -0,0 +1,44 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (C) 2011 Midokura KK +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from sqlalchemy import Column, Integer, MetaData, String, Table + +from nova import utils + + +meta = MetaData() + +virtual_interfaces = Table("virtual_interfaces", meta, + Column("id", Integer(), primary_key=True, + nullable=False)) +uuid_column = Column("uuid", String(36)) + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + virtual_interfaces.create_column(uuid_column) + + rows = migrate_engine.execute(virtual_interfaces.select()) + for row in rows: + vif_uuid = str(utils.gen_uuid()) + migrate_engine.execute(virtual_interfaces.update()\ + .where(virtual_interfaces.c.id == row[0])\ + .values(uuid=vif_uuid)) + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + virtual_interfaces.drop_column(uuid_column) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 9f4c7a0aa..3ab0a2b0c 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -565,6 +565,8 @@ class VirtualInterface(BASE, NovaBase): instance_id = Column(Integer, ForeignKey('instances.id'), nullable=False) instance = relationship(Instance, backref=backref('virtual_interfaces')) + uuid = Column(String(36)) + @property def fixed_ipv6(self): cidr_v6 = self.network.cidr_v6 -- cgit From 7407a1a86c4039bdc541e9a26cc68c9c93f49bc3 Mon Sep 17 00:00:00 2001 From: Ryu Ishimoto Date: Fri, 5 Aug 2011 18:29:32 +0900 Subject: Added virtual interfaces REST API extension controller --- nova/api/openstack/contrib/virtual_interfaces.py | 102 +++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 nova/api/openstack/contrib/virtual_interfaces.py diff --git a/nova/api/openstack/contrib/virtual_interfaces.py b/nova/api/openstack/contrib/virtual_interfaces.py new file mode 100644 index 000000000..3466d31c7 --- /dev/null +++ b/nova/api/openstack/contrib/virtual_interfaces.py @@ -0,0 +1,102 @@ +# Copyright (C) 2011 Midokura KK +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""The virtual interfaces extension.""" + +from webob import exc +import webob + +from nova import compute +from nova import exception +from nova import log as logging +from nova.api.openstack import common +from nova.api.openstack import extensions +from nova.api.openstack import faults + + +LOG = logging.getLogger("nova.api.virtual_interfaces") + + +def _translate_vif_summary_view(_context, vif): + """Maps keys for attachment summary view.""" + d = {} + d['id'] = vif['uuid'] + d['macAddress'] = vif['address'] + d['serverId'] = vif['instance_id'] + return d + + +class ServerVirtualInterfaceController(object): + """The instance VIF API controller for the Openstack API. + """ + + _serialization_metadata = { + 'application/xml': { + 'attributes': { + 'serverVirtualInterface': ['id', + 'macAddress']}}} + + def __init__(self): + self.compute_api = compute.API() + super(ServerVirtualInterfaceController, self).__init__() + + def _items(self, req, server_id, entity_maker): + """Returns a list of VIFs, transformed through entity_maker.""" + context = req.environ['nova.context'] + + try: + instance = self.compute_api.get(context, server_id) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + + vifs = instance['virtual_interfaces'] + limited_list = common.limited(vifs, req) + res = [entity_maker(context, vif) for vif in limited_list] + return {'serverVirtualInterfaces': res} + + def index(self, req, server_id): + """Returns the list of VIFs for a given instance.""" + return self._items(req, server_id, + entity_maker=_translate_vif_summary_view) + + +class VirtualInterfaces(extensions.ExtensionDescriptor): + + def get_name(self): + return "VirtualInterfaces" + + def get_alias(self): + return "os-virtual_interfaces" + + def get_description(self): + return "Virtual interface support" + + def get_namespace(self): + return "http://docs.openstack.org/ext/virtual_interfaces/api/v1.1" + + def get_updated(self): + return "2011-08-05T00:00:00+00:00" + + def get_resources(self): + resources = [] + + res = extensions.ResourceExtension('os-virtual_interfaces', + ServerVirtualInterfaceController(), + parent=dict( + member_name='server', + collection_name='servers')) + resources.append(res) + + return resources -- cgit From 4acc4a9757af6e68456aba1fea2b320b2311b971 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Fri, 5 Aug 2011 11:58:21 -0400 Subject: Pass tenant ids through on on requests --- nova/api/openstack/__init__.py | 17 ++++++++++++++++- nova/api/openstack/wsgi.py | 3 +++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 9ab8aeb58..9475f961c 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -65,6 +65,15 @@ class FaultWrapper(base_wsgi.Middleware): return faults.Fault(exc) +class TenantMapper(routes.Mapper): + + def resource(self, member_name, collection_name, **kwargs): + routes.Mapper.resource(self, member_name, + collection_name, + path_prefix='{tenant_id}/', + **kwargs) + + class APIRouter(base_wsgi.Router): """ Routes requests on the OpenStack API to the appropriate controller @@ -168,6 +177,12 @@ class APIRouterV10(APIRouter): class APIRouterV11(APIRouter): """Define routes specific to OpenStack API V1.1.""" + def __init__(self, ext_mgr=None): + mapper = TenantMapper() + self.server_members = {} + self._setup_routes(mapper) + super(APIRouter, self).__init__(mapper) + def _setup_routes(self, mapper): super(APIRouterV11, self)._setup_routes(mapper, '1.1') image_metadata_controller = image_metadata.create_resource() @@ -176,7 +191,7 @@ class APIRouterV11(APIRouter): parent_resource=dict(member_name='image', collection_name='images')) - mapper.connect("metadata", "/images/{image_id}/metadata", + mapper.connect("metadata", "{tenant_id}/images/{image_id}/metadata", controller=image_metadata_controller, action='update_all', conditions={"method": ['PUT']}) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 0eb47044e..7c22ed57a 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -486,6 +486,9 @@ class Resource(wsgi.Application): msg = _("Malformed request body") return faults.Fault(webob.exc.HTTPBadRequest(explanation=msg)) + #Remove tenant id + args.pop("tenant_id") + try: action_result = self.dispatch(request, action, args) except webob.exc.HTTPException as ex: -- cgit From fb0b82c0d6af2d67ec9a88842d857b558eaec5d1 Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Fri, 5 Aug 2011 15:00:31 -0700 Subject: Refactored code to reduce lines of code and changed method signature --- nova/db/api.py | 12 ++++++------ nova/db/sqlalchemy/api.py | 14 ++------------ nova/network/manager.py | 31 +++++++++++++++++-------------- nova/tests/test_network.py | 32 ++++++++++++++++---------------- 4 files changed, 41 insertions(+), 48 deletions(-) diff --git a/nova/db/api.py b/nova/db/api.py index b98859ef9..789e9bc97 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -710,9 +710,9 @@ def network_get_all(context): return IMPL.network_get_all(context) -def network_get_requested_networks(context, requested_networks): - """Return all defined networks.""" - return IMPL.network_get_requested_networks(context, requested_networks) +def network_get_networks_by_ids(context, network_ids): + """Return networks by ids.""" + return IMPL.network_get_networks_by_ids(context, network_ids) # pylint: disable=C0103 @@ -1262,14 +1262,14 @@ def project_get_networks(context, project_id, associate=True): return IMPL.project_get_networks(context, project_id, associate) -def project_get_requested_networks(context, requested_networks): - """Return the network associated with the project. +def project_get_networks_by_ids(context, network_ids): + """Return the networks by ids associated with the project. If associate is true, it will attempt to associate a new network if one is not found, otherwise it returns None. """ - return IMPL.project_get_requested_networks(context, requested_networks) + return IMPL.project_get_networks_by_ids(context, network_ids) def project_get_networks_v6(context, project_id): diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 5de720205..63964a193 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1701,13 +1701,8 @@ def network_get_all(context): @require_admin_context -def network_get_requested_networks(context, requested_networks): +def network_get_networks_by_ids(context, network_ids): session = get_session() - - network_ids = [] - for id, fixed_ip in requested_networks: - network_ids.append(id) - result = session.query(models.Network).\ filter(models.Network.id.in_(network_ids)).\ filter_by(deleted=False).all() @@ -2873,13 +2868,8 @@ def project_get_networks(context, project_id, associate=True): @require_context -def project_get_requested_networks(context, requested_networks): +def project_get_networks_by_ids(context, network_ids): session = get_session() - - network_ids = [] - for id, fixed_ip in requested_networks: - network_ids.append(id) - result = session.query(models.Network).\ filter(models.Network.id.in_(network_ids)).\ filter_by(deleted=False).\ diff --git a/nova/network/manager.py b/nova/network/manager.py index 655d5800a..b5c961e2d 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -134,10 +134,9 @@ class RPCAllocateFixedIP(object): for network in networks: address = None if requested_networks is not None: - for id, fixed_ip in requested_networks: - if (network['id'] == id): - address = fixed_ip - break + for address in (fixed_ip for (id, fixed_ip) in \ + requested_networks if network['id'] == id): + break # NOTE(vish): if we are not multi_host pass to the network host if not network['multi_host']: @@ -387,8 +386,9 @@ class NetworkManager(manager.SchedulerDependentManager): # there is a better way to determine which networks # a non-vlan instance should connect to if requested_networks is not None and len(requested_networks) != 0: - networks = self.db.network_get_requested_networks(context, - requested_networks) + network_ids = [id for (id, fixed_ip) in requested_networks] + networks = self.db.network_get_networks_by_ids(context, + network_ids) else: try: networks = self.db.network_get_all(context) @@ -743,7 +743,8 @@ class NetworkManager(manager.SchedulerDependentManager): if networks is None or len(networks) == 0: return - result = self.db.network_get_requested_networks(context, networks) + network_ids = [id for (id, fixed_ip) in networks] + result = self.db.network_get_networks_by_ids(context, network_ids) for network_id, fixed_ip in networks: # check if the fixed IP address is valid and # it actually belongs to the network @@ -793,10 +794,10 @@ class FlatManager(NetworkManager): for network in networks: address = None if requested_networks is not None: - for id, fixed_ip in requested_networks: - if (network['id'] == id): - address = fixed_ip - break + for address in (fixed_ip for (id, fixed_ip) in \ + requested_networks if network['id'] == id): + break + self.allocate_fixed_ip(context, instance_id, network, address=address) @@ -913,8 +914,9 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): """Determine which networks an instance should connect to.""" # get networks associated with project if requested_networks is not None and len(requested_networks) != 0: - networks = self.db.project_get_requested_networks(context, - requested_networks) + network_ids = [id for (id, fixed_ip) in requested_networks] + networks = self.db.project_get_networks_by_ids(context, + network_ids) else: networks = self.db.project_get_networks(context, project_id) return networks @@ -973,7 +975,8 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): if networks is None or len(networks) == 0: return - result = self.db.project_get_requested_networks(context, networks) + network_ids = [id for (id, fixed_ip) in networks] + result = self.db.project_get_networks_by_ids(context, network_ids) for network_id, fixed_ip in networks: # check if the fixed IP address is valid and diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index 53abbeb4f..9608121bb 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -181,11 +181,11 @@ class FlatNetworkTestCase(test.TestCase): self.assertDictListMatch(nw[1]['ips'], check) def test_validate_networks(self): - self.mox.StubOutWithMock(db, 'network_get_requested_networks') + self.mox.StubOutWithMock(db, 'network_get_networks_by_ids') self.mox.StubOutWithMock(db, "fixed_ip_validate_by_network_address") requested_networks = [(1, "192.168.0.100")] - db.network_get_requested_networks(mox.IgnoreArg(), + db.network_get_networks_by_ids(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(networks) db.fixed_ip_validate_by_network_address(mox.IgnoreArg(), mox.IgnoreArg(), @@ -204,9 +204,9 @@ class FlatNetworkTestCase(test.TestCase): self.network.validate_networks(None, requested_networks) def test_validate_networks_invalid_fixed_ip(self): - self.mox.StubOutWithMock(db, 'network_get_requested_networks') + self.mox.StubOutWithMock(db, 'network_get_networks_by_ids') requested_networks = [(1, "192.168.0.100.1")] - db.network_get_requested_networks(mox.IgnoreArg(), + db.network_get_networks_by_ids(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(networks) self.mox.ReplayAll() @@ -215,10 +215,10 @@ class FlatNetworkTestCase(test.TestCase): requested_networks) def test_validate_networks_empty_fixed_ip(self): - self.mox.StubOutWithMock(db, 'network_get_requested_networks') + self.mox.StubOutWithMock(db, 'network_get_networks_by_ids') requested_networks = [(1, "")] - db.network_get_requested_networks(mox.IgnoreArg(), + db.network_get_networks_by_ids(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(networks) self.mox.ReplayAll() @@ -227,10 +227,10 @@ class FlatNetworkTestCase(test.TestCase): None, requested_networks) def test_validate_networks_none_fixed_ip(self): - self.mox.StubOutWithMock(db, 'network_get_requested_networks') + self.mox.StubOutWithMock(db, 'network_get_networks_by_ids') requested_networks = [(1, None)] - db.network_get_requested_networks(mox.IgnoreArg(), + db.network_get_networks_by_ids(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(networks) self.mox.ReplayAll() @@ -294,11 +294,11 @@ class VlanNetworkTestCase(test.TestCase): cidr='192.168.0.1/24', network_size=100) def test_validate_networks(self): - self.mox.StubOutWithMock(db, 'project_get_requested_networks') + self.mox.StubOutWithMock(db, 'project_get_networks_by_ids') self.mox.StubOutWithMock(db, "fixed_ip_validate_by_network_address") requested_networks = [(1, "192.168.0.100")] - db.project_get_requested_networks(mox.IgnoreArg(), + db.project_get_networks_by_ids(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(networks) db.fixed_ip_validate_by_network_address(mox.IgnoreArg(), mox.IgnoreArg(), @@ -315,10 +315,10 @@ class VlanNetworkTestCase(test.TestCase): self.network.validate_networks(None, requested_networks) def test_validate_networks_invalid_fixed_ip(self): - self.mox.StubOutWithMock(db, 'project_get_requested_networks') + self.mox.StubOutWithMock(db, 'project_get_networks_by_ids') requested_networks = [(1, "192.168.0.100.1")] - db.project_get_requested_networks(mox.IgnoreArg(), + db.project_get_networks_by_ids(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(networks) self.mox.ReplayAll() @@ -327,10 +327,10 @@ class VlanNetworkTestCase(test.TestCase): None, requested_networks) def test_validate_networks_empty_fixed_ip(self): - self.mox.StubOutWithMock(db, 'project_get_requested_networks') + self.mox.StubOutWithMock(db, 'project_get_networks_by_ids') requested_networks = [(1, "")] - db.project_get_requested_networks(mox.IgnoreArg(), + db.project_get_networks_by_ids(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(networks) self.mox.ReplayAll() @@ -339,10 +339,10 @@ class VlanNetworkTestCase(test.TestCase): None, requested_networks) def test_validate_networks_none_fixed_ip(self): - self.mox.StubOutWithMock(db, 'project_get_requested_networks') + self.mox.StubOutWithMock(db, 'project_get_networks_by_ids') requested_networks = [(1, None)] - db.project_get_requested_networks(mox.IgnoreArg(), + db.project_get_networks_by_ids(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(networks) self.mox.ReplayAll() -- cgit From fe4c7ca6f21f367b3f6ca1a536fdcd550f301fba Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Mon, 8 Aug 2011 12:57:38 -0400 Subject: Assign tenant id in nova.context --- nova/api/openstack/wsgi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 7c22ed57a..bf5697b7e 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -486,8 +486,8 @@ class Resource(wsgi.Application): msg = _("Malformed request body") return faults.Fault(webob.exc.HTTPBadRequest(explanation=msg)) - #Remove tenant id - args.pop("tenant_id") + if 'tenant_id' in args: + request['nova.context']['tenant_id'] = args.pop("tenant_id") try: action_result = self.dispatch(request, action, args) -- cgit From b2b5131ac2ab532afb1a3e507992d60b15dd3855 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Mon, 8 Aug 2011 13:34:20 -0400 Subject: fixed wrong syntax --- nova/api/openstack/wsgi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index bf5697b7e..d834a62d8 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -487,7 +487,7 @@ class Resource(wsgi.Application): return faults.Fault(webob.exc.HTTPBadRequest(explanation=msg)) if 'tenant_id' in args: - request['nova.context']['tenant_id'] = args.pop("tenant_id") + request.environ['nova.context']['tenant_id'] = args.pop("tenant_id") try: action_result = self.dispatch(request, action, args) -- cgit From 6107aeceab2f3226bb1f3bff820cdcc2bc9be3cc Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Mon, 8 Aug 2011 13:50:31 -0400 Subject: Don't do anything with tenant_id for now --- nova/api/openstack/wsgi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index d834a62d8..34c31260b 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -487,7 +487,7 @@ class Resource(wsgi.Application): return faults.Fault(webob.exc.HTTPBadRequest(explanation=msg)) if 'tenant_id' in args: - request.environ['nova.context']['tenant_id'] = args.pop("tenant_id") + args.pop("tenant_id") try: action_result = self.dispatch(request, action, args) -- cgit From 21e4cc9c4e79e8e291ac845d9dc08153c09fbf02 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Mon, 8 Aug 2011 13:54:18 -0400 Subject: Updated test_images to use tenant ids --- nova/tests/api/openstack/test_images.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 942c0b333..cced0523d 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -391,7 +391,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): self.assertEqual(expected_image, actual_image) def test_get_image_v1_1(self): - request = webob.Request.blank('/v1.1/images/124') + request = webob.Request.blank('/v1.1/123/images/124') response = request.get_response(fakes.wsgi_app()) actual_image = json.loads(response.body) @@ -510,7 +510,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): self.assertEqual(expected.toxml(), actual.toxml()) def test_get_image_404_v1_1_json(self): - request = webob.Request.blank('/v1.1/images/NonExistantImage') + request = webob.Request.blank('/v1.1/123/images/NonExistantImage') response = request.get_response(fakes.wsgi_app()) self.assertEqual(404, response.status_int) @@ -526,7 +526,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): self.assertEqual(expected, actual) def test_get_image_404_v1_1_xml(self): - request = webob.Request.blank('/v1.1/images/NonExistantImage') + request = webob.Request.blank('/v1.1/123/images/NonExistantImage') request.accept = "application/xml" response = request.get_response(fakes.wsgi_app()) self.assertEqual(404, response.status_int) @@ -547,7 +547,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): self.assertEqual(expected.toxml(), actual.toxml()) def test_get_image_index_v1_1(self): - request = webob.Request.blank('/v1.1/images') + request = webob.Request.blank('/v1.1/123/images') response = request.get_response(fakes.wsgi_app()) response_dict = json.loads(response.body) @@ -634,7 +634,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): self.assertDictListMatch(expected, response_list) def test_get_image_details_v1_1(self): - request = webob.Request.blank('/v1.1/images/detail') + request = webob.Request.blank('/v1.1/123/images/detail') response = request.get_response(fakes.wsgi_app()) response_dict = json.loads(response.body) -- cgit From 59426d29116ed53dbbe4a060227a1e68fc49a178 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Mon, 8 Aug 2011 16:36:17 -0400 Subject: Updated servers tests to use tenant id --- nova/tests/api/openstack/test_servers.py | 94 ++++++++++++++++---------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 7062b0982..eec301c66 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -336,7 +336,7 @@ class ServersTest(test.TestCase): interfaces=interfaces) self.stubs.Set(nova.db.api, 'instance_get', new_return_server) - req = webob.Request.blank('/v1.1/servers/1') + req = webob.Request.blank('/v1.1/123/servers/1') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) expected_server = { @@ -428,7 +428,7 @@ class ServersTest(test.TestCase): interfaces=interfaces) self.stubs.Set(nova.db.api, 'instance_get', new_return_server) - req = webob.Request.blank('/v1.1/servers/1') + req = webob.Request.blank('/v1.1/123/servers/1') req.headers['Accept'] = 'application/xml' res = req.get_response(fakes.wsgi_app()) actual = minidom.parseString(res.body.replace(' ', '')) @@ -498,7 +498,7 @@ class ServersTest(test.TestCase): interfaces=interfaces, power_state=1) self.stubs.Set(nova.db.api, 'instance_get', new_return_server) - req = webob.Request.blank('/v1.1/servers/1') + req = webob.Request.blank('/v1.1/123/servers/1') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) expected_server = { @@ -589,7 +589,7 @@ class ServersTest(test.TestCase): flavor_id=flavor_id) self.stubs.Set(nova.db.api, 'instance_get', new_return_server) - req = webob.Request.blank('/v1.1/servers/1') + req = webob.Request.blank('/v1.1/123/servers/1') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) expected_server = { @@ -789,7 +789,7 @@ class ServersTest(test.TestCase): interfaces=interfaces) self.stubs.Set(nova.db.api, 'instance_get', new_return_server) - req = webob.Request.blank('/v1.1/servers/1') + req = webob.Request.blank('/v1.1/123/servers/1') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) @@ -833,7 +833,7 @@ class ServersTest(test.TestCase): interfaces=interfaces) self.stubs.Set(nova.db.api, 'instance_get', new_return_server) - req = webob.Request.blank('/v1.1/servers/1') + req = webob.Request.blank('/v1.1/123/servers/1') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) @@ -883,7 +883,7 @@ class ServersTest(test.TestCase): 'virtual_interface_get_by_instance', _return_vifs) - req = webob.Request.blank('/v1.1/servers/1/ips') + req = webob.Request.blank('/v1.1/123/servers/1/ips') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) @@ -933,7 +933,7 @@ class ServersTest(test.TestCase): 'virtual_interface_get_by_instance', _return_vifs) - req = webob.Request.blank('/v1.1/servers/1/ips/network_2') + req = webob.Request.blank('/v1.1/123/servers/1/ips/network_2') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) res_dict = json.loads(res.body) @@ -953,7 +953,7 @@ class ServersTest(test.TestCase): 'virtual_interface_get_by_instance', _return_vifs) - req = webob.Request.blank('/v1.1/servers/1/ips/network_0') + req = webob.Request.blank('/v1.1/123/servers/1/ips/network_0') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 404) @@ -963,7 +963,7 @@ class ServersTest(test.TestCase): 'virtual_interface_get_by_instance', _return_vifs) - req = webob.Request.blank('/v1.1/servers/600/ips') + req = webob.Request.blank('/v1.1/123/servers/600/ips') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 404) @@ -1032,7 +1032,7 @@ class ServersTest(test.TestCase): i += 1 def test_get_server_list_v1_1(self): - req = webob.Request.blank('/v1.1/servers') + req = webob.Request.blank('/v1.1/123/servers') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) @@ -1096,19 +1096,19 @@ class ServersTest(test.TestCase): self.assertTrue(res.body.find('offset param') > -1) def test_get_servers_with_marker(self): - req = webob.Request.blank('/v1.1/servers?marker=2') + req = webob.Request.blank('/v1.1/123/servers?marker=2') res = req.get_response(fakes.wsgi_app()) servers = json.loads(res.body)['servers'] self.assertEqual([s['name'] for s in servers], ["server3", "server4"]) def test_get_servers_with_limit_and_marker(self): - req = webob.Request.blank('/v1.1/servers?limit=2&marker=1') + req = webob.Request.blank('/v1.1/123/servers?limit=2&marker=1') res = req.get_response(fakes.wsgi_app()) servers = json.loads(res.body)['servers'] self.assertEqual([s['name'] for s in servers], ['server2', 'server3']) def test_get_servers_with_bad_marker(self): - req = webob.Request.blank('/v1.1/servers?limit=2&marker=asdf') + req = webob.Request.blank('/v1.1/123/servers?limit=2&marker=asdf') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) self.assertTrue(res.body.find('marker param') > -1) @@ -1364,7 +1364,7 @@ class ServersTest(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers') + req = webob.Request.blank('/v1.1/123/servers') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -1389,7 +1389,7 @@ class ServersTest(test.TestCase): name='server_test', imageRef=image_href, flavorRef=flavor_ref, metadata={'hello': 'world', 'open': 'stack'}, personality={})) - req = webob.Request.blank('/v1.1/servers') + req = webob.Request.blank('/v1.1/123/servers') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -1405,7 +1405,7 @@ class ServersTest(test.TestCase): name='server_test', imageRef=image_href, flavorRef=flavor_ref, metadata={'hello': 'world', 'open': 'stack'}, personality={})) - req = webob.Request.blank('/v1.1/servers') + req = webob.Request.blank('/v1.1/123/servers') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -1421,7 +1421,7 @@ class ServersTest(test.TestCase): name='server_test', imageRef=image_href, flavorRef=flavor_ref, metadata={'hello': 'world', 'open': 'stack'}, personality={})) - req = webob.Request.blank('/v1.1/servers') + req = webob.Request.blank('/v1.1/123/servers') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -1459,7 +1459,7 @@ class ServersTest(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers') + req = webob.Request.blank('/v1.1/123/servers') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -1506,7 +1506,7 @@ class ServersTest(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers') + req = webob.Request.blank('/v1.1/123/servers') req.method = 'POST' req.body = json.dumps(body) req.headers['content-type'] = "application/json" @@ -1527,7 +1527,7 @@ class ServersTest(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers') + req = webob.Request.blank('/v1.1/123/servers') req.method = 'POST' req.body = json.dumps(body) req.headers['content-type'] = "application/json" @@ -1601,13 +1601,13 @@ class ServersTest(test.TestCase): self.assertEqual(mock_method.password, 'bacon') def test_update_server_no_body_v1_1(self): - req = webob.Request.blank('/v1.0/servers/1') + req = webob.Request.blank('/v1.1/123/servers/1') req.method = 'PUT' res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) def test_update_server_name_v1_1(self): - req = webob.Request.blank('/v1.1/servers/1') + req = webob.Request.blank('/v1.1/123/servers/1') req.method = 'PUT' req.content_type = 'application/json' req.body = json.dumps({'server': {'name': 'new-name'}}) @@ -1627,7 +1627,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.db.api, 'instance_update', server_update) - req = webob.Request.blank('/v1.1/servers/1') + req = webob.Request.blank('/v1.1/123/servers/1') req.method = 'PUT' req.content_type = "application/json" req.body = self.body @@ -1658,7 +1658,7 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 501) def test_server_backup_schedule_deprecated_v1_1(self): - req = webob.Request.blank('/v1.1/servers/1/backup_schedule') + req = webob.Request.blank('/v1.1/123/servers/1/backup_schedule') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 404) @@ -1710,7 +1710,7 @@ class ServersTest(test.TestCase): }, ], } - req = webob.Request.blank('/v1.1/servers/detail') + req = webob.Request.blank('/v1.1/123/servers/detail') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) @@ -1836,7 +1836,7 @@ class ServersTest(test.TestCase): mock_method = MockSetAdminPassword() self.stubs.Set(nova.compute.api.API, 'set_admin_password', mock_method) body = {'changePassword': {'adminPass': '1234pass'}} - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -1847,7 +1847,7 @@ class ServersTest(test.TestCase): def test_server_change_password_bad_request_v1_1(self): body = {'changePassword': {'pass': '12345'}} - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -1856,7 +1856,7 @@ class ServersTest(test.TestCase): def test_server_change_password_empty_string_v1_1(self): body = {'changePassword': {'adminPass': ''}} - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -1865,7 +1865,7 @@ class ServersTest(test.TestCase): def test_server_change_password_none_v1_1(self): body = {'changePassword': {'adminPass': None}} - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -1874,7 +1874,7 @@ class ServersTest(test.TestCase): def test_server_change_password_not_a_string_v1_1(self): body = {'changePassword': {'adminPass': 1234}} - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -1949,7 +1949,7 @@ class ServersTest(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -1970,7 +1970,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.db, 'instance_get_by_uuid', return_server_with_uuid_and_power_state(state)) - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -1988,7 +1988,7 @@ class ServersTest(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -2004,7 +2004,7 @@ class ServersTest(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -2019,7 +2019,7 @@ class ServersTest(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -2038,7 +2038,7 @@ class ServersTest(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -2057,7 +2057,7 @@ class ServersTest(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -2117,7 +2117,7 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 422) def test_delete_server_instance_v1_1(self): - req = webob.Request.blank('/v1.1/servers/1') + req = webob.Request.blank('/v1.1/123/servers/1') req.method = 'DELETE' self.server_delete_called = False @@ -2147,7 +2147,7 @@ class ServersTest(test.TestCase): self.assertEqual(self.resize_called, True) def test_resize_server_v11(self): - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.content_type = 'application/json' req.method = 'POST' body_dict = { @@ -2193,7 +2193,7 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 400) def test_resize_invalid_flavorid_v1_1(self): - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.content_type = 'application/json' req.method = 'POST' resize_body = { @@ -2208,7 +2208,7 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 400) def test_resize_nonint_flavorid_v1_1(self): - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.content_type = 'application/json' req.method = 'POST' resize_body = { @@ -2349,7 +2349,7 @@ class ServersTest(test.TestCase): 'name': 'Snapshot 1', }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -2365,7 +2365,7 @@ class ServersTest(test.TestCase): 'metadata': {'key': 'asdf'}, }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -2378,7 +2378,7 @@ class ServersTest(test.TestCase): body = { 'createImage': {}, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -2392,7 +2392,7 @@ class ServersTest(test.TestCase): 'metadata': 'henry', }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -2431,7 +2431,7 @@ class ServersTest(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" -- cgit From d4d2227cd396455c881f2ed36008578b2d4a7720 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 9 Aug 2011 10:29:56 -0400 Subject: Updated TenantMapper to handle resources with parent resources --- nova/api/openstack/__init__.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 9475f961c..3e241cd91 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -68,9 +68,16 @@ class FaultWrapper(base_wsgi.Middleware): class TenantMapper(routes.Mapper): def resource(self, member_name, collection_name, **kwargs): + if not ('parent_resource' in kwargs): + kwargs['path_prefix'] = '{tenant_id}/' + else: + parent_resource = kwargs['parent_resource'] + p_collection = parent_resource['collection_name'] + p_member = parent_resource['member_name'] + kwargs['path_prefix'] = '{tenant_id}/%s/:%s_id' % (p_collection, + p_member) routes.Mapper.resource(self, member_name, collection_name, - path_prefix='{tenant_id}/', **kwargs) -- cgit From 8ae2a4cdebc080ba4def0ed07e3e1587f9db9bce Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 9 Aug 2011 11:24:27 -0400 Subject: updated v1.1 flavors tests to use tenant id --- nova/tests/api/openstack/test_flavors.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nova/tests/api/openstack/test_flavors.py b/nova/tests/api/openstack/test_flavors.py index d0fe72001..15c552e72 100644 --- a/nova/tests/api/openstack/test_flavors.py +++ b/nova/tests/api/openstack/test_flavors.py @@ -138,7 +138,7 @@ class FlavorsTest(test.TestCase): self.assertEqual(res.status_int, 404) def test_get_flavor_by_id_v1_1(self): - req = webob.Request.blank('/v1.1/flavors/12') + req = webob.Request.blank('/v1.1/123/flavors/12') req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) @@ -164,7 +164,7 @@ class FlavorsTest(test.TestCase): self.assertEqual(flavor, expected) def test_get_flavor_list_v1_1(self): - req = webob.Request.blank('/v1.1/flavors') + req = webob.Request.blank('/v1.1/123/flavors') req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) @@ -204,7 +204,7 @@ class FlavorsTest(test.TestCase): self.assertEqual(flavor, expected) def test_get_flavor_list_detail_v1_1(self): - req = webob.Request.blank('/v1.1/flavors/detail') + req = webob.Request.blank('/v1.1/123/flavors/detail') req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) @@ -252,7 +252,7 @@ class FlavorsTest(test.TestCase): return {} self.stubs.Set(nova.db.api, "instance_type_get_all", _return_empty) - req = webob.Request.blank('/v1.1/flavors') + req = webob.Request.blank('/v1.1/123/flavors') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) flavors = json.loads(res.body)["flavors"] -- cgit From 05cbe3032dfdfb4229718ead981c982864118f15 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Tue, 9 Aug 2011 12:51:42 -0400 Subject: Updated extensions to expect tenant ids Updated extensions tests to use tenant ids --- nova/api/openstack/extensions.py | 5 +++-- nova/tests/api/openstack/extensions/foxinsocks.py | 10 ++++++---- nova/tests/api/openstack/test_extensions.py | 17 ++++++++++------- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index cc889703e..f81879e3b 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -219,12 +219,13 @@ class ExtensionMiddleware(base_wsgi.Middleware): for action in ext_mgr.get_actions(): if not action.collection in action_resources.keys(): resource = ActionExtensionResource(application) - mapper.connect("/%s/:(id)/action.:(format)" % + mapper.connect("/:(tenant_id)/%s/:(id)/action.:(format)" % action.collection, action='action', controller=resource, conditions=dict(method=['POST'])) - mapper.connect("/%s/:(id)/action" % action.collection, + mapper.connect("/:(tenant_id)/%s/:(id)/action" % + action.collection, action='action', controller=resource, conditions=dict(method=['POST'])) diff --git a/nova/tests/api/openstack/extensions/foxinsocks.py b/nova/tests/api/openstack/extensions/foxinsocks.py index 03aad007a..294bae619 100644 --- a/nova/tests/api/openstack/extensions/foxinsocks.py +++ b/nova/tests/api/openstack/extensions/foxinsocks.py @@ -72,8 +72,9 @@ class Foxinsocks(object): res.body = json.dumps(data) return res - req_ext1 = extensions.RequestExtension('GET', '/v1.1/flavors/:(id)', - _goose_handler) + req_ext1 = extensions.RequestExtension('GET', + '/v1.1/:(tenant_id)/flavors/:(id)', + _goose_handler) request_exts.append(req_ext1) def _bands_handler(req, res): @@ -84,8 +85,9 @@ class Foxinsocks(object): res.body = json.dumps(data) return res - req_ext2 = extensions.RequestExtension('GET', '/v1.1/flavors/:(id)', - _bands_handler) + req_ext2 = extensions.RequestExtension('GET', + '/v1.1/:(tenant_id)/flavors/:(id)', + _bands_handler) request_exts.append(req_ext2) return request_exts diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 47c37225c..bab031d48 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -264,23 +264,26 @@ class ActionExtensionTest(test.TestCase): def test_extended_action(self): body = dict(add_tweedle=dict(name="test")) - response = self._send_server_action_request("/servers/1/action", body) + url = "/123/servers/1/action" + response = self._send_server_action_request(url, body) self.assertEqual(200, response.status_int) self.assertEqual("Tweedle Beetle Added.", response.body) body = dict(delete_tweedle=dict(name="test")) - response = self._send_server_action_request("/servers/1/action", body) + response = self._send_server_action_request(url, body) self.assertEqual(200, response.status_int) self.assertEqual("Tweedle Beetle Deleted.", response.body) def test_invalid_action_body(self): body = dict(blah=dict(name="test")) # Doesn't exist - response = self._send_server_action_request("/servers/1/action", body) + url = "/123/servers/1/action" + response = self._send_server_action_request(url, body) self.assertEqual(501, response.status_int) def test_invalid_action(self): body = dict(blah=dict(name="test")) - response = self._send_server_action_request("/fdsa/1/action", body) + url = "/123/fdsa/1/action" + response = self._send_server_action_request(url, body) self.assertEqual(404, response.status_int) @@ -301,13 +304,13 @@ class RequestExtensionTest(test.TestCase): return res req_ext = extensions.RequestExtension('GET', - '/v1.1/flavors/:(id)', + '/v1.1/123/flavors/:(id)', _req_handler) manager = StubExtensionManager(None, None, req_ext) app = fakes.wsgi_app() ext_midware = extensions.ExtensionMiddleware(app, manager) - request = webob.Request.blank("/v1.1/flavors/1?chewing=bluegoo") + request = webob.Request.blank("/v1.1/123/flavors/1?chewing=bluegoo") request.environ['api.version'] = '1.1' response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) @@ -318,7 +321,7 @@ class RequestExtensionTest(test.TestCase): app = fakes.wsgi_app() ext_midware = extensions.ExtensionMiddleware(app) - request = webob.Request.blank("/v1.1/flavors/1?chewing=newblue") + request = webob.Request.blank("/v1.1/123/flavors/1?chewing=newblue") request.environ['api.version'] = '1.1' response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) -- cgit From a8a5b27a577f8e007e2cc79570f97ae075fda767 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Tue, 9 Aug 2011 19:26:35 -0400 Subject: adding project_id to flavor, server, and image links for /servers requests --- nova/api/openstack/servers.py | 8 +-- nova/api/openstack/views/flavors.py | 15 +++--- nova/api/openstack/views/images.py | 16 ++++-- nova/api/openstack/views/servers.py | 8 +-- .../api/openstack/contrib/test_multinic_xs.py | 8 +-- nova/tests/api/openstack/test_image_metadata.py | 28 +++++------ nova/tests/api/openstack/test_server_actions.py | 58 +++++++++++----------- nova/tests/api/openstack/test_server_metadata.py | 56 ++++++++++----------- nova/tests/api/openstack/test_servers.py | 56 ++++++++++----------- nova/tests/integrated/api/client.py | 32 ++++++++---- nova/tests/integrated/test_extensions.py | 2 +- 11 files changed, 156 insertions(+), 131 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 4f34d63c9..127962ce2 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -596,14 +596,16 @@ class ControllerV11(Controller): return common.get_id_from_href(flavor_ref) def _build_view(self, req, instance, is_detail=False): + project_id = req.environ['nova.context'].project_id base_url = req.application_url flavor_builder = nova.api.openstack.views.flavors.ViewBuilderV11( - base_url) + base_url, project_id) image_builder = nova.api.openstack.views.images.ViewBuilderV11( - base_url) + base_url, project_id) addresses_builder = nova.api.openstack.views.addresses.ViewBuilderV11() builder = nova.api.openstack.views.servers.ViewBuilderV11( - addresses_builder, flavor_builder, image_builder, base_url) + addresses_builder, flavor_builder, image_builder, + base_url, project_id) return builder.build(instance, is_detail=is_detail) diff --git a/nova/api/openstack/views/flavors.py b/nova/api/openstack/views/flavors.py index 0403ece1b..aea34b424 100644 --- a/nova/api/openstack/views/flavors.py +++ b/nova/api/openstack/views/flavors.py @@ -15,6 +15,9 @@ # License for the specific language governing permissions and limitations # under the License. +import os.path + + from nova.api.openstack import common @@ -59,11 +62,12 @@ class ViewBuilder(object): class ViewBuilderV11(ViewBuilder): """Openstack API v1.1 flavors view builder.""" - def __init__(self, base_url): + def __init__(self, base_url, project_id=""): """ :param base_url: url of the root wsgi application """ self.base_url = base_url + self.project_id = project_id def _build_extra(self, flavor_obj): flavor_obj["links"] = self._build_links(flavor_obj) @@ -88,11 +92,10 @@ class ViewBuilderV11(ViewBuilder): def generate_href(self, flavor_id): """Create an url that refers to a specific flavor id.""" - return "%s/flavors/%s" % (self.base_url, flavor_id) + return os.path.join(self.base_url, self.project_id, + "flavors", str(flavor_id)) def generate_bookmark(self, flavor_id): """Create an url that refers to a specific flavor id.""" - return "%s/flavors/%s" % ( - common.remove_version_from_href(self.base_url), - flavor_id, - ) + return os.path.join(common.remove_version_from_href(self.base_url), + self.project_id, "flavors", str(flavor_id)) diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index 912303d14..21f1b2d3e 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -23,9 +23,10 @@ from nova.api.openstack import common class ViewBuilder(object): """Base class for generating responses to OpenStack API image requests.""" - def __init__(self, base_url): + def __init__(self, base_url, project_id=""): """Initialize new `ViewBuilder`.""" - self._url = base_url + self.base_url = base_url + self.project_id = project_id def _format_dates(self, image): """Update all date fields to ensure standardized formatting.""" @@ -54,7 +55,7 @@ class ViewBuilder(object): def generate_href(self, image_id): """Return an href string pointing to this object.""" - return os.path.join(self._url, "images", str(image_id)) + return os.path.join(self.base_url, "images", str(image_id)) def build(self, image_obj, detail=False): """Return a standardized image structure for display by the API.""" @@ -117,6 +118,11 @@ class ViewBuilderV11(ViewBuilder): except KeyError: return + def generate_href(self, image_id): + """Return an href string pointing to this object.""" + return os.path.join(self.base_url, self.project_id, + "images", str(image_id)) + def build(self, image_obj, detail=False): """Return a standardized image structure for display by the API.""" image = ViewBuilder.build(self, image_obj, detail) @@ -142,5 +148,5 @@ class ViewBuilderV11(ViewBuilder): def generate_bookmark(self, image_id): """Create an url that refers to a specific flavor id.""" - return os.path.join(common.remove_version_from_href(self._url), - "images", str(image_id)) + return os.path.join(common.remove_version_from_href(self.base_url), + self.project_id, "images", str(image_id)) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 2873a8e0f..18c1a9057 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -142,11 +142,12 @@ class ViewBuilderV10(ViewBuilder): class ViewBuilderV11(ViewBuilder): """Model an Openstack API V1.0 server response.""" def __init__(self, addresses_builder, flavor_builder, image_builder, - base_url): + base_url, project_id=""): ViewBuilder.__init__(self, addresses_builder) self.flavor_builder = flavor_builder self.image_builder = image_builder self.base_url = base_url + self.project_id = project_id def _build_detail(self, inst): response = super(ViewBuilderV11, self)._build_detail(inst) @@ -216,9 +217,10 @@ class ViewBuilderV11(ViewBuilder): def generate_href(self, server_id): """Create an url that refers to a specific server id.""" - return os.path.join(self.base_url, "servers", str(server_id)) + return os.path.join(self.base_url, self.project_id, + "servers", str(server_id)) def generate_bookmark(self, server_id): """Create an url that refers to a specific flavor id.""" return os.path.join(common.remove_version_from_href(self.base_url), - "servers", str(server_id)) + self.project_id, "servers", str(server_id)) diff --git a/nova/tests/api/openstack/contrib/test_multinic_xs.py b/nova/tests/api/openstack/contrib/test_multinic_xs.py index ac28f6be6..f659852e8 100644 --- a/nova/tests/api/openstack/contrib/test_multinic_xs.py +++ b/nova/tests/api/openstack/contrib/test_multinic_xs.py @@ -55,7 +55,7 @@ class FixedIpTest(test.TestCase): last_add_fixed_ip = (None, None) body = dict(addFixedIp=dict(networkId='test_net')) - req = webob.Request.blank('/v1.1/servers/test_inst/action') + req = webob.Request.blank('/v1.1/fake/servers/test_inst/action') req.method = 'POST' req.body = json.dumps(body) req.headers['content-type'] = 'application/json' @@ -69,7 +69,7 @@ class FixedIpTest(test.TestCase): last_add_fixed_ip = (None, None) body = dict(addFixedIp=dict()) - req = webob.Request.blank('/v1.1/servers/test_inst/action') + req = webob.Request.blank('/v1.1/fake/servers/test_inst/action') req.method = 'POST' req.body = json.dumps(body) req.headers['content-type'] = 'application/json' @@ -83,7 +83,7 @@ class FixedIpTest(test.TestCase): last_remove_fixed_ip = (None, None) body = dict(removeFixedIp=dict(address='10.10.10.1')) - req = webob.Request.blank('/v1.1/servers/test_inst/action') + req = webob.Request.blank('/v1.1/fake/servers/test_inst/action') req.method = 'POST' req.body = json.dumps(body) req.headers['content-type'] = 'application/json' @@ -97,7 +97,7 @@ class FixedIpTest(test.TestCase): last_remove_fixed_ip = (None, None) body = dict(removeFixedIp=dict()) - req = webob.Request.blank('/v1.1/servers/test_inst/action') + req = webob.Request.blank('/v1.1/fake/servers/test_inst/action') req.method = 'POST' req.body = json.dumps(body) req.headers['content-type'] = 'application/json' diff --git a/nova/tests/api/openstack/test_image_metadata.py b/nova/tests/api/openstack/test_image_metadata.py index 56a0932e7..6670e0929 100644 --- a/nova/tests/api/openstack/test_image_metadata.py +++ b/nova/tests/api/openstack/test_image_metadata.py @@ -90,7 +90,7 @@ class ImageMetaDataTest(test.TestCase): fakes.stub_out_glance(self.stubs, self.IMAGE_FIXTURES) def test_index(self): - req = webob.Request.blank('/v1.1/images/1/metadata') + req = webob.Request.blank('/v1.1/123/images/1/metadata') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) @@ -100,7 +100,7 @@ class ImageMetaDataTest(test.TestCase): self.assertEqual(value, res_dict['metadata'][key]) def test_show(self): - req = webob.Request.blank('/v1.1/images/1/metadata/key1') + req = webob.Request.blank('/v1.1/fake/images/1/metadata/key1') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) @@ -109,12 +109,12 @@ class ImageMetaDataTest(test.TestCase): self.assertEqual('value1', res_dict['meta']['key1']) def test_show_not_found(self): - req = webob.Request.blank('/v1.1/images/1/metadata/key9') + req = webob.Request.blank('/v1.1/fake/images/1/metadata/key9') res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) def test_create(self): - req = webob.Request.blank('/v1.1/images/2/metadata') + req = webob.Request.blank('/v1.1/fake/images/2/metadata') req.method = 'POST' req.body = '{"metadata": {"key9": "value9"}}' req.headers["content-type"] = "application/json" @@ -134,7 +134,7 @@ class ImageMetaDataTest(test.TestCase): self.assertEqual(expected_output, actual_output) def test_update_all(self): - req = webob.Request.blank('/v1.1/images/2/metadata') + req = webob.Request.blank('/v1.1/fake/images/1/metadata') req.method = 'PUT' req.body = '{"metadata": {"key9": "value9"}}' req.headers["content-type"] = "application/json" @@ -152,7 +152,7 @@ class ImageMetaDataTest(test.TestCase): self.assertEqual(expected_output, actual_output) def test_update_item(self): - req = webob.Request.blank('/v1.1/images/1/metadata/key1') + req = webob.Request.blank('/v1.1/fake/images/1/metadata/key1') req.method = 'PUT' req.body = '{"meta": {"key1": "zz"}}' req.headers["content-type"] = "application/json" @@ -168,7 +168,7 @@ class ImageMetaDataTest(test.TestCase): self.assertEqual(actual_output, expected_output) def test_update_item_bad_body(self): - req = webob.Request.blank('/v1.1/images/1/metadata/key1') + req = webob.Request.blank('/v1.1/fake/images/1/metadata/key1') req.method = 'PUT' req.body = '{"key1": "zz"}' req.headers["content-type"] = "application/json" @@ -176,7 +176,7 @@ class ImageMetaDataTest(test.TestCase): self.assertEqual(400, res.status_int) def test_update_item_too_many_keys(self): - req = webob.Request.blank('/v1.1/images/1/metadata/key1') + req = webob.Request.blank('/v1.1/fake/images/1/metadata/key1') req.method = 'PUT' req.body = '{"meta": {"key1": "value1", "key2": "value2"}}' req.headers["content-type"] = "application/json" @@ -184,7 +184,7 @@ class ImageMetaDataTest(test.TestCase): self.assertEqual(400, res.status_int) def test_update_item_body_uri_mismatch(self): - req = webob.Request.blank('/v1.1/images/1/metadata/bad') + req = webob.Request.blank('/v1.1/fake/images/1/metadata/bad') req.method = 'PUT' req.body = '{"meta": {"key1": "value1"}}' req.headers["content-type"] = "application/json" @@ -192,7 +192,7 @@ class ImageMetaDataTest(test.TestCase): self.assertEqual(400, res.status_int) def test_update_item_xml(self): - req = webob.Request.blank('/v1.1/images/1/metadata/key1') + req = webob.Request.blank('/v1.1/fake/images/1/metadata/key1') req.method = 'PUT' req.body = 'five' req.headers["content-type"] = "application/xml" @@ -208,14 +208,14 @@ class ImageMetaDataTest(test.TestCase): self.assertEqual(actual_output, expected_output) def test_delete(self): - req = webob.Request.blank('/v1.1/images/2/metadata/key1') + req = webob.Request.blank('/v1.1/fake/images/2/metadata/key1') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) self.assertEqual(204, res.status_int) self.assertEqual('', res.body) def test_delete_not_found(self): - req = webob.Request.blank('/v1.1/images/2/metadata/blah') + req = webob.Request.blank('/v1.1/fake/images/2/metadata/blah') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) @@ -225,7 +225,7 @@ class ImageMetaDataTest(test.TestCase): for num in range(FLAGS.quota_metadata_items + 1): data['metadata']['key%i' % num] = "blah" json_string = str(data).replace("\'", "\"") - req = webob.Request.blank('/v1.1/images/2/metadata') + req = webob.Request.blank('/v1.1/fake/images/2/metadata') req.method = 'POST' req.body = json_string req.headers["content-type"] = "application/json" @@ -233,7 +233,7 @@ class ImageMetaDataTest(test.TestCase): self.assertEqual(400, res.status_int) def test_too_many_metadata_items_on_put(self): - req = webob.Request.blank('/v1.1/images/3/metadata/blah') + req = webob.Request.blank('/v1.1/fake/images/3/metadata/blah') req.method = 'PUT' req.body = '{"meta": {"blah": "blah"}}' req.headers["content-type"] = "application/json" diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index 717e11c00..e8c8c2b8d 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -491,7 +491,7 @@ class ServerActionsTestV11(test.TestCase): mock_method = MockSetAdminPassword() self.stubs.Set(nova.compute.api.API, 'set_admin_password', mock_method) body = {'changePassword': {'adminPass': '1234pass'}} - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -503,7 +503,7 @@ class ServerActionsTestV11(test.TestCase): def test_server_change_password_xml(self): mock_method = MockSetAdminPassword() self.stubs.Set(nova.compute.api.API, 'set_admin_password', mock_method) - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = "application/xml" req.body = """ @@ -517,7 +517,7 @@ class ServerActionsTestV11(test.TestCase): def test_server_change_password_not_a_string(self): body = {'changePassword': {'adminPass': 1234}} - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -526,7 +526,7 @@ class ServerActionsTestV11(test.TestCase): def test_server_change_password_bad_request(self): body = {'changePassword': {'pass': '12345'}} - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -535,7 +535,7 @@ class ServerActionsTestV11(test.TestCase): def test_server_change_password_empty_string(self): body = {'changePassword': {'adminPass': ''}} - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -544,7 +544,7 @@ class ServerActionsTestV11(test.TestCase): def test_server_change_password_none(self): body = {'changePassword': {'adminPass': None}} - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -553,7 +553,7 @@ class ServerActionsTestV11(test.TestCase): def test_server_reboot_hard(self): body = dict(reboot=dict(type="HARD")) - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -562,7 +562,7 @@ class ServerActionsTestV11(test.TestCase): def test_server_reboot_soft(self): body = dict(reboot=dict(type="SOFT")) - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -571,7 +571,7 @@ class ServerActionsTestV11(test.TestCase): def test_server_reboot_incorrect_type(self): body = dict(reboot=dict(type="NOT_A_TYPE")) - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -580,7 +580,7 @@ class ServerActionsTestV11(test.TestCase): def test_server_reboot_missing_type(self): body = dict(reboot=dict()) - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -594,7 +594,7 @@ class ServerActionsTestV11(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -615,7 +615,7 @@ class ServerActionsTestV11(test.TestCase): self.stubs.Set(nova.db, 'instance_get_by_uuid', return_server_with_uuid_and_power_state(state)) - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -633,7 +633,7 @@ class ServerActionsTestV11(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -649,7 +649,7 @@ class ServerActionsTestV11(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -664,7 +664,7 @@ class ServerActionsTestV11(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -683,7 +683,7 @@ class ServerActionsTestV11(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -702,7 +702,7 @@ class ServerActionsTestV11(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -712,7 +712,7 @@ class ServerActionsTestV11(test.TestCase): def test_resize_server(self): - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.content_type = 'application/json' req.method = 'POST' body_dict = dict(resize=dict(flavorRef="http://localhost/3")) @@ -730,7 +730,7 @@ class ServerActionsTestV11(test.TestCase): self.assertEqual(self.resize_called, True) def test_resize_server_no_flavor(self): - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.content_type = 'application/json' req.method = 'POST' body_dict = dict(resize=dict()) @@ -740,7 +740,7 @@ class ServerActionsTestV11(test.TestCase): self.assertEqual(res.status_int, 400) def test_resize_server_no_flavor_ref(self): - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.content_type = 'application/json' req.method = 'POST' body_dict = dict(resize=dict(flavorRef=None)) @@ -750,7 +750,7 @@ class ServerActionsTestV11(test.TestCase): self.assertEqual(res.status_int, 400) def test_confirm_resize_server(self): - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.content_type = 'application/json' req.method = 'POST' body_dict = dict(confirmResize=None) @@ -768,7 +768,7 @@ class ServerActionsTestV11(test.TestCase): self.assertEqual(self.confirm_resize_called, True) def test_revert_resize_server(self): - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.content_type = 'application/json' req.method = 'POST' body_dict = dict(revertResize=None) @@ -791,7 +791,7 @@ class ServerActionsTestV11(test.TestCase): 'name': 'Snapshot 1', }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -810,7 +810,7 @@ class ServerActionsTestV11(test.TestCase): 'name': 'Snapshot 1', }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -824,7 +824,7 @@ class ServerActionsTestV11(test.TestCase): 'metadata': {'key': 'asdf'}, }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -842,7 +842,7 @@ class ServerActionsTestV11(test.TestCase): } for num in range(FLAGS.quota_metadata_items + 1): body['createImage']['metadata']['foo%i' % num] = "bar" - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -853,7 +853,7 @@ class ServerActionsTestV11(test.TestCase): body = { 'createImage': {}, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -867,7 +867,7 @@ class ServerActionsTestV11(test.TestCase): 'metadata': 'henry', }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -886,7 +886,7 @@ class ServerActionsTestV11(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" diff --git a/nova/tests/api/openstack/test_server_metadata.py b/nova/tests/api/openstack/test_server_metadata.py index ec446f0f0..d5d47f295 100644 --- a/nova/tests/api/openstack/test_server_metadata.py +++ b/nova/tests/api/openstack/test_server_metadata.py @@ -83,7 +83,7 @@ class ServerMetaDataTest(test.TestCase): def test_index(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', return_server_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata') res = req.get_response(fakes.wsgi_app()) self.assertEqual(200, res.status_int) res_dict = json.loads(res.body) @@ -100,7 +100,7 @@ class ServerMetaDataTest(test.TestCase): def test_index_xml(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', return_server_metadata) - request = webob.Request.blank("/v1.1/servers/1/metadata") + request = webob.Request.blank("/v1.1/fake/servers/1/metadata") request.accept = "application/xml" response = request.get_response(fakes.wsgi_app()) self.assertEqual(200, response.status_int) @@ -120,14 +120,14 @@ class ServerMetaDataTest(test.TestCase): def test_index_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) - req = webob.Request.blank('/v1.1/servers/1/metadata') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata') res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) def test_index_no_data(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', return_empty_server_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata') res = req.get_response(fakes.wsgi_app()) self.assertEqual(200, res.status_int) res_dict = json.loads(res.body) @@ -137,7 +137,7 @@ class ServerMetaDataTest(test.TestCase): def test_show(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', return_server_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata/key2') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata/key2') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) @@ -147,7 +147,7 @@ class ServerMetaDataTest(test.TestCase): def test_show_xml(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', return_server_metadata) - request = webob.Request.blank("/v1.1/servers/1/metadata/key2") + request = webob.Request.blank("/v1.1/fake/servers/1/metadata/key2") request.accept = "application/xml" response = request.get_response(fakes.wsgi_app()) self.assertEqual(200, response.status_int) @@ -164,14 +164,14 @@ class ServerMetaDataTest(test.TestCase): def test_show_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) - req = webob.Request.blank('/v1.1/servers/1/metadata/key2') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata/key2') res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) def test_show_meta_not_found(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', return_empty_server_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata/key6') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata/key6') res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) @@ -180,7 +180,7 @@ class ServerMetaDataTest(test.TestCase): return_server_metadata) self.stubs.Set(nova.db.api, 'instance_metadata_delete', delete_server_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata/key2') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata/key2') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) self.assertEqual(204, res.status_int) @@ -188,7 +188,7 @@ class ServerMetaDataTest(test.TestCase): def test_delete_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) - req = webob.Request.blank('/v1.1/servers/1/metadata/key1') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata/key1') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) @@ -196,7 +196,7 @@ class ServerMetaDataTest(test.TestCase): def test_delete_meta_not_found(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', return_empty_server_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata/key6') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata/key6') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) @@ -206,7 +206,7 @@ class ServerMetaDataTest(test.TestCase): return_server_metadata) self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata') req.method = 'POST' req.content_type = "application/json" input = {"metadata": {"key9": "value9"}} @@ -227,7 +227,7 @@ class ServerMetaDataTest(test.TestCase): return_server_metadata) self.stubs.Set(nova.db.api, "instance_metadata_update", return_create_instance_metadata) - req = webob.Request.blank("/v1.1/servers/1/metadata") + req = webob.Request.blank("/v1.1/fake/servers/1/metadata") req.method = "POST" req.content_type = "application/xml" req.accept = "application/xml" @@ -258,7 +258,7 @@ class ServerMetaDataTest(test.TestCase): def test_create_empty_body(self): self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata') req.method = 'POST' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) @@ -266,7 +266,7 @@ class ServerMetaDataTest(test.TestCase): def test_create_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) - req = webob.Request.blank('/v1.1/servers/100/metadata') + req = webob.Request.blank('/v1.1/fake/servers/100/metadata') req.method = 'POST' req.body = '{"metadata": {"key1": "value1"}}' req.headers["content-type"] = "application/json" @@ -276,7 +276,7 @@ class ServerMetaDataTest(test.TestCase): def test_update_all(self): self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata') req.method = 'PUT' req.content_type = "application/json" expected = { @@ -294,7 +294,7 @@ class ServerMetaDataTest(test.TestCase): def test_update_all_empty_container(self): self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata') req.method = 'PUT' req.content_type = "application/json" expected = {'metadata': {}} @@ -307,7 +307,7 @@ class ServerMetaDataTest(test.TestCase): def test_update_all_malformed_container(self): self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata') req.method = 'PUT' req.content_type = "application/json" expected = {'meta': {}} @@ -318,7 +318,7 @@ class ServerMetaDataTest(test.TestCase): def test_update_all_malformed_data(self): self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata') req.method = 'PUT' req.content_type = "application/json" expected = {'metadata': ['asdf']} @@ -328,7 +328,7 @@ class ServerMetaDataTest(test.TestCase): def test_update_all_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) - req = webob.Request.blank('/v1.1/servers/100/metadata') + req = webob.Request.blank('/v1.1/fake/servers/100/metadata') req.method = 'PUT' req.content_type = "application/json" req.body = json.dumps({'metadata': {'key10': 'value10'}}) @@ -338,7 +338,7 @@ class ServerMetaDataTest(test.TestCase): def test_update_item(self): self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata/key1') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata/key1') req.method = 'PUT' req.body = '{"meta": {"key1": "value1"}}' req.headers["content-type"] = "application/json" @@ -352,7 +352,7 @@ class ServerMetaDataTest(test.TestCase): def test_update_item_xml(self): self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata/key9') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata/key9') req.method = 'PUT' req.accept = "application/json" req.content_type = "application/xml" @@ -369,7 +369,7 @@ class ServerMetaDataTest(test.TestCase): def test_update_item_nonexistant_server(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_nonexistant) - req = webob.Request.blank('/v1.1/servers/asdf/metadata/key1') + req = webob.Request.blank('/v1.1/fake/servers/asdf/metadata/key1') req.method = 'PUT' req.body = '{"meta":{"key1": "value1"}}' req.headers["content-type"] = "application/json" @@ -379,7 +379,7 @@ class ServerMetaDataTest(test.TestCase): def test_update_item_empty_body(self): self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata/key1') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata/key1') req.method = 'PUT' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) @@ -388,7 +388,7 @@ class ServerMetaDataTest(test.TestCase): def test_update_item_too_many_keys(self): self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata/key1') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata/key1') req.method = 'PUT' req.body = '{"meta": {"key1": "value1", "key2": "value2"}}' req.headers["content-type"] = "application/json" @@ -398,7 +398,7 @@ class ServerMetaDataTest(test.TestCase): def test_update_item_body_uri_mismatch(self): self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata) - req = webob.Request.blank('/v1.1/servers/1/metadata/bad') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata/bad') req.method = 'PUT' req.body = '{"meta": {"key1": "value1"}}' req.headers["content-type"] = "application/json" @@ -412,7 +412,7 @@ class ServerMetaDataTest(test.TestCase): for num in range(FLAGS.quota_metadata_items + 1): data['metadata']['key%i' % num] = "blah" json_string = str(data).replace("\'", "\"") - req = webob.Request.blank('/v1.1/servers/1/metadata') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata') req.method = 'POST' req.body = json_string req.headers["content-type"] = "application/json" @@ -422,7 +422,7 @@ class ServerMetaDataTest(test.TestCase): def test_to_many_metadata_items_on_update_item(self): self.stubs.Set(nova.db.api, 'instance_metadata_update', return_create_instance_metadata_max) - req = webob.Request.blank('/v1.1/servers/1/metadata/key1') + req = webob.Request.blank('/v1.1/fake/servers/1/metadata/key1') req.method = 'PUT' req.body = '{"meta": {"a new key": "a new value"}}' req.headers["content-type"] = "application/json" diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 974b2a390..d2ef30f6e 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -296,10 +296,10 @@ class ServersTest(test.TestCase): self.assertEqual(res_dict['server']['name'], 'server1') def test_get_server_by_id_v1_1(self): - image_bookmark = "http://localhost/images/10" - flavor_ref = "http://localhost/v1.1/flavors/1" + image_bookmark = "http://localhost/fake/images/10" + flavor_ref = "http://localhost/v1.1/fake/flavors/1" flavor_id = "1" - flavor_bookmark = "http://localhost/flavors/1" + flavor_bookmark = "http://localhost/fake/flavors/1" public_ip = '192.168.0.3' private_ip = '172.19.0.1' @@ -373,11 +373,11 @@ class ServersTest(test.TestCase): { "rel": "self", #FIXME(wwolf) Do we want the links to be id or uuid? - "href": "http://localhost/v1.1/servers/1", + "href": "http://localhost/v1.1/fake/servers/1", }, { "rel": "bookmark", - "href": "http://localhost/servers/1", + "href": "http://localhost/fake/servers/1", }, ], } @@ -386,12 +386,12 @@ class ServersTest(test.TestCase): self.assertDictMatch(res_dict, expected_server) def test_get_server_by_id_v1_1_xml(self): - image_bookmark = "http://localhost/images/10" - flavor_ref = "http://localhost/v1.1/flavors/1" + image_bookmark = "http://localhost/fake/images/10" + flavor_ref = "http://localhost/v1.1/fake/flavors/1" flavor_id = "1" - flavor_bookmark = "http://localhost/flavors/1" - server_href = "http://localhost/v1.1/servers/1" - server_bookmark = "http://localhost/servers/1" + flavor_bookmark = "http://localhost/fake/flavors/1" + server_href = "http://localhost/v1.1/fake/servers/1" + server_bookmark = "http://localhost/fake/servers/1" public_ip = '192.168.0.3' private_ip = '172.19.0.1' @@ -458,10 +458,10 @@ class ServersTest(test.TestCase): self.assertEqual(expected.toxml(), actual.toxml()) def test_get_server_with_active_status_by_id_v1_1(self): - image_bookmark = "http://localhost/images/10" - flavor_ref = "http://localhost/v1.1/flavors/1" + image_bookmark = "http://localhost/fake/images/10" + flavor_ref = "http://localhost/v1.1/fake/flavors/1" flavor_id = "1" - flavor_bookmark = "http://localhost/flavors/1" + flavor_bookmark = "http://localhost/fake/flavors/1" private_ip = "192.168.0.3" public_ip = "1.2.3.4" @@ -534,11 +534,11 @@ class ServersTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/servers/1", + "href": "http://localhost/v1.1/fake/servers/1", }, { "rel": "bookmark", - "href": "http://localhost/servers/1", + "href": "http://localhost/fake/servers/1", }, ], } @@ -548,10 +548,10 @@ class ServersTest(test.TestCase): def test_get_server_with_id_image_ref_by_id_v1_1(self): image_ref = "10" - image_bookmark = "http://localhost/images/10" - flavor_ref = "http://localhost/v1.1/flavors/1" + image_bookmark = "http://localhost/fake/images/10" + flavor_ref = "http://localhost/v1.1/fake/flavors/1" flavor_id = "1" - flavor_bookmark = "http://localhost/flavors/1" + flavor_bookmark = "http://localhost/fake/flavors/1" private_ip = "192.168.0.3" public_ip = "1.2.3.4" @@ -625,11 +625,11 @@ class ServersTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/servers/1", + "href": "http://localhost/v1.1/fake/servers/1", }, { "rel": "bookmark", - "href": "http://localhost/servers/1", + "href": "http://localhost/fake/servers/1", }, ], } @@ -1030,11 +1030,11 @@ class ServersTest(test.TestCase): expected_links = [ { "rel": "self", - "href": "http://localhost/v1.1/servers/%s" % s['id'], + "href": "http://localhost/v1.1/fake/servers/%s" % s['id'], }, { "rel": "bookmark", - "href": "http://localhost/servers/%s" % s['id'], + "href": "http://localhost/fake/servers/%s" % s['id'], }, ] @@ -1318,7 +1318,7 @@ class ServersTest(test.TestCase): "links": [ { "rel": "bookmark", - "href": 'http://localhost/flavors/3', + "href": 'http://localhost/fake/flavors/3', }, ], } @@ -1327,7 +1327,7 @@ class ServersTest(test.TestCase): "links": [ { "rel": "bookmark", - "href": 'http://localhost/images/2', + "href": 'http://localhost/fake/images/2', }, ], } @@ -1423,7 +1423,7 @@ class ServersTest(test.TestCase): "links": [ { "rel": "bookmark", - "href": 'http://localhost/flavors/3', + "href": 'http://localhost/fake/flavors/3', }, ], } @@ -1432,7 +1432,7 @@ class ServersTest(test.TestCase): "links": [ { "rel": "bookmark", - "href": 'http://localhost/images/2', + "href": 'http://localhost/fake/images/2', }, ], } @@ -1682,7 +1682,7 @@ class ServersTest(test.TestCase): "links": [ { "rel": "bookmark", - "href": 'http://localhost/flavors/1', + "href": 'http://localhost/fake/flavors/1', }, ], } @@ -1691,7 +1691,7 @@ class ServersTest(test.TestCase): "links": [ { "rel": "bookmark", - "href": 'http://localhost/images/10', + "href": 'http://localhost/fake/images/10', }, ], } diff --git a/nova/tests/integrated/api/client.py b/nova/tests/integrated/api/client.py index 035a35aab..983f7cf7a 100644 --- a/nova/tests/integrated/api/client.py +++ b/nova/tests/integrated/api/client.py @@ -122,12 +122,18 @@ class TestOpenStackClient(object): self.auth_result = auth_headers return self.auth_result - def api_request(self, relative_uri, check_response_status=None, **kwargs): + def api_request(self, relative_uri, check_response_status=None, + use_project_id=True, **kwargs): auth_result = self._authenticate() # NOTE(justinsb): httplib 'helpfully' converts headers to lower case base_uri = auth_result['x-server-management-url'] - full_uri = base_uri + relative_uri + + if use_project_id: + # /fake is the project_id + full_uri = base_uri + '/fake' + relative_uri + else: + full_uri = base_uri + relative_uri headers = kwargs.setdefault('headers', {}) headers['X-Auth-Token'] = auth_result['x-auth-token'] @@ -234,30 +240,36 @@ class TestOpenStackClient(object): return self.api_delete('/flavors/%s' % flavor_id) def get_volume(self, volume_id): - return self.api_get('/os-volumes/%s' % volume_id)['volume'] + return self.api_get('/os-volumes/%s' % volume_id, + use_project_id=False)['volume'] def get_volumes(self, detail=True): rel_url = '/os-volumes/detail' if detail else '/os-volumes' - return self.api_get(rel_url)['volumes'] + return self.api_get(rel_url, use_project_id=False)['volumes'] def post_volume(self, volume): - return self.api_post('/os-volumes', volume)['volume'] + return self.api_post('/os-volumes', volume, + use_project_id=False)['volume'] def delete_volume(self, volume_id): - return self.api_delete('/os-volumes/%s' % volume_id) + return self.api_delete('/os-volumes/%s' % volume_id, + use_project_id=False) def get_server_volume(self, server_id, attachment_id): return self.api_get('/servers/%s/os-volume_attachments/%s' % - (server_id, attachment_id))['volumeAttachment'] + (server_id, attachment_id), use_project_id=False + )['volumeAttachment'] def get_server_volumes(self, server_id): return self.api_get('/servers/%s/os-volume_attachments' % - (server_id))['volumeAttachments'] + (server_id), use_project_id=False + )['volumeAttachments'] def post_server_volume(self, server_id, volume_attachment): return self.api_post('/servers/%s/os-volume_attachments' % - (server_id), volume_attachment)['volumeAttachment'] + (server_id), volume_attachment, + use_project_id=False)['volumeAttachment'] def delete_server_volume(self, server_id, attachment_id): return self.api_delete('/servers/%s/os-volume_attachments/%s' % - (server_id, attachment_id)) + (server_id, attachment_id), use_project_id=False) diff --git a/nova/tests/integrated/test_extensions.py b/nova/tests/integrated/test_extensions.py index c22cf0be0..02a7001e3 100644 --- a/nova/tests/integrated/test_extensions.py +++ b/nova/tests/integrated/test_extensions.py @@ -33,7 +33,7 @@ class ExtensionsTest(integrated_helpers._IntegratedTestBase): def test_get_foxnsocks(self): """Simple check that fox-n-socks works.""" - response = self.api.api_request('/foxnsocks') + response = self.api.api_request('/foxnsocks', use_project_id=False) foxnsocks = response.read() LOG.debug("foxnsocks: %s" % foxnsocks) self.assertEqual('Try to say this Mr. Knox, sir...', foxnsocks) -- cgit From e68ace1d6f7cb6db842aae69faa89cb4679016e7 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 10 Aug 2011 01:44:15 -0400 Subject: added project_id for images requests --- nova/api/openstack/images.py | 3 +- nova/tests/api/openstack/test_images.py | 92 ++++++++++++++++++--------------- 2 files changed, 52 insertions(+), 43 deletions(-) diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 0aabb9e56..ea4209e16 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -169,7 +169,8 @@ class ControllerV11(Controller): def get_builder(self, request): """Property to get the ViewBuilder class we need to use.""" base_url = request.application_url - return images_view.ViewBuilderV11(base_url) + project_id = request.environ['nova.context'].project_id + return images_view.ViewBuilderV11(base_url, project_id) def index(self, req): """Return an index listing of images available to the request. diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 5e979aba4..882b0aafd 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -339,6 +339,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): self.stubs.UnsetAll() super(ImageControllerWithGlanceServiceTest, self).tearDown() + def _get_fake_context(self): + class Context(object): + project_id = 'fake' + return Context() + def _applicable_fixture(self, fixture, user_id): """Determine if this fixture is applicable for given user id.""" is_public = fixture["is_public"] @@ -389,10 +394,12 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): request = webob.Request.blank('/v1.1/123/images/124') response = request.get_response(fakes.wsgi_app()) + print response.body + actual_image = json.loads(response.body) - href = "http://localhost/v1.1/images/124" - bookmark = "http://localhost/images/124" + href = "http://localhost/v1.1/fake/images/124" + bookmark = "http://localhost/fake/images/124" server_href = "http://localhost/v1.1/servers/42" server_bookmark = "http://localhost/servers/42" @@ -558,8 +565,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): fixtures.remove(image) continue - href = "http://localhost/v1.1/images/%s" % image["id"] - bookmark = "http://localhost/images/%s" % image["id"] + href = "http://localhost/v1.1/fake/images/%s" % image["id"] + bookmark = "http://localhost/fake/images/%s" % image["id"] test_image = { "id": image["id"], "name": image["name"], @@ -655,11 +662,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'progress': 100, "links": [{ "rel": "self", - "href": "http://localhost/v1.1/images/123", + "href": "http://localhost/v1.1/fake/images/123", }, { "rel": "bookmark", - "href": "http://localhost/images/123", + "href": "http://localhost/fake/images/123", }], }, { @@ -686,11 +693,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): }, "links": [{ "rel": "self", - "href": "http://localhost/v1.1/images/124", + "href": "http://localhost/v1.1/fake/images/124", }, { "rel": "bookmark", - "href": "http://localhost/images/124", + "href": "http://localhost/fake/images/124", }], }, { @@ -717,11 +724,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): }, "links": [{ "rel": "self", - "href": "http://localhost/v1.1/images/125", + "href": "http://localhost/v1.1/fake/images/125", }, { "rel": "bookmark", - "href": "http://localhost/images/125", + "href": "http://localhost/fake/images/125", }], }, { @@ -748,11 +755,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): }, "links": [{ "rel": "self", - "href": "http://localhost/v1.1/images/126", + "href": "http://localhost/v1.1/fake/images/126", }, { "rel": "bookmark", - "href": "http://localhost/images/126", + "href": "http://localhost/fake/images/126", }], }, { @@ -779,11 +786,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): }, "links": [{ "rel": "self", - "href": "http://localhost/v1.1/images/127", + "href": "http://localhost/v1.1/fake/images/127", }, { "rel": "bookmark", - "href": "http://localhost/images/127", + "href": "http://localhost/fake/images/127", }], }, { @@ -796,11 +803,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'progress': 100, "links": [{ "rel": "self", - "href": "http://localhost/v1.1/images/129", + "href": "http://localhost/v1.1/fake/images/129", }, { "rel": "bookmark", - "href": "http://localhost/images/129", + "href": "http://localhost/fake/images/129", }], }, ] @@ -809,7 +816,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_filter_with_name(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {'name': 'testname'} image_service.index(context, filters=filters).AndReturn([]) self.mox.ReplayAll() @@ -821,7 +828,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_filter_with_status(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {'status': 'ACTIVE'} image_service.index(context, filters=filters).AndReturn([]) self.mox.ReplayAll() @@ -833,7 +840,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_filter_with_property(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {'property-test': '3'} image_service.index(context, filters=filters).AndReturn([]) self.mox.ReplayAll() @@ -845,7 +852,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_filter_server(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() # 'server' should be converted to 'property-instance_ref' filters = {'property-instance_ref': 'http://localhost:8774/servers/12'} image_service.index(context, filters=filters).AndReturn([]) @@ -859,7 +866,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_filter_changes_since(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {'changes-since': '2011-01-24T17:08Z'} image_service.index(context, filters=filters).AndReturn([]) self.mox.ReplayAll() @@ -872,7 +879,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_filter_with_type(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {'property-image_type': 'BASE'} image_service.index(context, filters=filters).AndReturn([]) self.mox.ReplayAll() @@ -884,7 +891,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_filter_not_supported(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {'status': 'ACTIVE'} image_service.detail(context, filters=filters).AndReturn([]) self.mox.ReplayAll() @@ -897,7 +904,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_no_filters(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {} image_service.index( context, filters=filters).AndReturn([]) @@ -911,11 +918,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_detail_filter_with_name(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {'name': 'testname'} image_service.detail(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - request = webob.Request.blank('/v1.1/images/detail?name=testname') + request = webob.Request.blank('/v1.1/123/images/detail?name=testname') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) @@ -923,11 +930,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_detail_filter_with_status(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {'status': 'ACTIVE'} image_service.detail(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - request = webob.Request.blank('/v1.1/images/detail?status=ACTIVE') + request = webob.Request.blank('/v1.1/123/images/detail?status=ACTIVE') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) @@ -935,11 +942,12 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_detail_filter_with_property(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {'property-test': '3'} image_service.detail(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - request = webob.Request.blank('/v1.1/images/detail?property-test=3') + request = webob.Request.blank( + '/v1.1/123/images/detail?property-test=3') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) @@ -947,12 +955,12 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_detail_filter_server(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() # 'server' should be converted to 'property-instance_ref' filters = {'property-instance_ref': 'http://localhost:8774/servers/12'} image_service.index(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - request = webob.Request.blank('/v1.1/images/detail?server=' + request = webob.Request.blank('/v1.1/123/images/detail?server=' 'http://localhost:8774/servers/12') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) @@ -961,11 +969,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_detail_filter_changes_since(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {'changes-since': '2011-01-24T17:08Z'} image_service.index(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - request = webob.Request.blank('/v1.1/images/detail?changes-since=' + request = webob.Request.blank('/v1.1/123/images/detail?changes-since=' '2011-01-24T17:08Z') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) @@ -974,11 +982,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_detail_filter_with_type(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {'property-image_type': 'BASE'} image_service.index(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - request = webob.Request.blank('/v1.1/images/detail?type=BASE') + request = webob.Request.blank('/v1.1/123/images/detail?type=BASE') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) @@ -986,11 +994,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_detail_filter_not_supported(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {'status': 'ACTIVE'} image_service.detail(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - request = webob.Request.blank('/v1.1/images/detail?status=ACTIVE&' + request = webob.Request.blank('/v1.1/123/images/detail?status=ACTIVE&' 'UNSUPPORTEDFILTER=testname') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) @@ -999,11 +1007,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_image_detail_no_filters(self): image_service = self.mox.CreateMockAnything() - context = object() + context = self._get_fake_context() filters = {} image_service.detail(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - request = webob.Request.blank('/v1.1/images/detail') + request = webob.Request.blank('/v1.1/123/images/detail') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) @@ -1123,8 +1131,8 @@ class ImageXMLSerializationTest(test.TestCase): TIMESTAMP = "2010-10-11T10:30:22Z" SERVER_HREF = 'http://localhost/v1.1/servers/123' SERVER_BOOKMARK = 'http://localhost/servers/123' - IMAGE_HREF = 'http://localhost/v1.1/images/%s' - IMAGE_BOOKMARK = 'http://localhost/images/%s' + IMAGE_HREF = 'http://localhost/v1.1/fake/images/%s' + IMAGE_BOOKMARK = 'http://localhost/fake/images/%s' def test_show(self): serializer = images.ImageXMLSerializer() -- cgit From 434801e22bbfe2d8e74e18773c109ee657b22616 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 10 Aug 2011 02:01:03 -0400 Subject: added project_id for flavors requests links --- nova/api/openstack/flavors.py | 3 +- nova/api/openstack/images.py | 6 +-- nova/api/openstack/servers.py | 2 +- nova/tests/api/openstack/test_flavors.py | 80 ++++++++++++++++++-------------- 4 files changed, 52 insertions(+), 39 deletions(-) diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index b4bda68d4..fd36060da 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -72,7 +72,8 @@ class ControllerV11(Controller): def _get_view_builder(self, req): base_url = req.application_url - return views.flavors.ViewBuilderV11(base_url) + project_id = getattr(req.environ['nova.context'], 'project_id', '') + return views.flavors.ViewBuilderV11(base_url, project_id) class FlavorXMLSerializer(wsgi.XMLDictSerializer): diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index ea4209e16..1c8fc10c9 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -166,10 +166,10 @@ class ControllerV10(Controller): class ControllerV11(Controller): """Version 1.1 specific controller logic.""" - def get_builder(self, request): + def get_builder(self, req): """Property to get the ViewBuilder class we need to use.""" - base_url = request.application_url - project_id = request.environ['nova.context'].project_id + base_url = req.application_url + project_id = getattr(req.environ['nova.context'], 'project_id', '') return images_view.ViewBuilderV11(base_url, project_id) def index(self, req): diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index a516173d0..45d7dc214 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -639,7 +639,7 @@ class ControllerV11(Controller): return common.get_id_from_href(flavor_ref) def _build_view(self, req, instance, is_detail=False): - project_id = req.environ['nova.context'].project_id + project_id = getattr(req.environ['nova.context'], 'project_id', '') base_url = req.application_url flavor_builder = nova.api.openstack.views.flavors.ViewBuilderV11( base_url, project_id) diff --git a/nova/tests/api/openstack/test_flavors.py b/nova/tests/api/openstack/test_flavors.py index 15c552e72..2db69f4e3 100644 --- a/nova/tests/api/openstack/test_flavors.py +++ b/nova/tests/api/openstack/test_flavors.py @@ -152,11 +152,11 @@ class FlavorsTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/12", + "href": "http://localhost/v1.1/fake/flavors/12", }, { "rel": "bookmark", - "href": "http://localhost/flavors/12", + "href": "http://localhost/fake/flavors/12", }, ], }, @@ -177,11 +177,11 @@ class FlavorsTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/1", + "href": "http://localhost/v1.1/fake/flavors/1", }, { "rel": "bookmark", - "href": "http://localhost/flavors/1", + "href": "http://localhost/fake/flavors/1", }, ], }, @@ -191,11 +191,11 @@ class FlavorsTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/2", + "href": "http://localhost/v1.1/fake/flavors/2", }, { "rel": "bookmark", - "href": "http://localhost/flavors/2", + "href": "http://localhost/fake/flavors/2", }, ], }, @@ -219,11 +219,11 @@ class FlavorsTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/1", + "href": "http://localhost/v1.1/fake/flavors/1", }, { "rel": "bookmark", - "href": "http://localhost/flavors/1", + "href": "http://localhost/fake/flavors/1", }, ], }, @@ -235,11 +235,11 @@ class FlavorsTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/2", + "href": "http://localhost/v1.1/fake/flavors/2", }, { "rel": "bookmark", - "href": "http://localhost/flavors/2", + "href": "http://localhost/fake/flavors/2", }, ], }, @@ -274,11 +274,11 @@ class FlavorsXMLSerializationTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/12", + "href": "http://localhost/v1.1/fake/flavors/12", }, { "rel": "bookmark", - "href": "http://localhost/flavors/12", + "href": "http://localhost/fake/flavors/12", }, ], }, @@ -294,8 +294,10 @@ class FlavorsXMLSerializationTest(test.TestCase): name="asdf" ram="256" disk="10"> - - + + """.replace(" ", "")) @@ -313,11 +315,11 @@ class FlavorsXMLSerializationTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/12", + "href": "http://localhost/v1.1/fake/flavors/12", }, { "rel": "bookmark", - "href": "http://localhost/flavors/12", + "href": "http://localhost/fake/flavors/12", }, ], }, @@ -333,8 +335,10 @@ class FlavorsXMLSerializationTest(test.TestCase): name="asdf" ram="256" disk="10"> - - + + """.replace(" ", "")) @@ -353,11 +357,11 @@ class FlavorsXMLSerializationTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/23", + "href": "http://localhost/v1.1/fake/flavors/23", }, { "rel": "bookmark", - "href": "http://localhost/flavors/23", + "href": "http://localhost/fake/flavors/23", }, ], }, { @@ -368,11 +372,11 @@ class FlavorsXMLSerializationTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/13", + "href": "http://localhost/v1.1/fake/flavors/13", }, { "rel": "bookmark", - "href": "http://localhost/flavors/13", + "href": "http://localhost/fake/flavors/13", }, ], }, @@ -389,15 +393,19 @@ class FlavorsXMLSerializationTest(test.TestCase): name="flavor 23" ram="512" disk="20"> - - + + - - + + """.replace(" ", "") % locals()) @@ -417,11 +425,11 @@ class FlavorsXMLSerializationTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/23", + "href": "http://localhost/v1.1/fake/flavors/23", }, { "rel": "bookmark", - "href": "http://localhost/flavors/23", + "href": "http://localhost/fake/flavors/23", }, ], }, { @@ -432,11 +440,11 @@ class FlavorsXMLSerializationTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/flavors/13", + "href": "http://localhost/v1.1/fake/flavors/13", }, { "rel": "bookmark", - "href": "http://localhost/flavors/13", + "href": "http://localhost/fake/flavors/13", }, ], }, @@ -450,12 +458,16 @@ class FlavorsXMLSerializationTest(test.TestCase): - - + + - - + + """.replace(" ", "") % locals()) -- cgit From 9459fededca405881ed2c555b9a19c7bdcfcb7ff Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 10 Aug 2011 02:13:59 -0400 Subject: fix servers test issues and add a test --- nova/tests/api/openstack/test_servers.py | 52 +++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index c744b9c04..e794b40f2 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1120,7 +1120,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) - req = webob.Request.blank('/v1.1/servers?unknownoption=whee') + req = webob.Request.blank('/v1.1/123/servers?unknownoption=whee') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) servers = json.loads(res.body)['servers'] @@ -1137,7 +1137,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) self.flags(allow_admin_api=False) - req = webob.Request.blank('/v1.1/servers?image=12345') + req = webob.Request.blank('/v1.1/123/servers?image=12345') res = req.get_response(fakes.wsgi_app()) # The following assert will fail if either of the asserts in # fake_get_all() fail @@ -1157,7 +1157,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) self.flags(allow_admin_api=False) - req = webob.Request.blank('/v1.1/servers?flavor=12345') + req = webob.Request.blank('/v1.1/123/servers?flavor=12345') res = req.get_response(fakes.wsgi_app()) # The following assert will fail if either of the asserts in # fake_get_all() fail @@ -1177,7 +1177,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) self.flags(allow_admin_api=False) - req = webob.Request.blank('/v1.1/servers?status=active') + req = webob.Request.blank('/v1.1/123/servers?status=active') res = req.get_response(fakes.wsgi_app()) # The following assert will fail if either of the asserts in # fake_get_all() fail @@ -1191,7 +1191,7 @@ class ServersTest(test.TestCase): self.flags(allow_admin_api=False) - req = webob.Request.blank('/v1.1/servers?status=running') + req = webob.Request.blank('/v1.1/123/servers?status=running') res = req.get_response(fakes.wsgi_app()) # The following assert will fail if either of the asserts in # fake_get_all() fail @@ -1208,7 +1208,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) self.flags(allow_admin_api=False) - req = webob.Request.blank('/v1.1/servers?name=whee.*') + req = webob.Request.blank('/v1.1/123/servers?name=whee.*') res = req.get_response(fakes.wsgi_app()) # The following assert will fail if either of the asserts in # fake_get_all() fail @@ -1239,7 +1239,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) query_str = "name=foo&ip=10.*&status=active&unknown_option=meow" - req = webob.Request.blank('/v1.1/servers?%s' % query_str) + req = webob.Request.blank('/v1.1/123/servers?%s' % query_str) # Request admin context context = nova.context.RequestContext('testuser', 'testproject', is_admin=True) @@ -1273,7 +1273,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) query_str = "name=foo&ip=10.*&status=active&unknown_option=meow" - req = webob.Request.blank('/v1.1/servers?%s' % query_str) + req = webob.Request.blank('/v1.1/123/servers?%s' % query_str) # Request admin context context = nova.context.RequestContext('testuser', 'testproject', is_admin=False) @@ -1306,7 +1306,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) query_str = "name=foo&ip=10.*&status=active&unknown_option=meow" - req = webob.Request.blank('/v1.1/servers?%s' % query_str) + req = webob.Request.blank('/v1.1/123/servers?%s' % query_str) # Request admin context context = nova.context.RequestContext('testuser', 'testproject', is_admin=True) @@ -1332,7 +1332,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) - req = webob.Request.blank('/v1.1/servers?ip=10\..*') + req = webob.Request.blank('/v1.1/123/servers?ip=10\..*') # Request admin context context = nova.context.RequestContext('testuser', 'testproject', is_admin=True) @@ -1358,7 +1358,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) - req = webob.Request.blank('/v1.1/servers?ip6=ffff.*') + req = webob.Request.blank('/v1.1/123/servers?ip6=ffff.*') # Request admin context context = nova.context.RequestContext('testuser', 'testproject', is_admin=True) @@ -3022,18 +3022,19 @@ class ServersViewBuilderV11Test(test.TestCase): return instance - def _get_view_builder(self): + def _get_view_builder(self, project_id=""): base_url = "http://localhost/v1.1" views = nova.api.openstack.views address_builder = views.addresses.ViewBuilderV11() - flavor_builder = views.flavors.ViewBuilderV11(base_url) - image_builder = views.images.ViewBuilderV11(base_url) + flavor_builder = views.flavors.ViewBuilderV11(base_url, project_id) + image_builder = views.images.ViewBuilderV11(base_url, project_id) view_builder = nova.api.openstack.views.servers.ViewBuilderV11( address_builder, flavor_builder, image_builder, base_url, + project_id, ) return view_builder @@ -3059,6 +3060,29 @@ class ServersViewBuilderV11Test(test.TestCase): output = self.view_builder.build(self.instance, False) self.assertDictMatch(output, expected_server) + def test_build_server_with_project_id(self): + expected_server = { + "server": { + "id": 1, + "uuid": self.instance['uuid'], + "name": "test_server", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/fake/servers/1", + }, + { + "rel": "bookmark", + "href": "http://localhost/fake/servers/1", + }, + ], + } + } + + view_builder = self._get_view_builder(project_id='fake') + output = view_builder.build(self.instance, False) + self.assertDictMatch(output, expected_server) + def test_build_server_detail(self): image_bookmark = "http://localhost/images/5" flavor_bookmark = "http://localhost/flavors/1" -- cgit From caf7312a479a634ab02ccf38f53d510d20e25646 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 10 Aug 2011 11:23:40 -0400 Subject: Fixed metadata PUT routing --- nova/api/openstack/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 86169dc24..ca6c1b5f1 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -203,7 +203,7 @@ class APIRouterV11(APIRouter): parent_resource=dict(member_name='image', collection_name='images')) - mapper.connect("metadata", "{tenant_id}/images/{image_id}/metadata", + mapper.connect("metadata", "/{tenant_id}/images/{image_id}/metadata", controller=image_metadata_controller, action='update_all', conditions={"method": ['PUT']}) @@ -215,7 +215,7 @@ class APIRouterV11(APIRouter): parent_resource=dict(member_name='server', collection_name='servers')) - mapper.connect("metadata", "/servers/{server_id}/metadata", + mapper.connect("metadata", "/{tenant_id}/servers/{server_id}/metadata", controller=server_metadata_controller, action='update_all', conditions={"method": ['PUT']}) -- cgit From 057449d4f96fd168b2e949b6ce429ce012911bec Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 10 Aug 2011 11:37:44 -0400 Subject: fix pep8 issues --- nova/api/openstack/contrib/floating_ips.py | 2 +- nova/api/openstack/servers.py | 2 +- nova/tests/api/openstack/extensions/foxinsocks.py | 2 +- nova/tests/integrated/api/client.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 52c9c6cf9..2aba1068a 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -102,7 +102,7 @@ class FloatingIPController(object): def delete(self, req, id): context = req.environ['nova.context'] ip = self.network_api.get_floating_ip(context, id) - + if 'fixed_ip' in ip: try: self.disassociate(req, id, '') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 45d7dc214..fec319d8e 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -647,7 +647,7 @@ class ControllerV11(Controller): base_url, project_id) addresses_builder = nova.api.openstack.views.addresses.ViewBuilderV11() builder = nova.api.openstack.views.servers.ViewBuilderV11( - addresses_builder, flavor_builder, image_builder, + addresses_builder, flavor_builder, image_builder, base_url, project_id) return builder.build(instance, is_detail=is_detail) diff --git a/nova/tests/api/openstack/extensions/foxinsocks.py b/nova/tests/api/openstack/extensions/foxinsocks.py index 294bae619..11d90a9fb 100644 --- a/nova/tests/api/openstack/extensions/foxinsocks.py +++ b/nova/tests/api/openstack/extensions/foxinsocks.py @@ -72,7 +72,7 @@ class Foxinsocks(object): res.body = json.dumps(data) return res - req_ext1 = extensions.RequestExtension('GET', + req_ext1 = extensions.RequestExtension('GET', '/v1.1/:(tenant_id)/flavors/:(id)', _goose_handler) request_exts.append(req_ext1) diff --git a/nova/tests/integrated/api/client.py b/nova/tests/integrated/api/client.py index 983f7cf7a..19372f42d 100644 --- a/nova/tests/integrated/api/client.py +++ b/nova/tests/integrated/api/client.py @@ -122,7 +122,7 @@ class TestOpenStackClient(object): self.auth_result = auth_headers return self.auth_result - def api_request(self, relative_uri, check_response_status=None, + def api_request(self, relative_uri, check_response_status=None, use_project_id=True, **kwargs): auth_result = self._authenticate() -- cgit From 2046554bc54a2ebbc9ea681b9f35eef79e0a1c0c Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 10 Aug 2011 11:49:54 -0400 Subject: Updated extensions to use the TenantMapper --- nova/api/openstack/extensions.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index d7edd420c..f733b2d95 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -29,6 +29,7 @@ from nova import exception from nova import flags from nova import log as logging from nova import wsgi as base_wsgi +import nova.api.openstack from nova.api.openstack import common from nova.api.openstack import faults from nova.api.openstack import wsgi @@ -259,7 +260,7 @@ class ExtensionMiddleware(base_wsgi.Middleware): ext_mgr = ExtensionManager(FLAGS.osapi_extensions_path) self.ext_mgr = ext_mgr - mapper = routes.Mapper() + mapper = nova.api.openstack.TenantMapper() serializer = wsgi.ResponseSerializer( {'application/xml': ExtensionsXMLSerializer()}) @@ -267,12 +268,16 @@ class ExtensionMiddleware(base_wsgi.Middleware): for resource in ext_mgr.get_resources(): LOG.debug(_('Extended resource: %s'), resource.collection) - mapper.resource(resource.collection, resource.collection, + kargs = dict( controller=wsgi.Resource( resource.controller, serializer=serializer), collection=resource.collection_actions, - member=resource.member_actions, - parent_resource=resource.parent) + member=resource.member_actions) + + if resource.parent: + kargs['parent_resource'] = resource.parent + + mapper.resource(resource.collection, resource.collection, **kargs) # extended actions action_resources = self._action_ext_resources(application, ext_mgr, -- cgit From cec8ee56a3a2b43ba3c257390b89ef54a79aa78a Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 10 Aug 2011 13:24:11 -0400 Subject: get last extension-based tests to pass --- .../api/openstack/contrib/test_floating_ips.py | 12 ++++---- nova/tests/api/openstack/test_extensions.py | 14 ++++----- .../api/openstack/test_flavors_extra_specs.py | 22 +++++++-------- nova/tests/integrated/api/client.py | 33 ++++++++-------------- nova/tests/integrated/test_extensions.py | 2 +- 5 files changed, 37 insertions(+), 46 deletions(-) diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py index ab7ae2e54..ca5a2a767 100644 --- a/nova/tests/api/openstack/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -112,7 +112,7 @@ class FloatingIpTest(test.TestCase): self.assertTrue('floating_ip' in view) def test_floating_ips_list(self): - req = webob.Request.blank('/v1.1/os-floating-ips') + req = webob.Request.blank('/v1.1/123/os-floating-ips') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) res_dict = json.loads(res.body) @@ -127,7 +127,7 @@ class FloatingIpTest(test.TestCase): self.assertEqual(res_dict, response) def test_floating_ip_show(self): - req = webob.Request.blank('/v1.1/os-floating-ips/1') + req = webob.Request.blank('/v1.1/123/os-floating-ips/1') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) res_dict = json.loads(res.body) @@ -137,7 +137,7 @@ class FloatingIpTest(test.TestCase): self.assertEqual(res_dict['floating_ip']['instance_id'], None) def test_floating_ip_allocate(self): - req = webob.Request.blank('/v1.1/os-floating-ips') + req = webob.Request.blank('/v1.1/123/os-floating-ips') req.method = 'POST' req.headers['Content-Type'] = 'application/json' res = req.get_response(fakes.wsgi_app()) @@ -150,7 +150,7 @@ class FloatingIpTest(test.TestCase): self.assertEqual(ip, expected) def test_floating_ip_release(self): - req = webob.Request.blank('/v1.1/os-floating-ips/1') + req = webob.Request.blank('/v1.1/123/os-floating-ips/1') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) @@ -162,7 +162,7 @@ class FloatingIpTest(test.TestCase): def test_floating_ip_associate(self): body = dict(associate_address=dict(fixed_ip='1.2.3.4')) - req = webob.Request.blank('/v1.1/os-floating-ips/1/associate') + req = webob.Request.blank('/v1.1/123/os-floating-ips/1/associate') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -177,7 +177,7 @@ class FloatingIpTest(test.TestCase): self.assertEqual(actual, expected) def test_floating_ip_disassociate(self): - req = webob.Request.blank('/v1.1/os-floating-ips/1/disassociate') + req = webob.Request.blank('/v1.1/123/os-floating-ips/1/disassociate') req.method = 'POST' req.headers['Content-Type'] = 'application/json' res = req.get_response(fakes.wsgi_app()) diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 17a076ff8..2d6ae7897 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -88,7 +88,7 @@ class ExtensionControllerTest(test.TestCase): def test_list_extensions_json(self): app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app) - request = webob.Request.blank("/extensions") + request = webob.Request.blank("/123/extensions") response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) @@ -115,7 +115,7 @@ class ExtensionControllerTest(test.TestCase): def test_get_extension_json(self): app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app) - request = webob.Request.blank("/extensions/FOXNSOX") + request = webob.Request.blank("/123/extensions/FOXNSOX") response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) @@ -133,7 +133,7 @@ class ExtensionControllerTest(test.TestCase): def test_list_extensions_xml(self): app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app) - request = webob.Request.blank("/extensions") + request = webob.Request.blank("/123/extensions") request.accept = "application/xml" response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) @@ -160,7 +160,7 @@ class ExtensionControllerTest(test.TestCase): def test_get_extension_xml(self): app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app) - request = webob.Request.blank("/extensions/FOXNSOX") + request = webob.Request.blank("/123/extensions/FOXNSOX") request.accept = "application/xml" response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) @@ -201,7 +201,7 @@ class ResourceExtensionTest(test.TestCase): manager = StubExtensionManager(res_ext) app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app, manager) - request = webob.Request.blank("/tweedles") + request = webob.Request.blank("/123/tweedles") response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) self.assertEqual(response_body, response.body) @@ -212,7 +212,7 @@ class ResourceExtensionTest(test.TestCase): manager = StubExtensionManager(res_ext) app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app, manager) - request = webob.Request.blank("/tweedles") + request = webob.Request.blank("/123/tweedles") response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) self.assertEqual(response_body, response.body) @@ -235,7 +235,7 @@ class ExtensionManagerTest(test.TestCase): def test_get_resources(self): app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app) - request = webob.Request.blank("/foxnsocks") + request = webob.Request.blank("/123/foxnsocks") response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) self.assertEqual(response_body, response.body) diff --git a/nova/tests/api/openstack/test_flavors_extra_specs.py b/nova/tests/api/openstack/test_flavors_extra_specs.py index ccd1b0d9f..f382d06e9 100644 --- a/nova/tests/api/openstack/test_flavors_extra_specs.py +++ b/nova/tests/api/openstack/test_flavors_extra_specs.py @@ -63,7 +63,7 @@ class FlavorsExtraSpecsTest(test.TestCase): def test_index(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', return_flavor_extra_specs) - request = webob.Request.blank('/v1.1/flavors/1/os-extra_specs') + request = webob.Request.blank('/v1.1/123/flavors/1/os-extra_specs') res = request.get_response(fakes.wsgi_app()) self.assertEqual(200, res.status_int) res_dict = json.loads(res.body) @@ -73,7 +73,7 @@ class FlavorsExtraSpecsTest(test.TestCase): def test_index_no_data(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', return_empty_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs') + req = webob.Request.blank('/v1.1/123/flavors/1/os-extra_specs') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(200, res.status_int) @@ -83,7 +83,7 @@ class FlavorsExtraSpecsTest(test.TestCase): def test_show(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', return_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs/key5') + req = webob.Request.blank('/v1.1/123/flavors/1/os-extra_specs/key5') res = req.get_response(fakes.wsgi_app()) self.assertEqual(200, res.status_int) res_dict = json.loads(res.body) @@ -93,7 +93,7 @@ class FlavorsExtraSpecsTest(test.TestCase): def test_show_spec_not_found(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_get', return_empty_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs/key6') + req = webob.Request.blank('/v1.1/123/flavors/1/os-extra_specs/key6') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(404, res.status_int) @@ -101,7 +101,7 @@ class FlavorsExtraSpecsTest(test.TestCase): def test_delete(self): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_delete', delete_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs/key5') + req = webob.Request.blank('/v1.1/123/flavors/1/os-extra_specs/key5') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) self.assertEqual(200, res.status_int) @@ -110,7 +110,7 @@ class FlavorsExtraSpecsTest(test.TestCase): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_update_or_create', return_create_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs') + req = webob.Request.blank('/v1.1/123/flavors/1/os-extra_specs') req.method = 'POST' req.body = '{"extra_specs": {"key1": "value1"}}' req.headers["content-type"] = "application/json" @@ -124,7 +124,7 @@ class FlavorsExtraSpecsTest(test.TestCase): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_update_or_create', return_create_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs') + req = webob.Request.blank('/v1.1/123/flavors/1/os-extra_specs') req.method = 'POST' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) @@ -134,7 +134,7 @@ class FlavorsExtraSpecsTest(test.TestCase): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_update_or_create', return_create_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs/key1') + req = webob.Request.blank('/v1.1/123/flavors/1/os-extra_specs/key1') req.method = 'PUT' req.body = '{"key1": "value1"}' req.headers["content-type"] = "application/json" @@ -148,7 +148,7 @@ class FlavorsExtraSpecsTest(test.TestCase): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_update_or_create', return_create_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs/key1') + req = webob.Request.blank('/v1.1/123/flavors/1/os-extra_specs/key1') req.method = 'PUT' req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) @@ -158,7 +158,7 @@ class FlavorsExtraSpecsTest(test.TestCase): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_update_or_create', return_create_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs/key1') + req = webob.Request.blank('/v1.1/123/flavors/1/os-extra_specs/key1') req.method = 'PUT' req.body = '{"key1": "value1", "key2": "value2"}' req.headers["content-type"] = "application/json" @@ -169,7 +169,7 @@ class FlavorsExtraSpecsTest(test.TestCase): self.stubs.Set(nova.db.api, 'instance_type_extra_specs_update_or_create', return_create_flavor_extra_specs) - req = webob.Request.blank('/v1.1/flavors/1/os-extra_specs/bad') + req = webob.Request.blank('/v1.1/123/flavors/1/os-extra_specs/bad') req.method = 'PUT' req.body = '{"key1": "value1"}' req.headers["content-type"] = "application/json" diff --git a/nova/tests/integrated/api/client.py b/nova/tests/integrated/api/client.py index 19372f42d..f3221e7ad 100644 --- a/nova/tests/integrated/api/client.py +++ b/nova/tests/integrated/api/client.py @@ -122,18 +122,14 @@ class TestOpenStackClient(object): self.auth_result = auth_headers return self.auth_result - def api_request(self, relative_uri, check_response_status=None, - use_project_id=True, **kwargs): + def api_request(self, relative_uri, check_response_status=None, **kwargs): auth_result = self._authenticate() # NOTE(justinsb): httplib 'helpfully' converts headers to lower case base_uri = auth_result['x-server-management-url'] - if use_project_id: - # /fake is the project_id - full_uri = base_uri + '/fake' + relative_uri - else: - full_uri = base_uri + relative_uri + # /fake is the project_id + full_uri = base_uri + '/123' + relative_uri headers = kwargs.setdefault('headers', {}) headers['X-Auth-Token'] = auth_result['x-auth-token'] @@ -240,36 +236,31 @@ class TestOpenStackClient(object): return self.api_delete('/flavors/%s' % flavor_id) def get_volume(self, volume_id): - return self.api_get('/os-volumes/%s' % volume_id, - use_project_id=False)['volume'] + return self.api_get('/os-volumes/%s' % volume_id)['volume'] def get_volumes(self, detail=True): rel_url = '/os-volumes/detail' if detail else '/os-volumes' - return self.api_get(rel_url, use_project_id=False)['volumes'] + return self.api_get(rel_url)['volumes'] def post_volume(self, volume): - return self.api_post('/os-volumes', volume, - use_project_id=False)['volume'] + return self.api_post('/os-volumes', volume)['volume'] def delete_volume(self, volume_id): - return self.api_delete('/os-volumes/%s' % volume_id, - use_project_id=False) + return self.api_delete('/os-volumes/%s' % volume_id) def get_server_volume(self, server_id, attachment_id): return self.api_get('/servers/%s/os-volume_attachments/%s' % - (server_id, attachment_id), use_project_id=False - )['volumeAttachment'] + (server_id, attachment_id))['volumeAttachment'] def get_server_volumes(self, server_id): return self.api_get('/servers/%s/os-volume_attachments' % - (server_id), use_project_id=False - )['volumeAttachments'] + (server_id))['volumeAttachments'] def post_server_volume(self, server_id, volume_attachment): return self.api_post('/servers/%s/os-volume_attachments' % - (server_id), volume_attachment, - use_project_id=False)['volumeAttachment'] + (server_id), volume_attachment + )['volumeAttachment'] def delete_server_volume(self, server_id, attachment_id): return self.api_delete('/servers/%s/os-volume_attachments/%s' % - (server_id, attachment_id), use_project_id=False) + (server_id, attachment_id)) diff --git a/nova/tests/integrated/test_extensions.py b/nova/tests/integrated/test_extensions.py index 02a7001e3..c22cf0be0 100644 --- a/nova/tests/integrated/test_extensions.py +++ b/nova/tests/integrated/test_extensions.py @@ -33,7 +33,7 @@ class ExtensionsTest(integrated_helpers._IntegratedTestBase): def test_get_foxnsocks(self): """Simple check that fox-n-socks works.""" - response = self.api.api_request('/foxnsocks', use_project_id=False) + response = self.api.api_request('/foxnsocks') foxnsocks = response.read() LOG.debug("foxnsocks: %s" % foxnsocks) self.assertEqual('Try to say this Mr. Knox, sir...', foxnsocks) -- cgit From 203326be6c4acdd474fe307fb608ef35d95e0a4e Mon Sep 17 00:00:00 2001 From: William Wolf Date: Wed, 10 Aug 2011 15:47:22 -0400 Subject: tenant_id -> project_id --- nova/api/openstack/__init__.py | 9 +++++---- nova/api/openstack/extensions.py | 4 ++-- nova/api/openstack/wsgi.py | 4 ++-- nova/tests/api/openstack/contrib/test_multinic_xs.py | 8 ++++---- nova/tests/test_compute.py | 6 +++--- nova/utils.py | 2 +- 6 files changed, 17 insertions(+), 16 deletions(-) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index ca6c1b5f1..df8e0c564 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -72,12 +72,12 @@ class TenantMapper(routes.Mapper): def resource(self, member_name, collection_name, **kwargs): if not ('parent_resource' in kwargs): - kwargs['path_prefix'] = '{tenant_id}/' + kwargs['path_prefix'] = '{project_id}/' else: parent_resource = kwargs['parent_resource'] p_collection = parent_resource['collection_name'] p_member = parent_resource['member_name'] - kwargs['path_prefix'] = '{tenant_id}/%s/:%s_id' % (p_collection, + kwargs['path_prefix'] = '{project_id}/%s/:%s_id' % (p_collection, p_member) routes.Mapper.resource(self, member_name, collection_name, @@ -203,7 +203,7 @@ class APIRouterV11(APIRouter): parent_resource=dict(member_name='image', collection_name='images')) - mapper.connect("metadata", "/{tenant_id}/images/{image_id}/metadata", + mapper.connect("metadata", "/{project_id}/images/{image_id}/metadata", controller=image_metadata_controller, action='update_all', conditions={"method": ['PUT']}) @@ -215,7 +215,8 @@ class APIRouterV11(APIRouter): parent_resource=dict(member_name='server', collection_name='servers')) - mapper.connect("metadata", "/{tenant_id}/servers/{server_id}/metadata", + mapper.connect("metadata", + "/{project_id}/servers/{server_id}/metadata", controller=server_metadata_controller, action='update_all', conditions={"method": ['PUT']}) diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index f733b2d95..530972bec 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -221,12 +221,12 @@ class ExtensionMiddleware(base_wsgi.Middleware): for action in ext_mgr.get_actions(): if not action.collection in action_resources.keys(): resource = ActionExtensionResource(application) - mapper.connect("/:(tenant_id)/%s/:(id)/action.:(format)" % + mapper.connect("/:(project_id)/%s/:(id)/action.:(format)" % action.collection, action='action', controller=resource, conditions=dict(method=['POST'])) - mapper.connect("/:(tenant_id)/%s/:(id)/action" % + mapper.connect("/:(project_id)/%s/:(id)/action" % action.collection, action='action', controller=resource, diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 34c31260b..82fef6df8 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -486,8 +486,8 @@ class Resource(wsgi.Application): msg = _("Malformed request body") return faults.Fault(webob.exc.HTTPBadRequest(explanation=msg)) - if 'tenant_id' in args: - args.pop("tenant_id") + if "project_id" in args: + project_id = args.pop("project_id") try: action_result = self.dispatch(request, action, args) diff --git a/nova/tests/api/openstack/contrib/test_multinic_xs.py b/nova/tests/api/openstack/contrib/test_multinic_xs.py index f659852e8..cecc4af4f 100644 --- a/nova/tests/api/openstack/contrib/test_multinic_xs.py +++ b/nova/tests/api/openstack/contrib/test_multinic_xs.py @@ -55,7 +55,7 @@ class FixedIpTest(test.TestCase): last_add_fixed_ip = (None, None) body = dict(addFixedIp=dict(networkId='test_net')) - req = webob.Request.blank('/v1.1/fake/servers/test_inst/action') + req = webob.Request.blank('/v1.1/123/servers/test_inst/action') req.method = 'POST' req.body = json.dumps(body) req.headers['content-type'] = 'application/json' @@ -69,7 +69,7 @@ class FixedIpTest(test.TestCase): last_add_fixed_ip = (None, None) body = dict(addFixedIp=dict()) - req = webob.Request.blank('/v1.1/fake/servers/test_inst/action') + req = webob.Request.blank('/v1.1/123/servers/test_inst/action') req.method = 'POST' req.body = json.dumps(body) req.headers['content-type'] = 'application/json' @@ -83,7 +83,7 @@ class FixedIpTest(test.TestCase): last_remove_fixed_ip = (None, None) body = dict(removeFixedIp=dict(address='10.10.10.1')) - req = webob.Request.blank('/v1.1/fake/servers/test_inst/action') + req = webob.Request.blank('/v1.1/123/servers/test_inst/action') req.method = 'POST' req.body = json.dumps(body) req.headers['content-type'] = 'application/json' @@ -97,7 +97,7 @@ class FixedIpTest(test.TestCase): last_remove_fixed_ip = (None, None) body = dict(removeFixedIp=dict()) - req = webob.Request.blank('/v1.1/fake/servers/test_inst/action') + req = webob.Request.blank('/v1.1/123/servers/test_inst/action') req.method = 'POST' req.body = json.dumps(body) req.headers['content-type'] = 'application/json' diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 80f7ff489..a4c71a5d6 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -344,7 +344,7 @@ class ComputeTestCase(test.TestCase): self.assertEquals(msg['priority'], 'INFO') self.assertEquals(msg['event_type'], 'compute.instance.create') payload = msg['payload'] - self.assertEquals(payload['tenant_id'], self.project_id) + self.assertEquals(payload['project_id'], self.project_id) self.assertEquals(payload['user_id'], self.user_id) self.assertEquals(payload['instance_id'], instance_id) self.assertEquals(payload['instance_type'], 'm1.tiny') @@ -368,7 +368,7 @@ class ComputeTestCase(test.TestCase): self.assertEquals(msg['priority'], 'INFO') self.assertEquals(msg['event_type'], 'compute.instance.delete') payload = msg['payload'] - self.assertEquals(payload['tenant_id'], self.project_id) + self.assertEquals(payload['project_id'], self.project_id) self.assertEquals(payload['user_id'], self.user_id) self.assertEquals(payload['instance_id'], instance_id) self.assertEquals(payload['instance_type'], 'm1.tiny') @@ -451,7 +451,7 @@ class ComputeTestCase(test.TestCase): self.assertEquals(msg['priority'], 'INFO') self.assertEquals(msg['event_type'], 'compute.instance.resize.prep') payload = msg['payload'] - self.assertEquals(payload['tenant_id'], self.project_id) + self.assertEquals(payload['project_id'], self.project_id) self.assertEquals(payload['user_id'], self.user_id) self.assertEquals(payload['instance_id'], instance_id) self.assertEquals(payload['instance_type'], 'm1.tiny') diff --git a/nova/utils.py b/nova/utils.py index 372358b42..4b068e3a5 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -291,7 +291,7 @@ EASIER_PASSWORD_SYMBOLS = ('23456789' # Removed: 0, 1 def usage_from_instance(instance_ref, **kw): usage_info = dict( - tenant_id=instance_ref['project_id'], + project_id=instance_ref['project_id'], user_id=instance_ref['user_id'], instance_id=instance_ref['id'], instance_type=instance_ref['instance_type']['name'], -- cgit From 01c7da9e861fee3201e2bc5dcc289024aa5ced61 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 11 Aug 2011 14:40:05 -0400 Subject: got rid of tenant_id everywhere, got rid of X-Auth-Project-Id header support (not in the spec), and updated tests --- nova/api/openstack/__init__.py | 4 +- nova/api/openstack/auth.py | 10 ++- nova/api/openstack/extensions.py | 2 +- nova/api/openstack/wsgi.py | 5 +- nova/tests/api/openstack/contrib/test_keypairs.py | 8 +-- nova/tests/api/openstack/extensions/foxinsocks.py | 4 +- nova/tests/api/openstack/fakes.py | 1 + nova/tests/api/openstack/test_flavors.py | 8 +-- nova/tests/api/openstack/test_images.py | 28 ++++----- nova/tests/api/openstack/test_servers.py | 76 +++++++++++------------ nova/tests/integrated/api/client.py | 2 +- 11 files changed, 76 insertions(+), 72 deletions(-) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index de2aee96a..8805c4ef6 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -68,7 +68,7 @@ class FaultWrapper(base_wsgi.Middleware): return faults.Fault(exc) -class TenantMapper(routes.Mapper): +class ProjectMapper(routes.Mapper): def resource(self, member_name, collection_name, **kwargs): if not ('parent_resource' in kwargs): @@ -191,7 +191,7 @@ class APIRouterV11(APIRouter): """Define routes specific to OpenStack API V1.1.""" def __init__(self, ext_mgr=None): - mapper = TenantMapper() + mapper = ProjectMapper() self.server_members = {} self._setup_routes(mapper) super(APIRouter, self).__init__(mapper) diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index d42abe1f8..164a60cbc 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -55,9 +55,13 @@ class AuthMiddleware(wsgi.Middleware): LOG.warn(msg % locals()) return faults.Fault(webob.exc.HTTPUnauthorized()) - try: - project_id = req.headers["X-Auth-Project-Id"] - except KeyError: + project_id = "" + path_parts = req.path.split('/') + # TODO(wwolf): this v1.1 check will be temporary as + # keystone should be taking this over at some point + if len(path_parts) > 1 and path_parts[1] == 'v1.1': + project_id = path_parts[2] + elif len(path_parts) > 1 and path_parts[1] == 'v1.0': # FIXME(usrleon): It needed only for compatibility # while osapi clients don't use this header projects = self.auth.get_projects(user_id) diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 86ffb91c6..9c4d32eb4 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -260,7 +260,7 @@ class ExtensionMiddleware(base_wsgi.Middleware): ext_mgr = ExtensionManager(FLAGS.osapi_extensions_path) self.ext_mgr = ext_mgr - mapper = nova.api.openstack.TenantMapper() + mapper = nova.api.openstack.ProjectMapper() serializer = wsgi.ResponseSerializer( {'application/xml': ExtensionsXMLSerializer()}) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 82fef6df8..dc0f1b93e 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -486,8 +486,9 @@ class Resource(wsgi.Application): msg = _("Malformed request body") return faults.Fault(webob.exc.HTTPBadRequest(explanation=msg)) - if "project_id" in args: - project_id = args.pop("project_id") + project_id = args.pop("project_id", None) + if 'nova.context' in request.environ and project_id: + request.environ['nova.context'].project_id = project_id try: action_result = self.dispatch(request, action, args) diff --git a/nova/tests/api/openstack/contrib/test_keypairs.py b/nova/tests/api/openstack/contrib/test_keypairs.py index c9dc34d65..77e26974f 100644 --- a/nova/tests/api/openstack/contrib/test_keypairs.py +++ b/nova/tests/api/openstack/contrib/test_keypairs.py @@ -57,7 +57,7 @@ class KeypairsTest(test.TestCase): self.context = context.get_admin_context() def test_keypair_list(self): - req = webob.Request.blank('/v1.1/os-keypairs') + req = webob.Request.blank('/v1.1/123/os-keypairs') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) res_dict = json.loads(res.body) @@ -66,7 +66,7 @@ class KeypairsTest(test.TestCase): def test_keypair_create(self): body = {'keypair': {'name': 'create_test'}} - req = webob.Request.blank('/v1.1/os-keypairs') + req = webob.Request.blank('/v1.1/123/os-keypairs') req.method = 'POST' req.body = json.dumps(body) req.headers['Content-Type'] = 'application/json' @@ -79,7 +79,7 @@ class KeypairsTest(test.TestCase): def test_keypair_import(self): body = {'keypair': {'name': 'create_test', 'public_key': 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBYIznAx9D7118Q1VKGpXy2HDiKyUTM8XcUuhQpo0srqb9rboUp4a9NmCwpWpeElDLuva707GOUnfaBAvHBwsRXyxHJjRaI6YQj2oLJwqvaSaWUbyT1vtryRqy6J3TecN0WINY71f4uymiMZP0wby4bKBcYnac8KiCIlvkEl0ETjkOGUq8OyWRmn7ljj5SESEUdBP0JnuTFKddWTU/wD6wydeJaUhBTqOlHn0kX1GyqoNTE1UEhcM5ZRWgfUZfTjVyDF2kGj3vJLCJtJ8LoGcj7YaN4uPg1rBle+izwE/tLonRrds+cev8p6krSSrxWOwBbHkXa6OciiJDvkRzJXzf'}} - req = webob.Request.blank('/v1.1/os-keypairs') + req = webob.Request.blank('/v1.1/123/os-keypairs') req.method = 'POST' req.body = json.dumps(body) req.headers['Content-Type'] = 'application/json' @@ -91,7 +91,7 @@ class KeypairsTest(test.TestCase): self.assertFalse('private_key' in res_dict['keypair']) def test_keypair_delete(self): - req = webob.Request.blank('/v1.1/os-keypairs/FAKE') + req = webob.Request.blank('/v1.1/123/os-keypairs/FAKE') req.method = 'DELETE' req.headers['Content-Type'] = 'application/json' res = req.get_response(fakes.wsgi_app()) diff --git a/nova/tests/api/openstack/extensions/foxinsocks.py b/nova/tests/api/openstack/extensions/foxinsocks.py index 11d90a9fb..2d8313cf6 100644 --- a/nova/tests/api/openstack/extensions/foxinsocks.py +++ b/nova/tests/api/openstack/extensions/foxinsocks.py @@ -73,7 +73,7 @@ class Foxinsocks(object): return res req_ext1 = extensions.RequestExtension('GET', - '/v1.1/:(tenant_id)/flavors/:(id)', + '/v1.1/:(project_id)/flavors/:(id)', _goose_handler) request_exts.append(req_ext1) @@ -86,7 +86,7 @@ class Foxinsocks(object): return res req_ext2 = extensions.RequestExtension('GET', - '/v1.1/:(tenant_id)/flavors/:(id)', + '/v1.1/:(project_id)/flavors/:(id)', _bands_handler) request_exts.append(req_ext2) return request_exts diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index d11fbf788..0611ad962 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -83,6 +83,7 @@ def wsgi_app(inner_app10=None, inner_app11=None, fake_auth=True, ctxt = fake_auth_context else: ctxt = context.RequestContext('fake', 'fake') + api10 = openstack.FaultWrapper(wsgi.InjectContext(ctxt, limits.RateLimitingMiddleware(inner_app10))) api11 = openstack.FaultWrapper(wsgi.InjectContext(ctxt, diff --git a/nova/tests/api/openstack/test_flavors.py b/nova/tests/api/openstack/test_flavors.py index 2db69f4e3..812bece42 100644 --- a/nova/tests/api/openstack/test_flavors.py +++ b/nova/tests/api/openstack/test_flavors.py @@ -138,7 +138,7 @@ class FlavorsTest(test.TestCase): self.assertEqual(res.status_int, 404) def test_get_flavor_by_id_v1_1(self): - req = webob.Request.blank('/v1.1/123/flavors/12') + req = webob.Request.blank('/v1.1/fake/flavors/12') req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) @@ -164,7 +164,7 @@ class FlavorsTest(test.TestCase): self.assertEqual(flavor, expected) def test_get_flavor_list_v1_1(self): - req = webob.Request.blank('/v1.1/123/flavors') + req = webob.Request.blank('/v1.1/fake/flavors') req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) @@ -204,7 +204,7 @@ class FlavorsTest(test.TestCase): self.assertEqual(flavor, expected) def test_get_flavor_list_detail_v1_1(self): - req = webob.Request.blank('/v1.1/123/flavors/detail') + req = webob.Request.blank('/v1.1/fake/flavors/detail') req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) @@ -252,7 +252,7 @@ class FlavorsTest(test.TestCase): return {} self.stubs.Set(nova.db.api, "instance_type_get_all", _return_empty) - req = webob.Request.blank('/v1.1/123/flavors') + req = webob.Request.blank('/v1.1/fake/flavors') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) flavors = json.loads(res.body)["flavors"] diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 882b0aafd..2a7cfc382 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -391,11 +391,9 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): self.assertEqual(expected_image, actual_image) def test_get_image_v1_1(self): - request = webob.Request.blank('/v1.1/123/images/124') + request = webob.Request.blank('/v1.1/fake/images/124') response = request.get_response(fakes.wsgi_app()) - print response.body - actual_image = json.loads(response.body) href = "http://localhost/v1.1/fake/images/124" @@ -515,7 +513,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): self.assertEqual(expected.toxml(), actual.toxml()) def test_get_image_404_v1_1_json(self): - request = webob.Request.blank('/v1.1/123/images/NonExistantImage') + request = webob.Request.blank('/v1.1/fake/images/NonExistantImage') response = request.get_response(fakes.wsgi_app()) self.assertEqual(404, response.status_int) @@ -531,7 +529,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): self.assertEqual(expected, actual) def test_get_image_404_v1_1_xml(self): - request = webob.Request.blank('/v1.1/123/images/NonExistantImage') + request = webob.Request.blank('/v1.1/fake/images/NonExistantImage') request.accept = "application/xml" response = request.get_response(fakes.wsgi_app()) self.assertEqual(404, response.status_int) @@ -552,7 +550,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): self.assertEqual(expected.toxml(), actual.toxml()) def test_get_image_index_v1_1(self): - request = webob.Request.blank('/v1.1/123/images') + request = webob.Request.blank('/v1.1/fake/images') response = request.get_response(fakes.wsgi_app()) response_dict = json.loads(response.body) @@ -644,7 +642,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): self.assertDictListMatch(expected, response_list) def test_get_image_details_v1_1(self): - request = webob.Request.blank('/v1.1/123/images/detail') + request = webob.Request.blank('/v1.1/fake/images/detail') response = request.get_response(fakes.wsgi_app()) response_dict = json.loads(response.body) @@ -922,7 +920,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): filters = {'name': 'testname'} image_service.detail(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - request = webob.Request.blank('/v1.1/123/images/detail?name=testname') + request = webob.Request.blank('/v1.1/fake/images/detail?name=testname') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) @@ -934,7 +932,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): filters = {'status': 'ACTIVE'} image_service.detail(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - request = webob.Request.blank('/v1.1/123/images/detail?status=ACTIVE') + request = webob.Request.blank('/v1.1/fake/images/detail?status=ACTIVE') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) @@ -947,7 +945,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): image_service.detail(context, filters=filters).AndReturn([]) self.mox.ReplayAll() request = webob.Request.blank( - '/v1.1/123/images/detail?property-test=3') + '/v1.1/fake/images/detail?property-test=3') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) @@ -960,7 +958,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): filters = {'property-instance_ref': 'http://localhost:8774/servers/12'} image_service.index(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - request = webob.Request.blank('/v1.1/123/images/detail?server=' + request = webob.Request.blank('/v1.1/fake/images/detail?server=' 'http://localhost:8774/servers/12') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) @@ -973,7 +971,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): filters = {'changes-since': '2011-01-24T17:08Z'} image_service.index(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - request = webob.Request.blank('/v1.1/123/images/detail?changes-since=' + request = webob.Request.blank('/v1.1/fake/images/detail?changes-since=' '2011-01-24T17:08Z') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) @@ -986,7 +984,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): filters = {'property-image_type': 'BASE'} image_service.index(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - request = webob.Request.blank('/v1.1/123/images/detail?type=BASE') + request = webob.Request.blank('/v1.1/fake/images/detail?type=BASE') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.index(request) @@ -998,7 +996,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): filters = {'status': 'ACTIVE'} image_service.detail(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - request = webob.Request.blank('/v1.1/123/images/detail?status=ACTIVE&' + request = webob.Request.blank('/v1.1/fake/images/detail?status=ACTIVE&' 'UNSUPPORTEDFILTER=testname') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) @@ -1011,7 +1009,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): filters = {} image_service.detail(context, filters=filters).AndReturn([]) self.mox.ReplayAll() - request = webob.Request.blank('/v1.1/123/images/detail') + request = webob.Request.blank('/v1.1/fake/images/detail') request.environ['nova.context'] = context controller = images.ControllerV11(image_service=image_service) controller.detail(request) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index e794b40f2..f55ecbf1d 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -322,7 +322,7 @@ class ServersTest(test.TestCase): interfaces=interfaces) self.stubs.Set(nova.db.api, 'instance_get', new_return_server) - req = webob.Request.blank('/v1.1/123/servers/1') + req = webob.Request.blank('/v1.1/fake/servers/1') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) expected_server = { @@ -414,7 +414,7 @@ class ServersTest(test.TestCase): interfaces=interfaces) self.stubs.Set(nova.db.api, 'instance_get', new_return_server) - req = webob.Request.blank('/v1.1/123/servers/1') + req = webob.Request.blank('/v1.1/fake/servers/1') req.headers['Accept'] = 'application/xml' res = req.get_response(fakes.wsgi_app()) actual = minidom.parseString(res.body.replace(' ', '')) @@ -484,7 +484,7 @@ class ServersTest(test.TestCase): interfaces=interfaces, power_state=1) self.stubs.Set(nova.db.api, 'instance_get', new_return_server) - req = webob.Request.blank('/v1.1/123/servers/1') + req = webob.Request.blank('/v1.1/fake/servers/1') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) expected_server = { @@ -575,7 +575,7 @@ class ServersTest(test.TestCase): flavor_id=flavor_id) self.stubs.Set(nova.db.api, 'instance_get', new_return_server) - req = webob.Request.blank('/v1.1/123/servers/1') + req = webob.Request.blank('/v1.1/fake/servers/1') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) expected_server = { @@ -775,7 +775,7 @@ class ServersTest(test.TestCase): interfaces=interfaces) self.stubs.Set(nova.db.api, 'instance_get', new_return_server) - req = webob.Request.blank('/v1.1/123/servers/1') + req = webob.Request.blank('/v1.1/fake/servers/1') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) @@ -819,7 +819,7 @@ class ServersTest(test.TestCase): interfaces=interfaces) self.stubs.Set(nova.db.api, 'instance_get', new_return_server) - req = webob.Request.blank('/v1.1/123/servers/1') + req = webob.Request.blank('/v1.1/fake/servers/1') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) @@ -869,7 +869,7 @@ class ServersTest(test.TestCase): 'virtual_interface_get_by_instance', _return_vifs) - req = webob.Request.blank('/v1.1/123/servers/1/ips') + req = webob.Request.blank('/v1.1/fake/servers/1/ips') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) @@ -919,7 +919,7 @@ class ServersTest(test.TestCase): 'virtual_interface_get_by_instance', _return_vifs) - req = webob.Request.blank('/v1.1/123/servers/1/ips/network_2') + req = webob.Request.blank('/v1.1/fake/servers/1/ips/network_2') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) res_dict = json.loads(res.body) @@ -939,7 +939,7 @@ class ServersTest(test.TestCase): 'virtual_interface_get_by_instance', _return_vifs) - req = webob.Request.blank('/v1.1/123/servers/1/ips/network_0') + req = webob.Request.blank('/v1.1/fake/servers/1/ips/network_0') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 404) @@ -949,7 +949,7 @@ class ServersTest(test.TestCase): 'virtual_interface_get_by_instance', _return_vifs) - req = webob.Request.blank('/v1.1/123/servers/600/ips') + req = webob.Request.blank('/v1.1/fake/servers/600/ips') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 404) @@ -1018,7 +1018,7 @@ class ServersTest(test.TestCase): i += 1 def test_get_server_list_v1_1(self): - req = webob.Request.blank('/v1.1/123/servers') + req = webob.Request.blank('/v1.1/fake/servers') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) @@ -1082,19 +1082,19 @@ class ServersTest(test.TestCase): self.assertTrue(res.body.find('offset param') > -1) def test_get_servers_with_marker(self): - req = webob.Request.blank('/v1.1/123/servers?marker=2') + req = webob.Request.blank('/v1.1/fake/servers?marker=2') res = req.get_response(fakes.wsgi_app()) servers = json.loads(res.body)['servers'] self.assertEqual([s['name'] for s in servers], ["server3", "server4"]) def test_get_servers_with_limit_and_marker(self): - req = webob.Request.blank('/v1.1/123/servers?limit=2&marker=1') + req = webob.Request.blank('/v1.1/fake/servers?limit=2&marker=1') res = req.get_response(fakes.wsgi_app()) servers = json.loads(res.body)['servers'] self.assertEqual([s['name'] for s in servers], ['server2', 'server3']) def test_get_servers_with_bad_marker(self): - req = webob.Request.blank('/v1.1/123/servers?limit=2&marker=asdf') + req = webob.Request.blank('/v1.1/fake/servers?limit=2&marker=asdf') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) self.assertTrue(res.body.find('marker param') > -1) @@ -1120,7 +1120,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) - req = webob.Request.blank('/v1.1/123/servers?unknownoption=whee') + req = webob.Request.blank('/v1.1/fake/servers?unknownoption=whee') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) servers = json.loads(res.body)['servers'] @@ -1137,7 +1137,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) self.flags(allow_admin_api=False) - req = webob.Request.blank('/v1.1/123/servers?image=12345') + req = webob.Request.blank('/v1.1/fake/servers?image=12345') res = req.get_response(fakes.wsgi_app()) # The following assert will fail if either of the asserts in # fake_get_all() fail @@ -1157,7 +1157,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) self.flags(allow_admin_api=False) - req = webob.Request.blank('/v1.1/123/servers?flavor=12345') + req = webob.Request.blank('/v1.1/fake/servers?flavor=12345') res = req.get_response(fakes.wsgi_app()) # The following assert will fail if either of the asserts in # fake_get_all() fail @@ -1177,7 +1177,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) self.flags(allow_admin_api=False) - req = webob.Request.blank('/v1.1/123/servers?status=active') + req = webob.Request.blank('/v1.1/fake/servers?status=active') res = req.get_response(fakes.wsgi_app()) # The following assert will fail if either of the asserts in # fake_get_all() fail @@ -1191,7 +1191,7 @@ class ServersTest(test.TestCase): self.flags(allow_admin_api=False) - req = webob.Request.blank('/v1.1/123/servers?status=running') + req = webob.Request.blank('/v1.1/fake/servers?status=running') res = req.get_response(fakes.wsgi_app()) # The following assert will fail if either of the asserts in # fake_get_all() fail @@ -1208,7 +1208,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) self.flags(allow_admin_api=False) - req = webob.Request.blank('/v1.1/123/servers?name=whee.*') + req = webob.Request.blank('/v1.1/fake/servers?name=whee.*') res = req.get_response(fakes.wsgi_app()) # The following assert will fail if either of the asserts in # fake_get_all() fail @@ -1239,7 +1239,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) query_str = "name=foo&ip=10.*&status=active&unknown_option=meow" - req = webob.Request.blank('/v1.1/123/servers?%s' % query_str) + req = webob.Request.blank('/v1.1/fake/servers?%s' % query_str) # Request admin context context = nova.context.RequestContext('testuser', 'testproject', is_admin=True) @@ -1273,7 +1273,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) query_str = "name=foo&ip=10.*&status=active&unknown_option=meow" - req = webob.Request.blank('/v1.1/123/servers?%s' % query_str) + req = webob.Request.blank('/v1.1/fake/servers?%s' % query_str) # Request admin context context = nova.context.RequestContext('testuser', 'testproject', is_admin=False) @@ -1306,7 +1306,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) query_str = "name=foo&ip=10.*&status=active&unknown_option=meow" - req = webob.Request.blank('/v1.1/123/servers?%s' % query_str) + req = webob.Request.blank('/v1.1/fake/servers?%s' % query_str) # Request admin context context = nova.context.RequestContext('testuser', 'testproject', is_admin=True) @@ -1332,7 +1332,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) - req = webob.Request.blank('/v1.1/123/servers?ip=10\..*') + req = webob.Request.blank('/v1.1/fake/servers?ip=10\..*') # Request admin context context = nova.context.RequestContext('testuser', 'testproject', is_admin=True) @@ -1358,7 +1358,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.compute.API, 'get_all', fake_get_all) - req = webob.Request.blank('/v1.1/123/servers?ip6=ffff.*') + req = webob.Request.blank('/v1.1/fake/servers?ip6=ffff.*') # Request admin context context = nova.context.RequestContext('testuser', 'testproject', is_admin=True) @@ -1621,7 +1621,7 @@ class ServersTest(test.TestCase): }, } - req = webob.Request.blank('/v1.1/123/servers') + req = webob.Request.blank('/v1.1/fake/servers') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -1646,7 +1646,7 @@ class ServersTest(test.TestCase): name='server_test', imageRef=image_href, flavorRef=flavor_ref, metadata={'hello': 'world', 'open': 'stack'}, personality={})) - req = webob.Request.blank('/v1.1/123/servers') + req = webob.Request.blank('/v1.1/fake/servers') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -1662,7 +1662,7 @@ class ServersTest(test.TestCase): name='server_test', imageRef=image_href, flavorRef=flavor_ref, metadata={'hello': 'world', 'open': 'stack'}, personality={})) - req = webob.Request.blank('/v1.1/123/servers') + req = webob.Request.blank('/v1.1/fake/servers') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -1678,7 +1678,7 @@ class ServersTest(test.TestCase): name='server_test', imageRef=image_href, flavorRef=flavor_ref, metadata={'hello': 'world', 'open': 'stack'}, personality={})) - req = webob.Request.blank('/v1.1/123/servers') + req = webob.Request.blank('/v1.1/fake/servers') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -1716,7 +1716,7 @@ class ServersTest(test.TestCase): }, } - req = webob.Request.blank('/v1.1/123/servers') + req = webob.Request.blank('/v1.1/fake/servers') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -1763,7 +1763,7 @@ class ServersTest(test.TestCase): }, } - req = webob.Request.blank('/v1.1/123/servers') + req = webob.Request.blank('/v1.1/fake/servers') req.method = 'POST' req.body = json.dumps(body) req.headers['content-type'] = "application/json" @@ -1784,7 +1784,7 @@ class ServersTest(test.TestCase): }, } - req = webob.Request.blank('/v1.1/123/servers') + req = webob.Request.blank('/v1.1/fake/servers') req.method = 'POST' req.body = json.dumps(body) req.headers['content-type'] = "application/json" @@ -1858,13 +1858,13 @@ class ServersTest(test.TestCase): self.assertEqual(mock_method.password, 'bacon') def test_update_server_no_body_v1_1(self): - req = webob.Request.blank('/v1.1/123/servers/1') + req = webob.Request.blank('/v1.1/fake/servers/1') req.method = 'PUT' res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) def test_update_server_name_v1_1(self): - req = webob.Request.blank('/v1.1/123/servers/1') + req = webob.Request.blank('/v1.1/fake/servers/1') req.method = 'PUT' req.content_type = 'application/json' req.body = json.dumps({'server': {'name': 'new-name'}}) @@ -1884,7 +1884,7 @@ class ServersTest(test.TestCase): self.stubs.Set(nova.db.api, 'instance_update', server_update) - req = webob.Request.blank('/v1.1/123/servers/1') + req = webob.Request.blank('/v1.1/fake/servers/1') req.method = 'PUT' req.content_type = "application/json" req.body = self.body @@ -1915,7 +1915,7 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 501) def test_server_backup_schedule_deprecated_v1_1(self): - req = webob.Request.blank('/v1.1/123/servers/1/backup_schedule') + req = webob.Request.blank('/v1.1/fake/servers/1/backup_schedule') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 404) @@ -1968,7 +1968,7 @@ class ServersTest(test.TestCase): }, ], } - req = webob.Request.blank('/v1.1/123/servers/detail') + req = webob.Request.blank('/v1.1/fake/servers/detail') res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) @@ -2127,7 +2127,7 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 422) def test_delete_server_instance_v1_1(self): - req = webob.Request.blank('/v1.1/123/servers/1') + req = webob.Request.blank('/v1.1/fake/servers/1') req.method = 'DELETE' self.server_delete_called = False diff --git a/nova/tests/integrated/api/client.py b/nova/tests/integrated/api/client.py index f3221e7ad..2ccdb1bee 100644 --- a/nova/tests/integrated/api/client.py +++ b/nova/tests/integrated/api/client.py @@ -129,7 +129,7 @@ class TestOpenStackClient(object): base_uri = auth_result['x-server-management-url'] # /fake is the project_id - full_uri = base_uri + '/123' + relative_uri + full_uri = base_uri + '/fake' + relative_uri headers = kwargs.setdefault('headers', {}) headers['X-Auth-Token'] = auth_result['x-auth-token'] -- cgit From 4275c9062e2d89c30472ba6646fd3c2503c0e984 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 11 Aug 2011 15:49:28 -0400 Subject: fixed v1.0 stuff with X-Auth-Project-Id header, and fixed broken integrated tests --- nova/api/openstack/auth.py | 17 ++++++++++------- nova/tests/integrated/api/client.py | 4 ++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index 164a60cbc..d13c19852 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -62,13 +62,16 @@ class AuthMiddleware(wsgi.Middleware): if len(path_parts) > 1 and path_parts[1] == 'v1.1': project_id = path_parts[2] elif len(path_parts) > 1 and path_parts[1] == 'v1.0': - # FIXME(usrleon): It needed only for compatibility - # while osapi clients don't use this header - projects = self.auth.get_projects(user_id) - if projects: - project_id = projects[0].id - else: - return faults.Fault(webob.exc.HTTPUnauthorized()) + try: + project_id = req.headers["X-Auth-Project-Id"] + except KeyError: + # FIXME(usrleon): It needed only for compatibility + # while osapi clients don't use this header + projects = self.auth.get_projects(user_id) + if projects: + project_id = projects[0].id + else: + return faults.Fault(webob.exc.HTTPUnauthorized()) is_admin = self.auth.is_admin(user_id) req.environ['nova.context'] = context.RequestContext(user_id, diff --git a/nova/tests/integrated/api/client.py b/nova/tests/integrated/api/client.py index 2ccdb1bee..da78995cd 100644 --- a/nova/tests/integrated/api/client.py +++ b/nova/tests/integrated/api/client.py @@ -128,8 +128,8 @@ class TestOpenStackClient(object): # NOTE(justinsb): httplib 'helpfully' converts headers to lower case base_uri = auth_result['x-server-management-url'] - # /fake is the project_id - full_uri = base_uri + '/fake' + relative_uri + # /openstack is the project_id + full_uri = base_uri + '/openstack' + relative_uri headers = kwargs.setdefault('headers', {}) headers['X-Auth-Token'] = auth_result['x-auth-token'] -- cgit From 8131a998bba1ec2893043e5e02b66ea7df38a4ba Mon Sep 17 00:00:00 2001 From: William Wolf Date: Thu, 11 Aug 2011 16:06:28 -0400 Subject: fix pep8 --- nova/api/openstack/auth.py | 2 +- nova/tests/api/openstack/contrib/test_keypairs.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index d13c19852..c9c740a1d 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -57,7 +57,7 @@ class AuthMiddleware(wsgi.Middleware): project_id = "" path_parts = req.path.split('/') - # TODO(wwolf): this v1.1 check will be temporary as + # TODO(wwolf): this v1.1 check will be temporary as # keystone should be taking this over at some point if len(path_parts) > 1 and path_parts[1] == 'v1.1': project_id = path_parts[2] diff --git a/nova/tests/api/openstack/contrib/test_keypairs.py b/nova/tests/api/openstack/contrib/test_keypairs.py index 77e26974f..971dc4598 100644 --- a/nova/tests/api/openstack/contrib/test_keypairs.py +++ b/nova/tests/api/openstack/contrib/test_keypairs.py @@ -31,7 +31,6 @@ def fake_keypair(name): def db_key_pair_get_all_by_user(self, user_id): return [fake_keypair('FAKE')] - def db_key_pair_create(self, keypair): pass @@ -96,4 +95,3 @@ class KeypairsTest(test.TestCase): req.headers['Content-Type'] = 'application/json' res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) - -- cgit From 9ce9ef1166075e539442c61c65cf21b8d6e90cdd Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 11 Aug 2011 21:03:37 -0700 Subject: add keystone middlewares for ec2 api --- etc/nova/api-paste.ini | 32 +++++++++++++- nova/api/auth.py | 91 +++++++++++++++++++++++++++++++++++++++ nova/api/ec2/__init__.py | 55 +++++++++++++++++++++-- nova/tests/api/openstack/fakes.py | 5 ++- nova/tests/test_api.py | 3 +- nova/wsgi.py | 12 ------ 6 files changed, 179 insertions(+), 19 deletions(-) create mode 100644 nova/api/auth.py diff --git a/etc/nova/api-paste.ini b/etc/nova/api-paste.ini index abe8c20c4..ec3d88caf 100644 --- a/etc/nova/api-paste.ini +++ b/etc/nova/api-paste.ini @@ -20,7 +20,8 @@ use = egg:Paste#urlmap [pipeline:ec2cloud] pipeline = logrequest authenticate cloudrequest authorizer ec2executor -#pipeline = logrequest ec2lockout authenticate cloudrequest authorizer ec2executor +# NOTE(vish): use the following pipeline for keystone +# pipeline = logrequest totoken authtoken keystonecontext cloudrequest authorizer ec2executor [pipeline:ec2admin] pipeline = logrequest authenticate adminrequest authorizer ec2executor @@ -37,6 +38,9 @@ paste.filter_factory = nova.api.ec2:RequestLogging.factory [filter:ec2lockout] paste.filter_factory = nova.api.ec2:Lockout.factory +[filter:totoken] +paste.filter_factory = nova.api.ec2:ToToken.factory + [filter:authenticate] paste.filter_factory = nova.api.ec2:Authenticate.factory @@ -72,9 +76,13 @@ use = egg:Paste#urlmap [pipeline:openstackapi10] pipeline = faultwrap auth ratelimit osapiapp10 +# NOTE(vish): use the following pipeline for keystone +#pipeline = faultwrap authtoken keystonecontext ratelimit osapiapp10 [pipeline:openstackapi11] pipeline = faultwrap auth ratelimit extensions osapiapp11 +# NOTE(vish): use the following pipeline for keystone +# pipeline = faultwrap authtoken keystonecontext ratelimit extensions osapiapp11 [filter:faultwrap] paste.filter_factory = nova.api.openstack:FaultWrapper.factory @@ -99,3 +107,25 @@ pipeline = faultwrap osversionapp [app:osversionapp] paste.app_factory = nova.api.openstack.versions:Versions.factory + +########## +# Shared # +########## + +[filter:admincontext] +paste.filter_factory = nova.api.auth:AdminContext.factory + +[filter:keystonecontext] +paste.filter_factory = nova.api.auth:KeystoneContext.factory + +[filter:authtoken] +paste.filter_factory = keystone.middleware.auth_token:filter_factory +service_protocol = http +service_host = 127.0.0.1 +service_port = 808 +auth_host = 127.0.0.1 +auth_port = 5001 +auth_protocol = http +auth_uri = http://127.0.0.1:5000/ +admin_token = 999888777666 + diff --git a/nova/api/auth.py b/nova/api/auth.py new file mode 100644 index 000000000..034057d77 --- /dev/null +++ b/nova/api/auth.py @@ -0,0 +1,91 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 OpenStack, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# 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. +""" +Common Auth Middleware. + +""" + +from nova import context +from nova import flags +from nova import wsgi +import webob.dec +import webob.exc + + +FLAGS = flags.FLAGS +flags.DEFINE_boolean('use_forwarded_for', False, + 'Treat X-Forwarded-For as the canonical remote address. ' + 'Only enable this if you have a sanitizing proxy.') + + +class InjectContext(wsgi.Middleware): + """Add a 'nova.context' to WSGI environ.""" + def __init__(self, context, *args, **kwargs): + self.context = context + super(InjectContext, self).__init__(*args, **kwargs) + + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + req.environ['nova.context'] = self.context + return self.application + + +class AdminContext(wsgi.Middleware): + """Return an admin context no matter what""" + + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + # Build a context, including the auth_token... + remote_address = req.remote_addr + if FLAGS.use_forwarded_for: + remote_address = req.headers.get('X-Forwarded-For', remote_address) + ctx = context.RequestContext('admin', + 'admin', + is_admin=True, + remote_address=remote_address) + + req.environ['nova.context'] = ctx + return self.application + + +class KeystoneContext(wsgi.Middleware): + """Make a request context from keystone headers""" + + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + try: + user_id = req.headers['X_USER'] + except: + return webob.exc.HTTPUnauthorized() + # get the roles + roles = [r.strip() for r in req.headers.get('X_ROLE', '').split(',')] + project_id = req.headers['X_TENANT'] + # Get the auth token + auth_token = req.headers.get('X_AUTH_TOKEN', + req.headers.get('X_STORAGE_TOKEN')) + + # Build a context, including the auth_token... + remote_address = req.remote_addr + if FLAGS.use_forwarded_for: + remote_address = req.headers.get('X-Forwarded-For', remote_address) + ctx = context.RequestContext(user_id, + project_id, + roles=roles, + auth_token=auth_token, + remote_address=remote_address) + + req.environ['nova.context'] = ctx + return self.application diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 8b6e47cfb..f3e6fa124 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -20,6 +20,7 @@ Starting point for routing EC2 requests. """ +import httplib2 import webob import webob.dec import webob.exc @@ -37,15 +38,17 @@ from nova.auth import manager FLAGS = flags.FLAGS LOG = logging.getLogger("nova.api") -flags.DEFINE_boolean('use_forwarded_for', False, - 'Treat X-Forwarded-For as the canonical remote address. ' - 'Only enable this if you have a sanitizing proxy.') flags.DEFINE_integer('lockout_attempts', 5, 'Number of failed auths before lockout.') flags.DEFINE_integer('lockout_minutes', 15, 'Number of minutes to lockout if triggered.') flags.DEFINE_integer('lockout_window', 15, 'Number of minutes for lockout window.') +flags.DEFINE_integer('lockout_window', 15, + 'Number of minutes for lockout window.') +flags.DEFINE_string('keystone_ec2_url', + 'http://localhost:5000/v2.0/ec2tokens', + 'URL to get token from ec2 request.') class RequestLogging(wsgi.Middleware): @@ -138,6 +141,49 @@ class Lockout(wsgi.Middleware): return res +class ToToken(wsgi.Middleware): + """Authenticate an EC2 request with keystone and convert to token.""" + + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + # Read request signature and access id. + try: + signature = req.params['Signature'] + access = req.params['AWSAccessKeyId'] + except KeyError, e: + raise webob.exc.HTTPBadRequest() + + # Make a copy of args for authentication and signature verification. + auth_params = dict(req.params) + # Not part of authentication args + auth_params.pop('Signature') + + # Authenticate the request. + client = httplib2.Http() + creds = {'ec2Credentials': {'access': access, + 'signature': signature, + 'host': req.host, + 'verb': req.method, + 'path': req.path, + 'params': auth_params, + }} + headers = {'Content-Type': 'application/json'}, + resp, content = client.request(FLAGS.keystone_ec2_url, + 'POST', + headers=headers, + body=utils.dumps(creds)) + # NOTE(vish): We could save a call to keystone by + # having keystone return token, tenant, + # user, and roles from this call. + result = utils.loads(content) + # TODO(vish): check for errors + token_id = result['auth']['token']['id'] + + # Authenticated! + req.headers['X-Auth-Token'] = token_id + return self.application + + class Authenticate(wsgi.Middleware): """Authenticate an EC2 request and add 'nova.context' to WSGI environ.""" @@ -196,6 +242,7 @@ class Requestify(wsgi.Middleware): @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): + LOG.audit("in request", context=req.environ['nova.context']) non_args = ['Action', 'Signature', 'AWSAccessKeyId', 'SignatureMethod', 'SignatureVersion', 'Version', 'Timestamp'] args = dict(req.params) @@ -286,6 +333,8 @@ class Authorizer(wsgi.Middleware): @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): context = req.environ['nova.context'] + LOG.warn(req.environ['nova.context'].__dict__) + LOG.warn(req.environ['ec2.request'].__dict__) controller = req.environ['ec2.request'].controller.__class__.__name__ action = req.environ['ec2.request'].action allowed_roles = self.action_roles[controller].get(action, ['none']) diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index d11fbf788..a095dd90a 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -32,6 +32,7 @@ from nova import utils from nova import wsgi import nova.api.openstack.auth from nova.api import openstack +from nova.api import auth as api_auth from nova.api.openstack import auth from nova.api.openstack import extensions from nova.api.openstack import versions @@ -83,9 +84,9 @@ def wsgi_app(inner_app10=None, inner_app11=None, fake_auth=True, ctxt = fake_auth_context else: ctxt = context.RequestContext('fake', 'fake') - api10 = openstack.FaultWrapper(wsgi.InjectContext(ctxt, + api10 = openstack.FaultWrapper(api_auth.InjectContext(ctxt, limits.RateLimitingMiddleware(inner_app10))) - api11 = openstack.FaultWrapper(wsgi.InjectContext(ctxt, + api11 = openstack.FaultWrapper(api_auth.InjectContext(ctxt, limits.RateLimitingMiddleware( extensions.ExtensionMiddleware(inner_app11)))) else: diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index 2011ae756..526d1c490 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -32,6 +32,7 @@ from nova import context from nova import exception from nova import test from nova import wsgi +from nova.api import auth from nova.api import ec2 from nova.api.ec2 import apirequest from nova.api.ec2 import cloud @@ -199,7 +200,7 @@ class ApiEc2TestCase(test.TestCase): # NOTE(vish): skipping the Authorizer roles = ['sysadmin', 'netadmin'] ctxt = context.RequestContext('fake', 'fake', roles=roles) - self.app = wsgi.InjectContext(ctxt, + self.app = auth.InjectContext(ctxt, ec2.Requestify(ec2.Authorizer(ec2.Executor()), 'nova.api.ec2.cloud.CloudController')) diff --git a/nova/wsgi.py b/nova/wsgi.py index c8ddb97d7..eae3afcb4 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -274,18 +274,6 @@ class Middleware(Application): return self.process_response(response) -class InjectContext(Middleware): - """Add a 'nova.context' to WSGI environ.""" - def __init__(self, context, *args, **kwargs): - self.context = context - super(InjectContext, self).__init__(*args, **kwargs) - - @webob.dec.wsgify(RequestClass=Request) - def __call__(self, req): - req.environ['nova.context'] = self.context - return self.application - - class Debug(Middleware): """Helper class for debugging a WSGI application. -- cgit From e294303750f032f22dadaba7eb0c743effa8c3f5 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 11 Aug 2011 21:30:07 -0700 Subject: remove accidentally duplicated flag --- nova/api/ec2/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index f3e6fa124..a93285dba 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -44,8 +44,6 @@ flags.DEFINE_integer('lockout_minutes', 15, 'Number of minutes to lockout if triggered.') flags.DEFINE_integer('lockout_window', 15, 'Number of minutes for lockout window.') -flags.DEFINE_integer('lockout_window', 15, - 'Number of minutes for lockout window.') flags.DEFINE_string('keystone_ec2_url', 'http://localhost:5000/v2.0/ec2tokens', 'URL to get token from ec2 request.') -- cgit From 7295b93192d2b151c108d7631c3b404ef65fdedf Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 12 Aug 2011 01:21:47 -0700 Subject: remove extra log statements --- nova/api/ec2/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index a93285dba..1ae9a126a 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -240,7 +240,6 @@ class Requestify(wsgi.Middleware): @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): - LOG.audit("in request", context=req.environ['nova.context']) non_args = ['Action', 'Signature', 'AWSAccessKeyId', 'SignatureMethod', 'SignatureVersion', 'Version', 'Timestamp'] args = dict(req.params) @@ -331,8 +330,6 @@ class Authorizer(wsgi.Middleware): @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): context = req.environ['nova.context'] - LOG.warn(req.environ['nova.context'].__dict__) - LOG.warn(req.environ['ec2.request'].__dict__) controller = req.environ['ec2.request'].controller.__class__.__name__ action = req.environ['ec2.request'].action allowed_roles = self.action_roles[controller].get(action, ['none']) -- cgit From 26216f23741abb92cf520d9d144a1fb303567ed2 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Fri, 12 Aug 2011 14:09:25 -0400 Subject: fix merges from trunk --- nova/tests/api/openstack/test_servers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 4ac1168fe..99c947467 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1656,13 +1656,13 @@ class ServersTest(test.TestCase): def test_create_instance_v1_1_invalid_flavor_id_int(self): self._setup_for_create_instance() - image_href = 'http://localhost/v1.1/images/2' + image_href = 'http://localhost/v1.1/123/images/2' flavor_ref = -1 body = dict(server=dict( name='server_test', imageRef=image_href, flavorRef=flavor_ref, metadata={'hello': 'world', 'open': 'stack'}, personality={})) - req = webob.Request.blank('/v1.1/servers') + req = webob.Request.blank('/v1.1/123/servers') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" -- cgit From 9ab61aaa194a787b41b1d634c1b56c98574dcbc9 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 12 Aug 2011 11:28:47 -0700 Subject: updates from review --- nova/api/auth.py | 26 +++++--------------------- nova/api/ec2/__init__.py | 4 ++-- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/nova/api/auth.py b/nova/api/auth.py index 034057d77..cd3e3e8a0 100644 --- a/nova/api/auth.py +++ b/nova/api/auth.py @@ -18,11 +18,12 @@ Common Auth Middleware. """ +import webob.dec +import webob.exc + from nova import context from nova import flags from nova import wsgi -import webob.dec -import webob.exc FLAGS = flags.FLAGS @@ -33,6 +34,7 @@ flags.DEFINE_boolean('use_forwarded_for', False, class InjectContext(wsgi.Middleware): """Add a 'nova.context' to WSGI environ.""" + def __init__(self, context, *args, **kwargs): self.context = context super(InjectContext, self).__init__(*args, **kwargs) @@ -43,24 +45,6 @@ class InjectContext(wsgi.Middleware): return self.application -class AdminContext(wsgi.Middleware): - """Return an admin context no matter what""" - - @webob.dec.wsgify(RequestClass=wsgi.Request) - def __call__(self, req): - # Build a context, including the auth_token... - remote_address = req.remote_addr - if FLAGS.use_forwarded_for: - remote_address = req.headers.get('X-Forwarded-For', remote_address) - ctx = context.RequestContext('admin', - 'admin', - is_admin=True, - remote_address=remote_address) - - req.environ['nova.context'] = ctx - return self.application - - class KeystoneContext(wsgi.Middleware): """Make a request context from keystone headers""" @@ -68,7 +52,7 @@ class KeystoneContext(wsgi.Middleware): def __call__(self, req): try: user_id = req.headers['X_USER'] - except: + except KeyError: return webob.exc.HTTPUnauthorized() # get the roles roles = [r.strip() for r in req.headers.get('X_ROLE', '').split(',')] diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 1ae9a126a..2ae370f88 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -148,7 +148,7 @@ class ToToken(wsgi.Middleware): try: signature = req.params['Signature'] access = req.params['AWSAccessKeyId'] - except KeyError, e: + except KeyError: raise webob.exc.HTTPBadRequest() # Make a copy of args for authentication and signature verification. @@ -191,7 +191,7 @@ class Authenticate(wsgi.Middleware): try: signature = req.params['Signature'] access = req.params['AWSAccessKeyId'] - except KeyError, e: + except KeyError: raise webob.exc.HTTPBadRequest() # Make a copy of args for authentication and signature verification. -- cgit From 93207c19c72aff5eb2c99b0b42649a75def35cf0 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Fri, 12 Aug 2011 11:29:25 -0700 Subject: removed admincontext middleware --- etc/nova/api-paste.ini | 3 --- 1 file changed, 3 deletions(-) diff --git a/etc/nova/api-paste.ini b/etc/nova/api-paste.ini index ec3d88caf..b540509a2 100644 --- a/etc/nova/api-paste.ini +++ b/etc/nova/api-paste.ini @@ -112,9 +112,6 @@ paste.app_factory = nova.api.openstack.versions:Versions.factory # Shared # ########## -[filter:admincontext] -paste.filter_factory = nova.api.auth:AdminContext.factory - [filter:keystonecontext] paste.filter_factory = nova.api.auth:KeystoneContext.factory -- cgit From 19a4ddaf157ebb388cce37ddc142dfad304b8cf0 Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Fri, 12 Aug 2011 16:48:13 -0700 Subject: Added add securitygroup to instance and remove securitygroup from instance functionality --- nova/api/openstack/contrib/security_groups.py | 199 ++++++++++++-- nova/api/openstack/create_instance_helper.py | 30 ++- nova/db/api.py | 6 + nova/db/sqlalchemy/api.py | 15 ++ .../api/openstack/contrib/test_security_groups.py | 299 +++++++++++++++++++++ 5 files changed, 530 insertions(+), 19 deletions(-) diff --git a/nova/api/openstack/contrib/security_groups.py b/nova/api/openstack/contrib/security_groups.py index 6c57fbb51..a104a42e4 100644 --- a/nova/api/openstack/contrib/security_groups.py +++ b/nova/api/openstack/contrib/security_groups.py @@ -25,10 +25,11 @@ from nova import db from nova import exception from nova import flags from nova import log as logging +from nova import rpc from nova.api.openstack import common from nova.api.openstack import extensions from nova.api.openstack import wsgi - +from nova.compute import power_state from xml.dom import minidom @@ -73,33 +74,28 @@ class SecurityGroupController(object): context, rule)] return security_group - def show(self, req, id): - """Return data about the given security group.""" - context = req.environ['nova.context'] + def _get_security_group(self, context, id): try: id = int(id) security_group = db.security_group_get(context, id) except ValueError: - msg = _("Security group id is not integer") - return exc.HTTPBadRequest(explanation=msg) + msg = _("Security group id should be integer") + raise exc.HTTPBadRequest(explanation=msg) except exception.NotFound as exp: - return exc.HTTPNotFound(explanation=unicode(exp)) + raise exc.HTTPNotFound(explanation=unicode(exp)) + return security_group + def show(self, req, id): + """Return data about the given security group.""" + context = req.environ['nova.context'] + security_group = self._get_security_group(context, id) return {'security_group': self._format_security_group(context, security_group)} def delete(self, req, id): """Delete a security group.""" context = req.environ['nova.context'] - try: - id = int(id) - security_group = db.security_group_get(context, id) - except ValueError: - msg = _("Security group id is not integer") - return exc.HTTPBadRequest(explanation=msg) - except exception.SecurityGroupNotFound as exp: - return exc.HTTPNotFound(explanation=unicode(exp)) - + security_group = self._get_security_group(context, id) LOG.audit(_("Delete security group %s"), id, context=context) db.security_group_destroy(context, security_group.id) @@ -172,6 +168,135 @@ class SecurityGroupController(object): "than 255 characters.") % typ raise exc.HTTPBadRequest(explanation=msg) + def associate(self, req, id, body): + context = req.environ['nova.context'] + + if not body: + raise exc.HTTPUnprocessableEntity() + + if not 'security_group_associate' in body: + raise exc.HTTPUnprocessableEntity() + + security_group = self._get_security_group(context, id) + + servers = body['security_group_associate'].get('servers') + + if not servers: + msg = _("No servers found") + return exc.HTTPBadRequest(explanation=msg) + + hosts = set() + for server in servers: + if server['id']: + try: + # check if the server exists + inst = db.instance_get(context, server['id']) + #check if the security group is assigned to the server + if self._is_security_group_associated_to_server( + security_group, inst['id']): + msg = _("Security group %s is already associated with" + " the instance %s") % (security_group['id'], + server['id']) + raise exc.HTTPBadRequest(explanation=msg) + + #check if the instance is in running state + if inst['state'] != power_state.RUNNING: + msg = _("Server %s is not in the running state")\ + % server['id'] + raise exc.HTTPBadRequest(explanation=msg) + + hosts.add(inst['host']) + except exception.InstanceNotFound as exp: + return exc.HTTPNotFound(explanation=unicode(exp)) + + # Associate security group with the server in the db + for server in servers: + if server['id']: + db.instance_add_security_group(context.elevated(), + server['id'], + security_group['id']) + + for host in hosts: + rpc.cast(context, + db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "refresh_security_group_rules", + "args": {"security_group_id": security_group['id']}}) + + return exc.HTTPAccepted() + + def _is_security_group_associated_to_server(self, security_group, + instance_id): + if not security_group: + return False + + instances = security_group.get('instances') + if not instances: + return False + + inst_id = None + for inst_id in (instance['id'] for instance in instances \ + if instance_id == instance['id']): + return True + + return False + + def disassociate(self, req, id, body): + context = req.environ['nova.context'] + + if not body: + raise exc.HTTPUnprocessableEntity() + + if not 'security_group_disassociate' in body: + raise exc.HTTPUnprocessableEntity() + + security_group = self._get_security_group(context, id) + + servers = body['security_group_disassociate'].get('servers') + + if not servers: + msg = _("No servers found") + return exc.HTTPBadRequest(explanation=msg) + + hosts = set() + for server in servers: + if server['id']: + try: + # check if the instance exists + inst = db.instance_get(context, server['id']) + # Check if the security group is not associated + # with the instance + if not self._is_security_group_associated_to_server( + security_group, inst['id']): + msg = _("Security group %s is not associated with the" + "instance %s") % (security_group['id'], + server['id']) + raise exc.HTTPBadRequest(explanation=msg) + + #check if the instance is in running state + if inst['state'] != power_state.RUNNING: + msg = _("Server %s is not in the running state")\ + % server['id'] + raise exp.HTTPBadRequest(explanation=msg) + + hosts.add(inst['host']) + except exception.InstanceNotFound as exp: + return exc.HTTPNotFound(explanation=unicode(exp)) + + # Disassociate security group from the server + for server in servers: + if server['id']: + db.instance_remove_security_group(context.elevated(), + server['id'], + security_group['id']) + + for host in hosts: + rpc.cast(context, + db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "refresh_security_group_rules", + "args": {"security_group_id": security_group['id']}}) + + return exc.HTTPAccepted() + class SecurityGroupRulesController(SecurityGroupController): @@ -226,9 +351,9 @@ class SecurityGroupRulesController(SecurityGroupController): security_group_rule = db.security_group_rule_create(context, values) self.compute_api.trigger_security_group_rules_refresh(context, - security_group_id=security_group['id']) + security_group_id=security_group['id']) - return {'security_group_rule': self._format_security_group_rule( + return {"security_group_rule": self._format_security_group_rule( context, security_group_rule)} @@ -368,6 +493,10 @@ class Security_groups(extensions.ExtensionDescriptor): res = extensions.ResourceExtension('os-security-groups', controller=SecurityGroupController(), + member_actions={ + 'associate': 'POST', + 'disassociate': 'POST' + }, deserializer=deserializer, serializer=serializer) @@ -405,6 +534,40 @@ class SecurityGroupXMLDeserializer(wsgi.MetadataXMLDeserializer): security_group['description'] = self.extract_text(desc_node) return {'body': {'security_group': security_group}} + def _get_servers(self, node): + servers_dict = {'servers': []} + if node is not None: + servers_node = self.find_first_child_named(node, + 'servers') + if servers_node is not None: + for server_node in self.find_children_named(servers_node, + "server"): + servers_dict['servers'].append( + {"id": self.extract_text(server_node)}) + return servers_dict + + def associate(self, string): + """Deserialize an xml-formatted security group associate request""" + dom = minidom.parseString(string) + node = self.find_first_child_named(dom, + 'security_group_associate') + result = {'body': {}} + if node: + result['body']['security_group_associate'] = \ + self._get_servers(node) + return result + + def disassociate(self, string): + """Deserialize an xml-formatted security group disassociate request""" + dom = minidom.parseString(string) + node = self.find_first_child_named(dom, + 'security_group_disassociate') + result = {'body': {}} + if node: + result['body']['security_group_disassociate'] = \ + self._get_servers(node) + return result + class SecurityGroupRulesXMLDeserializer(wsgi.MetadataXMLDeserializer): """ diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 1425521a9..4ceb972c0 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -111,6 +111,16 @@ class CreateInstanceHelper(object): if personality: injected_files = self._get_injected_files(personality) + sg_names = [] + security_groups = server_dict.get('security_groups') + if security_groups: + sg_names = [sg['name'] for sg in security_groups if sg.get('name')] + if not sg_names: + sg_names.append('default') + + sg_names = list(set(sg_names)) + LOG.debug(sg_names) + try: flavor_id = self.controller._flavor_id_from_req_data(body) except ValueError as error: @@ -161,7 +171,8 @@ class CreateInstanceHelper(object): zone_blob=zone_blob, reservation_id=reservation_id, min_count=min_count, - max_count=max_count)) + max_count=max_count, + security_group=sg_names)) except quota.QuotaError as error: self._handle_quota_error(error) except exception.ImageNotFound as error: @@ -170,6 +181,8 @@ class CreateInstanceHelper(object): except exception.FlavorNotFound as error: msg = _("Invalid flavorRef provided.") raise exc.HTTPBadRequest(explanation=msg) + except exception.SecurityGroupNotFound as error: + raise exc.HTTPBadRequest(explanation=unicode(error)) # Let the caller deal with unhandled exceptions. def _handle_quota_error(self, error): @@ -454,6 +467,8 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer): if personality is not None: server["personality"] = personality + server["security_groups"] = self._extract_security_groups(server_node) + return server def _extract_personality(self, server_node): @@ -470,3 +485,16 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer): return personality else: return None + + def _extract_security_groups(self, server_node): + """Marshal the security_groups attribute of a parsed request""" + node = self.find_first_child_named(server_node, "security_groups") + security_groups = [] + if node is not None: + for sg_node in self.find_children_named(node, "security_group"): + item = {} + name_node = self.find_first_child_named(sg_node, "name") + if name_node: + item["name"] = self.extract_text(name_node) + security_groups.append(item) + return security_groups diff --git a/nova/db/api.py b/nova/db/api.py index 0f2218752..cf814d43e 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -570,6 +570,12 @@ def instance_add_security_group(context, instance_id, security_group_id): security_group_id) +def instance_remove_security_group(context, instance_id, security_group_id): + """Disassociate the given security group from the given instance.""" + return IMPL.instance_remove_security_group(context, instance_id, + security_group_id) + + def instance_get_vcpu_sum_by_host_and_project(context, hostname, proj_id): """Get instances.vcpus by host and project.""" return IMPL.instance_get_vcpu_sum_by_host_and_project(context, diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index e5d35a20b..ba16f9109 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1482,6 +1482,19 @@ def instance_add_security_group(context, instance_id, security_group_id): instance_ref.save(session=session) +@require_context +def instance_remove_security_group(context, instance_id, security_group_id): + """Disassociate the given security group from the given instance""" + session = get_session() + + session.query(models.SecurityGroupInstanceAssociation).\ + filter_by(instance_id=instance_id).\ + filter_by(security_group_id=security_group_id).\ + update({'deleted': True, + 'deleted_at': utils.utcnow(), + 'updated_at': literal_column('updated_at')}) + + @require_context def instance_get_vcpu_sum_by_host_and_project(context, hostname, proj_id): session = get_session() @@ -2456,6 +2469,7 @@ def security_group_get(context, security_group_id, session=None): filter_by(deleted=can_read_deleted(context),).\ filter_by(id=security_group_id).\ options(joinedload_all('rules')).\ + options(joinedload_all('instances')).\ first() else: result = session.query(models.SecurityGroup).\ @@ -2463,6 +2477,7 @@ def security_group_get(context, security_group_id, session=None): filter_by(id=security_group_id).\ filter_by(project_id=context.project_id).\ options(joinedload_all('rules')).\ + options(joinedload_all('instances')).\ first() if not result: raise exception.SecurityGroupNotFound( diff --git a/nova/tests/api/openstack/contrib/test_security_groups.py b/nova/tests/api/openstack/contrib/test_security_groups.py index 4317880ca..894b0c591 100644 --- a/nova/tests/api/openstack/contrib/test_security_groups.py +++ b/nova/tests/api/openstack/contrib/test_security_groups.py @@ -15,10 +15,13 @@ # under the License. import json +import mox +import nova import unittest import webob from xml.dom import minidom +from nova import exception from nova import test from nova.api.openstack.contrib import security_groups from nova.tests.api.openstack import fakes @@ -51,6 +54,28 @@ def _create_security_group_request_dict(security_group): return {'security_group': sg} +def return_server(context, server_id): + return {'id': server_id, 'state': 0x01, 'host': "localhost"} + + +def return_non_running_server(context, server_id): + return {'id': server_id, 'state': 0x02, + 'host': "localhost"} + + +def return_security_group(context, group_id): + return {'id': group_id, "instances": [ + {'id': 1}]} + + +def return_security_group_without_instances(context, group_id): + return {'id': group_id} + + +def return_server_nonexistant(context, server_id): + raise exception.InstanceNotFound() + + class TestSecurityGroups(test.TestCase): def setUp(self): super(TestSecurityGroups, self).setUp() @@ -325,6 +350,280 @@ class TestSecurityGroups(test.TestCase): response = self._delete_security_group(11111111) self.assertEquals(response.status_int, 404) + def test_associate_by_non_existing_security_group_id(self): + req = webob.Request.blank('/v1.1/os-security-groups/111111/associate') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + body_dict = {"security_group_associate": { + "servers": [ + {"id": '2'} + ] + } + } + req.body = json.dumps(body_dict) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 404) + + def test_associate_by_invalid_security_group_id(self): + req = webob.Request.blank('/v1.1/os-security-groups/invalid/associate') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + body_dict = {"security_group_associate": { + "servers": [ + {"id": "2"} + ] + } + } + req.body = json.dumps(body_dict) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 400) + + def test_associate_without_body(self): + req = webob.Request.blank('/v1.1/os-security-groups/1/associate') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + req.body = json.dumps(None) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 422) + + def test_associate_no_security_group_element(self): + req = webob.Request.blank('/v1.1/os-security-groups/1/associate') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + body_dict = {"security_group_associate_invalid": { + "servers": [ + {"id": "2"} + ] + } + } + req.body = json.dumps(body_dict) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 422) + + def test_associate_no_instances(self): + #self.stubs.Set(nova.db.api, 'instance_get', return_server) + self.stubs.Set(nova.db, 'security_group_get', return_security_group) + req = webob.Request.blank('/v1.1/os-security-groups/1/associate') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + body_dict = {"security_group_associate": { + "servers": [ + ] + } + } + req.body = json.dumps(body_dict) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 400) + + def test_associate_non_existing_instance(self): + self.stubs.Set(nova.db, 'instance_get', return_server_nonexistant) + self.stubs.Set(nova.db, 'security_group_get', return_security_group) + req = webob.Request.blank('/v1.1/os-security-groups/1/associate') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + body_dict = {"security_group_associate": { + "servers": [ + {'id': 2} + ] + } + } + req.body = json.dumps(body_dict) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 404) + + def test_associate_non_running_instance(self): + self.stubs.Set(nova.db, 'instance_get', return_non_running_server) + self.stubs.Set(nova.db, 'security_group_get', + return_security_group_without_instances) + req = webob.Request.blank('/v1.1/os-security-groups/1/associate') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + body_dict = {"security_group_associate": { + "servers": [ + {'id': 1} + ] + } + } + req.body = json.dumps(body_dict) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 400) + + def test_associate_already_associated_security_group_to_instance(self): + self.stubs.Set(nova.db, 'instance_get', return_server) + self.stubs.Set(nova.db, 'security_group_get', return_security_group) + req = webob.Request.blank('/v1.1/os-security-groups/1/associate') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + body_dict = {"security_group_associate": { + "servers": [ + {'id': 1} + ] + } + } + req.body = json.dumps(body_dict) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 400) + + def test_associate(self): + self.stubs.Set(nova.db, 'instance_get', return_server) + self.mox.StubOutWithMock(nova.db, 'instance_add_security_group') + nova.db.instance_add_security_group(mox.IgnoreArg(), + mox.IgnoreArg(), + mox.IgnoreArg()) + self.stubs.Set(nova.db, 'security_group_get', + return_security_group_without_instances) + self.mox.ReplayAll() + + req = webob.Request.blank('/v1.1/os-security-groups/1/associate') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + body_dict = {"security_group_associate": { + "servers": [ + {'id': 1} + ] + } + } + req.body = json.dumps(body_dict) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 202) + + def test_disassociate_by_non_existing_security_group_id(self): + req = webob.Request.blank('/v1.1/os-security-groups/1111/disassociate') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + body_dict = {"security_group_disassociate": { + "servers": [ + {"id": "2"} + ] + } + } + req.body = json.dumps(body_dict) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 404) + + def test_disassociate_by_invalid_security_group_id(self): + req = webob.Request.blank('/v1.1/os-security-groups/id/disassociate') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + body_dict = {"security_group_disassociate": { + "servers": [ + {"id": "2"} + ] + } + } + req.body = json.dumps(body_dict) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 400) + + def test_disassociate_without_body(self): + req = webob.Request.blank('/v1.1/os-security-groups/1/disassociate') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + req.body = json.dumps(None) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 422) + + def test_disassociate_no_security_group_element(self): + req = webob.Request.blank('/v1.1/os-security-groups/1/disassociate') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + body_dict = {"security_group_disassociate_invalid": { + "servers": [ + {"id": "2"} + ] + } + } + req.body = json.dumps(body_dict) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 422) + + def test_disassociate_no_instances(self): + #self.stubs.Set(nova.db.api, 'instance_get', return_server) + self.stubs.Set(nova.db, 'security_group_get', return_security_group) + req = webob.Request.blank('/v1.1/os-security-groups/1/disassociate') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + body_dict = {"security_group_disassociate": { + "servers": [ + ] + } + } + req.body = json.dumps(body_dict) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 400) + + def test_disassociate_non_existing_instance(self): + self.stubs.Set(nova.db, 'instance_get', return_server_nonexistant) + self.stubs.Set(nova.db, 'security_group_get', return_security_group) + req = webob.Request.blank('/v1.1/os-security-groups/1/disassociate') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + body_dict = {"security_group_disassociate": { + "servers": [ + {'id': 2} + ] + } + } + req.body = json.dumps(body_dict) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 404) + + def test_disassociate_non_running_instance(self): + self.stubs.Set(nova.db, 'instance_get', return_non_running_server) + self.stubs.Set(nova.db, 'security_group_get', + return_security_group_without_instances) + req = webob.Request.blank('/v1.1/os-security-groups/1/disassociate') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + body_dict = {"security_group_disassociate": { + "servers": [ + {'id': 1} + ] + } + } + req.body = json.dumps(body_dict) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 400) + + def test_disassociate_not_associated_security_group_to_instance(self): + self.stubs.Set(nova.db, 'instance_get', return_server) + self.stubs.Set(nova.db, 'security_group_get', return_security_group) + req = webob.Request.blank('/v1.1/os-security-groups/1/disassociate') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + body_dict = {"security_group_disassociate": { + "servers": [ + {'id': 2} + ] + } + } + req.body = json.dumps(body_dict) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 400) + + def test_disassociate(self): + self.stubs.Set(nova.db, 'instance_get', return_server) + self.mox.StubOutWithMock(nova.db, 'instance_remove_security_group') + nova.db.instance_remove_security_group(mox.IgnoreArg(), + mox.IgnoreArg(), + mox.IgnoreArg()) + self.stubs.Set(nova.db, 'security_group_get', + return_security_group) + self.mox.ReplayAll() + + req = webob.Request.blank('/v1.1/os-security-groups/1/disassociate') + req.headers['Content-Type'] = 'application/json' + req.method = 'POST' + body_dict = {"security_group_disassociate": { + "servers": [ + {'id': 1} + ] + } + } + req.body = json.dumps(body_dict) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 202) + class TestSecurityGroupRules(test.TestCase): def setUp(self): -- cgit From a1dc7e0dbcff7130adb0274e6628ce30d1ac83c1 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Mon, 15 Aug 2011 09:35:44 -0400 Subject: Dryed up contructors --- nova/api/openstack/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 8805c4ef6..3b74fefc9 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -97,10 +97,13 @@ class APIRouter(base_wsgi.Router): def __init__(self, ext_mgr=None): self.server_members = {} - mapper = routes.Mapper() + mapper = self._mapper() self._setup_routes(mapper) super(APIRouter, self).__init__(mapper) + def _mapper(self): + return routes.Mapper() + def _setup_routes(self, mapper): raise NotImplementedError(_("You must implement _setup_routes.")) @@ -190,11 +193,8 @@ class APIRouterV10(APIRouter): class APIRouterV11(APIRouter): """Define routes specific to OpenStack API V1.1.""" - def __init__(self, ext_mgr=None): - mapper = ProjectMapper() - self.server_members = {} - self._setup_routes(mapper) - super(APIRouter, self).__init__(mapper) + def _mapper(self): + return ProjectMapper() def _setup_routes(self, mapper): self._setup_base_routes(mapper, '1.1') -- cgit From 157047664a1ff7f6061cc122df533d3500e926b1 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Mon, 15 Aug 2011 14:45:08 -0400 Subject: Updated rate limiting tests to use tenants --- nova/tests/api/openstack/__init__.py | 28 ++++++++++++------------- nova/tests/api/openstack/test_server_actions.py | 4 ++-- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/nova/tests/api/openstack/__init__.py b/nova/tests/api/openstack/__init__.py index 458434a81..8314d5b02 100644 --- a/nova/tests/api/openstack/__init__.py +++ b/nova/tests/api/openstack/__init__.py @@ -44,14 +44,14 @@ class RateLimitingMiddlewareTest(test.TestCase): action = middleware.get_action_name(req) self.assertEqual(action, action_name) - verify('PUT', '/servers/4', 'PUT') - verify('DELETE', '/servers/4', 'DELETE') - verify('POST', '/images/4', 'POST') - verify('POST', '/servers/4', 'POST servers') - verify('GET', '/foo?a=4&changes-since=never&b=5', 'GET changes-since') - verify('GET', '/foo?a=4&monkeys-since=never&b=5', None) - verify('GET', '/servers/4', None) - verify('HEAD', '/servers/4', None) + verify('PUT', '/fake/servers/4', 'PUT') + verify('DELETE', '/fake/servers/4', 'DELETE') + verify('POST', '/fake/images/4', 'POST') + verify('POST', '/fake/servers/4', 'POST servers') + verify('GET', '/fake/foo?a=4&changes-since=never&b=5', 'GET changes-since') + verify('GET', '/fake/foo?a=4&monkeys-since=never&b=5', None) + verify('GET', '/fake/servers/4', None) + verify('HEAD', '/fake/servers/4', None) def exhaust(self, middleware, method, url, username, times): req = Request.blank(url, dict(REQUEST_METHOD=method), @@ -67,13 +67,13 @@ class RateLimitingMiddlewareTest(test.TestCase): def test_single_action(self): middleware = RateLimitingMiddleware(simple_wsgi) - self.exhaust(middleware, 'DELETE', '/servers/4', 'usr1', 100) - self.exhaust(middleware, 'DELETE', '/servers/4', 'usr2', 100) + self.exhaust(middleware, 'DELETE', '/fake/servers/4', 'usr1', 100) + self.exhaust(middleware, 'DELETE', '/fake/servers/4', 'usr2', 100) def test_POST_servers_action_implies_POST_action(self): middleware = RateLimitingMiddleware(simple_wsgi) - self.exhaust(middleware, 'POST', '/servers/4', 'usr1', 10) - self.exhaust(middleware, 'POST', '/images/4', 'usr2', 10) + self.exhaust(middleware, 'POST', '/fake/servers/4', 'usr1', 10) + self.exhaust(middleware, 'POST', '/fake/images/4', 'usr2', 10) self.assertTrue(set(middleware.limiter._levels) == \ set(['usr1:POST', 'usr1:POST servers', 'usr2:POST'])) @@ -81,11 +81,11 @@ class RateLimitingMiddlewareTest(test.TestCase): middleware = RateLimitingMiddleware(simple_wsgi) # Use up all of our "POST" allowance for the minute, 5 times for i in range(5): - self.exhaust(middleware, 'POST', '/servers/4', 'usr1', 10) + self.exhaust(middleware, 'POST', '/fake/servers/4', 'usr1', 10) # Reset the 'POST' action counter. del middleware.limiter._levels['usr1:POST'] # All 50 daily "POST servers" actions should be all used up - self.exhaust(middleware, 'POST', '/servers/4', 'usr1', 0) + self.exhaust(middleware, 'POST', '/fake/servers/4', 'usr1', 0) def test_proxy_ctor_works(self): middleware = RateLimitingMiddleware(simple_wsgi) diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index bd98d7d1a..627e208c4 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -489,7 +489,7 @@ class ServerActionsTestV11(test.TestCase): def test_server_bad_body(self): body = {} - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) @@ -498,7 +498,7 @@ class ServerActionsTestV11(test.TestCase): def test_server_unknown_action(self): body = {'sockTheFox': {'fakekey': '1234'}} - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/fake/servers/1/action') req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) -- cgit From 32ab7846b7781c557429600f419b9f7c8768cdce Mon Sep 17 00:00:00 2001 From: William Wolf Date: Mon, 15 Aug 2011 15:00:42 -0400 Subject: pep8 fix --- nova/tests/api/openstack/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/tests/api/openstack/__init__.py b/nova/tests/api/openstack/__init__.py index 8314d5b02..7d44489a1 100644 --- a/nova/tests/api/openstack/__init__.py +++ b/nova/tests/api/openstack/__init__.py @@ -48,7 +48,8 @@ class RateLimitingMiddlewareTest(test.TestCase): verify('DELETE', '/fake/servers/4', 'DELETE') verify('POST', '/fake/images/4', 'POST') verify('POST', '/fake/servers/4', 'POST servers') - verify('GET', '/fake/foo?a=4&changes-since=never&b=5', 'GET changes-since') + verify('GET', '/fake/foo?a=4&changes-since=never&b=5', + 'GET changes-since') verify('GET', '/fake/foo?a=4&monkeys-since=never&b=5', None) verify('GET', '/fake/servers/4', None) verify('HEAD', '/fake/servers/4', None) -- cgit From 8e7163cd413eebd2e08b5ad32d155f643e972740 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Mon, 15 Aug 2011 15:41:07 -0400 Subject: put tenant_id back in places where it was --- nova/api/openstack/contrib/security_groups.py | 6 +++--- nova/tests/api/openstack/contrib/test_security_groups.py | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/nova/api/openstack/contrib/security_groups.py b/nova/api/openstack/contrib/security_groups.py index 6c1043c83..d1230df69 100644 --- a/nova/api/openstack/contrib/security_groups.py +++ b/nova/api/openstack/contrib/security_groups.py @@ -56,7 +56,7 @@ class SecurityGroupController(object): if rule.group_id: source_group = db.security_group_get(context, rule.group_id) sg_rule['group'] = {'name': source_group.name, - 'project_id': source_group.project_id} + 'tenant_id': source_group.project_id} else: sg_rule['ip_range'] = {'cidr': rule.cidr} return sg_rule @@ -66,7 +66,7 @@ class SecurityGroupController(object): security_group['id'] = group.id security_group['description'] = group.description security_group['name'] = group.name - security_group['project_id'] = group.project_id + security_group['tenant_id'] = group.project_id security_group['rules'] = [] for rule in group.rules: security_group['rules'] += [self._format_security_group_rule( @@ -118,7 +118,7 @@ class SecurityGroupController(object): return {'security_groups': list(sorted(result, - key=lambda k: (k['project_id'], k['name'])))} + key=lambda k: (k['tenant_id'], k['name'])))} def create(self, req, body): """Creates a new security group.""" diff --git a/nova/tests/api/openstack/contrib/test_security_groups.py b/nova/tests/api/openstack/contrib/test_security_groups.py index aedc487c0..8d615eaea 100644 --- a/nova/tests/api/openstack/contrib/test_security_groups.py +++ b/nova/tests/api/openstack/contrib/test_security_groups.py @@ -247,7 +247,7 @@ class TestSecurityGroups(test.TestCase): expected = {'security_groups': [ {'id': 1, 'name':"default", - 'project_id': "123", + 'tenant_id': "123", "description":"default", "rules": [] }, @@ -257,7 +257,7 @@ class TestSecurityGroups(test.TestCase): { 'id': 2, 'name': "test", - 'project_id': "123", + 'tenant_id': "123", "description": "group-description", "rules": [] } @@ -283,12 +283,11 @@ class TestSecurityGroups(test.TestCase): 'security_group': { 'id': 2, 'name': "test", - 'project_id': "123", + 'tenant_id': "123", 'description': "group-description", 'rules': [] } } - self.assertEquals(response.status_int, 200) self.assertEquals(res_dict, expected) def test_get_security_group_by_invalid_id(self): -- cgit From 83b45a371665fd069fc7e372628f82874258fd08 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Tue, 16 Aug 2011 00:31:54 -0700 Subject: redux of floating ip api --- nova/api/openstack/contrib/floating_ips.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 44b35c385..722320534 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -78,11 +78,14 @@ class FloatingIPController(object): def index(self, req): context = req.environ['nova.context'] - floating_ips = self.network_api.list_floating_ips(context) + try: + floating_ips = self.network_api.list_floating_ips(context) + except exception.FloatingIpNotFoundForProject: + floating_ips = [] return _translate_floating_ips_view(floating_ips) - def create(self, req): + def create(self, req, body): context = req.environ['nova.context'] try: @@ -95,9 +98,7 @@ class FloatingIPController(object): else: raise - return {'allocated': { - "id": ip['id'], - "floating_ip": ip['address']}} + return _translate_floating_ip_view(ip) def delete(self, req, id): context = req.environ['nova.context'] @@ -125,26 +126,22 @@ class FloatingIPController(object): except rpc.RemoteError: raise - return {'associated': - { - "floating_ip_id": id, - "floating_ip": floating_ip, - "fixed_ip": fixed_ip}} + floating_ip = self.network_api.get_floating_ip(context, id) + return _translate_floating_ip_view(floating_ip) def disassociate(self, req, id, body=None): """ POST /floating_ips/{id}/disassociate """ context = req.environ['nova.context'] floating_ip = self.network_api.get_floating_ip(context, id) address = floating_ip['address'] - fixed_ip = floating_ip['fixed_ip']['address'] try: self.network_api.disassociate_floating_ip(context, address) except rpc.RemoteError: raise - return {'disassociated': {'floating_ip': address, - 'fixed_ip': fixed_ip}} + floating_ip = self.network_api.get_floating_ip(context, id) + return _translate_floating_ip_view(floating_ip) def _get_ip_by_id(self, context, value): """Checks that value is id and then returns its address.""" -- cgit From 92c6ee9dc7eeaa44bf6162387b5815fc0cdb1c71 Mon Sep 17 00:00:00 2001 From: Ryu Ishimoto Date: Tue, 16 Aug 2011 17:51:45 +0900 Subject: Fixed the naming of the extension --- nova/api/openstack/contrib/virtual_interfaces.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nova/api/openstack/contrib/virtual_interfaces.py b/nova/api/openstack/contrib/virtual_interfaces.py index 3466d31c7..38246aeb5 100644 --- a/nova/api/openstack/contrib/virtual_interfaces.py +++ b/nova/api/openstack/contrib/virtual_interfaces.py @@ -72,13 +72,13 @@ class ServerVirtualInterfaceController(object): entity_maker=_translate_vif_summary_view) -class VirtualInterfaces(extensions.ExtensionDescriptor): +class Virtual_interfaces(extensions.ExtensionDescriptor): def get_name(self): - return "VirtualInterfaces" + return "Virtual_interfaces" def get_alias(self): - return "os-virtual_interfaces" + return "os-virtual-interfaces" def get_description(self): return "Virtual interface support" @@ -92,7 +92,7 @@ class VirtualInterfaces(extensions.ExtensionDescriptor): def get_resources(self): resources = [] - res = extensions.ResourceExtension('os-virtual_interfaces', + res = extensions.ResourceExtension('os-virtual-interfaces', ServerVirtualInterfaceController(), parent=dict( member_name='server', -- cgit From ee06de65b674a7a91597bc9121b3bd3bd11e658b Mon Sep 17 00:00:00 2001 From: Ryu Ishimoto Date: Tue, 16 Aug 2011 18:37:50 +0900 Subject: Added uuid to allocate_mac_address --- nova/network/manager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/network/manager.py b/nova/network/manager.py index b1b3f8ba2..f115c66f1 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -523,7 +523,8 @@ class NetworkManager(manager.SchedulerDependentManager): for network in networks: vif = {'address': self.generate_mac_address(), 'instance_id': instance_id, - 'network_id': network['id']} + 'network_id': network['id'], + 'uuid': utils.gen_uuid()} # try FLAG times to create a vif record with a unique mac_address for i in range(FLAGS.create_unique_mac_address_attempts): try: -- cgit From c3c164455f9b5d4ea994a4453342ccb00d987766 Mon Sep 17 00:00:00 2001 From: Ryu Ishimoto Date: Tue, 16 Aug 2011 18:52:29 +0900 Subject: Include vif UUID in the network info dictionary --- nova/network/manager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/network/manager.py b/nova/network/manager.py index f115c66f1..f32f8e837 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -499,6 +499,7 @@ class NetworkManager(manager.SchedulerDependentManager): 'dhcp_server': dhcp_server, 'broadcast': network['broadcast'], 'mac': vif['address'], + 'vif_uuid': vif['uuid'], 'rxtx_cap': flavor['rxtx_cap'], 'dns': [], 'ips': [ip_dict(ip) for ip in network_IPs], -- cgit From fb43ea94e81e5eec51b73c2aab4a8a38cdf71361 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Tue, 16 Aug 2011 11:46:22 -0700 Subject: make delete more consistant --- nova/api/openstack/contrib/floating_ips.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 722320534..1276c0118 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -105,13 +105,13 @@ class FloatingIPController(object): ip = self.network_api.get_floating_ip(context, id) if 'fixed_ip' in ip: - self.disassociate(req, id) + try: + self.disassociate(req, id) + except exception.ApiError: + LOG.warn("disassociate failure %s", id) self.network_api.release_floating_ip(context, address=ip['address']) - - return {'released': { - "id": ip['id'], - "floating_ip": ip['address']}} + return exc.HTTPAccepted() def associate(self, req, id, body): """ /floating_ips/{id}/associate fixed ip in body """ -- cgit From f4d608549f0a539e48276be163593ced558a136f Mon Sep 17 00:00:00 2001 From: William Wolf Date: Tue, 16 Aug 2011 15:11:32 -0400 Subject: make project_id authorization work properly, with test --- nova/api/openstack/auth.py | 23 ++++++++++++++++++----- nova/tests/integrated/api/client.py | 15 +++++++++++++-- nova/tests/integrated/test_login.py | 6 ++++++ 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index c9c740a1d..8f1319cca 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -55,23 +55,36 @@ class AuthMiddleware(wsgi.Middleware): LOG.warn(msg % locals()) return faults.Fault(webob.exc.HTTPUnauthorized()) + # Get all valid projects for the user + projects = self.auth.get_projects(user_id) + if not projects: + return faults.Fault(webob.exc.HTTPUnauthorized()) + project_id = "" path_parts = req.path.split('/') # TODO(wwolf): this v1.1 check will be temporary as # keystone should be taking this over at some point if len(path_parts) > 1 and path_parts[1] == 'v1.1': project_id = path_parts[2] + # Check that the project for project_id exists, and that user + # is authorized to use it + try: + project = self.auth.get_project(project_id) + except exception.ProjectNotFound: + project = None + if project: + user = self.auth.get_user(user_id) + if not project.has_member(user): + return faults.Fault(webob.exc.HTTPUnauthorized()) + else: + return faults.Fault(webob.exc.HTTPUnauthorized()) elif len(path_parts) > 1 and path_parts[1] == 'v1.0': try: project_id = req.headers["X-Auth-Project-Id"] except KeyError: # FIXME(usrleon): It needed only for compatibility # while osapi clients don't use this header - projects = self.auth.get_projects(user_id) - if projects: - project_id = projects[0].id - else: - return faults.Fault(webob.exc.HTTPUnauthorized()) + project_id = projects[0].id is_admin = self.auth.is_admin(user_id) req.environ['nova.context'] = context.RequestContext(user_id, diff --git a/nova/tests/integrated/api/client.py b/nova/tests/integrated/api/client.py index da78995cd..fabf49817 100644 --- a/nova/tests/integrated/api/client.py +++ b/nova/tests/integrated/api/client.py @@ -48,6 +48,14 @@ class OpenStackApiAuthenticationException(OpenStackApiException): response) +class OpenStackApiAuthorizationException(OpenStackApiException): + def __init__(self, response=None, message=None): + if not message: + message = _("Authorization error") + super(OpenStackApiAuthorizationException, self).__init__(message, + response) + + class OpenStackApiNotFoundException(OpenStackApiException): def __init__(self, response=None, message=None): if not message: @@ -69,6 +77,8 @@ class TestOpenStackClient(object): self.auth_user = auth_user self.auth_key = auth_key self.auth_uri = auth_uri + # default project_id + self.project_id = 'openstack' def request(self, url, method='GET', body=None, headers=None): _headers = {'Content-Type': 'application/json'} @@ -128,8 +138,7 @@ class TestOpenStackClient(object): # NOTE(justinsb): httplib 'helpfully' converts headers to lower case base_uri = auth_result['x-server-management-url'] - # /openstack is the project_id - full_uri = base_uri + '/openstack' + relative_uri + full_uri = '%s/%s%s' % (base_uri, self.project_id, relative_uri) headers = kwargs.setdefault('headers', {}) headers['X-Auth-Token'] = auth_result['x-auth-token'] @@ -143,6 +152,8 @@ class TestOpenStackClient(object): if not http_status in check_response_status: if http_status == 404: raise OpenStackApiNotFoundException(response=response) + elif http_status == 401: + raise OpenStackApiAuthorizationException(response=response) else: raise OpenStackApiException( message=_("Unexpected status code"), diff --git a/nova/tests/integrated/test_login.py b/nova/tests/integrated/test_login.py index 06359a52f..9d1925bc0 100644 --- a/nova/tests/integrated/test_login.py +++ b/nova/tests/integrated/test_login.py @@ -59,6 +59,12 @@ class LoginTest(integrated_helpers._IntegratedTestBase): self.assertRaises(client.OpenStackApiAuthenticationException, bad_credentials_api.get_flavors) + def test_good_login_bad_project(self): + """Test that I get a 401 with valid user/pass but bad project""" + self.api.project_id = 'openstackBAD' + + self.assertRaises(client.OpenStackApiAuthorizationException, + self.api.get_flavors) if __name__ == "__main__": unittest.main() -- cgit From 9081e8b62ea01828238ecaebdcf3e627ada3fe9a Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Tue, 16 Aug 2011 16:04:18 -0700 Subject: Added uuid for networks and made changes to the Create server API format to accept network as uuid instead of id --- bin/nova-manage | 37 +++++------ nova/api/openstack/create_instance_helper.py | 39 +++++------ nova/db/api.py | 23 ++----- nova/db/sqlalchemy/api.py | 40 +++--------- .../versions/037_add_uuid_to_networks.py | 43 +++++++++++++ nova/db/sqlalchemy/models.py | 1 + nova/exception.py | 4 +- nova/network/manager.py | 67 +++++++++++-------- .../api/openstack/contrib/test_createserverext.py | 75 ++++++++++------------ nova/tests/api/openstack/test_servers.py | 38 +++++------ nova/tests/test_network.py | 72 ++++++++++++--------- 11 files changed, 230 insertions(+), 209 deletions(-) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/037_add_uuid_to_networks.py diff --git a/bin/nova-manage b/bin/nova-manage index 41433f1d6..9592d5132 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -760,25 +760,26 @@ class NetworkCommands(object): def list(self): """List all created networks""" - print "%-5s\t%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s" % ( - _('id'), - _('IPv4'), - _('IPv6'), - _('start address'), - _('DNS1'), - _('DNS2'), - _('VlanID'), - 'project') + _fmt = "%-5s\t%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s" + print _fmt % (_('id'), + _('IPv4'), + _('IPv6'), + _('start address'), + _('DNS1'), + _('DNS2'), + _('VlanID'), + _('project'), + _("uuid")) for network in db.network_get_all(context.get_admin_context()): - print "%-5s\t%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s" % ( - network.id, - network.cidr, - network.cidr_v6, - network.dhcp_start, - network.dns1, - network.dns2, - network.vlan, - network.project_id) + print _fmt % (network.id, + network.cidr, + network.cidr_v6, + network.dhcp_start, + network.dns1, + network.dns2, + network.vlan, + network.project_id, + network.uuid) @args('--network', dest="fixed_range", metavar='', help='Network to delete') diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index c1abd2eb6..8d5a9d2a3 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -304,15 +304,6 @@ class CreateInstanceHelper(object): raise exc.HTTPBadRequest(explanation=msg) return password - def _validate_fixed_ip(self, value): - if not isinstance(value, basestring): - msg = _("Fixed IP is not a string or unicode") - raise exc.HTTPBadRequest(explanation=msg) - - if value.strip() == '': - msg = _("Fixed IP is an empty string") - raise exc.HTTPBadRequest(explanation=msg) - def _get_requested_networks(self, requested_networks): """ Create a list of requested networks from the networks attribute @@ -320,31 +311,33 @@ class CreateInstanceHelper(object): networks = [] for network in requested_networks: try: - network_id = network['id'] - network_id = int(network_id) + network_uuid = network['uuid'] + + if not utils.is_uuid_like(network_uuid): + msg = _("Bad networks format: network uuid is not in" + " proper format (%s)") % network_uuid + raise exc.HTTPBadRequest(explanation=msg) + #fixed IP address is optional #if the fixed IP address is not provided then #it will use one of the available IP address from the network - fixed_ip = network.get('fixed_ip', None) - if fixed_ip is not None: - self._validate_fixed_ip(fixed_ip) + address = network.get('fixed_ip', None) + if address is not None and not utils.is_valid_ipv4(address): + msg = _("Invalid fixed IP address (%s)") % address + raise exc.HTTPBadRequest(explanation=msg) # check if the network id is already present in the list, # we don't want duplicate networks to be passed # at the boot time for id, ip in networks: - if id == network_id: + if id == network_uuid: expl = _("Duplicate networks (%s) are not allowed")\ - % network_id + % network_uuid raise exc.HTTPBadRequest(explanation=expl) - networks.append((network_id, fixed_ip)) + networks.append((network_uuid, address)) except KeyError as key: expl = _('Bad network format: missing %s') % key raise exc.HTTPBadRequest(explanation=expl) - except ValueError: - expl = _("Bad networks format: network id should " - "be integer (%s)") % network_id - raise exc.HTTPBadRequest(explanation=expl) except TypeError: expl = _('Bad networks format') raise exc.HTTPBadRequest(explanation=expl) @@ -543,8 +536,8 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer): for network_node in self.find_children_named(node, "network"): item = {} - if network_node.hasAttribute("id"): - item["id"] = network_node.getAttribute("id") + if network_node.hasAttribute("uuid"): + item["uuid"] = network_node.getAttribute("uuid") if network_node.hasAttribute("fixed_ip"): item["fixed_ip"] = network_node.getAttribute("fixed_ip") networks.append(item) diff --git a/nova/db/api.py b/nova/db/api.py index b6b98daf4..6833e6312 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -404,14 +404,6 @@ def fixed_ip_update(context, address, values): """Create a fixed ip from the values dictionary.""" return IMPL.fixed_ip_update(context, address, values) - -def fixed_ip_validate_by_network_address(context, network_id, - address): - """validates if the address belongs to the network""" - return IMPL.fixed_ip_validate_by_network_address(context, network_id, - address) - - #################### @@ -695,9 +687,9 @@ def network_get_all(context): return IMPL.network_get_all(context) -def network_get_networks_by_ids(context, network_ids): +def network_get_networks_by_uuids(context, network_uuids): """Return networks by ids.""" - return IMPL.network_get_networks_by_ids(context, network_ids) + return IMPL.network_get_networks_by_uuids(context, network_uuids) # pylint: disable=C0103 @@ -1252,14 +1244,9 @@ def project_get_networks(context, project_id, associate=True): return IMPL.project_get_networks(context, project_id, associate) -def project_get_networks_by_ids(context, network_ids): - """Return the networks by ids associated with the project. - - If associate is true, it will attempt to associate a new - network if one is not found, otherwise it returns None. - - """ - return IMPL.project_get_networks_by_ids(context, network_ids) +def project_get_networks_by_uuids(context, network_uuids): + """Return the networks by uuids associated with the project.""" + return IMPL.project_get_networks_by_uuids(context, network_uuids) def project_get_networks_v6(context, project_id): diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index dde371820..21eb85b2c 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -824,26 +824,6 @@ def fixed_ip_get_by_address(context, address, session=None): return result -@require_context -def fixed_ip_validate_by_network_address(context, network_id, - address): - session = get_session() - fixed_ip_ref = session.query(models.FixedIp).\ - filter_by(address=address).\ - filter_by(reserved=False).\ - filter_by(network_id=network_id).\ - filter_by(deleted=can_read_deleted(context)).\ - first() - - if fixed_ip_ref is None: - raise exception.FixedIpNotFoundForNetwork(address=address, - network_id=network_id) - if fixed_ip_ref.instance is not None: - raise exception.FixedIpAlreadyInUse(address=address) - - return fixed_ip_ref - - @require_context def fixed_ip_get_by_instance(context, instance_id): session = get_session() @@ -1778,10 +1758,10 @@ def network_get_all(context): @require_admin_context -def network_get_networks_by_ids(context, network_ids): +def network_get_networks_by_uuids(context, network_uuids): session = get_session() result = session.query(models.Network).\ - filter(models.Network.id.in_(network_ids)).\ + filter(models.Network.uuid.in_(network_uuids)).\ filter_by(deleted=False).all() if not result: raise exception.NoNetworksFound() @@ -1794,14 +1774,14 @@ def network_get_networks_by_ids(context, network_ids): #check if the result contains all the networks #we are looking for - for network_id in network_ids: + for network_uuid in network_uuids: found = False for network in result: - if network['id'] == network_id: + if network['uuid'] == network_uuid: found = True break if not found: - raise exception.NetworkNotFound(network_id=network_id) + raise exception.NetworkNotFound(network_id=network_uuid) return result @@ -2962,10 +2942,10 @@ def project_get_networks(context, project_id, associate=True): @require_context -def project_get_networks_by_ids(context, network_ids): +def project_get_networks_by_uuids(context, network_uuids): session = get_session() result = session.query(models.Network).\ - filter(models.Network.id.in_(network_ids)).\ + filter(models.Network.uuid.in_(network_uuids)).\ filter_by(deleted=False).\ filter_by(project_id=context.project_id).all() @@ -2980,14 +2960,14 @@ def project_get_networks_by_ids(context, network_ids): #check if the result contains all the networks #we are looking for - for network_id in network_ids: + for uuid in network_uuids: found = False for network in result: - if network['id'] == network_id: + if network['uuid'] == uuid: found = True break if not found: - raise exception.NetworkNotFoundForProject(network_id=network_id, + raise exception.NetworkNotFoundForProject(network_uuid=uuid, project_id=context.project_id) return result diff --git a/nova/db/sqlalchemy/migrate_repo/versions/037_add_uuid_to_networks.py b/nova/db/sqlalchemy/migrate_repo/versions/037_add_uuid_to_networks.py new file mode 100644 index 000000000..38c543d51 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/037_add_uuid_to_networks.py @@ -0,0 +1,43 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from sqlalchemy import Column, Integer, MetaData, String, Table + +from nova import utils + + +meta = MetaData() + +networks = Table("networks", meta, + Column("id", Integer(), primary_key=True, nullable=False)) +uuid_column = Column("uuid", String(36)) + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + networks.create_column(uuid_column) + + rows = migrate_engine.execute(networks.select()) + for row in rows: + networks_uuid = str(utils.gen_uuid()) + migrate_engine.execute(networks.update()\ + .where(networks.c.id == row[0])\ + .values(uuid=networks_uuid)) + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + networks.drop_column(uuid_column) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index f2a4680b0..5cadd156d 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -557,6 +557,7 @@ class Network(BASE, NovaBase): project_id = Column(String(255)) host = Column(String(255)) # , ForeignKey('hosts.id')) + uuid = Column(String(36)) class VirtualInterface(BASE, NovaBase): diff --git a/nova/exception.py b/nova/exception.py index 0c07934cf..c68c89cad 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -428,7 +428,7 @@ class NoNetworksFound(NotFound): class NetworkNotFoundForProject(NotFound): - message = _("Either Network %(network_id)s is not present or " + message = _("Either Network uuid %(network_uuid)s is not present or " "is not assigned to the project %(project_id)s.") @@ -471,7 +471,7 @@ class FixedIpNotFoundForHost(FixedIpNotFound): class FixedIpNotFoundForNetwork(FixedIpNotFound): message = _("Fixed IP address (%(address)s) does not exist in " - "network (%(network_id)s).") + "network (%(network_uuid)s).") class FixedIpAlreadyInUse(AlreadyInUse): diff --git a/nova/network/manager.py b/nova/network/manager.py index f8a21fe4b..2f30f8ec1 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -135,8 +135,8 @@ class RPCAllocateFixedIP(object): for network in networks: address = None if requested_networks is not None: - for address in (fixed_ip for (id, fixed_ip) in \ - requested_networks if network['id'] == id): + for address in (fixed_ip for (uuid, fixed_ip) in \ + requested_networks if network['uuid'] == uuid): break # NOTE(vish): if we are not multi_host pass to the network host @@ -397,9 +397,9 @@ class NetworkManager(manager.SchedulerDependentManager): # there is a better way to determine which networks # a non-vlan instance should connect to if requested_networks is not None and len(requested_networks) != 0: - network_ids = [id for (id, fixed_ip) in requested_networks] - networks = self.db.network_get_networks_by_ids(context, - network_ids) + network_uuids = [uuid for (uuid, fixed_ip) in requested_networks] + networks = self.db.network_get_networks_by_uuids(context, + network_uuids) else: try: networks = self.db.network_get_all(context) @@ -827,18 +827,24 @@ class NetworkManager(manager.SchedulerDependentManager): if networks is None or len(networks) == 0: return - network_ids = [id for (id, fixed_ip) in networks] - result = self.db.network_get_networks_by_ids(context, network_ids) - for network_id, fixed_ip in networks: + network_uuids = [uuid for (uuid, fixed_ip) in networks] + + self.db.network_get_networks_by_uuids(context, network_uuids) + + for network_uuid, address in networks: # check if the fixed IP address is valid and # it actually belongs to the network - if fixed_ip is not None: - if not utils.is_valid_ipv4(fixed_ip): - raise exception.FixedIpInvalid(address=fixed_ip) + if address is not None: + if not utils.is_valid_ipv4(address): + raise exception.FixedIpInvalid(address=address) - self.db.fixed_ip_validate_by_network_address(context, - network_id, - fixed_ip) + fixed_ip_ref = self.db.fixed_ip_get_by_address(context, + address) + if fixed_ip_ref['network']['uuid'] != network_uuid: + raise exception.FixedIpNotFoundForNetwork(address=address, + network_uuid=network_uuid) + if fixed_ip_ref['instance'] is not None: + raise exception.FixedIpAlreadyInUse(address=address) class FlatManager(NetworkManager): @@ -878,8 +884,8 @@ class FlatManager(NetworkManager): for network in networks: address = None if requested_networks is not None: - for address in (fixed_ip for (id, fixed_ip) in \ - requested_networks if network['id'] == id): + for address in (fixed_ip for (uuid, fixed_ip) in \ + requested_networks if network['uuid'] == uuid): break self.allocate_fixed_ip(context, instance_id, @@ -999,9 +1005,9 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): """Determine which networks an instance should connect to.""" # get networks associated with project if requested_networks is not None and len(requested_networks) != 0: - network_ids = [id for (id, fixed_ip) in requested_networks] - networks = self.db.project_get_networks_by_ids(context, - network_ids) + network_uuids = [uuid for (uuid, fixed_ip) in requested_networks] + networks = self.db.project_get_networks_by_uuids(context, + network_uuids) else: networks = self.db.project_get_networks(context, project_id) return networks @@ -1060,19 +1066,24 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): if networks is None or len(networks) == 0: return - network_ids = [id for (id, fixed_ip) in networks] - result = self.db.project_get_networks_by_ids(context, network_ids) + network_uuids = [uuid for (uuid, fixed_ip) in networks] - for network_id, fixed_ip in networks: + self.db.project_get_networks_by_uuids(context, network_uuids) + + for network_uuid, address in networks: # check if the fixed IP address is valid and # it actually belongs to the network if fixed_ip is not None: - if not utils.is_valid_ipv4(fixed_ip): - raise exception.FixedIpInvalid(address=fixed_ip) - - self.db.fixed_ip_validate_by_network_address(context, - network_id, - fixed_ip) + if not utils.is_valid_ipv4(address): + raise exception.FixedIpInvalid(address=address) + + fixed_ip_ref = self.db.fixed_ip_get_by_address(context, + address) + if fixed_ip_ref['network']['uuid'] != network_uuid: + raise exception.FixedIpNotFoundForNetwork(address=address, + network_uuid=network_uuid) + if fixed_ip_ref['instance'] is not None: + raise exception.FixedIpAlreadyInUse(address=address) @property def _bottom_reserved_ips(self): diff --git a/nova/tests/api/openstack/contrib/test_createserverext.py b/nova/tests/api/openstack/contrib/test_createserverext.py index 33a1eec50..38415179a 100644 --- a/nova/tests/api/openstack/contrib/test_createserverext.py +++ b/nova/tests/api/openstack/contrib/test_createserverext.py @@ -43,6 +43,14 @@ FLAGS.verbose = True FAKE_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' +FAKE_NETWORKS = [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '10.0.1.12'), + ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', '10.0.2.12')] + +DUPLICATE_NETWORKS = [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '10.0.1.12'), + ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '10.0.1.12')] + +INVALID_NETWORKS = [('invalid', 'invalid-ip-address')] + class CreateserverextTest(test.TestCase): @@ -106,8 +114,8 @@ class CreateserverextTest(test.TestCase): server['flavorRef'] = 1 if networks is not None: network_list = [] - for id, fixed_ip in networks: - network_list.append({'id': id, 'fixed_ip': fixed_ip}) + for uuid, fixed_ip in networks: + network_list.append({'uuid': uuid, 'fixed_ip': fixed_ip}) server['networks'] = network_list return {'server': server} @@ -148,8 +156,8 @@ class CreateserverextTest(test.TestCase): networks = server['networks'] body_parts.append('') for network in networks: - item = (network['id'], network['fixed_ip']) - body_parts.append('' + item = (network['uuid'], network['fixed_ip']) + body_parts.append('' % item) body_parts.append('') body_parts.append('') @@ -190,55 +198,44 @@ class CreateserverextTest(test.TestCase): self.assertEquals(networks, None) def test_create_instance_with_one_network(self): - id = 1 - fixed_ip = '10.0.1.12' - networks = [(id, fixed_ip)] request, response, networks = \ - self._create_instance_with_networks_json(networks) + self._create_instance_with_networks_json([FAKE_NETWORKS[0]]) self.assertEquals(response.status_int, 202) - self.assertEquals(networks, [(id, fixed_ip)]) + self.assertEquals(networks, [FAKE_NETWORKS[0]]) def test_create_instance_with_one_network_xml(self): - id = 1 - fixed_ip = '10.0.1.12' - networks = [(id, fixed_ip)] request, response, networks = \ - self._create_instance_with_networks_xml(networks) + self._create_instance_with_networks_xml([FAKE_NETWORKS[0]]) self.assertEquals(response.status_int, 202) - self.assertEquals(networks, [(id, fixed_ip)]) + self.assertEquals(networks, [FAKE_NETWORKS[0]]) def test_create_instance_with_two_networks(self): - networks = [(1, '10.0.1.12'), (2, '10.0.2.12')] request, response, networks = \ - self._create_instance_with_networks_json(networks) + self._create_instance_with_networks_json(FAKE_NETWORKS) self.assertEquals(response.status_int, 202) - self.assertEquals(networks, [(1, '10.0.1.12'), (2, '10.0.2.12')]) + self.assertEquals(networks, FAKE_NETWORKS) def test_create_instance_with_two_networks_xml(self): - networks = [(1, '10.0.1.12'), (2, '10.0.2.12')] request, response, networks = \ - self._create_instance_with_networks_xml(networks) + self._create_instance_with_networks_xml(FAKE_NETWORKS) self.assertEquals(response.status_int, 202) - self.assertEquals(networks, [(1, '10.0.1.12'), (2, '10.0.2.12')]) + self.assertEquals(networks, FAKE_NETWORKS) def test_create_instance_with_duplicate_networks(self): - networks = [(1, '10.0.1.12'), (1, '10.0.2.12')] request, response, networks = \ - self._create_instance_with_networks_json(networks) + self._create_instance_with_networks_json(DUPLICATE_NETWORKS) self.assertEquals(response.status_int, 400) self.assertEquals(networks, None) def test_create_instance_with_duplicate_networks_xml(self): - networks = [(1, '10.0.1.12'), (1, '10.0.2.12')] request, response, networks = \ - self._create_instance_with_networks_xml(networks) + self._create_instance_with_networks_xml(DUPLICATE_NETWORKS) self.assertEquals(response.status_int, 400) self.assertEquals(networks, None) def test_create_instance_with_network_no_id(self): - networks = [(1, '10.0.1.12')] - body_dict = self._create_networks_request_dict(networks) - del body_dict['server']['networks'][0]['id'] + body_dict = self._create_networks_request_dict([FAKE_NETWORKS[0]]) + del body_dict['server']['networks'][0]['uuid'] request = self._get_create_request_json(body_dict) compute_api, response = \ self._run_create_instance_with_mock_compute_api(request) @@ -246,26 +243,24 @@ class CreateserverextTest(test.TestCase): self.assertEquals(compute_api.networks, None) def test_create_instance_with_network_no_id_xml(self): - networks = [(1, '10.0.1.12')] - body_dict = self._create_networks_request_dict(networks) + body_dict = self._create_networks_request_dict([FAKE_NETWORKS[0]]) request = self._get_create_request_xml(body_dict) - request.body = request.body.replace(' id="1"', '') + uuid = ' uuid="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"' + request.body = request.body.replace(uuid, '') compute_api, response = \ self._run_create_instance_with_mock_compute_api(request) self.assertEquals(response.status_int, 400) self.assertEquals(compute_api.networks, None) def test_create_instance_with_network_invalid_id(self): - networks = [('asd123', '10.0.1.12')] request, response, networks = \ - self._create_instance_with_networks_json(networks) + self._create_instance_with_networks_json(INVALID_NETWORKS) self.assertEquals(response.status_int, 400) self.assertEquals(networks, None) def test_create_instance_with_network_invalid_id_xml(self): - networks = [('asd123', '10.0.1.12')] request, response, networks = \ - self._create_instance_with_networks_xml(networks) + self._create_instance_with_networks_xml(INVALID_NETWORKS) self.assertEquals(response.status_int, 400) self.assertEquals(networks, None) @@ -291,21 +286,21 @@ class CreateserverextTest(test.TestCase): self.assertEquals(networks, None) def test_create_instance_with_network_no_fixed_ip(self): - networks = [(1, '10.0.1.12')] - body_dict = self._create_networks_request_dict(networks) + body_dict = self._create_networks_request_dict([FAKE_NETWORKS[0]]) del body_dict['server']['networks'][0]['fixed_ip'] request = self._get_create_request_json(body_dict) compute_api, response = \ self._run_create_instance_with_mock_compute_api(request) self.assertEquals(response.status_int, 202) - self.assertEquals(compute_api.networks, [(1, None)]) + self.assertEquals(compute_api.networks, + [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', None)]) def test_create_instance_with_network_no_fixed_ip_xml(self): - networks = [(1, '10.0.1.12')] - body_dict = self._create_networks_request_dict(networks) + body_dict = self._create_networks_request_dict([FAKE_NETWORKS[0]]) request = self._get_create_request_xml(body_dict) request.body = request.body.replace(' fixed_ip="10.0.1.12"', '') compute_api, response = \ self._run_create_instance_with_mock_compute_api(request) self.assertEquals(response.status_int, 202) - self.assertEquals(compute_api.networks, [(1, None)]) + self.assertEquals(compute_api.networks, + [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', None)]) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 08eb1eb3c..4cfe18c40 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -2662,7 +2662,7 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): - + """ request = self.deserializer.deserialize(serial_request, 'create') @@ -2670,7 +2670,7 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): "name": "new-server-test", "imageRef": "1", "flavorRef": "1", - "networks": [{"id": "1", "fixed_ip": "10.0.1.12"}], + "networks": [{"uuid": "1", "fixed_ip": "10.0.1.12"}], }} self.assertEquals(request['body'], expected) @@ -2679,8 +2679,8 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): - - + + """ request = self.deserializer.deserialize(serial_request, 'create') @@ -2688,8 +2688,8 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): "name": "new-server-test", "imageRef": "1", "flavorRef": "1", - "networks": [{"id": "1", "fixed_ip": "10.0.1.12"}, - {"id": "2", "fixed_ip": "10.0.2.12"}], + "networks": [{"uuid": "1", "fixed_ip": "10.0.1.12"}, + {"uuid": "2", "fixed_ip": "10.0.2.12"}], }} self.assertEquals(request['body'], expected) @@ -2698,10 +2698,10 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): - + - + """ request = self.deserializer.deserialize(serial_request, 'create') @@ -2709,7 +2709,7 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): "name": "new-server-test", "imageRef": "1", "flavorRef": "1", - "networks": [{"id": "1", "fixed_ip": "10.0.1.12"}], + "networks": [{"uuid": "1", "fixed_ip": "10.0.1.12"}], }} self.assertEquals(request['body'], expected) @@ -2735,7 +2735,7 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): - + """ request = self.deserializer.deserialize(serial_request, 'create') @@ -2743,7 +2743,7 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): "name": "new-server-test", "imageRef": "1", "flavorRef": "1", - "networks": [{"id": "1"}], + "networks": [{"uuid": "1"}], }} self.assertEquals(request['body'], expected) @@ -2752,7 +2752,7 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): - + """ request = self.deserializer.deserialize(serial_request, 'create') @@ -2760,7 +2760,7 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): "name": "new-server-test", "imageRef": "1", "flavorRef": "1", - "networks": [{"id": "", "fixed_ip": "10.0.1.12"}], + "networks": [{"uuid": "", "fixed_ip": "10.0.1.12"}], }} self.assertEquals(request['body'], expected) @@ -2769,7 +2769,7 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): - + """ request = self.deserializer.deserialize(serial_request, 'create') @@ -2777,7 +2777,7 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): "name": "new-server-test", "imageRef": "1", "flavorRef": "1", - "networks": [{"id": "1", "fixed_ip": ""}], + "networks": [{"uuid": "1", "fixed_ip": ""}], }} self.assertEquals(request['body'], expected) @@ -2786,8 +2786,8 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): - - + + """ request = self.deserializer.deserialize(serial_request, 'create') @@ -2795,8 +2795,8 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): "name": "new-server-test", "imageRef": "1", "flavorRef": "1", - "networks": [{"id": "1", "fixed_ip": "10.0.1.12"}, - {"id": "1", "fixed_ip": "10.0.2.12"}], + "networks": [{"uuid": "1", "fixed_ip": "10.0.1.12"}, + {"uuid": "1", "fixed_ip": "10.0.2.12"}], }} self.assertEquals(request['body'], expected) diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index f5ab5ae5a..1f01695a8 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -41,6 +41,7 @@ class FakeModel(dict): networks = [{'id': 0, + 'uuid': "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", 'label': 'test0', 'injected': False, 'multi_host': False, @@ -60,6 +61,7 @@ networks = [{'id': 0, 'project_id': 'fake_project', 'vpn_public_address': '192.168.0.2'}, {'id': 1, + 'uuid': "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", 'label': 'test1', 'injected': False, 'multi_host': False, @@ -179,15 +181,18 @@ class FlatNetworkTestCase(test.TestCase): self.assertDictListMatch(nw[1]['ips'], check) def test_validate_networks(self): - self.mox.StubOutWithMock(db, 'network_get_networks_by_ids') - self.mox.StubOutWithMock(db, "fixed_ip_validate_by_network_address") + self.mox.StubOutWithMock(db, 'network_get_networks_by_uuids') + self.mox.StubOutWithMock(db, "fixed_ip_get_by_address") - requested_networks = [(1, "192.168.0.100")] - db.network_get_networks_by_ids(mox.IgnoreArg(), + requested_networks = [("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", + "192.168.1.100")] + db.network_get_networks_by_uuids(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(networks) - db.fixed_ip_validate_by_network_address(mox.IgnoreArg(), - mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn(fixed_ips[0]) + + fixed_ips[1]['network'] = FakeModel(**networks[1]) + fixed_ips[1]['instance'] = None + db.fixed_ip_get_by_address(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(fixed_ips[1]) self.mox.ReplayAll() self.network.validate_networks(None, requested_networks) @@ -202,9 +207,9 @@ class FlatNetworkTestCase(test.TestCase): self.network.validate_networks(None, requested_networks) def test_validate_networks_invalid_fixed_ip(self): - self.mox.StubOutWithMock(db, 'network_get_networks_by_ids') + self.mox.StubOutWithMock(db, 'network_get_networks_by_uuids') requested_networks = [(1, "192.168.0.100.1")] - db.network_get_networks_by_ids(mox.IgnoreArg(), + db.network_get_networks_by_uuids(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(networks) self.mox.ReplayAll() @@ -213,10 +218,10 @@ class FlatNetworkTestCase(test.TestCase): requested_networks) def test_validate_networks_empty_fixed_ip(self): - self.mox.StubOutWithMock(db, 'network_get_networks_by_ids') + self.mox.StubOutWithMock(db, 'network_get_networks_by_uuids') requested_networks = [(1, "")] - db.network_get_networks_by_ids(mox.IgnoreArg(), + db.network_get_networks_by_uuids(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(networks) self.mox.ReplayAll() @@ -225,10 +230,10 @@ class FlatNetworkTestCase(test.TestCase): None, requested_networks) def test_validate_networks_none_fixed_ip(self): - self.mox.StubOutWithMock(db, 'network_get_networks_by_ids') + self.mox.StubOutWithMock(db, 'network_get_networks_by_uuids') requested_networks = [(1, None)] - db.network_get_networks_by_ids(mox.IgnoreArg(), + db.network_get_networks_by_uuids(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(networks) self.mox.ReplayAll() @@ -271,7 +276,8 @@ class VlanNetworkTestCase(test.TestCase): db.instance_get(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn({'security_groups': [{'id': 0}]}) - db.fixed_ip_associate_pool(mox.IgnoreArg(), + db.fixed_ip_associate_by_address(mox.IgnoreArg(), + mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()).AndReturn('192.168.0.1') db.fixed_ip_update(mox.IgnoreArg(), @@ -295,17 +301,20 @@ class VlanNetworkTestCase(test.TestCase): cidr='192.168.0.1/24', network_size=100) def test_validate_networks(self): - self.mox.StubOutWithMock(db, 'project_get_networks_by_ids') - self.mox.StubOutWithMock(db, "fixed_ip_validate_by_network_address") + self.mox.StubOutWithMock(db, 'project_get_networks_by_uuids') + self.mox.StubOutWithMock(db, "fixed_ip_get_by_address") - requested_networks = [(1, "192.168.0.100")] - db.project_get_networks_by_ids(mox.IgnoreArg(), + requested_networks = [("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", + "192.168.1.100")] + db.project_get_networks_by_uuids(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(networks) - db.fixed_ip_validate_by_network_address(mox.IgnoreArg(), - mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn(fixed_ips[0]) - self.mox.ReplayAll() + fixed_ips[1]['network'] = FakeModel(**networks[1]) + fixed_ips[1]['instance'] = None + db.fixed_ip_get_by_address(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(fixed_ips[1]) + + self.mox.ReplayAll() self.network.validate_networks(None, requested_networks) def test_validate_networks_none_requested_networks(self): @@ -313,25 +322,26 @@ class VlanNetworkTestCase(test.TestCase): def test_validate_networks_empty_requested_networks(self): requested_networks = [] + self.mox.ReplayAll() + self.network.validate_networks(None, requested_networks) def test_validate_networks_invalid_fixed_ip(self): - self.mox.StubOutWithMock(db, 'project_get_networks_by_ids') - + self.mox.StubOutWithMock(db, 'project_get_networks_by_uuids') requested_networks = [(1, "192.168.0.100.1")] - db.project_get_networks_by_ids(mox.IgnoreArg(), + db.project_get_networks_by_uuids(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(networks) self.mox.ReplayAll() self.assertRaises(exception.FixedIpInvalid, - self.network.validate_networks, - None, requested_networks) + self.network.validate_networks, None, + requested_networks) def test_validate_networks_empty_fixed_ip(self): - self.mox.StubOutWithMock(db, 'project_get_networks_by_ids') + self.mox.StubOutWithMock(db, 'project_get_networks_by_uuids') requested_networks = [(1, "")] - db.project_get_networks_by_ids(mox.IgnoreArg(), + db.project_get_networks_by_uuids(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(networks) self.mox.ReplayAll() @@ -340,10 +350,10 @@ class VlanNetworkTestCase(test.TestCase): None, requested_networks) def test_validate_networks_none_fixed_ip(self): - self.mox.StubOutWithMock(db, 'project_get_networks_by_ids') + self.mox.StubOutWithMock(db, 'project_get_networks_by_uuids') requested_networks = [(1, None)] - db.project_get_networks_by_ids(mox.IgnoreArg(), + db.project_get_networks_by_uuids(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(networks) self.mox.ReplayAll() -- cgit From ed3927455cd4054b5741fe5a3f0917d91a9066db Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 16 Aug 2011 16:50:15 -0700 Subject: fix unit tests --- .../api/openstack/contrib/test_floating_ips.py | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py index 704d06582..2f6f6a64d 100644 --- a/nova/tests/api/openstack/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -153,15 +153,10 @@ class FloatingIpTest(test.TestCase): req = webob.Request.blank('/v1.1/os-floating-ips/1') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 200) - actual = json.loads(res.body)['released'] - expected = { - "id": 1, - "floating_ip": '10.10.10.10'} - self.assertEqual(actual, expected) + self.assertEqual(res.status_int, 202) def test_floating_ip_associate(self): - body = dict(associate_address=dict(fixed_ip='1.2.3.4')) + body = dict(associate_address=dict(fixed_ip='11.0.0.1')) req = webob.Request.blank('/v1.1/os-floating-ips/1/associate') req.method = 'POST' req.body = json.dumps(body) @@ -169,11 +164,13 @@ class FloatingIpTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) - actual = json.loads(res.body)['associated'] + actual = json.loads(res.body)['floating_ip'] + expected = { - "floating_ip_id": '1', - "floating_ip": "10.10.10.10", - "fixed_ip": "1.2.3.4"} + "id": 1, + "instance_id": None, + "ip": "10.10.10.10", + "fixed_ip": "11.0.0.1"} self.assertEqual(actual, expected) def test_floating_ip_disassociate(self): @@ -184,8 +181,11 @@ class FloatingIpTest(test.TestCase): req.headers['Content-Type'] = 'application/json' res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) - ip = json.loads(res.body)['disassociated'] + ip = json.loads(res.body)['floating_ip'] expected = { - "floating_ip": '10.10.10.10', + "id": 1, + "instance_id": None, + "ip": '10.10.10.10', "fixed_ip": '11.0.0.1'} + self.assertEqual(ip, expected) -- cgit From 83177757632b381d42cc5107fe7d1cba8830a10a Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 16 Aug 2011 16:59:36 -0700 Subject: all tests passing --- nova/api/openstack/contrib/floating_ips.py | 2 +- nova/tests/api/openstack/contrib/test_floating_ips.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 1276c0118..751b27c9f 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -85,7 +85,7 @@ class FloatingIPController(object): return _translate_floating_ips_view(floating_ips) - def create(self, req, body): + def create(self, req, body=None): context = req.environ['nova.context'] try: diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py index 2f6f6a64d..9b41a58c0 100644 --- a/nova/tests/api/openstack/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -143,10 +143,13 @@ class FloatingIpTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) print res self.assertEqual(res.status_int, 200) - ip = json.loads(res.body)['allocated'] + ip = json.loads(res.body)['floating_ip'] + expected = { "id": 1, - "floating_ip": '10.10.10.10'} + "instance_id": None, + "ip": "10.10.10.10", + "fixed_ip": None} self.assertEqual(ip, expected) def test_floating_ip_release(self): -- cgit From 536c1e95a68569abda6fe8ee4e3f571976521c8e Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Tue, 16 Aug 2011 20:36:49 -0700 Subject: add new vif uuid for OVS vifplug for libvirt + xenserver --- nova/virt/libvirt/vif.py | 13 +++++++------ nova/virt/xenapi/vif.py | 4 ++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/nova/virt/libvirt/vif.py b/nova/virt/libvirt/vif.py index 4cb9abda4..67366fdbe 100644 --- a/nova/virt/libvirt/vif.py +++ b/nova/virt/libvirt/vif.py @@ -98,10 +98,12 @@ class LibvirtBridgeDriver(VIFDriver): class LibvirtOpenVswitchDriver(VIFDriver): """VIF driver for Open vSwitch.""" + def get_dev_name(_self, iface_id): + return "tap-" + iface_id[0:15] + def plug(self, instance, network, mapping): - vif_id = str(instance['id']) + "-" + str(network['id']) - dev = "tap-%s" % vif_id - iface_id = "nova-" + vif_id + iface_id = mapping['vif_uuid'] + dev = self.get_dev_name(iface_id) if not linux_net._device_exists(dev): utils.execute('ip', 'tuntap', 'add', dev, 'mode', 'tap', run_as_root=True) @@ -125,11 +127,10 @@ class LibvirtOpenVswitchDriver(VIFDriver): def unplug(self, instance, network, mapping): """Unplug the VIF from the network by deleting the port from the bridge.""" - vif_id = str(instance['id']) + "-" + str(network['id']) - dev = "tap-%s" % vif_id + dev = self.get_dev_name(mapping['vif_uuid']) try: utils.execute('ovs-vsctl', 'del-port', - network['bridge'], dev, run_as_root=True) + FLAGS.libvirt_ovs_bridge, dev, run_as_root=True) utils.execute('ip', 'link', 'delete', dev, run_as_root=True) except exception.ProcessExecutionError: LOG.warning(_("Failed while unplugging vif of instance '%s'"), diff --git a/nova/virt/xenapi/vif.py b/nova/virt/xenapi/vif.py index 527602243..2f25efeb2 100644 --- a/nova/virt/xenapi/vif.py +++ b/nova/virt/xenapi/vif.py @@ -128,12 +128,12 @@ class XenAPIOpenVswitchDriver(VIFDriver): vif_rec['VM'] = vm_ref vif_rec['MAC'] = network_mapping['mac'] vif_rec['MTU'] = '1500' - vif_id = "nova-" + str(instance['id']) + "-" + str(network['id']) vif_rec['qos_algorithm_type'] = "" vif_rec['qos_algorithm_params'] = {} # OVS on the hypervisor monitors this key and uses it to # set the iface-id attribute - vif_rec['other_config'] = {"nicira-iface-id": vif_id} + vif_rec['other_config'] = \ + {"nicira-iface-id": network_mapping['vif_uuid']} return vif_rec def unplug(self, instance, network, mapping): -- cgit From 3dc1c357ca280705bc745b601daaa81e679d08d3 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Wed, 17 Aug 2011 01:18:16 -0400 Subject: Append the project_id to the SERVER-MANAGEMENT-URL header for v1.1 requests. Also, ensure that the project_id is correctly parsed from the request. --- nova/api/openstack/auth.py | 47 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index 8f1319cca..d14553cd4 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -28,6 +28,7 @@ from nova import flags from nova import log as logging from nova import utils from nova import wsgi +from nova.api.openstack import common from nova.api.openstack import faults LOG = logging.getLogger('nova.api.openstack') @@ -71,19 +72,16 @@ class AuthMiddleware(wsgi.Middleware): try: project = self.auth.get_project(project_id) except exception.ProjectNotFound: - project = None - if project: - user = self.auth.get_user(user_id) - if not project.has_member(user): - return faults.Fault(webob.exc.HTTPUnauthorized()) - else: return faults.Fault(webob.exc.HTTPUnauthorized()) - elif len(path_parts) > 1 and path_parts[1] == 'v1.0': + if project_id not in [p.id for p in projects]: + return faults.Fault(webob.exc.HTTPUnauthorized()) + else: + # As a fallback, set project_id from the headers, which is the v1.0 + # behavior. As a last resort, be forgiving to the user and set + # project_id based on a valid project of theirs. try: project_id = req.headers["X-Auth-Project-Id"] except KeyError: - # FIXME(usrleon): It needed only for compatibility - # while osapi clients don't use this header project_id = projects[0].id is_admin = self.auth.is_admin(user_id) @@ -115,12 +113,20 @@ class AuthMiddleware(wsgi.Middleware): LOG.warn(msg) return faults.Fault(webob.exc.HTTPUnauthorized(explanation=msg)) + # Gabe did this. + def _get_auth_header(key): + """Ensures that the KeyError returned is meaningful.""" + try: + return req.headers[key] + except KeyError as ex: + raise KeyError(key) try: - username = req.headers['X-Auth-User'] - key = req.headers['X-Auth-Key'] + username = _get_auth_header('X-Auth-User') + key = _get_auth_header('X-Auth-Key') except KeyError as ex: - LOG.warn(_("Could not find %s in request.") % ex) - return faults.Fault(webob.exc.HTTPUnauthorized()) + msg = _("Could not find %s in request.") % ex + LOG.warn(msg) + return faults.Fault(webob.exc.HTTPUnauthorized(explanation=msg)) token, user = self._authorize_user(username, key, req) if user and token: @@ -169,6 +175,16 @@ class AuthMiddleware(wsgi.Middleware): """ ctxt = context.get_admin_context() + project_id = req.headers.get('X-Auth-Project-Id') + if project_id is None: + # If the project_id is not provided in the headers, be forgiving to + # the user and set project_id based on a valid project of theirs. + user = self.auth.get_user_from_access_key(key) + projects = self.auth.get_projects(user.id) + if not projects: + raise webob.exc.HTTPUnauthorized() + project_id = projects[0].id + try: user = self.auth.get_user_from_access_key(key) except exception.NotFound: @@ -182,7 +198,10 @@ class AuthMiddleware(wsgi.Middleware): token_dict['token_hash'] = token_hash token_dict['cdn_management_url'] = '' os_url = req.url - token_dict['server_management_url'] = os_url + token_dict['server_management_url'] = os_url.strip('/') + version = common.get_version_from_href(os_url) + if version == '1.1': + token_dict['server_management_url'] += '/' + project_id token_dict['storage_url'] = '' token_dict['user_id'] = user.id token = self.db.auth_token_create(ctxt, token_dict) -- cgit From 77e1e0d3359bce9e5e30134f141151fc271a2e4b Mon Sep 17 00:00:00 2001 From: Ryu Ishimoto Date: Wed, 17 Aug 2011 19:05:29 +0900 Subject: Removed serverId from the response --- nova/api/openstack/contrib/virtual_interfaces.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/api/openstack/contrib/virtual_interfaces.py b/nova/api/openstack/contrib/virtual_interfaces.py index 38246aeb5..86d1128fd 100644 --- a/nova/api/openstack/contrib/virtual_interfaces.py +++ b/nova/api/openstack/contrib/virtual_interfaces.py @@ -34,7 +34,6 @@ def _translate_vif_summary_view(_context, vif): d = {} d['id'] = vif['uuid'] d['macAddress'] = vif['address'] - d['serverId'] = vif['instance_id'] return d -- cgit From 623aa3a38cab6cc617fb5fb512cdc733f69b4887 Mon Sep 17 00:00:00 2001 From: Ryu Ishimoto Date: Wed, 17 Aug 2011 19:07:14 +0900 Subject: Added virtual interfaces API test --- .../openstack/contrib/test_virtual_interfaces.py | 55 ++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 nova/tests/api/openstack/contrib/test_virtual_interfaces.py diff --git a/nova/tests/api/openstack/contrib/test_virtual_interfaces.py b/nova/tests/api/openstack/contrib/test_virtual_interfaces.py new file mode 100644 index 000000000..a3a177e33 --- /dev/null +++ b/nova/tests/api/openstack/contrib/test_virtual_interfaces.py @@ -0,0 +1,55 @@ +# Copyright (C) 2011 Midokura KK +# 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 json +import stubout +import webob + +from nova import test +from nova import compute +from nova.tests.api.openstack import fakes +from nova.api.openstack.contrib.virtual_interfaces import \ + ServerVirtualInterfaceController + + +def compute_api_get(self, context, server_id): + return {'virtual_interfaces': [ + {'uuid': '00000000-0000-0000-0000-00000000000000000', + 'address': '00-00-00-00-00-00'}, + {'uuid': '11111111-1111-1111-1111-11111111111111111', + 'address': '11-11-11-11-11-11'}]} + + +class ServerVirtualInterfaceTest(test.TestCase): + + def setUp(self): + super(ServerVirtualInterfaceTest, self).setUp() + self.controller = ServerVirtualInterfaceController() + self.stubs.Set(compute.api.API, "get", compute_api_get) + + def tearDown(self): + super(ServerVirtualInterfaceTest, self).tearDown() + + def test_get_virtual_interfaces_list(self): + req = webob.Request.blank('/v1.1/servers/1/os-virtual-interfaces') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + res_dict = json.loads(res.body) + response = {'serverVirtualInterfaces': [ + {'id': '00000000-0000-0000-0000-00000000000000000', + 'macAddress': '00-00-00-00-00-00'}, + {'id': '11111111-1111-1111-1111-11111111111111111', + 'macAddress': '11-11-11-11-11-11'}]} + self.assertEqual(res_dict, response) -- cgit From 751c8b4ff0e94b4f665af5541b9249637623d193 Mon Sep 17 00:00:00 2001 From: Ryu Ishimoto Date: Wed, 17 Aug 2011 19:58:26 +0900 Subject: Added XML support and changed JSON output keys --- nova/api/openstack/contrib/virtual_interfaces.py | 28 +++++++++++++++--------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/nova/api/openstack/contrib/virtual_interfaces.py b/nova/api/openstack/contrib/virtual_interfaces.py index 86d1128fd..715a54d52 100644 --- a/nova/api/openstack/contrib/virtual_interfaces.py +++ b/nova/api/openstack/contrib/virtual_interfaces.py @@ -24,16 +24,17 @@ from nova import log as logging from nova.api.openstack import common from nova.api.openstack import extensions from nova.api.openstack import faults +from nova.api.openstack import wsgi LOG = logging.getLogger("nova.api.virtual_interfaces") def _translate_vif_summary_view(_context, vif): - """Maps keys for attachment summary view.""" + """Maps keys for VIF summary view.""" d = {} d['id'] = vif['uuid'] - d['macAddress'] = vif['address'] + d['mac_address'] = vif['address'] return d @@ -41,12 +42,6 @@ class ServerVirtualInterfaceController(object): """The instance VIF API controller for the Openstack API. """ - _serialization_metadata = { - 'application/xml': { - 'attributes': { - 'serverVirtualInterface': ['id', - 'macAddress']}}} - def __init__(self): self.compute_api = compute.API() super(ServerVirtualInterfaceController, self).__init__() @@ -63,7 +58,7 @@ class ServerVirtualInterfaceController(object): vifs = instance['virtual_interfaces'] limited_list = common.limited(vifs, req) res = [entity_maker(context, vif) for vif in limited_list] - return {'serverVirtualInterfaces': res} + return {'virtual_interfaces': res} def index(self, req, server_id): """Returns the list of VIFs for a given instance.""" @@ -91,11 +86,24 @@ class Virtual_interfaces(extensions.ExtensionDescriptor): def get_resources(self): resources = [] + metadata = _get_metadata() + body_serializers = { + 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, + xmlns=wsgi.XMLNS_V11)} + serializer = wsgi.ResponseSerializer(body_serializers, None) res = extensions.ResourceExtension('os-virtual-interfaces', ServerVirtualInterfaceController(), parent=dict( member_name='server', - collection_name='servers')) + collection_name='servers'), + serializer=serializer) resources.append(res) return resources + + +def _get_metadata(): + metadata = { + "attributes": { + 'virtual_interface': ["id", "mac_address"]}} + return metadata -- cgit From ad8081a5b3abfc63834594c5dbf8ac1bb0721a4b Mon Sep 17 00:00:00 2001 From: Ryu Ishimoto Date: Wed, 17 Aug 2011 19:58:57 +0900 Subject: Fixed vif test to match the JSON key change --- nova/tests/api/openstack/contrib/test_virtual_interfaces.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/tests/api/openstack/contrib/test_virtual_interfaces.py b/nova/tests/api/openstack/contrib/test_virtual_interfaces.py index a3a177e33..d541a9e95 100644 --- a/nova/tests/api/openstack/contrib/test_virtual_interfaces.py +++ b/nova/tests/api/openstack/contrib/test_virtual_interfaces.py @@ -47,9 +47,9 @@ class ServerVirtualInterfaceTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) res_dict = json.loads(res.body) - response = {'serverVirtualInterfaces': [ + response = {'virtual_interfaces': [ {'id': '00000000-0000-0000-0000-00000000000000000', - 'macAddress': '00-00-00-00-00-00'}, + 'mac_address': '00-00-00-00-00-00'}, {'id': '11111111-1111-1111-1111-11111111111111111', - 'macAddress': '11-11-11-11-11-11'}]} + 'mac_address': '11-11-11-11-11-11'}]} self.assertEqual(res_dict, response) -- cgit From 4407405244c3797ed1c0433eec7686e15340dca7 Mon Sep 17 00:00:00 2001 From: Ryu Ishimoto Date: Wed, 17 Aug 2011 20:12:24 +0900 Subject: Cleaned up the file --- nova/api/openstack/contrib/virtual_interfaces.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/nova/api/openstack/contrib/virtual_interfaces.py b/nova/api/openstack/contrib/virtual_interfaces.py index 715a54d52..2d3850e12 100644 --- a/nova/api/openstack/contrib/virtual_interfaces.py +++ b/nova/api/openstack/contrib/virtual_interfaces.py @@ -38,6 +38,13 @@ def _translate_vif_summary_view(_context, vif): return d +def _get_metadata(): + metadata = { + "attributes": { + 'virtual_interface': ["id", "mac_address"]}} + return metadata + + class ServerVirtualInterfaceController(object): """The instance VIF API controller for the Openstack API. """ @@ -100,10 +107,3 @@ class Virtual_interfaces(extensions.ExtensionDescriptor): resources.append(res) return resources - - -def _get_metadata(): - metadata = { - "attributes": { - 'virtual_interface': ["id", "mac_address"]}} - return metadata -- cgit From 5415a59d473fb9ed374e746fb36f30fc664c4dec Mon Sep 17 00:00:00 2001 From: Ryu Ishimoto Date: Wed, 17 Aug 2011 20:17:09 +0900 Subject: Updated get_updated time --- nova/api/openstack/contrib/virtual_interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/openstack/contrib/virtual_interfaces.py b/nova/api/openstack/contrib/virtual_interfaces.py index 2d3850e12..b3bb00a8f 100644 --- a/nova/api/openstack/contrib/virtual_interfaces.py +++ b/nova/api/openstack/contrib/virtual_interfaces.py @@ -88,7 +88,7 @@ class Virtual_interfaces(extensions.ExtensionDescriptor): return "http://docs.openstack.org/ext/virtual_interfaces/api/v1.1" def get_updated(self): - return "2011-08-05T00:00:00+00:00" + return "2011-08-17T00:00:00+00:00" def get_resources(self): resources = [] -- cgit From 2e44657a20cdd620d982b252ca35413c07fd3c2b Mon Sep 17 00:00:00 2001 From: Ryu Ishimoto Date: Wed, 17 Aug 2011 20:23:21 +0900 Subject: Cleaned up the extension metadata API data --- nova/api/openstack/contrib/virtual_interfaces.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/nova/api/openstack/contrib/virtual_interfaces.py b/nova/api/openstack/contrib/virtual_interfaces.py index b3bb00a8f..dab61efc8 100644 --- a/nova/api/openstack/contrib/virtual_interfaces.py +++ b/nova/api/openstack/contrib/virtual_interfaces.py @@ -76,10 +76,10 @@ class ServerVirtualInterfaceController(object): class Virtual_interfaces(extensions.ExtensionDescriptor): def get_name(self): - return "Virtual_interfaces" + return "VirtualInterfaces" def get_alias(self): - return "os-virtual-interfaces" + return "virtual_interfaces" def get_description(self): return "Virtual interface support" @@ -98,12 +98,11 @@ class Virtual_interfaces(extensions.ExtensionDescriptor): 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, xmlns=wsgi.XMLNS_V11)} serializer = wsgi.ResponseSerializer(body_serializers, None) - res = extensions.ResourceExtension('os-virtual-interfaces', - ServerVirtualInterfaceController(), - parent=dict( - member_name='server', - collection_name='servers'), - serializer=serializer) + res = extensions.ResourceExtension( + 'os-virtual-interfaces', + controller=ServerVirtualInterfaceController(), + parent=dict(member_name='server', collection_name='servers'), + serializer=serializer) resources.append(res) return resources -- cgit From b29b5c85cbd5ab3c21fc00d1c037af1118ea30b4 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 17 Aug 2011 10:55:27 -0400 Subject: Updated tests to correctly use the tenant id --- nova/tests/api/openstack/contrib/test_quotas.py | 11 ++++++----- nova/tests/api/openstack/test_auth.py | 8 +++++--- nova/tests/integrated/api/client.py | 5 +++-- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/nova/tests/api/openstack/contrib/test_quotas.py b/nova/tests/api/openstack/contrib/test_quotas.py index f6a25385f..7faef08b2 100644 --- a/nova/tests/api/openstack/contrib/test_quotas.py +++ b/nova/tests/api/openstack/contrib/test_quotas.py @@ -78,7 +78,8 @@ class QuotaSetsTest(test.TestCase): self.assertEqual(qs['injected_file_content_bytes'], 10240) def test_quotas_defaults(self): - req = webob.Request.blank('/v1.1/os-quota-sets/fake_tenant/defaults') + uri = '/v1.1/fake_tenant/os-quota-sets/fake_tenant/defaults' + req = webob.Request.blank(uri) req.method = 'GET' req.headers['Content-Type'] = 'application/json' res = req.get_response(fakes.wsgi_app()) @@ -99,7 +100,7 @@ class QuotaSetsTest(test.TestCase): self.assertEqual(json.loads(res.body), expected) def test_quotas_show_as_admin(self): - req = webob.Request.blank('/v1.1/os-quota-sets/1234') + req = webob.Request.blank('/v1.1/1234/os-quota-sets/1234') req.method = 'GET' req.headers['Content-Type'] = 'application/json' res = req.get_response(fakes.wsgi_app( @@ -109,7 +110,7 @@ class QuotaSetsTest(test.TestCase): self.assertEqual(json.loads(res.body), quota_set('1234')) def test_quotas_show_as_unauthorized_user(self): - req = webob.Request.blank('/v1.1/os-quota-sets/1234') + req = webob.Request.blank('/v1.1/fake/os-quota-sets/1234') req.method = 'GET' req.headers['Content-Type'] = 'application/json' res = req.get_response(fakes.wsgi_app( @@ -124,7 +125,7 @@ class QuotaSetsTest(test.TestCase): 'metadata_items': 128, 'injected_files': 5, 'injected_file_content_bytes': 10240}} - req = webob.Request.blank('/v1.1/os-quota-sets/update_me') + req = webob.Request.blank('/v1.1/1234/os-quota-sets/update_me') req.method = 'PUT' req.body = json.dumps(updated_quota_set) req.headers['Content-Type'] = 'application/json' @@ -141,7 +142,7 @@ class QuotaSetsTest(test.TestCase): 'metadata_items': 128, 'injected_files': 5, 'injected_file_content_bytes': 10240}} - req = webob.Request.blank('/v1.1/os-quota-sets/update_me') + req = webob.Request.blank('/v1.1/1234/os-quota-sets/update_me') req.method = 'PUT' req.body = json.dumps(updated_quota_set) req.headers['Content-Type'] = 'application/json' diff --git a/nova/tests/api/openstack/test_auth.py b/nova/tests/api/openstack/test_auth.py index 306ae1aa0..7fd3935a2 100644 --- a/nova/tests/api/openstack/test_auth.py +++ b/nova/tests/api/openstack/test_auth.py @@ -53,6 +53,7 @@ class Test(test.TestCase): req = webob.Request.blank('/v1.0/') req.headers['X-Auth-User'] = 'user1' req.headers['X-Auth-Key'] = 'user1_key' + req.headers['X-Auth-Project-Id'] = 'user1_project' result = req.get_response(fakes.wsgi_app(fake_auth=False)) self.assertEqual(result.status, '204 No Content') self.assertEqual(len(result.headers['X-Auth-Token']), 40) @@ -73,14 +74,14 @@ class Test(test.TestCase): self.assertEqual(result.status, '204 No Content') self.assertEqual(len(result.headers['X-Auth-Token']), 40) self.assertEqual(result.headers['X-Server-Management-Url'], - "http://foo/v1.0/") + "http://foo/v1.0") self.assertEqual(result.headers['X-CDN-Management-Url'], "") self.assertEqual(result.headers['X-Storage-Url'], "") token = result.headers['X-Auth-Token'] self.stubs.Set(nova.api.openstack, 'APIRouterV10', fakes.FakeRouter) - req = webob.Request.blank('/v1.0/fake') + req = webob.Request.blank('/v1.0/user1_project') req.headers['X-Auth-Token'] = token result = req.get_response(fakes.wsgi_app(fake_auth=False)) self.assertEqual(result.status, '200 OK') @@ -125,7 +126,7 @@ class Test(test.TestCase): token = result.headers['X-Auth-Token'] self.stubs.Set(nova.api.openstack, 'APIRouterV10', fakes.FakeRouter) - req = webob.Request.blank('/v1.0/fake') + req = webob.Request.blank('/v1.0/') req.headers['X-Auth-Token'] = token req.headers['X-Auth-Project-Id'] = 'user2_project' result = req.get_response(fakes.wsgi_app(fake_auth=False)) @@ -136,6 +137,7 @@ class Test(test.TestCase): req = webob.Request.blank('/v1.0/') req.headers['X-Auth-User'] = 'unknown_user' req.headers['X-Auth-Key'] = 'unknown_user_key' + req.headers['X-Auth-Project-Id'] = 'user_project' result = req.get_response(fakes.wsgi_app(fake_auth=False)) self.assertEqual(result.status, '401 Unauthorized') diff --git a/nova/tests/integrated/api/client.py b/nova/tests/integrated/api/client.py index fabf49817..67c35fe6b 100644 --- a/nova/tests/integrated/api/client.py +++ b/nova/tests/integrated/api/client.py @@ -115,7 +115,8 @@ class TestOpenStackClient(object): auth_uri = self.auth_uri headers = {'X-Auth-User': self.auth_user, - 'X-Auth-Key': self.auth_key} + 'X-Auth-Key': self.auth_key, + 'X-Auth-Project-Id': self.project_id} response = self.request(auth_uri, headers=headers) @@ -138,7 +139,7 @@ class TestOpenStackClient(object): # NOTE(justinsb): httplib 'helpfully' converts headers to lower case base_uri = auth_result['x-server-management-url'] - full_uri = '%s/%s%s' % (base_uri, self.project_id, relative_uri) + full_uri = '%s/%s' % (base_uri, relative_uri) headers = kwargs.setdefault('headers', {}) headers['X-Auth-Token'] = auth_result['x-auth-token'] -- cgit From 289219f8ef7ae677aaa8d0720167470e80843fe1 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 17 Aug 2011 12:36:39 -0400 Subject: Undo an unecessary change --- nova/api/openstack/contrib/security_groups.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/openstack/contrib/security_groups.py b/nova/api/openstack/contrib/security_groups.py index d1230df69..6c57fbb51 100644 --- a/nova/api/openstack/contrib/security_groups.py +++ b/nova/api/openstack/contrib/security_groups.py @@ -458,7 +458,7 @@ class SecurityGroupRulesXMLDeserializer(wsgi.MetadataXMLDeserializer): def _get_metadata(): metadata = { "attributes": { - "security_group": ["id", "project_id", "name"], + "security_group": ["id", "tenant_id", "name"], "rule": ["id", "parent_group_id"], "security_group_rule": ["id", "parent_group_id"], } -- cgit From fdfd551dd46b831f5c44a8c62614de7bcbc1a5eb Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 17 Aug 2011 12:37:50 -0400 Subject: very minor cleanup --- nova/api/openstack/auth.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index d14553cd4..b6ff1126b 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -113,7 +113,6 @@ class AuthMiddleware(wsgi.Middleware): LOG.warn(msg) return faults.Fault(webob.exc.HTTPUnauthorized(explanation=msg)) - # Gabe did this. def _get_auth_header(key): """Ensures that the KeyError returned is meaningful.""" try: -- cgit From 4f3a33859c350ff13b2fd94e33de4f10a7f93bc1 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 17 Aug 2011 10:05:01 -0700 Subject: fix some naming inconsistencies, make associate/disassociate PUTs --- nova/api/openstack/contrib/floating_ips.py | 35 +++++++++------------- .../api/openstack/contrib/test_floating_ips.py | 6 ++-- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 751b27c9f..af3eee16a 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -102,45 +102,38 @@ class FloatingIPController(object): def delete(self, req, id): context = req.environ['nova.context'] - ip = self.network_api.get_floating_ip(context, id) + floating_ip = self.network_api.get_floating_ip(context, id) - if 'fixed_ip' in ip: - try: - self.disassociate(req, id) - except exception.ApiError: - LOG.warn("disassociate failure %s", id) + if 'fixed_ip' in floating_ip: + self.network_api.disassociate_floating_ip(context, floating_ip['address']) - self.network_api.release_floating_ip(context, address=ip['address']) + self.network_api.release_floating_ip(context, address=floating_ip['address']) return exc.HTTPAccepted() def associate(self, req, id, body): - """ /floating_ips/{id}/associate fixed ip in body """ + """PUT /floating_ips/{id}/associate fixed ip in body """ context = req.environ['nova.context'] floating_ip = self._get_ip_by_id(context, id) - fixed_ip = body['associate_address']['fixed_ip'] + fixed_ip = body['floating_ip']['fixed_ip'] - try: - self.network_api.associate_floating_ip(context, - floating_ip, fixed_ip) - except rpc.RemoteError: - raise + self.network_api.associate_floating_ip(context, + floating_ip, fixed_ip) floating_ip = self.network_api.get_floating_ip(context, id) return _translate_floating_ip_view(floating_ip) def disassociate(self, req, id, body=None): - """ POST /floating_ips/{id}/disassociate """ + """PUT /floating_ips/{id}/disassociate """ context = req.environ['nova.context'] floating_ip = self.network_api.get_floating_ip(context, id) address = floating_ip['address'] - try: + # no-op if this ip is already disassociated + if 'fixed_ip' in floating_ip: self.network_api.disassociate_floating_ip(context, address) - except rpc.RemoteError: - raise + floating_ip = self.network_api.get_floating_ip(context, id) - floating_ip = self.network_api.get_floating_ip(context, id) return _translate_floating_ip_view(floating_ip) def _get_ip_by_id(self, context, value): @@ -170,8 +163,8 @@ class Floating_ips(extensions.ExtensionDescriptor): res = extensions.ResourceExtension('os-floating-ips', FloatingIPController(), member_actions={ - 'associate': 'POST', - 'disassociate': 'POST'}) + 'associate': 'PUT', + 'disassociate': 'PUT'}) resources.append(res) return resources diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py index 9b41a58c0..e506519f4 100644 --- a/nova/tests/api/openstack/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -159,9 +159,9 @@ class FloatingIpTest(test.TestCase): self.assertEqual(res.status_int, 202) def test_floating_ip_associate(self): - body = dict(associate_address=dict(fixed_ip='11.0.0.1')) + body = dict(floating_ip=dict(fixed_ip='11.0.0.1')) req = webob.Request.blank('/v1.1/os-floating-ips/1/associate') - req.method = 'POST' + req.method = 'PUT' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -179,7 +179,7 @@ class FloatingIpTest(test.TestCase): def test_floating_ip_disassociate(self): body = dict() req = webob.Request.blank('/v1.1/os-floating-ips/1/disassociate') - req.method = 'POST' + req.method = 'PUT' req.body = json.dumps(body) req.headers['Content-Type'] = 'application/json' res = req.get_response(fakes.wsgi_app()) -- cgit From 65d7db1136557b7af1f0b9413bacc8fc59e7211f Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 17 Aug 2011 10:23:44 -0700 Subject: pep8 fix --- nova/api/openstack/contrib/floating_ips.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index af3eee16a..2f5fdd001 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -105,9 +105,11 @@ class FloatingIPController(object): floating_ip = self.network_api.get_floating_ip(context, id) if 'fixed_ip' in floating_ip: - self.network_api.disassociate_floating_ip(context, floating_ip['address']) + self.network_api.disassociate_floating_ip(context, + floating_ip['address']) - self.network_api.release_floating_ip(context, address=floating_ip['address']) + self.network_api.release_floating_ip(context, + address=floating_ip['address']) return exc.HTTPAccepted() def associate(self, req, id, body): -- cgit From 69c26210dd821df0d2160e51b10f147db2a40249 Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Wed, 17 Aug 2011 12:08:14 -0700 Subject: Fixed broken unit testcases --- nova/tests/api/openstack/test_extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 9c29363c6..878ec00e5 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -85,7 +85,7 @@ class ExtensionControllerTest(test.TestCase): ext_path = os.path.join(os.path.dirname(__file__), "extensions") self.flags(osapi_extensions_path=ext_path) self.ext_list = [ - "Createserverext" + "Createserverext", "FlavorExtraSpecs", "Floating_ips", "Fox In Socks", -- cgit From 7e3f360eb256ba82629a44de60d36be643d5105d Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 17 Aug 2011 15:33:08 -0400 Subject: Added migration for accessIPv4 and accessIPv6 --- .../versions/037_add_instances_accessip.py | 49 ++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/037_add_instances_accessip.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/037_add_instances_accessip.py b/nova/db/sqlalchemy/migrate_repo/versions/037_add_instances_accessip.py new file mode 100644 index 000000000..82de2a874 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/037_add_instances_accessip.py @@ -0,0 +1,49 @@ +# Copyright 2011 OpenStack LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from sqlalchemy import Column, Integer, MetaData, Table, String + +meta = MetaData() + +accessIPv4 = Column( + 'access_ip_v4', + String(length=255, convert_unicode=False, assert_unicode=None, + unicode_error=None, _warn_on_bytestring=False), + nullable=True) + +accessIPv6 = Column( + 'access_ip_v6', + String(length=255, convert_unicode=False, assert_unicode=None, + unicode_error=None, _warn_on_bytestring=False), + nullable=True) + +instances = Table('instances', meta, + Column('id', Integer(), primary_key=True, nullable=False), + ) + + + +def upgrade(migrate_engine): + # Upgrade operations go here. Don't create your own engine; + # bind migrate_engine to your metadata + meta.bind = migrate_engine + instances.create_column(accessIPv4) + instances.create_column(accessIPv6) + + +def downgrade(migrate_engine): + # Operations to reverse the above upgrade go here. + meta.bind = migrate_engine + instances.drop_column('access_ip_v4') + instances.drop_column('access_ip_v6') -- cgit From 8ba3ea03aa58d5b0791b9fd3654dd034cbd3a8bc Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 17 Aug 2011 15:40:17 -0400 Subject: Added accessip to models pep8 --- .../sqlalchemy/migrate_repo/versions/037_add_instances_accessip.py | 1 - nova/db/sqlalchemy/models.py | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/037_add_instances_accessip.py b/nova/db/sqlalchemy/migrate_repo/versions/037_add_instances_accessip.py index 82de2a874..39f0dd6ce 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/037_add_instances_accessip.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/037_add_instances_accessip.py @@ -33,7 +33,6 @@ instances = Table('instances', meta, ) - def upgrade(migrate_engine): # Upgrade operations go here. Don't create your own engine; # bind migrate_engine to your metadata diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index f2a4680b0..1249a6269 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -232,6 +232,11 @@ class Instance(BASE, NovaBase): root_device_name = Column(String(255)) + # User editable field meant to represent what ip should be used + # to connect to the instance + access_ip_v4 = Column(String(255)) + access_ip_v6 = Column(String(255)) + # TODO(vish): see Ewan's email about state improvements, probably # should be in a driver base class or some such # vmstate_state = running, halted, suspended, paused -- cgit From a4379a342798016a9dc40761561c996093945d87 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Wed, 17 Aug 2011 16:03:03 -0400 Subject: Updated server create XML deserializer to account for accessIPv4 and accessIPv6 --- nova/api/openstack/create_instance_helper.py | 3 +- nova/tests/api/openstack/test_servers.py | 56 ++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 4e1da549e..5ba8afe97 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -443,7 +443,8 @@ class ServerXMLDeserializerV11(wsgi.MetadataXMLDeserializer): server = {} server_node = self.find_first_child_named(node, 'server') - attributes = ["name", "imageRef", "flavorRef", "adminPass"] + attributes = ["name", "imageRef", "flavorRef", "adminPass", + "accessIPv4", "accessIPv6"] for attr in attributes: if server_node.getAttribute(attr): server[attr] = server_node.getAttribute(attr) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index a510d7d97..6f1173d46 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -2491,6 +2491,62 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): } self.assertEquals(request['body'], expected) + def test_access_ipv4(self): + serial_request = """ +""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = { + "server": { + "name": "new-server-test", + "imageRef": "1", + "flavorRef": "2", + "accessIPv4": "1.2.3.4", + }, + } + self.assertEquals(request['body'], expected) + + def test_access_ipv6(self): + serial_request = """ +""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = { + "server": { + "name": "new-server-test", + "imageRef": "1", + "flavorRef": "2", + "accessIPv6": "fead:::::1234", + }, + } + self.assertEquals(request['body'], expected) + + def test_access_ip(self): + serial_request = """ +""" + request = self.deserializer.deserialize(serial_request, 'create') + expected = { + "server": { + "name": "new-server-test", + "imageRef": "1", + "flavorRef": "2", + "accessIPv4": "1.2.3.4", + "accessIPv6": "fead:::::1234", + }, + } + self.assertEquals(request['body'], expected) + def test_admin_pass(self): serial_request = """ Date: Thu, 18 Aug 2011 10:53:01 -0400 Subject: Added accessIPv4 and accessIPv6 to servers view builder Updated compute api to handle accessIPv4 and 6 --- nova/api/openstack/create_instance_helper.py | 2 + nova/api/openstack/views/servers.py | 4 + nova/compute/api.py | 15 ++- nova/tests/api/openstack/test_servers.py | 173 +++++++++++++++++++++++++++ 4 files changed, 189 insertions(+), 5 deletions(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 5ba8afe97..332d5d9bb 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -157,6 +157,8 @@ class CreateInstanceHelper(object): key_name=key_name, key_data=key_data, metadata=server_dict.get('metadata', {}), + access_ip_v4=server_dict.get('accessIPv4'), + access_ip_v6=server_dict.get('accessIPv6'), injected_files=injected_files, admin_password=password, zone_blob=zone_blob, diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index edc328129..3b91c037a 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -182,6 +182,10 @@ class ViewBuilderV11(ViewBuilder): def _build_extra(self, response, inst): self._build_links(response, inst) response['uuid'] = inst['uuid'] + if inst.get('access_ip_v4'): + response['accessIPv4'] = inst['access_ip_v4'] + if inst.get('access_ip_v6'): + response['accessIPv6'] = inst['access_ip_v6'] def _build_links(self, response, inst): href = self.generate_href(inst["id"]) diff --git a/nova/compute/api.py b/nova/compute/api.py index e909e9959..168d46689 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -153,7 +153,7 @@ class API(base.Base): key_name=None, key_data=None, security_group='default', availability_zone=None, user_data=None, metadata=None, injected_files=None, admin_password=None, zone_blob=None, - reservation_id=None): + reservation_id=None, access_ip_v4=None, access_ip_v6=None): """Verify all the input parameters regardless of the provisioning strategy being performed.""" @@ -247,6 +247,8 @@ class API(base.Base): 'key_data': key_data, 'locked': False, 'metadata': metadata, + 'access_ip_v4': access_ip_v4, + 'access_ip_v6': access_ip_v6, 'availability_zone': availability_zone, 'os_type': os_type, 'architecture': architecture, @@ -421,6 +423,7 @@ class API(base.Base): 'num_instances': num_instances, } + print base_options rpc.cast(context, FLAGS.scheduler_topic, {"method": "run_instance", @@ -438,7 +441,8 @@ class API(base.Base): key_name=None, key_data=None, security_group='default', availability_zone=None, user_data=None, metadata=None, injected_files=None, admin_password=None, zone_blob=None, - reservation_id=None, block_device_mapping=None): + reservation_id=None, block_device_mapping=None, + access_ip_v4=None, access_ip_v6=None): """Provision the instances by passing the whole request to the Scheduler for execution. Returns a Reservation ID related to the creation of all of these instances.""" @@ -454,7 +458,7 @@ class API(base.Base): key_name, key_data, security_group, availability_zone, user_data, metadata, injected_files, admin_password, zone_blob, - reservation_id) + reservation_id, access_ip_v4, access_ip_v6) self._ask_scheduler_to_create_instance(context, base_options, instance_type, zone_blob, @@ -472,7 +476,8 @@ class API(base.Base): key_name=None, key_data=None, security_group='default', availability_zone=None, user_data=None, metadata=None, injected_files=None, admin_password=None, zone_blob=None, - reservation_id=None, block_device_mapping=None): + reservation_id=None, block_device_mapping=None, + access_ip_v4=None, access_ip_v6=None): """ Provision the instances by sending off a series of single instance requests to the Schedulers. This is fine for trival @@ -496,7 +501,7 @@ class API(base.Base): key_name, key_data, security_group, availability_zone, user_data, metadata, injected_files, admin_password, zone_blob, - reservation_id) + reservation_id, access_ip_v4, access_ip_v6) block_device_mapping = block_device_mapping or [] instances = [] diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 6f1173d46..25fce95b4 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1379,6 +1379,8 @@ class ServersTest(test.TestCase): 'display_name': 'server_test', 'uuid': FAKE_UUID, 'instance_type': dict(inst_type), + 'access_ip_v4': '1.2.3.4', + 'access_ip_v6': 'fead::1234', 'image_ref': image_ref, "created_at": datetime.datetime(2010, 10, 10, 12, 0, 0), "updated_at": datetime.datetime(2010, 11, 11, 11, 0, 0), @@ -1579,6 +1581,69 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) + def test_create_instance_with_access_ip_v1_1(self): + self._setup_for_create_instance() + + # proper local hrefs must start with 'http://localhost/v1.1/' + image_href = 'http://localhost/v1.1/images/2' + flavor_ref = 'http://localhost/flavors/3' + access_ipv4 = '1.2.3.4' + access_ipv6 = 'fead::1234' + expected_flavor = { + "id": "3", + "links": [ + { + "rel": "bookmark", + "href": 'http://localhost/flavors/3', + }, + ], + } + expected_image = { + "id": "2", + "links": [ + { + "rel": "bookmark", + "href": 'http://localhost/images/2', + }, + ], + } + body = { + 'server': { + 'name': 'server_test', + 'imageRef': image_href, + 'flavorRef': flavor_ref, + 'accessIPv4': access_ipv4, + 'accessIPv6': access_ipv6, + 'metadata': { + 'hello': 'world', + 'open': 'stack', + }, + 'personality': [ + { + "path": "/etc/banner.txt", + "contents": "MQ==", + }, + ], + }, + } + + req = webob.Request.blank('/v1.1/servers') + req.method = 'POST' + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + + res = req.get_response(fakes.wsgi_app()) + + self.assertEqual(res.status_int, 202) + server = json.loads(res.body)['server'] + self.assertEqual(16, len(server['adminPass'])) + self.assertEqual(1, server['id']) + self.assertEqual(0, server['progress']) + self.assertEqual('server_test', server['name']) + self.assertEqual(expected_flavor, server['flavor']) + self.assertEqual(expected_image, server['image']) + self.assertEqual(access_ipv4, server['accessIPv4']) + def test_create_instance_v1_1(self): self._setup_for_create_instance() @@ -3095,6 +3160,8 @@ class ServersViewBuilderV11Test(test.TestCase): "display_description": "", "locked": False, "metadata": [], + "accessIPv4": "1.2.3.4", + "accessIPv6": "fead::::1234", #"address": , #"floating_ips": [{"address":ip} for ip in public_addresses]} "uuid": "deadbeef-feed-edee-beef-d0ea7beefedd"} @@ -3237,6 +3304,112 @@ class ServersViewBuilderV11Test(test.TestCase): output = self.view_builder.build(self.instance, True) self.assertDictMatch(output, expected_server) + def test_build_server_detail_with_accessipv4(self): + + self.instance['access_ip_v4'] = '1.2.3.4' + + image_bookmark = "http://localhost/images/5" + flavor_bookmark = "http://localhost/flavors/1" + expected_server = { + "server": { + "id": 1, + "uuid": self.instance['uuid'], + "updated": "2010-11-11T11:00:00Z", + "created": "2010-10-10T12:00:00Z", + "progress": 0, + "name": "test_server", + "status": "BUILD", + "hostId": '', + "image": { + "id": "5", + "links": [ + { + "rel": "bookmark", + "href": image_bookmark, + }, + ], + }, + "flavor": { + "id": "1", + "links": [ + { + "rel": "bookmark", + "href": flavor_bookmark, + }, + ], + }, + "addresses": {}, + "metadata": {}, + "accessIPv4": "1.2.3.4", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/servers/1", + }, + { + "rel": "bookmark", + "href": "http://localhost/servers/1", + }, + ], + } + } + + output = self.view_builder.build(self.instance, True) + self.assertDictMatch(output, expected_server) + + def test_build_server_detail_with_accessipv6(self): + + self.instance['access_ip_v6'] = 'fead::1234' + + image_bookmark = "http://localhost/images/5" + flavor_bookmark = "http://localhost/flavors/1" + expected_server = { + "server": { + "id": 1, + "uuid": self.instance['uuid'], + "updated": "2010-11-11T11:00:00Z", + "created": "2010-10-10T12:00:00Z", + "progress": 0, + "name": "test_server", + "status": "BUILD", + "hostId": '', + "image": { + "id": "5", + "links": [ + { + "rel": "bookmark", + "href": image_bookmark, + }, + ], + }, + "flavor": { + "id": "1", + "links": [ + { + "rel": "bookmark", + "href": flavor_bookmark, + }, + ], + }, + "addresses": {}, + "metadata": {}, + "accessIPv6": "fead::1234", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/servers/1", + }, + { + "rel": "bookmark", + "href": "http://localhost/servers/1", + }, + ], + } + } + + output = self.view_builder.build(self.instance, True) + self.assertDictMatch(output, expected_server) + def test_build_server_detail_with_metadata(self): metadata = [] -- cgit From 9b5416e8afc115fabb76664a65b6d33e9ba89b7f Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 18 Aug 2011 11:05:59 -0400 Subject: Updated ServersXMLSerializer to allow accessIPv4 and accessIPv6 in XML responses --- nova/api/openstack/servers.py | 4 ++++ nova/tests/api/openstack/test_servers.py | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 335ecad86..f06ee6b62 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -837,6 +837,10 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): node.setAttribute('created', str(server['created'])) node.setAttribute('updated', str(server['updated'])) node.setAttribute('status', server['status']) + if 'accessIPv4' in server: + node.setAttribute('accessIPv4', str(server['accessIPv4'])) + if 'accessIPv6' in server: + node.setAttribute('accessIPv6', str(server['accessIPv6'])) if 'progress' in server: node.setAttribute('progress', str(server['progress'])) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 25fce95b4..0bdbb2006 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -3494,6 +3494,8 @@ class ServerXMLSerializationTest(test.TestCase): "name": "test_server", "status": "BUILD", "hostId": 'e4d909c290d0fb1ca068ffaddf22cbd0', + "accessIPv4": "1.2.3.4", + "accessIPv6": "fead::1234", "image": { "id": "5", "links": [ @@ -3570,6 +3572,8 @@ class ServerXMLSerializationTest(test.TestCase): created="%(expected_now)s" hostId="e4d909c290d0fb1ca068ffaddf22cbd0" status="BUILD" + accessIPv4="1.2.3.4" + accessIPv6="fead::1234" progress="0"> @@ -3614,6 +3618,7 @@ class ServerXMLSerializationTest(test.TestCase): "progress": 0, "name": "test_server", "status": "BUILD", + "accessIPv6": "fead::1234", "hostId": "e4d909c290d0fb1ca068ffaddf22cbd0", "adminPass": "test_password", "image": { @@ -3692,6 +3697,7 @@ class ServerXMLSerializationTest(test.TestCase): created="%(expected_now)s" hostId="e4d909c290d0fb1ca068ffaddf22cbd0" status="BUILD" + accessIPv6="fead::1234" adminPass="test_password" progress="0"> -- cgit From 155d640d3d53bcf76daa0ff0ae67ac5dbbe3022a Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 18 Aug 2011 12:19:47 -0400 Subject: Fixed issue where accessIP was added in none detail responses --- nova/api/openstack/views/servers.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 3b91c037a..8b3a1e221 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -143,6 +143,12 @@ class ViewBuilderV11(ViewBuilder): response['server']['progress'] = 100 elif response['server']['status'] == "BUILD": response['server']['progress'] = 0 + + if inst.get('access_ip_v4'): + response['server']['accessIPv4'] = inst['access_ip_v4'] + if inst.get('access_ip_v6'): + response['server']['accessIPv6'] = inst['access_ip_v6'] + return response def _build_image(self, response, inst): @@ -182,10 +188,6 @@ class ViewBuilderV11(ViewBuilder): def _build_extra(self, response, inst): self._build_links(response, inst) response['uuid'] = inst['uuid'] - if inst.get('access_ip_v4'): - response['accessIPv4'] = inst['access_ip_v4'] - if inst.get('access_ip_v6'): - response['accessIPv6'] = inst['access_ip_v6'] def _build_links(self, response, inst): href = self.generate_href(inst["id"]) -- cgit From 186987d854fabde120a37713909eaecfbabeaece Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 18 Aug 2011 16:22:56 +0000 Subject: Corrected the hardcoded filter path. Also simplified the filter matching code in host_filter.py --- nova/compute/api.py | 3 +-- nova/scheduler/host_filter.py | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index e909e9959..229d02af4 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -411,12 +411,11 @@ class API(base.Base): LOG.debug(_("Casting to scheduler for %(pid)s/%(uid)s's" " (all-at-once)") % locals()) - filter_class = 'nova.scheduler.host_filter.InstanceTypeFilter' request_spec = { 'image': image, 'instance_properties': base_options, 'instance_type': instance_type, - 'filter': filter_class, + 'filter': 'InstanceTypeFilter' 'blob': zone_blob, 'num_instances': num_instances, } diff --git a/nova/scheduler/host_filter.py b/nova/scheduler/host_filter.py index 4bc5158cc..826a99b0a 100644 --- a/nova/scheduler/host_filter.py +++ b/nova/scheduler/host_filter.py @@ -58,8 +58,6 @@ def choose_host_filter(filter_name=None): if not filter_name: filter_name = FLAGS.default_host_filter for filter_class in _get_filters(): - host_match = "%s.%s" % (filter_class.__module__, filter_class.__name__) - if (host_match.startswith("nova.scheduler.filters") and - (host_match.split(".")[-1] == filter_name)): + if filter_class.__name__ == filter_name: return filter_class() raise exception.SchedulerHostFilterNotFound(filter_name=filter_name) -- cgit From 7c957d7821437604b99d7383c8674676dc3921dc Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 18 Aug 2011 16:40:41 +0000 Subject: Added the fix for the missing parameter for the call to create_db_entry_for_new_instance() --- nova/scheduler/abstract_scheduler.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nova/scheduler/abstract_scheduler.py b/nova/scheduler/abstract_scheduler.py index 77db67773..3930148e2 100644 --- a/nova/scheduler/abstract_scheduler.py +++ b/nova/scheduler/abstract_scheduler.py @@ -62,12 +62,13 @@ class AbstractScheduler(driver.Scheduler): host = build_plan_item['hostname'] base_options = request_spec['instance_properties'] image = request_spec['image'] + instance_type = request_spec['instance_type'] # TODO(sandy): I guess someone needs to add block_device_mapping # support at some point? Also, OS API has no concept of security # groups. instance = compute_api.API().create_db_entry_for_new_instance(context, - image, base_options, None, []) + instance_type, image, base_options, None, []) instance_id = instance['id'] kwargs['instance_id'] = instance_id @@ -158,8 +159,8 @@ class AbstractScheduler(driver.Scheduler): self._ask_child_zone_to_create_instance(context, host_info, request_spec, kwargs) else: - self._provision_resource_locally(context, host_info, request_spec, - kwargs) + self._provision_resource_locally(context, instance_type, host_info, + request_spec, kwargs) def _provision_resource(self, context, build_plan_item, instance_id, request_spec, kwargs): -- cgit From 9033d4879556452d3b7c0ee9fa9fcafbea59e5be Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 18 Aug 2011 12:55:27 -0400 Subject: minor cleanup --- nova/compute/api.py | 1 - nova/tests/api/openstack/test_servers.py | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 168d46689..49222d476 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -423,7 +423,6 @@ class API(base.Base): 'num_instances': num_instances, } - print base_options rpc.cast(context, FLAGS.scheduler_topic, {"method": "run_instance", diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 0bdbb2006..b3e9bfe04 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -2580,14 +2580,14 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): name="new-server-test" imageRef="1" flavorRef="2" - accessIPv6="fead:::::1234"/>""" + accessIPv6="fead::1234"/>""" request = self.deserializer.deserialize(serial_request, 'create') expected = { "server": { "name": "new-server-test", "imageRef": "1", "flavorRef": "2", - "accessIPv6": "fead:::::1234", + "accessIPv6": "fead::1234", }, } self.assertEquals(request['body'], expected) @@ -2599,7 +2599,7 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): imageRef="1" flavorRef="2" accessIPv4="1.2.3.4" - accessIPv6="fead:::::1234"/>""" + accessIPv6="fead::1234"/>""" request = self.deserializer.deserialize(serial_request, 'create') expected = { "server": { @@ -2607,7 +2607,7 @@ class TestServerCreateRequestXMLDeserializerV11(test.TestCase): "imageRef": "1", "flavorRef": "2", "accessIPv4": "1.2.3.4", - "accessIPv6": "fead:::::1234", + "accessIPv6": "fead::1234", }, } self.assertEquals(request['body'], expected) @@ -3161,7 +3161,7 @@ class ServersViewBuilderV11Test(test.TestCase): "locked": False, "metadata": [], "accessIPv4": "1.2.3.4", - "accessIPv6": "fead::::1234", + "accessIPv6": "fead::1234", #"address": , #"floating_ips": [{"address":ip} for ip in public_addresses]} "uuid": "deadbeef-feed-edee-beef-d0ea7beefedd"} -- cgit From ffbf26392f06ecac55e72ed25f59fd550a5262f5 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 18 Aug 2011 17:30:00 +0000 Subject: Changed the filter specified in _ask_scheduler_to_create_instance() to None, since the value isn't used when creating an instance. --- nova/compute/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 229d02af4..e033c6c74 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -415,7 +415,7 @@ class API(base.Base): 'image': image, 'instance_properties': base_options, 'instance_type': instance_type, - 'filter': 'InstanceTypeFilter' + 'filter': None, 'blob': zone_blob, 'num_instances': num_instances, } -- cgit From cca07a461d6c826a9dcc902b7b88afe602377756 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 18 Aug 2011 16:27:49 -0400 Subject: updated PUT to severs/id to handle accessIPv4 and accessIPv6 --- nova/api/openstack/servers.py | 10 +++++- nova/tests/api/openstack/test_servers.py | 53 +++++++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index f06ee6b62..df55d981a 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -163,7 +163,7 @@ class Controller(object): @scheduler_api.redirect_handler def update(self, req, id, body): - """Update server name then pass on to version-specific controller""" + """Update server then pass on to version-specific controller""" if len(req.body) == 0: raise exc.HTTPUnprocessableEntity() @@ -178,6 +178,14 @@ class Controller(object): self.helper._validate_server_name(name) update_dict['display_name'] = name.strip() + if 'accessIPv4' in body['server']: + access_ipv4 = body['server']['accessIPv4'] + update_dict['access_ip_v4'] = access_ipv4.strip() + + if 'accessIPv6' in body['server']: + access_ipv6 = body['server']['accessIPv6'] + update_dict['access_ip_v6'] = access_ipv6.strip() + try: self.compute_api.update(ctxt, id, **update_dict) except exception.NotFound: diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index b3e9bfe04..a813d4f96 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -145,7 +145,8 @@ def instance_addresses(context, instance_id): def stub_instance(id, user_id='fake', project_id='fake', private_address=None, public_addresses=None, host=None, power_state=0, reservation_id="", uuid=FAKE_UUID, image_ref="10", - flavor_id="1", interfaces=None, name=None): + flavor_id="1", interfaces=None, name=None, + access_ipv4=None, access_ipv6=None): metadata = [] metadata.append(InstanceMetadata(key='seq', value=id)) @@ -197,6 +198,8 @@ def stub_instance(id, user_id='fake', project_id='fake', private_address=None, "display_description": "", "locked": False, "metadata": metadata, + "access_ip_v4": access_ipv4, + "access_ip_v6": access_ipv6, "uuid": uuid, "virtual_interfaces": interfaces} @@ -1944,6 +1947,28 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) + def test_update_server_all_attributes_v1_1(self): + self.stubs.Set(nova.db.api, 'instance_get', + return_server_with_attributes(name='server_test', + access_ipv4='0.0.0.0', + access_ipv6='beef::0123')) + req = webob.Request.blank('/v1.1/servers/1') + req.method = 'PUT' + req.content_type = 'application/json' + body = {'server': { + 'name': 'server_test', + 'accessIPv4': '0.0.0.0', + 'accessIPv6': 'beef::0123', + }} + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + res_dict = json.loads(res.body) + self.assertEqual(res_dict['server']['id'], 1) + self.assertEqual(res_dict['server']['name'], 'server_test') + self.assertEqual(res_dict['server']['accessIPv4'], '0.0.0.0') + self.assertEqual(res_dict['server']['accessIPv6'], 'beef::0123') + def test_update_server_name_v1_1(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_with_attributes(name='server_test')) @@ -1957,6 +1982,32 @@ class ServersTest(test.TestCase): self.assertEqual(res_dict['server']['id'], 1) self.assertEqual(res_dict['server']['name'], 'server_test') + def test_update_server_access_ipv4_v1_1(self): + self.stubs.Set(nova.db.api, 'instance_get', + return_server_with_attributes(access_ipv4='0.0.0.0')) + req = webob.Request.blank('/v1.1/servers/1') + req.method = 'PUT' + req.content_type = 'application/json' + req.body = json.dumps({'server': {'accessIPv4': '0.0.0.0'}}) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + res_dict = json.loads(res.body) + self.assertEqual(res_dict['server']['id'], 1) + self.assertEqual(res_dict['server']['accessIPv4'], '0.0.0.0') + + def test_update_server_access_ipv6_v1_1(self): + self.stubs.Set(nova.db.api, 'instance_get', + return_server_with_attributes(access_ipv6='beef::0123')) + req = webob.Request.blank('/v1.1/servers/1') + req.method = 'PUT' + req.content_type = 'application/json' + req.body = json.dumps({'server': {'accessIPv6': 'beef::0123'}}) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + res_dict = json.loads(res.body) + self.assertEqual(res_dict['server']['id'], 1) + self.assertEqual(res_dict['server']['accessIPv6'], 'beef::0123') + def test_update_server_adminPass_ignored_v1_1(self): inst_dict = dict(name='server_test', adminPass='bacon') self.body = json.dumps(dict(server=inst_dict)) -- cgit From ce5c95424148649cbd4faca1d5c85c0d6209e3d4 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 18 Aug 2011 21:38:29 +0000 Subject: Removed extra parameter from the call to _provision_resource_locally() --- nova/scheduler/abstract_scheduler.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/scheduler/abstract_scheduler.py b/nova/scheduler/abstract_scheduler.py index 3930148e2..e8c343a4b 100644 --- a/nova/scheduler/abstract_scheduler.py +++ b/nova/scheduler/abstract_scheduler.py @@ -62,7 +62,7 @@ class AbstractScheduler(driver.Scheduler): host = build_plan_item['hostname'] base_options = request_spec['instance_properties'] image = request_spec['image'] - instance_type = request_spec['instance_type'] + instance_type = request_spec.get('instance_type') # TODO(sandy): I guess someone needs to add block_device_mapping # support at some point? Also, OS API has no concept of security @@ -159,8 +159,8 @@ class AbstractScheduler(driver.Scheduler): self._ask_child_zone_to_create_instance(context, host_info, request_spec, kwargs) else: - self._provision_resource_locally(context, instance_type, host_info, - request_spec, kwargs) + self._provision_resource_locally(context, host_info, request_spec, + kwargs) def _provision_resource(self, context, build_plan_item, instance_id, request_spec, kwargs): -- cgit From 509ce9d3016731c183bb565e8726a27010eaf02a Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 18 Aug 2011 15:41:20 -0700 Subject: declare the use_forwarded_for flag --- nova/api/ec2/__init__.py | 1 + nova/api/ec2/metadatarequesthandler.py | 1 + 2 files changed, 2 insertions(+) diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 2ae370f88..52f381dbb 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -47,6 +47,7 @@ flags.DEFINE_integer('lockout_window', 15, flags.DEFINE_string('keystone_ec2_url', 'http://localhost:5000/v2.0/ec2tokens', 'URL to get token from ec2 request.') +flags.DECLARE('use_forwarded_for', 'nova.api.auth') class RequestLogging(wsgi.Middleware): diff --git a/nova/api/ec2/metadatarequesthandler.py b/nova/api/ec2/metadatarequesthandler.py index 1dc275c90..0198bf490 100644 --- a/nova/api/ec2/metadatarequesthandler.py +++ b/nova/api/ec2/metadatarequesthandler.py @@ -30,6 +30,7 @@ from nova.api.ec2 import cloud LOG = logging.getLogger('nova.api.ec2.metadata') FLAGS = flags.FLAGS +flags.DECLARE('use_forwarded_for', 'nova.api.auth') class MetadataRequestHandler(wsgi.Application): -- cgit From 794d73a916a6bf97cbad4d0cfd5cf359e0418af0 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Fri, 19 Aug 2011 09:53:31 +0200 Subject: Move documentation from nova.virt.fake to nova.virt.driver. --- nova/virt/driver.py | 247 +++++++++++++++++++++++++++++++++++++++++++--- nova/virt/fake.py | 278 +--------------------------------------------------- 2 files changed, 238 insertions(+), 287 deletions(-) diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 20af2666d..72fe118b4 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -62,11 +62,41 @@ def block_device_info_get_mapping(block_device_info): class ComputeDriver(object): """Base class for compute drivers. - Lots of documentation is currently on fake.py. + The interface to this class talks in terms of 'instances' (Amazon EC2 and + internal Nova terminology), by which we mean 'running virtual machine' + (XenAPI terminology) or domain (Xen or libvirt terminology). + + An instance has an ID, which is the identifier chosen by Nova to represent + the instance further up the stack. This is unfortunately also called a + 'name' elsewhere. As far as this layer is concerned, 'instance ID' and + 'instance name' are synonyms. + + Note that the instance ID or name is not human-readable or + customer-controlled -- it's an internal ID chosen by Nova. At the + nova.virt layer, instances do not have human-readable names at all -- such + things are only known higher up the stack. + + Most virtualization platforms will also have their own identity schemes, + to uniquely identify a VM or domain. These IDs must stay internal to the + platform-specific layer, and never escape the connection interface. The + platform-specific layer is responsible for keeping track of which instance + ID maps to which platform-specific ID, and vice versa. + + In contrast, the list_disks and list_interfaces calls may return + platform-specific IDs. These identify a specific virtual disk or specific + virtual network interface, and these IDs are opaque to the rest of Nova. + + Some methods here take an instance of nova.compute.service.Instance. This + is the datastructure used by nova.compute to store details regarding an + instance, and pass them into this layer. This layer is responsible for + translating that generic datastructure into terms that are specific to the + virtualization platform. + """ def init_host(self, host): - """Adopt existing VM's running here""" + """Initialize anything that is necessary for the driver to function, + including catching up with currently running VM's on the given host.""" # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() @@ -84,6 +114,10 @@ class ComputeDriver(object): raise NotImplementedError() def list_instances(self): + """ + Return the names of all the instances known to the virtualization + layer, as a list. + """ # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() @@ -94,7 +128,20 @@ class ComputeDriver(object): def spawn(self, context, instance, network_info=None, block_device_info=None): - """Launch a VM for the specified instance""" + """ + Create a new instance/VM/domain on the virtualization platform. + + The given parameter is an instance of nova.compute.service.Instance. + This function should use the data there to guide the creation of + the new instance. + + Once this successfully completes, the instance should be + running (power_state.RUNNING). + + If this fails, any partial instance should be completely + cleaned up, and the virtualization platform should be in the state + that it was before this call began. + """ raise NotImplementedError() def destroy(self, instance, network_info, cleanup=True): @@ -115,7 +162,11 @@ class ComputeDriver(object): raise NotImplementedError() def reboot(self, instance, network_info): - """Reboot specified VM""" + """Reboot the specified instance. + + The given parameter is an instance of nova.compute.service.Instance, + and so the instance is being specified as instance.name. + """ # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() @@ -140,26 +191,40 @@ class ComputeDriver(object): raise NotImplementedError() def get_host_ip_addr(self): + """ + Retrieves the IP address of the dom0 + """ # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def attach_volume(self, context, instance_id, volume_id, mountpoint): + """Attach the disk at device_path to the instance at mountpoint""" raise NotImplementedError() def detach_volume(self, context, instance_id, volume_id): + """Detach the disk attached to the instance at mountpoint""" raise NotImplementedError() - def compare_cpu(self, context, cpu_info): + def compare_cpu(self, cpu_info): raise NotImplementedError() def migrate_disk_and_power_off(self, instance, dest): - """Transfers the VHD of a running instance to another host, then shuts - off the instance copies over the COW disk""" + """ + Transfers the disk of a running instance in multiple phases, turning + off the instance before the end. + """ # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def snapshot(self, context, instance, image_id): - """Create snapshot from a running VM instance.""" + """ + Snapshots the specified instance. + + The given parameter is an instance of nova.compute.service.Instance, + and so the instance is being specified as instance.name. + + The second parameter is the name of the snapshot. + """ raise NotImplementedError() def finish_migration(self, context, instance, disk_info, network_info, @@ -173,7 +238,7 @@ class ComputeDriver(object): raise NotImplementedError() def pause(self, instance, callback): - """Pause VM instance""" + """Pause the specified instance.""" # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() @@ -235,15 +300,69 @@ class ComputeDriver(object): raise NotImplementedError() def refresh_security_group_rules(self, security_group_id): + """This method is called after a change to security groups. + + All security groups and their associated rules live in the datastore, + and calling this method should apply the updated rules to instances + running the specified security group. + + An error should be raised if the operation cannot complete. + + """ # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def refresh_security_group_members(self, security_group_id): + """This method is called when a security group is added to an instance. + + This message is sent to the virtualization drivers on hosts that are + running an instance that belongs to a security group that has a rule + that references the security group identified by `security_group_id`. + It is the responsiblity of this method to make sure any rules + that authorize traffic flow with members of the security group are + updated and any new members can communicate, and any removed members + cannot. + + Scenario: + * we are running on host 'H0' and we have an instance 'i-0'. + * instance 'i-0' is a member of security group 'speaks-b' + * group 'speaks-b' has an ingress rule that authorizes group 'b' + * another host 'H1' runs an instance 'i-1' + * instance 'i-1' is a member of security group 'b' + + When 'i-1' launches or terminates we will recieve the message + to update members of group 'b', at which time we will make + any changes needed to the rules for instance 'i-0' to allow + or deny traffic coming from 'i-1', depending on if it is being + added or removed from the group. + + In this scenario, 'i-1' could just as easily have been running on our + host 'H0' and this method would still have been called. The point was + that this method isn't called on the host where instances of that + group are running (as is the case with + :method:`refresh_security_group_rules`) but is called where references + are made to authorizing those instances. + + An error should be raised if the operation cannot complete. + + """ # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def refresh_provider_fw_rules(self, security_group_id): - """See: nova/virt/fake.py for docs.""" + """This triggers a firewall update based on database changes. + + When this is called, rules have either been added or removed from the + datastore. You can retrieve rules with + :method:`nova.db.api.provider_fw_rule_get_all`. + + Provider rules take precedence over security group rules. If an IP + would be allowed by a security group ingress rule, but blocked by + a provider rule, then packets from the IP are dropped. This includes + intra-project traffic in the case of the allow_project_net_traffic + flag for the libvirt-derived classes. + + """ # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() @@ -284,18 +403,38 @@ class ComputeDriver(object): raise NotImplementedError() def set_admin_password(self, context, instance_id, new_pass=None): - """Set the root/admin password for an instance on this server.""" + """ + Set the root password on the specified instance. + + The first parameter is an instance of nova.compute.service.Instance, + and so the instance is being specified as instance.name. The second + parameter is the value of the new password. + """ raise NotImplementedError() def inject_file(self, instance, b64_path, b64_contents): - """Create a file on the VM instance. The file path and contents - should be base64-encoded. + """ + Writes a file on the specified instance. + + The first parameter is an instance of nova.compute.service.Instance, + and so the instance is being specified as instance.name. The second + parameter is the base64-encoded path to which the file is to be + written on the instance; the third is the contents of the file, also + base64-encoded. """ # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() def agent_update(self, instance, url, md5hash): - """Update agent on the VM instance.""" + """ + Update agent on the specified instance. + + The first parameter is an instance of nova.compute.service.Instance, + and so the instance is being specified as instance.name. The second + parameter is the URL of the agent to be fetched and updated on the + instance; the third is the md5 hash of the file for verification + purposes. + """ # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() @@ -322,3 +461,83 @@ class ComputeDriver(object): """Plugs in VIFs to networks.""" # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() + + def update_host_status(self): + """Refresh host stats""" + raise NotImplementedError() + + def get_host_stats(self, refresh=False): + """Return currently known host stats""" + raise NotImplementedError() + + def list_disks(self, instance_name): + """ + Return the IDs of all the virtual disks attached to the specified + instance, as a list. These IDs are opaque to the caller (they are + only useful for giving back to this layer as a parameter to + disk_stats). These IDs only need to be unique for a given instance. + + Note that this function takes an instance ID. + """ + raise NotImplementedError() + + def list_interfaces(self, instance_name): + """ + Return the IDs of all the virtual network interfaces attached to the + specified instance, as a list. These IDs are opaque to the caller + (they are only useful for giving back to this layer as a parameter to + interface_stats). These IDs only need to be unique for a given + instance. + + Note that this function takes an instance ID. + """ + raise NotImplementedError() + + def resize(self, instance, flavor): + """ + Resizes/Migrates the specified instance. + + The flavor parameter determines whether or not the instance RAM and + disk space are modified, and if so, to what size. + """ + raise NotImplementedError() + + def block_stats(self, instance_name, disk_id): + """ + Return performance counters associated with the given disk_id on the + given instance_name. These are returned as [rd_req, rd_bytes, wr_req, + wr_bytes, errs], where rd indicates read, wr indicates write, req is + the total number of I/O requests made, bytes is the total number of + bytes transferred, and errs is the number of requests held up due to a + full pipeline. + + All counters are long integers. + + This method is optional. On some platforms (e.g. XenAPI) performance + statistics can be retrieved directly in aggregate form, without Nova + having to do the aggregation. On those platforms, this method is + unused. + + Note that this function takes an instance ID. + """ + raise NotImplementedError() + + def interface_stats(self, instance_name, iface_id): + """ + Return performance counters associated with the given iface_id on the + given instance_id. These are returned as [rx_bytes, rx_packets, + rx_errs, rx_drop, tx_bytes, tx_packets, tx_errs, tx_drop], where rx + indicates receive, tx indicates transmit, bytes and packets indicate + the total number of bytes or packets transferred, and errs and dropped + is the total number of packets failed / dropped. + + All counters are long integers. + + This method is optional. On some platforms (e.g. XenAPI) performance + statistics can be retrieved directly in aggregate form, without Nova + having to do the aggregation. On those platforms, this method is + unused. + + Note that this function takes an instance ID. + """ + raise NotImplementedError() diff --git a/nova/virt/fake.py b/nova/virt/fake.py index dc0628772..67cc1ea29 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -48,37 +48,7 @@ class FakeInstance(object): class FakeConnection(driver.ComputeDriver): - """ - The interface to this class talks in terms of 'instances' (Amazon EC2 and - internal Nova terminology), by which we mean 'running virtual machine' - (XenAPI terminology) or domain (Xen or libvirt terminology). - - An instance has an ID, which is the identifier chosen by Nova to represent - the instance further up the stack. This is unfortunately also called a - 'name' elsewhere. As far as this layer is concerned, 'instance ID' and - 'instance name' are synonyms. - - Note that the instance ID or name is not human-readable or - customer-controlled -- it's an internal ID chosen by Nova. At the - nova.virt layer, instances do not have human-readable names at all -- such - things are only known higher up the stack. - - Most virtualization platforms will also have their own identity schemes, - to uniquely identify a VM or domain. These IDs must stay internal to the - platform-specific layer, and never escape the connection interface. The - platform-specific layer is responsible for keeping track of which instance - ID maps to which platform-specific ID, and vice versa. - - In contrast, the list_disks and list_interfaces calls may return - platform-specific IDs. These identify a specific virtual disk or specific - virtual network interface, and these IDs are opaque to the rest of Nova. - - Some methods here take an instance of nova.compute.service.Instance. This - is the datastructure used by nova.compute to store details regarding an - instance, and pass them into this layer. This layer is responsible for - translating that generic datastructure into terms that are specific to the - virtualization platform. - """ + """Fake hypervisor driver""" def __init__(self): self.instances = {} @@ -105,17 +75,9 @@ class FakeConnection(driver.ComputeDriver): return cls._instance def init_host(self, host): - """ - Initialize anything that is necessary for the driver to function, - including catching up with currently running VM's on the given host. - """ return def list_instances(self): - """ - Return the names of all the instances known to the virtualization - layer, as a list. - """ return self.instances.keys() def _map_to_instance_info(self, instance): @@ -131,167 +93,54 @@ class FakeConnection(driver.ComputeDriver): def spawn(self, context, instance, network_info=None, block_device_info=None): - """ - Create a new instance/VM/domain on the virtualization platform. - - The given parameter is an instance of nova.compute.service.Instance. - This function should use the data there to guide the creation of - the new instance. - - The work will be done asynchronously. This function returns a - task that allows the caller to detect when it is complete. - - Once this successfully completes, the instance should be - running (power_state.RUNNING). - - If this fails, any partial instance should be completely - cleaned up, and the virtualization platform should be in the state - that it was before this call began. - """ - name = instance.name state = power_state.RUNNING fake_instance = FakeInstance(name, state) self.instances[name] = fake_instance def snapshot(self, context, instance, name): - """ - Snapshots the specified instance. - - The given parameter is an instance of nova.compute.service.Instance, - and so the instance is being specified as instance.name. - - The second parameter is the name of the snapshot. - - The work will be done asynchronously. This function returns a - task that allows the caller to detect when it is complete. - """ pass def reboot(self, instance, network_info): - """ - Reboot the specified instance. - - The given parameter is an instance of nova.compute.service.Instance, - and so the instance is being specified as instance.name. - - The work will be done asynchronously. This function returns a - task that allows the caller to detect when it is complete. - """ pass def get_host_ip_addr(self): - """ - Retrieves the IP address of the dom0 - """ - pass + return '192.168.0.1' def resize(self, instance, flavor): - """ - Resizes/Migrates the specified instance. - - The flavor parameter determines whether or not the instance RAM and - disk space are modified, and if so, to what size. - - The work will be done asynchronously. This function returns a task - that allows the caller to detect when it is complete. - """ pass def set_admin_password(self, instance, new_pass): - """ - Set the root password on the specified instance. - - The first parameter is an instance of nova.compute.service.Instance, - and so the instance is being specified as instance.name. The second - parameter is the value of the new password. - - The work will be done asynchronously. This function returns a - task that allows the caller to detect when it is complete. - """ pass def inject_file(self, instance, b64_path, b64_contents): - """ - Writes a file on the specified instance. - - The first parameter is an instance of nova.compute.service.Instance, - and so the instance is being specified as instance.name. The second - parameter is the base64-encoded path to which the file is to be - written on the instance; the third is the contents of the file, also - base64-encoded. - - The work will be done asynchronously. This function returns a - task that allows the caller to detect when it is complete. - """ pass def agent_update(self, instance, url, md5hash): - """ - Update agent on the specified instance. - - The first parameter is an instance of nova.compute.service.Instance, - and so the instance is being specified as instance.name. The second - parameter is the URL of the agent to be fetched and updated on the - instance; the third is the md5 hash of the file for verification - purposes. - - The work will be done asynchronously. This function returns a - task that allows the caller to detect when it is complete. - """ pass def rescue(self, context, instance, callback, network_info): - """ - Rescue the specified instance. - """ pass def unrescue(self, instance, callback, network_info): - """ - Unrescue the specified instance. - """ pass def poll_rescued_instances(self, timeout): - """Poll for rescued instances""" pass def migrate_disk_and_power_off(self, instance, dest): - """ - Transfers the disk of a running instance in multiple phases, turning - off the instance before the end. - """ - pass - - def attach_disk(self, instance, disk_info): - """ - Attaches the disk to an instance given the metadata disk_info - """ pass def pause(self, instance, callback): - """ - Pause the specified instance. - """ pass def unpause(self, instance, callback): - """ - Unpause the specified instance. - """ pass def suspend(self, instance, callback): - """ - suspend the specified instance - """ pass def resume(self, instance, callback): - """ - resume the specified instance - """ pass def destroy(self, instance, network_info, cleanup=True): @@ -303,25 +152,14 @@ class FakeConnection(driver.ComputeDriver): (key, self.instances)) def attach_volume(self, instance_name, device_path, mountpoint): - """Attach the disk at device_path to the instance at mountpoint""" + self._mounts[mountpoint] = device_path return True def detach_volume(self, instance_name, mountpoint): - """Detach the disk attached to the instance at mountpoint""" + del self._mounts[mountpoint] return True def get_info(self, instance_name): - """ - Get a block of information about the given instance. This is returned - as a dictionary containing 'state': The power_state of the instance, - 'max_mem': The maximum memory for the instance, in KiB, 'mem': The - current memory the instance has, in KiB, 'num_cpu': The current number - of virtual CPUs the instance has, 'cpu_time': The total CPU time used - by the instance, in nanoseconds. - - This method should raise exception.NotFound if the hypervisor has no - knowledge of the instance - """ if instance_name not in self.instances: raise exception.InstanceNotFound(instance_id=instance_name) i = self.instances[instance_name] @@ -332,69 +170,18 @@ class FakeConnection(driver.ComputeDriver): 'cpu_time': 0} def get_diagnostics(self, instance_name): - pass + return {} def list_disks(self, instance_name): - """ - Return the IDs of all the virtual disks attached to the specified - instance, as a list. These IDs are opaque to the caller (they are - only useful for giving back to this layer as a parameter to - disk_stats). These IDs only need to be unique for a given instance. - - Note that this function takes an instance ID. - """ return ['A_DISK'] def list_interfaces(self, instance_name): - """ - Return the IDs of all the virtual network interfaces attached to the - specified instance, as a list. These IDs are opaque to the caller - (they are only useful for giving back to this layer as a parameter to - interface_stats). These IDs only need to be unique for a given - instance. - - Note that this function takes an instance ID. - """ return ['A_VIF'] def block_stats(self, instance_name, disk_id): - """ - Return performance counters associated with the given disk_id on the - given instance_name. These are returned as [rd_req, rd_bytes, wr_req, - wr_bytes, errs], where rd indicates read, wr indicates write, req is - the total number of I/O requests made, bytes is the total number of - bytes transferred, and errs is the number of requests held up due to a - full pipeline. - - All counters are long integers. - - This method is optional. On some platforms (e.g. XenAPI) performance - statistics can be retrieved directly in aggregate form, without Nova - having to do the aggregation. On those platforms, this method is - unused. - - Note that this function takes an instance ID. - """ return [0L, 0L, 0L, 0L, None] def interface_stats(self, instance_name, iface_id): - """ - Return performance counters associated with the given iface_id on the - given instance_id. These are returned as [rx_bytes, rx_packets, - rx_errs, rx_drop, tx_bytes, tx_packets, tx_errs, tx_drop], where rx - indicates receive, tx indicates transmit, bytes and packets indicate - the total number of bytes or packets transferred, and errs and dropped - is the total number of packets failed / dropped. - - All counters are long integers. - - This method is optional. On some platforms (e.g. XenAPI) performance - statistics can be retrieved directly in aggregate form, without Nova - having to do the aggregation. On those platforms, this method is - unused. - - Note that this function takes an instance ID. - """ return [0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L] def get_console_output(self, instance): @@ -416,67 +203,12 @@ class FakeConnection(driver.ComputeDriver): 'password': 'fakepassword'} def refresh_security_group_rules(self, security_group_id): - """This method is called after a change to security groups. - - All security groups and their associated rules live in the datastore, - and calling this method should apply the updated rules to instances - running the specified security group. - - An error should be raised if the operation cannot complete. - - """ return True def refresh_security_group_members(self, security_group_id): - """This method is called when a security group is added to an instance. - - This message is sent to the virtualization drivers on hosts that are - running an instance that belongs to a security group that has a rule - that references the security group identified by `security_group_id`. - It is the responsiblity of this method to make sure any rules - that authorize traffic flow with members of the security group are - updated and any new members can communicate, and any removed members - cannot. - - Scenario: - * we are running on host 'H0' and we have an instance 'i-0'. - * instance 'i-0' is a member of security group 'speaks-b' - * group 'speaks-b' has an ingress rule that authorizes group 'b' - * another host 'H1' runs an instance 'i-1' - * instance 'i-1' is a member of security group 'b' - - When 'i-1' launches or terminates we will recieve the message - to update members of group 'b', at which time we will make - any changes needed to the rules for instance 'i-0' to allow - or deny traffic coming from 'i-1', depending on if it is being - added or removed from the group. - - In this scenario, 'i-1' could just as easily have been running on our - host 'H0' and this method would still have been called. The point was - that this method isn't called on the host where instances of that - group are running (as is the case with - :method:`refresh_security_group_rules`) but is called where references - are made to authorizing those instances. - - An error should be raised if the operation cannot complete. - - """ return True def refresh_provider_fw_rules(self): - """This triggers a firewall update based on database changes. - - When this is called, rules have either been added or removed from the - datastore. You can retrieve rules with - :method:`nova.db.api.provider_fw_rule_get_all`. - - Provider rules take precedence over security group rules. If an IP - would be allowed by a security group ingress rule, but blocked by - a provider rule, then packets from the IP are dropped. This includes - intra-project traffic in the case of the allow_project_net_traffic - flag for the libvirt-derived classes. - - """ pass def update_available_resource(self, ctxt, host): -- cgit From a2c7e0f7fc6e25828ab32f133965b300e37d9264 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Fri, 19 Aug 2011 13:44:32 +0200 Subject: Start improving documentation. --- nova/virt/driver.py | 57 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 72fe118b4..6d3af7e0a 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -104,6 +104,7 @@ class ComputeDriver(object): """Get the current status of an instance, by name (not ID!) Returns a dict containing: + :state: the running state, one of the power_state codes :max_mem: (int) the maximum memory in KBytes allowed :mem: (int) the memory in KBytes used by the domain @@ -131,16 +132,20 @@ class ComputeDriver(object): """ Create a new instance/VM/domain on the virtualization platform. - The given parameter is an instance of nova.compute.service.Instance. - This function should use the data there to guide the creation of - the new instance. - Once this successfully completes, the instance should be running (power_state.RUNNING). If this fails, any partial instance should be completely cleaned up, and the virtualization platform should be in the state that it was before this call began. + + :param context: security context + :param instance: Instance of {nova.compute.service.Instance}. + This function should use the data there to guide + the creation of the new instance. + :param network_info: + :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info` + :param block_device_info: """ raise NotImplementedError() @@ -148,15 +153,17 @@ class ComputeDriver(object): """Destroy (shutdown and delete) the specified instance. The given parameter is an instance of nova.compute.service.Instance, - and so the instance is being specified as instance.name. - - The work will be done asynchronously. This function returns a - task that allows the caller to detect when it is complete. If the instance is not found (for example if networking failed), this function should still succeed. It's probably a good idea to log a warning in that case. + :param instance: Instance of {nova.compute.service.Instance} and so + the instance is being specified as instance.name. + :param network_info: + :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info` + :param cleanup: + """ # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() @@ -164,8 +171,10 @@ class ComputeDriver(object): def reboot(self, instance, network_info): """Reboot the specified instance. - The given parameter is an instance of nova.compute.service.Instance, - and so the instance is being specified as instance.name. + :param instance: Instance of {nova.compute.service.Instance} and so + the instance is being specified as instance.name. + :param network_info: + :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info` """ # TODO(Vek): Need to pass context in for access to auth_token raise NotImplementedError() @@ -206,6 +215,17 @@ class ComputeDriver(object): raise NotImplementedError() def compare_cpu(self, cpu_info): + """Compares given cpu info against host + + Before attempting to migrate a VM to this host, + compare_cpu is called to ensure that the VM will + actually run here. + + :param cpu_info: (str) JSON structure describing the source CPU. + :returns: None if migration is acceptable + :raises: :py:class:`~nova.exception.InvalidCPUInfo` if migration + is not acceptable. + """ raise NotImplementedError() def migrate_disk_and_power_off(self, instance, dest): @@ -229,7 +249,11 @@ class ComputeDriver(object): def finish_migration(self, context, instance, disk_info, network_info, resize_instance): - """Completes a resize, turning on the migrated instance""" + """Completes a resize, turning on the migrated instance + + :param network_info: + :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info` + """ raise NotImplementedError() def revert_migration(self, instance): @@ -283,15 +307,15 @@ class ComputeDriver(object): post_method, recover_method): """Spawning live_migration operation for distributing high-load. - :params ctxt: security context - :params instance_ref: + :param ctxt: security context + :param instance_ref: nova.db.sqlalchemy.models.Instance object instance object that is migrated. - :params dest: destination host - :params post_method: + :param dest: destination host + :param post_method: post operation method. expected nova.compute.manager.post_live_migration. - :params recover_method: + :param recover_method: recovery method when any exception occurs. expected nova.compute.manager.recover_live_migration. @@ -541,3 +565,4 @@ class ComputeDriver(object): Note that this function takes an instance ID. """ raise NotImplementedError() + -- cgit From d1f5d1fae10bb8a1d0e445ec2b9153542eb025f4 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Fri, 19 Aug 2011 13:54:18 +0200 Subject: pep8 --- nova/virt/driver.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 6d3af7e0a..93290aba7 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -565,4 +565,3 @@ class ComputeDriver(object): Note that this function takes an instance ID. """ raise NotImplementedError() - -- cgit From be0c70562ce978e3ffa85465fc08dd5cb3ca07c3 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Fri, 19 Aug 2011 10:47:16 -0400 Subject: updated migration number --- .../versions/037_add_instances_accessip.py | 48 ---------------------- .../versions/038_add_instances_accessip.py | 48 ++++++++++++++++++++++ 2 files changed, 48 insertions(+), 48 deletions(-) delete mode 100644 nova/db/sqlalchemy/migrate_repo/versions/037_add_instances_accessip.py create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/038_add_instances_accessip.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/037_add_instances_accessip.py b/nova/db/sqlalchemy/migrate_repo/versions/037_add_instances_accessip.py deleted file mode 100644 index 39f0dd6ce..000000000 --- a/nova/db/sqlalchemy/migrate_repo/versions/037_add_instances_accessip.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright 2011 OpenStack LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from sqlalchemy import Column, Integer, MetaData, Table, String - -meta = MetaData() - -accessIPv4 = Column( - 'access_ip_v4', - String(length=255, convert_unicode=False, assert_unicode=None, - unicode_error=None, _warn_on_bytestring=False), - nullable=True) - -accessIPv6 = Column( - 'access_ip_v6', - String(length=255, convert_unicode=False, assert_unicode=None, - unicode_error=None, _warn_on_bytestring=False), - nullable=True) - -instances = Table('instances', meta, - Column('id', Integer(), primary_key=True, nullable=False), - ) - - -def upgrade(migrate_engine): - # Upgrade operations go here. Don't create your own engine; - # bind migrate_engine to your metadata - meta.bind = migrate_engine - instances.create_column(accessIPv4) - instances.create_column(accessIPv6) - - -def downgrade(migrate_engine): - # Operations to reverse the above upgrade go here. - meta.bind = migrate_engine - instances.drop_column('access_ip_v4') - instances.drop_column('access_ip_v6') diff --git a/nova/db/sqlalchemy/migrate_repo/versions/038_add_instances_accessip.py b/nova/db/sqlalchemy/migrate_repo/versions/038_add_instances_accessip.py new file mode 100644 index 000000000..39f0dd6ce --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/038_add_instances_accessip.py @@ -0,0 +1,48 @@ +# Copyright 2011 OpenStack LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from sqlalchemy import Column, Integer, MetaData, Table, String + +meta = MetaData() + +accessIPv4 = Column( + 'access_ip_v4', + String(length=255, convert_unicode=False, assert_unicode=None, + unicode_error=None, _warn_on_bytestring=False), + nullable=True) + +accessIPv6 = Column( + 'access_ip_v6', + String(length=255, convert_unicode=False, assert_unicode=None, + unicode_error=None, _warn_on_bytestring=False), + nullable=True) + +instances = Table('instances', meta, + Column('id', Integer(), primary_key=True, nullable=False), + ) + + +def upgrade(migrate_engine): + # Upgrade operations go here. Don't create your own engine; + # bind migrate_engine to your metadata + meta.bind = migrate_engine + instances.create_column(accessIPv4) + instances.create_column(accessIPv6) + + +def downgrade(migrate_engine): + # Operations to reverse the above upgrade go here. + meta.bind = migrate_engine + instances.drop_column('access_ip_v4') + instances.drop_column('access_ip_v6') -- cgit From ba074440df8908727f6bffdba5100f146000f05a Mon Sep 17 00:00:00 2001 From: William Wolf Date: Fri, 19 Aug 2011 13:00:22 -0400 Subject: fix test_rescue tests for tenant_id changes --- nova/tests/api/openstack/contrib/test_rescue.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/tests/api/openstack/contrib/test_rescue.py b/nova/tests/api/openstack/contrib/test_rescue.py index fc8e4be4e..f8126d461 100644 --- a/nova/tests/api/openstack/contrib/test_rescue.py +++ b/nova/tests/api/openstack/contrib/test_rescue.py @@ -36,7 +36,7 @@ class RescueTest(test.TestCase): def test_rescue(self): body = dict(rescue=None) - req = webob.Request.blank('/v1.1/servers/test_inst/action') + req = webob.Request.blank('/v1.1/123/servers/test_inst/action') req.method = "POST" req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -46,7 +46,7 @@ class RescueTest(test.TestCase): def test_unrescue(self): body = dict(unrescue=None) - req = webob.Request.blank('/v1.1/servers/test_inst/action') + req = webob.Request.blank('/v1.1/123/servers/test_inst/action') req.method = "POST" req.body = json.dumps(body) req.headers["content-type"] = "application/json" -- cgit From ba9c7ded1da97094743a1c27d8f0665378ad726f Mon Sep 17 00:00:00 2001 From: William Wolf Date: Fri, 19 Aug 2011 13:01:21 -0400 Subject: fix test_virtual interfaces for tenant_id stuff --- nova/tests/api/openstack/contrib/test_virtual_interfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/api/openstack/contrib/test_virtual_interfaces.py b/nova/tests/api/openstack/contrib/test_virtual_interfaces.py index d541a9e95..1db253b35 100644 --- a/nova/tests/api/openstack/contrib/test_virtual_interfaces.py +++ b/nova/tests/api/openstack/contrib/test_virtual_interfaces.py @@ -43,7 +43,7 @@ class ServerVirtualInterfaceTest(test.TestCase): super(ServerVirtualInterfaceTest, self).tearDown() def test_get_virtual_interfaces_list(self): - req = webob.Request.blank('/v1.1/servers/1/os-virtual-interfaces') + req = webob.Request.blank('/v1.1/123/servers/1/os-virtual-interfaces') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) res_dict = json.loads(res.body) -- cgit From c11a156b1e50fde6cf3047057746564d491634e2 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Fri, 19 Aug 2011 10:01:25 -0700 Subject: Fixes primitive with builtins, modules, etc --- nova/tests/test_utils.py | 10 ++++++++++ nova/utils.py | 12 +++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/nova/tests/test_utils.py b/nova/tests/test_utils.py index ec5098a37..28e366a8e 100644 --- a/nova/tests/test_utils.py +++ b/nova/tests/test_utils.py @@ -384,3 +384,13 @@ class ToPrimitiveTestCase(test.TestCase): def test_typeerror(self): x = bytearray # Class, not instance self.assertEquals(utils.to_primitive(x), u"") + + def test_nasties(self): + def foo(): + pass + x = [datetime, foo, dir] + ret = utils.to_primitive(x) + self.assertEquals(len(ret), 3) + self.assertTrue(ret[0].startswith(u"') diff --git a/nova/utils.py b/nova/utils.py index 54126f644..b42f76457 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -547,11 +547,17 @@ def to_primitive(value, convert_instances=False, level=0): Therefore, convert_instances=True is lossy ... be aware. """ - if inspect.isclass(value): - return unicode(value) + nasty = [inspect.ismodule, inspect.isclass, inspect.ismethod, + inspect.isfunction, inspect.isgeneratorfunction, + inspect.isgenerator, inspect.istraceback, inspect.isframe, + inspect.iscode, inspect.isbuiltin, inspect.isroutine, + inspect.isabstract] + for test in nasty: + if test(value): + return unicode(value) if level > 3: - return [] + return '?' # The try block may not be necessary after the class check above, # but just in case ... -- cgit From 25ec08a208474e1cf5827c1eb8b231d631c489e9 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Fri, 19 Aug 2011 20:09:31 +0200 Subject: Revert irrelevant changes that accidentally crept into this patch :( --- nova/virt/fake.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 67cc1ea29..13b7aeab5 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -152,11 +152,9 @@ class FakeConnection(driver.ComputeDriver): (key, self.instances)) def attach_volume(self, instance_name, device_path, mountpoint): - self._mounts[mountpoint] = device_path return True def detach_volume(self, instance_name, mountpoint): - del self._mounts[mountpoint] return True def get_info(self, instance_name): -- cgit From fe8800ada8670cb29417fcdec085800b66cd881f Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Fri, 19 Aug 2011 15:21:04 -0400 Subject: Updated test_show in ServerXMLSerializationTest to use XML validation --- nova/tests/api/openstack/test_servers.py | 101 ++++++++++++++++++------------- 1 file changed, 58 insertions(+), 43 deletions(-) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 326962d72..cfe3f624e 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -19,6 +19,7 @@ import base64 import datetime import json import unittest +from lxml import etree from xml.dom import minidom import webob @@ -32,6 +33,7 @@ import nova.api.openstack from nova.api.openstack import create_instance_helper from nova.api.openstack import servers from nova.api.openstack import wsgi +from nova.api.openstack import xmlutil import nova.compute.api from nova.compute import instance_types from nova.compute import power_state @@ -46,6 +48,8 @@ from nova.tests.api.openstack import fakes FAKE_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' +NS = "{http://docs.openstack.org/compute/api/v1.1}" +ATOMNS = "{http://www.w3.org/2005/Atom}" def fake_gen_uuid(): @@ -3605,7 +3609,9 @@ class ServerXMLSerializationTest(test.TestCase): } output = serializer.serialize(fixture, 'show') - actual = minidom.parseString(output.replace(" ", "")) + print output + root = etree.XML(output) + xmlutil.validate_schema(root, 'server') expected_server_href = self.SERVER_HREF expected_server_bookmark = self.SERVER_BOOKMARK @@ -3613,49 +3619,58 @@ class ServerXMLSerializationTest(test.TestCase): expected_flavor_bookmark = self.FLAVOR_BOOKMARK expected_now = self.TIMESTAMP expected_uuid = FAKE_UUID - expected = minidom.parseString(""" - - - - - - - - - - - - Stack - - - 1 - - - - - - - - - - - - - - """.replace(" ", "") % (locals())) + server_dict = fixture['server'] + + for key in ['name', 'id', 'uuid', 'created', 'accessIPv4', + 'updated', 'progress', 'status', 'hostId', + 'accessIPv6']: + self.assertEqual(root.get(key), str(server_dict[key])) + + link_nodes = root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 2) + for i, link in enumerate(server_dict['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + metadata_root = root.find('{0}metadata'.format(NS)) + metadata_elems = metadata_root.findall('{0}meta'.format(NS)) + self.assertEqual(len(metadata_elems), 2) + for i, metadata_elem in enumerate(metadata_elems): + (meta_key, meta_value) = server_dict['metadata'].items()[i] + self.assertEqual(str(metadata_elem.get('key')), str(meta_key)) + self.assertEqual(str(metadata_elem.text).strip(), str(meta_value)) + + image_root = root.find('{0}image'.format(NS)) + self.assertEqual(image_root.get('id'), server_dict['image']['id']) + link_nodes = image_root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 1) + for i, link in enumerate(server_dict['image']['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + flavor_root = root.find('{0}flavor'.format(NS)) + self.assertEqual(flavor_root.get('id'), server_dict['flavor']['id']) + link_nodes = flavor_root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 1) + for i, link in enumerate(server_dict['flavor']['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + addresses_root = root.find('{0}addresses'.format(NS)) + addresses_dict = server_dict['addresses'] + network_elems = addresses_root.findall('{0}network'.format(NS)) + self.assertEqual(len(network_elems), 2) + for i, network_elem in enumerate(network_elems): + network = addresses_dict.items()[i] + self.assertEqual(str(network_elem.get('id')), str(network[0])) + ip_elems = network_elem.findall('{0}ip'.format(NS)) + for z, ip_elem in enumerate(ip_elems): + ip = network[1][z] + self.assertEqual(str(ip_elem.get('version')), + str(ip['version'])) + self.assertEqual(str(ip_elem.get('addr')), + str(ip['addr'])) - self.assertEqual(expected.toxml(), actual.toxml()) def test_create(self): serializer = servers.ServerXMLSerializer() -- cgit From c75e132786a65501477f77efa1bc9147b7763c31 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Fri, 19 Aug 2011 15:55:56 -0400 Subject: Finished changing ServerXMLSerializationTest to use XML validation and lxml --- nova/api/openstack/schemas/v1.1/server.rng | 50 ++++ nova/api/openstack/schemas/v1.1/servers.rng | 6 + nova/api/openstack/schemas/v1.1/servers_index.rng | 12 + nova/tests/api/openstack/test_servers.py | 349 ++++++++++++---------- 4 files changed, 251 insertions(+), 166 deletions(-) create mode 100644 nova/api/openstack/schemas/v1.1/server.rng create mode 100644 nova/api/openstack/schemas/v1.1/servers.rng create mode 100644 nova/api/openstack/schemas/v1.1/servers_index.rng diff --git a/nova/api/openstack/schemas/v1.1/server.rng b/nova/api/openstack/schemas/v1.1/server.rng new file mode 100644 index 000000000..dbd169a83 --- /dev/null +++ b/nova/api/openstack/schemas/v1.1/server.rng @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nova/api/openstack/schemas/v1.1/servers.rng b/nova/api/openstack/schemas/v1.1/servers.rng new file mode 100644 index 000000000..4e2bb8853 --- /dev/null +++ b/nova/api/openstack/schemas/v1.1/servers.rng @@ -0,0 +1,6 @@ + + + + + diff --git a/nova/api/openstack/schemas/v1.1/servers_index.rng b/nova/api/openstack/schemas/v1.1/servers_index.rng new file mode 100644 index 000000000..768f0912d --- /dev/null +++ b/nova/api/openstack/schemas/v1.1/servers_index.rng @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index cfe3f624e..961d2fb7c 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -3684,6 +3684,7 @@ class ServerXMLSerializationTest(test.TestCase): "progress": 0, "name": "test_server", "status": "BUILD", + "accessIPv4": "1.2.3.4", "accessIPv6": "fead::1234", "hostId": "e4d909c290d0fb1ca068ffaddf22cbd0", "adminPass": "test_password", @@ -3745,7 +3746,9 @@ class ServerXMLSerializationTest(test.TestCase): } output = serializer.serialize(fixture, 'create') - actual = minidom.parseString(output.replace(" ", "")) + print output + root = etree.XML(output) + xmlutil.validate_schema(root, 'server') expected_server_href = self.SERVER_HREF expected_server_bookmark = self.SERVER_BOOKMARK @@ -3753,49 +3756,57 @@ class ServerXMLSerializationTest(test.TestCase): expected_flavor_bookmark = self.FLAVOR_BOOKMARK expected_now = self.TIMESTAMP expected_uuid = FAKE_UUID - expected = minidom.parseString(""" - - - - - - - - - - - - Stack - - - 1 - - - - - - - - - - - - - - """.replace(" ", "") % (locals())) + server_dict = fixture['server'] - self.assertEqual(expected.toxml(), actual.toxml()) + for key in ['name', 'id', 'uuid', 'created', 'accessIPv4', + 'updated', 'progress', 'status', 'hostId', + 'accessIPv6', 'adminPass']: + self.assertEqual(root.get(key), str(server_dict[key])) + + link_nodes = root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 2) + for i, link in enumerate(server_dict['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + metadata_root = root.find('{0}metadata'.format(NS)) + metadata_elems = metadata_root.findall('{0}meta'.format(NS)) + self.assertEqual(len(metadata_elems), 2) + for i, metadata_elem in enumerate(metadata_elems): + (meta_key, meta_value) = server_dict['metadata'].items()[i] + self.assertEqual(str(metadata_elem.get('key')), str(meta_key)) + self.assertEqual(str(metadata_elem.text).strip(), str(meta_value)) + + image_root = root.find('{0}image'.format(NS)) + self.assertEqual(image_root.get('id'), server_dict['image']['id']) + link_nodes = image_root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 1) + for i, link in enumerate(server_dict['image']['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + flavor_root = root.find('{0}flavor'.format(NS)) + self.assertEqual(flavor_root.get('id'), server_dict['flavor']['id']) + link_nodes = flavor_root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 1) + for i, link in enumerate(server_dict['flavor']['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + addresses_root = root.find('{0}addresses'.format(NS)) + addresses_dict = server_dict['addresses'] + network_elems = addresses_root.findall('{0}network'.format(NS)) + self.assertEqual(len(network_elems), 2) + for i, network_elem in enumerate(network_elems): + network = addresses_dict.items()[i] + self.assertEqual(str(network_elem.get('id')), str(network[0])) + ip_elems = network_elem.findall('{0}ip'.format(NS)) + for z, ip_elem in enumerate(ip_elems): + ip = network[1][z] + self.assertEqual(str(ip_elem.get('version')), + str(ip['version'])) + self.assertEqual(str(ip_elem.get('addr')), + str(ip['addr'])) def test_index(self): serializer = servers.ServerXMLSerializer() @@ -3836,23 +3847,21 @@ class ServerXMLSerializationTest(test.TestCase): ]} output = serializer.serialize(fixture, 'index') - actual = minidom.parseString(output.replace(" ", "")) - - expected = minidom.parseString(""" - - - - - - - - - - - """.replace(" ", "") % (locals())) - - self.assertEqual(expected.toxml(), actual.toxml()) + print output + root = etree.XML(output) + xmlutil.validate_schema(root, 'servers_index') + server_elems = root.findall('{0}server'.format(NS)) + self.assertEqual(len(server_elems), 2) + for i, server_elem in enumerate(server_elems): + server_dict = fixture['servers'][i] + for key in ['name', 'id']: + self.assertEqual(server_elem.get(key), str(server_dict[key])) + + link_nodes = server_elem.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 2) + for i, link in enumerate(server_dict['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) def test_detail(self): serializer = servers.ServerXMLSerializer() @@ -3875,6 +3884,8 @@ class ServerXMLSerializationTest(test.TestCase): "progress": 0, "name": "test_server", "status": "BUILD", + "accessIPv4": "1.2.3.4", + "accessIPv6": "fead::1234", "hostId": 'e4d909c290d0fb1ca068ffaddf22cbd0', "image": { "id": "5", @@ -3928,6 +3939,8 @@ class ServerXMLSerializationTest(test.TestCase): "progress": 100, "name": "test_server_2", "status": "ACTIVE", + "accessIPv4": "1.2.3.4", + "accessIPv6": "fead::1234", "hostId": 'e4d909c290d0fb1ca068ffaddf22cbd0', "image": { "id": "5", @@ -3976,71 +3989,61 @@ class ServerXMLSerializationTest(test.TestCase): ]} output = serializer.serialize(fixture, 'detail') - actual = minidom.parseString(output.replace(" ", "")) - - expected = minidom.parseString(""" - - - - - - - - - - - - - 1 - - - - - - - - - - - - - - - - - - - - - 2 - - - - - - - - - - - """.replace(" ", "") % (locals())) - - self.assertEqual(expected.toxml(), actual.toxml()) + print output + root = etree.XML(output) + xmlutil.validate_schema(root, 'servers') + server_elems = root.findall('{0}server'.format(NS)) + self.assertEqual(len(server_elems), 2) + for i, server_elem in enumerate(server_elems): + server_dict = fixture['servers'][i] + + for key in ['name', 'id', 'uuid', 'created', 'accessIPv4', + 'updated', 'progress', 'status', 'hostId', + 'accessIPv6']: + self.assertEqual(server_elem.get(key), str(server_dict[key])) + + link_nodes = server_elem.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 2) + for i, link in enumerate(server_dict['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + metadata_root = server_elem.find('{0}metadata'.format(NS)) + metadata_elems = metadata_root.findall('{0}meta'.format(NS)) + for i, metadata_elem in enumerate(metadata_elems): + (meta_key, meta_value) = server_dict['metadata'].items()[i] + self.assertEqual(str(metadata_elem.get('key')), str(meta_key)) + self.assertEqual(str(metadata_elem.text).strip(), str(meta_value)) + + image_root = server_elem.find('{0}image'.format(NS)) + self.assertEqual(image_root.get('id'), server_dict['image']['id']) + link_nodes = image_root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 1) + for i, link in enumerate(server_dict['image']['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + flavor_root = server_elem.find('{0}flavor'.format(NS)) + self.assertEqual(flavor_root.get('id'), server_dict['flavor']['id']) + link_nodes = flavor_root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 1) + for i, link in enumerate(server_dict['flavor']['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + addresses_root = server_elem.find('{0}addresses'.format(NS)) + addresses_dict = server_dict['addresses'] + network_elems = addresses_root.findall('{0}network'.format(NS)) + for i, network_elem in enumerate(network_elems): + network = addresses_dict.items()[i] + self.assertEqual(str(network_elem.get('id')), str(network[0])) + ip_elems = network_elem.findall('{0}ip'.format(NS)) + for z, ip_elem in enumerate(ip_elems): + ip = network[1][z] + self.assertEqual(str(ip_elem.get('version')), + str(ip['version'])) + self.assertEqual(str(ip_elem.get('addr')), + str(ip['addr'])) def test_update(self): serializer = servers.ServerXMLSerializer() @@ -4055,6 +4058,8 @@ class ServerXMLSerializationTest(test.TestCase): "name": "test_server", "status": "BUILD", "hostId": 'e4d909c290d0fb1ca068ffaddf22cbd0', + "accessIPv4": "1.2.3.4", + "accessIPv6": "fead::1234", "image": { "id": "5", "links": [ @@ -4113,7 +4118,9 @@ class ServerXMLSerializationTest(test.TestCase): } output = serializer.serialize(fixture, 'update') - actual = minidom.parseString(output.replace(" ", "")) + print output + root = etree.XML(output) + xmlutil.validate_schema(root, 'server') expected_server_href = self.SERVER_HREF expected_server_bookmark = self.SERVER_BOOKMARK @@ -4121,44 +4128,54 @@ class ServerXMLSerializationTest(test.TestCase): expected_flavor_bookmark = self.FLAVOR_BOOKMARK expected_now = self.TIMESTAMP expected_uuid = FAKE_UUID - expected = minidom.parseString(""" - - - - - - - - - - - - Stack - - - 1 - - - - - - - - - - - - - - """.replace(" ", "") % (locals())) + server_dict = fixture['server'] - self.assertEqual(expected.toxml(), actual.toxml()) + for key in ['name', 'id', 'uuid', 'created', 'accessIPv4', + 'updated', 'progress', 'status', 'hostId', + 'accessIPv6']: + self.assertEqual(root.get(key), str(server_dict[key])) + + link_nodes = root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 2) + for i, link in enumerate(server_dict['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + metadata_root = root.find('{0}metadata'.format(NS)) + metadata_elems = metadata_root.findall('{0}meta'.format(NS)) + self.assertEqual(len(metadata_elems), 2) + for i, metadata_elem in enumerate(metadata_elems): + (meta_key, meta_value) = server_dict['metadata'].items()[i] + self.assertEqual(str(metadata_elem.get('key')), str(meta_key)) + self.assertEqual(str(metadata_elem.text).strip(), str(meta_value)) + + image_root = root.find('{0}image'.format(NS)) + self.assertEqual(image_root.get('id'), server_dict['image']['id']) + link_nodes = image_root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 1) + for i, link in enumerate(server_dict['image']['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + flavor_root = root.find('{0}flavor'.format(NS)) + self.assertEqual(flavor_root.get('id'), server_dict['flavor']['id']) + link_nodes = flavor_root.findall('{0}link'.format(ATOMNS)) + self.assertEqual(len(link_nodes), 1) + for i, link in enumerate(server_dict['flavor']['links']): + for key, value in link.items(): + self.assertEqual(link_nodes[i].get(key), value) + + addresses_root = root.find('{0}addresses'.format(NS)) + addresses_dict = server_dict['addresses'] + network_elems = addresses_root.findall('{0}network'.format(NS)) + self.assertEqual(len(network_elems), 2) + for i, network_elem in enumerate(network_elems): + network = addresses_dict.items()[i] + self.assertEqual(str(network_elem.get('id')), str(network[0])) + ip_elems = network_elem.findall('{0}ip'.format(NS)) + for z, ip_elem in enumerate(ip_elems): + ip = network[1][z] + self.assertEqual(str(ip_elem.get('version')), + str(ip['version'])) + self.assertEqual(str(ip_elem.get('addr')), + str(ip['addr'])) -- cgit From 9827c92838d144f7c129e9e5545126f100926dba Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Fri, 19 Aug 2011 15:58:50 -0400 Subject: pep8 --- nova/tests/api/openstack/test_servers.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 961d2fb7c..6cf2d2d6a 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -3671,7 +3671,6 @@ class ServerXMLSerializationTest(test.TestCase): self.assertEqual(str(ip_elem.get('addr')), str(ip['addr'])) - def test_create(self): serializer = servers.ServerXMLSerializer() @@ -4013,7 +4012,8 @@ class ServerXMLSerializationTest(test.TestCase): for i, metadata_elem in enumerate(metadata_elems): (meta_key, meta_value) = server_dict['metadata'].items()[i] self.assertEqual(str(metadata_elem.get('key')), str(meta_key)) - self.assertEqual(str(metadata_elem.text).strip(), str(meta_value)) + self.assertEqual(str(metadata_elem.text).strip(), + str(meta_value)) image_root = server_elem.find('{0}image'.format(NS)) self.assertEqual(image_root.get('id'), server_dict['image']['id']) @@ -4024,7 +4024,8 @@ class ServerXMLSerializationTest(test.TestCase): self.assertEqual(link_nodes[i].get(key), value) flavor_root = server_elem.find('{0}flavor'.format(NS)) - self.assertEqual(flavor_root.get('id'), server_dict['flavor']['id']) + self.assertEqual(flavor_root.get('id'), + server_dict['flavor']['id']) link_nodes = flavor_root.findall('{0}link'.format(ATOMNS)) self.assertEqual(len(link_nodes), 1) for i, link in enumerate(server_dict['flavor']['links']): -- cgit From 5366332a84b89bc5a056bd7f43e528a908e8d188 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Fri, 19 Aug 2011 13:15:42 -0700 Subject: incorporate feedback from brian waldon and brian lamar. Move associate/disassociate to server actions --- nova/api/openstack/contrib/floating_ips.py | 69 ++++++++++++++-------- .../api/openstack/contrib/test_floating_ips.py | 57 ++++++++---------- 2 files changed, 69 insertions(+), 57 deletions(-) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 2f5fdd001..b305ebdcb 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -15,8 +15,9 @@ # 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 webob import exc +import webob +from nova import compute from nova import exception from nova import log as logging from nova import network @@ -71,7 +72,7 @@ class FloatingIPController(object): try: floating_ip = self.network_api.get_floating_ip(context, id) except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) + return faults.Fault(webob.exc.HTTPNotFound()) return _translate_floating_ip_view(floating_ip) @@ -110,40 +111,49 @@ class FloatingIPController(object): self.network_api.release_floating_ip(context, address=floating_ip['address']) - return exc.HTTPAccepted() + return webob.exc.HTTPAccepted() - def associate(self, req, id, body): - """PUT /floating_ips/{id}/associate fixed ip in body """ + def _get_ip_by_id(self, context, value): + """Checks that value is id and then returns its address.""" + return self.network_api.get_floating_ip(context, value)['address'] + + +class Floating_ips(extensions.ExtensionDescriptor): + def __init__(self): + self.compute_api = compute.API() + self.network_api = network.API() + super(Floating_ips, self).__init__() + + def _add_floating_ip(self, input_dict, req, instance_id): + """Associate floating_ip to an instance.""" context = req.environ['nova.context'] - floating_ip = self._get_ip_by_id(context, id) - fixed_ip = body['floating_ip']['fixed_ip'] + try: + address = input_dict['addFloatingIp']['address'] + except KeyError: + msg = _("Address not specified") + raise webob.exc.HTTPBadRequest(explanation=msg) - self.network_api.associate_floating_ip(context, - floating_ip, fixed_ip) + self.compute_api.associate_floating_ip(context, instance_id, address) - floating_ip = self.network_api.get_floating_ip(context, id) - return _translate_floating_ip_view(floating_ip) + return webob.Response(status_int=202) - def disassociate(self, req, id, body=None): - """PUT /floating_ips/{id}/disassociate """ + def _remove_floating_ip(self, input_dict, req, instance_id): + """Dissociate floating_ip from an instance.""" context = req.environ['nova.context'] - floating_ip = self.network_api.get_floating_ip(context, id) - address = floating_ip['address'] - # no-op if this ip is already disassociated + try: + address = input_dict['removeFloatingIp']['address'] + except KeyError: + msg = _("Address not specified") + raise webob.exc.HTTPBadRequest(explanation=msg) + + floating_ip = self.network_api.get_floating_ip_by_ip(context, address) if 'fixed_ip' in floating_ip: self.network_api.disassociate_floating_ip(context, address) - floating_ip = self.network_api.get_floating_ip(context, id) - - return _translate_floating_ip_view(floating_ip) - - def _get_ip_by_id(self, context, value): - """Checks that value is id and then returns its address.""" - return self.network_api.get_floating_ip(context, value)['address'] + return webob.Response(status_int=202) -class Floating_ips(extensions.ExtensionDescriptor): def get_name(self): return "Floating_ips" @@ -170,3 +180,14 @@ class Floating_ips(extensions.ExtensionDescriptor): resources.append(res) return resources + + def get_actions(self): + """Return the actions the extension adds, as required by contract.""" + actions = [ + extensions.ActionExtension("servers", "addFloatingIp", + self._add_floating_ip), + extensions.ActionExtension("servers", "removeFloatingIp", + self._remove_floating_ip), + ] + + return actions diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py index e506519f4..09234072a 100644 --- a/nova/tests/api/openstack/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -17,6 +17,7 @@ import json import stubout import webob +from nova import compute from nova import context from nova import db from nova import test @@ -29,6 +30,11 @@ from nova.api.openstack.contrib.floating_ips import _translate_floating_ip_view def network_api_get_floating_ip(self, context, id): + return {'id': 1, 'address': '10.10.10.10', + 'fixed_ip': None} + + +def network_api_get_floating_ip_by_ip(self, context, address): return {'id': 1, 'address': '10.10.10.10', 'fixed_ip': {'address': '11.0.0.1'}} @@ -50,7 +56,7 @@ def network_api_release(self, context, address): pass -def network_api_associate(self, context, floating_ip, fixed_ip): +def compute_api_associate(self, context, instance_id, floating_ip): pass @@ -78,14 +84,16 @@ class FloatingIpTest(test.TestCase): fakes.stub_out_rate_limiting(self.stubs) self.stubs.Set(network.api.API, "get_floating_ip", network_api_get_floating_ip) + self.stubs.Set(network.api.API, "get_floating_ip_by_ip", + network_api_get_floating_ip) self.stubs.Set(network.api.API, "list_floating_ips", network_api_list_floating_ips) self.stubs.Set(network.api.API, "allocate_floating_ip", network_api_allocate) self.stubs.Set(network.api.API, "release_floating_ip", network_api_release) - self.stubs.Set(network.api.API, "associate_floating_ip", - network_api_associate) + self.stubs.Set(compute.api.API, "associate_floating_ip", + compute_api_associate) self.stubs.Set(network.api.API, "disassociate_floating_ip", network_api_disassociate) self.context = context.get_admin_context() @@ -133,7 +141,6 @@ class FloatingIpTest(test.TestCase): res_dict = json.loads(res.body) self.assertEqual(res_dict['floating_ip']['id'], 1) self.assertEqual(res_dict['floating_ip']['ip'], '10.10.10.10') - self.assertEqual(res_dict['floating_ip']['fixed_ip'], '11.0.0.1') self.assertEqual(res_dict['floating_ip']['instance_id'], None) def test_floating_ip_allocate(self): @@ -141,7 +148,6 @@ class FloatingIpTest(test.TestCase): req.method = 'POST' req.headers['Content-Type'] = 'application/json' res = req.get_response(fakes.wsgi_app()) - print res self.assertEqual(res.status_int, 200) ip = json.loads(res.body)['floating_ip'] @@ -158,37 +164,22 @@ class FloatingIpTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) - def test_floating_ip_associate(self): - body = dict(floating_ip=dict(fixed_ip='11.0.0.1')) - req = webob.Request.blank('/v1.1/os-floating-ips/1/associate') - req.method = 'PUT' + def test_add_floating_ip_to_instance(self): + body = dict(addFloatingIp=dict(address='11.0.0.1')) + req = webob.Request.blank('/v1.1/servers/test_inst/action') + req.method = "POST" req.body = json.dumps(body) req.headers["content-type"] = "application/json" - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 200) - actual = json.loads(res.body)['floating_ip'] - - expected = { - "id": 1, - "instance_id": None, - "ip": "10.10.10.10", - "fixed_ip": "11.0.0.1"} - self.assertEqual(actual, expected) + resp = req.get_response(fakes.wsgi_app()) + self.assertEqual(resp.status_int, 202) - def test_floating_ip_disassociate(self): - body = dict() - req = webob.Request.blank('/v1.1/os-floating-ips/1/disassociate') - req.method = 'PUT' + def test_remove_floating_ip_from_instance(self): + body = dict(removeFloatingIp=dict(address='11.0.0.1')) + req = webob.Request.blank('/v1.1/servers/test_inst/action') + req.method = "POST" req.body = json.dumps(body) - req.headers['Content-Type'] = 'application/json' - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 200) - ip = json.loads(res.body)['floating_ip'] - expected = { - "id": 1, - "instance_id": None, - "ip": '10.10.10.10', - "fixed_ip": '11.0.0.1'} + req.headers["content-type"] = "application/json" - self.assertEqual(ip, expected) + resp = req.get_response(fakes.wsgi_app()) + self.assertEqual(resp.status_int, 202) -- cgit From 468893c667c7ce6cddb9d62906dfcb807fcd6da1 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Fri, 19 Aug 2011 13:25:33 -0700 Subject: a few tweaks - remove unused member functions, add comment --- nova/api/openstack/contrib/floating_ips.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index b305ebdcb..3b400807a 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -80,6 +80,7 @@ class FloatingIPController(object): context = req.environ['nova.context'] try: + # FIXME - why does self.network_api.list_floating_ips raise this? floating_ips = self.network_api.list_floating_ips(context) except exception.FloatingIpNotFoundForProject: floating_ips = [] @@ -174,9 +175,7 @@ class Floating_ips(extensions.ExtensionDescriptor): res = extensions.ResourceExtension('os-floating-ips', FloatingIPController(), - member_actions={ - 'associate': 'PUT', - 'disassociate': 'PUT'}) + member_actions={}) resources.append(res) return resources -- cgit From ce4ac4be2b813a8f025a9f2891fbc1ed4101c496 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Fri, 19 Aug 2011 13:31:49 -0700 Subject: tweak to comment --- nova/api/openstack/contrib/floating_ips.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 3b400807a..0f27f2f27 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -80,7 +80,7 @@ class FloatingIPController(object): context = req.environ['nova.context'] try: - # FIXME - why does self.network_api.list_floating_ips raise this? + # FIXME(ja) - why does self.network_api.list_floating_ips raise? floating_ips = self.network_api.list_floating_ips(context) except exception.FloatingIpNotFoundForProject: floating_ips = [] -- cgit From 5f6cd490425d8d91870de1b4a492a6cb34502bcb Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Fri, 19 Aug 2011 16:36:20 -0400 Subject: Updated accessIPv4 and accessIPv6 to always be in a servers response --- nova/api/openstack/views/servers.py | 6 ++---- nova/tests/api/openstack/test_servers.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 8b3a1e221..d2c1b0ba1 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -144,10 +144,8 @@ class ViewBuilderV11(ViewBuilder): elif response['server']['status'] == "BUILD": response['server']['progress'] = 0 - if inst.get('access_ip_v4'): - response['server']['accessIPv4'] = inst['access_ip_v4'] - if inst.get('access_ip_v6'): - response['server']['accessIPv6'] = inst['access_ip_v6'] + response['server']['accessIPv4'] = inst.get('access_ip_v4') or "" + response['server']['accessIPv6'] = inst.get('access_ip_v6') or "" return response diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 6cf2d2d6a..d3eb4c517 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -341,6 +341,8 @@ class ServersTest(test.TestCase): "progress": 0, "name": "server1", "status": "BUILD", + "accessIPv4": "", + "accessIPv6": "", "hostId": '', "image": { "id": "10", @@ -438,6 +440,8 @@ class ServersTest(test.TestCase): created="%(expected_created)s" hostId="" status="BUILD" + accessIPv4="" + accessIPv6="" progress="0"> @@ -503,6 +507,8 @@ class ServersTest(test.TestCase): "progress": 100, "name": "server1", "status": "ACTIVE", + "accessIPv4": "", + "accessIPv6": "", "hostId": '', "image": { "id": "10", @@ -594,6 +600,8 @@ class ServersTest(test.TestCase): "progress": 100, "name": "server1", "status": "ACTIVE", + "accessIPv4": "", + "accessIPv6": "", "hostId": '', "image": { "id": "10", @@ -1650,6 +1658,7 @@ class ServersTest(test.TestCase): self.assertEqual(expected_flavor, server['flavor']) self.assertEqual(expected_image, server['image']) self.assertEqual(access_ipv4, server['accessIPv4']) + self.assertEqual(access_ipv6, server['accessIPv6']) def test_create_instance_v1_1(self): self._setup_for_create_instance() @@ -1708,6 +1717,8 @@ class ServersTest(test.TestCase): self.assertEqual('server_test', server['name']) self.assertEqual(expected_flavor, server['flavor']) self.assertEqual(expected_image, server['image']) + self.assertEqual('1.2.3.4', server['accessIPv4']) + self.assertEqual('fead::1234', server['accessIPv6']) def test_create_instance_v1_1_invalid_flavor_href(self): self._setup_for_create_instance() @@ -3271,6 +3282,8 @@ class ServersViewBuilderV11Test(test.TestCase): "progress": 0, "name": "test_server", "status": "BUILD", + "accessIPv4": "", + "accessIPv6": "", "hostId": '', "image": { "id": "5", @@ -3322,6 +3335,8 @@ class ServersViewBuilderV11Test(test.TestCase): "progress": 100, "name": "test_server", "status": "ACTIVE", + "accessIPv4": "", + "accessIPv6": "", "hostId": '', "image": { "id": "5", @@ -3396,6 +3411,7 @@ class ServersViewBuilderV11Test(test.TestCase): "addresses": {}, "metadata": {}, "accessIPv4": "1.2.3.4", + "accessIPv6": "", "links": [ { "rel": "self", @@ -3448,6 +3464,7 @@ class ServersViewBuilderV11Test(test.TestCase): }, "addresses": {}, "metadata": {}, + "accessIPv4": "", "accessIPv6": "fead::1234", "links": [ { @@ -3483,6 +3500,8 @@ class ServersViewBuilderV11Test(test.TestCase): "progress": 0, "name": "test_server", "status": "BUILD", + "accessIPv4": "", + "accessIPv6": "", "hostId": '', "image": { "id": "5", -- cgit From 9b65cdf0b2d5cc7ed7adcaca0dde4d6e2a10bf95 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Fri, 19 Aug 2011 14:16:57 -0700 Subject: better handle malformed input, and add associated tests --- nova/api/openstack/contrib/floating_ips.py | 6 ++++ .../api/openstack/contrib/test_floating_ips.py | 40 ++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/nova/api/openstack/contrib/floating_ips.py b/nova/api/openstack/contrib/floating_ips.py index 0f27f2f27..40086f778 100644 --- a/nova/api/openstack/contrib/floating_ips.py +++ b/nova/api/openstack/contrib/floating_ips.py @@ -131,6 +131,9 @@ class Floating_ips(extensions.ExtensionDescriptor): try: address = input_dict['addFloatingIp']['address'] + except TypeError: + msg = _("Missing parameter dict") + raise webob.exc.HTTPBadRequest(explanation=msg) except KeyError: msg = _("Address not specified") raise webob.exc.HTTPBadRequest(explanation=msg) @@ -145,6 +148,9 @@ class Floating_ips(extensions.ExtensionDescriptor): try: address = input_dict['removeFloatingIp']['address'] + except TypeError: + msg = _("Missing parameter dict") + raise webob.exc.HTTPBadRequest(explanation=msg) except KeyError: msg = _("Address not specified") raise webob.exc.HTTPBadRequest(explanation=msg) diff --git a/nova/tests/api/openstack/contrib/test_floating_ips.py b/nova/tests/api/openstack/contrib/test_floating_ips.py index 09234072a..d2ca9c365 100644 --- a/nova/tests/api/openstack/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/contrib/test_floating_ips.py @@ -183,3 +183,43 @@ class FloatingIpTest(test.TestCase): resp = req.get_response(fakes.wsgi_app()) self.assertEqual(resp.status_int, 202) + + def test_bad_address_param_in_remove_floating_ip(self): + body = dict(removeFloatingIp=dict(badparam='11.0.0.1')) + req = webob.Request.blank('/v1.1/servers/test_inst/action') + req.method = "POST" + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + + resp = req.get_response(fakes.wsgi_app()) + self.assertEqual(resp.status_int, 400) + + def test_missing_dict_param_in_remove_floating_ip(self): + body = dict(removeFloatingIp='11.0.0.1') + req = webob.Request.blank('/v1.1/servers/test_inst/action') + req.method = "POST" + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + + resp = req.get_response(fakes.wsgi_app()) + self.assertEqual(resp.status_int, 400) + + def test_bad_address_param_in_add_floating_ip(self): + body = dict(addFloatingIp=dict(badparam='11.0.0.1')) + req = webob.Request.blank('/v1.1/servers/test_inst/action') + req.method = "POST" + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + + resp = req.get_response(fakes.wsgi_app()) + self.assertEqual(resp.status_int, 400) + + def test_missing_dict_param_in_add_floating_ip(self): + body = dict(addFloatingIp='11.0.0.1') + req = webob.Request.blank('/v1.1/servers/test_inst/action') + req.method = "POST" + req.body = json.dumps(body) + req.headers["content-type"] = "application/json" + + resp = req.get_response(fakes.wsgi_app()) + self.assertEqual(resp.status_int, 400) -- cgit From d27a4d4a59a0103762ece2776ddd484629a72d54 Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Fri, 19 Aug 2011 14:25:41 -0700 Subject: Fixed review comments --- nova/db/api.py | 21 +---- nova/db/sqlalchemy/api.py | 104 ++++++--------------- .../versions/037_add_uuid_to_networks.py | 43 --------- .../versions/039_add_uuid_to_networks.py | 43 +++++++++ nova/exception.py | 6 +- nova/network/manager.py | 68 ++++++-------- nova/tests/test_network.py | 81 ++++++++-------- 7 files changed, 151 insertions(+), 215 deletions(-) delete mode 100644 nova/db/sqlalchemy/migrate_repo/versions/037_add_uuid_to_networks.py create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/039_add_uuid_to_networks.py diff --git a/nova/db/api.py b/nova/db/api.py index 6833e6312..34d560ca3 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -323,13 +323,13 @@ def migration_get_by_instance_and_status(context, instance_uuid, status): #################### -def fixed_ip_associate(context, address, instance_id): +def fixed_ip_associate(context, address, instance_id, network_id=None): """Associate fixed ip to instance. Raises if fixed ip is not available. """ - return IMPL.fixed_ip_associate(context, address, instance_id) + return IMPL.fixed_ip_associate(context, address, instance_id, network_id) def fixed_ip_associate_pool(context, network_id, instance_id=None, host=None): @@ -342,14 +342,6 @@ def fixed_ip_associate_pool(context, network_id, instance_id=None, host=None): instance_id, host) -def fixed_ip_associate_by_address(context, network_id, instance_id, address): - """check if the address is free and is in the network - and it is not associated to any instance. - """ - return IMPL.fixed_ip_associate_by_address(context, network_id, - instance_id, address) - - def fixed_ip_create(context, values): """Create a fixed ip from the values dictionary.""" return IMPL.fixed_ip_create(context, values) @@ -687,9 +679,9 @@ def network_get_all(context): return IMPL.network_get_all(context) -def network_get_networks_by_uuids(context, network_uuids): +def network_get_all_by_uuids(context, network_uuids, project_id=None): """Return networks by ids.""" - return IMPL.network_get_networks_by_uuids(context, network_uuids) + return IMPL.network_get_all_by_uuids(context, network_uuids, project_id) # pylint: disable=C0103 @@ -1244,11 +1236,6 @@ def project_get_networks(context, project_id, associate=True): return IMPL.project_get_networks(context, project_id, associate) -def project_get_networks_by_uuids(context, network_uuids): - """Return the networks by uuids associated with the project.""" - return IMPL.project_get_networks_by_uuids(context, network_uuids) - - def project_get_networks_v6(context, project_id): return IMPL.project_get_networks_v6(context, project_id) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 5aa49213a..005500058 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -266,7 +266,7 @@ def service_get_all_network_sorted(context): session = get_session() with session.begin(): topic = 'network' - label = 'network_' + label = 'network_count' subq = session.query(models.Network.host, func.count(models.Network.id).label(label)).\ filter_by(deleted=False).\ @@ -652,23 +652,36 @@ def floating_ip_update(context, address, values): ################### -@require_context -def fixed_ip_associate(context, address, instance_id): +@require_admin_context +def fixed_ip_associate(context, address, instance_id, network_id=None): session = get_session() with session.begin(): - instance = instance_get(context, instance_id, session=session) + network_or_none = or_(models.FixedIp.network_id == network_id, + models.FixedIp.network_id == None) fixed_ip_ref = session.query(models.FixedIp).\ - filter_by(address=address).\ + filter(network_or_none).\ + filter_by(reserved=False).\ filter_by(deleted=False).\ - filter_by(instance=None).\ + filter_by(address=address).\ with_lockmode('update').\ first() # NOTE(vish): if with_lockmode isn't supported, as in sqlite, # then this has concurrency issues - if not fixed_ip_ref: - raise exception.NoMoreFixedIps() - fixed_ip_ref.instance = instance + if fixed_ip_ref is None: + raise exception.FixedIpNotFoundForNetwork(address=address, + network_id=network_id) + if fixed_ip_ref.instance is not None: + raise exception.FixedIpAlreadyInUse(address=address) + + if not fixed_ip_ref.network: + fixed_ip_ref.network = network_get(context, + network_id, + session=session) + fixed_ip_ref.instance = instance_get(context, + instance_id, + session=session) session.add(fixed_ip_ref) + return fixed_ip_ref['address'] @require_admin_context @@ -703,40 +716,6 @@ def fixed_ip_associate_pool(context, network_id, instance_id=None, host=None): return fixed_ip_ref['address'] -@require_admin_context -def fixed_ip_associate_by_address(context, network_id, instance_id, - address): - if address is None: - return fixed_ip_associate_pool(context, network_id, instance_id) - - session = get_session() - with session.begin(): - fixed_ip_ref = session.query(models.FixedIp).\ - filter_by(reserved=False).\ - filter_by(deleted=False).\ - filter_by(network_id=network_id).\ - filter_by(address=address).\ - with_lockmode('update').\ - first() - # NOTE(vish): if with_lockmode isn't supported, as in sqlite, - # then this has concurrency issues - if fixed_ip_ref is None: - raise exception.FixedIpNotFoundForNetwork(address=address, - network_id=network_id) - if fixed_ip_ref.instance is not None: - raise exception.FixedIpAlreadyInUse(address=address) - - if not fixed_ip_ref.network: - fixed_ip_ref.network = network_get(context, - network_id, - session=session) - fixed_ip_ref.instance = instance_get(context, - instance_id, - session=session) - session.add(fixed_ip_ref) - return fixed_ip_ref['address'] - - @require_context def fixed_ip_create(_context, values): fixed_ip_ref = models.FixedIp() @@ -1777,10 +1756,13 @@ def network_get_all(context): @require_admin_context -def network_get_networks_by_uuids(context, network_uuids): +def network_get_all_by_uuids(context, network_uuids, project_id=None): session = get_session() + project_or_none = or_(models.Network.project_id == project_id, + models.Network.project_id == None) result = session.query(models.Network).\ filter(models.Network.uuid.in_(network_uuids)).\ + filter(project_or_none).\ filter_by(deleted=False).all() if not result: raise exception.NoNetworksFound() @@ -1800,6 +1782,9 @@ def network_get_networks_by_uuids(context, network_uuids): found = True break if not found: + if project_id: + raise exception.NetworkNotFoundForProject(network_uuid=uuid, + project_id=context.project_id) raise exception.NetworkNotFound(network_id=network_uuid) return result @@ -2961,37 +2946,6 @@ def project_get_networks(context, project_id, associate=True): return result -@require_context -def project_get_networks_by_uuids(context, network_uuids): - session = get_session() - result = session.query(models.Network).\ - filter(models.Network.uuid.in_(network_uuids)).\ - filter_by(deleted=False).\ - filter_by(project_id=context.project_id).all() - - if not result: - raise exception.NoNetworksFound() - - #check if host is set to all of the networks - # returned in the result - for network in result: - if network['host'] is None: - raise exception.NetworkHostNotSet(network_id=network['id']) - - #check if the result contains all the networks - #we are looking for - for uuid in network_uuids: - found = False - for network in result: - if network['uuid'] == uuid: - found = True - break - if not found: - raise exception.NetworkNotFoundForProject(network_uuid=uuid, - project_id=context.project_id) - return result - - @require_context def project_get_networks_v6(context, project_id): return project_get_networks(context, project_id) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/037_add_uuid_to_networks.py b/nova/db/sqlalchemy/migrate_repo/versions/037_add_uuid_to_networks.py deleted file mode 100644 index 38c543d51..000000000 --- a/nova/db/sqlalchemy/migrate_repo/versions/037_add_uuid_to_networks.py +++ /dev/null @@ -1,43 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 OpenStack LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from sqlalchemy import Column, Integer, MetaData, String, Table - -from nova import utils - - -meta = MetaData() - -networks = Table("networks", meta, - Column("id", Integer(), primary_key=True, nullable=False)) -uuid_column = Column("uuid", String(36)) - - -def upgrade(migrate_engine): - meta.bind = migrate_engine - networks.create_column(uuid_column) - - rows = migrate_engine.execute(networks.select()) - for row in rows: - networks_uuid = str(utils.gen_uuid()) - migrate_engine.execute(networks.update()\ - .where(networks.c.id == row[0])\ - .values(uuid=networks_uuid)) - - -def downgrade(migrate_engine): - meta.bind = migrate_engine - networks.drop_column(uuid_column) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/039_add_uuid_to_networks.py b/nova/db/sqlalchemy/migrate_repo/versions/039_add_uuid_to_networks.py new file mode 100644 index 000000000..38c543d51 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/039_add_uuid_to_networks.py @@ -0,0 +1,43 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from sqlalchemy import Column, Integer, MetaData, String, Table + +from nova import utils + + +meta = MetaData() + +networks = Table("networks", meta, + Column("id", Integer(), primary_key=True, nullable=False)) +uuid_column = Column("uuid", String(36)) + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + networks.create_column(uuid_column) + + rows = migrate_engine.execute(networks.select()) + for row in rows: + networks_uuid = str(utils.gen_uuid()) + migrate_engine.execute(networks.update()\ + .where(networks.c.id == row[0])\ + .values(uuid=networks_uuid)) + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + networks.drop_column(uuid_column) diff --git a/nova/exception.py b/nova/exception.py index 97275fa25..262122990 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -185,10 +185,6 @@ class Invalid(NovaException): message = _("Unacceptable parameters.") -class AlreadyInUse(NovaException): - message = _("Already is in use.") - - class InvalidSignature(Invalid): message = _("Invalid signature %(signature)s for user %(user)s.") @@ -474,7 +470,7 @@ class FixedIpNotFoundForNetwork(FixedIpNotFound): "network (%(network_uuid)s).") -class FixedIpAlreadyInUse(AlreadyInUse): +class FixedIpAlreadyInUse(NovaException): message = _("Fixed IP address %(address)s is already in use.") diff --git a/nova/network/manager.py b/nova/network/manager.py index 9565c879f..aa2a3700c 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -399,8 +399,8 @@ class NetworkManager(manager.SchedulerDependentManager): # a non-vlan instance should connect to if requested_networks is not None and len(requested_networks) != 0: network_uuids = [uuid for (uuid, fixed_ip) in requested_networks] - networks = self.db.network_get_networks_by_uuids(context, - network_uuids) + networks = self.db.network_get_all_by_uuids(context, + network_uuids) else: try: networks = self.db.network_get_all(context) @@ -588,10 +588,15 @@ class NetworkManager(manager.SchedulerDependentManager): # network_get_by_compute_host address = None if network['cidr']: - address = self.db.fixed_ip_associate_by_address(context.elevated(), - network['id'], - instance_id, - kwargs.get('address', None)) + address = kwargs.get('address', None) + if address: + address = self.db.fixed_ip_associate(context, + address, instance_id, + network['id']) + else: + address = self.db.fixed_ip_associate_pool(context.elevated(), + network['id'], + instance_id) self._do_trigger_security_group_members_refresh_for_instance( instance_id) get_vif = self.db.virtual_interface_get_by_instance_and_network @@ -826,7 +831,7 @@ class NetworkManager(manager.SchedulerDependentManager): network_uuids = [uuid for (uuid, fixed_ip) in networks] - self.db.network_get_networks_by_uuids(context, network_uuids) + self._get_networks_by_uuids(context, network_uuids) for network_uuid, address in networks: # check if the fixed IP address is valid and @@ -843,6 +848,9 @@ class NetworkManager(manager.SchedulerDependentManager): if fixed_ip_ref['instance'] is not None: raise exception.FixedIpAlreadyInUse(address=address) + def _get_networks_by_uuids(self, context, network_uuids): + return self.db.network_get_all_by_uuids(context, network_uuids) + class FlatManager(NetworkManager): """Basic network where no vlans are used. @@ -980,10 +988,15 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): address, instance_id) else: - address = self.db.fixed_ip_associate_by_address(context, - network['id'], - instance_id, - kwargs.get('address', None)) + address = kwargs.get('address', None) + if address: + address = self.db.fixed_ip_associate(context, address, + instance_id, + network['id']) + else: + address = self.db.fixed_ip_associate_pool(context, + network['id'], + instance_id) self._do_trigger_security_group_members_refresh_for_instance( instance_id) vif = self.db.virtual_interface_get_by_instance_and_network(context, @@ -1005,8 +1018,9 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): # get networks associated with project if requested_networks is not None and len(requested_networks) != 0: network_uuids = [uuid for (uuid, fixed_ip) in requested_networks] - networks = self.db.project_get_networks_by_uuids(context, - network_uuids) + networks = self.db.network_get_all_by_uuids(context, + network_uuids, + project_id) else: networks = self.db.project_get_networks(context, project_id) return networks @@ -1058,31 +1072,9 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): self.db.network_update(context, network_ref['id'], {'gateway_v6': gateway}) - def validate_networks(self, context, networks): - """check if the networks exists and host - is set to each network. - """ - if networks is None or len(networks) == 0: - return - - network_uuids = [uuid for (uuid, fixed_ip) in networks] - - self.db.project_get_networks_by_uuids(context, network_uuids) - - for network_uuid, address in networks: - # check if the fixed IP address is valid and - # it actually belongs to the network - if fixed_ip is not None: - if not utils.is_valid_ipv4(address): - raise exception.FixedIpInvalid(address=address) - - fixed_ip_ref = self.db.fixed_ip_get_by_address(context, - address) - if fixed_ip_ref['network']['uuid'] != network_uuid: - raise exception.FixedIpNotFoundForNetwork(address=address, - network_uuid=network_uuid) - if fixed_ip_ref['instance'] is not None: - raise exception.FixedIpAlreadyInUse(address=address) + def _get_networks_by_uuids(self, context, network_uuids): + return self.db.network_get_all_by_uuids(context, network_uuids, + context.project_id) @property def _bottom_reserved_ips(self): diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index 86fa9ee7f..0b8539442 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -15,6 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. +from nova import context from nova import db from nova import exception from nova import log as logging @@ -128,6 +129,8 @@ class FlatNetworkTestCase(test.TestCase): super(FlatNetworkTestCase, self).setUp() self.network = network_manager.FlatManager(host=HOST) self.network.db = db + self.context = context.RequestContext('testuser', 'testproject', + is_admin=False) def test_get_instance_nw_info(self): self.mox.StubOutWithMock(db, 'fixed_ip_get_by_instance') @@ -186,13 +189,13 @@ class FlatNetworkTestCase(test.TestCase): self.assertDictListMatch(nw[1]['ips'], check) def test_validate_networks(self): - self.mox.StubOutWithMock(db, 'network_get_networks_by_uuids') + self.mox.StubOutWithMock(db, 'network_get_all_by_uuids') self.mox.StubOutWithMock(db, "fixed_ip_get_by_address") requested_networks = [("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", "192.168.1.100")] - db.network_get_networks_by_uuids(mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn(networks) + db.network_get_all_by_uuids(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks) fixed_ips[1]['network'] = FakeModel(**networks[1]) fixed_ips[1]['instance'] = None @@ -200,22 +203,22 @@ class FlatNetworkTestCase(test.TestCase): mox.IgnoreArg()).AndReturn(fixed_ips[1]) self.mox.ReplayAll() - self.network.validate_networks(None, requested_networks) + self.network.validate_networks(self.context, requested_networks) def test_validate_networks_none_requested_networks(self): - self.network.validate_networks(None, None) + self.network.validate_networks(self.context, None) def test_validate_networks_empty_requested_networks(self): requested_networks = [] self.mox.ReplayAll() - self.network.validate_networks(None, requested_networks) + self.network.validate_networks(self.context, requested_networks) def test_validate_networks_invalid_fixed_ip(self): - self.mox.StubOutWithMock(db, 'network_get_networks_by_uuids') + self.mox.StubOutWithMock(db, 'network_get_all_by_uuids') requested_networks = [(1, "192.168.0.100.1")] - db.network_get_networks_by_uuids(mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn(networks) + db.network_get_all_by_uuids(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks) self.mox.ReplayAll() self.assertRaises(exception.FixedIpInvalid, @@ -223,11 +226,11 @@ class FlatNetworkTestCase(test.TestCase): requested_networks) def test_validate_networks_empty_fixed_ip(self): - self.mox.StubOutWithMock(db, 'network_get_networks_by_uuids') + self.mox.StubOutWithMock(db, 'network_get_all_by_uuids') requested_networks = [(1, "")] - db.network_get_networks_by_uuids(mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn(networks) + db.network_get_all_by_uuids(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks) self.mox.ReplayAll() self.assertRaises(exception.FixedIpInvalid, @@ -235,10 +238,10 @@ class FlatNetworkTestCase(test.TestCase): None, requested_networks) def test_validate_networks_none_fixed_ip(self): - self.mox.StubOutWithMock(db, 'network_get_networks_by_uuids') + self.mox.StubOutWithMock(db, 'network_get_all_by_uuids') requested_networks = [(1, None)] - db.network_get_networks_by_uuids(mox.IgnoreArg(), + db.network_get_all_by_uuids(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(networks) self.mox.ReplayAll() @@ -250,6 +253,8 @@ class VlanNetworkTestCase(test.TestCase): super(VlanNetworkTestCase, self).setUp() self.network = network_manager.VlanManager(host=HOST) self.network.db = db + self.context = context.RequestContext('testuser', 'testproject', + is_admin=False) def test_vpn_allocate_fixed_ip(self): self.mox.StubOutWithMock(db, 'fixed_ip_associate') @@ -272,7 +277,7 @@ class VlanNetworkTestCase(test.TestCase): self.network.allocate_fixed_ip(None, 0, network, vpn=True) def test_allocate_fixed_ip(self): - self.mox.StubOutWithMock(db, 'fixed_ip_associate_by_address') + self.mox.StubOutWithMock(db, 'fixed_ip_associate_pool') self.mox.StubOutWithMock(db, 'fixed_ip_update') self.mox.StubOutWithMock(db, 'virtual_interface_get_by_instance_and_network') @@ -281,8 +286,7 @@ class VlanNetworkTestCase(test.TestCase): db.instance_get(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn({'security_groups': [{'id': 0}]}) - db.fixed_ip_associate_by_address(mox.IgnoreArg(), - mox.IgnoreArg(), + db.fixed_ip_associate_pool(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()).AndReturn('192.168.0.1') db.fixed_ip_update(mox.IgnoreArg(), @@ -294,7 +298,7 @@ class VlanNetworkTestCase(test.TestCase): network = dict(networks[0]) network['vpn_private_address'] = '192.168.0.2' - self.network.allocate_fixed_ip(None, 0, network) + self.network.allocate_fixed_ip(self.context, 0, network) def test_create_networks_too_big(self): self.assertRaises(ValueError, self.network.create_networks, None, @@ -306,13 +310,14 @@ class VlanNetworkTestCase(test.TestCase): cidr='192.168.0.1/24', network_size=100) def test_validate_networks(self): - self.mox.StubOutWithMock(db, 'project_get_networks_by_uuids') + self.mox.StubOutWithMock(db, 'network_get_all_by_uuids') self.mox.StubOutWithMock(db, "fixed_ip_get_by_address") requested_networks = [("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", "192.168.1.100")] - db.project_get_networks_by_uuids(mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn(networks) + db.network_get_all_by_uuids(mox.IgnoreArg(), + mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks) fixed_ips[1]['network'] = FakeModel(**networks[1]) fixed_ips[1]['instance'] = None @@ -320,49 +325,51 @@ class VlanNetworkTestCase(test.TestCase): mox.IgnoreArg()).AndReturn(fixed_ips[1]) self.mox.ReplayAll() - self.network.validate_networks(None, requested_networks) + self.network.validate_networks(self.context, requested_networks) def test_validate_networks_none_requested_networks(self): - self.network.validate_networks(None, None) + self.network.validate_networks(self.context, None) def test_validate_networks_empty_requested_networks(self): requested_networks = [] self.mox.ReplayAll() - self.network.validate_networks(None, requested_networks) + self.network.validate_networks(self.context, requested_networks) def test_validate_networks_invalid_fixed_ip(self): - self.mox.StubOutWithMock(db, 'project_get_networks_by_uuids') + self.mox.StubOutWithMock(db, 'network_get_all_by_uuids') requested_networks = [(1, "192.168.0.100.1")] - db.project_get_networks_by_uuids(mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn(networks) + db.network_get_all_by_uuids(mox.IgnoreArg(), + mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks) self.mox.ReplayAll() self.assertRaises(exception.FixedIpInvalid, - self.network.validate_networks, None, + self.network.validate_networks, self.context, requested_networks) def test_validate_networks_empty_fixed_ip(self): - self.mox.StubOutWithMock(db, 'project_get_networks_by_uuids') + self.mox.StubOutWithMock(db, 'network_get_all_by_uuids') requested_networks = [(1, "")] - db.project_get_networks_by_uuids(mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn(networks) + db.network_get_all_by_uuids(mox.IgnoreArg(), + mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks) self.mox.ReplayAll() self.assertRaises(exception.FixedIpInvalid, self.network.validate_networks, - None, requested_networks) + self.context, requested_networks) def test_validate_networks_none_fixed_ip(self): - self.mox.StubOutWithMock(db, 'project_get_networks_by_uuids') + self.mox.StubOutWithMock(db, 'network_get_all_by_uuids') requested_networks = [(1, None)] - db.project_get_networks_by_uuids(mox.IgnoreArg(), - mox.IgnoreArg()).AndReturn(networks) + db.network_get_all_by_uuids(mox.IgnoreArg(), + mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks) self.mox.ReplayAll() - - self.network.validate_networks(None, requested_networks) + self.network.validate_networks(self.context, requested_networks) class CommonNetworkTestCase(test.TestCase): -- cgit From bb989133196744779527e36cba22a76bd44e533b Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Sat, 20 Aug 2011 15:38:13 -0700 Subject: add/remove security groups to/from the servers as server actions --- nova/api/openstack/contrib/security_groups.py | 248 ++++++----------- nova/compute/api.py | 72 +++++ nova/exception.py | 10 + .../api/openstack/contrib/test_security_groups.py | 294 ++++++++++----------- 4 files changed, 296 insertions(+), 328 deletions(-) diff --git a/nova/api/openstack/contrib/security_groups.py b/nova/api/openstack/contrib/security_groups.py index a104a42e4..1fd64f3b8 100644 --- a/nova/api/openstack/contrib/security_groups.py +++ b/nova/api/openstack/contrib/security_groups.py @@ -168,135 +168,6 @@ class SecurityGroupController(object): "than 255 characters.") % typ raise exc.HTTPBadRequest(explanation=msg) - def associate(self, req, id, body): - context = req.environ['nova.context'] - - if not body: - raise exc.HTTPUnprocessableEntity() - - if not 'security_group_associate' in body: - raise exc.HTTPUnprocessableEntity() - - security_group = self._get_security_group(context, id) - - servers = body['security_group_associate'].get('servers') - - if not servers: - msg = _("No servers found") - return exc.HTTPBadRequest(explanation=msg) - - hosts = set() - for server in servers: - if server['id']: - try: - # check if the server exists - inst = db.instance_get(context, server['id']) - #check if the security group is assigned to the server - if self._is_security_group_associated_to_server( - security_group, inst['id']): - msg = _("Security group %s is already associated with" - " the instance %s") % (security_group['id'], - server['id']) - raise exc.HTTPBadRequest(explanation=msg) - - #check if the instance is in running state - if inst['state'] != power_state.RUNNING: - msg = _("Server %s is not in the running state")\ - % server['id'] - raise exc.HTTPBadRequest(explanation=msg) - - hosts.add(inst['host']) - except exception.InstanceNotFound as exp: - return exc.HTTPNotFound(explanation=unicode(exp)) - - # Associate security group with the server in the db - for server in servers: - if server['id']: - db.instance_add_security_group(context.elevated(), - server['id'], - security_group['id']) - - for host in hosts: - rpc.cast(context, - db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "refresh_security_group_rules", - "args": {"security_group_id": security_group['id']}}) - - return exc.HTTPAccepted() - - def _is_security_group_associated_to_server(self, security_group, - instance_id): - if not security_group: - return False - - instances = security_group.get('instances') - if not instances: - return False - - inst_id = None - for inst_id in (instance['id'] for instance in instances \ - if instance_id == instance['id']): - return True - - return False - - def disassociate(self, req, id, body): - context = req.environ['nova.context'] - - if not body: - raise exc.HTTPUnprocessableEntity() - - if not 'security_group_disassociate' in body: - raise exc.HTTPUnprocessableEntity() - - security_group = self._get_security_group(context, id) - - servers = body['security_group_disassociate'].get('servers') - - if not servers: - msg = _("No servers found") - return exc.HTTPBadRequest(explanation=msg) - - hosts = set() - for server in servers: - if server['id']: - try: - # check if the instance exists - inst = db.instance_get(context, server['id']) - # Check if the security group is not associated - # with the instance - if not self._is_security_group_associated_to_server( - security_group, inst['id']): - msg = _("Security group %s is not associated with the" - "instance %s") % (security_group['id'], - server['id']) - raise exc.HTTPBadRequest(explanation=msg) - - #check if the instance is in running state - if inst['state'] != power_state.RUNNING: - msg = _("Server %s is not in the running state")\ - % server['id'] - raise exp.HTTPBadRequest(explanation=msg) - - hosts.add(inst['host']) - except exception.InstanceNotFound as exp: - return exc.HTTPNotFound(explanation=unicode(exp)) - - # Disassociate security group from the server - for server in servers: - if server['id']: - db.instance_remove_security_group(context.elevated(), - server['id'], - security_group['id']) - - for host in hosts: - rpc.cast(context, - db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "refresh_security_group_rules", - "args": {"security_group_id": security_group['id']}}) - - return exc.HTTPAccepted() - class SecurityGroupRulesController(SecurityGroupController): @@ -461,6 +332,11 @@ class SecurityGroupRulesController(SecurityGroupController): class Security_groups(extensions.ExtensionDescriptor): + + def __init__(self): + self.compute_api = compute.API() + super(Security_groups, self).__init__() + def get_name(self): return "SecurityGroups" @@ -476,6 +352,82 @@ class Security_groups(extensions.ExtensionDescriptor): def get_updated(self): return "2011-07-21T00:00:00+00:00" + def _addSecurityGroup(self, input_dict, req, instance_id): + context = req.environ['nova.context'] + + try: + body = input_dict['addSecurityGroup'] + group_name = body['name'] + instance_id = int(instance_id) + except ValueError: + msg = _("Server id should be integer") + raise exc.HTTPBadRequest(explanation=msg) + except TypeError: + msg = _("Missing parameter dict") + raise webob.exc.HTTPBadRequest(explanation=msg) + except KeyError: + msg = _("Security group not specified") + raise webob.exc.HTTPBadRequest(explanation=msg) + + if not group_name or group_name.strip() == '': + msg = _("Security group name cannot be empty") + raise webob.exc.HTTPBadRequest(explanation=msg) + + try: + self.compute_api.add_security_group(context, instance_id, + group_name) + except exception.SecurityGroupNotFound as exp: + return exc.HTTPNotFound(explanation=unicode(exp)) + except exception.InstanceNotFound as exp: + return exc.HTTPNotFound(explanation=unicode(exp)) + except exception.Invalid as exp: + return exc.HTTPBadRequest(explanation=unicode(exp)) + + return exc.HTTPAccepted() + + def _removeSecurityGroup(self, input_dict, req, instance_id): + context = req.environ['nova.context'] + + try: + body = input_dict['removeSecurityGroup'] + group_name = body['name'] + instance_id = int(instance_id) + except ValueError: + msg = _("Server id should be integer") + raise exc.HTTPBadRequest(explanation=msg) + except TypeError: + msg = _("Missing parameter dict") + raise webob.exc.HTTPBadRequest(explanation=msg) + except KeyError: + msg = _("Security group not specified") + raise webob.exc.HTTPBadRequest(explanation=msg) + + if not group_name or group_name.strip() == '': + msg = _("Security group name cannot be empty") + raise webob.exc.HTTPBadRequest(explanation=msg) + + try: + self.compute_api.remove_security_group(context, instance_id, + group_name) + except exception.SecurityGroupNotFound as exp: + return exc.HTTPNotFound(explanation=unicode(exp)) + except exception.InstanceNotFound as exp: + return exc.HTTPNotFound(explanation=unicode(exp)) + except exception.Invalid as exp: + return exc.HTTPBadRequest(explanation=unicode(exp)) + + return exc.HTTPAccepted() + + def get_actions(self): + """Return the actions the extensions adds""" + actions = [ + extensions.ActionExtension("servers", "addSecurityGroup", + self._addSecurityGroup), + extensions.ActionExtension("servers", "removeSecurityGroup", + self._removeSecurityGroup) + ] + return actions + def get_resources(self): resources = [] @@ -493,10 +445,6 @@ class Security_groups(extensions.ExtensionDescriptor): res = extensions.ResourceExtension('os-security-groups', controller=SecurityGroupController(), - member_actions={ - 'associate': 'POST', - 'disassociate': 'POST' - }, deserializer=deserializer, serializer=serializer) @@ -534,40 +482,6 @@ class SecurityGroupXMLDeserializer(wsgi.MetadataXMLDeserializer): security_group['description'] = self.extract_text(desc_node) return {'body': {'security_group': security_group}} - def _get_servers(self, node): - servers_dict = {'servers': []} - if node is not None: - servers_node = self.find_first_child_named(node, - 'servers') - if servers_node is not None: - for server_node in self.find_children_named(servers_node, - "server"): - servers_dict['servers'].append( - {"id": self.extract_text(server_node)}) - return servers_dict - - def associate(self, string): - """Deserialize an xml-formatted security group associate request""" - dom = minidom.parseString(string) - node = self.find_first_child_named(dom, - 'security_group_associate') - result = {'body': {}} - if node: - result['body']['security_group_associate'] = \ - self._get_servers(node) - return result - - def disassociate(self, string): - """Deserialize an xml-formatted security group disassociate request""" - dom = minidom.parseString(string) - node = self.find_first_child_named(dom, - 'security_group_disassociate') - result = {'body': {}} - if node: - result['body']['security_group_disassociate'] = \ - self._get_servers(node) - return result - class SecurityGroupRulesXMLDeserializer(wsgi.MetadataXMLDeserializer): """ diff --git a/nova/compute/api.py b/nova/compute/api.py index efc9da79b..0c6beacaa 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -613,6 +613,78 @@ class API(base.Base): self.db.queue_get_for(context, FLAGS.compute_topic, host), {'method': 'refresh_provider_fw_rules', 'args': {}}) + def _is_security_group_associated_with_server(self, security_group, + instance_id): + """Check if the security group is already associated + with the instance. If Yes, return True. + """ + + if not security_group: + return False + + instances = security_group.get('instances') + if not instances: + return False + + inst_id = None + for inst_id in (instance['id'] for instance in instances \ + if instance_id == instance['id']): + return True + + return False + + def add_security_group(self, context, instance_id, security_group_name): + """Add security group to the instance""" + security_group = db.security_group_get_by_name(context, + context.project_id, + security_group_name) + # check if the server exists + inst = db.instance_get(context, instance_id) + #check if the security group is associated with the server + if self._is_security_group_associated_with_server(security_group, + instance_id): + raise exception.SecurityGroupExistsForInstance( + security_group_id=security_group['id'], + instance_id=instance_id) + + #check if the instance is in running state + if inst['state'] != power_state.RUNNING: + raise exception.InstanceNotRunning(instance_id=instance_id) + + db.instance_add_security_group(context.elevated(), + instance_id, + security_group['id']) + rpc.cast(context, + db.queue_get_for(context, FLAGS.compute_topic, inst['host']), + {"method": "refresh_security_group_rules", + "args": {"security_group_id": security_group['id']}}) + + def remove_security_group(self, context, instance_id, security_group_name): + """Remove the security group associated with the instance""" + security_group = db.security_group_get_by_name(context, + context.project_id, + security_group_name) + # check if the server exists + inst = db.instance_get(context, instance_id) + #check if the security group is associated with the server + if not self._is_security_group_associated_with_server(security_group, + instance_id): + raise exception.SecurityGroupNotExistsForInstance( + security_group_id=security_group['id'], + instance_id=instance_id) + + #check if the instance is in running state + if inst['state'] != power_state.RUNNING: + raise exception.InstanceNotRunning(instance_id=instance_id) + + db.instance_remove_security_group(context.elevated(), + instance_id, + security_group['id']) + rpc.cast(context, + db.queue_get_for(context, FLAGS.compute_topic, inst['host']), + {"method": "refresh_security_group_rules", + "args": {"security_group_id": security_group['id']}}) + @scheduler_api.reroute_compute("update") def update(self, context, instance_id, **kwargs): """Updates the instance in the datastore. diff --git a/nova/exception.py b/nova/exception.py index b09d50797..e8cb7bcb5 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -541,6 +541,16 @@ class SecurityGroupNotFoundForRule(SecurityGroupNotFound): message = _("Security group with rule %(rule_id)s not found.") +class SecurityGroupExistsForInstance(Invalid): + message = _("Security group %(security_group_id)s is already associated" + " with the instance %(instance_id)s") + + +class SecurityGroupNotExistsForInstance(Invalid): + message = _("Security group %(security_group_id)s is not associated with" + " the instance %(instance_id)s") + + class MigrationNotFound(NotFound): message = _("Migration %(migration_id)s could not be found.") diff --git a/nova/tests/api/openstack/contrib/test_security_groups.py b/nova/tests/api/openstack/contrib/test_security_groups.py index 894b0c591..b44ebc9fb 100644 --- a/nova/tests/api/openstack/contrib/test_security_groups.py +++ b/nova/tests/api/openstack/contrib/test_security_groups.py @@ -63,17 +63,17 @@ def return_non_running_server(context, server_id): 'host': "localhost"} -def return_security_group(context, group_id): - return {'id': group_id, "instances": [ +def return_security_group(context, project_id, group_name): + return {'id': 1, 'name': group_name, "instances": [ {'id': 1}]} -def return_security_group_without_instances(context, group_id): - return {'id': group_id} +def return_security_group_without_instances(context, project_id, group_name): + return {'id': 1, 'name': group_name} def return_server_nonexistant(context, server_id): - raise exception.InstanceNotFound() + raise exception.InstanceNotFound(instance_id=server_id) class TestSecurityGroups(test.TestCase): @@ -350,117 +350,89 @@ class TestSecurityGroups(test.TestCase): response = self._delete_security_group(11111111) self.assertEquals(response.status_int, 404) - def test_associate_by_non_existing_security_group_id(self): - req = webob.Request.blank('/v1.1/os-security-groups/111111/associate') + def test_associate_by_non_existing_security_group_name(self): + body = dict(addSecurityGroup=dict(name='non-existing')) + req = webob.Request.blank('/v1.1/servers/1/action') req.headers['Content-Type'] = 'application/json' req.method = 'POST' - body_dict = {"security_group_associate": { - "servers": [ - {"id": '2'} - ] - } - } - req.body = json.dumps(body_dict) + req.body = json.dumps(body) response = req.get_response(fakes.wsgi_app()) self.assertEquals(response.status_int, 404) - def test_associate_by_invalid_security_group_id(self): - req = webob.Request.blank('/v1.1/os-security-groups/invalid/associate') + def test_associate_by_invalid_server_id(self): + body = dict(addSecurityGroup=dict(name='test')) + self.stubs.Set(nova.db, 'security_group_get_by_name', + return_security_group) + req = webob.Request.blank('/v1.1/servers/invalid/action') req.headers['Content-Type'] = 'application/json' req.method = 'POST' - body_dict = {"security_group_associate": { - "servers": [ - {"id": "2"} - ] - } - } - req.body = json.dumps(body_dict) + req.body = json.dumps(body) response = req.get_response(fakes.wsgi_app()) self.assertEquals(response.status_int, 400) def test_associate_without_body(self): - req = webob.Request.blank('/v1.1/os-security-groups/1/associate') + req = webob.Request.blank('/v1.1/servers/1/action') + body = dict(addSecurityGroup=None) + self.stubs.Set(nova.db, 'instance_get', return_server) req.headers['Content-Type'] = 'application/json' req.method = 'POST' - req.body = json.dumps(None) + req.body = json.dumps(body) response = req.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 422) + self.assertEquals(response.status_int, 400) - def test_associate_no_security_group_element(self): - req = webob.Request.blank('/v1.1/os-security-groups/1/associate') + def test_associate_no_security_group_name(self): + req = webob.Request.blank('/v1.1/servers/1/action') + body = dict(addSecurityGroup=dict()) + self.stubs.Set(nova.db, 'instance_get', return_server) req.headers['Content-Type'] = 'application/json' req.method = 'POST' - body_dict = {"security_group_associate_invalid": { - "servers": [ - {"id": "2"} - ] - } - } - req.body = json.dumps(body_dict) + req.body = json.dumps(body) response = req.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 422) + self.assertEquals(response.status_int, 400) - def test_associate_no_instances(self): - #self.stubs.Set(nova.db.api, 'instance_get', return_server) - self.stubs.Set(nova.db, 'security_group_get', return_security_group) - req = webob.Request.blank('/v1.1/os-security-groups/1/associate') + def test_associate_security_group_name_with_whitespaces(self): + req = webob.Request.blank('/v1.1/servers/1/action') + body = dict(addSecurityGroup=dict(name=" ")) + self.stubs.Set(nova.db, 'instance_get', return_server) req.headers['Content-Type'] = 'application/json' req.method = 'POST' - body_dict = {"security_group_associate": { - "servers": [ - ] - } - } - req.body = json.dumps(body_dict) + req.body = json.dumps(body) response = req.get_response(fakes.wsgi_app()) self.assertEquals(response.status_int, 400) def test_associate_non_existing_instance(self): self.stubs.Set(nova.db, 'instance_get', return_server_nonexistant) - self.stubs.Set(nova.db, 'security_group_get', return_security_group) - req = webob.Request.blank('/v1.1/os-security-groups/1/associate') + body = dict(addSecurityGroup=dict(name="test")) + self.stubs.Set(nova.db, 'security_group_get_by_name', + return_security_group) + req = webob.Request.blank('/v1.1/servers/10000/action') req.headers['Content-Type'] = 'application/json' req.method = 'POST' - body_dict = {"security_group_associate": { - "servers": [ - {'id': 2} - ] - } - } - req.body = json.dumps(body_dict) + req.body = json.dumps(body) response = req.get_response(fakes.wsgi_app()) self.assertEquals(response.status_int, 404) def test_associate_non_running_instance(self): self.stubs.Set(nova.db, 'instance_get', return_non_running_server) - self.stubs.Set(nova.db, 'security_group_get', + self.stubs.Set(nova.db, 'security_group_get_by_name', return_security_group_without_instances) - req = webob.Request.blank('/v1.1/os-security-groups/1/associate') + body = dict(addSecurityGroup=dict(name="test")) + req = webob.Request.blank('/v1.1/servers/1/action') req.headers['Content-Type'] = 'application/json' req.method = 'POST' - body_dict = {"security_group_associate": { - "servers": [ - {'id': 1} - ] - } - } - req.body = json.dumps(body_dict) + req.body = json.dumps(body) response = req.get_response(fakes.wsgi_app()) self.assertEquals(response.status_int, 400) def test_associate_already_associated_security_group_to_instance(self): self.stubs.Set(nova.db, 'instance_get', return_server) - self.stubs.Set(nova.db, 'security_group_get', return_security_group) - req = webob.Request.blank('/v1.1/os-security-groups/1/associate') + self.stubs.Set(nova.db, 'security_group_get_by_name', + return_security_group) + body = dict(addSecurityGroup=dict(name="test")) + req = webob.Request.blank('/v1.1/servers/1/action') req.headers['Content-Type'] = 'application/json' req.method = 'POST' - body_dict = {"security_group_associate": { - "servers": [ - {'id': 1} - ] - } - } - req.body = json.dumps(body_dict) + req.body = json.dumps(body) response = req.get_response(fakes.wsgi_app()) self.assertEquals(response.status_int, 400) @@ -470,134 +442,120 @@ class TestSecurityGroups(test.TestCase): nova.db.instance_add_security_group(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()) - self.stubs.Set(nova.db, 'security_group_get', + self.stubs.Set(nova.db, 'security_group_get_by_name', return_security_group_without_instances) self.mox.ReplayAll() - req = webob.Request.blank('/v1.1/os-security-groups/1/associate') + body = dict(addSecurityGroup=dict(name="test")) + req = webob.Request.blank('/v1.1/servers/1/action') req.headers['Content-Type'] = 'application/json' req.method = 'POST' - body_dict = {"security_group_associate": { - "servers": [ - {'id': 1} - ] - } - } - req.body = json.dumps(body_dict) + req.body = json.dumps(body) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 202) + + def test_associate_xml(self): + self.stubs.Set(nova.db, 'instance_get', return_server) + self.mox.StubOutWithMock(nova.db, 'instance_add_security_group') + nova.db.instance_add_security_group(mox.IgnoreArg(), + mox.IgnoreArg(), + mox.IgnoreArg()) + self.stubs.Set(nova.db, 'security_group_get_by_name', + return_security_group_without_instances) + self.mox.ReplayAll() + + req = webob.Request.blank('/v1.1/servers/1/action') + req.headers['Content-Type'] = 'application/xml' + req.method = 'POST' + req.body = """ + test + """ response = req.get_response(fakes.wsgi_app()) self.assertEquals(response.status_int, 202) - def test_disassociate_by_non_existing_security_group_id(self): - req = webob.Request.blank('/v1.1/os-security-groups/1111/disassociate') + def test_disassociate_by_non_existing_security_group_name(self): + body = dict(removeSecurityGroup=dict(name='non-existing')) + req = webob.Request.blank('/v1.1/servers/1/action') req.headers['Content-Type'] = 'application/json' req.method = 'POST' - body_dict = {"security_group_disassociate": { - "servers": [ - {"id": "2"} - ] - } - } - req.body = json.dumps(body_dict) + req.body = json.dumps(body) response = req.get_response(fakes.wsgi_app()) self.assertEquals(response.status_int, 404) - def test_disassociate_by_invalid_security_group_id(self): - req = webob.Request.blank('/v1.1/os-security-groups/id/disassociate') + def test_disassociate_by_invalid_server_id(self): + body = dict(removeSecurityGroup=dict(name='test')) + self.stubs.Set(nova.db, 'security_group_get_by_name', + return_security_group) + req = webob.Request.blank('/v1.1/servers/invalid/action') req.headers['Content-Type'] = 'application/json' req.method = 'POST' - body_dict = {"security_group_disassociate": { - "servers": [ - {"id": "2"} - ] - } - } - req.body = json.dumps(body_dict) + req.body = json.dumps(body) response = req.get_response(fakes.wsgi_app()) self.assertEquals(response.status_int, 400) def test_disassociate_without_body(self): - req = webob.Request.blank('/v1.1/os-security-groups/1/disassociate') + req = webob.Request.blank('/v1.1/servers/1/action') + body = dict(removeSecurityGroup=None) + self.stubs.Set(nova.db, 'instance_get', return_server) req.headers['Content-Type'] = 'application/json' req.method = 'POST' - req.body = json.dumps(None) + req.body = json.dumps(body) response = req.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 422) + self.assertEquals(response.status_int, 400) - def test_disassociate_no_security_group_element(self): - req = webob.Request.blank('/v1.1/os-security-groups/1/disassociate') + def test_disassociate_no_security_group_name(self): + req = webob.Request.blank('/v1.1/servers/1/action') + body = dict(removeSecurityGroup=dict()) + self.stubs.Set(nova.db, 'instance_get', return_server) req.headers['Content-Type'] = 'application/json' req.method = 'POST' - body_dict = {"security_group_disassociate_invalid": { - "servers": [ - {"id": "2"} - ] - } - } - req.body = json.dumps(body_dict) + req.body = json.dumps(body) response = req.get_response(fakes.wsgi_app()) - self.assertEquals(response.status_int, 422) + self.assertEquals(response.status_int, 400) - def test_disassociate_no_instances(self): - #self.stubs.Set(nova.db.api, 'instance_get', return_server) - self.stubs.Set(nova.db, 'security_group_get', return_security_group) - req = webob.Request.blank('/v1.1/os-security-groups/1/disassociate') + def test_disassociate_security_group_name_with_whitespaces(self): + req = webob.Request.blank('/v1.1/servers/1/action') + body = dict(removeSecurityGroup=dict(name=" ")) + self.stubs.Set(nova.db, 'instance_get', return_server) req.headers['Content-Type'] = 'application/json' req.method = 'POST' - body_dict = {"security_group_disassociate": { - "servers": [ - ] - } - } - req.body = json.dumps(body_dict) + req.body = json.dumps(body) response = req.get_response(fakes.wsgi_app()) self.assertEquals(response.status_int, 400) def test_disassociate_non_existing_instance(self): self.stubs.Set(nova.db, 'instance_get', return_server_nonexistant) - self.stubs.Set(nova.db, 'security_group_get', return_security_group) - req = webob.Request.blank('/v1.1/os-security-groups/1/disassociate') + body = dict(removeSecurityGroup=dict(name="test")) + self.stubs.Set(nova.db, 'security_group_get_by_name', + return_security_group) + req = webob.Request.blank('/v1.1/servers/10000/action') req.headers['Content-Type'] = 'application/json' req.method = 'POST' - body_dict = {"security_group_disassociate": { - "servers": [ - {'id': 2} - ] - } - } - req.body = json.dumps(body_dict) + req.body = json.dumps(body) response = req.get_response(fakes.wsgi_app()) self.assertEquals(response.status_int, 404) def test_disassociate_non_running_instance(self): self.stubs.Set(nova.db, 'instance_get', return_non_running_server) - self.stubs.Set(nova.db, 'security_group_get', - return_security_group_without_instances) - req = webob.Request.blank('/v1.1/os-security-groups/1/disassociate') + self.stubs.Set(nova.db, 'security_group_get_by_name', + return_security_group) + body = dict(removeSecurityGroup=dict(name="test")) + req = webob.Request.blank('/v1.1/servers/1/action') req.headers['Content-Type'] = 'application/json' req.method = 'POST' - body_dict = {"security_group_disassociate": { - "servers": [ - {'id': 1} - ] - } - } - req.body = json.dumps(body_dict) + req.body = json.dumps(body) response = req.get_response(fakes.wsgi_app()) self.assertEquals(response.status_int, 400) - def test_disassociate_not_associated_security_group_to_instance(self): + def test_disassociate_already_associated_security_group_to_instance(self): self.stubs.Set(nova.db, 'instance_get', return_server) - self.stubs.Set(nova.db, 'security_group_get', return_security_group) - req = webob.Request.blank('/v1.1/os-security-groups/1/disassociate') + self.stubs.Set(nova.db, 'security_group_get_by_name', + return_security_group_without_instances) + body = dict(removeSecurityGroup=dict(name="test")) + req = webob.Request.blank('/v1.1/servers/1/action') req.headers['Content-Type'] = 'application/json' req.method = 'POST' - body_dict = {"security_group_disassociate": { - "servers": [ - {'id': 2} - ] - } - } - req.body = json.dumps(body_dict) + req.body = json.dumps(body) response = req.get_response(fakes.wsgi_app()) self.assertEquals(response.status_int, 400) @@ -607,20 +565,34 @@ class TestSecurityGroups(test.TestCase): nova.db.instance_remove_security_group(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()) - self.stubs.Set(nova.db, 'security_group_get', + self.stubs.Set(nova.db, 'security_group_get_by_name', return_security_group) self.mox.ReplayAll() - req = webob.Request.blank('/v1.1/os-security-groups/1/disassociate') + body = dict(removeSecurityGroup=dict(name="test")) + req = webob.Request.blank('/v1.1/servers/1/action') req.headers['Content-Type'] = 'application/json' req.method = 'POST' - body_dict = {"security_group_disassociate": { - "servers": [ - {'id': 1} - ] - } - } - req.body = json.dumps(body_dict) + req.body = json.dumps(body) + response = req.get_response(fakes.wsgi_app()) + self.assertEquals(response.status_int, 202) + + def test_disassociate_xml(self): + self.stubs.Set(nova.db, 'instance_get', return_server) + self.mox.StubOutWithMock(nova.db, 'instance_remove_security_group') + nova.db.instance_remove_security_group(mox.IgnoreArg(), + mox.IgnoreArg(), + mox.IgnoreArg()) + self.stubs.Set(nova.db, 'security_group_get_by_name', + return_security_group) + self.mox.ReplayAll() + + req = webob.Request.blank('/v1.1/servers/1/action') + req.headers['Content-Type'] = 'application/xml' + req.method = 'POST' + req.body = """ + test + """ response = req.get_response(fakes.wsgi_app()) self.assertEquals(response.status_int, 202) -- cgit From 3fd594b0c51f3dcd5bdea252bf365c243864bd8b Mon Sep 17 00:00:00 2001 From: William Wolf Date: Mon, 22 Aug 2011 10:08:48 -0400 Subject: update test_security_group tests that have been added --- .../api/openstack/contrib/test_security_groups.py | 40 +++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/nova/tests/api/openstack/contrib/test_security_groups.py b/nova/tests/api/openstack/contrib/test_security_groups.py index 79cb721f2..bc1536911 100644 --- a/nova/tests/api/openstack/contrib/test_security_groups.py +++ b/nova/tests/api/openstack/contrib/test_security_groups.py @@ -351,7 +351,7 @@ class TestSecurityGroups(test.TestCase): def test_associate_by_non_existing_security_group_name(self): body = dict(addSecurityGroup=dict(name='non-existing')) - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.headers['Content-Type'] = 'application/json' req.method = 'POST' req.body = json.dumps(body) @@ -362,7 +362,7 @@ class TestSecurityGroups(test.TestCase): body = dict(addSecurityGroup=dict(name='test')) self.stubs.Set(nova.db, 'security_group_get_by_name', return_security_group) - req = webob.Request.blank('/v1.1/servers/invalid/action') + req = webob.Request.blank('/v1.1/123/servers/invalid/action') req.headers['Content-Type'] = 'application/json' req.method = 'POST' req.body = json.dumps(body) @@ -370,7 +370,7 @@ class TestSecurityGroups(test.TestCase): self.assertEquals(response.status_int, 400) def test_associate_without_body(self): - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') body = dict(addSecurityGroup=None) self.stubs.Set(nova.db, 'instance_get', return_server) req.headers['Content-Type'] = 'application/json' @@ -380,7 +380,7 @@ class TestSecurityGroups(test.TestCase): self.assertEquals(response.status_int, 400) def test_associate_no_security_group_name(self): - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') body = dict(addSecurityGroup=dict()) self.stubs.Set(nova.db, 'instance_get', return_server) req.headers['Content-Type'] = 'application/json' @@ -390,7 +390,7 @@ class TestSecurityGroups(test.TestCase): self.assertEquals(response.status_int, 400) def test_associate_security_group_name_with_whitespaces(self): - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') body = dict(addSecurityGroup=dict(name=" ")) self.stubs.Set(nova.db, 'instance_get', return_server) req.headers['Content-Type'] = 'application/json' @@ -404,7 +404,7 @@ class TestSecurityGroups(test.TestCase): body = dict(addSecurityGroup=dict(name="test")) self.stubs.Set(nova.db, 'security_group_get_by_name', return_security_group) - req = webob.Request.blank('/v1.1/servers/10000/action') + req = webob.Request.blank('/v1.1/123/servers/10000/action') req.headers['Content-Type'] = 'application/json' req.method = 'POST' req.body = json.dumps(body) @@ -416,7 +416,7 @@ class TestSecurityGroups(test.TestCase): self.stubs.Set(nova.db, 'security_group_get_by_name', return_security_group_without_instances) body = dict(addSecurityGroup=dict(name="test")) - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.headers['Content-Type'] = 'application/json' req.method = 'POST' req.body = json.dumps(body) @@ -428,7 +428,7 @@ class TestSecurityGroups(test.TestCase): self.stubs.Set(nova.db, 'security_group_get_by_name', return_security_group) body = dict(addSecurityGroup=dict(name="test")) - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.headers['Content-Type'] = 'application/json' req.method = 'POST' req.body = json.dumps(body) @@ -446,7 +446,7 @@ class TestSecurityGroups(test.TestCase): self.mox.ReplayAll() body = dict(addSecurityGroup=dict(name="test")) - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.headers['Content-Type'] = 'application/json' req.method = 'POST' req.body = json.dumps(body) @@ -463,7 +463,7 @@ class TestSecurityGroups(test.TestCase): return_security_group_without_instances) self.mox.ReplayAll() - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.headers['Content-Type'] = 'application/xml' req.method = 'POST' req.body = """ @@ -474,7 +474,7 @@ class TestSecurityGroups(test.TestCase): def test_disassociate_by_non_existing_security_group_name(self): body = dict(removeSecurityGroup=dict(name='non-existing')) - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.headers['Content-Type'] = 'application/json' req.method = 'POST' req.body = json.dumps(body) @@ -485,7 +485,7 @@ class TestSecurityGroups(test.TestCase): body = dict(removeSecurityGroup=dict(name='test')) self.stubs.Set(nova.db, 'security_group_get_by_name', return_security_group) - req = webob.Request.blank('/v1.1/servers/invalid/action') + req = webob.Request.blank('/v1.1/123/servers/invalid/action') req.headers['Content-Type'] = 'application/json' req.method = 'POST' req.body = json.dumps(body) @@ -493,7 +493,7 @@ class TestSecurityGroups(test.TestCase): self.assertEquals(response.status_int, 400) def test_disassociate_without_body(self): - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') body = dict(removeSecurityGroup=None) self.stubs.Set(nova.db, 'instance_get', return_server) req.headers['Content-Type'] = 'application/json' @@ -503,7 +503,7 @@ class TestSecurityGroups(test.TestCase): self.assertEquals(response.status_int, 400) def test_disassociate_no_security_group_name(self): - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') body = dict(removeSecurityGroup=dict()) self.stubs.Set(nova.db, 'instance_get', return_server) req.headers['Content-Type'] = 'application/json' @@ -513,7 +513,7 @@ class TestSecurityGroups(test.TestCase): self.assertEquals(response.status_int, 400) def test_disassociate_security_group_name_with_whitespaces(self): - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') body = dict(removeSecurityGroup=dict(name=" ")) self.stubs.Set(nova.db, 'instance_get', return_server) req.headers['Content-Type'] = 'application/json' @@ -527,7 +527,7 @@ class TestSecurityGroups(test.TestCase): body = dict(removeSecurityGroup=dict(name="test")) self.stubs.Set(nova.db, 'security_group_get_by_name', return_security_group) - req = webob.Request.blank('/v1.1/servers/10000/action') + req = webob.Request.blank('/v1.1/123/servers/10000/action') req.headers['Content-Type'] = 'application/json' req.method = 'POST' req.body = json.dumps(body) @@ -539,7 +539,7 @@ class TestSecurityGroups(test.TestCase): self.stubs.Set(nova.db, 'security_group_get_by_name', return_security_group) body = dict(removeSecurityGroup=dict(name="test")) - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.headers['Content-Type'] = 'application/json' req.method = 'POST' req.body = json.dumps(body) @@ -551,7 +551,7 @@ class TestSecurityGroups(test.TestCase): self.stubs.Set(nova.db, 'security_group_get_by_name', return_security_group_without_instances) body = dict(removeSecurityGroup=dict(name="test")) - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.headers['Content-Type'] = 'application/json' req.method = 'POST' req.body = json.dumps(body) @@ -569,7 +569,7 @@ class TestSecurityGroups(test.TestCase): self.mox.ReplayAll() body = dict(removeSecurityGroup=dict(name="test")) - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.headers['Content-Type'] = 'application/json' req.method = 'POST' req.body = json.dumps(body) @@ -586,7 +586,7 @@ class TestSecurityGroups(test.TestCase): return_security_group) self.mox.ReplayAll() - req = webob.Request.blank('/v1.1/servers/1/action') + req = webob.Request.blank('/v1.1/123/servers/1/action') req.headers['Content-Type'] = 'application/xml' req.method = 'POST' req.body = """ -- cgit From e490e3c612792725970c8b5a697e457153fac827 Mon Sep 17 00:00:00 2001 From: William Wolf Date: Mon, 22 Aug 2011 10:19:01 -0400 Subject: fix test_servers tests --- nova/tests/api/openstack/test_servers.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 64b2349d0..800a2e229 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1600,8 +1600,8 @@ class ServersTest(test.TestCase): self._setup_for_create_instance() # proper local hrefs must start with 'http://localhost/v1.1/' - image_href = 'http://localhost/v1.1/images/2' - flavor_ref = 'http://localhost/flavors/3' + image_href = 'http://localhost/v1.1/123/images/2' + flavor_ref = 'http://localhost/123/flavors/3' access_ipv4 = '1.2.3.4' access_ipv6 = 'fead::1234' expected_flavor = { @@ -1609,7 +1609,7 @@ class ServersTest(test.TestCase): "links": [ { "rel": "bookmark", - "href": 'http://localhost/flavors/3', + "href": 'http://localhost/123/flavors/3', }, ], } @@ -1618,7 +1618,7 @@ class ServersTest(test.TestCase): "links": [ { "rel": "bookmark", - "href": 'http://localhost/images/2', + "href": 'http://localhost/123/images/2', }, ], } @@ -1642,7 +1642,7 @@ class ServersTest(test.TestCase): }, } - req = webob.Request.blank('/v1.1/servers') + req = webob.Request.blank('/v1.1/123/servers') req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" @@ -1665,7 +1665,7 @@ class ServersTest(test.TestCase): # proper local hrefs must start with 'http://localhost/v1.1/' image_href = 'http://localhost/v1.1/images/2' - flavor_ref = 'http://localhost/flavors/3' + flavor_ref = 'http://localhost/123/flavors/3' expected_flavor = { "id": "3", "links": [ @@ -1967,7 +1967,7 @@ class ServersTest(test.TestCase): return_server_with_attributes(name='server_test', access_ipv4='0.0.0.0', access_ipv6='beef::0123')) - req = webob.Request.blank('/v1.1/servers/1') + req = webob.Request.blank('/v1.1/123/servers/1') req.method = 'PUT' req.content_type = 'application/json' body = {'server': { @@ -2000,7 +2000,7 @@ class ServersTest(test.TestCase): def test_update_server_access_ipv4_v1_1(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_with_attributes(access_ipv4='0.0.0.0')) - req = webob.Request.blank('/v1.1/servers/1') + req = webob.Request.blank('/v1.1/123/servers/1') req.method = 'PUT' req.content_type = 'application/json' req.body = json.dumps({'server': {'accessIPv4': '0.0.0.0'}}) @@ -2013,7 +2013,7 @@ class ServersTest(test.TestCase): def test_update_server_access_ipv6_v1_1(self): self.stubs.Set(nova.db.api, 'instance_get', return_server_with_attributes(access_ipv6='beef::0123')) - req = webob.Request.blank('/v1.1/servers/1') + req = webob.Request.blank('/v1.1/123/servers/1') req.method = 'PUT' req.content_type = 'application/json' req.body = json.dumps({'server': {'accessIPv6': 'beef::0123'}}) -- cgit From 8d62d47a1148cc79c0ef0330e0c2d70177ea71c8 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Mon, 22 Aug 2011 16:39:05 -0400 Subject: Stubbed out the database in order to improve tests --- nova/tests/test_nova_manage.py | 42 +++++++++++------------------------------- 1 file changed, 11 insertions(+), 31 deletions(-) diff --git a/nova/tests/test_nova_manage.py b/nova/tests/test_nova_manage.py index 9c6563f14..3a6e48dec 100644 --- a/nova/tests/test_nova_manage.py +++ b/nova/tests/test_nova_manage.py @@ -28,55 +28,35 @@ sys.dont_write_bytecode = True import imp nova_manage = imp.load_source('nova_manage.py', NOVA_MANAGE_PATH) sys.dont_write_bytecode = False +import mox +import stubout -import netaddr from nova import context from nova import db -from nova import flags +from nova import exception from nova import test - -FLAGS = flags.FLAGS +from nova.tests.db import fakes as db_fakes class FixedIpCommandsTestCase(test.TestCase): def setUp(self): super(FixedIpCommandsTestCase, self).setUp() - cidr = '10.0.0.0/24' - net = netaddr.IPNetwork(cidr) - net_info = {'bridge': 'fakebr', - 'bridge_interface': 'fakeeth', - 'dns': FLAGS.flat_network_dns, - 'cidr': cidr, - 'netmask': str(net.netmask), - 'gateway': str(net[1]), - 'broadcast': str(net.broadcast), - 'dhcp_start': str(net[2])} - self.network = db.network_create_safe(context.get_admin_context(), - net_info) - num_ips = len(net) - for index in range(num_ips): - address = str(net[index]) - reserved = (index == 1 or index == 2) - db.fixed_ip_create(context.get_admin_context(), - {'network_id': self.network['id'], - 'address': address, - 'reserved': reserved}) + self.stubs = stubout.StubOutForTesting() + db_fakes.stub_out_db_network_api(self.stubs) self.commands = nova_manage.FixedIpCommands() def tearDown(self): - db.network_delete_safe(context.get_admin_context(), self.network['id']) super(FixedIpCommandsTestCase, self).tearDown() + self.stubs.UnsetAll() def test_reserve(self): - self.commands.reserve('10.0.0.100') + self.commands.reserve('192.168.0.100') address = db.fixed_ip_get_by_address(context.get_admin_context(), - '10.0.0.100') + '192.168.0.100') self.assertEqual(address['reserved'], True) def test_unreserve(self): - db.fixed_ip_update(context.get_admin_context(), '10.0.0.100', - {'reserved': True}) - self.commands.unreserve('10.0.0.100') + self.commands.unreserve('192.168.0.100') address = db.fixed_ip_get_by_address(context.get_admin_context(), - '10.0.0.100') + '192.168.0.100') self.assertEqual(address['reserved'], False) -- cgit From 77f15157c5ca7013df397abc22a8866cce02976d Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Mon, 22 Aug 2011 17:08:11 -0400 Subject: Ensure that reserve and unreserve exit when an address is not found --- bin/nova-manage | 2 ++ nova/tests/test_nova_manage.py | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/bin/nova-manage b/bin/nova-manage index 8e6419c0b..3d9a40afb 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -611,6 +611,8 @@ class FixedIpCommands(object): try: fixed_ip = db.fixed_ip_get_by_address(ctxt, address) + if fixed_ip is None: + raise exception.NotFound('Could not find address') db.fixed_ip_update(ctxt, fixed_ip['address'], {'reserved': reserved}) except exception.NotFound as ex: diff --git a/nova/tests/test_nova_manage.py b/nova/tests/test_nova_manage.py index 3a6e48dec..f5ea68a03 100644 --- a/nova/tests/test_nova_manage.py +++ b/nova/tests/test_nova_manage.py @@ -55,8 +55,18 @@ class FixedIpCommandsTestCase(test.TestCase): '192.168.0.100') self.assertEqual(address['reserved'], True) + def test_reserve_nonexistent_address(self): + self.assertRaises(SystemExit, + self.commands.reserve, + '55.55.55.55') + def test_unreserve(self): self.commands.unreserve('192.168.0.100') address = db.fixed_ip_get_by_address(context.get_admin_context(), '192.168.0.100') self.assertEqual(address['reserved'], False) + + def test_unreserve_nonexistent_address(self): + self.assertRaises(SystemExit, + self.commands.unreserve, + '55.55.55.55') -- cgit