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 d963e25906b75a48c75b6e589deb2a53f75d6ee3 Mon Sep 17 00:00:00 2001 From: Christopher MacGown Date: Fri, 22 Jul 2011 20:29:37 -0700 Subject: Config-Drive happiness, minus smoketest --- Authors | 1 + nova/api/openstack/create_instance_helper.py | 7 +- nova/api/openstack/views/servers.py | 4 + nova/compute/api.py | 21 +++- .../versions/035_add_config_drive_to_instances.py | 43 +++++++ nova/db/sqlalchemy/models.py | 1 + nova/scheduler/simple.py | 1 + nova/tests/api/openstack/test_servers.py | 135 ++++++++++++++++++++- nova/tests/test_compute.py | 14 +++ nova/virt/libvirt.xml.template | 7 ++ nova/virt/libvirt/connection.py | 51 ++++++-- run_tests.sh | 2 +- 12 files changed, 265 insertions(+), 22 deletions(-) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/035_add_config_drive_to_instances.py diff --git a/Authors b/Authors index f3129c26e..51f511996 100644 --- a/Authors +++ b/Authors @@ -16,6 +16,7 @@ Chiradeep Vittal Chmouel Boudjnah Chris Behrens Christian Berendt +Christopher MacGown Chuck Short Cory Wright Dan Prince diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 2654e3c40..fe92bae2e 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -96,6 +96,7 @@ class CreateInstanceHelper(object): locals()) raise faults.Fault(exc.HTTPBadRequest(explanation=msg)) + config_drive = body['server'].get('config_drive') personality = body['server'].get('personality') injected_files = [] @@ -130,6 +131,7 @@ class CreateInstanceHelper(object): extra_values = { 'instance_type': inst_type, 'image_ref': image_href, + 'config_drive': config_drive, 'password': password} return (extra_values, @@ -148,7 +150,8 @@ class CreateInstanceHelper(object): zone_blob=zone_blob, reservation_id=reservation_id, min_count=min_count, - max_count=max_count)) + max_count=max_count, + config_drive=config_drive,)) except quota.QuotaError as error: self._handle_quota_error(error) except exception.ImageNotFound as error: @@ -160,6 +163,8 @@ class CreateInstanceHelper(object): def _handle_quota_error(self, error): """ Reraise quota errors as api-specific http exceptions + + """ if error.code == "OnsetFileLimitExceeded": expl = _("Personality file limit exceeded") diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index ab7e8da61..961932a4e 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -164,6 +164,7 @@ class ViewBuilderV11(ViewBuilder): def _build_extra(self, response, inst): self._build_links(response, inst) + self._build_config_drive(response, inst) def _build_links(self, response, inst): href = self.generate_href(inst["id"]) @@ -182,6 +183,9 @@ class ViewBuilderV11(ViewBuilder): response["server"]["links"] = links + def _build_config_drive(self, response, inst): + response['server']['config_drive'] = inst.get('config_drive') + 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)) diff --git a/nova/compute/api.py b/nova/compute/api.py index 67aa3c20f..8adcbbef6 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -150,7 +150,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, config_drive=None,): """Verify all the input parameters regardless of the provisioning strategy being performed.""" @@ -181,6 +181,11 @@ class API(base.Base): (image_service, image_id) = nova.image.get_image_service(image_href) image = image_service.show(context, image_id) + config_drive_id = None + if config_drive and config_drive is not True: + # config_drive is volume id + config_drive, config_drive_id = None, config_drive + os_type = None if 'properties' in image and 'os_type' in image['properties']: os_type = image['properties']['os_type'] @@ -208,6 +213,8 @@ class API(base.Base): image_service.show(context, kernel_id) if ramdisk_id: image_service.show(context, ramdisk_id) + if config_drive_id: + image_service.show(context, config_drive_id) self.ensure_default_security_group(context) @@ -226,6 +233,8 @@ class API(base.Base): 'image_ref': image_href, 'kernel_id': kernel_id or '', 'ramdisk_id': ramdisk_id or '', + 'config_drive_id': config_drive_id or '', + 'config_drive': config_drive or '', 'state': 0, 'state_description': 'scheduling', 'user_id': context.user_id, @@ -397,7 +406,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, + config_drive=None): """Provision the instances by passing the whole request to the Scheduler for execution. Returns a Reservation ID related to the creation of all of these instances.""" @@ -409,7 +419,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, config_drive) self._ask_scheduler_to_create_instance(context, base_options, instance_type, zone_blob, @@ -426,7 +436,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, + config_drive=None,): """ Provision the instances by sending off a series of single instance requests to the Schedulers. This is fine for trival @@ -447,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, config_drive) block_device_mapping = block_device_mapping or [] instances = [] diff --git a/nova/db/sqlalchemy/migrate_repo/versions/035_add_config_drive_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/035_add_config_drive_to_instances.py new file mode 100644 index 000000000..65ea012dd --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/035_add_config_drive_to_instances.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() + +instances = Table("instances", meta, + Column("id", Integer(), primary_key=True, nullable=False)) +config_drive_column = Column("config_drive", String(255)) # matches image_ref + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + instances.create_column(config_drive_column) + + rows = migrate_engine.execute(instances.select()) + for row in rows: + instance_config_drive = None # pre-existing instances don't have one. + migrate_engine.execute(instances.update()\ + .where(instances.c.id == row[0])\ + .values(config_drive=instance_config_drive)) + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + instances.drop_column(config_drive_column) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index c1150f7ca..73ad1f011 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -238,6 +238,7 @@ class Instance(BASE, NovaBase): uuid = Column(String(36)) root_device_name = Column(String(255)) + config_drive = Column(String(255)) # TODO(vish): see Ewan's email about state improvements, probably # should be in a driver base class or some such diff --git a/nova/scheduler/simple.py b/nova/scheduler/simple.py index fc1b3142a..61c76b35d 100644 --- a/nova/scheduler/simple.py +++ b/nova/scheduler/simple.py @@ -41,6 +41,7 @@ class SimpleScheduler(chance.ChanceScheduler): def _schedule_instance(self, context, instance_id, *_args, **_kwargs): """Picks a host that is up and has the fewest running instances.""" + instance_ref = db.instance_get(context, instance_id) if (instance_ref['availability_zone'] and ':' in instance_ref['availability_zone'] diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 3fc38b73c..6a1eb8c87 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -775,11 +775,14 @@ class ServersTest(test.TestCase): self.assertEqual(res.status_int, 400) self.assertTrue(res.body.find('marker param') > -1) - def _setup_for_create_instance(self): + def _setup_for_create_instance(self, instance_db_stub=None): """Shared implementation for tests below that create instance""" def instance_create(context, inst): - return {'id': 1, 'display_name': 'server_test', - 'uuid': FAKE_UUID} + if instance_db_stub: + return instance_db_stub + else: + return {'id': 1, 'display_name': 'server_test', + 'uuid': FAKE_UUID,} def server_update(context, id, params): return instance_create(context, id) @@ -997,6 +1000,132 @@ class ServersTest(test.TestCase): self.assertEqual(flavor_ref, server['flavorRef']) self.assertEqual(image_href, server['imageRef']) + def test_create_instance_with_config_drive_v1_1(self): + db_stub = {'id': 100, 'display_name': 'config_drive_test', + 'uuid': FAKE_UUID, 'config_drive': True} + self._setup_for_create_instance(instance_db_stub=db_stub) + + image_href = 'http://localhost/v1.1/images/2' + flavor_ref = 'http://localhost/v1.1/flavors/3' + body = { + 'server': { + 'name': 'config_drive_test', + 'imageRef': image_href, + 'flavorRef': flavor_ref, + 'metadata': { + 'hello': 'world', + 'open': 'stack', + }, + 'personality': {}, + 'config_drive': True, + }, + } + + 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, 200) + server = json.loads(res.body)['server'] + self.assertEqual(100, server['id']) + self.assertTrue(server['config_drive']) + + def test_create_instance_with_config_drive_as_id_v1_1(self): + db_stub = {'id': 100, 'display_name': 'config_drive_test', + 'uuid': FAKE_UUID, 'config_drive': 2} + self._setup_for_create_instance(instance_db_stub=db_stub) + + image_href = 'http://localhost/v1.1/images/2' + flavor_ref = 'http://localhost/v1.1/flavors/3' + body = { + 'server': { + 'name': 'config_drive_test', + 'imageRef': image_href, + 'flavorRef': flavor_ref, + 'metadata': { + 'hello': 'world', + 'open': 'stack', + }, + 'personality': {}, + 'config_drive': 2, + }, + } + + 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, 200) + server = json.loads(res.body)['server'] + self.assertEqual(100, server['id']) + self.assertTrue(server['config_drive']) + self.assertEqual(2, server['config_drive']) + + def test_create_instance_with_bad_config_drive_v1_1(self): + db_stub = {'id': 100, 'display_name': 'config_drive_test', + 'uuid': FAKE_UUID, 'config_drive': 'asdf'} + self._setup_for_create_instance(instance_db_stub=db_stub) + + image_href = 'http://localhost/v1.1/images/2' + flavor_ref = 'http://localhost/v1.1/flavors/3' + body = { + 'server': { + 'name': 'config_drive_test', + 'imageRef': image_href, + 'flavorRef': flavor_ref, + 'metadata': { + 'hello': 'world', + 'open': 'stack', + }, + 'personality': {}, + 'config_drive': 'asdf', + }, + } + + 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, 400) + + def test_create_instance_without_config_drive_v1_1(self): + db_stub = {'id': 100, 'display_name': 'config_drive_test', + 'uuid': FAKE_UUID, 'config_drive': None} + self._setup_for_create_instance(instance_db_stub=db_stub) + + image_href = 'http://localhost/v1.1/images/2' + flavor_ref = 'http://localhost/v1.1/flavors/3' + body = { + 'server': { + 'name': 'config_drive_test', + 'imageRef': image_href, + 'flavorRef': flavor_ref, + 'metadata': { + 'hello': 'world', + 'open': 'stack', + }, + 'personality': {}, + 'config_drive': True, + }, + } + + 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, 200) + server = json.loads(res.body)['server'] + self.assertEqual(100, server['id']) + self.assertFalse(server['config_drive']) + def test_create_instance_v1_1_bad_href(self): self._setup_for_create_instance() diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 5d59b628a..bc681706d 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -162,6 +162,20 @@ class ComputeTestCase(test.TestCase): db.security_group_destroy(self.context, group['id']) db.instance_destroy(self.context, ref[0]['id']) + def test_create_instance_associates_config_drive(self): + """Make sure create associates a config drive.""" + + instance_id = self._create_instance(params={'config_drive': True,}) + + try: + self.compute.run_instance(self.context, instance_id) + instances = db.instance_get_all(context.get_admin_context()) + instance = instances[0] + + self.assertTrue(instance.config_drive) + finally: + db.instance_destroy(self.context, instance_id) + def test_default_hostname_generator(self): cases = [(None, 'server_1'), ('Hello, Server!', 'hello_server'), ('<}\x1fh\x10e\x08l\x02l\x05o\x12!{>', 'hello')] diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template index e1a683da8..4422f349f 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -55,6 +55,13 @@ #else + #if $getVar('config', False) + + + + + + #end if #if $getVar('rescue', False) diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 342dea98f..ad97dc796 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -123,6 +123,8 @@ flags.DEFINE_string('qemu_img', 'qemu-img', 'binary to use for qemu-img commands') flags.DEFINE_bool('start_guests_on_host_boot', False, 'Whether to restart guests when the host reboots') +flags.DEFINE_string('default_local_format', None, + 'Default filesystem format for local drives') def get_connection(read_only): @@ -586,6 +588,8 @@ class LibvirtConnection(driver.ComputeDriver): block_device_mapping = block_device_mapping or [] self.firewall_driver.setup_basic_filtering(instance, network_info) self.firewall_driver.prepare_instance_filter(instance, network_info) + + # This is where things actually get built. self._create_image(instance, xml, network_info=network_info, block_device_mapping=block_device_mapping) domain = self._create_new_domain(xml) @@ -763,10 +767,15 @@ class LibvirtConnection(driver.ComputeDriver): if size: disk.extend(target, size) - def _create_local(self, target, local_gb): + def _create_local(self, target, local_size, prefix='G', fs_format=None): """Create a blank image of specified size""" - utils.execute('truncate', target, '-s', "%dG" % local_gb) - # TODO(vish): should we format disk by default? + + if not fs_format: + fs_format = FLAGS.default_local_format + + utils.execute('truncate', target, '-s', "%d%c" % (local_size, prefix)) + if fs_format: + utils.execute('mkfs', '-t', fs_format, target) def _create_image(self, inst, libvirt_xml, suffix='', disk_images=None, network_info=None, block_device_mapping=None): @@ -859,6 +868,18 @@ class LibvirtConnection(driver.ComputeDriver): if FLAGS.libvirt_type == 'lxc': target_partition = None + else: + if inst['config_drive_id']: + fname = '%08x' % int(inst['config_drive_id']) + self._cache_image(fn=self._fetch_image, + target=basepath('config'), + fname=fname, + image_id=inst['config_drive_id'], + user=user, + project=project) + elif inst['config_drive']: + self._create_local(basepath('config'), 64, prefix="M", + fs_format='msdos') # 64MB if inst['key_data']: key = str(inst['key_data']) @@ -903,17 +924,23 @@ class LibvirtConnection(driver.ComputeDriver): searchList=[{'interfaces': nets, 'use_ipv6': FLAGS.use_ipv6}])) - if key or net: + if any(key, net, inst['metadata']): inst_name = inst['name'] - img_id = inst.image_ref - if key: - LOG.info(_('instance %(inst_name)s: injecting key into' - ' image %(img_id)s') % locals()) - if net: - LOG.info(_('instance %(inst_name)s: injecting net into' - ' image %(img_id)s') % locals()) + + if inst['config_drive']: # Should be True or None by now. + injection_path = basepath('config') + img_id = 'config-drive' + else: + injection_path = basepath('disk') + img_id = inst.image_ref + + for injection in ('metadata', 'key', 'net'): + if locals()[injection]: + LOG.info(_('instance %(inst_name)s: injecting ' + '%(injection)s into image %(img_id)s' + % locals())) try: - disk.inject_data(basepath('disk'), key, net, + disk.inject_data(injection_path, key, net, inst['metadata'], partition=target_partition, nbd=FLAGS.use_cow_images) diff --git a/run_tests.sh b/run_tests.sh index b8078e150..5dba410b0 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -59,7 +59,7 @@ function run_tests { ERRSIZE=`wc -l run_tests.log | awk '{print \$1}'` if [ "$ERRSIZE" -lt "40" ]; then - cat run_tests.log + echo cat run_tests.log fi fi return $RESULT -- cgit From af9eab72fb89350d23a5dcd5f64f0ddc0a2a6154 Mon Sep 17 00:00:00 2001 From: Launchpad Translations on behalf of nova-core <> Date: Tue, 26 Jul 2011 04:53:45 +0000 Subject: Launchpad automatic translations update. --- po/pt_BR.po | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/po/pt_BR.po b/po/pt_BR.po index f067a69e0..a166c862b 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: nova\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2011-02-21 10:03-0500\n" -"PO-Revision-Date: 2011-03-24 14:51+0000\n" +"PO-Revision-Date: 2011-07-25 17:40+0000\n" "Last-Translator: msinhore \n" "Language-Team: Brazilian Portuguese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n" +"X-Launchpad-Export-Date: 2011-07-26 04:53+0000\n" "X-Generator: Launchpad (build 13405)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 @@ -36,6 +36,11 @@ msgid "" "Stdout: %(stdout)r\n" "Stderr: %(stderr)r" msgstr "" +"%(description)s\n" +"Comando: %(cmd)s\n" +"Código de saída: %(exit_code)s\n" +"Saída padrão: %(stdout)r\n" +"Erro: %(stderr)r" #: ../nova/exception.py:107 msgid "DB exception wrapped" @@ -420,7 +425,7 @@ msgstr "instância %s: suspendendo" #: ../nova/compute/manager.py:472 #, python-format msgid "instance %s: resuming" -msgstr "" +msgstr "instância %s: resumindo" #: ../nova/compute/manager.py:491 #, python-format @@ -435,12 +440,12 @@ msgstr "instância %s: desbloqueando" #: ../nova/compute/manager.py:513 #, python-format msgid "instance %s: getting locked state" -msgstr "" +msgstr "instância %s: obtendo estado de bloqueio" #: ../nova/compute/manager.py:526 #, python-format msgid "instance %s: reset network" -msgstr "" +msgstr "instância %s: reset da rede" #: ../nova/compute/manager.py:535 ../nova/api/ec2/cloud.py:515 #, python-format @@ -457,6 +462,7 @@ msgstr "instância %s: obtendo console ajax" msgid "" "instance %(instance_id)s: attaching volume %(volume_id)s to %(mountpoint)s" msgstr "" +"instância %(instance_id)s: atachando volume %(volume_id)s para %(mountpoint)s" #. pylint: disable=W0702 #. NOTE(vish): The inline callback eats the exception info so we @@ -466,6 +472,8 @@ msgstr "" #, python-format msgid "instance %(instance_id)s: attach failed %(mountpoint)s, removing" msgstr "" +"instância %(instance_id)s: falha ao atachar ponto de montagem " +"%(mountpoint)s, removendo" #: ../nova/compute/manager.py:585 #, python-format @@ -486,7 +494,7 @@ msgstr "Host %s não está ativo" #: ../nova/scheduler/simple.py:65 msgid "All hosts have too many cores" -msgstr "" +msgstr "Todos os hosts tem muitos núcleos de CPU" #: ../nova/scheduler/simple.py:87 #, python-format @@ -811,7 +819,7 @@ msgstr "Tamanho da imagem %(image)s:%(virtual_size)d" #: ../nova/virt/xenapi/vm_utils.py:332 #, python-format msgid "Glance image %s" -msgstr "" +msgstr "Visão geral da imagem %s" #. we need to invoke a plugin for copying VDI's #. content into proper path @@ -843,7 +851,7 @@ msgstr "Kernel PV no VDI: %s" #: ../nova/virt/xenapi/vm_utils.py:405 #, python-format msgid "Running pygrub against %s" -msgstr "" +msgstr "Rodando pygrub novamente %s" #: ../nova/virt/xenapi/vm_utils.py:411 #, python-format @@ -877,12 +885,12 @@ msgstr "(VM_UTILS) xenapi power_state -> |%s|" #: ../nova/virt/xenapi/vm_utils.py:525 #, python-format msgid "VHD %(vdi_uuid)s has parent %(parent_ref)s" -msgstr "" +msgstr "O VHD %(vdi_uuid)s tem pai %(parent_ref)s" #: ../nova/virt/xenapi/vm_utils.py:542 #, python-format msgid "Re-scanning SR %s" -msgstr "" +msgstr "Re-escaneando SR %s" #: ../nova/virt/xenapi/vm_utils.py:567 #, python-format -- 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 00f47c6637b740ffa890250000e6d02dc557832f Mon Sep 17 00:00:00 2001 From: Launchpad Translations on behalf of nova-core <> Date: Sun, 31 Jul 2011 04:51:55 +0000 Subject: Launchpad automatic translations update. --- po/es.po | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/po/es.po b/po/es.po index eeaf209a9..182ce121c 100644 --- a/po/es.po +++ b/po/es.po @@ -8,20 +8,20 @@ msgstr "" "Project-Id-Version: nova\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2011-02-21 10:03-0500\n" -"PO-Revision-Date: 2011-06-30 16:42+0000\n" -"Last-Translator: David Caro \n" +"PO-Revision-Date: 2011-07-30 19:06+0000\n" +"Last-Translator: Juan Alfredo Salas Santillana \n" "Language-Team: Spanish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n" +"X-Launchpad-Export-Date: 2011-07-31 04:51+0000\n" "X-Generator: Launchpad (build 13405)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 #: ../nova/scheduler/simple.py:122 msgid "No hosts found" -msgstr "No se han encontrado hosts" +msgstr "No se encontro anfitriones." #: ../nova/exception.py:33 msgid "Unexpected error while running command." @@ -2769,7 +2769,7 @@ msgstr "" #: ../nova/auth/ldapdriver.py:478 #, python-format msgid "Group can't be created because user %s doesn't exist" -msgstr "" +msgstr "El grupo no se puede crear porque el usuario %s no existe" #: ../nova/auth/ldapdriver.py:495 #, python-format @@ -2789,18 +2789,20 @@ msgstr "" #: ../nova/auth/ldapdriver.py:513 #, python-format msgid "User %(uid)s is already a member of the group %(group_dn)s" -msgstr "" +msgstr "El usuario %(uid)s es actualmente miembro del grupo %(group_dn)s" #: ../nova/auth/ldapdriver.py:524 #, python-format msgid "" "User %s can't be removed from the group because the user doesn't exist" msgstr "" +"El usuario %s no se pudo borrar de el grupo a causa de que el usuario no " +"existe" #: ../nova/auth/ldapdriver.py:528 #, python-format msgid "User %s is not a member of the group" -msgstr "" +msgstr "El usuario %s no es miembro de el grupo" #: ../nova/auth/ldapdriver.py:542 #, python-format -- cgit From 9fcd2e3719e1f3cd33f10a070e44c015a59817dd Mon Sep 17 00:00:00 2001 From: Launchpad Translations on behalf of nova-core <> Date: Mon, 1 Aug 2011 04:58:49 +0000 Subject: Launchpad automatic translations update. --- po/es.po | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/po/es.po b/po/es.po index 182ce121c..f64fe217f 100644 --- a/po/es.po +++ b/po/es.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: nova\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2011-02-21 10:03-0500\n" -"PO-Revision-Date: 2011-07-30 19:06+0000\n" +"PO-Revision-Date: 2011-08-01 03:23+0000\n" "Last-Translator: Juan Alfredo Salas Santillana \n" "Language-Team: Spanish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-07-31 04:51+0000\n" +"X-Launchpad-Export-Date: 2011-08-01 04:58+0000\n" "X-Generator: Launchpad (build 13405)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 @@ -2625,7 +2625,7 @@ msgstr "" #: ../nova/auth/manager.py:289 #, python-format msgid "User %(uid)s is not a member of project %(pjid)s" -msgstr "" +msgstr "El usuario %(uid)s no es miembro del proyecto %(pjid)s" #: ../nova/auth/manager.py:298 ../nova/auth/manager.py:309 #, python-format @@ -2643,7 +2643,7 @@ msgstr "Debes especificar un proyecto" #: ../nova/auth/manager.py:414 #, python-format msgid "The %s role can not be found" -msgstr "El rol %s no se ha podido encontrar" +msgstr "" #: ../nova/auth/manager.py:416 #, python-format @@ -2673,27 +2673,27 @@ msgstr "" #: ../nova/auth/manager.py:515 #, python-format msgid "Created project %(name)s with manager %(manager_user)s" -msgstr "" +msgstr "Creado el proyecto %(name)s con administrador %(manager_user)s" #: ../nova/auth/manager.py:533 #, python-format msgid "modifying project %s" -msgstr "modificando proyecto %s" +msgstr "Modificando proyecto %s" #: ../nova/auth/manager.py:545 #, python-format msgid "Adding user %(uid)s to project %(pid)s" -msgstr "" +msgstr "Agregando usuario %(uid)s para el proyecto %(pid)s" #: ../nova/auth/manager.py:566 #, python-format msgid "Remove user %(uid)s from project %(pid)s" -msgstr "" +msgstr "Borrar usuario %(uid)s del proyecto %(pid)s" #: ../nova/auth/manager.py:592 #, python-format msgid "Deleting project %s" -msgstr "Eliminando proyecto %s" +msgstr "Borrando proyecto %s" #: ../nova/auth/manager.py:650 #, python-format @@ -2703,7 +2703,7 @@ msgstr "" #: ../nova/auth/manager.py:659 #, python-format msgid "Deleting user %s" -msgstr "Eliminando usuario %s" +msgstr "Borrando usuario %s" #: ../nova/auth/manager.py:669 #, python-format -- cgit From 92ab29ef58dd6dd42bb44ca721ba68cb615ae917 Mon Sep 17 00:00:00 2001 From: Launchpad Translations on behalf of nova-core <> Date: Tue, 2 Aug 2011 04:52:00 +0000 Subject: Launchpad automatic translations update. --- po/es.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/po/es.po b/po/es.po index f64fe217f..05edc00c4 100644 --- a/po/es.po +++ b/po/es.po @@ -14,8 +14,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-08-01 04:58+0000\n" -"X-Generator: Launchpad (build 13405)\n" +"X-Launchpad-Export-Date: 2011-08-02 04:52+0000\n" +"X-Generator: Launchpad (build 13552)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 -- cgit From 26a14fe56708594362f7f17a4872c8797242fb69 Mon Sep 17 00:00:00 2001 From: Launchpad Translations on behalf of nova-core <> Date: Wed, 3 Aug 2011 04:44:31 +0000 Subject: Launchpad automatic translations update. --- po/ast.po | 63 ++------------------------------- po/cs.po | 63 ++------------------------------- po/da.po | 63 ++------------------------------- po/de.po | 81 +++++++++++-------------------------------- po/en_AU.po | 63 ++------------------------------- po/en_GB.po | 84 +++++++++++++------------------------------- po/es.po | 98 ++++++++++++++++++++-------------------------------- po/fr.po | 113 +++++++++++++++++++++++++++--------------------------------- po/it.po | 86 +++++++++++++-------------------------------- po/ja.po | 109 ++++++++++++++++++++++++++------------------------------- po/pt_BR.po | 86 +++++++++++++-------------------------------- po/ru.po | 81 +++++++++++-------------------------------- po/tl.po | 63 ++------------------------------- po/uk.po | 71 ++++++-------------------------------- po/zh_CN.po | 80 +++++++++++------------------------------- po/zh_TW.po | 74 +++++++-------------------------------- 16 files changed, 300 insertions(+), 978 deletions(-) diff --git a/po/ast.po b/po/ast.po index ac6190424..48682ec90 100644 --- a/po/ast.po +++ b/po/ast.po @@ -14,8 +14,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-07-23 05:11+0000\n" -"X-Generator: Launchpad (build 13405)\n" +"X-Launchpad-Export-Date: 2011-08-03 04:43+0000\n" +"X-Generator: Launchpad (build 13573)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -125,33 +125,6 @@ msgstr "" msgid "compute.api::resume %s" msgstr "" -#: ../nova/twistd.py:157 -msgid "Wrong number of arguments." -msgstr "" - -#: ../nova/twistd.py:209 -#, python-format -msgid "pidfile %s does not exist. Daemon not running?\n" -msgstr "" - -#: ../nova/twistd.py:221 -msgid "No such process" -msgstr "" - -#: ../nova/twistd.py:230 ../nova/service.py:224 -#, python-format -msgid "Serving %s" -msgstr "" - -#: ../nova/twistd.py:262 ../nova/service.py:225 -msgid "Full set of FLAGS:" -msgstr "" - -#: ../nova/twistd.py:266 -#, python-format -msgid "Starting %s" -msgstr "" - #: ../nova/virt/xenapi/volumeops.py:48 ../nova/virt/xenapi/volumeops.py:101 #: ../nova/db/sqlalchemy/api.py:731 ../nova/virt/libvirt_conn.py:741 #: ../nova/api/ec2/__init__.py:317 @@ -1778,34 +1751,6 @@ msgstr "" msgid "Got exception: %s" msgstr "" -#: ../nova/compute/monitor.py:259 -#, python-format -msgid "updating %s..." -msgstr "" - -#: ../nova/compute/monitor.py:289 -msgid "unexpected error during update" -msgstr "" - -#: ../nova/compute/monitor.py:356 -#, python-format -msgid "Cannot get blockstats for \"%(disk)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:379 -#, python-format -msgid "Cannot get ifstats for \"%(interface)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:414 -msgid "unexpected exception getting connection" -msgstr "" - -#: ../nova/compute/monitor.py:429 -#, python-format -msgid "Found instance: %s" -msgstr "" - #: ../nova/volume/san.py:67 #, python-format msgid "Could not find iSCSI export for volume %s" @@ -2263,10 +2208,6 @@ msgstr "" msgid "You must implement __call__" msgstr "" -#: ../bin/nova-instancemonitor.py:55 -msgid "Starting instance monitor" -msgstr "" - #: ../bin/nova-dhcpbridge.py:58 msgid "leasing ip" msgstr "" diff --git a/po/cs.po b/po/cs.po index 33f7808a5..07bdf1928 100644 --- a/po/cs.po +++ b/po/cs.po @@ -14,8 +14,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-07-23 05:11+0000\n" -"X-Generator: Launchpad (build 13405)\n" +"X-Launchpad-Export-Date: 2011-08-03 04:43+0000\n" +"X-Generator: Launchpad (build 13573)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -125,33 +125,6 @@ msgstr "" msgid "compute.api::resume %s" msgstr "" -#: ../nova/twistd.py:157 -msgid "Wrong number of arguments." -msgstr "" - -#: ../nova/twistd.py:209 -#, python-format -msgid "pidfile %s does not exist. Daemon not running?\n" -msgstr "" - -#: ../nova/twistd.py:221 -msgid "No such process" -msgstr "" - -#: ../nova/twistd.py:230 ../nova/service.py:224 -#, python-format -msgid "Serving %s" -msgstr "" - -#: ../nova/twistd.py:262 ../nova/service.py:225 -msgid "Full set of FLAGS:" -msgstr "" - -#: ../nova/twistd.py:266 -#, python-format -msgid "Starting %s" -msgstr "" - #: ../nova/virt/xenapi/volumeops.py:48 ../nova/virt/xenapi/volumeops.py:101 #: ../nova/db/sqlalchemy/api.py:731 ../nova/virt/libvirt_conn.py:741 #: ../nova/api/ec2/__init__.py:317 @@ -1778,34 +1751,6 @@ msgstr "" msgid "Got exception: %s" msgstr "" -#: ../nova/compute/monitor.py:259 -#, python-format -msgid "updating %s..." -msgstr "" - -#: ../nova/compute/monitor.py:289 -msgid "unexpected error during update" -msgstr "" - -#: ../nova/compute/monitor.py:356 -#, python-format -msgid "Cannot get blockstats for \"%(disk)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:379 -#, python-format -msgid "Cannot get ifstats for \"%(interface)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:414 -msgid "unexpected exception getting connection" -msgstr "" - -#: ../nova/compute/monitor.py:429 -#, python-format -msgid "Found instance: %s" -msgstr "" - #: ../nova/volume/san.py:67 #, python-format msgid "Could not find iSCSI export for volume %s" @@ -2265,10 +2210,6 @@ msgstr "" msgid "You must implement __call__" msgstr "" -#: ../bin/nova-instancemonitor.py:55 -msgid "Starting instance monitor" -msgstr "" - #: ../bin/nova-dhcpbridge.py:58 msgid "leasing ip" msgstr "" diff --git a/po/da.po b/po/da.po index 5aead53c3..0b379c9d7 100644 --- a/po/da.po +++ b/po/da.po @@ -14,8 +14,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n" -"X-Generator: Launchpad (build 13405)\n" +"X-Launchpad-Export-Date: 2011-08-03 04:43+0000\n" +"X-Generator: Launchpad (build 13573)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -125,33 +125,6 @@ msgstr "" msgid "compute.api::resume %s" msgstr "" -#: ../nova/twistd.py:157 -msgid "Wrong number of arguments." -msgstr "" - -#: ../nova/twistd.py:209 -#, python-format -msgid "pidfile %s does not exist. Daemon not running?\n" -msgstr "" - -#: ../nova/twistd.py:221 -msgid "No such process" -msgstr "" - -#: ../nova/twistd.py:230 ../nova/service.py:224 -#, python-format -msgid "Serving %s" -msgstr "" - -#: ../nova/twistd.py:262 ../nova/service.py:225 -msgid "Full set of FLAGS:" -msgstr "" - -#: ../nova/twistd.py:266 -#, python-format -msgid "Starting %s" -msgstr "" - #: ../nova/virt/xenapi/volumeops.py:48 ../nova/virt/xenapi/volumeops.py:101 #: ../nova/db/sqlalchemy/api.py:731 ../nova/virt/libvirt_conn.py:741 #: ../nova/api/ec2/__init__.py:317 @@ -1778,34 +1751,6 @@ msgstr "" msgid "Got exception: %s" msgstr "" -#: ../nova/compute/monitor.py:259 -#, python-format -msgid "updating %s..." -msgstr "" - -#: ../nova/compute/monitor.py:289 -msgid "unexpected error during update" -msgstr "" - -#: ../nova/compute/monitor.py:356 -#, python-format -msgid "Cannot get blockstats for \"%(disk)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:379 -#, python-format -msgid "Cannot get ifstats for \"%(interface)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:414 -msgid "unexpected exception getting connection" -msgstr "" - -#: ../nova/compute/monitor.py:429 -#, python-format -msgid "Found instance: %s" -msgstr "" - #: ../nova/volume/san.py:67 #, python-format msgid "Could not find iSCSI export for volume %s" @@ -2263,10 +2208,6 @@ msgstr "" msgid "You must implement __call__" msgstr "" -#: ../bin/nova-instancemonitor.py:55 -msgid "Starting instance monitor" -msgstr "" - #: ../bin/nova-dhcpbridge.py:58 msgid "leasing ip" msgstr "" diff --git a/po/de.po b/po/de.po index cf40a0f5e..1f652c373 100644 --- a/po/de.po +++ b/po/de.po @@ -14,8 +14,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n" -"X-Generator: Launchpad (build 13405)\n" +"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n" +"X-Generator: Launchpad (build 13573)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -131,33 +131,6 @@ msgstr "" msgid "compute.api::resume %s" msgstr "" -#: ../nova/twistd.py:157 -msgid "Wrong number of arguments." -msgstr "" - -#: ../nova/twistd.py:209 -#, python-format -msgid "pidfile %s does not exist. Daemon not running?\n" -msgstr "PID-Datei %s existiert nicht. Läuft der Daemon nicht?\n" - -#: ../nova/twistd.py:221 -msgid "No such process" -msgstr "Kein passender Prozess gefunden" - -#: ../nova/twistd.py:230 ../nova/service.py:224 -#, python-format -msgid "Serving %s" -msgstr "Bedient %s" - -#: ../nova/twistd.py:262 ../nova/service.py:225 -msgid "Full set of FLAGS:" -msgstr "Alle vorhandenen FLAGS:" - -#: ../nova/twistd.py:266 -#, python-format -msgid "Starting %s" -msgstr "%s wird gestartet" - #: ../nova/virt/xenapi/volumeops.py:48 ../nova/virt/xenapi/volumeops.py:101 #: ../nova/db/sqlalchemy/api.py:731 ../nova/virt/libvirt_conn.py:741 #: ../nova/api/ec2/__init__.py:317 @@ -1785,34 +1758,6 @@ msgstr "" msgid "Got exception: %s" msgstr "" -#: ../nova/compute/monitor.py:259 -#, python-format -msgid "updating %s..." -msgstr "" - -#: ../nova/compute/monitor.py:289 -msgid "unexpected error during update" -msgstr "" - -#: ../nova/compute/monitor.py:356 -#, python-format -msgid "Cannot get blockstats for \"%(disk)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:379 -#, python-format -msgid "Cannot get ifstats for \"%(interface)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:414 -msgid "unexpected exception getting connection" -msgstr "" - -#: ../nova/compute/monitor.py:429 -#, python-format -msgid "Found instance: %s" -msgstr "" - #: ../nova/volume/san.py:67 #, python-format msgid "Could not find iSCSI export for volume %s" @@ -2270,10 +2215,6 @@ msgstr "" msgid "You must implement __call__" msgstr "" -#: ../bin/nova-instancemonitor.py:55 -msgid "Starting instance monitor" -msgstr "" - #: ../bin/nova-dhcpbridge.py:58 msgid "leasing ip" msgstr "" @@ -2892,3 +2833,21 @@ msgstr "" #~ msgid "Data store %s is unreachable. Trying again in %d seconds." #~ msgstr "" #~ "Datastore %s ist nicht erreichbar. Versuche es erneut in %d Sekunden." + +#~ msgid "Full set of FLAGS:" +#~ msgstr "Alle vorhandenen FLAGS:" + +#, python-format +#~ msgid "pidfile %s does not exist. Daemon not running?\n" +#~ msgstr "PID-Datei %s existiert nicht. Läuft der Daemon nicht?\n" + +#, python-format +#~ msgid "Starting %s" +#~ msgstr "%s wird gestartet" + +#~ msgid "No such process" +#~ msgstr "Kein passender Prozess gefunden" + +#, python-format +#~ msgid "Serving %s" +#~ msgstr "Bedient %s" diff --git a/po/en_AU.po b/po/en_AU.po index e53f9fc07..a51b9ff2d 100644 --- a/po/en_AU.po +++ b/po/en_AU.po @@ -14,8 +14,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n" -"X-Generator: Launchpad (build 13405)\n" +"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n" +"X-Generator: Launchpad (build 13573)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -125,33 +125,6 @@ msgstr "" msgid "compute.api::resume %s" msgstr "" -#: ../nova/twistd.py:157 -msgid "Wrong number of arguments." -msgstr "" - -#: ../nova/twistd.py:209 -#, python-format -msgid "pidfile %s does not exist. Daemon not running?\n" -msgstr "" - -#: ../nova/twistd.py:221 -msgid "No such process" -msgstr "" - -#: ../nova/twistd.py:230 ../nova/service.py:224 -#, python-format -msgid "Serving %s" -msgstr "" - -#: ../nova/twistd.py:262 ../nova/service.py:225 -msgid "Full set of FLAGS:" -msgstr "" - -#: ../nova/twistd.py:266 -#, python-format -msgid "Starting %s" -msgstr "" - #: ../nova/virt/xenapi/volumeops.py:48 ../nova/virt/xenapi/volumeops.py:101 #: ../nova/db/sqlalchemy/api.py:731 ../nova/virt/libvirt_conn.py:741 #: ../nova/api/ec2/__init__.py:317 @@ -1778,34 +1751,6 @@ msgstr "" msgid "Got exception: %s" msgstr "" -#: ../nova/compute/monitor.py:259 -#, python-format -msgid "updating %s..." -msgstr "" - -#: ../nova/compute/monitor.py:289 -msgid "unexpected error during update" -msgstr "" - -#: ../nova/compute/monitor.py:356 -#, python-format -msgid "Cannot get blockstats for \"%(disk)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:379 -#, python-format -msgid "Cannot get ifstats for \"%(interface)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:414 -msgid "unexpected exception getting connection" -msgstr "" - -#: ../nova/compute/monitor.py:429 -#, python-format -msgid "Found instance: %s" -msgstr "" - #: ../nova/volume/san.py:67 #, python-format msgid "Could not find iSCSI export for volume %s" @@ -2263,10 +2208,6 @@ msgstr "" msgid "You must implement __call__" msgstr "" -#: ../bin/nova-instancemonitor.py:55 -msgid "Starting instance monitor" -msgstr "" - #: ../bin/nova-dhcpbridge.py:58 msgid "leasing ip" msgstr "" diff --git a/po/en_GB.po b/po/en_GB.po index 601f6170b..59247f4fa 100644 --- a/po/en_GB.po +++ b/po/en_GB.po @@ -14,8 +14,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n" -"X-Generator: Launchpad (build 13405)\n" +"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n" +"X-Generator: Launchpad (build 13573)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -130,33 +130,6 @@ msgstr "compute.api::suspend %s" msgid "compute.api::resume %s" msgstr "compute.api::resume %s" -#: ../nova/twistd.py:157 -msgid "Wrong number of arguments." -msgstr "Wrong number of arguments." - -#: ../nova/twistd.py:209 -#, python-format -msgid "pidfile %s does not exist. Daemon not running?\n" -msgstr "pidfile %s does not exist. Daemon not running?\n" - -#: ../nova/twistd.py:221 -msgid "No such process" -msgstr "No such process" - -#: ../nova/twistd.py:230 ../nova/service.py:224 -#, python-format -msgid "Serving %s" -msgstr "Serving %s" - -#: ../nova/twistd.py:262 ../nova/service.py:225 -msgid "Full set of FLAGS:" -msgstr "Full set of FLAGS:" - -#: ../nova/twistd.py:266 -#, python-format -msgid "Starting %s" -msgstr "Starting %s" - #: ../nova/virt/xenapi/volumeops.py:48 ../nova/virt/xenapi/volumeops.py:101 #: ../nova/db/sqlalchemy/api.py:731 ../nova/virt/libvirt_conn.py:741 #: ../nova/api/ec2/__init__.py:317 @@ -1803,34 +1776,6 @@ msgstr "" msgid "Got exception: %s" msgstr "" -#: ../nova/compute/monitor.py:259 -#, python-format -msgid "updating %s..." -msgstr "" - -#: ../nova/compute/monitor.py:289 -msgid "unexpected error during update" -msgstr "" - -#: ../nova/compute/monitor.py:356 -#, python-format -msgid "Cannot get blockstats for \"%(disk)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:379 -#, python-format -msgid "Cannot get ifstats for \"%(interface)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:414 -msgid "unexpected exception getting connection" -msgstr "" - -#: ../nova/compute/monitor.py:429 -#, python-format -msgid "Found instance: %s" -msgstr "" - #: ../nova/volume/san.py:67 #, python-format msgid "Could not find iSCSI export for volume %s" @@ -2288,10 +2233,6 @@ msgstr "" msgid "You must implement __call__" msgstr "" -#: ../bin/nova-instancemonitor.py:55 -msgid "Starting instance monitor" -msgstr "" - #: ../bin/nova-dhcpbridge.py:58 msgid "leasing ip" msgstr "" @@ -2871,3 +2812,24 @@ msgstr "" #, python-format msgid "Removing user %(user)s from project %(project)s" msgstr "" + +#~ msgid "Wrong number of arguments." +#~ msgstr "Wrong number of arguments." + +#~ msgid "No such process" +#~ msgstr "No such process" + +#~ msgid "Full set of FLAGS:" +#~ msgstr "Full set of FLAGS:" + +#, python-format +#~ msgid "pidfile %s does not exist. Daemon not running?\n" +#~ msgstr "pidfile %s does not exist. Daemon not running?\n" + +#, python-format +#~ msgid "Starting %s" +#~ msgstr "Starting %s" + +#, python-format +#~ msgid "Serving %s" +#~ msgstr "Serving %s" diff --git a/po/es.po b/po/es.po index 05edc00c4..7371eae8c 100644 --- a/po/es.po +++ b/po/es.po @@ -14,8 +14,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-08-02 04:52+0000\n" -"X-Generator: Launchpad (build 13552)\n" +"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n" +"X-Generator: Launchpad (build 13573)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -130,33 +130,6 @@ msgstr "compute.api::suspend %s" msgid "compute.api::resume %s" msgstr "compute.api::resume %s" -#: ../nova/twistd.py:157 -msgid "Wrong number of arguments." -msgstr "Cantidad de argumentos incorrecta" - -#: ../nova/twistd.py:209 -#, python-format -msgid "pidfile %s does not exist. Daemon not running?\n" -msgstr "El \"pidfile\" %s no existe. Quizás el servicio no este corriendo.\n" - -#: ../nova/twistd.py:221 -msgid "No such process" -msgstr "No existe el proceso" - -#: ../nova/twistd.py:230 ../nova/service.py:224 -#, python-format -msgid "Serving %s" -msgstr "Sirviendo %s" - -#: ../nova/twistd.py:262 ../nova/service.py:225 -msgid "Full set of FLAGS:" -msgstr "Conjunto completo de opciones (FLAGS):" - -#: ../nova/twistd.py:266 -#, python-format -msgid "Starting %s" -msgstr "Iniciando %s" - #: ../nova/virt/xenapi/volumeops.py:48 ../nova/virt/xenapi/volumeops.py:101 #: ../nova/db/sqlalchemy/api.py:731 ../nova/virt/libvirt_conn.py:741 #: ../nova/api/ec2/__init__.py:317 @@ -1819,34 +1792,6 @@ msgstr "" msgid "Got exception: %s" msgstr "Obtenida excepción %s" -#: ../nova/compute/monitor.py:259 -#, python-format -msgid "updating %s..." -msgstr "actualizando %s..." - -#: ../nova/compute/monitor.py:289 -msgid "unexpected error during update" -msgstr "error inesperado durante la actualización" - -#: ../nova/compute/monitor.py:356 -#, python-format -msgid "Cannot get blockstats for \"%(disk)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:379 -#, python-format -msgid "Cannot get ifstats for \"%(interface)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:414 -msgid "unexpected exception getting connection" -msgstr "excepción inexperada al obtener la conexión" - -#: ../nova/compute/monitor.py:429 -#, python-format -msgid "Found instance: %s" -msgstr "Encontrada interfaz: %s" - #: ../nova/volume/san.py:67 #, python-format msgid "Could not find iSCSI export for volume %s" @@ -2309,10 +2254,6 @@ msgstr "" msgid "You must implement __call__" msgstr "" -#: ../bin/nova-instancemonitor.py:55 -msgid "Starting instance monitor" -msgstr "" - #: ../bin/nova-dhcpbridge.py:58 msgid "leasing ip" msgstr "" @@ -2938,6 +2879,10 @@ msgstr "Eliminando el usuario %(user)s del proyecto %(project)s" #~ msgstr "" #~ "El almacen de datos %s es inalcanzable. Reintentandolo en %d segundos." +#, python-format +#~ msgid "Serving %s" +#~ msgstr "Sirviendo %s" + #, python-format #~ msgid "Couldn't get IP, using 127.0.0.1 %s" #~ msgstr "No puedo obtener IP, usando 127.0.0.1 %s" @@ -3098,10 +3043,24 @@ msgstr "Eliminando el usuario %(user)s del proyecto %(project)s" #~ msgid "Detach volume %s from mountpoint %s on instance %s" #~ msgstr "Desvinculando volumen %s del punto de montaje %s en la instancia %s" +#~ msgid "unexpected exception getting connection" +#~ msgstr "excepción inexperada al obtener la conexión" + +#~ msgid "unexpected error during update" +#~ msgstr "error inesperado durante la actualización" + #, python-format #~ msgid "Cannot get blockstats for \"%s\" on \"%s\"" #~ msgstr "No puedo obtener estadísticas del bloque para \"%s\" en \"%s\"" +#, python-format +#~ msgid "updating %s..." +#~ msgstr "actualizando %s..." + +#, python-format +#~ msgid "Found instance: %s" +#~ msgstr "Encontrada interfaz: %s" + #, python-format #~ msgid "Cannot get ifstats for \"%s\" on \"%s\"" #~ msgstr "No puedo obtener estadísticas de la interfaz para \"%s\" en \"%s\"" @@ -3380,3 +3339,20 @@ msgstr "Eliminando el usuario %(user)s del proyecto %(project)s" #, python-format #~ msgid "Spawning VM %s created %s." #~ msgstr "Iniciando VM %s creado %s." + +#~ msgid "No such process" +#~ msgstr "No existe el proceso" + +#~ msgid "Full set of FLAGS:" +#~ msgstr "Conjunto completo de opciones (FLAGS):" + +#~ msgid "Wrong number of arguments." +#~ msgstr "Cantidad de argumentos incorrecta" + +#, python-format +#~ msgid "pidfile %s does not exist. Daemon not running?\n" +#~ msgstr "El \"pidfile\" %s no existe. Quizás el servicio no este corriendo.\n" + +#, python-format +#~ msgid "Starting %s" +#~ msgstr "Iniciando %s" diff --git a/po/fr.po b/po/fr.po index 9dd789b3c..7cb298a94 100644 --- a/po/fr.po +++ b/po/fr.po @@ -14,8 +14,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n" -"X-Generator: Launchpad (build 13405)\n" +"X-Launchpad-Export-Date: 2011-08-03 04:43+0000\n" +"X-Generator: Launchpad (build 13573)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -133,35 +133,6 @@ msgstr "compute.api::suspend %s" msgid "compute.api::resume %s" msgstr "compute.api::resume %s" -#: ../nova/twistd.py:157 -msgid "Wrong number of arguments." -msgstr "Nombre d'arguments incorrect." - -#: ../nova/twistd.py:209 -#, python-format -msgid "pidfile %s does not exist. Daemon not running?\n" -msgstr "" -"Le fichier pid %s n'existe pas. Est-ce que le processus est en cours " -"d'exécution ?\n" - -#: ../nova/twistd.py:221 -msgid "No such process" -msgstr "Aucun processus de ce type" - -#: ../nova/twistd.py:230 ../nova/service.py:224 -#, python-format -msgid "Serving %s" -msgstr "En train de servir %s" - -#: ../nova/twistd.py:262 ../nova/service.py:225 -msgid "Full set of FLAGS:" -msgstr "Ensemble de propriétés complet :" - -#: ../nova/twistd.py:266 -#, python-format -msgid "Starting %s" -msgstr "Démarrage de %s" - #: ../nova/virt/xenapi/volumeops.py:48 ../nova/virt/xenapi/volumeops.py:101 #: ../nova/db/sqlalchemy/api.py:731 ../nova/virt/libvirt_conn.py:741 #: ../nova/api/ec2/__init__.py:317 @@ -1865,34 +1836,6 @@ msgstr "Tâche [%(name)s] %(task)s état : %(status)s %(error_info)s" msgid "Got exception: %s" msgstr "Reçu exception : %s" -#: ../nova/compute/monitor.py:259 -#, python-format -msgid "updating %s..." -msgstr "mise à jour %s..." - -#: ../nova/compute/monitor.py:289 -msgid "unexpected error during update" -msgstr "erreur inopinée pendant la ise à jour" - -#: ../nova/compute/monitor.py:356 -#, python-format -msgid "Cannot get blockstats for \"%(disk)s\" on \"%(iid)s\"" -msgstr "Ne peut pas récupérer blockstats pour \"%(disk)s\" sur \"%(iid)s\"" - -#: ../nova/compute/monitor.py:379 -#, python-format -msgid "Cannot get ifstats for \"%(interface)s\" on \"%(iid)s\"" -msgstr "Ne peut pas récupérer ifstats pour \"%(interface)s\" sur \"%(iid)s\"" - -#: ../nova/compute/monitor.py:414 -msgid "unexpected exception getting connection" -msgstr "erreur inopinée pendant la connexion" - -#: ../nova/compute/monitor.py:429 -#, python-format -msgid "Found instance: %s" -msgstr "Instance trouvée : %s" - #: ../nova/volume/san.py:67 #, python-format msgid "Could not find iSCSI export for volume %s" @@ -2373,10 +2316,6 @@ msgstr "Démarrage %(arg0)s sur %(host)s:%(port)s" msgid "You must implement __call__" msgstr "Vous devez implémenter __call__" -#: ../bin/nova-instancemonitor.py:55 -msgid "Starting instance monitor" -msgstr "Démarrage du superviseur d'instance" - #: ../bin/nova-dhcpbridge.py:58 msgid "leasing ip" msgstr "Allocation IP" @@ -2990,3 +2929,51 @@ msgstr "Ajout de l'utilisateur %(user)s au projet %(project)s" #, python-format msgid "Removing user %(user)s from project %(project)s" msgstr "Suppression de l'utilisateur %(user)s du projet %(project)s" + +#~ msgid "Wrong number of arguments." +#~ msgstr "Nombre d'arguments incorrect." + +#~ msgid "No such process" +#~ msgstr "Aucun processus de ce type" + +#, python-format +#~ msgid "Starting %s" +#~ msgstr "Démarrage de %s" + +#~ msgid "Full set of FLAGS:" +#~ msgstr "Ensemble de propriétés complet :" + +#, python-format +#~ msgid "pidfile %s does not exist. Daemon not running?\n" +#~ msgstr "" +#~ "Le fichier pid %s n'existe pas. Est-ce que le processus est en cours " +#~ "d'exécution ?\n" + +#, python-format +#~ msgid "Serving %s" +#~ msgstr "En train de servir %s" + +#, python-format +#~ msgid "Cannot get blockstats for \"%(disk)s\" on \"%(iid)s\"" +#~ msgstr "Ne peut pas récupérer blockstats pour \"%(disk)s\" sur \"%(iid)s\"" + +#, python-format +#~ msgid "Cannot get ifstats for \"%(interface)s\" on \"%(iid)s\"" +#~ msgstr "Ne peut pas récupérer ifstats pour \"%(interface)s\" sur \"%(iid)s\"" + +#~ msgid "unexpected error during update" +#~ msgstr "erreur inopinée pendant la ise à jour" + +#, python-format +#~ msgid "updating %s..." +#~ msgstr "mise à jour %s..." + +#, python-format +#~ msgid "Found instance: %s" +#~ msgstr "Instance trouvée : %s" + +#~ msgid "unexpected exception getting connection" +#~ msgstr "erreur inopinée pendant la connexion" + +#~ msgid "Starting instance monitor" +#~ msgstr "Démarrage du superviseur d'instance" diff --git a/po/it.po b/po/it.po index 60c3a388a..14e9a6de6 100644 --- a/po/it.po +++ b/po/it.po @@ -14,8 +14,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n" -"X-Generator: Launchpad (build 13405)\n" +"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n" +"X-Generator: Launchpad (build 13573)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -134,34 +134,6 @@ msgstr "compute.api::suspend %s" msgid "compute.api::resume %s" msgstr "compute.api::resume %s" -#: ../nova/twistd.py:157 -msgid "Wrong number of arguments." -msgstr "Numero errato di argomenti" - -#: ../nova/twistd.py:209 -#, python-format -msgid "pidfile %s does not exist. Daemon not running?\n" -msgstr "" -"Il pidfile %s non esiste. Assicurarsi che il demone é in esecuzione.\n" - -#: ../nova/twistd.py:221 -msgid "No such process" -msgstr "Nessun processo trovato" - -#: ../nova/twistd.py:230 ../nova/service.py:224 -#, python-format -msgid "Serving %s" -msgstr "Servire %s" - -#: ../nova/twistd.py:262 ../nova/service.py:225 -msgid "Full set of FLAGS:" -msgstr "Insieme di FLAGS:" - -#: ../nova/twistd.py:266 -#, python-format -msgid "Starting %s" -msgstr "Avvio di %s" - #: ../nova/virt/xenapi/volumeops.py:48 ../nova/virt/xenapi/volumeops.py:101 #: ../nova/db/sqlalchemy/api.py:731 ../nova/virt/libvirt_conn.py:741 #: ../nova/api/ec2/__init__.py:317 @@ -1791,34 +1763,6 @@ msgstr "" msgid "Got exception: %s" msgstr "" -#: ../nova/compute/monitor.py:259 -#, python-format -msgid "updating %s..." -msgstr "" - -#: ../nova/compute/monitor.py:289 -msgid "unexpected error during update" -msgstr "" - -#: ../nova/compute/monitor.py:356 -#, python-format -msgid "Cannot get blockstats for \"%(disk)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:379 -#, python-format -msgid "Cannot get ifstats for \"%(interface)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:414 -msgid "unexpected exception getting connection" -msgstr "" - -#: ../nova/compute/monitor.py:429 -#, python-format -msgid "Found instance: %s" -msgstr "" - #: ../nova/volume/san.py:67 #, python-format msgid "Could not find iSCSI export for volume %s" @@ -2278,10 +2222,6 @@ msgstr "" msgid "You must implement __call__" msgstr "" -#: ../bin/nova-instancemonitor.py:55 -msgid "Starting instance monitor" -msgstr "" - #: ../bin/nova-dhcpbridge.py:58 msgid "leasing ip" msgstr "" @@ -2896,3 +2836,25 @@ msgstr "" #, python-format #~ msgid "Data store %s is unreachable. Trying again in %d seconds." #~ msgstr "Datastore %s é irrangiungibile. Riprovare in %d seconds." + +#~ msgid "Full set of FLAGS:" +#~ msgstr "Insieme di FLAGS:" + +#, python-format +#~ msgid "pidfile %s does not exist. Daemon not running?\n" +#~ msgstr "" +#~ "Il pidfile %s non esiste. Assicurarsi che il demone é in esecuzione.\n" + +#, python-format +#~ msgid "Starting %s" +#~ msgstr "Avvio di %s" + +#, python-format +#~ msgid "Serving %s" +#~ msgstr "Servire %s" + +#~ msgid "Wrong number of arguments." +#~ msgstr "Numero errato di argomenti" + +#~ msgid "No such process" +#~ msgstr "Nessun processo trovato" diff --git a/po/ja.po b/po/ja.po index 09d17af3b..179302b55 100644 --- a/po/ja.po +++ b/po/ja.po @@ -14,8 +14,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n" -"X-Generator: Launchpad (build 13405)\n" +"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n" +"X-Generator: Launchpad (build 13573)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -130,33 +130,6 @@ msgstr "例外: compute.api::suspend %s" msgid "compute.api::resume %s" msgstr "例外: compute.api::resume %s" -#: ../nova/twistd.py:157 -msgid "Wrong number of arguments." -msgstr "引数の数が異なります。" - -#: ../nova/twistd.py:209 -#, python-format -msgid "pidfile %s does not exist. Daemon not running?\n" -msgstr "pidfile %s が存在しません。デーモンは実行中ですか?\n" - -#: ../nova/twistd.py:221 -msgid "No such process" -msgstr "そのようなプロセスはありません" - -#: ../nova/twistd.py:230 ../nova/service.py:224 -#, python-format -msgid "Serving %s" -msgstr "%s サービスの開始" - -#: ../nova/twistd.py:262 ../nova/service.py:225 -msgid "Full set of FLAGS:" -msgstr "FLAGSの一覧:" - -#: ../nova/twistd.py:266 -#, python-format -msgid "Starting %s" -msgstr "%s を起動中" - #: ../nova/virt/xenapi/volumeops.py:48 ../nova/virt/xenapi/volumeops.py:101 #: ../nova/db/sqlalchemy/api.py:731 ../nova/virt/libvirt_conn.py:741 #: ../nova/api/ec2/__init__.py:317 @@ -1808,34 +1781,6 @@ msgstr "タスク [%(name)s] %(task)s 状態: %(status)s %(error_info)s" msgid "Got exception: %s" msgstr "例外 %s が発生しました。" -#: ../nova/compute/monitor.py:259 -#, python-format -msgid "updating %s..." -msgstr "%s の情報の更新…" - -#: ../nova/compute/monitor.py:289 -msgid "unexpected error during update" -msgstr "更新の最中に予期しないエラーが発生しました。" - -#: ../nova/compute/monitor.py:356 -#, python-format -msgid "Cannot get blockstats for \"%(disk)s\" on \"%(iid)s\"" -msgstr "\"%(iid)s\" 上の \"%(disk)s\" 用のブロック統計(blockstats)が取得できません" - -#: ../nova/compute/monitor.py:379 -#, python-format -msgid "Cannot get ifstats for \"%(interface)s\" on \"%(iid)s\"" -msgstr "\"%(iid)s\" 上の %(interface)s\" 用インターフェース統計(ifstats)が取得できません" - -#: ../nova/compute/monitor.py:414 -msgid "unexpected exception getting connection" -msgstr "接続に際し予期しないエラーが発生しました。" - -#: ../nova/compute/monitor.py:429 -#, python-format -msgid "Found instance: %s" -msgstr "インスタンス %s が見つかりました。" - #: ../nova/volume/san.py:67 #, python-format msgid "Could not find iSCSI export for volume %s" @@ -2311,10 +2256,6 @@ msgstr "%(host)s:%(port)s 上で %(arg0)s を開始しています" msgid "You must implement __call__" msgstr "__call__ を実装しなければなりません" -#: ../bin/nova-instancemonitor.py:55 -msgid "Starting instance monitor" -msgstr "インスタンスモニタを開始しています" - #: ../bin/nova-dhcpbridge.py:58 msgid "leasing ip" msgstr "IP アドレスをリースしました" @@ -2937,6 +2878,17 @@ msgstr "ユーザ %(user)s をプロジェクト %(project)s から削除しま #~ msgid "Data store %s is unreachable. Trying again in %d seconds." #~ msgstr "データストア %s に接続できません。 %d 秒後に再接続します。" +#, python-format +#~ msgid "Serving %s" +#~ msgstr "%s サービスの開始" + +#~ msgid "Full set of FLAGS:" +#~ msgstr "FLAGSの一覧:" + +#, python-format +#~ msgid "pidfile %s does not exist. Daemon not running?\n" +#~ msgstr "pidfile %s が存在しません。デーモンは実行中ですか?\n" + #, python-format #~ msgid "Couldn't get IP, using 127.0.0.1 %s" #~ msgstr "IPを取得できません。127.0.0.1 を %s として使います。" @@ -3097,6 +3049,13 @@ msgstr "ユーザ %(user)s をプロジェクト %(project)s から削除しま #~ msgid "Detach volume %s from mountpoint %s on instance %s" #~ msgstr "Detach volume: ボリューム %s をマウントポイント %s (インスタンス%s)からデタッチします。" +#, python-format +#~ msgid "updating %s..." +#~ msgstr "%s の情報の更新…" + +#~ msgid "unexpected error during update" +#~ msgstr "更新の最中に予期しないエラーが発生しました。" + #, python-format #~ msgid "Cannot get blockstats for \"%s\" on \"%s\"" #~ msgstr "ブロックデバイス \"%s\" の統計を \"%s\" について取得できません。" @@ -3105,6 +3064,13 @@ msgstr "ユーザ %(user)s をプロジェクト %(project)s から削除しま #~ msgid "Cannot get ifstats for \"%s\" on \"%s\"" #~ msgstr "インタフェース \"%s\" の統計を \"%s\" について取得できません。" +#~ msgid "unexpected exception getting connection" +#~ msgstr "接続に際し予期しないエラーが発生しました。" + +#, python-format +#~ msgid "Found instance: %s" +#~ msgstr "インスタンス %s が見つかりました。" + #, python-format #~ msgid "No service for %s, %s" #~ msgstr "%s, %s のserviceが存在しません。" @@ -3377,3 +3343,24 @@ msgstr "ユーザ %(user)s をプロジェクト %(project)s から削除しま #, python-format #~ msgid "volume %s: creating lv of size %sG" #~ msgstr "ボリューム%sの%sGのlv (論理ボリューム) を作成します。" + +#~ msgid "Wrong number of arguments." +#~ msgstr "引数の数が異なります。" + +#~ msgid "No such process" +#~ msgstr "そのようなプロセスはありません" + +#, python-format +#~ msgid "Cannot get blockstats for \"%(disk)s\" on \"%(iid)s\"" +#~ msgstr "\"%(iid)s\" 上の \"%(disk)s\" 用のブロック統計(blockstats)が取得できません" + +#, python-format +#~ msgid "Cannot get ifstats for \"%(interface)s\" on \"%(iid)s\"" +#~ msgstr "\"%(iid)s\" 上の %(interface)s\" 用インターフェース統計(ifstats)が取得できません" + +#~ msgid "Starting instance monitor" +#~ msgstr "インスタンスモニタを開始しています" + +#, python-format +#~ msgid "Starting %s" +#~ msgstr "%s を起動中" diff --git a/po/pt_BR.po b/po/pt_BR.po index a166c862b..d6d57a9b1 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -14,8 +14,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-07-26 04:53+0000\n" -"X-Generator: Launchpad (build 13405)\n" +"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n" +"X-Generator: Launchpad (build 13573)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -131,34 +131,6 @@ msgstr "compute.api::suspend %s" msgid "compute.api::resume %s" msgstr "compute.api::resume %s" -#: ../nova/twistd.py:157 -msgid "Wrong number of arguments." -msgstr "Número errado de argumentos." - -#: ../nova/twistd.py:209 -#, python-format -msgid "pidfile %s does not exist. Daemon not running?\n" -msgstr "" -"Arquivo do id do processo (pidfile) %s não existe. O Daemon está parado?\n" - -#: ../nova/twistd.py:221 -msgid "No such process" -msgstr "Processo inexistente" - -#: ../nova/twistd.py:230 ../nova/service.py:224 -#, python-format -msgid "Serving %s" -msgstr "Servindo %s" - -#: ../nova/twistd.py:262 ../nova/service.py:225 -msgid "Full set of FLAGS:" -msgstr "Conjunto completo de FLAGS:" - -#: ../nova/twistd.py:266 -#, python-format -msgid "Starting %s" -msgstr "Iniciando %s" - #: ../nova/virt/xenapi/volumeops.py:48 ../nova/virt/xenapi/volumeops.py:101 #: ../nova/db/sqlalchemy/api.py:731 ../nova/virt/libvirt_conn.py:741 #: ../nova/api/ec2/__init__.py:317 @@ -1812,34 +1784,6 @@ msgstr "" msgid "Got exception: %s" msgstr "" -#: ../nova/compute/monitor.py:259 -#, python-format -msgid "updating %s..." -msgstr "" - -#: ../nova/compute/monitor.py:289 -msgid "unexpected error during update" -msgstr "" - -#: ../nova/compute/monitor.py:356 -#, python-format -msgid "Cannot get blockstats for \"%(disk)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:379 -#, python-format -msgid "Cannot get ifstats for \"%(interface)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:414 -msgid "unexpected exception getting connection" -msgstr "" - -#: ../nova/compute/monitor.py:429 -#, python-format -msgid "Found instance: %s" -msgstr "" - #: ../nova/volume/san.py:67 #, python-format msgid "Could not find iSCSI export for volume %s" @@ -2298,10 +2242,6 @@ msgstr "" msgid "You must implement __call__" msgstr "" -#: ../bin/nova-instancemonitor.py:55 -msgid "Starting instance monitor" -msgstr "" - #: ../bin/nova-dhcpbridge.py:58 msgid "leasing ip" msgstr "" @@ -2925,6 +2865,17 @@ msgstr "" #~ "Repositório de dados %s não pode ser atingido. Tentando novamente em %d " #~ "segundos." +#~ msgid "Full set of FLAGS:" +#~ msgstr "Conjunto completo de FLAGS:" + +#, python-format +#~ msgid "Starting %s" +#~ msgstr "Iniciando %s" + +#, python-format +#~ msgid "Serving %s" +#~ msgstr "Servindo %s" + #, python-format #~ msgid "Couldn't get IP, using 127.0.0.1 %s" #~ msgstr "Não foi possível obter IP, usando 127.0.0.1 %s" @@ -3033,3 +2984,14 @@ msgstr "" #, python-format #~ msgid "Created user %s (admin: %r)" #~ msgstr "Criado usuário %s (administrador: %r)" + +#~ msgid "No such process" +#~ msgstr "Processo inexistente" + +#, python-format +#~ msgid "pidfile %s does not exist. Daemon not running?\n" +#~ msgstr "" +#~ "Arquivo do id do processo (pidfile) %s não existe. O Daemon está parado?\n" + +#~ msgid "Wrong number of arguments." +#~ msgstr "Número errado de argumentos." diff --git a/po/ru.po b/po/ru.po index 5d8532f2e..746db964a 100644 --- a/po/ru.po +++ b/po/ru.po @@ -14,8 +14,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n" -"X-Generator: Launchpad (build 13405)\n" +"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n" +"X-Generator: Launchpad (build 13573)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -125,33 +125,6 @@ msgstr "" msgid "compute.api::resume %s" msgstr "" -#: ../nova/twistd.py:157 -msgid "Wrong number of arguments." -msgstr "Неверное число аргументов." - -#: ../nova/twistd.py:209 -#, python-format -msgid "pidfile %s does not exist. Daemon not running?\n" -msgstr "pidfile %s не обнаружен. Демон не запущен?\n" - -#: ../nova/twistd.py:221 -msgid "No such process" -msgstr "" - -#: ../nova/twistd.py:230 ../nova/service.py:224 -#, python-format -msgid "Serving %s" -msgstr "" - -#: ../nova/twistd.py:262 ../nova/service.py:225 -msgid "Full set of FLAGS:" -msgstr "" - -#: ../nova/twistd.py:266 -#, python-format -msgid "Starting %s" -msgstr "Запускается %s" - #: ../nova/virt/xenapi/volumeops.py:48 ../nova/virt/xenapi/volumeops.py:101 #: ../nova/db/sqlalchemy/api.py:731 ../nova/virt/libvirt_conn.py:741 #: ../nova/api/ec2/__init__.py:317 @@ -1779,34 +1752,6 @@ msgstr "" msgid "Got exception: %s" msgstr "" -#: ../nova/compute/monitor.py:259 -#, python-format -msgid "updating %s..." -msgstr "обновление %s..." - -#: ../nova/compute/monitor.py:289 -msgid "unexpected error during update" -msgstr "неожиданная ошибка во время обновления" - -#: ../nova/compute/monitor.py:356 -#, python-format -msgid "Cannot get blockstats for \"%(disk)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:379 -#, python-format -msgid "Cannot get ifstats for \"%(interface)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:414 -msgid "unexpected exception getting connection" -msgstr "" - -#: ../nova/compute/monitor.py:429 -#, python-format -msgid "Found instance: %s" -msgstr "" - #: ../nova/volume/san.py:67 #, python-format msgid "Could not find iSCSI export for volume %s" @@ -2264,10 +2209,6 @@ msgstr "" msgid "You must implement __call__" msgstr "" -#: ../bin/nova-instancemonitor.py:55 -msgid "Starting instance monitor" -msgstr "" - #: ../bin/nova-dhcpbridge.py:58 msgid "leasing ip" msgstr "" @@ -2848,6 +2789,10 @@ msgstr "" msgid "Removing user %(user)s from project %(project)s" msgstr "" +#, python-format +#~ msgid "Starting %s" +#~ msgstr "Запускается %s" + #, python-format #~ msgid "arg: %s\t\tval: %s" #~ msgstr "arg: %s\t\tval: %s" @@ -2900,6 +2845,13 @@ msgstr "" #~ msgid "Adding role %s to user %s in project %s" #~ msgstr "Добавление роли %s для пользователя %s в проект %s" +#~ msgid "unexpected error during update" +#~ msgstr "неожиданная ошибка во время обновления" + +#, python-format +#~ msgid "updating %s..." +#~ msgstr "обновление %s..." + #, python-format #~ msgid "Getting object: %s / %s" #~ msgstr "Получение объекта: %s / %s" @@ -2950,6 +2902,10 @@ msgstr "" #~ msgid "Couldn't get IP, using 127.0.0.1 %s" #~ msgstr "Не удалось получить IP, используем 127.0.0.1 %s" +#, python-format +#~ msgid "pidfile %s does not exist. Daemon not running?\n" +#~ msgstr "pidfile %s не обнаружен. Демон не запущен?\n" + #, python-format #~ msgid "Getting from %s: %s" #~ msgstr "Получение из %s: %s" @@ -2965,3 +2921,6 @@ msgstr "" #, python-format #~ msgid "Authenticated Request For %s:%s)" #~ msgstr "Запрос аутентификации для %s:%s)" + +#~ msgid "Wrong number of arguments." +#~ msgstr "Неверное число аргументов." diff --git a/po/tl.po b/po/tl.po index 7f600e8f1..84e9d26e6 100644 --- a/po/tl.po +++ b/po/tl.po @@ -14,8 +14,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n" -"X-Generator: Launchpad (build 13405)\n" +"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n" +"X-Generator: Launchpad (build 13573)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -125,33 +125,6 @@ msgstr "" msgid "compute.api::resume %s" msgstr "" -#: ../nova/twistd.py:157 -msgid "Wrong number of arguments." -msgstr "" - -#: ../nova/twistd.py:209 -#, python-format -msgid "pidfile %s does not exist. Daemon not running?\n" -msgstr "" - -#: ../nova/twistd.py:221 -msgid "No such process" -msgstr "" - -#: ../nova/twistd.py:230 ../nova/service.py:224 -#, python-format -msgid "Serving %s" -msgstr "" - -#: ../nova/twistd.py:262 ../nova/service.py:225 -msgid "Full set of FLAGS:" -msgstr "" - -#: ../nova/twistd.py:266 -#, python-format -msgid "Starting %s" -msgstr "" - #: ../nova/virt/xenapi/volumeops.py:48 ../nova/virt/xenapi/volumeops.py:101 #: ../nova/db/sqlalchemy/api.py:731 ../nova/virt/libvirt_conn.py:741 #: ../nova/api/ec2/__init__.py:317 @@ -1778,34 +1751,6 @@ msgstr "" msgid "Got exception: %s" msgstr "" -#: ../nova/compute/monitor.py:259 -#, python-format -msgid "updating %s..." -msgstr "" - -#: ../nova/compute/monitor.py:289 -msgid "unexpected error during update" -msgstr "" - -#: ../nova/compute/monitor.py:356 -#, python-format -msgid "Cannot get blockstats for \"%(disk)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:379 -#, python-format -msgid "Cannot get ifstats for \"%(interface)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:414 -msgid "unexpected exception getting connection" -msgstr "" - -#: ../nova/compute/monitor.py:429 -#, python-format -msgid "Found instance: %s" -msgstr "" - #: ../nova/volume/san.py:67 #, python-format msgid "Could not find iSCSI export for volume %s" @@ -2265,10 +2210,6 @@ msgstr "" msgid "You must implement __call__" msgstr "" -#: ../bin/nova-instancemonitor.py:55 -msgid "Starting instance monitor" -msgstr "" - #: ../bin/nova-dhcpbridge.py:58 msgid "leasing ip" msgstr "" diff --git a/po/uk.po b/po/uk.po index bbd831de9..bcc53fed3 100644 --- a/po/uk.po +++ b/po/uk.po @@ -14,8 +14,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n" -"X-Generator: Launchpad (build 13405)\n" +"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n" +"X-Generator: Launchpad (build 13573)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -125,33 +125,6 @@ msgstr "" msgid "compute.api::resume %s" msgstr "" -#: ../nova/twistd.py:157 -msgid "Wrong number of arguments." -msgstr "" - -#: ../nova/twistd.py:209 -#, python-format -msgid "pidfile %s does not exist. Daemon not running?\n" -msgstr "" - -#: ../nova/twistd.py:221 -msgid "No such process" -msgstr "" - -#: ../nova/twistd.py:230 ../nova/service.py:224 -#, python-format -msgid "Serving %s" -msgstr "Обслуговування %s" - -#: ../nova/twistd.py:262 ../nova/service.py:225 -msgid "Full set of FLAGS:" -msgstr "" - -#: ../nova/twistd.py:266 -#, python-format -msgid "Starting %s" -msgstr "Запускається %s" - #: ../nova/virt/xenapi/volumeops.py:48 ../nova/virt/xenapi/volumeops.py:101 #: ../nova/db/sqlalchemy/api.py:731 ../nova/virt/libvirt_conn.py:741 #: ../nova/api/ec2/__init__.py:317 @@ -1778,34 +1751,6 @@ msgstr "" msgid "Got exception: %s" msgstr "" -#: ../nova/compute/monitor.py:259 -#, python-format -msgid "updating %s..." -msgstr "" - -#: ../nova/compute/monitor.py:289 -msgid "unexpected error during update" -msgstr "" - -#: ../nova/compute/monitor.py:356 -#, python-format -msgid "Cannot get blockstats for \"%(disk)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:379 -#, python-format -msgid "Cannot get ifstats for \"%(interface)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:414 -msgid "unexpected exception getting connection" -msgstr "" - -#: ../nova/compute/monitor.py:429 -#, python-format -msgid "Found instance: %s" -msgstr "" - #: ../nova/volume/san.py:67 #, python-format msgid "Could not find iSCSI export for volume %s" @@ -2263,10 +2208,6 @@ msgstr "" msgid "You must implement __call__" msgstr "" -#: ../bin/nova-instancemonitor.py:55 -msgid "Starting instance monitor" -msgstr "" - #: ../bin/nova-dhcpbridge.py:58 msgid "leasing ip" msgstr "" @@ -2851,6 +2792,14 @@ msgstr "" #~ msgid "AMQP server on %s:%d is unreachable. Trying again in %d seconds." #~ msgstr "AMQP сервер %s:%d недоступний. Спроба під'єднання через %d секунд." +#, python-format +#~ msgid "Starting %s" +#~ msgstr "Запускається %s" + +#, python-format +#~ msgid "Serving %s" +#~ msgstr "Обслуговування %s" + #, python-format #~ msgid "Couldn't get IP, using 127.0.0.1 %s" #~ msgstr "Не вдалось отримати IP, використовуючи 127.0.0.1 %s" diff --git a/po/zh_CN.po b/po/zh_CN.po index c3d292a93..de7eb41e0 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -14,13 +14,12 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n" -"X-Generator: Launchpad (build 13405)\n" +"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n" +"X-Generator: Launchpad (build 13573)\n" -#: ../nova/twistd.py:266 #, python-format -msgid "Starting %s" -msgstr "启动 %s 中" +#~ msgid "Starting %s" +#~ msgstr "启动 %s 中" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -135,28 +134,6 @@ msgstr "" msgid "compute.api::resume %s" msgstr "" -#: ../nova/twistd.py:157 -msgid "Wrong number of arguments." -msgstr "错误参数个数。" - -#: ../nova/twistd.py:209 -#, python-format -msgid "pidfile %s does not exist. Daemon not running?\n" -msgstr "pidfile %s 不存在,守护进程是否运行?\n" - -#: ../nova/twistd.py:221 -msgid "No such process" -msgstr "没有该进程" - -#: ../nova/twistd.py:230 ../nova/service.py:224 -#, python-format -msgid "Serving %s" -msgstr "正在为 %s 服务" - -#: ../nova/twistd.py:262 ../nova/service.py:225 -msgid "Full set of FLAGS:" -msgstr "FLAGS全集:" - #: ../nova/virt/xenapi/volumeops.py:48 ../nova/virt/xenapi/volumeops.py:101 #: ../nova/db/sqlalchemy/api.py:731 ../nova/virt/libvirt_conn.py:741 #: ../nova/api/ec2/__init__.py:317 @@ -1785,34 +1762,6 @@ msgstr "" msgid "Got exception: %s" msgstr "" -#: ../nova/compute/monitor.py:259 -#, python-format -msgid "updating %s..." -msgstr "" - -#: ../nova/compute/monitor.py:289 -msgid "unexpected error during update" -msgstr "" - -#: ../nova/compute/monitor.py:356 -#, python-format -msgid "Cannot get blockstats for \"%(disk)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:379 -#, python-format -msgid "Cannot get ifstats for \"%(interface)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:414 -msgid "unexpected exception getting connection" -msgstr "" - -#: ../nova/compute/monitor.py:429 -#, python-format -msgid "Found instance: %s" -msgstr "" - #: ../nova/volume/san.py:67 #, python-format msgid "Could not find iSCSI export for volume %s" @@ -2270,10 +2219,6 @@ msgstr "" msgid "You must implement __call__" msgstr "" -#: ../bin/nova-instancemonitor.py:55 -msgid "Starting instance monitor" -msgstr "" - #: ../bin/nova-dhcpbridge.py:58 msgid "leasing ip" msgstr "" @@ -2888,6 +2833,9 @@ msgstr "从项目 %(project)s 中移除用户 %(user)s" #~ msgid "Data store %s is unreachable. Trying again in %d seconds." #~ msgstr "数据储存服务%s不可用。%d秒之后继续尝试。" +#~ msgid "Full set of FLAGS:" +#~ msgstr "FLAGS全集:" + #, python-format #~ msgid "(%s) publish (key: %s) %s" #~ msgstr "(%s)发布(键值:%s)%s" @@ -2945,3 +2893,17 @@ msgstr "从项目 %(project)s 中移除用户 %(user)s" #, python-format #~ msgid "Removing role %s from user %s for project %s" #~ msgstr "正将角色%s从用户%s在工程%s中移除" + +#~ msgid "No such process" +#~ msgstr "没有该进程" + +#, python-format +#~ msgid "Serving %s" +#~ msgstr "正在为 %s 服务" + +#, python-format +#~ msgid "pidfile %s does not exist. Daemon not running?\n" +#~ msgstr "pidfile %s 不存在,守护进程是否运行?\n" + +#~ msgid "Wrong number of arguments." +#~ msgstr "错误参数个数。" diff --git a/po/zh_TW.po b/po/zh_TW.po index ad14c0e32..a5a826aa0 100644 --- a/po/zh_TW.po +++ b/po/zh_TW.po @@ -14,8 +14,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-07-23 05:12+0000\n" -"X-Generator: Launchpad (build 13405)\n" +"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n" +"X-Generator: Launchpad (build 13573)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -125,33 +125,6 @@ msgstr "" msgid "compute.api::resume %s" msgstr "" -#: ../nova/twistd.py:157 -msgid "Wrong number of arguments." -msgstr "" - -#: ../nova/twistd.py:209 -#, python-format -msgid "pidfile %s does not exist. Daemon not running?\n" -msgstr "pidfile %s 不存在. Daemon未啟動?\n" - -#: ../nova/twistd.py:221 -msgid "No such process" -msgstr "沒有此一程序" - -#: ../nova/twistd.py:230 ../nova/service.py:224 -#, python-format -msgid "Serving %s" -msgstr "" - -#: ../nova/twistd.py:262 ../nova/service.py:225 -msgid "Full set of FLAGS:" -msgstr "" - -#: ../nova/twistd.py:266 -#, python-format -msgid "Starting %s" -msgstr "正在啟動 %s" - #: ../nova/virt/xenapi/volumeops.py:48 ../nova/virt/xenapi/volumeops.py:101 #: ../nova/db/sqlalchemy/api.py:731 ../nova/virt/libvirt_conn.py:741 #: ../nova/api/ec2/__init__.py:317 @@ -1778,34 +1751,6 @@ msgstr "" msgid "Got exception: %s" msgstr "" -#: ../nova/compute/monitor.py:259 -#, python-format -msgid "updating %s..." -msgstr "" - -#: ../nova/compute/monitor.py:289 -msgid "unexpected error during update" -msgstr "" - -#: ../nova/compute/monitor.py:356 -#, python-format -msgid "Cannot get blockstats for \"%(disk)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:379 -#, python-format -msgid "Cannot get ifstats for \"%(interface)s\" on \"%(iid)s\"" -msgstr "" - -#: ../nova/compute/monitor.py:414 -msgid "unexpected exception getting connection" -msgstr "" - -#: ../nova/compute/monitor.py:429 -#, python-format -msgid "Found instance: %s" -msgstr "" - #: ../nova/volume/san.py:67 #, python-format msgid "Could not find iSCSI export for volume %s" @@ -2263,10 +2208,6 @@ msgstr "" msgid "You must implement __call__" msgstr "" -#: ../bin/nova-instancemonitor.py:55 -msgid "Starting instance monitor" -msgstr "" - #: ../bin/nova-dhcpbridge.py:58 msgid "leasing ip" msgstr "" @@ -2846,3 +2787,14 @@ msgstr "" #, python-format msgid "Removing user %(user)s from project %(project)s" msgstr "" + +#~ msgid "No such process" +#~ msgstr "沒有此一程序" + +#, python-format +#~ msgid "pidfile %s does not exist. Daemon not running?\n" +#~ msgstr "pidfile %s 不存在. Daemon未啟動?\n" + +#, python-format +#~ msgid "Starting %s" +#~ msgstr "正在啟動 %s" -- cgit From fa340e03bf33105053160d1d10973f5ea34cb0da Mon Sep 17 00:00:00 2001 From: Launchpad Translations on behalf of nova-core <> Date: Fri, 5 Aug 2011 04:47:13 +0000 Subject: Launchpad automatic translations update. --- po/zh_CN.po | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/po/zh_CN.po b/po/zh_CN.po index de7eb41e0..d2caff810 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -8,14 +8,14 @@ msgstr "" "Project-Id-Version: nova\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2011-02-21 10:03-0500\n" -"PO-Revision-Date: 2011-06-14 14:44+0000\n" -"Last-Translator: chong \n" +"PO-Revision-Date: 2011-08-04 05:50+0000\n" +"Last-Translator: zhangjunfeng \n" "Language-Team: Chinese (Simplified) \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n" -"X-Generator: Launchpad (build 13573)\n" +"X-Launchpad-Export-Date: 2011-08-05 04:47+0000\n" +"X-Generator: Launchpad (build 13604)\n" #, python-format #~ msgid "Starting %s" @@ -2670,12 +2670,12 @@ msgstr "用户 %s 不存在" #: ../nova/auth/ldapdriver.py:472 #, python-format msgid "Group can't be created because group %s already exists" -msgstr "" +msgstr "组不能被创建,因为组 %s 已经存在" #: ../nova/auth/ldapdriver.py:478 #, python-format msgid "Group can't be created because user %s doesn't exist" -msgstr "" +msgstr "组不能被创建,因为用户 %s 不存在" #: ../nova/auth/ldapdriver.py:495 #, python-format @@ -2690,50 +2690,50 @@ msgstr "" #: ../nova/auth/ldapdriver.py:510 ../nova/auth/ldapdriver.py:521 #, python-format msgid "The group at dn %s doesn't exist" -msgstr "" +msgstr "识别名为 %s 的组不存在" #: ../nova/auth/ldapdriver.py:513 #, python-format msgid "User %(uid)s is already a member of the group %(group_dn)s" -msgstr "" +msgstr "用户 %(uid)s 已经是 组 %(group_dn)s 中的成员" #: ../nova/auth/ldapdriver.py:524 #, python-format msgid "" "User %s can't be removed from the group because the user doesn't exist" -msgstr "" +msgstr "用户 %s 不能从组中删除,因为这个用户不存在" #: ../nova/auth/ldapdriver.py:528 #, python-format msgid "User %s is not a member of the group" -msgstr "" +msgstr "用户 %s 不是这个组的成员" #: ../nova/auth/ldapdriver.py:542 #, python-format msgid "" "Attempted to remove the last member of a group. Deleting the group at %s " "instead." -msgstr "" +msgstr "尝试删除组中最后一个成员,用删除组 %s 来代替。" #: ../nova/auth/ldapdriver.py:549 #, python-format msgid "User %s can't be removed from all because the user doesn't exist" -msgstr "" +msgstr "用户 %s 不能从系统中删除,因为这个用户不存在" #: ../nova/auth/ldapdriver.py:564 #, python-format msgid "Group at dn %s doesn't exist" -msgstr "" +msgstr "可识别名为 %s 的组不存在" #: ../nova/virt/xenapi/network_utils.py:40 #, python-format msgid "Found non-unique network for bridge %s" -msgstr "" +msgstr "发现网桥 %s 的网络不唯一" #: ../nova/virt/xenapi/network_utils.py:43 #, python-format msgid "Found no network for bridge %s" -msgstr "" +msgstr "发现网桥 %s 没有网络" #: ../nova/api/ec2/admin.py:97 #, python-format @@ -2748,22 +2748,22 @@ msgstr "删除用户: %s" #: ../nova/api/ec2/admin.py:127 #, python-format msgid "Adding role %(role)s to user %(user)s for project %(project)s" -msgstr "" +msgstr "添加角色 %(role)s 给项目 %(project)s 中的用户 %(user)s" #: ../nova/api/ec2/admin.py:131 #, python-format msgid "Adding sitewide role %(role)s to user %(user)s" -msgstr "" +msgstr "给用户 %(user)s 添加站点角色 %(role)s" #: ../nova/api/ec2/admin.py:137 #, python-format msgid "Removing role %(role)s from user %(user)s for project %(project)s" -msgstr "" +msgstr "删除项目 %(project)s中用户 %(user)s的角色 %(role)s" #: ../nova/api/ec2/admin.py:141 #, python-format msgid "Removing sitewide role %(role)s from user %(user)s" -msgstr "" +msgstr "删除用户 %(user)s 的站点角色 %(role)s" #: ../nova/api/ec2/admin.py:146 ../nova/api/ec2/admin.py:223 msgid "operation must be add or remove" @@ -2772,22 +2772,22 @@ msgstr "操作必须为添加或删除" #: ../nova/api/ec2/admin.py:159 #, python-format msgid "Getting x509 for user: %(name)s on project: %(project)s" -msgstr "" +msgstr "获得用户: %(name)s 在项目 :%(project)s中的x509" #: ../nova/api/ec2/admin.py:177 #, python-format msgid "Create project %(name)s managed by %(manager_user)s" -msgstr "" +msgstr "创建被%(manager_user)s 管理的项目 %(name)s" #: ../nova/api/ec2/admin.py:190 #, python-format msgid "Modify project: %(name)s managed by %(manager_user)s" -msgstr "" +msgstr "更改被 %(manager_user)s 管理的项目: %(name)s" #: ../nova/api/ec2/admin.py:200 #, python-format msgid "Delete project: %s" -msgstr "删除工程 %s" +msgstr "" #: ../nova/api/ec2/admin.py:214 #, python-format -- 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 f22cfa05f7c796fbda3d832e4bfadc325f8af6f5 Mon Sep 17 00:00:00 2001 From: Christopher MacGown Date: Thu, 11 Aug 2011 17:40:13 -0700 Subject: Updates to libvirt, write metadata, net, and key to the config drive --- nova/network/manager.py | 3 ++- nova/virt/disk.py | 33 +++++++++++++++++++++----- nova/virt/libvirt.xml.template | 14 +++++------ nova/virt/libvirt/connection.py | 51 ++++++++++++++++++++++++++--------------- 4 files changed, 68 insertions(+), 33 deletions(-) diff --git a/nova/network/manager.py b/nova/network/manager.py index 75c3f668d..44bf662ce 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -893,7 +893,6 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): def _setup_network(self, context, network_ref): """Sets up network on this host.""" - network_ref['dhcp_server'] = self._get_dhcp_ip(context, network_ref) if not network_ref['vpn_public_address']: net = {} address = FLAGS.vpn_ip @@ -901,6 +900,8 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): network_ref = db.network_update(context, network_ref['id'], net) else: address = network_ref['vpn_public_address'] + + network_ref['dhcp_server'] = self._get_dhcp_ip(context, network_ref) self.driver.ensure_vlan_bridge(network_ref['vlan'], network_ref['bridge'], network_ref['bridge_interface'], diff --git a/nova/virt/disk.py b/nova/virt/disk.py index f8aea1f34..fda3f5f29 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -2,6 +2,9 @@ # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. +# +# Copyright 2011, Piston Cloud Computing, Inc. +# # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -22,6 +25,7 @@ Includes injection of SSH PGP keys into authorized_keys file. """ +import json import os import tempfile import time @@ -60,7 +64,8 @@ def extend(image, size): utils.execute('resize2fs', image, check_exit_code=False) -def inject_data(image, key=None, net=None, partition=None, nbd=False): +def inject_data(image, key=None, net=None, metadata=None, + partition=None, nbd=False, tune2fs=True): """Injects a ssh key and optionally net data into a disk image. it will mount the image as a fully partitioned disk and attempt to inject @@ -89,9 +94,10 @@ def inject_data(image, key=None, net=None, partition=None, nbd=False): ' only inject raw disk images): %s' % mapped_device) - # Configure ext2fs so that it doesn't auto-check every N boots - out, err = utils.execute('sudo', 'tune2fs', - '-c', 0, '-i', 0, mapped_device) + if tune2fs: + # Configure ext2fs so that it doesn't auto-check every N boots + out, err = utils.execute('sudo', 'tune2fs', + '-c', 0, '-i', 0, mapped_device) tmpdir = tempfile.mkdtemp() try: @@ -103,7 +109,8 @@ def inject_data(image, key=None, net=None, partition=None, nbd=False): % err) try: - inject_data_into_fs(tmpdir, key, net, utils.execute) + inject_data_into_fs(tmpdir, key, net, metadata, + utils.execute) finally: # unmount device utils.execute('sudo', 'umount', mapped_device) @@ -155,6 +162,7 @@ def destroy_container(target, instance, nbd=False): def _link_device(image, nbd): """Link image to device using loopback or nbd""" + if nbd: device = _allocate_device() utils.execute('sudo', 'qemu-nbd', '-c', device, image) @@ -189,6 +197,7 @@ def _allocate_device(): # NOTE(vish): This assumes no other processes are allocating nbd devices. # It may race cause a race condition if multiple # workers are running on a given machine. + while True: if not _DEVICES: raise exception.Error(_('No free nbd devices')) @@ -202,7 +211,7 @@ def _free_device(device): _DEVICES.append(device) -def inject_data_into_fs(fs, key, net, execute): +def inject_data_into_fs(fs, key, net, metadata, execute): """Injects data into a filesystem already mounted by the caller. Virt connections can call this directly if they mount their fs in a different way to inject_data @@ -211,7 +220,19 @@ def inject_data_into_fs(fs, key, net, execute): _inject_key_into_fs(key, fs, execute=execute) if net: _inject_net_into_fs(net, fs, execute=execute) + if metadata: + _inject_metadata_into_fs(metadata, fs, execute=execute) + +def _inject_file_into_fs(injected_files, fs, execute=None): + for path, data in injected_files: + + +def _inject_metadata_into_fs(metadata, fs, execute=None): + metadata_path = os.path.join(fs, "meta.js") + metadata = dict([(m.key, m.value) for m in metadata]) + utils.execute('sudo', 'tee', '-a', metadata_path, + process_input=json.dumps(metadata)) def _inject_key_into_fs(key, fs, execute=None): """Add the given public ssh key to root's authorized_keys. diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template index 4422f349f..1fa536ad1 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -55,13 +55,6 @@ #else - #if $getVar('config', False) - - - - - - #end if #if $getVar('rescue', False) @@ -96,6 +89,13 @@ #end for #end if + #if $getVar('config_drive', False) + + + + + + #end if #end if #for $nic in $nics diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index ad97dc796..35ce0dcde 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -781,6 +781,7 @@ class LibvirtConnection(driver.ComputeDriver): network_info=None, block_device_mapping=None): block_device_mapping = block_device_mapping or [] + if not suffix: suffix = '' @@ -857,7 +858,7 @@ class LibvirtConnection(driver.ComputeDriver): target=basepath('disk.local'), fname="local_%s" % inst_type['local_gb'], cow=FLAGS.use_cow_images, - local_gb=inst_type['local_gb']) + local_size=inst_type['local_gb']) # For now, we assume that if we're not using a kernel, we're using a # partitioned disk image where the target partition is the first @@ -866,20 +867,23 @@ class LibvirtConnection(driver.ComputeDriver): if not inst['kernel_id']: target_partition = "1" - if FLAGS.libvirt_type == 'lxc': + config_drive_id = inst.get('config_drive_id') + config_drive = inst.get('config_drive') + + if any((FLAGS.libvirt_type == 'lxc', config_drive, config_drive_id)): target_partition = None - else: - if inst['config_drive_id']: - fname = '%08x' % int(inst['config_drive_id']) - self._cache_image(fn=self._fetch_image, - target=basepath('config'), - fname=fname, - image_id=inst['config_drive_id'], - user=user, - project=project) - elif inst['config_drive']: - self._create_local(basepath('config'), 64, prefix="M", - fs_format='msdos') # 64MB + + if config_drive_id: + fname = '%08x' % int(config_drive_id) + self._cache_image(fn=self._fetch_image, + target=basepath('disk.config'), + fname=fname, + image_id=config_drive_id, + user=user, + project=project) + elif config_drive: + self._create_local(basepath('disk.config'), 64, prefix="M", + fs_format='msdos') # 64MB if inst['key_data']: key = str(inst['key_data']) @@ -924,15 +928,18 @@ class LibvirtConnection(driver.ComputeDriver): searchList=[{'interfaces': nets, 'use_ipv6': FLAGS.use_ipv6}])) - if any(key, net, inst['metadata']): + metadata = inst.get('metadata') + if any((key, net, metadata)): inst_name = inst['name'] - if inst['config_drive']: # Should be True or None by now. - injection_path = basepath('config') + if config_drive: # Should be True or None by now. + injection_path = basepath('disk.config') img_id = 'config-drive' + tune2fs = False else: injection_path = basepath('disk') img_id = inst.image_ref + tune2fs = True for injection in ('metadata', 'key', 'net'): if locals()[injection]: @@ -940,9 +947,10 @@ class LibvirtConnection(driver.ComputeDriver): '%(injection)s into image %(img_id)s' % locals())) try: - disk.inject_data(injection_path, key, net, inst['metadata'], + disk.inject_data(injection_path, key, net, metadata, partition=target_partition, - nbd=FLAGS.use_cow_images) + nbd=FLAGS.use_cow_images, + tune2fs=tune2fs) if FLAGS.libvirt_type == 'lxc': disk.setup_container(basepath('disk'), @@ -1043,6 +1051,11 @@ class LibvirtConnection(driver.ComputeDriver): 'ebs_root': ebs_root, 'volumes': block_device_mapping} + + config_drive = False + if instance.get('config_drive') or instance.get('config_drive_id'): + xml_info['config_drive'] = xml_info['basepath'] + "/disk.config" + if FLAGS.vnc_enabled and FLAGS.libvirt_type not in ('lxc', 'uml'): xml_info['vncserver_host'] = FLAGS.vncserver_host xml_info['vnc_keymap'] = FLAGS.vnc_keymap -- cgit From b776f19c21d1a56ac851435182c0c267166d49dd Mon Sep 17 00:00:00 2001 From: Christopher MacGown Date: Thu, 11 Aug 2011 17:59:41 -0700 Subject: Accidentally added inject_files to merge --- nova/virt/disk.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nova/virt/disk.py b/nova/virt/disk.py index fda3f5f29..63e09d014 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -223,9 +223,6 @@ def inject_data_into_fs(fs, key, net, metadata, execute): if metadata: _inject_metadata_into_fs(metadata, fs, execute=execute) -def _inject_file_into_fs(injected_files, fs, execute=None): - for path, data in injected_files: - def _inject_metadata_into_fs(metadata, fs, execute=None): metadata_path = os.path.join(fs, "meta.js") @@ -234,6 +231,7 @@ def _inject_metadata_into_fs(metadata, fs, execute=None): utils.execute('sudo', 'tee', '-a', metadata_path, process_input=json.dumps(metadata)) + def _inject_key_into_fs(key, fs, execute=None): """Add the given public ssh key to root's authorized_keys. -- cgit From ca7bf95e610bdc47f01b8fb7b459269bb8e5df66 Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Thu, 11 Aug 2011 18:11:59 -0700 Subject: Initial version --- nova/api/__init__.py | 6 ++ nova/api/ec2/__init__.py | 3 - nova/api/openstack/create_instance_helper.py | 4 +- nova/api/openstack/userdatarequesthandler.py | 110 +++++++++++++++++++++ nova/network/linux_net.py | 5 + nova/tests/api/openstack/fakes.py | 2 + .../api/openstack/test_userdatarequesthandler.py | 80 +++++++++++++++ 7 files changed, 206 insertions(+), 4 deletions(-) create mode 100644 nova/api/openstack/userdatarequesthandler.py create mode 100644 nova/tests/api/openstack/test_userdatarequesthandler.py diff --git a/nova/api/__init__.py b/nova/api/__init__.py index 747015af5..6e6b092b3 100644 --- a/nova/api/__init__.py +++ b/nova/api/__init__.py @@ -15,3 +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 nova import 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.') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 8b6e47cfb..e497b499a 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -37,9 +37,6 @@ 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, diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 1425521a9..144697790 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -122,6 +122,7 @@ class CreateInstanceHelper(object): raise exc.HTTPBadRequest(explanation=msg) zone_blob = server_dict.get('blob') + user_data = server_dict.get('user_data') name = server_dict['name'] self._validate_server_name(name) name = name.strip() @@ -161,7 +162,8 @@ class CreateInstanceHelper(object): zone_blob=zone_blob, reservation_id=reservation_id, min_count=min_count, - max_count=max_count)) + max_count=max_count, + user_data=user_data)) except quota.QuotaError as error: self._handle_quota_error(error) except exception.ImageNotFound as error: diff --git a/nova/api/openstack/userdatarequesthandler.py b/nova/api/openstack/userdatarequesthandler.py new file mode 100644 index 000000000..5daa37e95 --- /dev/null +++ b/nova/api/openstack/userdatarequesthandler.py @@ -0,0 +1,110 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""User data request handler.""" + +import base64 +import webob.dec +import webob.exc + +from nova import log as logging +from nova import context +from nova import exception +from nova import db +from nova import flags +from nova import wsgi + + +LOG = logging.getLogger('nova.api.openstack.userdata') +FLAGS = flags.FLAGS + + +class Controller(object): + """ The server user-data API controller for the Openstack API """ + + def __init__(self): + super(Controller, self).__init__() + + @staticmethod + def _format_user_data(instance_ref): + return base64.b64decode(instance_ref['user_data']) + + def get_user_data(self, address): + ctxt = context.get_admin_context() + try: + instance_ref = db.instance_get_by_fixed_ip(ctxt, address) + except exception.NotFound: + instance_ref = None + if not instance_ref: + return None + + data = {'user-data': self._format_user_data(instance_ref)} + return data + + +class UserdataRequestHandler(wsgi.Application): + """Serve user-data from the OS API.""" + + def __init__(self): + self.cc = Controller() + + def print_data(self, data): + if isinstance(data, dict): + output = '' + for key in data: + if key == '_name': + continue + output += key + if isinstance(data[key], dict): + if '_name' in data[key]: + output += '=' + str(data[key]['_name']) + else: + output += '/' + output += '\n' + # Cut off last \n + return output[:-1] + elif isinstance(data, list): + return '\n'.join(data) + else: + return str(data) + + def lookup(self, path, data): + items = path.split('/') + for item in items: + if item: + if not isinstance(data, dict): + return data + if not item in data: + return None + data = data[item] + return data + + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + remote_address = "10.0.1.6"#req.remote_addr + if FLAGS.use_forwarded_for: + remote_address = req.headers.get('X-Forwarded-For', remote_address) + + data = self.cc.get_user_data(remote_address) + if data is None: + LOG.error(_('Failed to get user data for ip: %s'), remote_address) + raise webob.exc.HTTPNotFound() + data = self.lookup(req.path_info, data) + if data is None: + raise webob.exc.HTTPNotFound() + return self.print_data(data) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 4e1e1f85a..d8fff8a32 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -371,6 +371,11 @@ def metadata_forward(): '-p tcp -m tcp --dport 80 -j DNAT ' '--to-destination %s:%s' % \ (FLAGS.ec2_dmz_host, FLAGS.ec2_port)) + iptables_manager.ipv4['nat'].add_rule('PREROUTING', + '-s 0.0.0.0/0 -d 169.254.169.253/32 ' + '-p tcp -m tcp --dport 80 -j DNAT ' + '--to-destination %s:%s' % \ + (FLAGS.osapi_host, FLAGS.osapi_port)) iptables_manager.apply() diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index d11fbf788..aa5aeef16 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -36,6 +36,7 @@ from nova.api.openstack import auth from nova.api.openstack import extensions from nova.api.openstack import versions from nova.api.openstack import limits +from nova.api.openstack import userdatarequesthandler from nova.auth.manager import User, Project import nova.image.fake from nova.image import glance @@ -99,6 +100,7 @@ def wsgi_app(inner_app10=None, inner_app11=None, fake_auth=True, mapper['/v1.0'] = api10 mapper['/v1.1'] = api11 mapper['/'] = openstack.FaultWrapper(versions.Versions()) + mapper['/latest'] = userdatarequesthandler.UserdataRequestHandler() return mapper diff --git a/nova/tests/api/openstack/test_userdatarequesthandler.py b/nova/tests/api/openstack/test_userdatarequesthandler.py new file mode 100644 index 000000000..0c63076b4 --- /dev/null +++ b/nova/tests/api/openstack/test_userdatarequesthandler.py @@ -0,0 +1,80 @@ +# 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 +import webob + +from nova import context +from nova import db +from nova import exception +from nova import flags +from nova import test +from nova import log as logging + +from nova.tests.api.openstack import fakes + +LOG = logging.getLogger('nova.api.openstack.userdata') + +USER_DATA_STRING = ("This is an encoded string") +ENCODE_STRING = base64.b64encode(USER_DATA_STRING) + + +def return_server_by_address(context, address): + instance = {"user_data": ENCODE_STRING} + instance["fixed_ips"] = {"address": address, + "floating_ips": []} + return instance + + +def return_non_existing_server_by_address(context, address): + raise exception.NotFound() + + +class TestUserdatarequesthandler(test.TestCase): + + def setUp(self): + super(TestUserdatarequesthandler, self).setUp() + self.stubs.Set(db, 'instance_get_by_fixed_ip', + return_server_by_address) + + def test_user_data(self): + req = webob.Request.blank('/latest/user-data') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + self.assertEqual(res.body, USER_DATA_STRING) + + def test_user_data_non_existing_fixed_address(self): + self.stubs.Set(db, 'instance_get_by_fixed_ip', + return_non_existing_server_by_address) + self.flags(use_forwarded_for=False) + req = webob.Request.blank('/latest/user-data') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 404) + + def test_user_data_invalid_url(self): + req = webob.Request.blank('/latest/user-data-invalid') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 404) + + def test_user_data_with_use_forwarded_header(self): + self.flags(use_forwarded_for=True) + req = webob.Request.blank('/latest/user-data') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + self.assertEqual(res.body, USER_DATA_STRING) -- cgit From 7507ba23004c989c75962c47efbd2ce5e5178a90 Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Thu, 11 Aug 2011 18:22:35 -0700 Subject: added userdata entry in the api paste ini --- etc/nova/api-paste.ini | 7 +++++++ nova/api/openstack/userdatarequesthandler.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/etc/nova/api-paste.ini b/etc/nova/api-paste.ini index abe8c20c4..46a3b0af9 100644 --- a/etc/nova/api-paste.ini +++ b/etc/nova/api-paste.ini @@ -69,6 +69,7 @@ use = egg:Paste#urlmap /: osversions /v1.0: openstackapi10 /v1.1: openstackapi11 +/latest: osuserdata [pipeline:openstackapi10] pipeline = faultwrap auth ratelimit osapiapp10 @@ -76,6 +77,9 @@ pipeline = faultwrap auth ratelimit osapiapp10 [pipeline:openstackapi11] pipeline = faultwrap auth ratelimit extensions osapiapp11 +[pipeline:osuserdata] +pipeline = logrequest osappud + [filter:faultwrap] paste.filter_factory = nova.api.openstack:FaultWrapper.factory @@ -99,3 +103,6 @@ pipeline = faultwrap osversionapp [app:osversionapp] paste.app_factory = nova.api.openstack.versions:Versions.factory + +[app:osappud] +paste.app_factory = nova.api.openstack.userdatarequesthandler:UserdataRequestHandler.factory diff --git a/nova/api/openstack/userdatarequesthandler.py b/nova/api/openstack/userdatarequesthandler.py index 5daa37e95..f0205419b 100644 --- a/nova/api/openstack/userdatarequesthandler.py +++ b/nova/api/openstack/userdatarequesthandler.py @@ -96,7 +96,7 @@ class UserdataRequestHandler(wsgi.Application): @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): - remote_address = "10.0.1.6"#req.remote_addr + remote_address = req.remote_addr if FLAGS.use_forwarded_for: remote_address = req.headers.get('X-Forwarded-For', remote_address) -- 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 91eaa647506a2e343e8c689289529eafea0bc9d3 Mon Sep 17 00:00:00 2001 From: Christopher MacGown Date: Fri, 12 Aug 2011 14:33:27 -0700 Subject: Fix ugly little violations before someone says anything --- nova/api/openstack/create_instance_helper.py | 2 -- run_tests.sh | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 0ec455167..d776ae92d 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -178,8 +178,6 @@ class CreateInstanceHelper(object): def _handle_quota_error(self, error): """ Reraise quota errors as api-specific http exceptions - - """ if error.code == "OnsetFileLimitExceeded": expl = _("Personality file limit exceeded") diff --git a/run_tests.sh b/run_tests.sh index ba15242b6..871332b4a 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -67,7 +67,7 @@ function run_tests { ERRSIZE=`wc -l run_tests.log | awk '{print \$1}'` if [ "$ERRSIZE" -lt "40" ]; then - echo cat run_tests.log + cat run_tests.log fi fi return $RESULT -- 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 8666aca320ce95840a378231bfe81bc4e759df6e Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Mon, 15 Aug 2011 11:50:54 -0700 Subject: Fixed merging issue --- nova/api/openstack/create_instance_helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index 7e9d48c02..c8798536e 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -164,7 +164,7 @@ class CreateInstanceHelper(object): reservation_id=reservation_id, min_count=min_count, max_count=max_count, - user_data=user_data)) + user_data=user_data, availability_zone=availability_zone)) except quota.QuotaError as error: self._handle_quota_error(error) -- 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 066b675e3ce5c2bd67dde124cbe01b68bd1eded8 Mon Sep 17 00:00:00 2001 From: John Tran Date: Mon, 15 Aug 2011 13:22:14 -0700 Subject: fix bug which DescribeInstances in EC2 api was returning deleted instances --- nova/db/sqlalchemy/api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index e5d35a20b..e7b71d494 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1209,7 +1209,8 @@ def instance_get_all_by_filters(context, filters): options(joinedload('security_groups')).\ options(joinedload_all('fixed_ips.network')).\ options(joinedload('metadata')).\ - options(joinedload('instance_type')) + options(joinedload('instance_type')).\ + filter_by(deleted=can_read_deleted(context)) # Make a copy of the filters dictionary to use going forward, as we'll # be modifying it and we shouldn't affect the caller's use of it. -- cgit From f06f80591a41f5d1b373677937bbbcddcfb0bb7c Mon Sep 17 00:00:00 2001 From: John Tran Date: Mon, 15 Aug 2011 13:48:09 -0700 Subject: added cloud unit test for describe_instances to ensure doesn't return deleted instances --- nova/tests/test_cloud.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index b2afc53c9..07a35c447 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -487,6 +487,16 @@ class CloudTestCase(test.TestCase): db.service_destroy(self.context, comp1['id']) db.service_destroy(self.context, comp2['id']) + def test_describe_instances_deleted(self): + args = {'reservation_id': 'a', 'image_ref': 1, 'host': 'host1'} + inst1 = db.instance_create(self.context, args) + inst2 = db.instance_create(self.context, args) + db.instance_destroy(self.context, inst1.id) + result = self.cloud.describe_instances(self.context) + result = result['reservationSet'][0]['instancesSet'] + print result + self.assertEqual(1, len(result)) + def _block_device_mapping_create(self, instance_id, mappings): volumes = [] for bdm in mappings: -- cgit From 3e561f148fcba627f8fbd4ab1089f426fbc2e61b Mon Sep 17 00:00:00 2001 From: John Tran Date: Mon, 15 Aug 2011 13:58:44 -0700 Subject: adding sqlalchemi api tests for test_instance_get_all_by_filter to ensure doesn't return deleted instances --- nova/tests/test_cloud.py | 1 - nova/tests/test_db_api.py | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 07a35c447..39358eeff 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -494,7 +494,6 @@ class CloudTestCase(test.TestCase): db.instance_destroy(self.context, inst1.id) result = self.cloud.describe_instances(self.context) result = result['reservationSet'][0]['instancesSet'] - print result self.assertEqual(1, len(result)) def _block_device_mapping_create(self, instance_id, mappings): diff --git a/nova/tests/test_db_api.py b/nova/tests/test_db_api.py index 0c07cbb7c..ed363d1be 100644 --- a/nova/tests/test_db_api.py +++ b/nova/tests/test_db_api.py @@ -76,3 +76,18 @@ class DbApiTestCase(test.TestCase): self.assertEqual(instance['id'], result['id']) self.assertEqual(result['fixed_ips'][0]['floating_ips'][0].address, '1.2.1.2') + + def test_instance_get_all_by_filters(self): + args = {'reservation_id': 'a', 'image_ref': 1, 'host': 'host1'} + inst1 = db.instance_create(self.context, args) + inst2 = db.instance_create(self.context, args) + result = db.instance_get_all_by_filters(self.context, {}) + self.assertTrue(2, len(result)) + + def test_instance_get_all_by_filters_deleted(self): + args = {'reservation_id': 'a', 'image_ref': 1, 'host': 'host1'} + inst1 = db.instance_create(self.context, args) + inst2 = db.instance_create(self.context, args) + db.instance_destroy(self.context, inst1.id) + result = db.instance_get_all_by_filters(self.context, {}) + self.assertTrue(1, len(result)) -- cgit From 9a4b1deb5f9abdc88809ff80bccdfb503e66dccd Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Mon, 15 Aug 2011 15:09:42 -0700 Subject: Removed newly added userdatarequesthandler for OS API, there is no need to add this handler since the existing Ec2 API metadatarequesthandler does the same job --- etc/nova/api-paste.ini | 7 -- nova/api/__init__.py | 6 -- nova/api/ec2/__init__.py | 3 + nova/api/openstack/userdatarequesthandler.py | 110 --------------------- nova/network/linux_net.py | 5 - nova/tests/api/openstack/fakes.py | 2 - .../api/openstack/test_userdatarequesthandler.py | 80 --------------- 7 files changed, 3 insertions(+), 210 deletions(-) delete mode 100644 nova/api/openstack/userdatarequesthandler.py delete mode 100644 nova/tests/api/openstack/test_userdatarequesthandler.py diff --git a/etc/nova/api-paste.ini b/etc/nova/api-paste.ini index 46a3b0af9..abe8c20c4 100644 --- a/etc/nova/api-paste.ini +++ b/etc/nova/api-paste.ini @@ -69,7 +69,6 @@ use = egg:Paste#urlmap /: osversions /v1.0: openstackapi10 /v1.1: openstackapi11 -/latest: osuserdata [pipeline:openstackapi10] pipeline = faultwrap auth ratelimit osapiapp10 @@ -77,9 +76,6 @@ pipeline = faultwrap auth ratelimit osapiapp10 [pipeline:openstackapi11] pipeline = faultwrap auth ratelimit extensions osapiapp11 -[pipeline:osuserdata] -pipeline = logrequest osappud - [filter:faultwrap] paste.filter_factory = nova.api.openstack:FaultWrapper.factory @@ -103,6 +99,3 @@ pipeline = faultwrap osversionapp [app:osversionapp] paste.app_factory = nova.api.openstack.versions:Versions.factory - -[app:osappud] -paste.app_factory = nova.api.openstack.userdatarequesthandler:UserdataRequestHandler.factory diff --git a/nova/api/__init__.py b/nova/api/__init__.py index 6e6b092b3..747015af5 100644 --- a/nova/api/__init__.py +++ b/nova/api/__init__.py @@ -15,9 +15,3 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from nova import 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.') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 2e9278b52..96df97393 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -37,6 +37,9 @@ 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, diff --git a/nova/api/openstack/userdatarequesthandler.py b/nova/api/openstack/userdatarequesthandler.py deleted file mode 100644 index f0205419b..000000000 --- a/nova/api/openstack/userdatarequesthandler.py +++ /dev/null @@ -1,110 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""User data request handler.""" - -import base64 -import webob.dec -import webob.exc - -from nova import log as logging -from nova import context -from nova import exception -from nova import db -from nova import flags -from nova import wsgi - - -LOG = logging.getLogger('nova.api.openstack.userdata') -FLAGS = flags.FLAGS - - -class Controller(object): - """ The server user-data API controller for the Openstack API """ - - def __init__(self): - super(Controller, self).__init__() - - @staticmethod - def _format_user_data(instance_ref): - return base64.b64decode(instance_ref['user_data']) - - def get_user_data(self, address): - ctxt = context.get_admin_context() - try: - instance_ref = db.instance_get_by_fixed_ip(ctxt, address) - except exception.NotFound: - instance_ref = None - if not instance_ref: - return None - - data = {'user-data': self._format_user_data(instance_ref)} - return data - - -class UserdataRequestHandler(wsgi.Application): - """Serve user-data from the OS API.""" - - def __init__(self): - self.cc = Controller() - - def print_data(self, data): - if isinstance(data, dict): - output = '' - for key in data: - if key == '_name': - continue - output += key - if isinstance(data[key], dict): - if '_name' in data[key]: - output += '=' + str(data[key]['_name']) - else: - output += '/' - output += '\n' - # Cut off last \n - return output[:-1] - elif isinstance(data, list): - return '\n'.join(data) - else: - return str(data) - - def lookup(self, path, data): - items = path.split('/') - for item in items: - if item: - if not isinstance(data, dict): - return data - if not item in data: - return None - data = data[item] - return data - - @webob.dec.wsgify(RequestClass=wsgi.Request) - def __call__(self, req): - remote_address = req.remote_addr - if FLAGS.use_forwarded_for: - remote_address = req.headers.get('X-Forwarded-For', remote_address) - - data = self.cc.get_user_data(remote_address) - if data is None: - LOG.error(_('Failed to get user data for ip: %s'), remote_address) - raise webob.exc.HTTPNotFound() - data = self.lookup(req.path_info, data) - if data is None: - raise webob.exc.HTTPNotFound() - return self.print_data(data) diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index d8fff8a32..4e1e1f85a 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -371,11 +371,6 @@ def metadata_forward(): '-p tcp -m tcp --dport 80 -j DNAT ' '--to-destination %s:%s' % \ (FLAGS.ec2_dmz_host, FLAGS.ec2_port)) - iptables_manager.ipv4['nat'].add_rule('PREROUTING', - '-s 0.0.0.0/0 -d 169.254.169.253/32 ' - '-p tcp -m tcp --dport 80 -j DNAT ' - '--to-destination %s:%s' % \ - (FLAGS.osapi_host, FLAGS.osapi_port)) iptables_manager.apply() diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index aa5aeef16..d11fbf788 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -36,7 +36,6 @@ from nova.api.openstack import auth from nova.api.openstack import extensions from nova.api.openstack import versions from nova.api.openstack import limits -from nova.api.openstack import userdatarequesthandler from nova.auth.manager import User, Project import nova.image.fake from nova.image import glance @@ -100,7 +99,6 @@ def wsgi_app(inner_app10=None, inner_app11=None, fake_auth=True, mapper['/v1.0'] = api10 mapper['/v1.1'] = api11 mapper['/'] = openstack.FaultWrapper(versions.Versions()) - mapper['/latest'] = userdatarequesthandler.UserdataRequestHandler() return mapper diff --git a/nova/tests/api/openstack/test_userdatarequesthandler.py b/nova/tests/api/openstack/test_userdatarequesthandler.py deleted file mode 100644 index 0c63076b4..000000000 --- a/nova/tests/api/openstack/test_userdatarequesthandler.py +++ /dev/null @@ -1,80 +0,0 @@ -# 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 -import webob - -from nova import context -from nova import db -from nova import exception -from nova import flags -from nova import test -from nova import log as logging - -from nova.tests.api.openstack import fakes - -LOG = logging.getLogger('nova.api.openstack.userdata') - -USER_DATA_STRING = ("This is an encoded string") -ENCODE_STRING = base64.b64encode(USER_DATA_STRING) - - -def return_server_by_address(context, address): - instance = {"user_data": ENCODE_STRING} - instance["fixed_ips"] = {"address": address, - "floating_ips": []} - return instance - - -def return_non_existing_server_by_address(context, address): - raise exception.NotFound() - - -class TestUserdatarequesthandler(test.TestCase): - - def setUp(self): - super(TestUserdatarequesthandler, self).setUp() - self.stubs.Set(db, 'instance_get_by_fixed_ip', - return_server_by_address) - - def test_user_data(self): - req = webob.Request.blank('/latest/user-data') - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 200) - self.assertEqual(res.body, USER_DATA_STRING) - - def test_user_data_non_existing_fixed_address(self): - self.stubs.Set(db, 'instance_get_by_fixed_ip', - return_non_existing_server_by_address) - self.flags(use_forwarded_for=False) - req = webob.Request.blank('/latest/user-data') - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 404) - - def test_user_data_invalid_url(self): - req = webob.Request.blank('/latest/user-data-invalid') - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 404) - - def test_user_data_with_use_forwarded_header(self): - self.flags(use_forwarded_for=True) - req = webob.Request.blank('/latest/user-data') - res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status_int, 200) - self.assertEqual(res.body, USER_DATA_STRING) -- 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 0385ef219b47fca0e98130d1c4c54c1673519f48 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 16 Aug 2011 12:02:39 -0400 Subject: Cleanup the '_base' directory in libvirt tests. --- nova/tests/test_libvirt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 688518bb8..6a213b4f0 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -836,6 +836,7 @@ class LibvirtConnTestCase(test.TestCase): count = (0 <= str(e.message).find('Unexpected method call')) shutil.rmtree(os.path.join(FLAGS.instances_path, instance.name)) + shutil.rmtree(os.path.join(FLAGS.instances_path, '_base')) self.assertTrue(count) -- cgit From ca13037d2cd130f5b970d3af219566f3a70a9cb5 Mon Sep 17 00:00:00 2001 From: John Tran Date: Tue, 16 Aug 2011 09:18:13 -0700 Subject: test improvements per peer review --- nova/tests/test_cloud.py | 10 ++++++---- nova/tests/test_db_api.py | 12 +++++++----- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 39358eeff..0793784f8 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -488,13 +488,15 @@ class CloudTestCase(test.TestCase): db.service_destroy(self.context, comp2['id']) def test_describe_instances_deleted(self): - args = {'reservation_id': 'a', 'image_ref': 1, 'host': 'host1'} - inst1 = db.instance_create(self.context, args) - inst2 = db.instance_create(self.context, args) + args1 = {'reservation_id': 'a', 'image_ref': 1, 'host': 'host1'} + inst1 = db.instance_create(self.context, args1) + args2 = {'reservation_id': 'b', 'image_ref': 1, 'host': 'host1'} + inst2 = db.instance_create(self.context, args2) db.instance_destroy(self.context, inst1.id) result = self.cloud.describe_instances(self.context) result = result['reservationSet'][0]['instancesSet'] - self.assertEqual(1, len(result)) + self.assertEqual(result[0]['instanceId'], + ec2utils.id_to_ec2_id(inst2.id)) def _block_device_mapping_create(self, instance_id, mappings): volumes = [] diff --git a/nova/tests/test_db_api.py b/nova/tests/test_db_api.py index ed363d1be..038c07f40 100644 --- a/nova/tests/test_db_api.py +++ b/nova/tests/test_db_api.py @@ -85,9 +85,11 @@ class DbApiTestCase(test.TestCase): self.assertTrue(2, len(result)) def test_instance_get_all_by_filters_deleted(self): - args = {'reservation_id': 'a', 'image_ref': 1, 'host': 'host1'} - inst1 = db.instance_create(self.context, args) - inst2 = db.instance_create(self.context, args) + args1 = {'reservation_id': 'a', 'image_ref': 1, 'host': 'host1'} + inst1 = db.instance_create(self.context, args1) + args2 = {'reservation_id': 'b', 'image_ref': 1, 'host': 'host1'} + inst2 = db.instance_create(self.context, args2) db.instance_destroy(self.context, inst1.id) - result = db.instance_get_all_by_filters(self.context, {}) - self.assertTrue(1, len(result)) + result = db.instance_get_all_by_filters(self.context.elevated(), {}) + self.assertEqual(1, len(result)) + self.assertEqual(result[0].id, inst2.id) -- 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 c890722ddfec7b6ef1911bfbbfd834ac1e3666d5 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 16 Aug 2011 23:15:54 -0400 Subject: Remove instances.admin_pass column. --- .../versions/037_instances_drop_admin_pass.py | 37 ++++++++++++++++++++++ nova/db/sqlalchemy/models.py | 1 - nova/virt/xenapi/vmops.py | 3 -- 3 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/037_instances_drop_admin_pass.py diff --git a/nova/db/sqlalchemy/migrate_repo/versions/037_instances_drop_admin_pass.py b/nova/db/sqlalchemy/migrate_repo/versions/037_instances_drop_admin_pass.py new file mode 100644 index 000000000..b957666c2 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/037_instances_drop_admin_pass.py @@ -0,0 +1,37 @@ +# 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, MetaData, Table, String + +meta = MetaData() + +admin_pass = Column( + 'admin_pass', + String(length=255, convert_unicode=False, assert_unicode=None, + unicode_error=None, _warn_on_bytestring=False), + nullable=True) + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + instances = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + instances.drop_column('admin_pass') + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + instances = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + instances.create_column(admin_pass) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index f2a4680b0..a8e9c36db 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -173,7 +173,6 @@ class Instance(BASE, NovaBase): base_name += "-rescue" return base_name - admin_pass = Column(String(255)) user_id = Column(String(255)) project_id = Column(String(255)) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index eb0a846b5..9a6215f88 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -709,9 +709,6 @@ class VMOps(object): if resp['returncode'] != '0': LOG.error(_('Failed to update password: %(resp)r') % locals()) return None - db.instance_update(nova_context.get_admin_context(), - instance['id'], - dict(admin_pass=new_pass)) return resp['message'] def inject_file(self, instance, path, contents): -- 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: Wed, 17 Aug 2011 16:25:53 -0700 Subject: Make all services use the same launching strategy --- bin/nova-api | 44 +++++++++++++++++--------------------------- nova/service.py | 47 ++++++++++++++++++++++++++++------------------- nova/utils.py | 41 +++-------------------------------------- nova/wsgi.py | 3 --- 4 files changed, 48 insertions(+), 87 deletions(-) diff --git a/bin/nova-api b/bin/nova-api index fe8e83366..d2086dc92 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -19,12 +19,15 @@ """Starter script for Nova API. -Starts both the EC2 and OpenStack APIs in separate processes. +Starts both the EC2 and OpenStack APIs in separate greenthreads. """ +import eventlet +eventlet.monkey_patch() + +import gettext import os -import signal import sys @@ -33,32 +36,19 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath( if os.path.exists(os.path.join(possible_topdir, "nova", "__init__.py")): sys.path.insert(0, possible_topdir) -import nova.service -import nova.utils +gettext.install('nova', unicode=1) from nova import flags - - -FLAGS = flags.FLAGS - - -def main(): - """Launch EC2 and OSAPI services.""" - nova.utils.Bootstrapper.bootstrap_binary(sys.argv) - - launcher = nova.service.Launcher() - - for api in FLAGS.enabled_apis: - service = nova.service.WSGIService(api) - launcher.launch_service(service) - - signal.signal(signal.SIGTERM, lambda *_: launcher.stop()) - - try: - launcher.wait() - except KeyboardInterrupt: - launcher.stop() - +from nova import log as logging +from nova import service +from nova import utils if __name__ == '__main__': - sys.exit(main()) + utils.default_flagfile() + flags.FLAGS(sys.argv) + logging.setup() + services = [] + for api in flags.FLAGS.enabled_apis: + services.append(service.WSGIService(api)) + service.serve(*services) + service.wait() diff --git a/nova/service.py b/nova/service.py index 6e9eddc5a..e0735d26f 100644 --- a/nova/service.py +++ b/nova/service.py @@ -20,13 +20,12 @@ """Generic Node baseclass for all workers that run on hosts.""" import inspect -import multiprocessing import os +import signal +import eventlet import greenlet -from eventlet import greenthread - from nova import context from nova import db from nova import exception @@ -77,10 +76,7 @@ class Launcher(object): """ service.start() - try: - service.wait() - except KeyboardInterrupt: - service.stop() + service.wait() def launch_service(self, service): """Load and start the given service. @@ -89,10 +85,8 @@ class Launcher(object): :returns: None """ - process = multiprocessing.Process(target=self.run_service, - args=(service,)) - process.start() - self._services.append(process) + gt = eventlet.spawn(self.run_service, service) + self._services.append(gt) def stop(self): """Stop all services which are currently running. @@ -101,8 +95,7 @@ class Launcher(object): """ for service in self._services: - if service.is_alive(): - service.terminate() + service.kill() def wait(self): """Waits until all services have been stopped, and then returns. @@ -111,7 +104,10 @@ class Launcher(object): """ for service in self._services: - service.join() + try: + service.wait() + except greenlet.GreenletExit: + pass class Service(object): @@ -121,6 +117,7 @@ class Service(object): periodic_interval=None, *args, **kwargs): self.host = host self.binary = binary + self.name = binary self.topic = topic self.manager_class_name = manager manager_class = utils.import_class(self.manager_class_name) @@ -173,7 +170,7 @@ class Service(object): finally: consumer_set.close() - self.consumer_set_thread = greenthread.spawn(_wait) + self.consumer_set_thread = eventlet.spawn(_wait) if self.report_interval: pulse = utils.LoopingCall(self.report_state) @@ -339,7 +336,17 @@ class WSGIService(object): self.server.wait() +# NOTE(vish): the global launcher is to maintain the existing +# functionality of calling service.serve + +# service.wait +_launcher = None + + def serve(*services): + global _launcher + if not _launcher: + _launcher = Launcher() + signal.signal(signal.SIGTERM, lambda *args: _launcher.stop()) try: if not services: services = [Service.create()] @@ -354,7 +361,7 @@ def serve(*services): flags.DEFINE_flag(flags.HelpXMLFlag()) FLAGS.ParseNewFlags() - name = '_'.join(x.binary for x in services) + name = '_'.join(x.name for x in services) logging.debug(_('Serving %s'), name) logging.debug(_('Full set of FLAGS:')) for flag in FLAGS: @@ -362,9 +369,11 @@ def serve(*services): logging.debug('%(flag)s : %(flag_get)s' % locals()) for x in services: - x.start() + _launcher.launch_service(x) def wait(): - while True: - greenthread.sleep(5) + try: + _launcher.wait() + except KeyboardInterrupt: + _launcher.stop() diff --git a/nova/utils.py b/nova/utils.py index 7276b6bd5..54126f644 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -260,8 +260,9 @@ def default_flagfile(filename='nova.conf', args=None): filename = "./nova.conf" if not os.path.exists(filename): filename = '/etc/nova/nova.conf' - flagfile = '--flagfile=%s' % filename - args.insert(1, flagfile) + if os.path.exists(filename): + flagfile = '--flagfile=%s' % filename + args.insert(1, flagfile) def debug(arg): @@ -837,39 +838,3 @@ def bool_from_str(val): return True if int(val) else False except ValueError: return val.lower() == 'true' - - -class Bootstrapper(object): - """Provides environment bootstrapping capabilities for entry points.""" - - @staticmethod - def bootstrap_binary(argv): - """Initialize the Nova environment using command line arguments.""" - Bootstrapper.setup_flags(argv) - Bootstrapper.setup_logging() - Bootstrapper.log_flags() - - @staticmethod - def setup_logging(): - """Initialize logging and log a message indicating the Nova version.""" - logging.setup() - logging.audit(_("Nova Version (%s)") % - version.version_string_with_vcs()) - - @staticmethod - def setup_flags(input_flags): - """Initialize flags, load flag file, and print help if needed.""" - default_flagfile(args=input_flags) - FLAGS(input_flags or []) - flags.DEFINE_flag(flags.HelpFlag()) - flags.DEFINE_flag(flags.HelpshortFlag()) - flags.DEFINE_flag(flags.HelpXMLFlag()) - FLAGS.ParseNewFlags() - - @staticmethod - def log_flags(): - """Log the list of all active flags being used.""" - logging.audit(_("Currently active flags:")) - for key in FLAGS: - value = FLAGS.get(key, None) - logging.audit(_("%(key)s : %(value)s" % locals())) diff --git a/nova/wsgi.py b/nova/wsgi.py index c8ddb97d7..f2846aa73 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -39,9 +39,6 @@ from nova import log as logging from nova import utils -eventlet.patcher.monkey_patch(socket=True, time=True) - - FLAGS = flags.FLAGS LOG = logging.getLogger('nova.wsgi') -- cgit From 90650e5becb541790a8949edebaf0bff0ceb8f5b Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 17 Aug 2011 19:31:01 -0700 Subject: make admin context the default, clean up pipelib --- bin/nova-manage | 19 ++++++++++--------- etc/nova/api-paste.ini | 15 ++++++++++++--- nova/api/auth.py | 18 ++++++++++++++++++ nova/api/ec2/admin.py | 4 +++- nova/auth/manager.py | 3 +++ nova/cloudpipe/pipelib.py | 7 +++---- 6 files changed, 49 insertions(+), 17 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 8e6419c0b..7474b61cb 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -134,7 +134,7 @@ class VpnCommands(object): help='Project name') def list(self, project=None): """Print a listing of the VPN data for one or all projects.""" - + print "WARNING: This method only works with deprecated auth" print "%-12s\t" % 'project', print "%-20s\t" % 'ip:port', print "%-20s\t" % 'private_ip', @@ -170,17 +170,22 @@ class VpnCommands(object): def spawn(self): """Run all VPNs.""" + print "WARNING: This method only works with deprecated auth" for p in reversed(self.manager.get_projects()): if not self._vpn_for(p.id): print 'spawning %s' % p.id - self.pipe.launch_vpn_instance(p.id) + self.pipe.launch_vpn_instance(p.id, p.project_manager_id) time.sleep(10) @args('--project', dest="project_id", metavar='', help='Project name') - def run(self, project_id): - """Start the VPN for a given project.""" - self.pipe.launch_vpn_instance(project_id) + @args('--user', dest="user_id", metavar='', help='User name') + def run(self, project_id, user_id): + """Start the VPN for a given project and user.""" + if not user_id: + print "WARNING: This method only works with deprecated auth" + user_id = self.manager.get_project(project_id).project_manager_id + self.pipe.launch_vpn_instance(project_id, user_id) @args('--project', dest="project_id", metavar='', help='Project name') @@ -195,10 +200,6 @@ class VpnCommands(object): """ # TODO(tr3buchet): perhaps this shouldn't update all networks # associated with a project in the future - project = self.manager.get_project(project_id) - if not project: - print 'No project %s' % (project_id) - return admin_context = context.get_admin_context() networks = db.project_get_networks(admin_context, project_id) for network in networks: diff --git a/etc/nova/api-paste.ini b/etc/nova/api-paste.ini index b540509a2..108360466 100644 --- a/etc/nova/api-paste.ini +++ b/etc/nova/api-paste.ini @@ -19,7 +19,9 @@ use = egg:Paste#urlmap /1.0: ec2metadata [pipeline:ec2cloud] -pipeline = logrequest authenticate cloudrequest authorizer ec2executor +pipeline = logrequest admincontext cloudrequest authorizer ec2executor +# NOTE(vish): use the following pipeline for deprecated auth +#pipeline = logrequest authenticate cloudrequest authorizer ec2executor # NOTE(vish): use the following pipeline for keystone # pipeline = logrequest totoken authtoken keystonecontext cloudrequest authorizer ec2executor @@ -75,12 +77,16 @@ use = egg:Paste#urlmap /v1.1: openstackapi11 [pipeline:openstackapi10] -pipeline = faultwrap auth ratelimit osapiapp10 +pipeline = faultwrap admincontext ratelimit osapiapp10 +# NOTE(vish): use the following pipeline for deprecated auth +# 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 +pipeline = faultwrap admincontext ratelimit extensions osapiapp11 +# NOTE(vish): use the following pipeline for deprecated auth +# pipeline = faultwrap auth ratelimit extensions osapiapp11 # NOTE(vish): use the following pipeline for keystone # pipeline = faultwrap authtoken keystonecontext ratelimit extensions osapiapp11 @@ -112,6 +118,9 @@ 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 diff --git a/nova/api/auth.py b/nova/api/auth.py index cd3e3e8a0..050216fd7 100644 --- a/nova/api/auth.py +++ b/nova/api/auth.py @@ -45,6 +45,24 @@ 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""" diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index df7876b9d..dfbbc0a2b 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -283,8 +283,10 @@ class AdminController(object): # NOTE(vish) import delayed because of __init__.py from nova.cloudpipe import pipelib pipe = pipelib.CloudPipe() + proj = manager.AuthManager().get_project(project) + user_id = proj.project_manager_id try: - pipe.launch_vpn_instance(project) + pipe.launch_vpn_instance(project, user_id) except db.NoMoreNetworks: raise exception.ApiError("Unable to claim IP for VPN instance" ", ensure it isn't running, and try " diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 6205cfb56..c9178c0dd 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -17,6 +17,9 @@ # under the License. """ +WARNING: This code is deprecated and will be removed. +Keystone is recommended is the recommended solution for auth management. + Nova authentication management """ diff --git a/nova/cloudpipe/pipelib.py b/nova/cloudpipe/pipelib.py index 2c4673f9e..1b742384c 100644 --- a/nova/cloudpipe/pipelib.py +++ b/nova/cloudpipe/pipelib.py @@ -93,11 +93,10 @@ class CloudPipe(object): zippy.close() return encoded - def launch_vpn_instance(self, project_id): + def launch_vpn_instance(self, project_id, user_id): LOG.debug(_("Launching VPN for %s") % (project_id)) - project = self.manager.get_project(project_id) - ctxt = context.RequestContext(user=project.project_manager_id, - project=project.id) + ctxt = context.RequestContext(user_id=user_id, + project_id=project_id) key_name = self.setup_key_pair(ctxt) group_name = self.setup_security_group(ctxt) -- cgit From 41819d8d048b889f2e7f5e4ee0ff2873bfdef904 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 17 Aug 2011 20:22:30 -0700 Subject: fix integration tests --- etc/nova/api-paste.ini | 7 +- nova/api/openstack/auth.py | 17 +++++ nova/tests/integrated/integrated_helpers.py | 109 ++++------------------------ nova/tests/integrated/test_login.py | 33 --------- nova/tests/integrated/test_servers.py | 2 +- 5 files changed, 36 insertions(+), 132 deletions(-) diff --git a/etc/nova/api-paste.ini b/etc/nova/api-paste.ini index 108360466..a9ae0abf6 100644 --- a/etc/nova/api-paste.ini +++ b/etc/nova/api-paste.ini @@ -77,14 +77,14 @@ use = egg:Paste#urlmap /v1.1: openstackapi11 [pipeline:openstackapi10] -pipeline = faultwrap admincontext ratelimit osapiapp10 +pipeline = faultwrap noauth admincontext ratelimit osapiapp10 # NOTE(vish): use the following pipeline for deprecated auth # pipeline = faultwrap auth ratelimit osapiapp10 # NOTE(vish): use the following pipeline for keystone #pipeline = faultwrap authtoken keystonecontext ratelimit osapiapp10 [pipeline:openstackapi11] -pipeline = faultwrap admincontext ratelimit extensions osapiapp11 +pipeline = faultwrap noauth admincontext ratelimit extensions osapiapp11 # NOTE(vish): use the following pipeline for deprecated auth # pipeline = faultwrap auth ratelimit extensions osapiapp11 # NOTE(vish): use the following pipeline for keystone @@ -96,6 +96,9 @@ paste.filter_factory = nova.api.openstack:FaultWrapper.factory [filter:auth] paste.filter_factory = nova.api.openstack.auth:AuthMiddleware.factory +[filter:noauth] +paste.filter_factory = nova.api.openstack.auth:NoAuthMiddleware.factory + [filter:ratelimit] paste.filter_factory = nova.api.openstack.limits:RateLimitingMiddleware.factory diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index d42abe1f8..f4a50fc46 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -34,6 +34,23 @@ LOG = logging.getLogger('nova.api.openstack') FLAGS = flags.FLAGS +class NoAuthMiddleware(wsgi.Middleware): + """Return a fake token if one isn't specified.""" + + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + if 'X-Auth-Token' in req.headers: + return self.application + logging.debug("Got no auth token, returning fake info.") + res = webob.Response() + res.headers['X-Auth-Token'] = 'fake' + res.headers['X-Server-Management-Url'] = req.url + res.headers['X-Storage-Url'] = '' + res.headers['X-CDN-Management-Url'] = '' + res.content_type = 'text/plain' + res.status = '204' + return res + class AuthMiddleware(wsgi.Middleware): """Authorize the openstack API request or return an HTTP Forbidden.""" diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py index fb2f88502..343190427 100644 --- a/nova/tests/integrated/integrated_helpers.py +++ b/nova/tests/integrated/integrated_helpers.py @@ -22,10 +22,8 @@ Provides common functionality for integrated unit tests import random import string -from nova import exception from nova import service from nova import test # For the flags -from nova.auth import manager import nova.image.glance from nova.log import logging from nova.tests.integrated.api import client @@ -58,90 +56,6 @@ def generate_new_element(items, prefix, numeric=False): LOG.debug("Random collision on %s" % candidate) -class TestUser(object): - def __init__(self, name, secret, auth_url): - self.name = name - self.secret = secret - self.auth_url = auth_url - - if not auth_url: - raise exception.Error("auth_url is required") - self.openstack_api = client.TestOpenStackClient(self.name, - self.secret, - self.auth_url) - - def get_unused_server_name(self): - servers = self.openstack_api.get_servers() - server_names = [server['name'] for server in servers] - return generate_new_element(server_names, 'server') - - def get_invalid_image(self): - images = self.openstack_api.get_images() - image_ids = [image['id'] for image in images] - return generate_new_element(image_ids, '', numeric=True) - - def get_valid_image(self, create=False): - images = self.openstack_api.get_images() - if create and not images: - # TODO(justinsb): No way currently to create an image through API - #created_image = self.openstack_api.post_image(image) - #images.append(created_image) - raise exception.Error("No way to create an image through API") - - if images: - return images[0] - return None - - -class IntegratedUnitTestContext(object): - def __init__(self, auth_url): - self.auth_manager = manager.AuthManager() - - self.auth_url = auth_url - self.project_name = None - - self.test_user = None - - self.setup() - - def setup(self): - self._create_test_user() - - def _create_test_user(self): - self.test_user = self._create_unittest_user() - - # No way to currently pass this through the OpenStack API - self.project_name = 'openstack' - self._configure_project(self.project_name, self.test_user) - - def cleanup(self): - self.test_user = None - - def _create_unittest_user(self): - users = self.auth_manager.get_users() - user_names = [user.name for user in users] - auth_name = generate_new_element(user_names, 'unittest_user_') - auth_key = generate_random_alphanumeric(16) - - # Right now there's a bug where auth_name and auth_key are reversed - # bug732907 - auth_key = auth_name - - self.auth_manager.create_user(auth_name, auth_name, auth_key, False) - return TestUser(auth_name, auth_key, self.auth_url) - - def _configure_project(self, project_name, user): - projects = self.auth_manager.get_projects() - project_names = [project.name for project in projects] - if not project_name in project_names: - project = self.auth_manager.create_project(project_name, - user.name, - description=None, - member_users=None) - else: - self.auth_manager.add_to_project(user.name, project_name) - - class _IntegratedTestBase(test.TestCase): def setUp(self): super(_IntegratedTestBase, self).setUp() @@ -163,10 +77,7 @@ class _IntegratedTestBase(test.TestCase): self._start_api_service() - self.context = IntegratedUnitTestContext(self.auth_url) - - self.user = self.context.test_user - self.api = self.user.openstack_api + self.api = client.TestOpenStackClient('fake', 'fake', self.auth_url) def _start_api_service(self): osapi = service.WSGIService("osapi") @@ -174,10 +85,6 @@ class _IntegratedTestBase(test.TestCase): self.auth_url = 'http://%s:%s/v1.1' % (osapi.host, osapi.port) LOG.warn(self.auth_url) - def tearDown(self): - self.context.cleanup() - super(_IntegratedTestBase, self).tearDown() - def _get_flags(self): """An opportunity to setup flags, before the services are started.""" f = {} @@ -190,10 +97,20 @@ class _IntegratedTestBase(test.TestCase): f['fake_network'] = True return f + def get_unused_server_name(self): + servers = self.api.get_servers() + server_names = [server['name'] for server in servers] + return generate_new_element(server_names, 'server') + + def get_invalid_image(self): + images = self.api.get_images() + image_ids = [image['id'] for image in images] + return generate_new_element(image_ids, '', numeric=True) + def _build_minimal_create_server_request(self): server = {} - image = self.user.get_valid_image(create=True) + image = self.api.get_images()[0] LOG.debug("Image: %s" % image) if 'imageRef' in image: @@ -211,7 +128,7 @@ class _IntegratedTestBase(test.TestCase): server['flavorRef'] = 'http://fake.server/%s' % flavor['id'] # Set a valid server name - server_name = self.user.get_unused_server_name() + server_name = self.get_unused_server_name() server['name'] = server_name return server diff --git a/nova/tests/integrated/test_login.py b/nova/tests/integrated/test_login.py index 06359a52f..3a863d0f9 100644 --- a/nova/tests/integrated/test_login.py +++ b/nova/tests/integrated/test_login.py @@ -15,11 +15,9 @@ # License for the specific language governing permissions and limitations # under the License. -import unittest from nova.log import logging from nova.tests.integrated import integrated_helpers -from nova.tests.integrated.api import client LOG = logging.getLogger('nova.tests.integrated') @@ -31,34 +29,3 @@ class LoginTest(integrated_helpers._IntegratedTestBase): flavors = self.api.get_flavors() for flavor in flavors: LOG.debug(_("flavor: %s") % flavor) - - def test_bad_login_password(self): - """Test that I get a 401 with a bad username.""" - bad_credentials_api = client.TestOpenStackClient(self.user.name, - "notso_password", - self.user.auth_url) - - self.assertRaises(client.OpenStackApiAuthenticationException, - bad_credentials_api.get_flavors) - - def test_bad_login_username(self): - """Test that I get a 401 with a bad password.""" - bad_credentials_api = client.TestOpenStackClient("notso_username", - self.user.secret, - self.user.auth_url) - - self.assertRaises(client.OpenStackApiAuthenticationException, - bad_credentials_api.get_flavors) - - def test_bad_login_both_bad(self): - """Test that I get a 401 with both bad username and bad password.""" - bad_credentials_api = client.TestOpenStackClient("notso_username", - "notso_password", - self.user.auth_url) - - self.assertRaises(client.OpenStackApiAuthenticationException, - bad_credentials_api.get_flavors) - - -if __name__ == "__main__": - unittest.main() diff --git a/nova/tests/integrated/test_servers.py b/nova/tests/integrated/test_servers.py index 725f6d529..c2f800689 100644 --- a/nova/tests/integrated/test_servers.py +++ b/nova/tests/integrated/test_servers.py @@ -51,7 +51,7 @@ class ServersTest(integrated_helpers._IntegratedTestBase): self.api.post_server, post) # With an invalid imageRef, this throws 500. - server['imageRef'] = self.user.get_invalid_image() + server['imageRef'] = self.get_invalid_image() # TODO(justinsb): Check whatever the spec says should be thrown here self.assertRaises(client.OpenStackApiException, self.api.post_server, post) -- cgit From a1ceed43d6ab871d3dea721b855bd7eabec48433 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 17 Aug 2011 20:24:52 -0700 Subject: clean up fake auth from server actions test --- nova/tests/api/openstack/test_server_actions.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/nova/tests/api/openstack/test_server_actions.py b/nova/tests/api/openstack/test_server_actions.py index 687a19390..3b419dec5 100644 --- a/nova/tests/api/openstack/test_server_actions.py +++ b/nova/tests/api/openstack/test_server_actions.py @@ -1,17 +1,13 @@ import base64 import json -import unittest -from xml.dom import minidom import stubout import webob from nova import context -from nova import db from nova import utils from nova import flags from nova.api.openstack import create_instance_helper -from nova.compute import instance_types from nova.compute import power_state import nova.db.api from nova import test @@ -103,8 +99,6 @@ class ServerActionsTest(test.TestCase): super(ServerActionsTest, self).setUp() self.flags(verbose=True) self.stubs = stubout.StubOutForTesting() - fakes.FakeAuthManager.reset_fake_data() - fakes.FakeAuthDatabase.data = {} fakes.stub_out_auth(self.stubs) self.stubs.Set(nova.db.api, 'instance_get', return_server_by_id) self.stubs.Set(nova.db.api, 'instance_update', instance_update) @@ -468,8 +462,6 @@ class ServerActionsTestV11(test.TestCase): self.maxDiff = None super(ServerActionsTestV11, self).setUp() self.stubs = stubout.StubOutForTesting() - fakes.FakeAuthManager.reset_fake_data() - fakes.FakeAuthDatabase.data = {} fakes.stub_out_auth(self.stubs) self.stubs.Set(nova.db.api, 'instance_get', return_server_by_id) self.stubs.Set(nova.db.api, 'instance_update', instance_update) -- cgit From 6c256a9a5c013f8674776adb2005b4f541f705b5 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 17 Aug 2011 20:26:33 -0700 Subject: remove extra reference in pipelib --- nova/cloudpipe/pipelib.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nova/cloudpipe/pipelib.py b/nova/cloudpipe/pipelib.py index 1b742384c..3eb372844 100644 --- a/nova/cloudpipe/pipelib.py +++ b/nova/cloudpipe/pipelib.py @@ -34,7 +34,6 @@ from nova import exception from nova import flags from nova import log as logging from nova import utils -from nova.auth import manager # TODO(eday): Eventually changes these to something not ec2-specific from nova.api.ec2 import cloud @@ -57,7 +56,6 @@ LOG = logging.getLogger('nova.cloudpipe') class CloudPipe(object): def __init__(self): self.controller = cloud.CloudController() - self.manager = manager.AuthManager() def get_encoded_zip(self, project_id): # Make a payload.zip -- cgit From 6d87608cf835e1c27f3b6b6b31e6b41b0aa90b90 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 17 Aug 2011 20:35:54 -0700 Subject: pep8 --- nova/api/openstack/auth.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index f4a50fc46..b37f9aade 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -51,6 +51,7 @@ class NoAuthMiddleware(wsgi.Middleware): res.status = '204' return res + class AuthMiddleware(wsgi.Middleware): """Authorize the openstack API request or return an HTTP Forbidden.""" -- cgit From 635306fd009ea9e50259d01e10762f6b5ab45049 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Wed, 17 Aug 2011 22:00:38 -0700 Subject: bug #828429: remove references to interface in nova-dhcpbridge --- bin/nova-dhcpbridge | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bin/nova-dhcpbridge b/bin/nova-dhcpbridge index a47ea7a76..c2fd8994d 100755 --- a/bin/nova-dhcpbridge +++ b/bin/nova-dhcpbridge @@ -52,7 +52,7 @@ flags.DECLARE('update_dhcp_on_disassociate', 'nova.network.manager') LOG = logging.getLogger('nova.dhcpbridge') -def add_lease(mac, ip_address, _interface): +def add_lease(mac, ip_address): """Set the IP that was assigned by the DHCP server.""" if FLAGS.fake_rabbit: LOG.debug(_("leasing ip")) @@ -66,13 +66,13 @@ def add_lease(mac, ip_address, _interface): "args": {"address": ip_address}}) -def old_lease(mac, ip_address, interface): +def old_lease(mac, ip_address): """Update just as add lease.""" LOG.debug(_("Adopted old lease or got a change of mac")) - add_lease(mac, ip_address, interface) + add_lease(mac, ip_address) -def del_lease(mac, ip_address, _interface): +def del_lease(mac, ip_address): """Called when a lease expires.""" if FLAGS.fake_rabbit: LOG.debug(_("releasing ip")) @@ -116,9 +116,9 @@ def main(): mac = argv[2] ip = argv[3] msg = _("Called %(action)s for mac %(mac)s with ip %(ip)s" - " on interface %(interface)s") % locals() + " for network %(network_id)s") % locals() LOG.debug(msg) - globals()[action + '_lease'](mac, ip, interface) + globals()[action + '_lease'](mac, ip) else: print init_leases(network_id) -- cgit From b7019a57c416f7a14f8e8229776a18c28c109d38 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Wed, 17 Aug 2011 22:29:04 -0700 Subject: in dhcpbridge, only grab network id from env if needed --- bin/nova-dhcpbridge | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bin/nova-dhcpbridge b/bin/nova-dhcpbridge index c2fd8994d..afafca548 100755 --- a/bin/nova-dhcpbridge +++ b/bin/nova-dhcpbridge @@ -99,8 +99,6 @@ def main(): utils.default_flagfile(flagfile) argv = FLAGS(sys.argv) logging.setup() - # check ENV first so we don't break any older deploys - network_id = int(os.environ.get('NETWORK_ID')) if int(os.environ.get('TESTING', '0')): from nova.tests import fake_flags @@ -115,11 +113,11 @@ def main(): if action in ['add', 'del', 'old']: mac = argv[2] ip = argv[3] - msg = _("Called %(action)s for mac %(mac)s with ip %(ip)s" - " for network %(network_id)s") % locals() + msg = _("Called %(action)s for mac %(mac)s with ip %(ip)s") % locals() LOG.debug(msg) globals()[action + '_lease'](mac, ip) else: + network_id = int(os.environ.get('NETWORK_ID')) print init_leases(network_id) if __name__ == "__main__": -- cgit From af333cc72e753a4a28d0deb20369076df7bf09e3 Mon Sep 17 00:00:00 2001 From: Alex Meade 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 9011bf57d8caf8a0bd11dfb33cf968b2b65fe294 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 18 Aug 2011 11:21:35 -0500 Subject: Added rescue mode extension. --- nova/api/openstack/contrib/rescue.py | 72 ++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 nova/api/openstack/contrib/rescue.py diff --git a/nova/api/openstack/contrib/rescue.py b/nova/api/openstack/contrib/rescue.py new file mode 100644 index 000000000..efb882fd6 --- /dev/null +++ b/nova/api/openstack/contrib/rescue.py @@ -0,0 +1,72 @@ +# 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. + +"""The rescue mode extension.""" + +import webob +from webob import exc + +from nova import compute +from nova import log as logging +from nova.api.openstack import extensions as exts +from nova.api.openstack import faults + +LOG = logging.getLogger("nova.api.contrib.rescue") + + +class Rescue(exts.ExtensionDescriptor): + """The Rescue API controller for the OpenStack API.""" + def __init__(self): + super(Rescue, self).__init__() + self.compute_api = compute.API() + + def _rescue(self, input_dict, req, instance_id): + """Enable or disable rescue mode.""" + context = req.environ["nova.context"] + action = input_dict["rescue"]["action"] + + try: + if action == "rescue": + self.compute_api.rescue(context, instance_id) + elif action == "unrescue": + self.compute_api.unrescue(context, instance_id) + except Exception, e: + LOG.exception(_("Error in %(action)s: %(e)s") % locals()) + return faults.Fault(exc.HTTPBadRequest()) + + return webob.Response(status_int=202) + + def get_name(self): + return "Rescue" + + def get_alias(self): + return "rescue" + + def get_description(self): + return "Instance rescue mode" + + def get_namespace(self): + return "http://docs.openstack.org/ext/rescue/api/v1.1" + + def get_updated(self): + return "2011-08-18T00:00:00+00:00" + + def get_actions(self): + """Return the actions the extension adds, as required by contract.""" + actions = [ + exts.ActionExtension("servers", "rescue", self._rescue), + exts.ActionExtension("servers", "unrescue", self._rescue), + ] + + return actions -- 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 1ef677a2eac6129aa3847aa10996f4357ec72a48 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Thu, 18 Aug 2011 09:50:24 -0700 Subject: dhcpbridge: add better error if NETWORK_ID is not set, convert locals() to static dict --- bin/nova-dhcpbridge | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/bin/nova-dhcpbridge b/bin/nova-dhcpbridge index afafca548..1c9ae951e 100755 --- a/bin/nova-dhcpbridge +++ b/bin/nova-dhcpbridge @@ -113,11 +113,19 @@ def main(): if action in ['add', 'del', 'old']: mac = argv[2] ip = argv[3] - msg = _("Called %(action)s for mac %(mac)s with ip %(ip)s") % locals() + msg = _("Called '%(action)s' for mac '%(mac)s' with ip '%(ip)s'") % \ + {"action": action, + "mac": mac, + "ip": ip} LOG.debug(msg) globals()[action + '_lease'](mac, ip) else: - network_id = int(os.environ.get('NETWORK_ID')) + try: + network_id = int(os.environ.get('NETWORK_ID')) + except TypeError: + LOG.error(_("Environment variable 'NETWORK_ID' must be set.")) + sys.exit(1) + print init_leases(network_id) if __name__ == "__main__": -- 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 a68c1cde2e73e6d39d7ff6024cd3ff289c465619 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 18 Aug 2011 12:20:40 -0500 Subject: Refactored a little and updated unit test. --- nova/api/openstack/contrib/rescue.py | 12 ++++++++---- nova/tests/api/openstack/test_extensions.py | 1 + 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/nova/api/openstack/contrib/rescue.py b/nova/api/openstack/contrib/rescue.py index efb882fd6..dac269efb 100644 --- a/nova/api/openstack/contrib/rescue.py +++ b/nova/api/openstack/contrib/rescue.py @@ -31,10 +31,10 @@ class Rescue(exts.ExtensionDescriptor): super(Rescue, self).__init__() self.compute_api = compute.API() - def _rescue(self, input_dict, req, instance_id): - """Enable or disable rescue mode.""" + def _rescue(self, input_dict, req, instance_id, exit_rescue=False): + """Rescue an instance.""" context = req.environ["nova.context"] - action = input_dict["rescue"]["action"] + action = "unrescue" if exit_rescue else "rescue" try: if action == "rescue": @@ -47,6 +47,10 @@ class Rescue(exts.ExtensionDescriptor): return webob.Response(status_int=202) + def _unrescue(self, input_dict, req, instance_id): + """Unrescue an instance.""" + self._rescue(input_dict, req, instance_id, exit_rescue=True) + def get_name(self): return "Rescue" @@ -66,7 +70,7 @@ class Rescue(exts.ExtensionDescriptor): """Return the actions the extension adds, as required by contract.""" actions = [ exts.ActionExtension("servers", "rescue", self._rescue), - exts.ActionExtension("servers", "unrescue", self._rescue), + exts.ActionExtension("servers", "unrescue", self._unrescue), ] return actions diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 5d3208e10..34a4b3f89 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -94,6 +94,7 @@ class ExtensionControllerTest(test.TestCase): "Quotas", "SecurityGroups", "Volumes", + "Rescue", ] self.ext_list.sort() -- cgit From a9d87715133ae79518cef6aafd87c95e26f20765 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 18 Aug 2011 12:25:22 -0500 Subject: Minor housecleaning. --- nova/api/openstack/contrib/rescue.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/contrib/rescue.py b/nova/api/openstack/contrib/rescue.py index dac269efb..65ce2874b 100644 --- a/nova/api/openstack/contrib/rescue.py +++ b/nova/api/openstack/contrib/rescue.py @@ -22,17 +22,22 @@ from nova import log as logging from nova.api.openstack import extensions as exts from nova.api.openstack import faults + LOG = logging.getLogger("nova.api.contrib.rescue") class Rescue(exts.ExtensionDescriptor): - """The Rescue API controller for the OpenStack API.""" + """The Rescue controller for the OpenStack API.""" def __init__(self): super(Rescue, self).__init__() self.compute_api = compute.API() def _rescue(self, input_dict, req, instance_id, exit_rescue=False): - """Rescue an instance.""" + """Rescue an instance. + + If exit_rescue is True, rescue mode should be torn down and the + instance restored to its original state. + """ context = req.environ["nova.context"] action = "unrescue" if exit_rescue else "rescue" -- 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 125a2affec7713cdbcb925537d34aea29a2e4230 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 18 Aug 2011 10:55:39 -0700 Subject: more cleanup of binaries per review --- bin/nova-ajax-console-proxy | 7 +++---- bin/nova-api | 8 +++----- bin/nova-compute | 5 ++--- bin/nova-console | 5 ++--- bin/nova-direct-api | 11 +++++++---- bin/nova-network | 5 ++--- bin/nova-objectstore | 14 +++++++------- bin/nova-scheduler | 5 ++--- bin/nova-vncproxy | 15 ++++++--------- bin/nova-volume | 5 ++--- 10 files changed, 36 insertions(+), 44 deletions(-) diff --git a/bin/nova-ajax-console-proxy b/bin/nova-ajax-console-proxy index 2329581a2..0a789b4b9 100755 --- a/bin/nova-ajax-console-proxy +++ b/bin/nova-ajax-console-proxy @@ -24,7 +24,6 @@ from eventlet import greenthread from eventlet.green import urllib2 import exceptions -import gettext import os import sys import time @@ -38,11 +37,11 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): sys.path.insert(0, possible_topdir) -gettext.install('nova', unicode=1) from nova import flags from nova import log as logging from nova import rpc +from nova import service from nova import utils from nova import wsgi @@ -141,5 +140,5 @@ if __name__ == '__main__': acp = AjaxConsoleProxy() acp.register_listeners() server = wsgi.Server("AJAX Console Proxy", acp, port=acp_port) - server.start() - server.wait() + service.serve(server) + service.wait() diff --git a/bin/nova-api b/bin/nova-api index d2086dc92..38e2624d8 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -26,7 +26,6 @@ Starts both the EC2 and OpenStack APIs in separate greenthreads. import eventlet eventlet.monkey_patch() -import gettext import os import sys @@ -36,7 +35,6 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath( if os.path.exists(os.path.join(possible_topdir, "nova", "__init__.py")): sys.path.insert(0, possible_topdir) -gettext.install('nova', unicode=1) from nova import flags from nova import log as logging @@ -47,8 +45,8 @@ if __name__ == '__main__': utils.default_flagfile() flags.FLAGS(sys.argv) logging.setup() - services = [] + servers = [] for api in flags.FLAGS.enabled_apis: - services.append(service.WSGIService(api)) - service.serve(*services) + servers.append(service.WSGIService(api)) + service.serve(*servers) service.wait() diff --git a/bin/nova-compute b/bin/nova-compute index cd7c78def..9aef201e6 100755 --- a/bin/nova-compute +++ b/bin/nova-compute @@ -22,7 +22,6 @@ import eventlet eventlet.monkey_patch() -import gettext import os import sys @@ -34,7 +33,6 @@ POSSIBLE_TOPDIR = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'nova', '__init__.py')): sys.path.insert(0, POSSIBLE_TOPDIR) -gettext.install('nova', unicode=1) from nova import flags from nova import log as logging @@ -45,5 +43,6 @@ if __name__ == '__main__': utils.default_flagfile() flags.FLAGS(sys.argv) logging.setup() - service.serve() + server = service.Server(binary='nova-compute') + service.serve(server) service.wait() diff --git a/bin/nova-console b/bin/nova-console index 40608b995..7f76fdc29 100755 --- a/bin/nova-console +++ b/bin/nova-console @@ -21,7 +21,6 @@ import eventlet eventlet.monkey_patch() -import gettext import os import sys @@ -33,7 +32,6 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): sys.path.insert(0, possible_topdir) -gettext.install('nova', unicode=1) from nova import flags from nova import log as logging @@ -44,5 +42,6 @@ if __name__ == '__main__': utils.default_flagfile() flags.FLAGS(sys.argv) logging.setup() - service.serve() + server = service.Server(binary='nova-console') + service.serve(server) service.wait() diff --git a/bin/nova-direct-api b/bin/nova-direct-api index c6cf9b2ff..106e89ba9 100755 --- a/bin/nova-direct-api +++ b/bin/nova-direct-api @@ -20,7 +20,9 @@ """Starter script for Nova Direct API.""" -import gettext +import eventlet +eventlet.monkey_patch() + import os import sys @@ -32,12 +34,12 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): sys.path.insert(0, possible_topdir) -gettext.install('nova', unicode=1) from nova import compute from nova import flags from nova import log as logging from nova import network +from nova import service from nova import utils from nova import volume from nova import wsgi @@ -97,5 +99,6 @@ if __name__ == '__main__': with_auth, host=FLAGS.direct_host, port=FLAGS.direct_port) - server.start() - server.wait() + + service.serve(server) + service.wait() diff --git a/bin/nova-network b/bin/nova-network index 101761ef7..ce93e9354 100755 --- a/bin/nova-network +++ b/bin/nova-network @@ -22,7 +22,6 @@ import eventlet eventlet.monkey_patch() -import gettext import os import sys @@ -34,7 +33,6 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): sys.path.insert(0, possible_topdir) -gettext.install('nova', unicode=1) from nova import flags from nova import log as logging @@ -45,5 +43,6 @@ if __name__ == '__main__': utils.default_flagfile() flags.FLAGS(sys.argv) logging.setup() - service.serve() + server = service.Server(binary='nova-compute') + service.serve(server) service.wait() diff --git a/bin/nova-objectstore b/bin/nova-objectstore index 4d5aec445..c7a76e120 100755 --- a/bin/nova-objectstore +++ b/bin/nova-objectstore @@ -17,11 +17,11 @@ # License for the specific language governing permissions and limitations # under the License. -""" - Daemon for nova objectstore. Supports S3 API. -""" +"""Daemon for nova objectstore. Supports S3 API.""" + +import eventlet +eventlet.monkey_patch() -import gettext import os import sys @@ -33,10 +33,10 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): sys.path.insert(0, possible_topdir) -gettext.install('nova', unicode=1) from nova import flags from nova import log as logging +from nova import service from nova import utils from nova import wsgi from nova.objectstore import s3server @@ -54,5 +54,5 @@ if __name__ == '__main__': router, port=FLAGS.s3_port, host=FLAGS.s3_host) - server.start() - server.wait() + service.serve(server) + service.wait() diff --git a/bin/nova-scheduler b/bin/nova-scheduler index 0c205a80f..07d1c55e6 100755 --- a/bin/nova-scheduler +++ b/bin/nova-scheduler @@ -22,7 +22,6 @@ import eventlet eventlet.monkey_patch() -import gettext import os import sys @@ -34,7 +33,6 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): sys.path.insert(0, possible_topdir) -gettext.install('nova', unicode=1) from nova import flags from nova import log as logging @@ -45,5 +43,6 @@ if __name__ == '__main__': utils.default_flagfile() flags.FLAGS(sys.argv) logging.setup() - service.serve() + server = service.Server(binary='nova-compute') + service.serve(server) service.wait() diff --git a/bin/nova-vncproxy b/bin/nova-vncproxy index bdbb30a7f..dc08e2433 100755 --- a/bin/nova-vncproxy +++ b/bin/nova-vncproxy @@ -19,7 +19,8 @@ """VNC Console Proxy Server.""" import eventlet -import gettext +eventlet.monkey_patch() + import os import sys @@ -29,7 +30,6 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): sys.path.insert(0, possible_topdir) -gettext.install('nova', unicode=1) from nova import flags from nova import log as logging @@ -41,7 +41,7 @@ from nova.vnc import auth from nova.vnc import proxy -LOG = logging.getLogger('nova.vnc-proxy') +LOG = logging.getLogger('nova.vncproxy') FLAGS = flags.FLAGS @@ -81,7 +81,7 @@ if __name__ == "__main__": FLAGS(sys.argv) logging.setup() - LOG.audit(_("Starting nova-vnc-proxy node (version %s)"), + LOG.audit(_("Starting nova-vncproxy node (version %s)"), version.version_string_with_vcs()) if not (os.path.exists(FLAGS.vncproxy_wwwroot) and @@ -107,13 +107,10 @@ if __name__ == "__main__": else: with_auth = auth.VNCNovaAuthMiddleware(with_logging) - service.serve() - server = wsgi.Server("VNC Proxy", with_auth, host=FLAGS.vncproxy_host, port=FLAGS.vncproxy_port) - server.start() server.start_tcp(handle_flash_socket_policy, 843, host=FLAGS.vncproxy_host) - - server.wait() + service.serve(server) + service.wait() diff --git a/bin/nova-volume b/bin/nova-volume index 8dcdbc500..1451de44a 100755 --- a/bin/nova-volume +++ b/bin/nova-volume @@ -22,7 +22,6 @@ import eventlet eventlet.monkey_patch() -import gettext import os import sys @@ -34,7 +33,6 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): sys.path.insert(0, possible_topdir) -gettext.install('nova', unicode=1) from nova import flags from nova import log as logging @@ -45,5 +43,6 @@ if __name__ == '__main__': utils.default_flagfile() flags.FLAGS(sys.argv) logging.setup() - service.serve() + server = service.Server(binary='nova-volume') + service.serve(server) service.wait() -- cgit From 0cf36be73e7de4942f395a2a7dfeb58df5870821 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 18 Aug 2011 10:56:14 -0700 Subject: add separate api binaries --- bin/nova-api-ec2 | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ bin/nova-api-os | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100755 bin/nova-api-ec2 create mode 100755 bin/nova-api-os diff --git a/bin/nova-api-ec2 b/bin/nova-api-ec2 new file mode 100755 index 000000000..9fac7b63a --- /dev/null +++ b/bin/nova-api-ec2 @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Starter script for Nova API. + +Starts both the EC2 and OpenStack APIs in separate greenthreads. + +""" + +import eventlet +eventlet.monkey_patch() + +import os +import sys + + +possible_topdir = os.path.normpath(os.path.join(os.path.abspath( + sys.argv[0]), os.pardir, os.pardir)) +if os.path.exists(os.path.join(possible_topdir, "nova", "__init__.py")): + sys.path.insert(0, possible_topdir) + + +from nova import flags +from nova import log as logging +from nova import service +from nova import utils + +if __name__ == '__main__': + utils.default_flagfile() + flags.FLAGS(sys.argv) + logging.setup() + server = service.WSGIService('ec2') + service.serve(server) + service.wait() diff --git a/bin/nova-api-os b/bin/nova-api-os new file mode 100755 index 000000000..9d9a7b05e --- /dev/null +++ b/bin/nova-api-os @@ -0,0 +1,50 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Starter script for Nova API. + +Starts both the EC2 and OpenStack APIs in separate greenthreads. + +""" + +import eventlet +eventlet.monkey_patch() + +import os +import sys + + +possible_topdir = os.path.normpath(os.path.join(os.path.abspath( + sys.argv[0]), os.pardir, os.pardir)) +if os.path.exists(os.path.join(possible_topdir, "nova", "__init__.py")): + sys.path.insert(0, possible_topdir) + + +from nova import flags +from nova import log as logging +from nova import service +from nova import utils + +if __name__ == '__main__': + utils.default_flagfile() + flags.FLAGS(sys.argv) + logging.setup() + server = service.WSGIService('osapi') + service.serve(server) + service.wait() -- cgit From 788e5c5e94c224c3909c4f12ecc569bba3ba1c9e Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 18 Aug 2011 11:00:47 -0700 Subject: remove signal handling and clean up service.serve --- nova/service.py | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/nova/service.py b/nova/service.py index e0735d26f..8ffd39629 100644 --- a/nova/service.py +++ b/nova/service.py @@ -21,7 +21,6 @@ import inspect import os -import signal import eventlet import greenlet @@ -346,33 +345,21 @@ def serve(*services): global _launcher if not _launcher: _launcher = Launcher() - signal.signal(signal.SIGTERM, lambda *args: _launcher.stop()) - try: - if not services: - services = [Service.create()] - except Exception: - logging.exception('in Service.create()') - raise - finally: - # After we've loaded up all our dynamic bits, check - # whether we should print help - flags.DEFINE_flag(flags.HelpFlag()) - flags.DEFINE_flag(flags.HelpshortFlag()) - flags.DEFINE_flag(flags.HelpXMLFlag()) - FLAGS.ParseNewFlags() - - name = '_'.join(x.name for x in services) - logging.debug(_('Serving %s'), name) - logging.debug(_('Full set of FLAGS:')) - for flag in FLAGS: - flag_get = FLAGS.get(flag, None) - logging.debug('%(flag)s : %(flag_get)s' % locals()) - for x in services: _launcher.launch_service(x) def wait(): + # After we've loaded up all our dynamic bits, check + # whether we should print help + flags.DEFINE_flag(flags.HelpFlag()) + flags.DEFINE_flag(flags.HelpshortFlag()) + flags.DEFINE_flag(flags.HelpXMLFlag()) + FLAGS.ParseNewFlags() + logging.debug(_('Full set of FLAGS:')) + for flag in FLAGS: + flag_get = FLAGS.get(flag, None) + logging.debug('%(flag)s : %(flag_get)s' % locals()) try: _launcher.wait() except KeyboardInterrupt: -- cgit From 97552f05d5d26e596ddf0cda8169f3a5d131a55a Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 18 Aug 2011 11:28:02 -0700 Subject: fix typo --- bin/nova-compute | 2 +- bin/nova-console | 2 +- bin/nova-network | 2 +- bin/nova-scheduler | 2 +- bin/nova-volume | 2 +- nova/service.py | 35 +++++++++++++++++++---------------- 6 files changed, 24 insertions(+), 21 deletions(-) diff --git a/bin/nova-compute b/bin/nova-compute index 9aef201e6..5239fae72 100755 --- a/bin/nova-compute +++ b/bin/nova-compute @@ -43,6 +43,6 @@ if __name__ == '__main__': utils.default_flagfile() flags.FLAGS(sys.argv) logging.setup() - server = service.Server(binary='nova-compute') + server = service.Service.create(binary='nova-compute') service.serve(server) service.wait() diff --git a/bin/nova-console b/bin/nova-console index 7f76fdc29..22f6ef171 100755 --- a/bin/nova-console +++ b/bin/nova-console @@ -42,6 +42,6 @@ if __name__ == '__main__': utils.default_flagfile() flags.FLAGS(sys.argv) logging.setup() - server = service.Server(binary='nova-console') + server = service.Service.create(binary='nova-console') service.serve(server) service.wait() diff --git a/bin/nova-network b/bin/nova-network index ce93e9354..57759d30a 100755 --- a/bin/nova-network +++ b/bin/nova-network @@ -43,6 +43,6 @@ if __name__ == '__main__': utils.default_flagfile() flags.FLAGS(sys.argv) logging.setup() - server = service.Server(binary='nova-compute') + server = service.Service.create(binary='nova-network') service.serve(server) service.wait() diff --git a/bin/nova-scheduler b/bin/nova-scheduler index 07d1c55e6..3b627e62d 100755 --- a/bin/nova-scheduler +++ b/bin/nova-scheduler @@ -43,6 +43,6 @@ if __name__ == '__main__': utils.default_flagfile() flags.FLAGS(sys.argv) logging.setup() - server = service.Server(binary='nova-compute') + server = service.Service.create(binary='nova-compute') service.serve(server) service.wait() diff --git a/bin/nova-volume b/bin/nova-volume index 1451de44a..5405aebbb 100755 --- a/bin/nova-volume +++ b/bin/nova-volume @@ -43,6 +43,6 @@ if __name__ == '__main__': utils.default_flagfile() flags.FLAGS(sys.argv) logging.setup() - server = service.Server(binary='nova-volume') + server = service.Service.create(binary='nova-volume') service.serve(server) service.wait() diff --git a/nova/service.py b/nova/service.py index 8ffd39629..959e79052 100644 --- a/nova/service.py +++ b/nova/service.py @@ -67,24 +67,24 @@ class Launcher(object): self._services = [] @staticmethod - def run_service(service): - """Start and wait for a service to finish. + def run_server(server): + """Start and wait for a server to finish. - :param service: Service to run and wait for. + :param service: Server to run and wait for. :returns: None """ - service.start() - service.wait() + server.start() + server.wait() - def launch_service(self, service): - """Load and start the given service. + def launch_server(self, server): + """Load and start the given server. - :param service: The service you would like to start. + :param server: The server you would like to start. :returns: None """ - gt = eventlet.spawn(self.run_service, service) + gt = eventlet.spawn(self.run_server, server) self._services.append(gt) def stop(self): @@ -110,13 +110,16 @@ class Launcher(object): class Service(object): - """Base class for workers that run on hosts.""" + """Service object for binaries running on hosts. + + A service takes a manager and enables rpc by listening to queues based + on topic. It also periodically runs tasks on the manager and reports + it state to the database services table.""" def __init__(self, host, binary, topic, manager, report_interval=None, periodic_interval=None, *args, **kwargs): self.host = host self.binary = binary - self.name = binary self.topic = topic self.manager_class_name = manager manager_class = utils.import_class(self.manager_class_name) @@ -289,9 +292,9 @@ class WSGIService(object): """Provides ability to launch API from a 'paste' configuration.""" def __init__(self, name, loader=None): - """Initialize, but do not start the WSGI service. + """Initialize, but do not start the WSGI server. - :param name: The name of the WSGI service given to the loader. + :param name: The name of the WSGI server given to the loader. :param loader: Loads the WSGI application using the given name. :returns: None @@ -341,12 +344,12 @@ class WSGIService(object): _launcher = None -def serve(*services): +def serve(*servers): global _launcher if not _launcher: _launcher = Launcher() - for x in services: - _launcher.launch_service(x) + for server in servers: + _launcher.launch_server(server) def wait(): -- cgit From 05e8c1755d8fde5a9a3bde02e339938f670694c6 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 18 Aug 2011 11:28:43 -0700 Subject: one more --- bin/nova-scheduler | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/nova-scheduler b/bin/nova-scheduler index 3b627e62d..2e168cbc6 100755 --- a/bin/nova-scheduler +++ b/bin/nova-scheduler @@ -43,6 +43,6 @@ if __name__ == '__main__': utils.default_flagfile() flags.FLAGS(sys.argv) logging.setup() - server = service.Service.create(binary='nova-compute') + server = service.Service.create(binary='nova-scheduler') service.serve(server) service.wait() -- cgit From a4d63f18971bad12ea812c63bcee35d8070333f7 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 18 Aug 2011 11:31:28 -0700 Subject: fix docstrings in new api bins --- bin/nova-api-ec2 | 6 +----- bin/nova-api-os | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/bin/nova-api-ec2 b/bin/nova-api-ec2 index 9fac7b63a..df50f713d 100755 --- a/bin/nova-api-ec2 +++ b/bin/nova-api-ec2 @@ -17,11 +17,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Starter script for Nova API. - -Starts both the EC2 and OpenStack APIs in separate greenthreads. - -""" +"""Starter script for Nova EC2 API.""" import eventlet eventlet.monkey_patch() diff --git a/bin/nova-api-os b/bin/nova-api-os index 9d9a7b05e..374e850ea 100755 --- a/bin/nova-api-os +++ b/bin/nova-api-os @@ -17,11 +17,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Starter script for Nova API. - -Starts both the EC2 and OpenStack APIs in separate greenthreads. - -""" +"""Starter script for Nova OS API.""" import eventlet eventlet.monkey_patch() -- cgit From b98c14c411ae09d9a8b5b2112d0e1b01b71ced44 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 18 Aug 2011 14:34:14 -0400 Subject: Don't send 'injected_files' and 'admin_pass' to db.update. --- nova/compute/manager.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 66458fb36..47e7864c4 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -393,11 +393,12 @@ class ComputeManager(manager.SchedulerDependentManager): updates['host'] = self.host updates['launched_on'] = self.host # NOTE(vish): used by virt but not in database - updates['injected_files'] = kwargs.get('injected_files', []) - updates['admin_pass'] = kwargs.get('admin_password', None) instance = self.db.instance_update(context, instance_id, updates) + instance['injected_files'] = kwargs.get('injected_files', []) + instance['admin_pass'] = kwargs.get('admin_password', None) + self.db.instance_set_state(context, instance_id, power_state.NOSTATE, -- cgit From 6b8c26d230d06c35921e2e0a2d30d9d3d745eff4 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 18 Aug 2011 14:44:10 -0400 Subject: Remove old comment. --- nova/compute/manager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 47e7864c4..091b3b6b2 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -392,7 +392,6 @@ class ComputeManager(manager.SchedulerDependentManager): updates = {} updates['host'] = self.host updates['launched_on'] = self.host - # NOTE(vish): used by virt but not in database instance = self.db.instance_update(context, instance_id, updates) -- cgit From af9681bc82d7509cb2f65d213bd4d8ae24286663 Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Thu, 18 Aug 2011 13:47:09 -0500 Subject: Moved compute calls to their own handler --- nova/compute/api.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index e909e9959..0c5d4349d 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -1068,14 +1068,20 @@ class API(base.Base): """Unpause the given instance.""" self._cast_compute_message('unpause_instance', context, instance_id) + def _make_compute_call_for_host(self context, host, params): + """Call method deliberately designed to make host/service only calls""" + queue = self.db.queue_get_for(context, FLAGS.compute_topic, host) + kwargs = {'method': method, 'args': params} + return rpc.call(context, queue, kwargs) + def set_host_enabled(self, context, host, enabled): """Sets the specified host's ability to accept new instances.""" - return self._call_compute_message("set_host_enabled", context, + return self._make_compute_call_for_host("set_host_enabled", context, host=host, params={"enabled": enabled}) def host_power_action(self, context, host, action): """Reboots, shuts down or powers up the host.""" - return self._call_compute_message("host_power_action", context, + return self._make_compute_call_for_host("host_power_action", context, host=host, params={"action": action}) @scheduler_api.reroute_compute("diagnostics") -- cgit From 69996e83f10387b83bdc7e5e76b62fe67ea6c2ab Mon Sep 17 00:00:00 2001 From: "matt.dietz@rackspace.com" <> Date: Thu, 18 Aug 2011 13:55:38 -0500 Subject: Syntax error --- 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 0c5d4349d..3110cd92d 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -1068,7 +1068,7 @@ class API(base.Base): """Unpause the given instance.""" self._cast_compute_message('unpause_instance', context, instance_id) - def _make_compute_call_for_host(self context, host, params): + def _make_compute_call_for_host(self, context, host, params): """Call method deliberately designed to make host/service only calls""" queue = self.db.queue_get_for(context, FLAGS.compute_topic, host) kwargs = {'method': method, 'args': params} -- cgit From c6c004c44595f218f66eee8f6f9173c6108be8a4 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 18 Aug 2011 14:39:25 -0500 Subject: Updated the distributed scheduler docs with the latest changes to the classes. --- doc/source/devref/distributed_scheduler.rst | 56 ++++++++++++++-------------- doc/source/images/base_scheduler.png | Bin 0 -> 17068 bytes doc/source/images/zone_overview.png | Bin 0 -> 51587 bytes 3 files changed, 27 insertions(+), 29 deletions(-) create mode 100644 doc/source/images/base_scheduler.png create mode 100755 doc/source/images/zone_overview.png diff --git a/doc/source/devref/distributed_scheduler.rst b/doc/source/devref/distributed_scheduler.rst index e33fda4d2..c63e62f7f 100644 --- a/doc/source/devref/distributed_scheduler.rst +++ b/doc/source/devref/distributed_scheduler.rst @@ -31,9 +31,9 @@ This is the purpose of the Distributed Scheduler (DS). The DS utilizes the Capab So, how does this all work? -This document will explain the strategy employed by the `ZoneAwareScheduler` and its derivations. You should read the :doc:`devguide/zones` documentation before reading this. +This document will explain the strategy employed by the `BaseScheduler`, which is the base for all schedulers designed to work across zones, and its derivations. You should read the :doc:`devguide/zones` documentation before reading this. - .. image:: /images/zone_aware_scheduler.png + .. image:: /images/base_scheduler.png Costs & Weights --------------- @@ -52,32 +52,32 @@ This Weight is computed for each Instance requested. If the customer asked for 1 .. image:: /images/costs_weights.png -nova.scheduler.zone_aware_scheduler.ZoneAwareScheduler +nova.scheduler.base_scheduler.BaseScheduler ------------------------------------------------------ -As we explained in the Zones documentation, each Scheduler has a `ZoneManager` object that collects "Capabilities" about child Zones and each of the services running in the current Zone. The `ZoneAwareScheduler` uses this information to make its decisions. +As we explained in the Zones documentation, each Scheduler has a `ZoneManager` object that collects "Capabilities" about child Zones and each of the services running in the current Zone. The `BaseScheduler` uses this information to make its decisions. Here is how it works: 1. The compute nodes are filtered and the nodes remaining are weighed. - 2. Filtering the hosts is a simple matter of ensuring the compute node has ample resources (CPU, RAM, Disk, etc) to fulfil the request. + 2. Filtering the hosts is a simple matter of ensuring the compute node has ample resources (CPU, RAM, Disk, etc) to fulfil the request. 3. Weighing of the remaining compute nodes assigns a number based on their suitability for the request. 4. The same request is sent to each child Zone and step #1 is done there too. The resulting weighted list is returned to the parent. 5. The parent Zone sorts and aggregates all the weights and a final build plan is constructed. 6. The build plan is executed upon. Concurrently, instance create requests are sent to each of the selected hosts, be they local or in a child zone. Child Zones may forward the requests to their child Zones as needed. - .. image:: /images/zone_aware_overview.png + .. image:: /images/zone_overview.png -`ZoneAwareScheduler` by itself is not capable of handling all the provisioning itself. Derived classes are used to select which host filtering and weighing strategy will be used. +`BaseScheduler` by itself is not capable of handling all the provisioning itself. You should also specify the filter classes and weighting classes to be used in determining which host is selected for new instance creation. Filtering and Weighing ---------------------- -The filtering (excluding compute nodes incapable of fulfilling the request) and weighing (computing the relative "fitness" of a compute node to fulfill the request) rules used are very subjective operations ... Service Providers will probably have a very different set of filtering and weighing rules than private cloud administrators. The filtering and weighing aspects of the `ZoneAwareScheduler` are flexible and extensible. +The filtering (excluding compute nodes incapable of fulfilling the request) and weighing (computing the relative "fitness" of a compute node to fulfill the request) rules used are very subjective operations ... Service Providers will probably have a very different set of filtering and weighing rules than private cloud administrators. The filtering and weighing aspects of the `BaseScheduler` are flexible and extensible. .. image:: /images/filtering.png Requesting a new instance ------------------------- -Prior to the `ZoneAwareScheduler`, to request a new instance, a call was made to `nova.compute.api.create()`. The type of instance created depended on the value of the `InstanceType` record being passed in. The `InstanceType` determined the amount of disk, CPU, RAM and network required for the instance. Administrators can add new `InstanceType` records to suit their needs. For more complicated instance requests we need to go beyond the default fields in the `InstanceType` table. +Prior to the `BaseScheduler`, to request a new instance, a call was made to `nova.compute.api.create()`. The type of instance created depended on the value of the `InstanceType` record being passed in. The `InstanceType` determined the amount of disk, CPU, RAM and network required for the instance. Administrators can add new `InstanceType` records to suit their needs. For more complicated instance requests we need to go beyond the default fields in the `InstanceType` table. `nova.compute.api.create()` performed the following actions: 1. it validated all the fields passed into it. @@ -89,11 +89,11 @@ Prior to the `ZoneAwareScheduler`, to request a new instance, a call was made to .. image:: /images/nova.compute.api.create.png -Generally, the standard schedulers (like `ChanceScheduler` and `AvailabilityZoneScheduler`) only operate in the current Zone. They have no concept of child Zones. +Generally, the simplest schedulers (like `ChanceScheduler` and `AvailabilityZoneScheduler`) only operate in the current Zone. They have no concept of child Zones. The problem with this approach is each request is scattered amongst each of the schedulers. If we are asking for 1000 instances, each scheduler gets the requests one-at-a-time. There is no possability of optimizing the requests to take into account all 1000 instances as a group. We call this Single-Shot vs. All-at-Once. -For the `ZoneAwareScheduler` we need to use the All-at-Once approach. We need to consider all the hosts across all the Zones before deciding where they should reside. In order to handle this we have a new method `nova.compute.api.create_all_at_once()`. This method does things a little differently: +For the `BaseScheduler` we need to use the All-at-Once approach. We need to consider all the hosts across all the Zones before deciding where they should reside. In order to handle this we have a new method `nova.compute.api.create_all_at_once()`. This method does things a little differently: 1. it validates all the fields passed into it. 2. it creates a single `reservation_id` for all of instances created. This is a UUID. 3. it creates a single `run_instance` request in the scheduler queue @@ -109,21 +109,19 @@ For the `ZoneAwareScheduler` we need to use the All-at-Once approach. We need to The Catch --------- -This all seems pretty straightforward but, like most things, there's a catch. Zones are expected to operate in complete isolation from each other. Each Zone has its own AMQP service, database and set of Nova services. But, for security reasons Zones should never leak information about the architectural layout internally. That means Zones cannot leak information about hostnames or service IP addresses outside of its world. +This all seems pretty straightforward but, like most things, there's a catch. Zones are expected to operate in complete isolation from each other. Each Zone has its own AMQP service, database and set of Nova services. But for security reasons Zones should never leak information about the architectural layout internally. That means Zones cannot leak information about hostnames or service IP addresses outside of its world. -When `POST /zones/select` is called to estimate which compute node to use, time passes until the `POST /servers` call is issued. If we only passed the weight back from the `select` we would have to re-compute the appropriate compute node for the create command ... and we could end up with a different host. Somehow we need to remember the results of our computations and pass them outside of the Zone. Now, we could store this information in the local database and return a reference to it, but remember that the vast majority of weights are going to be ignored. Storing them in the database would result in a flood of disk access and then we have to clean up all these entries periodically. Recall that there are going to be many many `select` calls issued to child Zones asking for estimates. +When `POST /zones/select` is called to estimate which compute node to use, time passes until the `POST /servers` call is issued. If we only passed the weight back from the `select` we would have to re-compute the appropriate compute node for the create command ... and we could end up with a different host. Somehow we need to remember the results of our computations and pass them outside of the Zone. Now, we could store this information in the local database and return a reference to it, but remember that the vast majority of weights are going to be ignored. Storing them in the database would result in a flood of disk access and then we have to clean up all these entries periodically. Recall that there are going to be many, many `select` calls issued to child Zones asking for estimates. -Instead, we take a rather innovative approach to the problem. We encrypt all the child zone internal details and pass them back the to parent Zone. If the parent zone decides to use a child Zone for the instance it simply passes the encrypted data back to the child during the `POST /servers` call as an extra parameter. The child Zone can then decrypt the hint and go directly to the Compute node previously selected. If the estimate isn't used, it is simply discarded by the parent. It's for this reason that it is so important that each Zone defines a unique encryption key via `--build_plan_encryption_key` +Instead, we take a rather innovative approach to the problem. We encrypt all the child Zone internal details and pass them back the to parent Zone. In the case of a nested Zone layout, each nesting layer will encrypt the data from all of its children and pass that to its parent Zone. In the case of nested child Zones, each Zone re-encrypts the weighted list results and passes those values to the parent. Every Zone interface adds another layer of encryption, using its unique key. -In the case of nested child Zones, each Zone re-encrypts the weighted list results and passes those values to the parent. +Once a host is selected, it will either be local to the Zone that received the initial API call, or one of its child Zones. In the latter case, the parent Zone it simply passes the encrypted data for the selected host back to each of its child Zones during the `POST /servers` call as an extra parameter. If the child Zone can decrypt the data, then it is the correct Zone for the selected host; all other Zones will not be able to decrypt the data and will discard the request. This is why it is critical that each Zone has a unique value specified in its config in `--build_plan_encryption_key`: it controls the ability to locate the selected host without having to hard-code path information or other identifying information. The child Zone can then act on the decrypted data and either go directly to the Compute node previously selected if it is located in that Zone, or repeat the process with its child Zones until the target Zone containing the selected host is reached. -Throughout the `nova.api.openstack.servers`, `nova.api.openstack.zones`, `nova.compute.api.create*` and `nova.scheduler.zone_aware_scheduler` code you'll see references to `blob` and `child_blob`. These are the encrypted hints about which Compute node to use. +Throughout the `nova.api.openstack.servers`, `nova.api.openstack.zones`, `nova.compute.api.create*` and `nova.scheduler.base_scheduler` code you'll see references to `blob` and `child_blob`. These are the encrypted hints about which Compute node to use. Reservation IDs --------------- -NOTE: The features described in this section are related to the up-coming 'merge-4' branch. - The OpenStack API allows a user to list all the instances they own via the `GET /servers/` command or the details on a particular instance via `GET /servers/###`. This mechanism is usually sufficient since OS API only allows for creating one instance at a time, unlike the EC2 API which allows you to specify a quantity of instances to be created. NOTE: currently the `GET /servers` command is not Zone-aware since all operations done in child Zones are done via a single administrative account. Therefore, asking a child Zone to `GET /servers` would return all the active instances ... and that would not be what the user intended. Later, when the Keystone Auth system is integrated with Nova, this functionality will be enabled. @@ -137,23 +135,23 @@ Finally, we need to give the user a way to get information on each of the instan Host Filter ----------- -As we mentioned earlier, filtering hosts is a very deployment-specific process. Service Providers may have a different set of criteria for filtering Compute nodes than a University. To faciliate this the `nova.scheduler.host_filter` module supports a variety of filtering strategies as well as an easy means for plugging in your own algorithms. +As we mentioned earlier, filtering hosts is a very deployment-specific process. Service Providers may have a different set of criteria for filtering Compute nodes than a University. To faciliate this the `nova.scheduler.filters` module supports a variety of filtering strategies as well as an easy means for plugging in your own algorithms. -The filter used is determined by the `--default_host_filter` flag, which points to a Python Class. By default this flag is set to `nova.scheduler.host_filter.AllHostsFilter` which simply returns all available hosts. But there are others: +The filter used is determined by the `--default_host_filters` flag, which points to a Python Class. By default this flag is set to `[AllHostsFilter]` which simply returns all available hosts. But there are others: - * `nova.scheduler.host_filter.InstanceTypeFilter` provides host filtering based on the memory and disk size specified in the `InstanceType` record passed into `run_instance`. + * `InstanceTypeFilter` provides host filtering based on the memory and disk size specified in the `InstanceType` record passed into `run_instance`. - * `nova.scheduler.host_filter.JSONFilter` filters hosts based on simple JSON expression grammar. Using a LISP-like JSON structure the caller can request instances based on criteria well beyond what `InstanceType` specifies. See `nova.tests.test_host_filter` for examples. + * `JSONFilter` filters hosts based on simple JSON expression grammar. Using a LISP-like JSON structure the caller can request instances based on criteria well beyond what `InstanceType` specifies. See `nova.tests.test_host_filter` for examples. -To create your own `HostFilter` the user simply has to derive from `nova.scheduler.host_filter.HostFilter` and implement two methods: `instance_type_to_filter` and `filter_hosts`. Since Nova is currently dependent on the `InstanceType` structure, the `instance_type_to_filter` method should take an `InstanceType` and turn it into an internal data structure usable by your filter. This is for backward compatibility with existing OpenStack and EC2 API calls. If you decide to create your own call for creating instances not based on `Flavors` or `InstanceTypes` you can ignore this method. The real work is done in `filter_hosts` which must return a list of host tuples for each appropriate host. The set of all available hosts is in the `ZoneManager` object passed into the call as well as the filter query. The host tuple contains (``, ``) where `` is whatever you want it to be. +To create your own `HostFilter` the user simply has to derive from `nova.scheduler.filters.AbstractHostFilter` and implement two methods: `instance_type_to_filter` and `filter_hosts`. Since Nova is currently dependent on the `InstanceType` structure, the `instance_type_to_filter` method should take an `InstanceType` and turn it into an internal data structure usable by your filter. This is for backward compatibility with existing OpenStack and EC2 API calls. If you decide to create your own call for creating instances not based on `Flavors` or `InstanceTypes` you can ignore this method. The real work is done in `filter_hosts` which must return a list of host tuples for each appropriate host. The set of available hosts is in the `host_list` parameter passed into the call as well as the filter query. The host tuple contains (``, ``) where `` is whatever you want it to be. By default, it is the capabilities reported by the host. Cost Scheduler Weighing ----------------------- -Every `ZoneAwareScheduler` derivation must also override the `weigh_hosts` method. This takes the list of filtered hosts (generated by the `filter_hosts` method) and returns a list of weight dicts. The weight dicts must contain two keys: `weight` and `hostname` where `weight` is simply an integer (lower is better) and `hostname` is the name of the host. The list does not need to be sorted, this will be done by the `ZoneAwareScheduler` base class when all the results have been assembled. +Every `BaseScheduler` subclass should also override the `weigh_hosts` method. This takes the list of filtered hosts (generated by the `filter_hosts` method) and returns a list of weight dicts. The weight dicts must contain two keys: `weight` and `hostname` where `weight` is simply an integer (lower is better) and `hostname` is the name of the host. The list does not need to be sorted, this will be done by the `BaseScheduler` when all the results have been assembled. -Simple Zone Aware Scheduling +Simple Scheduling Across Zones ---------------------------- -The easiest way to get started with the `ZoneAwareScheduler` is to use the `nova.scheduler.host_filter.HostFilterScheduler`. This scheduler uses the default Host Filter and the `weight_hosts` method simply returns a weight of 1 for all hosts. But, from this, you can see calls being routed from Zone to Zone and follow the flow of things. +The `BaseScheduler` uses the default `filter_hosts` method, which will use either any filters specified in the request's `filter` parameter, or, if that is not specified, the filters specified in the `FLAGS.default_host_filters` setting. Its `weight_hosts` method simply returns a weight of 1 for all hosts. But, from this, you can see calls being routed from Zone to Zone and follow the flow of things. The `--scheduler_driver` flag is how you specify the scheduler class name. @@ -168,14 +166,14 @@ All this Zone and Distributed Scheduler stuff can seem a little daunting to conf --enable_zone_routing=true --zone_name=zone1 --build_plan_encryption_key=c286696d887c9aa0611bbb3e2025a45b - --scheduler_driver=nova.scheduler.host_filter.HostFilterScheduler - --default_host_filter=nova.scheduler.host_filter.AllHostsFilter + --scheduler_driver=nova.scheduler.base_scheduler.BaseScheduler + --default_host_filter=nova.scheduler.filters.AllHostsFilter `--allow_admin_api` must be set for OS API to enable the new `/zones/*` commands. `--enable_zone_routing` must be set for OS API commands such as `create()`, `pause()` and `delete()` to get routed from Zone to Zone when looking for instances. `--zone_name` is only required in child Zones. The default Zone name is `nova`, but you may want to name your child Zones something useful. Duplicate Zone names are not an issue. `build_plan_encryption_key` is the SHA-256 key for encrypting/decrypting the Host information when it leaves a Zone. Be sure to change this key for each Zone you create. Do not duplicate keys. -`scheduler_driver` is the real workhorse of the operation. For Distributed Scheduler, you need to specify a class derived from `nova.scheduler.zone_aware_scheduler.ZoneAwareScheduler`. +`scheduler_driver` is the real workhorse of the operation. For Distributed Scheduler, you need to specify a class derived from `nova.scheduler.base_scheduler.BaseScheduler`. `default_host_filter` is the host filter to be used for filtering candidate Compute nodes. Some optional flags which are handy for debugging are: diff --git a/doc/source/images/base_scheduler.png b/doc/source/images/base_scheduler.png new file mode 100644 index 000000000..75d029338 Binary files /dev/null and b/doc/source/images/base_scheduler.png differ diff --git a/doc/source/images/zone_overview.png b/doc/source/images/zone_overview.png new file mode 100755 index 000000000..cc891df0a Binary files /dev/null and b/doc/source/images/zone_overview.png differ -- cgit From 19495e51bc86bf1bc333759e3825ab4b5592ff66 Mon Sep 17 00:00:00 2001 From: Matt Dietz Date: Thu, 18 Aug 2011 19:40:59 +0000 Subject: Need to pass the action --- nova/compute/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 3110cd92d..598270ba1 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -1068,10 +1068,10 @@ class API(base.Base): """Unpause the given instance.""" self._cast_compute_message('unpause_instance', context, instance_id) - def _make_compute_call_for_host(self, context, host, params): + def _make_compute_call_for_host(self, action, context, host, params): """Call method deliberately designed to make host/service only calls""" queue = self.db.queue_get_for(context, FLAGS.compute_topic, host) - kwargs = {'method': method, 'args': params} + kwargs = {'method': action, 'args': params} return rpc.call(context, queue, kwargs) def set_host_enabled(self, context, host, enabled): -- cgit From fe28c88a6bfff9d8e0d83751ab89e83173aaf092 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 18 Aug 2011 14:56:22 -0500 Subject: Review feedback. --- nova/api/openstack/contrib/rescue.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/contrib/rescue.py b/nova/api/openstack/contrib/rescue.py index 65ce2874b..5ee071696 100644 --- a/nova/api/openstack/contrib/rescue.py +++ b/nova/api/openstack/contrib/rescue.py @@ -48,7 +48,7 @@ class Rescue(exts.ExtensionDescriptor): self.compute_api.unrescue(context, instance_id) except Exception, e: LOG.exception(_("Error in %(action)s: %(e)s") % locals()) - return faults.Fault(exc.HTTPBadRequest()) + return faults.Fault(exc.HTTPInternalServerError()) return webob.Response(status_int=202) @@ -60,7 +60,7 @@ class Rescue(exts.ExtensionDescriptor): return "Rescue" def get_alias(self): - return "rescue" + return "os-rescue" def get_description(self): return "Instance rescue mode" -- cgit From 508b45a3fda9caa92c90282045495acb6e2f638b Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 18 Aug 2011 15:08:51 -0500 Subject: Better docstring for _unrescue(). --- nova/api/openstack/contrib/rescue.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nova/api/openstack/contrib/rescue.py b/nova/api/openstack/contrib/rescue.py index 5ee071696..a30ed6dff 100644 --- a/nova/api/openstack/contrib/rescue.py +++ b/nova/api/openstack/contrib/rescue.py @@ -53,7 +53,11 @@ class Rescue(exts.ExtensionDescriptor): return webob.Response(status_int=202) def _unrescue(self, input_dict, req, instance_id): - """Unrescue an instance.""" + """Unrescue an instance. + + We pass exit_rescue=True here so _rescue() knows we would like to exit + rescue mode. + """ self._rescue(input_dict, req, instance_id, exit_rescue=True) def get_name(self): -- cgit From bbcb84a5fed2c537bd6d2143e344fa96f669d231 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Thu, 18 Aug 2011 20:25:32 +0000 Subject: DB password should be an empty string for MySQLdb --- nova/db/sqlalchemy/session.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nova/db/sqlalchemy/session.py b/nova/db/sqlalchemy/session.py index 07f281938..643e2338e 100644 --- a/nova/db/sqlalchemy/session.py +++ b/nova/db/sqlalchemy/session.py @@ -73,9 +73,11 @@ def get_engine(): elif MySQLdb and "mysql" in connection_dict.drivername: LOG.info(_("Using mysql/eventlet db_pool.")) + # MySQLdb won't accept 'None' in the password field + password = connection_dict.password or '' pool_args = { "db": connection_dict.database, - "passwd": connection_dict.password, + "passwd": password, "host": connection_dict.host, "user": connection_dict.username, "min_size": FLAGS.sql_min_pool_size, -- 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 56129e4a0b0c5cb2f8766e023bcaff77fc990008 Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Thu, 18 Aug 2011 13:45:45 -0700 Subject: Added more unit testcases for userdata functionality --- nova/tests/test_metadata.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/nova/tests/test_metadata.py b/nova/tests/test_metadata.py index bfc7a6d44..b06e5c136 100644 --- a/nova/tests/test_metadata.py +++ b/nova/tests/test_metadata.py @@ -23,12 +23,21 @@ import httplib import webob +from nova import exception from nova import test from nova import wsgi from nova.api.ec2 import metadatarequesthandler from nova.db.sqlalchemy import api +USER_DATA_STRING = ("This is an encoded string") +ENCODE_USER_DATA_STRING = base64.b64encode(USER_DATA_STRING) + + +def return_non_existing_server_by_address(context, address): + raise exception.NotFound() + + class MetadataTestCase(test.TestCase): """Test that metadata is returning proper values.""" @@ -79,3 +88,34 @@ class MetadataTestCase(test.TestCase): self.stubs.Set(api, 'security_group_get_by_instance', sg_get) self.assertEqual(self.request('/meta-data/security-groups'), 'default\nother') + + def test_user_data_non_existing_fixed_address(self): + self.stubs.Set(api, 'instance_get_all_by_filters', + return_non_existing_server_by_address) + request = webob.Request.blank('/user-data') + request.remote_addr = "127.1.1.1" + response = request.get_response(self.app) + self.assertEqual(response.status_int, 404) + + def test_user_data_none_fixed_address(self): + self.stubs.Set(api, 'instance_get_all_by_filters', + return_non_existing_server_by_address) + request = webob.Request.blank('/user-data') + request.remote_addr = None + response = request.get_response(self.app) + self.assertEqual(response.status_int, 500) + + def test_user_data_invalid_url(self): + request = webob.Request.blank('/user-data-invalid') + request.remote_addr = "127.0.0.1" + response = request.get_response(self.app) + self.assertEqual(response.status_int, 404) + + def test_user_data_with_use_forwarded_header(self): + self.instance['user_data'] = ENCODE_USER_DATA_STRING + self.flags(use_forwarded_for=True) + request = webob.Request.blank('/user-data') + request.remote_addr = "127.0.0.1" + response = request.get_response(self.app) + self.assertEqual(response.status_int, 200) + self.assertEqual(response.body, USER_DATA_STRING) -- cgit From 041dcdb2eba968d5be17c9a10bf333e1307f0537 Mon Sep 17 00:00:00 2001 From: Alex Meade Date: Thu, 18 Aug 2011 16:56:23 -0400 Subject: Added 'update' method to ServersXMLSerializer --- nova/api/openstack/servers.py | 6 ++ nova/tests/api/openstack/test_servers.py | 121 +++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 335ecad86..41e63ec3c 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -923,6 +923,12 @@ class ServerXMLSerializer(wsgi.XMLDictSerializer): node.setAttribute('adminPass', server_dict['server']['adminPass']) return self.to_xml_string(node, True) + def update(self, server_dict): + xml_doc = minidom.Document() + node = self._server_to_xml_detailed(xml_doc, + server_dict['server']) + return self.to_xml_string(node, True) + def create_resource(version='1.0'): controller = { diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index a510d7d97..437620854 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -3740,3 +3740,124 @@ class ServerXMLSerializationTest(test.TestCase): """.replace(" ", "") % (locals())) self.assertEqual(expected.toxml(), actual.toxml()) + + def test_update(self): + serializer = servers.ServerXMLSerializer() + + fixture = { + "server": { + "id": 1, + "uuid": FAKE_UUID, + 'created': self.TIMESTAMP, + 'updated': self.TIMESTAMP, + "progress": 0, + "name": "test_server", + "status": "BUILD", + "hostId": 'e4d909c290d0fb1ca068ffaddf22cbd0', + "image": { + "id": "5", + "links": [ + { + "rel": "bookmark", + "href": self.IMAGE_BOOKMARK, + }, + ], + }, + "flavor": { + "id": "1", + "links": [ + { + "rel": "bookmark", + "href": self.FLAVOR_BOOKMARK, + }, + ], + }, + "addresses": { + "network_one": [ + { + "version": 4, + "addr": "67.23.10.138", + }, + { + "version": 6, + "addr": "::babe:67.23.10.138", + }, + ], + "network_two": [ + { + "version": 4, + "addr": "67.23.10.139", + }, + { + "version": 6, + "addr": "::babe:67.23.10.139", + }, + ], + }, + "metadata": { + "Open": "Stack", + "Number": "1", + }, + 'links': [ + { + 'href': self.SERVER_HREF, + 'rel': 'self', + }, + { + 'href': self.SERVER_BOOKMARK, + 'rel': 'bookmark', + }, + ], + } + } + + output = serializer.serialize(fixture, 'update') + actual = minidom.parseString(output.replace(" ", "")) + + expected_server_href = self.SERVER_HREF + expected_server_bookmark = self.SERVER_BOOKMARK + expected_image_bookmark = self.IMAGE_BOOKMARK + expected_flavor_bookmark = self.FLAVOR_BOOKMARK + expected_now = self.TIMESTAMP + expected_uuid = FAKE_UUID + expected = minidom.parseString(""" + + + + + + + + + + + + Stack + + + 1 + + + + + + + + + + + + + + """.replace(" ", "") % (locals())) + + self.assertEqual(expected.toxml(), actual.toxml()) -- cgit From f86a5cc4bc43923077ffe1d4098e550841f1c4f0 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 18 Aug 2011 15:58:12 -0500 Subject: Review feedback. --- nova/api/openstack/contrib/rescue.py | 40 +++++++++++++++++------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/nova/api/openstack/contrib/rescue.py b/nova/api/openstack/contrib/rescue.py index a30ed6dff..399bb7f35 100644 --- a/nova/api/openstack/contrib/rescue.py +++ b/nova/api/openstack/contrib/rescue.py @@ -26,39 +26,37 @@ from nova.api.openstack import faults LOG = logging.getLogger("nova.api.contrib.rescue") +def wrap_errors(fn): + """"Ensure errors are not passed along.""" + def wrapped(*args): + try: + fn(*args) + except Exception, e: + return faults.Fault(exc.HTTPInternalServerError()) + return wrapped + + class Rescue(exts.ExtensionDescriptor): """The Rescue controller for the OpenStack API.""" def __init__(self): super(Rescue, self).__init__() self.compute_api = compute.API() - def _rescue(self, input_dict, req, instance_id, exit_rescue=False): - """Rescue an instance. - - If exit_rescue is True, rescue mode should be torn down and the - instance restored to its original state. - """ + @wrap_errors + def _rescue(self, input_dict, req, instance_id): + """Rescue an instance.""" context = req.environ["nova.context"] - action = "unrescue" if exit_rescue else "rescue" - - try: - if action == "rescue": - self.compute_api.rescue(context, instance_id) - elif action == "unrescue": - self.compute_api.unrescue(context, instance_id) - except Exception, e: - LOG.exception(_("Error in %(action)s: %(e)s") % locals()) - return faults.Fault(exc.HTTPInternalServerError()) + self.compute_api.rescue(context, instance_id) return webob.Response(status_int=202) + @wrap_errors def _unrescue(self, input_dict, req, instance_id): - """Unrescue an instance. + """Rescue an instance.""" + context = req.environ["nova.context"] + self.compute_api.unrescue(context, instance_id) - We pass exit_rescue=True here so _rescue() knows we would like to exit - rescue mode. - """ - self._rescue(input_dict, req, instance_id, exit_rescue=True) + return webob.Response(status_int=202) def get_name(self): return "Rescue" -- cgit From 22ba538b3cb3ddd22cef0fc06b136db433a8d202 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 18 Aug 2011 16:07:02 -0500 Subject: Oops. --- nova/api/openstack/contrib/rescue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/openstack/contrib/rescue.py b/nova/api/openstack/contrib/rescue.py index 399bb7f35..3de128895 100644 --- a/nova/api/openstack/contrib/rescue.py +++ b/nova/api/openstack/contrib/rescue.py @@ -52,7 +52,7 @@ class Rescue(exts.ExtensionDescriptor): @wrap_errors def _unrescue(self, input_dict, req, instance_id): - """Rescue an instance.""" + """Unrescue an instance.""" context = req.environ["nova.context"] self.compute_api.unrescue(context, instance_id) -- 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 c718702496a98cefb434b4b21c3ea22fc6c8dc2d Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 18 Aug 2011 17:09:34 -0500 Subject: Added unit test. --- nova/tests/api/openstack/contrib/test_rescue.py | 55 +++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 nova/tests/api/openstack/contrib/test_rescue.py diff --git a/nova/tests/api/openstack/contrib/test_rescue.py b/nova/tests/api/openstack/contrib/test_rescue.py new file mode 100644 index 000000000..fc8e4be4e --- /dev/null +++ b/nova/tests/api/openstack/contrib/test_rescue.py @@ -0,0 +1,55 @@ +# 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. + +import json +import webob + +from nova import compute +from nova import test +from nova.tests.api.openstack import fakes + + +def rescue(self, context, instance_id): + pass + + +def unrescue(self, context, instance_id): + pass + + +class RescueTest(test.TestCase): + def setUp(self): + super(RescueTest, self).setUp() + self.stubs.Set(compute.api.API, "rescue", rescue) + self.stubs.Set(compute.api.API, "unrescue", unrescue) + + def test_rescue(self): + body = dict(rescue=None) + 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, 200) + + def test_unrescue(self): + body = dict(unrescue=None) + 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, 200) -- 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 32e57db9fdc5c48b3546640e838f5eb260080442 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 18 Aug 2011 16:22:22 -0700 Subject: rename the test method --- nova/tests/test_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/test_service.py b/nova/tests/test_service.py index 8f92406ff..760b150be 100644 --- a/nova/tests/test_service.py +++ b/nova/tests/test_service.py @@ -205,6 +205,6 @@ class TestLauncher(test.TestCase): def test_launch_app(self): self.assertEquals(0, self.service.port) launcher = service.Launcher() - launcher.launch_service(self.service) + launcher.launch_server(self.service) self.assertEquals(0, self.service.port) launcher.stop() -- 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 83d4c5b9b1f7ed9b75ae04464423b7ca4b5d627d Mon Sep 17 00:00:00 2001 From: Christopher MacGown Date: Fri, 19 Aug 2011 08:08:23 -0700 Subject: Fix config_drive migration, per Matt Dietz. --- .../versions/037_add_config_drive_to_instances.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/037_add_config_drive_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/037_add_config_drive_to_instances.py index 65ea012dd..36a6af16f 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/037_add_config_drive_to_instances.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/037_add_config_drive_to_instances.py @@ -23,20 +23,15 @@ meta = MetaData() instances = Table("instances", meta, Column("id", Integer(), primary_key=True, nullable=False)) -config_drive_column = Column("config_drive", String(255)) # matches image_ref + +# matches the size of an image_ref +config_drive_column = Column("config_drive", String(255), nullable=True) def upgrade(migrate_engine): meta.bind = migrate_engine instances.create_column(config_drive_column) - rows = migrate_engine.execute(instances.select()) - for row in rows: - instance_config_drive = None # pre-existing instances don't have one. - migrate_engine.execute(instances.update()\ - .where(instances.c.id == row[0])\ - .values(config_drive=instance_config_drive)) - def downgrade(migrate_engine): meta.bind = migrate_engine -- cgit From 276403dcb6a8c7802c456b88f8dad249b7513e64 Mon Sep 17 00:00:00 2001 From: Christopher MacGown Date: Fri, 19 Aug 2011 08:16:17 -0700 Subject: Define FLAGS.default_local_format. By default it's None, to match current expected _create_local --- nova/virt/libvirt/connection.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 1b48fd795..a715da66e 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -126,6 +126,10 @@ flags.DEFINE_string('libvirt_vif_type', 'bridge', flags.DEFINE_string('libvirt_vif_driver', 'nova.virt.libvirt.vif.LibvirtBridgeDriver', 'The libvirt VIF driver to configure the VIFs.') +flags.DEFINE_string('default_local_format', + None, + 'The default format a local_volume will be formatted with ' + 'on creation.') def get_connection(read_only): -- cgit From c4fc9f0737ec9f8d5c950b850fed9930a68164f4 Mon Sep 17 00:00:00 2001 From: Christopher MacGown Date: Fri, 19 Aug 2011 08:44:14 -0700 Subject: Add copyright notices --- nova/api/openstack/create_instance_helper.py | 1 + nova/api/openstack/views/servers.py | 1 + nova/compute/api.py | 1 + .../migrate_repo/versions/037_add_config_drive_to_instances.py | 4 ++-- nova/db/sqlalchemy/models.py | 1 + nova/scheduler/simple.py | 1 - nova/tests/api/openstack/test_servers.py | 1 + nova/tests/test_compute.py | 1 + nova/virt/libvirt/connection.py | 1 + nova/virt/xenapi/vm_utils.py | 1 + 10 files changed, 10 insertions(+), 3 deletions(-) diff --git a/nova/api/openstack/create_instance_helper.py b/nova/api/openstack/create_instance_helper.py index d776ae92d..563ef1c42 100644 --- a/nova/api/openstack/create_instance_helper.py +++ b/nova/api/openstack/create_instance_helper.py @@ -1,4 +1,5 @@ # Copyright 2011 OpenStack LLC. +# Copyright 2011 Piston Cloud Computing, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index c7bc03bcb..19acb0899 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -1,6 +1,7 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010-2011 OpenStack LLC. +# Copyright 2011 Piston Cloud Computing, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may diff --git a/nova/compute/api.py b/nova/compute/api.py index e42a5bbad..ccf29bd1a 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -2,6 +2,7 @@ # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. +# Copyright 2011 Piston Cloud Computing, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may diff --git a/nova/db/sqlalchemy/migrate_repo/versions/037_add_config_drive_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/037_add_config_drive_to_instances.py index 36a6af16f..d3058f00d 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/037_add_config_drive_to_instances.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/037_add_config_drive_to_instances.py @@ -1,6 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 OpenStack LLC. +# +# Copyright 2011 Piston Cloud Computing, Inc. # # 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 diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 8a6e2f673..c454cfcc3 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -2,6 +2,7 @@ # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. +# Copyright 2011 Piston Cloud Computing, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may diff --git a/nova/scheduler/simple.py b/nova/scheduler/simple.py index 61c76b35d..fc1b3142a 100644 --- a/nova/scheduler/simple.py +++ b/nova/scheduler/simple.py @@ -41,7 +41,6 @@ class SimpleScheduler(chance.ChanceScheduler): def _schedule_instance(self, context, instance_id, *_args, **_kwargs): """Picks a host that is up and has the fewest running instances.""" - instance_ref = db.instance_get(context, instance_id) if (instance_ref['availability_zone'] and ':' in instance_ref['availability_zone'] diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 7d8b222cc..0a46c3fd1 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1,6 +1,7 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010-2011 OpenStack LLC. +# Copyright 2011 Piston Cloud Computing, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 227b42fd7..8d1b95f74 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -2,6 +2,7 @@ # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. +# Copyright 2011 Piston Cloud Computing, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index a715da66e..ea47fa99d 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -4,6 +4,7 @@ # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. # Copyright (c) 2010 Citrix Systems, Inc. +# Copyright (c) 2011 Piston Cloud Computing, Inc # # 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 diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 206f522c9..3861f6bd8 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -1,6 +1,7 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright (c) 2010 Citrix Systems, Inc. +# Copyright 2011 Piston Cloud Computing, Inc. # # 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 -- 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 c06bbe99734f2fea35cfb4bdd854814c9119b617 Mon Sep 17 00:00:00 2001 From: Nachi Ueno Date: Fri, 19 Aug 2011 12:30:55 -0700 Subject: Added monkey patching notification code function w --- bin/nova-api | 2 +- bin/nova-compute | 1 + bin/nova-network | 1 + bin/nova-objectstore | 1 + bin/nova-scheduler | 1 + nova/flags.py | 8 ++++++++ nova/notifier/api.py | 18 ++++++++++++++++++ nova/utils.py | 24 +++++++++++++++++++++++- tools/pip-requires | 1 + 9 files changed, 55 insertions(+), 2 deletions(-) diff --git a/bin/nova-api b/bin/nova-api index fe8e83366..4edf77c7f 100755 --- a/bin/nova-api +++ b/bin/nova-api @@ -45,7 +45,7 @@ FLAGS = flags.FLAGS def main(): """Launch EC2 and OSAPI services.""" nova.utils.Bootstrapper.bootstrap_binary(sys.argv) - + nova.utils.monkey_patch() launcher = nova.service.Launcher() for api in FLAGS.enabled_apis: diff --git a/bin/nova-compute b/bin/nova-compute index cd7c78def..1de53c075 100755 --- a/bin/nova-compute +++ b/bin/nova-compute @@ -45,5 +45,6 @@ if __name__ == '__main__': utils.default_flagfile() flags.FLAGS(sys.argv) logging.setup() + utils.monkey_patch() service.serve() service.wait() diff --git a/bin/nova-network b/bin/nova-network index 101761ef7..5b66b8f2b 100755 --- a/bin/nova-network +++ b/bin/nova-network @@ -45,5 +45,6 @@ if __name__ == '__main__': utils.default_flagfile() flags.FLAGS(sys.argv) logging.setup() + utils.monkey_patch() service.serve() service.wait() diff --git a/bin/nova-objectstore b/bin/nova-objectstore index 4d5aec445..c2860f534 100755 --- a/bin/nova-objectstore +++ b/bin/nova-objectstore @@ -49,6 +49,7 @@ if __name__ == '__main__': utils.default_flagfile() FLAGS(sys.argv) logging.setup() + utils.monkey_patch() router = s3server.S3Application(FLAGS.buckets_path) server = wsgi.Server("S3 Objectstore", router, diff --git a/bin/nova-scheduler b/bin/nova-scheduler index 0c205a80f..ae3db51bc 100755 --- a/bin/nova-scheduler +++ b/bin/nova-scheduler @@ -45,5 +45,6 @@ if __name__ == '__main__': utils.default_flagfile() flags.FLAGS(sys.argv) logging.setup() + utils.monkey_patch() service.serve() service.wait() diff --git a/nova/flags.py b/nova/flags.py index 48d5e8168..09c30c7c0 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -402,3 +402,11 @@ DEFINE_bool('resume_guests_state_on_host_boot', False, DEFINE_string('root_helper', 'sudo', 'Command prefix to use for running commands as root') + +DEFINE_bool('monkey_patch', False, + 'Whether to monkey patch') + +DEFINE_list('monkey_patch_modules', + ['nova.api.ec2.cloud:nova.notifier.api.notify_decorator', + 'nova.compute.api:nova.notifier.api.notify_decorator'], + 'Module list representng monkey patched module and decorator') diff --git a/nova/notifier/api.py b/nova/notifier/api.py index e18f3e280..ca0ff60a4 100644 --- a/nova/notifier/api.py +++ b/nova/notifier/api.py @@ -39,6 +39,24 @@ class BadPriorityException(Exception): pass +def notify_decorator(name, fn): + def wrapped_func(*args, **kwarg): + body = {} + body['args'] = [] + body['kwarg'] = {} + for arg in args: + body['args'].append(arg) + for key in kwarg: + body['kwarg'][key] = kwarg[key] + LOG.debug("Notify Decorator: %s %r" % (name, body)) + notify(FLAGS.host, + name, + DEBUG, + body) + fn(*args, **kwarg) + return wrapped_func + + def publisher_id(service, host=None): if not host: host = FLAGS.host diff --git a/nova/utils.py b/nova/utils.py index 7276b6bd5..8024a3517 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -35,6 +35,7 @@ import sys import time import types import uuid +import pyclbr from xml.sax import saxutils from eventlet import event @@ -634,7 +635,7 @@ def synchronized(name, external=False): Different methods can share the same lock: @synchronized('mylock') - def foo(self, *args): + Gdef foo(self, *args): ... @synchronized('mylock') @@ -873,3 +874,24 @@ class Bootstrapper(object): for key in FLAGS: value = FLAGS.get(key, None) logging.audit(_("%(key)s : %(value)s" % locals())) + + +def monkey_patch(): + if not FLAGS.monkey_patch: + return + for module_and_decorator in FLAGS.monkey_patch_modules: + module, decorator_name = module_and_decorator.split(':') + decorator = import_class(decorator_name) + __import__(module) + module_data = pyclbr.readmodule_ex(module) + for key in module_data.keys(): + if isinstance(module_data[key], pyclbr.Class): + clz = import_class("%s.%s" % (module, key)) + for method, func in inspect.getmembers(clz, inspect.ismethod): + setattr(clz, method,\ + decorator("%s.%s" % (module, key), func)) + if isinstance(module_data[key], pyclbr.Function): + func = import_class("%s.%s" % (module, key)) + setattr(sys.modules[module], key,\ + setattr(sys.modules[module], key, \ + decorator("%s.%s" % (module, key), func))) diff --git a/tools/pip-requires b/tools/pip-requires index 60b502ffd..7eb220a95 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -34,3 +34,4 @@ coverage nosexcover GitPython paramiko +pyclbr -- 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 46ba1b111bfff27edcb963bc43869f26b02d569a Mon Sep 17 00:00:00 2001 From: Nachi Ueno Date: Fri, 19 Aug 2011 13:31:54 -0700 Subject: Fixed mistake on mergew --- bin/nova-scheduler | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/nova-scheduler b/bin/nova-scheduler index ae3db51bc..c1033a304 100755 --- a/bin/nova-scheduler +++ b/bin/nova-scheduler @@ -46,5 +46,6 @@ if __name__ == '__main__': flags.FLAGS(sys.argv) logging.setup() utils.monkey_patch() - service.serve() + server = service.Service.create(binary='nova-scheduler') + service.serve(server) service.wait() -- 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 75eb485c3a0e53380b9247d45e2a66159928dcd2 Mon Sep 17 00:00:00 2001 From: Nachi Ueno Date: Fri, 19 Aug 2011 14:04:58 -0700 Subject: Fixed NoneType returned bugw --- nova/notifier/api.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nova/notifier/api.py b/nova/notifier/api.py index ca0ff60a4..99b8b0102 100644 --- a/nova/notifier/api.py +++ b/nova/notifier/api.py @@ -48,12 +48,11 @@ def notify_decorator(name, fn): body['args'].append(arg) for key in kwarg: body['kwarg'][key] = kwarg[key] - LOG.debug("Notify Decorator: %s %r" % (name, body)) notify(FLAGS.host, name, DEBUG, body) - fn(*args, **kwarg) + return fn(*args, **kwarg) return wrapped_func -- cgit From 27723b95f7b3a64226ebe431e17cbf681b40303b Mon Sep 17 00:00:00 2001 From: Nachi Ueno Date: Fri, 19 Aug 2011 14:13:39 -0700 Subject: Fixed typo --- nova/flags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/flags.py b/nova/flags.py index 09c30c7c0..4e6e3ad40 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -409,4 +409,4 @@ DEFINE_bool('monkey_patch', False, DEFINE_list('monkey_patch_modules', ['nova.api.ec2.cloud:nova.notifier.api.notify_decorator', 'nova.compute.api:nova.notifier.api.notify_decorator'], - 'Module list representng monkey patched module and decorator') + 'Module list representing monkey patched module and decorator') -- 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 e10aa40bd6c2f96b2f5bba8b38b9605f019328e9 Mon Sep 17 00:00:00 2001 From: Nachi Ueno Date: Fri, 19 Aug 2011 14:22:53 -0700 Subject: Fixed typo --- nova/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/utils.py b/nova/utils.py index 171035068..d7e14b1b0 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -636,7 +636,7 @@ def synchronized(name, external=False): Different methods can share the same lock: @synchronized('mylock') - Gdef foo(self, *args): + def foo(self, *args): ... @synchronized('mylock') -- 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 f2fdb6a3028172d9085d0759d4b6770da9e71cb7 Mon Sep 17 00:00:00 2001 From: Launchpad Translations on behalf of nova-core <> Date: Sat, 20 Aug 2011 05:06:37 +0000 Subject: Launchpad automatic translations update. --- po/zh_CN.po | 122 ++++++++---------------------------------------------------- 1 file changed, 15 insertions(+), 107 deletions(-) diff --git a/po/zh_CN.po b/po/zh_CN.po index d2caff810..6284ee46c 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -8,14 +8,14 @@ msgstr "" "Project-Id-Version: nova\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2011-02-21 10:03-0500\n" -"PO-Revision-Date: 2011-08-04 05:50+0000\n" +"PO-Revision-Date: 2011-08-19 09:26+0000\n" "Last-Translator: zhangjunfeng \n" "Language-Team: Chinese (Simplified) \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-08-05 04:47+0000\n" -"X-Generator: Launchpad (build 13604)\n" +"X-Launchpad-Export-Date: 2011-08-20 05:06+0000\n" +"X-Generator: Launchpad (build 13697)\n" #, python-format #~ msgid "Starting %s" @@ -48,7 +48,7 @@ msgstr "" #: ../nova/exception.py:107 msgid "DB exception wrapped" -msgstr "" +msgstr "数据库异常" #. exc_type, exc_value, exc_traceback = sys.exc_info() #: ../nova/exception.py:120 @@ -88,7 +88,7 @@ msgstr "获取外网IP失败" #: ../nova/api/openstack/servers.py:152 #, python-format msgid "%(param)s property not found for image %(_image_id)s" -msgstr "" +msgstr "没有找到镜像文件%(_image_id)s 的属性 %(param)s" #: ../nova/api/openstack/servers.py:168 msgid "No keypairs defined" @@ -97,55 +97,55 @@ msgstr "未定义密钥对" #: ../nova/api/openstack/servers.py:238 #, python-format msgid "Compute.api::lock %s" -msgstr "" +msgstr "compute.api::加锁 %s" #: ../nova/api/openstack/servers.py:253 #, python-format msgid "Compute.api::unlock %s" -msgstr "" +msgstr "compute.api::解锁 %s" #: ../nova/api/openstack/servers.py:267 #, python-format msgid "Compute.api::get_lock %s" -msgstr "" +msgstr "Compute.api::得到锁 %s" #: ../nova/api/openstack/servers.py:281 #, python-format msgid "Compute.api::reset_network %s" -msgstr "" +msgstr "Compute.api::重置网络 %s" #: ../nova/api/openstack/servers.py:292 #, python-format msgid "Compute.api::pause %s" -msgstr "" +msgstr "Compute.api::暂停 %s" #: ../nova/api/openstack/servers.py:303 #, python-format msgid "Compute.api::unpause %s" -msgstr "" +msgstr "Compute.api::继续 %s" #: ../nova/api/openstack/servers.py:314 #, python-format msgid "compute.api::suspend %s" -msgstr "" +msgstr "compute.api::挂起 %s" #: ../nova/api/openstack/servers.py:325 #, python-format msgid "compute.api::resume %s" -msgstr "" +msgstr "compute.api::回复 %s" #: ../nova/virt/xenapi/volumeops.py:48 ../nova/virt/xenapi/volumeops.py:101 #: ../nova/db/sqlalchemy/api.py:731 ../nova/virt/libvirt_conn.py:741 #: ../nova/api/ec2/__init__.py:317 #, python-format msgid "Instance %s not found" -msgstr "" +msgstr "实例 %s 没有找到" #. NOTE: No Resource Pool concept so far #: ../nova/virt/xenapi/volumeops.py:51 #, python-format msgid "Attach_volume: %(instance_name)s, %(device_path)s, %(mountpoint)s" -msgstr "" +msgstr "挂载卷:%(instance_name)s, %(device_path)s, %(mountpoint)s" #: ../nova/virt/xenapi/volumeops.py:69 #, python-format @@ -2799,101 +2799,9 @@ msgstr "添加用户 %(user)s 到项目 %(project)s 中" msgid "Removing user %(user)s from project %(project)s" msgstr "从项目 %(project)s 中移除用户 %(user)s" -#, python-format -#~ msgid "" -#~ "%s\n" -#~ "Command: %s\n" -#~ "Exit code: %s\n" -#~ "Stdout: %r\n" -#~ "Stderr: %r" -#~ msgstr "" -#~ "%s\n" -#~ "命令:%s\n" -#~ "退出代码:%s\n" -#~ "标准输出(stdout):%r\n" -#~ "标准错误(stderr):%r" - -#, python-format -#~ msgid "Binding %s to %s with key %s" -#~ msgstr "将%s绑定到%s(以%s键值)" - -#, python-format -#~ msgid "AMQP server on %s:%d is unreachable. Trying again in %d seconds." -#~ msgstr "位于%s:%d的AMQP服务器不可用。%d秒后重试。" - -#, python-format -#~ msgid "Getting from %s: %s" -#~ msgstr "从%s获得如下内容:%s" - -#, python-format -#~ msgid "Starting %s node" -#~ msgstr "启动%s节点" - -#, python-format -#~ msgid "Data store %s is unreachable. Trying again in %d seconds." -#~ msgstr "数据储存服务%s不可用。%d秒之后继续尝试。" - #~ msgid "Full set of FLAGS:" #~ msgstr "FLAGS全集:" -#, python-format -#~ msgid "(%s) publish (key: %s) %s" -#~ msgstr "(%s)发布(键值:%s)%s" - -#, python-format -#~ msgid "Couldn't get IP, using 127.0.0.1 %s" -#~ msgstr "不能获取IP,将使用 127.0.0.1 %s" - -#, python-format -#~ msgid "" -#~ "Access key %s has had %d failed authentications and will be locked out for " -#~ "%d minutes." -#~ msgstr "访问键 %s时,存在%d个失败的认证,将于%d分钟后解锁" - -#, python-format -#~ msgid "Authenticated Request For %s:%s)" -#~ msgstr "为%s:%s申请认证" - -#, python-format -#~ msgid "arg: %s\t\tval: %s" -#~ msgstr "键为: %s\t\t值为: %s" - -#, python-format -#~ msgid "Getting x509 for user: %s on project: %s" -#~ msgstr "为用户 %s从工程%s中获取 x509" - -#, python-format -#~ msgid "Create project %s managed by %s" -#~ msgstr "创建工程%s,此工程由%s管理" - -#, python-format -#~ msgid "Unsupported API request: controller = %s,action = %s" -#~ msgstr "不支持的API请求: 控制器 = %s,执行 = %s" - -#, python-format -#~ msgid "Adding sitewide role %s to user %s" -#~ msgstr "增加站点范围的 %s角色给用户 %s" - -#, python-format -#~ msgid "Adding user %s to project %s" -#~ msgstr "增加用户%s到%s工程" - -#, python-format -#~ msgid "Unauthorized request for controller=%s and action=%s" -#~ msgstr "对控制器=%s及动作=%s未经授权" - -#, python-format -#~ msgid "Removing user %s from project %s" -#~ msgstr "正将用户%s从工程%s中移除" - -#, python-format -#~ msgid "Adding role %s to user %s for project %s" -#~ msgstr "正将%s角色赋予用户%s(在工程%s中)" - -#, python-format -#~ msgid "Removing role %s from user %s for project %s" -#~ msgstr "正将角色%s从用户%s在工程%s中移除" - #~ msgid "No such process" #~ msgstr "没有该进程" -- cgit From 43e2ca531f0fdea5173b7f237627fc3543caf13b Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Sat, 20 Aug 2011 15:30:59 -0700 Subject: lp:828610 --- nova/ipv6/account_identifier.py | 2 +- nova/tests/test_ipv6.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/nova/ipv6/account_identifier.py b/nova/ipv6/account_identifier.py index 258678f0a..02b653925 100644 --- a/nova/ipv6/account_identifier.py +++ b/nova/ipv6/account_identifier.py @@ -34,7 +34,7 @@ def to_global(prefix, mac, project_id): mac_addr = netaddr.IPAddress(int_addr) maskIP = netaddr.IPNetwork(prefix).ip return (project_hash ^ static_num ^ mac_addr | maskIP).format() - except TypeError: + except netaddr.AddrFormatError: raise TypeError(_('Bad mac for to_global_ipv6: %s') % mac) diff --git a/nova/tests/test_ipv6.py b/nova/tests/test_ipv6.py index d123df6f1..6afc7e3f9 100644 --- a/nova/tests/test_ipv6.py +++ b/nova/tests/test_ipv6.py @@ -55,3 +55,8 @@ class IPv6AccountIdentiferTestCase(test.TestCase): def test_to_mac(self): mac = ipv6.to_mac('2001:db8::a94a:8fe5:ff33:4455') self.assertEquals(mac, '02:16:3e:33:44:55') + + def test_to_global_with_bad_mac(self): + bad_mac = '02:16:3e:33:44:5X' + self.assertRaises(TypeError, ipv6.to_global, + '2001:db8::', bad_mac, 'test') -- 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 3bbecbbb8c857079f75bea6fc6610bce9942de34 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Sat, 20 Aug 2011 18:20:55 -0700 Subject: added unit tests for versions.py --- nova/tests/test_versions.py | 58 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 nova/tests/test_versions.py diff --git a/nova/tests/test_versions.py b/nova/tests/test_versions.py new file mode 100644 index 000000000..9578eda6d --- /dev/null +++ b/nova/tests/test_versions.py @@ -0,0 +1,58 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Ken Pepple +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +from nova import exception +from nova import test +from nova import utils +from nova import version + + +class VersionTestCase(test.TestCase): + def setUp(self): + """setup test with unchanging values""" + super(VersionTestCase, self).setUp() + self.version = version + self.version.FINAL = False + self.version.NOVA_VERSION = ['2012', '10'] + self.version.YEAR, self.version.COUNT = self.version.NOVA_VERSION + self.version.version_info = {'branch_nick': u'LOCALBRANCH', + 'revision_id': 'LOCALREVISION', + 'revno': 0} + + def test_version_string_is_good(self): + """Ensure version string works""" + self.assertEqual("2012.10-dev", self.version.version_string()) + + def test_canonical_version_string_is_good(self): + """Ensure canonical version works""" + self.assertEqual("2012.10", self.version.canonical_version_string()) + + def test_final_version_strings_are_identical(self): + """Ensure final version strings match""" + self.version.FINAL = True + self.assertEqual(self.version.canonical_version_string(), + self.version.version_string()) + + def test_vcs_version_string_is_good(self): + """""" + self.assertEqual("LOCALBRANCH:LOCALREVISION", + self.version.vcs_version_string()) + + def test_version_string_with_vcs_is_good(self): + """""" + self.assertEqual("2012.10-LOCALBRANCH:LOCALREVISION", + self.version.version_string_with_vcs()) -- cgit From f2981d8463779fa1fca52c840d91b47845719340 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Sat, 20 Aug 2011 18:28:30 -0700 Subject: comment strings --- nova/tests/test_versions.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/nova/tests/test_versions.py b/nova/tests/test_versions.py index 9578eda6d..4621b042b 100644 --- a/nova/tests/test_versions.py +++ b/nova/tests/test_versions.py @@ -22,6 +22,7 @@ from nova import version class VersionTestCase(test.TestCase): + """Test cases for Versions code""" def setUp(self): """setup test with unchanging values""" super(VersionTestCase, self).setUp() @@ -42,17 +43,19 @@ class VersionTestCase(test.TestCase): self.assertEqual("2012.10", self.version.canonical_version_string()) def test_final_version_strings_are_identical(self): - """Ensure final version strings match""" + """Ensure final version strings match only at release""" + self.assertNotEqual(self.version.canonical_version_string(), + self.version.version_string()) self.version.FINAL = True self.assertEqual(self.version.canonical_version_string(), self.version.version_string()) def test_vcs_version_string_is_good(self): - """""" + """Ensure uninstalled code generates local """ self.assertEqual("LOCALBRANCH:LOCALREVISION", self.version.vcs_version_string()) def test_version_string_with_vcs_is_good(self): - """""" + """Ensure uninstalled code get version string""" self.assertEqual("2012.10-LOCALBRANCH:LOCALREVISION", self.version.version_string_with_vcs()) -- cgit From 326cfda8cc50f5db083e9df381d3109e0302605d Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Sun, 21 Aug 2011 17:55:54 -0700 Subject: added exception catch for bad prefix and matching test --- nova/ipv6/account_identifier.py | 2 ++ nova/tests/test_ipv6.py | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/nova/ipv6/account_identifier.py b/nova/ipv6/account_identifier.py index 02b653925..4ca7b5983 100644 --- a/nova/ipv6/account_identifier.py +++ b/nova/ipv6/account_identifier.py @@ -36,6 +36,8 @@ def to_global(prefix, mac, project_id): return (project_hash ^ static_num ^ mac_addr | maskIP).format() except netaddr.AddrFormatError: raise TypeError(_('Bad mac for to_global_ipv6: %s') % mac) + except TypeError: + raise TypeError(_('Bad prefix for to_global_ipv6: %s') % prefix) def to_mac(ipv6_address): diff --git a/nova/tests/test_ipv6.py b/nova/tests/test_ipv6.py index 6afc7e3f9..f9c2517a7 100644 --- a/nova/tests/test_ipv6.py +++ b/nova/tests/test_ipv6.py @@ -60,3 +60,10 @@ class IPv6AccountIdentiferTestCase(test.TestCase): bad_mac = '02:16:3e:33:44:5X' self.assertRaises(TypeError, ipv6.to_global, '2001:db8::', bad_mac, 'test') + + def test_to_global_with_bad_prefix(self): + bad_prefix = '78' + self.assertRaises(TypeError, ipv6.to_global, + bad_prefix, + '2001:db8::a94a:8fe5:ff33:4455', + 'test') -- cgit From b5bf5fbb77e95b44f3254a111374ddba73016c4d Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Sun, 21 Aug 2011 18:01:34 -0700 Subject: added exception catch and test for bad project_id --- nova/ipv6/account_identifier.py | 2 ++ nova/tests/test_ipv6.py | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/nova/ipv6/account_identifier.py b/nova/ipv6/account_identifier.py index 4ca7b5983..27bb01988 100644 --- a/nova/ipv6/account_identifier.py +++ b/nova/ipv6/account_identifier.py @@ -38,6 +38,8 @@ def to_global(prefix, mac, project_id): raise TypeError(_('Bad mac for to_global_ipv6: %s') % mac) except TypeError: raise TypeError(_('Bad prefix for to_global_ipv6: %s') % prefix) + except NameError: + raise TypeError(_('Bad project_id for to_global_ipv6: %s') % project_id) def to_mac(ipv6_address): diff --git a/nova/tests/test_ipv6.py b/nova/tests/test_ipv6.py index f9c2517a7..5c333b17e 100644 --- a/nova/tests/test_ipv6.py +++ b/nova/tests/test_ipv6.py @@ -67,3 +67,10 @@ class IPv6AccountIdentiferTestCase(test.TestCase): bad_prefix, '2001:db8::a94a:8fe5:ff33:4455', 'test') + + def test_to_global_with_bad_project(self): + bad_project = 'non-existent-project-name' + self.assertRaises(TypeError, ipv6.to_global, + '2001:db8::', + '2001:db8::a94a:8fe5:ff33:4455', + bad_project) -- cgit From 34e310eff24b96bcc27df176bfecbd02ac863e7c Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Sun, 21 Aug 2011 22:59:46 -0400 Subject: fixing bug lp:830817 --- nova/api/openstack/views/addresses.py | 26 +++++++++++++++++++++++--- nova/api/openstack/wsgi.py | 6 +++--- nova/tests/api/openstack/test_servers.py | 21 +++++++++++++++++++++ 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py index ddbf7a144..05028db41 100644 --- a/nova/api/openstack/views/addresses.py +++ b/nova/api/openstack/views/addresses.py @@ -15,11 +15,15 @@ # License for the specific language governing permissions and limitations # under the License. +import traceback + from nova import flags from nova import utils +from nova import log as logging from nova.api.openstack import common FLAGS = flags.FLAGS +LOG = logging.getLogger('nova.api.openstack.views.addresses') class ViewBuilder(object): @@ -48,7 +52,11 @@ class ViewBuilderV11(ViewBuilder): def build(self, interfaces): networks = {} for interface in interfaces: - network_label = interface['network']['label'] + try: + network_label = self._extract_network_label(interface) + except TypeError: + LOG.error(traceback.format_exc()) + continue if network_label not in networks: networks[network_label] = [] @@ -64,9 +72,14 @@ class ViewBuilderV11(ViewBuilder): return networks - def build_network(self, interfaces, network_label): + def build_network(self, interfaces, requested_network): for interface in interfaces: - if interface['network']['label'] == network_label: + try: + network_label = self._extract_network_label(interface) + except TypeError: + continue + + if network_label == requested_network: ips = list(self._extract_ipv4_addresses(interface)) ipv6 = self._extract_ipv6_address(interface) if ipv6 is not None: @@ -74,6 +87,13 @@ class ViewBuilderV11(ViewBuilder): return {network_label: ips} return None + def _extract_network_label(self, interface): + try: + return interface['network']['label'] + except (TypeError, KeyError): + LOG.error(traceback.format_exc()) + raise TypeError + def _extract_ipv4_addresses(self, interface): for fixed_ip in interface['fixed_ips']: yield self._build_ip_entity(fixed_ip['address'], 4) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 0eb47044e..c2185ebfd 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -516,6 +516,6 @@ class Resource(wsgi.Application): controller_method = getattr(self.controller, action) try: return controller_method(req=request, **action_args) - except TypeError, exc: - LOG.debug(str(exc)) - return webob.exc.HTTPBadRequest() + except TypeError: + LOG.debug(traceback.format_exc()) + return faults.Fault(webob.exc.HTTPBadRequest()) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 437620854..8167c4ace 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -752,6 +752,27 @@ class ServersTest(test.TestCase): (ip,) = private_node.getElementsByTagName('ip') self.assertEquals(ip.getAttribute('addr'), private) + # NOTE(bcwaldon): lp830817 + def test_get_server_by_id_malformed_networks_v1_1(self): + ifaces = [ + { + 'network': None, + 'fixed_ips': [ + {'address': '192.168.0.3'}, + {'address': '192.168.0.4'}, + ], + }, + ] + new_return_server = return_server_with_attributes(interfaces=ifaces) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + + req = webob.Request.blank('/v1.1/servers/1') + 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'], 'server1') + def test_get_server_by_id_with_addresses_v1_1(self): self.flags(use_ipv6=True) interfaces = [ -- cgit From 9fc23f1055be435e8a21b999f748a8461552bd13 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Sun, 21 Aug 2011 23:16:26 -0400 Subject: adding import --- nova/api/openstack/wsgi.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index c2185ebfd..572aba993 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -1,5 +1,6 @@ import json +import traceback import webob from xml.dom import minidom from xml.parsers import expat -- cgit From 6d6ea16b2840d876120f7cad2aa679eee0370cb9 Mon Sep 17 00:00:00 2001 From: Launchpad Translations on behalf of nova-core <> Date: Mon, 22 Aug 2011 04:48:50 +0000 Subject: Launchpad automatic translations update. --- po/it.po | 83 +++++++++++++++++++--------------------------------------------- 1 file changed, 24 insertions(+), 59 deletions(-) diff --git a/po/it.po b/po/it.po index 14e9a6de6..e166297f1 100644 --- a/po/it.po +++ b/po/it.po @@ -8,14 +8,14 @@ msgstr "" "Project-Id-Version: nova\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2011-02-21 10:03-0500\n" -"PO-Revision-Date: 2011-02-22 19:34+0000\n" -"Last-Translator: Armando Migliaccio \n" +"PO-Revision-Date: 2011-08-21 22:50+0000\n" +"Last-Translator: Guido Davide Dall'Olio \n" "Language-Team: Italian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2011-08-03 04:44+0000\n" -"X-Generator: Launchpad (build 13573)\n" +"X-Launchpad-Export-Date: 2011-08-22 04:48+0000\n" +"X-Generator: Launchpad (build 13697)\n" #: ../nova/scheduler/chance.py:37 ../nova/scheduler/zone.py:55 #: ../nova/scheduler/simple.py:75 ../nova/scheduler/simple.py:110 @@ -449,24 +449,24 @@ msgstr "" #: ../nova/scheduler/simple.py:53 #, python-format msgid "Host %s is not alive" -msgstr "" +msgstr "L'host %s non è attivo" #: ../nova/scheduler/simple.py:65 msgid "All hosts have too many cores" -msgstr "" +msgstr "Gli host hanno troppi core" #: ../nova/scheduler/simple.py:87 #, python-format msgid "Host %s not available" -msgstr "" +msgstr "Host %s non disponibile" #: ../nova/scheduler/simple.py:99 msgid "All hosts have too many gigabytes" -msgstr "" +msgstr "Gli Host hanno troppy gigabyte" #: ../nova/scheduler/simple.py:119 msgid "All hosts have too many networks" -msgstr "" +msgstr "Gli host hanno troppe reti" #: ../nova/volume/manager.py:85 #, python-format @@ -496,7 +496,7 @@ msgstr "" #: ../nova/volume/manager.py:123 #, python-format msgid "volume %s: created successfully" -msgstr "" +msgstr "volume %s: creato con successo" #: ../nova/volume/manager.py:131 msgid "Volume is still attached" @@ -514,12 +514,12 @@ msgstr "" #: ../nova/volume/manager.py:138 #, python-format msgid "volume %s: deleting" -msgstr "" +msgstr "volume %s: rimuovendo" #: ../nova/volume/manager.py:147 #, python-format msgid "volume %s: deleted successfully" -msgstr "" +msgstr "volume %s: rimosso con successo" #: ../nova/virt/xenapi/fake.py:74 #, python-format @@ -529,7 +529,7 @@ msgstr "" #: ../nova/virt/xenapi/fake.py:304 ../nova/virt/xenapi/fake.py:404 #: ../nova/virt/xenapi/fake.py:422 ../nova/virt/xenapi/fake.py:478 msgid "Raising NotImplemented" -msgstr "" +msgstr "Sollevando NotImplemented" #: ../nova/virt/xenapi/fake.py:306 #, python-format @@ -539,7 +539,7 @@ msgstr "" #: ../nova/virt/xenapi/fake.py:341 #, python-format msgid "Calling %(localname)s %(impl)s" -msgstr "" +msgstr "Chiamando %(localname)s %(impl)s" #: ../nova/virt/xenapi/fake.py:346 #, python-format @@ -564,17 +564,17 @@ msgstr "" #: ../nova/virt/connection.py:73 msgid "Failed to open connection to the hypervisor" -msgstr "" +msgstr "Fallita l'apertura della connessione verso l'hypervisor" #: ../nova/network/linux_net.py:187 #, python-format msgid "Starting VLAN inteface %s" -msgstr "" +msgstr "Avviando l'interfaccia VLAN %s" #: ../nova/network/linux_net.py:208 #, python-format msgid "Starting Bridge interface for %s" -msgstr "" +msgstr "Avviando l'interfaccia Bridge per %s" #. pylint: disable=W0703 #: ../nova/network/linux_net.py:314 @@ -632,7 +632,7 @@ msgstr "Il risultato é %s" #: ../nova/utils.py:159 #, python-format msgid "Running cmd (SSH): %s" -msgstr "" +msgstr "Eseguendo cmd (SSH): %s" #: ../nova/utils.py:217 #, python-format @@ -642,7 +642,7 @@ msgstr "debug in callback: %s" #: ../nova/utils.py:222 #, python-format msgid "Running %s" -msgstr "" +msgstr "Eseguendo %s" #: ../nova/utils.py:262 #, python-format @@ -697,12 +697,12 @@ msgstr "" #: ../nova/virt/xenapi/vm_utils.py:135 ../nova/virt/hyperv.py:171 #, python-format msgid "Created VM %s..." -msgstr "" +msgstr "Creata VM %s.." #: ../nova/virt/xenapi/vm_utils.py:138 #, python-format msgid "Created VM %(instance_name)s as %(vm_ref)s." -msgstr "" +msgstr "Creata VM %(instance_name)s come %(vm_ref)s" #: ../nova/virt/xenapi/vm_utils.py:168 #, python-format @@ -771,7 +771,7 @@ msgstr "" #: ../nova/virt/xenapi/vm_utils.py:332 #, python-format msgid "Glance image %s" -msgstr "" +msgstr "Immagine Glance %s" #. we need to invoke a plugin for copying VDI's #. content into proper path @@ -783,7 +783,7 @@ msgstr "" #: ../nova/virt/xenapi/vm_utils.py:352 #, python-format msgid "Kernel/Ramdisk VDI %s destroyed" -msgstr "" +msgstr "Kernel/Ramdisk VDI %s distrutti" #: ../nova/virt/xenapi/vm_utils.py:361 #, python-format @@ -793,7 +793,7 @@ msgstr "" #: ../nova/virt/xenapi/vm_utils.py:386 ../nova/virt/xenapi/vm_utils.py:402 #, python-format msgid "Looking up vdi %s for PV kernel" -msgstr "" +msgstr "Cercando vdi %s per kernel PV" #: ../nova/virt/xenapi/vm_utils.py:397 #, python-format @@ -2802,41 +2802,6 @@ msgstr "" msgid "Removing user %(user)s from project %(project)s" msgstr "" -#, python-format -#~ msgid "" -#~ "%s\n" -#~ "Command: %s\n" -#~ "Exit code: %s\n" -#~ "Stdout: %r\n" -#~ "Stderr: %r" -#~ msgstr "" -#~ "%s\n" -#~ "Comando: %s\n" -#~ "Exit code: %s\n" -#~ "Stdout: %r\n" -#~ "Stderr: %r" - -#, python-format -#~ msgid "(%s) publish (key: %s) %s" -#~ msgstr "(%s) pubblica (chiave: %s) %s" - -#, python-format -#~ msgid "AMQP server on %s:%d is unreachable. Trying again in %d seconds." -#~ msgstr "" -#~ "Il server AMQP su %s:%d non é raggiungibile. Riprovare in %d secondi." - -#, python-format -#~ msgid "Binding %s to %s with key %s" -#~ msgstr "Collegando %s a %s con la chiave %s" - -#, python-format -#~ msgid "Starting %s node" -#~ msgstr "Avviando il nodo %s" - -#, python-format -#~ msgid "Data store %s is unreachable. Trying again in %d seconds." -#~ msgstr "Datastore %s é irrangiungibile. Riprovare in %d seconds." - #~ msgid "Full set of FLAGS:" #~ msgstr "Insieme di FLAGS:" -- 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 269b4e00e82b8f99d2fc24f935ff165d62f19891 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 22 Aug 2011 16:23:48 -0400 Subject: logging as exception rather than error --- nova/api/openstack/views/addresses.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py index 05028db41..d54013d61 100644 --- a/nova/api/openstack/views/addresses.py +++ b/nova/api/openstack/views/addresses.py @@ -55,7 +55,7 @@ class ViewBuilderV11(ViewBuilder): try: network_label = self._extract_network_label(interface) except TypeError: - LOG.error(traceback.format_exc()) + LOG.exception(traceback.format_exc()) continue if network_label not in networks: @@ -91,7 +91,7 @@ class ViewBuilderV11(ViewBuilder): try: return interface['network']['label'] except (TypeError, KeyError): - LOG.error(traceback.format_exc()) + LOG.exception(traceback.format_exc()) raise TypeError def _extract_ipv4_addresses(self, interface): -- 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 From 5a288485215a13f3892ae17a46b9644ed84fc089 Mon Sep 17 00:00:00 2001 From: Nachi Ueno Date: Mon, 22 Aug 2011 14:24:37 -0700 Subject: Added Test Code, doc string, and fixed pip-requiresw --- nova/flags.py | 5 +++-- nova/notifier/api.py | 8 ++++++++ nova/tests/test_notifier.py | 22 ++++++++++++++++++++++ nova/tests/test_utils.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ nova/utils.py | 26 +++++++++++++++++++++++--- tools/pip-requires | 1 - 6 files changed, 100 insertions(+), 6 deletions(-) diff --git a/nova/flags.py b/nova/flags.py index 4e6e3ad40..e9bf6e334 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -404,9 +404,10 @@ DEFINE_string('root_helper', 'sudo', 'Command prefix to use for running commands as root') DEFINE_bool('monkey_patch', False, - 'Whether to monkey patch') + 'Whether to log monkey patching') DEFINE_list('monkey_patch_modules', ['nova.api.ec2.cloud:nova.notifier.api.notify_decorator', 'nova.compute.api:nova.notifier.api.notify_decorator'], - 'Module list representing monkey patched module and decorator') + 'Module list representing monkey\ + patched module and decorator') diff --git a/nova/notifier/api.py b/nova/notifier/api.py index 99b8b0102..a98f17dbe 100644 --- a/nova/notifier/api.py +++ b/nova/notifier/api.py @@ -40,6 +40,14 @@ class BadPriorityException(Exception): def notify_decorator(name, fn): + """ decorator for notify which is used from utils.monkey_patch() + Parameters: + + name - name of the function + function - object of the function + + + """ def wrapped_func(*args, **kwarg): body = {} body['args'] = [] diff --git a/nova/tests/test_notifier.py b/nova/tests/test_notifier.py index 64b799a2c..ab5dfb692 100644 --- a/nova/tests/test_notifier.py +++ b/nova/tests/test_notifier.py @@ -134,3 +134,25 @@ class NotifierTestCase(test.TestCase): self.assertEqual(msg['event_type'], 'error_notification') self.assertEqual(msg['priority'], 'ERROR') self.assertEqual(msg['payload']['error'], 'foo') + + def test_send_notification_by_decorator(self): + self.notify_called = False + + def example_api(arg1, arg2): + return arg1 + arg2 + + example_api =\ + nova.notifier.api.notify_decorator( + 'example_api', + example_api) + + def mock_notify(cls, *args): + self.notify_called = True + + self.stubs.Set(nova.notifier.no_op_notifier, 'notify', + mock_notify) + + class Mock(object): + pass + self.assertEqual(3, example_api(1, 2)) + self.assertEqual(self.notify_called, True) diff --git a/nova/tests/test_utils.py b/nova/tests/test_utils.py index ec5098a37..317ec46d0 100644 --- a/nova/tests/test_utils.py +++ b/nova/tests/test_utils.py @@ -18,6 +18,7 @@ import datetime import os import tempfile +import nova from nova import exception from nova import test from nova import utils @@ -384,3 +385,46 @@ class ToPrimitiveTestCase(test.TestCase): def test_typeerror(self): x = bytearray # Class, not instance self.assertEquals(utils.to_primitive(x), u"") + + +class MonkeyPatchTestCase(test.TestCase): + """Unit test for utils.monkey_patch().""" + def setUp(self): + super(MonkeyPatchTestCase, self).setUp() + self.flags( + monkey_patch=True, + monkey_patch_modules=['nova.tests.example.example_a' + ':' + + 'nova.tests.example.example_decorator']) + + def test_monkey_patch(self): + utils.monkey_patch() + nova.tests.example.CALLED_FUNCTION = [] + from nova.tests.example import example_a, example_b + + self.assertEqual('Example function', example_a.example_function_a()) + exampleA = example_a.ExampleClassA() + exampleA.example_method() + ret_a = exampleA.example_method_add(3, 5) + self.assertEqual(ret_a, 8) + + self.assertEqual('Example function', example_b.example_function_b()) + exampleB = example_b.ExampleClassB() + exampleB.example_method() + ret_b = exampleB.example_method_add(3, 5) + + self.assertEqual(ret_b, 8) + package_a = 'nova.tests.example.example_a.' + self.assertTrue(package_a + 'example_function_a' + in nova.tests.example.CALLED_FUNCTION) + + self.assertTrue(package_a + 'ExampleClassA.example_method' + in nova.tests.example.CALLED_FUNCTION) + self.assertTrue(package_a + 'ExampleClassA.example_method_add' + in nova.tests.example.CALLED_FUNCTION) + package_b = 'nova.tests.example.example_b.' + self.assertFalse(package_b + 'example_function_b' + in nova.tests.example.CALLED_FUNCTION) + self.assertFalse(package_b + 'ExampleClassB.example_method' + in nova.tests.example.CALLED_FUNCTION) + self.assertFalse(package_b + 'ExampleClassB.example_method_add' + in nova.tests.example.CALLED_FUNCTION) diff --git a/nova/utils.py b/nova/utils.py index d7e14b1b0..be2ba68f9 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -842,21 +842,41 @@ def bool_from_str(val): def monkey_patch(): + """ If the Flags.monkey_patch set as True, + this functuion patches a decorator + for all functions in specified modules. + You can set decorators for each modules + using FLAGS.monkey_patch_modules. + The format is "Module path:Decorator function". + Example: 'nova.api.ec2.cloud:nova.notifier.api.notify_decorator' + + Parameters of the decorator is as follows. + (See nova.notifier.api.notify_decorator) + + name - name of the function + function - object of the function + """ + + # If FLAGS.monkey_patch is not True, this function do nothing. if not FLAGS.monkey_patch: return + # Get list of moudles and decorators for module_and_decorator in FLAGS.monkey_patch_modules: module, decorator_name = module_and_decorator.split(':') + # import decorator function decorator = import_class(decorator_name) __import__(module) + # Retrive module information using pyclbr module_data = pyclbr.readmodule_ex(module) for key in module_data.keys(): + # set the decorator for the class methods if isinstance(module_data[key], pyclbr.Class): clz = import_class("%s.%s" % (module, key)) for method, func in inspect.getmembers(clz, inspect.ismethod): setattr(clz, method,\ - decorator("%s.%s" % (module, key), func)) + decorator("%s.%s.%s" % (module, key, method), func)) + # set the decorator for the function if isinstance(module_data[key], pyclbr.Function): func = import_class("%s.%s" % (module, key)) setattr(sys.modules[module], key,\ - setattr(sys.modules[module], key, \ - decorator("%s.%s" % (module, key), func))) + decorator("%s.%s" % (module, key), func)) diff --git a/tools/pip-requires b/tools/pip-requires index 7eb220a95..60b502ffd 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -34,4 +34,3 @@ coverage nosexcover GitPython paramiko -pyclbr -- cgit From c3ed01d7d53dbade412122743078d60131adbf9f Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Mon, 22 Aug 2011 14:24:59 -0700 Subject: change NoAuth to actually use a tenant and user --- etc/nova/api-paste.ini | 12 +++++------ nova/api/auth.py | 19 +---------------- nova/api/ec2/__init__.py | 21 +++++++++++++++++++ nova/api/openstack/auth.py | 52 ++++++++++++++++++++++++++++++++++------------ 4 files changed, 67 insertions(+), 37 deletions(-) diff --git a/etc/nova/api-paste.ini b/etc/nova/api-paste.ini index a9ae0abf6..dafdef877 100644 --- a/etc/nova/api-paste.ini +++ b/etc/nova/api-paste.ini @@ -19,7 +19,7 @@ use = egg:Paste#urlmap /1.0: ec2metadata [pipeline:ec2cloud] -pipeline = logrequest admincontext cloudrequest authorizer ec2executor +pipeline = logrequest ec2noauth cloudrequest authorizer ec2executor # NOTE(vish): use the following pipeline for deprecated auth #pipeline = logrequest authenticate cloudrequest authorizer ec2executor # NOTE(vish): use the following pipeline for keystone @@ -43,6 +43,9 @@ paste.filter_factory = nova.api.ec2:Lockout.factory [filter:totoken] paste.filter_factory = nova.api.ec2:ToToken.factory +[filter:ec2noauth] +paste.filter_factory = nova.api.ec2:NoAuth.factory + [filter:authenticate] paste.filter_factory = nova.api.ec2:Authenticate.factory @@ -77,14 +80,14 @@ use = egg:Paste#urlmap /v1.1: openstackapi11 [pipeline:openstackapi10] -pipeline = faultwrap noauth admincontext ratelimit osapiapp10 +pipeline = faultwrap noauth ratelimit osapiapp10 # NOTE(vish): use the following pipeline for deprecated auth # pipeline = faultwrap auth ratelimit osapiapp10 # NOTE(vish): use the following pipeline for keystone #pipeline = faultwrap authtoken keystonecontext ratelimit osapiapp10 [pipeline:openstackapi11] -pipeline = faultwrap noauth admincontext ratelimit extensions osapiapp11 +pipeline = faultwrap noauth ratelimit extensions osapiapp11 # NOTE(vish): use the following pipeline for deprecated auth # pipeline = faultwrap auth ratelimit extensions osapiapp11 # NOTE(vish): use the following pipeline for keystone @@ -121,9 +124,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 diff --git a/nova/api/auth.py b/nova/api/auth.py index 050216fd7..cd0d38b3f 100644 --- a/nova/api/auth.py +++ b/nova/api/auth.py @@ -45,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""" @@ -80,6 +62,7 @@ class KeystoneContext(wsgi.Middleware): req.headers.get('X_STORAGE_TOKEN')) # Build a context, including the auth_token... + remote_address = getattr(req, 'remote_address', '127.0.0.1') remote_address = req.remote_addr if FLAGS.use_forwarded_for: remote_address = req.headers.get('X-Forwarded-For', remote_address) diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 17969099d..5430f443d 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -183,6 +183,27 @@ class ToToken(wsgi.Middleware): return self.application +class NoAuth(wsgi.Middleware): + """Add user:project as 'nova.context' to WSGI environ.""" + + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + if 'AWSAccessKeyId' not in req.params: + raise webob.exc.HTTPBadRequest() + user_id, _sep, project_id = req.params['AWSAccessKeyId'].partition(':') + project_id = project_id or user_id + remote_address = getattr(req, 'remote_address', '127.0.0.1') + if FLAGS.use_forwarded_for: + remote_address = req.headers.get('X-Forwarded-For', remote_address) + ctx = context.RequestContext(user_id, + project_id, + is_admin=True, + remote_address=remote_address) + + req.environ['nova.context'] = ctx + return self.application + + class Authenticate(wsgi.Middleware): """Authenticate an EC2 request and add 'nova.context' to WSGI environ.""" diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index 0d9c7562a..f2dc89094 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -33,6 +33,7 @@ from nova.api.openstack import faults LOG = logging.getLogger('nova.api.openstack') FLAGS = flags.FLAGS +flags.DECLARE('use_forwarded_for', 'nova.api.auth') class NoAuthMiddleware(wsgi.Middleware): @@ -40,17 +41,36 @@ class NoAuthMiddleware(wsgi.Middleware): @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): - if 'X-Auth-Token' in req.headers: + if 'X-Auth-Token' not in req.headers: + os_url = req.url + version = common.get_version_from_href(os_url) + user_id = req.headers.get('X-Auth-User', 'admin') + project_id = req.headers.get('X-Auth-Project-Id', 'admin') + if version == '1.1': + os_url += '/' + project_id + res = webob.Response() + res.headers['X-Auth-Token'] = '%s:%s' % (user_id, project_id) + res.headers['X-Server-Management-Url'] = os_url + res.headers['X-Storage-Url'] = '' + res.headers['X-CDN-Management-Url'] = '' + res.content_type = 'text/plain' + res.status = '204' + return res + else: + token = req.headers['X-Auth-Token'] + user_id, _sep, project_id = token.partition(':') + project_id = project_id or user_id + remote_address = getattr(req, 'remote_address', '127.0.0.1') + if FLAGS.use_forwarded_for: + remote_address = req.headers.get('X-Forwarded-For', + remote_address) + ctx = context.RequestContext(user_id, + project_id, + is_admin=True, + remote_address=remote_address) + + req.environ['nova.context'] = ctx return self.application - logging.debug("Got no auth token, returning fake info.") - res = webob.Response() - res.headers['X-Auth-Token'] = 'fake' - res.headers['X-Server-Management-Url'] = req.url - res.headers['X-Storage-Url'] = '' - res.headers['X-CDN-Management-Url'] = '' - res.content_type = 'text/plain' - res.status = '204' - return res class AuthMiddleware(wsgi.Middleware): @@ -103,9 +123,15 @@ class AuthMiddleware(wsgi.Middleware): project_id = projects[0].id is_admin = self.auth.is_admin(user_id) - req.environ['nova.context'] = context.RequestContext(user_id, - project_id, - is_admin) + remote_address = getattr(req, 'remote_address', '127.0.0.1') + if FLAGS.use_forwarded_for: + remote_address = req.headers.get('X-Forwarded-For', remote_address) + ctx = context.RequestContext(user_id, + project_id, + is_admin=is_admin, + remote_address=remote_address) + req.environ['nova.context'] = ctx + if not is_admin and not self.auth.is_project_member(user_id, project_id): msg = _("%(user_id)s must be an admin or a " -- cgit From 43add36446e6b4172dc8ed5043e11187a9992474 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Mon, 22 Aug 2011 14:26:41 -0700 Subject: fix comment --- nova/auth/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index c9178c0dd..85227bea0 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -18,7 +18,7 @@ """ WARNING: This code is deprecated and will be removed. -Keystone is recommended is the recommended solution for auth management. +Keystone is the recommended solution for auth management. Nova authentication management """ -- cgit From d994b06f65af9d4c523a4123f915c6147ada7c05 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 22 Aug 2011 22:00:13 -0400 Subject: fixing exception logging --- nova/api/openstack/views/addresses.py | 9 +++------ nova/api/openstack/wsgi.py | 5 ++--- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py index d54013d61..8fccc690f 100644 --- a/nova/api/openstack/views/addresses.py +++ b/nova/api/openstack/views/addresses.py @@ -15,8 +15,6 @@ # License for the specific language governing permissions and limitations # under the License. -import traceback - from nova import flags from nova import utils from nova import log as logging @@ -54,8 +52,7 @@ class ViewBuilderV11(ViewBuilder): for interface in interfaces: try: network_label = self._extract_network_label(interface) - except TypeError: - LOG.exception(traceback.format_exc()) + except TypeError as exc: continue if network_label not in networks: @@ -90,8 +87,8 @@ class ViewBuilderV11(ViewBuilder): def _extract_network_label(self, interface): try: return interface['network']['label'] - except (TypeError, KeyError): - LOG.exception(traceback.format_exc()) + except (TypeError, KeyError) as exc: + LOG.exception(exc) raise TypeError def _extract_ipv4_addresses(self, interface): diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 572aba993..3616c9ec6 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -1,6 +1,5 @@ import json -import traceback import webob from xml.dom import minidom from xml.parsers import expat @@ -517,6 +516,6 @@ class Resource(wsgi.Application): controller_method = getattr(self.controller, action) try: return controller_method(req=request, **action_args) - except TypeError: - LOG.debug(traceback.format_exc()) + except TypeError as exc: + LOG.exception(exc) return faults.Fault(webob.exc.HTTPBadRequest()) -- cgit From 6f3610042452cc1cb6b1e0c204a127c0c48794f0 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Mon, 22 Aug 2011 19:25:22 -0700 Subject: unindented per review, added a note about auth v2 --- nova/api/openstack/auth.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index f2dc89094..6754fea27 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -49,6 +49,9 @@ class NoAuthMiddleware(wsgi.Middleware): if version == '1.1': os_url += '/' + project_id res = webob.Response() + # NOTE(vish): This is expecting and returning Auth(1.1), whereas + # keystone uses 2.0 auth. We should probably allow + # 2.0 auth here as well. res.headers['X-Auth-Token'] = '%s:%s' % (user_id, project_id) res.headers['X-Server-Management-Url'] = os_url res.headers['X-Storage-Url'] = '' @@ -56,21 +59,20 @@ class NoAuthMiddleware(wsgi.Middleware): res.content_type = 'text/plain' res.status = '204' return res - else: - token = req.headers['X-Auth-Token'] - user_id, _sep, project_id = token.partition(':') - project_id = project_id or user_id - remote_address = getattr(req, 'remote_address', '127.0.0.1') - if FLAGS.use_forwarded_for: - remote_address = req.headers.get('X-Forwarded-For', - remote_address) - ctx = context.RequestContext(user_id, - project_id, - is_admin=True, - remote_address=remote_address) - - req.environ['nova.context'] = ctx - return self.application + + token = req.headers['X-Auth-Token'] + user_id, _sep, project_id = token.partition(':') + project_id = project_id or user_id + remote_address = getattr(req, 'remote_address', '127.0.0.1') + if FLAGS.use_forwarded_for: + remote_address = req.headers.get('X-Forwarded-For', remote_address) + ctx = context.RequestContext(user_id, + project_id, + is_admin=True, + remote_address=remote_address) + + req.environ['nova.context'] = ctx + return self.application class AuthMiddleware(wsgi.Middleware): -- cgit From 5ad22e341e0ad5ff62e97906edf7822ee53b4ae9 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 22 Aug 2011 23:30:12 -0400 Subject: removing unnecessary tthing --- nova/api/openstack/views/addresses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py index 8fccc690f..8f07a2289 100644 --- a/nova/api/openstack/views/addresses.py +++ b/nova/api/openstack/views/addresses.py @@ -52,7 +52,7 @@ class ViewBuilderV11(ViewBuilder): for interface in interfaces: try: network_label = self._extract_network_label(interface) - except TypeError as exc: + except TypeError: continue if network_label not in networks: -- cgit From 4c2674516897b6cce0441efe4ebb005c01cb3411 Mon Sep 17 00:00:00 2001 From: Christopher MacGown Date: Mon, 22 Aug 2011 21:06:47 -0700 Subject: Added the fixes suggested by Eric Windisch from cloudscaling... --- nova/virt/disk.py | 2 +- nova/virt/xenapi/vm_utils.py | 2 +- nova/virt/xenapi/vmops.py | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/nova/virt/disk.py b/nova/virt/disk.py index 54b191fa9..809d3323c 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -228,7 +228,7 @@ def _inject_metadata_into_fs(metadata, fs, execute=None): metadata_path = os.path.join(fs, "meta.js") metadata = dict([(m.key, m.value) for m in metadata]) - utils.execute('sudo', 'tee', '-a', metadata_path, + utils.execute('sudo', 'tee', metadata_path, process_input=json.dumps(metadata)) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 3861f6bd8..e517dcf28 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -641,7 +641,7 @@ class VMHelper(HelperBase): # everything mount_required = False key, net, metadata = _prepare_injectables(instance, network_info) - mount_required = key or net + mount_required = key or net or metadata if not mount_required: return diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index b1522729a..606041d12 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -251,8 +251,9 @@ class VMOps(object): bootable=False) userdevice += 1 - # Alter the image before VM start for, e.g. network injection - if FLAGS.flat_injected: + # Alter the image before VM start for, e.g. network injection also + # alter the image if there's metadata. + if FLAGS.flat_injected or instance['metadata']: VMHelper.preconfigure_instance(self._session, instance, first_vdi_ref, network_info) -- cgit From 7f1adb50cfab91a553f2d129b9b2eef1e5b2145b Mon Sep 17 00:00:00 2001 From: Christopher MacGown Date: Mon, 22 Aug 2011 22:17:51 -0700 Subject: Moved migration and fixed tests from upstream --- nova/compute/api.py | 2 +- .../versions/041_add_config_drive_to_instances.py | 38 ++++++++++++++++++++++ nova/tests/api/openstack/test_servers.py | 28 +++++++++------- nova/tests/test_compute.py | 2 +- nova/virt/disk.py | 2 +- nova/virt/libvirt/connection.py | 7 ++-- nova/virt/xenapi/vm_utils.py | 2 +- 7 files changed, 61 insertions(+), 20 deletions(-) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/041_add_config_drive_to_instances.py diff --git a/nova/compute/api.py b/nova/compute/api.py index 74149f17d..69f76bf40 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -526,7 +526,7 @@ class API(base.Base): availability_zone, user_data, metadata, injected_files, admin_password, zone_blob, reservation_id, access_ip_v4, access_ip_v6, - requested_networks, config_drive + requested_networks, config_drive) block_device_mapping = block_device_mapping or [] instances = [] diff --git a/nova/db/sqlalchemy/migrate_repo/versions/041_add_config_drive_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/041_add_config_drive_to_instances.py new file mode 100644 index 000000000..d3058f00d --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/041_add_config_drive_to_instances.py @@ -0,0 +1,38 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 Piston Cloud Computing, Inc. +# +# 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() + +instances = Table("instances", meta, + Column("id", Integer(), primary_key=True, nullable=False)) + +# matches the size of an image_ref +config_drive_column = Column("config_drive", String(255), nullable=True) + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + instances.create_column(config_drive_column) + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + instances.drop_column(config_drive_column) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index f854a500c..aec2ad947 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1776,8 +1776,8 @@ class ServersTest(test.TestCase): self.config_drive = True self._setup_for_create_instance() - image_href = 'http://localhost/v1.1/images/2' - flavor_ref = 'http://localhost/v1.1/flavors/3' + image_href = 'http://localhost/v1.1/123/images/2' + flavor_ref = 'http://localhost/v1.1/123/flavors/3' body = { 'server': { 'name': 'config_drive_test', @@ -1792,12 +1792,13 @@ 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" res = req.get_response(fakes.wsgi_app()) + print res self.assertEqual(res.status_int, 202) server = json.loads(res.body)['server'] self.assertEqual(1, server['id']) @@ -1807,8 +1808,8 @@ class ServersTest(test.TestCase): self.config_drive = 2 self._setup_for_create_instance() - image_href = 'http://localhost/v1.1/images/2' - flavor_ref = 'http://localhost/v1.1/flavors/3' + image_href = 'http://localhost/v1.1/123/images/2' + flavor_ref = 'http://localhost/v1.1/123/flavors/3' body = { 'server': { 'name': 'config_drive_test', @@ -1823,7 +1824,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" @@ -1840,8 +1841,8 @@ class ServersTest(test.TestCase): self.config_drive = "asdf" self._setup_for_create_instance() - image_href = 'http://localhost/v1.1/images/2' - flavor_ref = 'http://localhost/v1.1/flavors/3' + image_href = 'http://localhost/v1.1/123/images/2' + flavor_ref = 'http://localhost/v1.1/123/flavors/3' body = { 'server': { 'name': 'config_drive_test', @@ -1856,7 +1857,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" @@ -1867,8 +1868,8 @@ class ServersTest(test.TestCase): def test_create_instance_without_config_drive_v1_1(self): self._setup_for_create_instance() - image_href = 'http://localhost/v1.1/images/2' - flavor_ref = 'http://localhost/v1.1/flavors/3' + image_href = 'http://localhost/v1.1/123/images/2' + flavor_ref = 'http://localhost/v1.1/123/flavors/3' body = { 'server': { 'name': 'config_drive_test', @@ -1883,7 +1884,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" @@ -3588,6 +3589,7 @@ class ServersViewBuilderV11Test(test.TestCase): "id": 1, "uuid": self.instance['uuid'], "name": "test_server", + "config_drive": None, "links": [ { "rel": "self", @@ -3747,6 +3749,7 @@ class ServersViewBuilderV11Test(test.TestCase): }, "addresses": {}, "metadata": {}, + "config_drive": None, "accessIPv4": "1.2.3.4", "accessIPv6": "", "links": [ @@ -3801,6 +3804,7 @@ class ServersViewBuilderV11Test(test.TestCase): }, "addresses": {}, "metadata": {}, + "config_drive": None, "accessIPv4": "", "accessIPv6": "fead::1234", "links": [ diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index b75e25dda..0523d73b6 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -163,7 +163,7 @@ class ComputeTestCase(test.TestCase): def test_create_instance_associates_config_drive(self): """Make sure create associates a config drive.""" - instance_id = self._create_instance(params={'config_drive': True,}) + instance_id = self._create_instance(params={'config_drive': True, }) try: self.compute.run_instance(self.context, instance_id) diff --git a/nova/virt/disk.py b/nova/virt/disk.py index 809d3323c..52b2881e8 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -228,7 +228,7 @@ def _inject_metadata_into_fs(metadata, fs, execute=None): metadata_path = os.path.join(fs, "meta.js") metadata = dict([(m.key, m.value) for m in metadata]) - utils.execute('sudo', 'tee', metadata_path, + utils.execute('sudo', 'tee', metadata_path, process_input=json.dumps(metadata)) diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py index 23fa86f65..4388291db 100644 --- a/nova/virt/libvirt/connection.py +++ b/nova/virt/libvirt/connection.py @@ -131,8 +131,8 @@ flags.DEFINE_string('libvirt_vif_type', 'bridge', flags.DEFINE_string('libvirt_vif_driver', 'nova.virt.libvirt.vif.LibvirtBridgeDriver', 'The libvirt VIF driver to configure the VIFs.') -flags.DEFINE_string('default_local_format', - None, +flags.DEFINE_string('default_local_format', + None, 'The default format a local_volume will be formatted with ' 'on creation.') @@ -970,7 +970,7 @@ class LibvirtConnection(driver.ComputeDriver): for injection in ('metadata', 'key', 'net'): if locals()[injection]: LOG.info(_('instance %(inst_name)s: injecting ' - '%(injection)s into image %(img_id)s' + '%(injection)s into image %(img_id)s' % locals())) try: disk.inject_data(injection_path, key, net, metadata, @@ -1106,7 +1106,6 @@ class LibvirtConnection(driver.ComputeDriver): block_device_info)): xml_info['swap_device'] = self.default_swap_device - config_drive = False if instance.get('config_drive') or instance.get('config_drive_id'): xml_info['config_drive'] = xml_info['basepath'] + "/disk.config" diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 18fe84e6c..efbea7076 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -741,7 +741,7 @@ class VMHelper(HelperBase): # if at all, so determine whether it's required first, and then do # everything mount_required = False - key, net, metadata = _prepare_injectables(instance, network_info) + key, net, metadata = _prepare_injectables(instance, network_info) mount_required = key or net or metadata if not mount_required: return -- cgit From 909e0ea5c61ba66e5c07b91ff225d64adf60f960 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 23 Aug 2011 10:21:07 -0400 Subject: Move use_ipv6 into flags. Its used in multiple places (network manager and the OSAPI) and should be defined at the top level. --- nova/flags.py | 2 ++ nova/network/manager.py | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/flags.py b/nova/flags.py index 48d5e8168..f822ae61a 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -402,3 +402,5 @@ DEFINE_bool('resume_guests_state_on_host_boot', False, DEFINE_string('root_helper', 'sudo', 'Command prefix to use for running commands as root') + +DEFINE_bool('use_ipv6', False, 'use the ipv6') diff --git a/nova/network/manager.py b/nova/network/manager.py index aa2a3700c..404a3180e 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -106,8 +106,6 @@ flags.DEFINE_integer('create_unique_mac_address_attempts', 5, 'Number of attempts to create unique mac address') flags.DEFINE_bool('auto_assign_floating_ip', False, 'Autoassigning floating ip to VM') -flags.DEFINE_bool('use_ipv6', False, - 'use the ipv6') flags.DEFINE_string('network_host', socket.gethostname(), 'Network host to use for ip allocation in flat modes') flags.DEFINE_bool('fake_call', False, -- cgit From e1c27761863a50bf33a2dcfffa96e911ae9b5b55 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 23 Aug 2011 10:31:34 -0400 Subject: 'use the ipv6' -- 'use ipv6' --- nova/flags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/flags.py b/nova/flags.py index f822ae61a..ce5356723 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -403,4 +403,4 @@ DEFINE_bool('resume_guests_state_on_host_boot', False, DEFINE_string('root_helper', 'sudo', 'Command prefix to use for running commands as root') -DEFINE_bool('use_ipv6', False, 'use the ipv6') +DEFINE_bool('use_ipv6', False, 'use ipv6') -- cgit From 5ae44219fd82d843cc5e715c318d9e80ab20b1a2 Mon Sep 17 00:00:00 2001 From: Nachi Ueno Date: Tue, 23 Aug 2011 08:07:25 -0700 Subject: Fixed typo and docstring and example class name --- nova/notifier/api.py | 7 +++--- nova/tests/example/__init__.py | 34 ---------------------------- nova/tests/example/example_a.py | 32 -------------------------- nova/tests/example/example_b.py | 32 -------------------------- nova/tests/monkey_patch_example/__init__.py | 34 ++++++++++++++++++++++++++++ nova/tests/monkey_patch_example/example_a.py | 32 ++++++++++++++++++++++++++ nova/tests/monkey_patch_example/example_b.py | 32 ++++++++++++++++++++++++++ nova/tests/test_utils.py | 25 ++++++++++---------- nova/utils.py | 4 ++-- 9 files changed, 116 insertions(+), 116 deletions(-) delete mode 100644 nova/tests/example/__init__.py delete mode 100644 nova/tests/example/example_a.py delete mode 100644 nova/tests/example/example_b.py create mode 100644 nova/tests/monkey_patch_example/__init__.py create mode 100644 nova/tests/monkey_patch_example/example_a.py create mode 100644 nova/tests/monkey_patch_example/example_b.py diff --git a/nova/notifier/api.py b/nova/notifier/api.py index a98f17dbe..f5cf95d2a 100644 --- a/nova/notifier/api.py +++ b/nova/notifier/api.py @@ -41,11 +41,10 @@ class BadPriorityException(Exception): def notify_decorator(name, fn): """ decorator for notify which is used from utils.monkey_patch() - Parameters: - - name - name of the function - function - object of the function + :param name: name of the function + :param function: - object of the function + :returns: function -- decorated function """ def wrapped_func(*args, **kwarg): diff --git a/nova/tests/example/__init__.py b/nova/tests/example/__init__.py deleted file mode 100644 index 1cfdf8a7e..000000000 --- a/nova/tests/example/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright 2011 OpenStack LLC. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -"""Example Module for testing utils.monkey_patch() -""" - - -CALLED_FUNCTION = [] - - -def example_decorator(name, function): - """ decorator for notify which is used from utils.monkey_patch() - Parameters: - - name - name of the function - function - object of the function - - - """ - def wrapped_func(*args, **kwarg): - CALLED_FUNCTION.append(name) - return function(*args, **kwarg) - return wrapped_func diff --git a/nova/tests/example/example_a.py b/nova/tests/example/example_a.py deleted file mode 100644 index 91bf048e4..000000000 --- a/nova/tests/example/example_a.py +++ /dev/null @@ -1,32 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Example Module A for testing utils.monkey_patch() -""" - - -def example_function_a(): - return 'Example function' - - -class ExampleClassA(): - def example_method(self): - return 'Example method' - - def example_method_add(self, arg1, arg2): - return arg1 + arg2 diff --git a/nova/tests/example/example_b.py b/nova/tests/example/example_b.py deleted file mode 100644 index edd267c4f..000000000 --- a/nova/tests/example/example_b.py +++ /dev/null @@ -1,32 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Example Module B for testing utils.monkey_patch() -""" - - -def example_function_b(): - return 'Example function' - - -class ExampleClassB(): - def example_method(self): - return 'Example method' - - def example_method_add(self, arg1, arg2): - return arg1 + arg2 diff --git a/nova/tests/monkey_patch_example/__init__.py b/nova/tests/monkey_patch_example/__init__.py new file mode 100644 index 000000000..1cfdf8a7e --- /dev/null +++ b/nova/tests/monkey_patch_example/__init__.py @@ -0,0 +1,34 @@ +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +"""Example Module for testing utils.monkey_patch() +""" + + +CALLED_FUNCTION = [] + + +def example_decorator(name, function): + """ decorator for notify which is used from utils.monkey_patch() + Parameters: + + name - name of the function + function - object of the function + + + """ + def wrapped_func(*args, **kwarg): + CALLED_FUNCTION.append(name) + return function(*args, **kwarg) + return wrapped_func diff --git a/nova/tests/monkey_patch_example/example_a.py b/nova/tests/monkey_patch_example/example_a.py new file mode 100644 index 000000000..91bf048e4 --- /dev/null +++ b/nova/tests/monkey_patch_example/example_a.py @@ -0,0 +1,32 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Example Module A for testing utils.monkey_patch() +""" + + +def example_function_a(): + return 'Example function' + + +class ExampleClassA(): + def example_method(self): + return 'Example method' + + def example_method_add(self, arg1, arg2): + return arg1 + arg2 diff --git a/nova/tests/monkey_patch_example/example_b.py b/nova/tests/monkey_patch_example/example_b.py new file mode 100644 index 000000000..edd267c4f --- /dev/null +++ b/nova/tests/monkey_patch_example/example_b.py @@ -0,0 +1,32 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Example Module B for testing utils.monkey_patch() +""" + + +def example_function_b(): + return 'Example function' + + +class ExampleClassB(): + def example_method(self): + return 'Example method' + + def example_method_add(self, arg1, arg2): + return arg1 + arg2 diff --git a/nova/tests/test_utils.py b/nova/tests/test_utils.py index f80ffb179..1ba794a1a 100644 --- a/nova/tests/test_utils.py +++ b/nova/tests/test_utils.py @@ -401,15 +401,16 @@ class MonkeyPatchTestCase(test.TestCase): """Unit test for utils.monkey_patch().""" def setUp(self): super(MonkeyPatchTestCase, self).setUp() + self.example_package = 'nova.tests.monkey_patch_example.' self.flags( monkey_patch=True, - monkey_patch_modules=['nova.tests.example.example_a' + ':' - + 'nova.tests.example.example_decorator']) + monkey_patch_modules=[self.example_package + 'example_a' + ':' + + self.example_package + 'example_decorator']) def test_monkey_patch(self): utils.monkey_patch() - nova.tests.example.CALLED_FUNCTION = [] - from nova.tests.example import example_a, example_b + nova.tests.monkey_patch_example.CALLED_FUNCTION = [] + from nova.tests.monkey_patch_example import example_a, example_b self.assertEqual('Example function', example_a.example_function_a()) exampleA = example_a.ExampleClassA() @@ -423,18 +424,18 @@ class MonkeyPatchTestCase(test.TestCase): ret_b = exampleB.example_method_add(3, 5) self.assertEqual(ret_b, 8) - package_a = 'nova.tests.example.example_a.' + package_a = self.example_package + 'example_a.' self.assertTrue(package_a + 'example_function_a' - in nova.tests.example.CALLED_FUNCTION) + in nova.tests.monkey_patch_example.CALLED_FUNCTION) self.assertTrue(package_a + 'ExampleClassA.example_method' - in nova.tests.example.CALLED_FUNCTION) + in nova.tests.monkey_patch_example.CALLED_FUNCTION) self.assertTrue(package_a + 'ExampleClassA.example_method_add' - in nova.tests.example.CALLED_FUNCTION) - package_b = 'nova.tests.example.example_b.' + in nova.tests.monkey_patch_example.CALLED_FUNCTION) + package_b = self.example_package + 'example_b.' self.assertFalse(package_b + 'example_function_b' - in nova.tests.example.CALLED_FUNCTION) + in nova.tests.monkey_patch_example.CALLED_FUNCTION) self.assertFalse(package_b + 'ExampleClassB.example_method' - in nova.tests.example.CALLED_FUNCTION) + in nova.tests.monkey_patch_example.CALLED_FUNCTION) self.assertFalse(package_b + 'ExampleClassB.example_method_add' - in nova.tests.example.CALLED_FUNCTION) + in nova.tests.monkey_patch_example.CALLED_FUNCTION) diff --git a/nova/utils.py b/nova/utils.py index 44a0d4398..edf67384d 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -866,13 +866,13 @@ def monkey_patch(): # If FLAGS.monkey_patch is not True, this function do nothing. if not FLAGS.monkey_patch: return - # Get list of moudles and decorators + # Get list of modules and decorators for module_and_decorator in FLAGS.monkey_patch_modules: module, decorator_name = module_and_decorator.split(':') # import decorator function decorator = import_class(decorator_name) __import__(module) - # Retrive module information using pyclbr + # Retrieve module information using pyclbr module_data = pyclbr.readmodule_ex(module) for key in module_data.keys(): # set the decorator for the class methods -- cgit From f380b65cdce439d440b68b0f4a65be45d13ce453 Mon Sep 17 00:00:00 2001 From: Nachi Ueno Date: Tue, 23 Aug 2011 08:51:44 -0700 Subject: Removed blank line --- nova/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/utils.py b/nova/utils.py index 5c7d52c70..21e6221b2 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -851,7 +851,6 @@ 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 -- cgit From 76f02277a3677d40a13a8b05a12f9d83053808c3 Mon Sep 17 00:00:00 2001 From: Nachi Ueno Date: Tue, 23 Aug 2011 09:46:49 -0700 Subject: Fixed some docstring Added default publisher_id flagw --- nova/notifier/api.py | 7 +++++-- nova/tests/monkey_patch_example/__init__.py | 13 ++++++------- nova/tests/monkey_patch_example/example_a.py | 7 ++----- nova/tests/monkey_patch_example/example_b.py | 6 ++---- nova/tests/test_notifier.py | 3 +-- 5 files changed, 16 insertions(+), 20 deletions(-) diff --git a/nova/notifier/api.py b/nova/notifier/api.py index f5cf95d2a..6ef4a050e 100644 --- a/nova/notifier/api.py +++ b/nova/notifier/api.py @@ -25,6 +25,9 @@ FLAGS = flags.FLAGS flags.DEFINE_string('default_notification_level', 'INFO', 'Default notification level for outgoing notifications') +flags.DEFINE_string('default_publisher_id', FLAGS.host, + 'Default publisher_id for outgoing notifications') + WARN = 'WARN' INFO = 'INFO' @@ -55,9 +58,9 @@ def notify_decorator(name, fn): body['args'].append(arg) for key in kwarg: body['kwarg'][key] = kwarg[key] - notify(FLAGS.host, + notify(FLAGS.default_publisher_id, name, - DEBUG, + FLAGS.default_notification_level, body) return fn(*args, **kwarg) return wrapped_func diff --git a/nova/tests/monkey_patch_example/__init__.py b/nova/tests/monkey_patch_example/__init__.py index 1cfdf8a7e..25cf9ccfe 100644 --- a/nova/tests/monkey_patch_example/__init__.py +++ b/nova/tests/monkey_patch_example/__init__.py @@ -1,3 +1,5 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + # Copyright 2011 OpenStack LLC. # All Rights Reserved. # @@ -12,8 +14,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -"""Example Module for testing utils.monkey_patch() -""" +"""Example Module for testing utils.monkey_patch().""" CALLED_FUNCTION = [] @@ -21,12 +22,10 @@ CALLED_FUNCTION = [] def example_decorator(name, function): """ decorator for notify which is used from utils.monkey_patch() - Parameters: - - name - name of the function - function - object of the function - + :param name: name of the function + :param function: - object of the function + :returns: function -- decorated function """ def wrapped_func(*args, **kwarg): CALLED_FUNCTION.append(name) diff --git a/nova/tests/monkey_patch_example/example_a.py b/nova/tests/monkey_patch_example/example_a.py index 91bf048e4..21e79bcb0 100644 --- a/nova/tests/monkey_patch_example/example_a.py +++ b/nova/tests/monkey_patch_example/example_a.py @@ -1,7 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. +# Copyright 2011 OpenStack LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -15,9 +14,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - -"""Example Module A for testing utils.monkey_patch() -""" +"""Example Module A for testing utils.monkey_patch().""" def example_function_a(): diff --git a/nova/tests/monkey_patch_example/example_b.py b/nova/tests/monkey_patch_example/example_b.py index edd267c4f..9d8f6d339 100644 --- a/nova/tests/monkey_patch_example/example_b.py +++ b/nova/tests/monkey_patch_example/example_b.py @@ -1,7 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. +# Copyright 2011 OpenStack LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -16,8 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. -"""Example Module B for testing utils.monkey_patch() -""" +"""Example Module B for testing utils.monkey_patch().""" def example_function_b(): diff --git a/nova/tests/test_notifier.py b/nova/tests/test_notifier.py index ab5dfb692..7de3a4a99 100644 --- a/nova/tests/test_notifier.py +++ b/nova/tests/test_notifier.py @@ -141,8 +141,7 @@ class NotifierTestCase(test.TestCase): def example_api(arg1, arg2): return arg1 + arg2 - example_api =\ - nova.notifier.api.notify_decorator( + example_api = nova.notifier.api.notify_decorator( 'example_api', example_api) -- cgit From 295bcc8ef70d767bf1539defe1a79a67bdf555ff Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 23 Aug 2011 13:44:39 -0400 Subject: updating tests --- nova/tests/api/openstack/test_servers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index cec6eecc0..e5c1f2c34 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -785,7 +785,7 @@ class ServersTest(test.TestCase): new_return_server = return_server_with_attributes(interfaces=ifaces) 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/fake/servers/1') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) res_dict = json.loads(res.body) -- cgit From 6bbef7627200f6c6ef27b5ae5c9b114e8e6d0f52 Mon Sep 17 00:00:00 2001 From: Nachi Ueno Date: Tue, 23 Aug 2011 12:06:25 -0700 Subject: Fixed doc string --- nova/flags.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nova/flags.py b/nova/flags.py index 05d0db8af..95000df1b 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -409,7 +409,7 @@ DEFINE_bool('monkey_patch', False, 'Whether to log monkey patching') DEFINE_list('monkey_patch_modules', - ['nova.api.ec2.cloud:nova.notifier.api.notify_decorator', - 'nova.compute.api:nova.notifier.api.notify_decorator'], - 'Module list representing monkey\ - patched module and decorator') + ['nova.api.ec2.cloud:nova.notifier.api.notify_decorator', + 'nova.compute.api:nova.notifier.api.notify_decorator'], + 'Module list representing monkey ' + 'patched module and decorator') -- cgit From a5fd82841bfada1b59066d82094f41ffa9389dec Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 23 Aug 2011 12:21:52 -0700 Subject: fix for rc generation using noauth. --- nova/auth/manager.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 85227bea0..44e6e11ac 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -41,10 +41,13 @@ from nova.auth import signer FLAGS = flags.FLAGS +flags.DEFINE_bool('use_deprecated_auth', + False, + 'This flag must be set to use old style auth') + flags.DEFINE_list('allowed_roles', ['cloudadmin', 'itsec', 'sysadmin', 'netadmin', 'developer'], 'Allowed roles for project') - # NOTE(vish): a user with one of these roles will be a superuser and # have access to all api commands flags.DEFINE_list('superuser_roles', ['cloudadmin'], @@ -814,7 +817,13 @@ class AuthManager(object): s3_host = host ec2_host = host rc = open(FLAGS.credentials_template).read() - rc = rc % {'access': user.access, + # NOTE(vish): Deprecated auth uses an access key, no auth uses a + # the user_id in place of it. + if FLAGS.use_deprecated_auth: + access = user.access + else: + access = user.id + rc = rc % {'access': access, 'project': pid, 'secret': user.secret, 'ec2': '%s://%s:%s%s' % (FLAGS.ec2_scheme, -- cgit From 8c30e3e4b1847e6f44790fc4b614fe56de84cbfb Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 23 Aug 2011 13:44:21 -0700 Subject: Forgot to set the flag for the test --- nova/tests/test_auth.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/tests/test_auth.py b/nova/tests/test_auth.py index 4561eb7f2..1b3166af7 100644 --- a/nova/tests/test_auth.py +++ b/nova/tests/test_auth.py @@ -147,6 +147,7 @@ class _AuthManagerBaseTestCase(test.TestCase): '/services/Cloud')) def test_can_get_credentials(self): + self.flags(use_deprecated_auth=True) st = {'access': 'access', 'secret': 'secret'} with user_and_project_generator(self.manager, user_state=st) as (u, p): credentials = self.manager.get_environment_rc(u, p) -- cgit