From 6930c62a02a39f64506a7b2d2ec5b04dbff5fe3a Mon Sep 17 00:00:00 2001 From: sateesh Date: Mon, 5 Sep 2011 12:51:07 +0530 Subject: Multi-NIC support for vmwareapi virt driver in nova. Does injection of Multi-NIC information to instances with Operating system flavors Ubuntu, Windows and RHEL. vmwareapi virt driver now relies on calls to network manager instead of nova db calls for network configuration information of instance. Ensure if port group is properly associated with vlan_interface specified in case of VLAN networking for instances. Re-oranized VMWareVlanBridgeDriver and added session parmeter to methods to use existing session. Also removed session creation code as session comes as argument. Added check for flat_inject flag before attempting an inject operation. Removed stale code from vmwareapi stubs. Also updated some comments to be more meaningful. Did pep8 and pylint checks. Tried to improve pylint score for newly added lines of code. --- nova/tests/vmwareapi/stubs.py | 2 - nova/virt/vmwareapi/fake.py | 2 +- nova/virt/vmwareapi/vif.py | 27 +++++------ nova/virt/vmwareapi/vm_util.py | 26 +++++----- nova/virt/vmwareapi/vmops.py | 106 ++++++++++++++++++++++++----------------- tools/esx/guest_tool.py | 70 +++++++++++++++------------ 6 files changed, 124 insertions(+), 109 deletions(-) diff --git a/nova/tests/vmwareapi/stubs.py b/nova/tests/vmwareapi/stubs.py index 0ed5e9b68..7de10e612 100644 --- a/nova/tests/vmwareapi/stubs.py +++ b/nova/tests/vmwareapi/stubs.py @@ -45,8 +45,6 @@ def set_stubs(stubs): stubs.Set(vmware_images, 'get_vmdk_size_and_properties', fake.fake_get_vmdk_size_and_properties) stubs.Set(vmware_images, 'upload_image', fake.fake_upload_image) - stubs.Set(vmwareapi_conn.VMWareAPISession, "_get_vim_object", - fake_get_vim_object) stubs.Set(vmwareapi_conn.VMWareAPISession, "_get_vim_object", fake_get_vim_object) stubs.Set(vmwareapi_conn.VMWareAPISession, "_is_vim_object", diff --git a/nova/virt/vmwareapi/fake.py b/nova/virt/vmwareapi/fake.py index 4c62d18bb..f47ffdf1c 100644 --- a/nova/virt/vmwareapi/fake.py +++ b/nova/virt/vmwareapi/fake.py @@ -409,7 +409,7 @@ def fake_plug_vifs(*args, **kwargs): def fake_get_network(*args, **kwargs): """Fake get network.""" - return [{'type': 'fake'}] + return {'type': 'fake'} def fake_fetch_image(image, instance, **kwargs): diff --git a/nova/virt/vmwareapi/vif.py b/nova/virt/vmwareapi/vif.py index fb6548b34..9906b89e1 100644 --- a/nova/virt/vmwareapi/vif.py +++ b/nova/virt/vmwareapi/vif.py @@ -17,42 +17,35 @@ """VIF drivers for VMWare.""" -from nova import db from nova import exception from nova import flags from nova import log as logging -from nova import utils from nova.virt.vif import VIFDriver -from nova.virt.vmwareapi_conn import VMWareAPISession from nova.virt.vmwareapi import network_utils LOG = logging.getLogger("nova.virt.vmwareapi.vif") FLAGS = flags.FLAGS +FLAGS['vmwareapi_vlan_interface'].SetDefault('vmnic0') class VMWareVlanBridgeDriver(VIFDriver): """VIF Driver to setup bridge/VLAN networking using VMWare API.""" def plug(self, instance, network, mapping): + """Plug the VIF to specified instance using information passed. + Currently we are plugging the VIF(s) during instance creation itself. + We can use this method when we add support to add additional NIC to + an existing instance.""" + pass + + def ensure_vlan_bridge(self, session, network): """Create a vlan and bridge unless they already exist.""" vlan_num = network['vlan'] bridge = network['bridge'] - bridge_interface = network['bridge_interface'] + vlan_interface = FLAGS.vmwareapi_vlan_interface - # Open vmwareapi session - host_ip = FLAGS.vmwareapi_host_ip - host_username = FLAGS.vmwareapi_host_username - host_password = FLAGS.vmwareapi_host_password - if not host_ip or host_username is None or host_password is None: - raise Exception(_('Must specify vmwareapi_host_ip, ' - 'vmwareapi_host_username ' - 'and vmwareapi_host_password to use ' - 'connection_type=vmwareapi')) - session = VMWareAPISession(host_ip, host_username, host_password, - FLAGS.vmwareapi_api_retry_count) - vlan_interface = bridge_interface # Check if the vlan_interface physical network adapter exists on the # host. if not network_utils.check_if_vlan_interface_exists(session, @@ -92,4 +85,6 @@ class VMWareVlanBridgeDriver(VIFDriver): pgroup=pg_vlanid) def unplug(self, instance, network, mapping): + """Cleanup operations like deleting port group if no instance + is associated with it.""" pass diff --git a/nova/virt/vmwareapi/vm_util.py b/nova/virt/vmwareapi/vm_util.py index 82b5f7214..dd1c81196 100644 --- a/nova/virt/vmwareapi/vm_util.py +++ b/nova/virt/vmwareapi/vm_util.py @@ -39,8 +39,7 @@ def split_datastore_path(datastore_path): def get_vm_create_spec(client_factory, instance, data_store_name, - network_name="vmnet0", - os_type="otherGuest", network_ref=None): + vif_infos, os_type="otherGuest"): """Builds the VM Create spec.""" config_spec = client_factory.create('ns0:VirtualMachineConfigSpec') config_spec.name = instance.name @@ -61,14 +60,12 @@ def get_vm_create_spec(client_factory, instance, data_store_name, config_spec.numCPUs = int(instance.vcpus) config_spec.memoryMB = int(instance.memory_mb) - mac_address = None - if instance['mac_addresses']: - mac_address = instance['mac_addresses'][0]['address'] + vif_spec_list = [] + for vif_info in vif_infos: + vif_spec = create_network_spec(client_factory, vif_info) + vif_spec_list.append(vif_spec) - nic_spec = create_network_spec(client_factory, - network_name, mac_address) - - device_config_spec = [nic_spec] + device_config_spec = vif_spec_list config_spec.deviceChange = device_config_spec return config_spec @@ -93,8 +90,7 @@ def create_controller_spec(client_factory, key): return virtual_device_config -def create_network_spec(client_factory, network_name, mac_address, - network_ref=None): +def create_network_spec(client_factory, vif_info): """ Builds a config spec for the addition of a new network adapter to the VM. @@ -109,6 +105,9 @@ def create_network_spec(client_factory, network_name, mac_address, # NOTE(asomya): Only works on ESXi if the portgroup binding is set to # ephemeral. Invalid configuration if set to static and the NIC does # not come up on boot if set to dynamic. + network_ref = vif_info['network_ref'] + network_name = vif_info['network_name'] + mac_address = vif_info['mac_address'] backing = None if (network_ref and network_ref['type'] == "DistributedVirtualPortgroup"): @@ -295,11 +294,8 @@ def get_dummy_vm_create_spec(client_factory, name, data_store_name): return config_spec -def get_machine_id_change_spec(client_factory, mac, ip_addr, netmask, - gateway, broadcast, dns): +def get_machine_id_change_spec(client_factory, machine_id_str): """Builds the machine id change config spec.""" - machine_id_str = "%s;%s;%s;%s;%s;%s" % (mac, ip_addr, netmask, - gateway, broadcast, dns) virtual_machine_config_spec = \ client_factory.create('ns0:VirtualMachineConfigSpec') diff --git a/nova/virt/vmwareapi/vmops.py b/nova/virt/vmwareapi/vmops.py index 07a6ba6ab..3ac99f923 100644 --- a/nova/virt/vmwareapi/vmops.py +++ b/nova/virt/vmwareapi/vmops.py @@ -27,7 +27,6 @@ import urllib2 import uuid from nova import context as nova_context -from nova import db from nova import exception from nova import flags from nova import log as logging @@ -111,22 +110,6 @@ class VMWareVMOps(object): client_factory = self._session._get_vim().client.factory service_content = self._session._get_vim().get_service_content() - network = db.network_get_by_instance(nova_context.get_admin_context(), - instance['id']) - - net_name = network['bridge'] - - def _check_if_network_bridge_exists(): - network_ref = \ - network_utils.get_network_with_the_name(self._session, - net_name) - if network_ref is None: - raise exception.NetworkNotFoundForBridge(bridge=net_name) - return network_ref - - self.plug_vifs(instance, network_info) - network_obj = _check_if_network_bridge_exists() - def _get_datastore_ref(): """Get the datastore list and choose the first local storage.""" data_stores = self._session._call_method(vim_util, "get_objects", @@ -182,11 +165,36 @@ class VMWareVMOps(object): vm_folder_mor, res_pool_mor = _get_vmfolder_and_res_pool_mors() + def _check_if_network_bridge_exists(network_name): + network_ref = \ + network_utils.get_network_with_the_name(self._session, + network_name) + if network_ref is None: + raise exception.NetworkNotFoundForBridge(bridge=network_name) + return network_ref + + def _get_vif_infos(): + vif_infos = [] + for (network, mapping) in network_info: + mac_address = mapping['mac'] + network_name = network['bridge'] + if mapping.get('should_create_vlan'): + network_ref = self._vif_driver.ensure_vlan_bridge( + self._session, network) + else: + network_ref = _check_if_network_bridge_exists(network_name) + vif_infos.append({'network_name': network_name, + 'mac_address': mac_address, + 'network_ref': network_ref, + }) + return vif_infos + + vif_infos = _get_vif_infos() + # Get the create vm config spec config_spec = vm_util.get_vm_create_spec( client_factory, instance, - data_store_name, net_name, os_type, - network_obj) + data_store_name, vif_infos, os_type) def _execute_create_vm(): """Create VM on ESX host.""" @@ -204,8 +212,10 @@ class VMWareVMOps(object): _execute_create_vm() - # Set the machine id for the VM for setting the IP - self._set_machine_id(client_factory, instance) + # Set the machine.id parameter of the instance to inject + # the NIC configuration inside the VM + if FLAGS.flat_injected: + self._set_machine_id(client_factory, instance, network_info) # Naming the VM files in correspondence with the VM instance name # The flat vmdk file name @@ -716,39 +726,45 @@ class VMWareVMOps(object): """Return link to instance's ajax console.""" return 'http://fakeajaxconsole/fake_url' - def _set_machine_id(self, client_factory, instance): + def _set_machine_id(self, client_factory, instance, network_info): """ - Set the machine id of the VM for guest tools to pick up and change - the IP. + Set the machine id of the VM for guest tools to pick up and reconfigure + the network interfaces. """ - admin_context = nova_context.get_admin_context() vm_ref = self._get_vm_ref_from_the_name(instance.name) if vm_ref is None: raise exception.InstanceNotFound(instance_id=instance.id) - network = db.network_get_by_instance(nova_context.get_admin_context(), - instance['id']) - mac_address = None - if instance['mac_addresses']: - mac_address = instance['mac_addresses'][0]['address'] - - net_mask = network["netmask"] - gateway = network["gateway"] - broadcast = network["broadcast"] - # TODO(vish): add support for dns2 - dns = network["dns1"] - - addresses = db.instance_get_fixed_addresses(admin_context, - instance['id']) - ip_addr = addresses[0] if addresses else None + + machine_id_str = '' + for (network, info) in network_info: + # TODO(vish): add support for dns2 + # TODO(sateesh): add support for injection of ipv6 configuration + ip_v4 = ip_v6 = None + if 'ips' in info and len(info['ips']) > 0: + ip_v4 = info['ips'][0] + if 'ip6s' in info and len(info['ip6s']) > 0: + ip_v6 = info['ip6s'][0] + if len(info['dns']) > 0: + dns = info['dns'][0] + else: + dns = '' + + interface_str = "%s;%s;%s;%s;%s;%s" % \ + (info['mac'], + ip_v4 and ip_v4['ip'] or '', + ip_v4 and ip_v4['netmask'] or '', + info['gateway'], + info['broadcast'], + dns) + machine_id_str = machine_id_str + interface_str + '#' machine_id_change_spec = \ - vm_util.get_machine_id_change_spec(client_factory, mac_address, - ip_addr, net_mask, gateway, - broadcast, dns) + vm_util.get_machine_id_change_spec(client_factory, machine_id_str) + LOG.debug(_("Reconfiguring VM instance %(name)s to set the machine id " "with ip - %(ip_addr)s") % ({'name': instance.name, - 'ip_addr': ip_addr})) + 'ip_addr': ip_v4['ip']})) reconfig_task = self._session._call_method(self._session._get_vim(), "ReconfigVM_Task", vm_ref, spec=machine_id_change_spec) @@ -756,7 +772,7 @@ class VMWareVMOps(object): LOG.debug(_("Reconfigured VM instance %(name)s to set the machine id " "with ip - %(ip_addr)s") % ({'name': instance.name, - 'ip_addr': ip_addr})) + 'ip_addr': ip_v4['ip']})) def _get_datacenter_name_and_ref(self): """Get the datacenter name and the reference.""" diff --git a/tools/esx/guest_tool.py b/tools/esx/guest_tool.py index 97b5302ba..5158d883a 100644 --- a/tools/esx/guest_tool.py +++ b/tools/esx/guest_tool.py @@ -81,28 +81,34 @@ def _bytes2int(bytes): def _parse_network_details(machine_id): """ - Parse the machine.id field to get MAC, IP, Netmask and Gateway fields - machine.id is of the form MAC;IP;Netmask;Gateway;Broadcast;DNS1,DNS2 - where ';' is the separator. + Parse the machine_id to get MAC, IP, Netmask and Gateway fields per NIC. + machine_id is of the form ('NIC_record#NIC_record#', '') + Each of the NIC will have record NIC_record in the form + 'MAC;IP;Netmask;Gateway;Broadcast;DNS' where ';' is field separator. + Each record is separated by '#' from next record. """ + logging.debug(_("Received machine_id from vmtools : %s") % machine_id[0]) network_details = [] if machine_id[1].strip() == "1": pass else: - network_info_list = machine_id[0].split(';') - assert len(network_info_list) % 6 == 0 - no_grps = len(network_info_list) / 6 - i = 0 - while i < no_grps: - k = i * 6 - network_details.append(( - network_info_list[k].strip().lower(), - network_info_list[k + 1].strip(), - network_info_list[k + 2].strip(), - network_info_list[k + 3].strip(), - network_info_list[k + 4].strip(), - network_info_list[k + 5].strip().split(','))) - i += 1 + for machine_id_str in machine_id[0].split('#'): + network_info_list = machine_id_str.split(';') + if len(network_info_list) % 6 != 0: + break + no_grps = len(network_info_list) / 6 + i = 0 + while i < no_grps: + k = i * 6 + network_details.append(( + network_info_list[k].strip().lower(), + network_info_list[k + 1].strip(), + network_info_list[k + 2].strip(), + network_info_list[k + 3].strip(), + network_info_list[k + 4].strip(), + network_info_list[k + 5].strip().split(','))) + i += 1 + logging.debug(_("NIC information from vmtools : %s") % network_details) return network_details @@ -279,6 +285,7 @@ def _filter_duplicates(all_entries): def _set_rhel_networking(network_details=None): + """Set IPv4 network settings for RHEL distros.""" network_details = network_details or [] all_dns_servers = [] for network_detail in network_details: @@ -320,31 +327,33 @@ def _set_rhel_networking(network_details=None): def _set_ubuntu_networking(network_details=None): + """Set IPv4 network settings for Ubuntu.""" network_details = network_details or [] - """ Set IPv4 network settings for Ubuntu """ all_dns_servers = [] - for network_detail in network_details: + interface_file_name = '/etc/network/interfaces' + # Remove file + os.remove(interface_file_name) + # Touch file + _execute(['touch', interface_file_name]) + interface_file = open(interface_file_name, 'w') + for device, network_detail in enumerate(network_details): mac_address, ip_address, subnet_mask, gateway, broadcast,\ dns_servers = network_detail all_dns_servers.extend(dns_servers) adapter_name, current_ip_address = \ _get_linux_adapter_name_and_ip_address(mac_address) - if adapter_name and not ip_address == current_ip_address: - interface_file_name = \ - '/etc/network/interfaces' - # Remove file - os.remove(interface_file_name) - # Touch file - _execute(['touch', interface_file_name]) - interface_file = open(interface_file_name, 'w') + if adapter_name: interface_file.write('\nauto %s' % adapter_name) interface_file.write('\niface %s inet static' % adapter_name) interface_file.write('\nbroadcast %s' % broadcast) interface_file.write('\ngateway %s' % gateway) interface_file.write('\nnetmask %s' % subnet_mask) - interface_file.write('\naddress %s' % ip_address) - interface_file.close() + interface_file.write('\naddress %s\n' % ip_address) + logging.debug(_("Successfully configured NIC %d with " + "NIC info %s") % (device, network_detail)) + interface_file.close() + if all_dns_servers: dns_file_name = "/etc/resolv.conf" os.remove(dns_file_name) @@ -355,7 +364,8 @@ def _set_ubuntu_networking(network_details=None): for dns_server in unique_entries: dns_file.write("\nnameserver %s" % dns_server) dns_file.close() - print "\nRestarting networking....\n" + + logging.debug(_("Restarting networking....\n")) _execute(['/etc/init.d/networking', 'restart']) -- cgit From 9b12a6c5ec11fd6ef3e110e6f0574762060ac809 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 6 Sep 2011 15:19:37 -0400 Subject: Fixes an issue where 'invalid literal for int' would occur when listing images after making a v1.1 server snapshot (with a UUID). v1.1 image id's are now treated as strings (not integer ID's). The v1.0 API still tries to treat image id's as integers but doesn't fail miserably if they are uuid's either. This should pave the way for image ID's as uuids and more closely matches the v1.1 spec with regards to images and the server refs they contain. --- nova/api/openstack/common.py | 24 ++------- nova/api/openstack/views/images.py | 10 ++++ nova/tests/api/openstack/test_common.py | 40 ++++++++++---- nova/tests/api/openstack/test_images.py | 95 +++++++++++++++++++++++++-------- 4 files changed, 118 insertions(+), 51 deletions(-) diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index d743a66ef..dba3ec8e9 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -185,30 +185,16 @@ def limited_by_marker(items, request, max_limit=FLAGS.osapi_max_limit): def get_id_from_href(href): - """Return the id portion of a url as an int. + """Return the id or uuid portion of a url. Given: 'http://www.foo.com/bar/123?q=4' - Returns: 123 + Returns: '123' - In order to support local hrefs, the href argument can be just an id: - Given: '123' - Returns: 123 + Given: 'http://www.foo.com/bar/abc123?q=4' + Returns: 'abc123' """ - LOG.debug(_("Attempting to treat %(href)s as an integer ID.") % locals()) - - try: - return int(href) - except ValueError: - pass - - LOG.debug(_("Attempting to treat %(href)s as a URL.") % locals()) - - try: - return int(urlparse.urlsplit(href).path.split('/')[-1]) - except ValueError as error: - LOG.debug(_("Failed to parse ID from %(href)s: %(error)s") % locals()) - raise + return urlparse.urlsplit("%s" % href).path.split('/')[-1] def remove_version_from_href(href): diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index 21f1b2d3e..20c99124b 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -70,6 +70,7 @@ class ViewBuilder(object): } self._build_server(image, image_obj) + self._build_image_id(image, image_obj) if detail: image.update({ @@ -95,6 +96,12 @@ class ViewBuilderV10(ViewBuilder): except (KeyError, ValueError): pass + def _build_image_id(self, image, image_obj): + try: + image['id'] = int(image_obj['id']) + except ValueError: + pass + class ViewBuilderV11(ViewBuilder): """OpenStack API v1.1 Image Builder""" @@ -118,6 +125,9 @@ class ViewBuilderV11(ViewBuilder): except KeyError: return + def _build_image_id(self, image, image_obj): + image['id'] = "%s" % image_obj['id'] + def generate_href(self, image_id): """Return an href string pointing to this object.""" return os.path.join(self.base_url, self.project_id, diff --git a/nova/tests/api/openstack/test_common.py b/nova/tests/api/openstack/test_common.py index b422bc4d1..f519ea72b 100644 --- a/nova/tests/api/openstack/test_common.py +++ b/nova/tests/api/openstack/test_common.py @@ -237,21 +237,41 @@ class MiscFunctionsTest(test.TestCase): common.remove_version_from_href, fixture) - def test_get_id_from_href(self): + def test_get_id_from_href_with_int_url(self): fixture = 'http://www.testsite.com/dir/45' actual = common.get_id_from_href(fixture) - expected = 45 + expected = '45' self.assertEqual(actual, expected) - def test_get_id_from_href_bad_request(self): - fixture = 'http://45' - self.assertRaises(ValueError, - common.get_id_from_href, - fixture) + def test_get_id_from_href_with_int(self): + fixture = '45' + actual = common.get_id_from_href(fixture) + expected = '45' + self.assertEqual(actual, expected) - def test_get_id_from_href_int(self): - fixture = 1 - self.assertEqual(fixture, common.get_id_from_href(fixture)) + def test_get_id_from_href_with_int_url_query(self): + fixture = 'http://www.testsite.com/dir/45?asdf=jkl' + actual = common.get_id_from_href(fixture) + expected = '45' + self.assertEqual(actual, expected) + + def test_get_id_from_href_with_uuid_url(self): + fixture = 'http://www.testsite.com/dir/abc123' + actual = common.get_id_from_href(fixture) + expected = "abc123" + self.assertEqual(actual, expected) + + def test_get_id_from_href_with_uuid_url_query(self): + fixture = 'http://www.testsite.com/dir/abc123?asdf=jkl' + actual = common.get_id_from_href(fixture) + expected = "abc123" + self.assertEqual(actual, expected) + + def test_get_id_from_href_with_uuid(self): + fixture = 'abc123' + actual = common.get_id_from_href(fixture) + expected = 'abc123' + self.assertEqual(actual, expected) def test_get_version_from_href(self): fixture = 'http://www.testsite.com/v1.1/images' diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 2a7cfc382..6448e9986 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -365,7 +365,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): {'id': 125, 'name': 'saving snapshot'}, {'id': 126, 'name': 'active snapshot'}, {'id': 127, 'name': 'killed snapshot'}, - {'id': 129, 'name': None}] + {'id': 128, 'name': 'active UUID snapshot'}, + {'id': 130, 'name': None}] self.assertDictListMatch(response_list, expected) @@ -403,14 +404,14 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): expected_image = { "image": { - "id": 124, + "id": '124', "name": "queued snapshot", "updated": self.NOW_API_FORMAT, "created": self.NOW_API_FORMAT, "status": "QUEUED", "progress": 0, 'server': { - 'id': 42, + 'id': '42', "links": [{ "rel": "self", "href": server_href, @@ -458,7 +459,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): self.assertEqual(expected_image.toxml(), actual_image.toxml()) def test_get_image_xml_no_name(self): - request = webob.Request.blank('/v1.0/images/129') + request = webob.Request.blank('/v1.0/images/130') request.accept = "application/xml" response = request.get_response(fakes.wsgi_app()) @@ -466,7 +467,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): expected_now = self.NOW_API_FORMAT expected_image = minidom.parseString(""" - Date: Thu, 8 Sep 2011 16:02:10 -0700 Subject: if no public-key is given (--key), do not show public-keys in metadata service --- nova/api/ec2/cloud.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 049ca6f93..c6f24858a 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -304,11 +304,6 @@ class CloudController(object): instance_ref = db.instance_get(ctxt, instance_ref[0]['id']) mpi = self._get_mpi_data(ctxt, instance_ref['project_id']) - if instance_ref['key_name']: - keys = {'0': {'_name': instance_ref['key_name'], - 'openssh-key': instance_ref['key_data']}} - else: - keys = '' hostname = instance_ref['hostname'] host = instance_ref['host'] availability_zone = self._get_availability_zone_by_host(ctxt, host) @@ -336,11 +331,15 @@ class CloudController(object): 'placement': {'availability-zone': availability_zone}, 'public-hostname': hostname, 'public-ipv4': floating_ip or '', - 'public-keys': keys, 'reservation-id': instance_ref['reservation_id'], 'security-groups': security_groups, 'mpi': mpi}} + # public-keys should only show up if it is non-empty (if user specified one) + if instance_ref['key_name']: + data['keys'] = {'0': {'_name': instance_ref['key_name'], + 'openssh-key': instance_ref['key_data']}} + for image_type in ['kernel', 'ramdisk']: if instance_ref.get('%s_id' % image_type): ec2_id = self.image_ec2_id(instance_ref['%s_id' % image_type], -- cgit From f2c887824cd56fe83f4db2bf94279684a1daba05 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Thu, 8 Sep 2011 17:42:49 -0700 Subject: metadata key is 'public-keys', not 'keys' --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index c6f24858a..c8ff47864 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -337,7 +337,7 @@ class CloudController(object): # public-keys should only show up if it is non-empty (if user specified one) if instance_ref['key_name']: - data['keys'] = {'0': {'_name': instance_ref['key_name'], + data['public-keys'] = {'0': {'_name': instance_ref['key_name'], 'openssh-key': instance_ref['key_data']}} for image_type in ['kernel', 'ramdisk']: -- cgit From 3202b7a9193796170fbb25a793e40ff14f9b9621 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Thu, 8 Sep 2011 18:02:02 -0700 Subject: put key into meta-data, not top level 'data' --- nova/api/ec2/cloud.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index c8ff47864..bb860a131 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -337,8 +337,9 @@ class CloudController(object): # public-keys should only show up if it is non-empty (if user specified one) if instance_ref['key_name']: - data['public-keys'] = {'0': {'_name': instance_ref['key_name'], - 'openssh-key': instance_ref['key_data']}} + data['meta-data']['public-keys'] = { + '0': {'_name': instance_ref['key_name'], + 'openssh-key': instance_ref['key_data']}} for image_type in ['kernel', 'ramdisk']: if instance_ref.get('%s_id' % image_type): -- cgit From 5ddfd1c1add955aa14c5e5174b1942eb8f748031 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Fri, 9 Sep 2011 10:20:36 -0700 Subject: shorten comment to < 79 chars --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index bb860a131..eafecbca8 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -335,7 +335,7 @@ class CloudController(object): 'security-groups': security_groups, 'mpi': mpi}} - # public-keys should only show up if it is non-empty (if user specified one) + # public-keys should be in meta-data only if user specified one if instance_ref['key_name']: data['meta-data']['public-keys'] = { '0': {'_name': instance_ref['key_name'], -- cgit From 050be203cb43a12ca430eadfd30c87690b33b9cf Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Mon, 12 Sep 2011 20:10:57 +0000 Subject: Add support for vendor content types --- nova/api/openstack/versions.py | 7 +++++++ nova/api/openstack/wsgi.py | 20 +++++++++++++++++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index e2f892fb6..04d9915ca 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -100,13 +100,17 @@ class Versions(wsgi.Resource): body_serializers = { 'application/atom+xml': VersionsAtomSerializer(metadata=metadata), 'application/xml': VersionsXMLSerializer(metadata=metadata), + 'application/vnd.openstack.compute+xml': + VersionsXMLSerializer(metadata=metadata), } serializer = wsgi.ResponseSerializer( body_serializers=body_serializers, headers_serializer=headers_serializer) supported_content_types = ('application/json', + 'application/vnd.openstack.compute+json', 'application/xml', + 'application/vnd.openstack.compute+xml', 'application/atom+xml') deserializer = VersionsRequestDeserializer( supported_content_types=supported_content_types) @@ -383,12 +387,15 @@ def create_resource(version='1.0'): body_serializers = { 'application/xml': VersionsXMLSerializer(), + 'application/vnd.openstack.compute+xml': VersionsXMLSerializer(), 'application/atom+xml': VersionsAtomSerializer(), } serializer = wsgi.ResponseSerializer(body_serializers) supported_content_types = ('application/json', + 'application/vnd.openstack.compute+json', 'application/xml', + 'application/vnd.openstack.compute+xml', 'application/atom+xml') deserializer = wsgi.RequestDeserializer( supported_content_types=supported_content_types) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 8641e960a..ee6b87403 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -28,8 +28,12 @@ class Request(webob.Request): Based on the query extension then the Accept header. """ + LOG.info('supported = %s' % repr(supported_content_types)) supported_content_types = supported_content_types or \ - ('application/json', 'application/xml') + ('application/json', + 'application/vnd.openstack.compute+json', + 'application/xml', + 'application/vnd.openstack.compute+xml') parts = self.path.rsplit('.', 1) if len(parts) > 1: @@ -51,7 +55,10 @@ class Request(webob.Request): if not "Content-Type" in self.headers: return None - allowed_types = ("application/xml", "application/json") + allowed_types = ('application/json', + 'application/vnd.openstack.compute+json', + 'application/xml', + 'application/vnd.openstack.compute+xml') content_type = self.content_type if content_type not in allowed_types: @@ -191,11 +198,16 @@ class RequestDeserializer(object): supported_content_types=None): self.supported_content_types = supported_content_types or \ - ('application/json', 'application/xml') + ('application/json', + 'application/vnd.openstack.compute+json', + 'application/xml', + 'application/vnd.openstack.compute+xml') self.body_deserializers = { 'application/xml': XMLDeserializer(), + 'application/vnd.openstack.compute+xml': XMLDeserializer(), 'application/json': JSONDeserializer(), + 'application/vnd.openstack.compute+json': JSONDeserializer(), } self.body_deserializers.update(body_deserializers or {}) @@ -409,7 +421,9 @@ class ResponseSerializer(object): def __init__(self, body_serializers=None, headers_serializer=None): self.body_serializers = { 'application/xml': XMLDictSerializer(), + 'application/vnd.openstack.compute+xml': XMLDictSerializer(), 'application/json': JSONDictSerializer(), + 'application/vnd.openstack.compute+json': JSONDictSerializer(), } self.body_serializers.update(body_serializers or {}) -- cgit From e411fcd647e3cdcf415465288e527aecfd026fc5 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Tue, 13 Sep 2011 14:15:29 -0400 Subject: add additional data to flavor's ViewBuilder This adds missing fields that were trying to be used by python-novaclient. Previously, 'nova flavor-list' would have empty columns for fields other than 'disk' and 'ram'. Now all columns are filled in appropriately. --- nova/api/openstack/views/flavors.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nova/api/openstack/views/flavors.py b/nova/api/openstack/views/flavors.py index aea34b424..023acce0d 100644 --- a/nova/api/openstack/views/flavors.py +++ b/nova/api/openstack/views/flavors.py @@ -48,6 +48,10 @@ class ViewBuilder(object): detail = { "ram": flavor_obj["memory_mb"], "disk": flavor_obj["local_gb"], + "swap": flavor_obj["swap"], + "rxtx_quota": flavor_obj["rxtx_quota"], + "rxtx_cap": flavor_obj["rxtx_cap"], + "vcpus": flavor_obj["vcpus"], } detail.update(simple) -- cgit From 7d76c99526271077420e3f6a03e14bf1e037a3eb Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Tue, 13 Sep 2011 18:32:42 +0000 Subject: Test new content-types --- nova/tests/api/openstack/test_wsgi.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/nova/tests/api/openstack/test_wsgi.py b/nova/tests/api/openstack/test_wsgi.py index 6dea78d17..46e890cee 100644 --- a/nova/tests/api/openstack/test_wsgi.py +++ b/nova/tests/api/openstack/test_wsgi.py @@ -28,15 +28,14 @@ class RequestTest(test.TestCase): self.assertEqual(result, "application/json") def test_content_type_from_accept_xml(self): - request = wsgi.Request.blank('/tests/123') - request.headers["Accept"] = "application/xml" - result = request.best_match_content_type() - self.assertEqual(result, "application/xml") - - request = wsgi.Request.blank('/tests/123') - request.headers["Accept"] = "application/json" - result = request.best_match_content_type() - self.assertEqual(result, "application/json") + for content_type in ('application/xml', + 'application/vnd.openstack.compute+xml', + 'application/json', + 'application/vnd.openstack.compute+json'): + request = wsgi.Request.blank('/tests/123') + request.headers["Accept"] = content_type + result = request.best_match_content_type() + self.assertEqual(result, content_type) request = wsgi.Request.blank('/tests/123') request.headers["Accept"] = "application/xml, application/json" @@ -231,7 +230,9 @@ class ResponseSerializerTest(test.TestCase): self.body_serializers = { 'application/json': JSONSerializer(), - 'application/XML': XMLSerializer(), + 'application/vnd.openstack.compute+json': JSONSerializer(), + 'application/xml': XMLSerializer(), + 'application/vnd.openstack.compute+xml': XMLSerializer(), } self.serializer = wsgi.ResponseSerializer(self.body_serializers, @@ -281,7 +282,9 @@ class RequestDeserializerTest(test.TestCase): self.body_deserializers = { 'application/json': JSONDeserializer(), - 'application/XML': XMLDeserializer(), + 'application/vnd.openstack.compute+json': JSONDeserializer(), + 'application/xml': XMLDeserializer(), + 'application/vnd.openstack.compute+xml': XMLDeserializer(), } self.deserializer = wsgi.RequestDeserializer(self.body_deserializers) -- cgit From 2b8463b664d450af045c1bd01accce4126933755 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Tue, 13 Sep 2011 18:33:09 +0000 Subject: Actually test expected matches received --- nova/tests/api/openstack/test_versions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 1269f13c9..b3412f97c 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -534,6 +534,8 @@ class VersionsTest(test.TestCase): """.replace(" ", "").replace("\n", "") % (wsgi.XMLNS_V11, wsgi.XMLNS_ATOM) + self.assertEqual(expected, res.body) + def test_multi_choice_server_atom(self): """ Make sure multi choice responses do not have content-type -- cgit From b05121e1e1b2a8276d1dd21583c379dc4755b3dd Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 13 Sep 2011 15:48:10 -0400 Subject: Update test_libvirt so that flags and fakes are used instead of mocks for utils.import_class and utils.import_object. Fixes #lp849329. --- nova/tests/fake_network.py | 30 ++++++++++++++++ nova/tests/test_libvirt.py | 87 +++++++++++++++++----------------------------- 2 files changed, 62 insertions(+), 55 deletions(-) diff --git a/nova/tests/fake_network.py b/nova/tests/fake_network.py index 1ecb99b31..142206755 100644 --- a/nova/tests/fake_network.py +++ b/nova/tests/fake_network.py @@ -25,6 +25,36 @@ HOST = "testhost" FLAGS = flags.FLAGS +class FakeIptablesFirewallDriver(object): + def __init__(self, **kwargs): + pass + + def setattr(self, key, val): + self.__setattr__(key, val) + + def apply_instance_filter(self, instance, network_info): + pass + + +class FakeVIFDriver(object): + + def __init__(self, **kwargs): + pass + + def setattr(self, key, val): + self.__setattr__(key, val) + + def plug(self, instance, network, mapping): + return { + 'id': 'fake', + 'bridge_name': 'fake', + 'mac_address': 'fake', + 'ip_address': 'fake', + 'dhcp_server': 'fake', + 'extra_params': 'fake', + } + + class FakeModel(dict): """Represent a model from the db""" def __init__(self, *args, **kwargs): diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 233ee14de..8193d6ec2 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -51,6 +51,32 @@ def _concurrency(wait, done, target): done.send() +class FakeVirtDomain(object): + + def __init__(self, fake_xml=None): + if fake_xml: + self._fake_dom_xml = fake_xml + else: + self._fake_dom_xml=""" + + + + + + + + """ + + def snapshotCreateXML(self, *args): + return None + + def createWithFlags(self, launch_flags): + pass + + def XMLDesc(self, *args): + return self._fake_dom_xml + + class CacheConcurrencyTestCase(test.TestCase): def setUp(self): super(CacheConcurrencyTestCase, self).setUp() @@ -152,70 +178,23 @@ class LibvirtConnTestCase(test.TestCase): # A fake libvirt.virConnect class FakeLibvirtConnection(object): - pass - - # A fake connection.IptablesFirewallDriver - class FakeIptablesFirewallDriver(object): - - def __init__(self, **kwargs): - pass - - def setattr(self, key, val): - self.__setattr__(key, val) - - # A fake VIF driver - class FakeVIFDriver(object): - - def __init__(self, **kwargs): - pass - - def setattr(self, key, val): - self.__setattr__(key, val) - - def plug(self, instance, network, mapping): - return { - 'id': 'fake', - 'bridge_name': 'fake', - 'mac_address': 'fake', - 'ip_address': 'fake', - 'dhcp_server': 'fake', - 'extra_params': 'fake', - } + def defineXML(self, xml): + return FakeVirtDomain() # Creating mocks fake = FakeLibvirtConnection() - fakeip = FakeIptablesFirewallDriver - fakevif = FakeVIFDriver() # Customizing above fake if necessary for key, val in kwargs.items(): fake.__setattr__(key, val) - # Inevitable mocks for connection.LibvirtConnection - self.mox.StubOutWithMock(connection.utils, 'import_class') - connection.utils.import_class(mox.IgnoreArg()).AndReturn(fakeip) - self.mox.StubOutWithMock(connection.utils, 'import_object') - connection.utils.import_object(mox.IgnoreArg()).AndReturn(fakevif) + self.flags(image_service='nova.image.fake.FakeImageService') + self.flags(firewall_driver="nova.tests.fake_network.FakeIptablesFirewallDriver") + self.flags(libvirt_vif_driver="nova.tests.fake_network.FakeVIFDriver") + self.mox.StubOutWithMock(connection.LibvirtConnection, '_conn') connection.LibvirtConnection._conn = fake def fake_lookup(self, instance_name): - - class FakeVirtDomain(object): - - def snapshotCreateXML(self, *args): - return None - - def XMLDesc(self, *args): - return """ - - - - - - - - """ - return FakeVirtDomain() def fake_execute(self, *args): @@ -797,8 +776,6 @@ class LibvirtConnTestCase(test.TestCase): shutil.rmtree(os.path.join(FLAGS.instances_path, instance.name)) shutil.rmtree(os.path.join(FLAGS.instances_path, '_base')) - self.assertTrue(count) - def test_get_host_ip_addr(self): conn = connection.LibvirtConnection(False) ip = conn.get_host_ip_addr() -- cgit From b815c3c6cadd72c496e087080a4a6652e0a5be72 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Tue, 13 Sep 2011 20:06:18 +0000 Subject: Remove debugging print --- nova/tests/api/openstack/test_wsgi.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/tests/api/openstack/test_wsgi.py b/nova/tests/api/openstack/test_wsgi.py index 46e890cee..b06098da5 100644 --- a/nova/tests/api/openstack/test_wsgi.py +++ b/nova/tests/api/openstack/test_wsgi.py @@ -259,7 +259,6 @@ class ResponseSerializerTest(test.TestCase): def test_serialize_response_None(self): response = self.serializer.serialize(None, 'application/json') - print response self.assertEqual(response.headers['Content-Type'], 'application/json') self.assertEqual(response.body, '') self.assertEqual(response.status_int, 404) -- cgit From 9ced215b37dca65b3bf9cfe2d41518c2a563ff2d Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Tue, 13 Sep 2011 20:15:51 +0000 Subject: Split accept tests to better match the name of the test --- nova/tests/api/openstack/test_wsgi.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/nova/tests/api/openstack/test_wsgi.py b/nova/tests/api/openstack/test_wsgi.py index b06098da5..b73027643 100644 --- a/nova/tests/api/openstack/test_wsgi.py +++ b/nova/tests/api/openstack/test_wsgi.py @@ -27,7 +27,7 @@ class RequestTest(test.TestCase): result = request.get_content_type() self.assertEqual(result, "application/json") - def test_content_type_from_accept_xml(self): + def test_content_type_from_accept(self): for content_type in ('application/xml', 'application/vnd.openstack.compute+xml', 'application/json', @@ -37,6 +37,7 @@ class RequestTest(test.TestCase): result = request.best_match_content_type() self.assertEqual(result, content_type) + def test_content_type_from_accept_best(self): request = wsgi.Request.blank('/tests/123') request.headers["Accept"] = "application/xml, application/json" result = request.best_match_content_type() @@ -230,9 +231,7 @@ class ResponseSerializerTest(test.TestCase): self.body_serializers = { 'application/json': JSONSerializer(), - 'application/vnd.openstack.compute+json': JSONSerializer(), 'application/xml': XMLSerializer(), - 'application/vnd.openstack.compute+xml': XMLSerializer(), } self.serializer = wsgi.ResponseSerializer(self.body_serializers, @@ -281,9 +280,7 @@ class RequestDeserializerTest(test.TestCase): self.body_deserializers = { 'application/json': JSONDeserializer(), - 'application/vnd.openstack.compute+json': JSONDeserializer(), 'application/xml': XMLDeserializer(), - 'application/vnd.openstack.compute+xml': XMLDeserializer(), } self.deserializer = wsgi.RequestDeserializer(self.body_deserializers) @@ -292,8 +289,9 @@ class RequestDeserializerTest(test.TestCase): pass def test_get_deserializer(self): - expected = self.deserializer.get_body_deserializer('application/json') - self.assertEqual(expected, self.body_deserializers['application/json']) + ctype = 'application/json' + expected = self.deserializer.get_body_deserializer(ctype) + self.assertEqual(expected, self.body_deserializers[ctype]) def test_get_deserializer_unknown_content_type(self): self.assertRaises(exception.InvalidContentType, @@ -301,10 +299,11 @@ class RequestDeserializerTest(test.TestCase): 'application/unknown') def test_get_expected_content_type(self): + ctype = 'application/json' request = wsgi.Request.blank('/') - request.headers['Accept'] = 'application/json' + request.headers['Accept'] = ctype self.assertEqual(self.deserializer.get_expected_content_type(request), - 'application/json') + ctype) def test_get_action_args(self): env = { -- cgit From 660706ec03a55e5875b1af875f1a5f157d4a04f1 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Tue, 13 Sep 2011 20:36:54 +0000 Subject: Test new vendor content types as well --- nova/tests/api/openstack/test_api.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/nova/tests/api/openstack/test_api.py b/nova/tests/api/openstack/test_api.py index 7321c329f..b7a0b01ef 100644 --- a/nova/tests/api/openstack/test_api.py +++ b/nova/tests/api/openstack/test_api.py @@ -20,6 +20,7 @@ import json import webob.exc import webob.dec +from lxml import etree from webob import Request from nova import test @@ -52,6 +53,30 @@ class APITest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) + def test_vendor_content_type_json(self): + ctype = 'application/vnd.openstack.compute+json' + + req = webob.Request.blank('/') + req.headers['Accept'] = ctype + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + self.assertEqual(res.content_type, ctype) + + body = json.loads(res.body) + + def test_vendor_content_type_xml(self): + ctype = 'application/vnd.openstack.compute+xml' + + req = webob.Request.blank('/') + req.headers['Accept'] = ctype + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + self.assertEqual(res.content_type, ctype) + + body = etree.XML(res.body) + def test_exceptions_are_converted_to_faults(self): @webob.dec.wsgify -- cgit From 877d92845a5d2002c4adc0c8398469e66fd0907e Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Tue, 13 Sep 2011 17:06:15 -0400 Subject: make tests pass --- nova/api/openstack/views/flavors.py | 7 +++---- nova/tests/api/openstack/test_flavors.py | 24 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/nova/api/openstack/views/flavors.py b/nova/api/openstack/views/flavors.py index 023acce0d..4c5e2c1e6 100644 --- a/nova/api/openstack/views/flavors.py +++ b/nova/api/openstack/views/flavors.py @@ -48,12 +48,11 @@ class ViewBuilder(object): detail = { "ram": flavor_obj["memory_mb"], "disk": flavor_obj["local_gb"], - "swap": flavor_obj["swap"], - "rxtx_quota": flavor_obj["rxtx_quota"], - "rxtx_cap": flavor_obj["rxtx_cap"], - "vcpus": flavor_obj["vcpus"], } + for key in ( "vcpus", "swap", "rxtx_quota", "rxtx_cap"): + detail[key] = flavor_obj.get(key,"") + detail.update(simple) return detail diff --git a/nova/tests/api/openstack/test_flavors.py b/nova/tests/api/openstack/test_flavors.py index 812bece42..b7a5d0e09 100644 --- a/nova/tests/api/openstack/test_flavors.py +++ b/nova/tests/api/openstack/test_flavors.py @@ -107,12 +107,20 @@ class FlavorsTest(test.TestCase): "name": "flavor 1", "ram": "256", "disk": "10", + "rxtx_cap": "", + "rxtx_quota": "", + "swap": "", + "vcpus": "", }, { "id": "2", "name": "flavor 2", "ram": "256", "disk": "10", + "rxtx_cap": "", + "rxtx_quota": "", + "swap": "", + "vcpus": "", }, ] self.assertEqual(flavors, expected) @@ -127,6 +135,10 @@ class FlavorsTest(test.TestCase): "name": "flavor 12", "ram": "256", "disk": "10", + "rxtx_cap": "", + "rxtx_quota": "", + "swap": "", + "vcpus": "", } self.assertEqual(flavor, expected) @@ -149,6 +161,10 @@ class FlavorsTest(test.TestCase): "name": "flavor 12", "ram": "256", "disk": "10", + "rxtx_cap": "", + "rxtx_quota": "", + "swap": "", + "vcpus": "", "links": [ { "rel": "self", @@ -216,6 +232,10 @@ class FlavorsTest(test.TestCase): "name": "flavor 1", "ram": "256", "disk": "10", + "rxtx_cap": "", + "rxtx_quota": "", + "swap": "", + "vcpus": "", "links": [ { "rel": "self", @@ -232,6 +252,10 @@ class FlavorsTest(test.TestCase): "name": "flavor 2", "ram": "256", "disk": "10", + "rxtx_cap": "", + "rxtx_quota": "", + "swap": "", + "vcpus": "", "links": [ { "rel": "self", -- cgit From 147290d01389d72d3754bbaa088660f38a6871d8 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Tue, 13 Sep 2011 21:09:25 +0000 Subject: Fix mismerge --- nova/tests/api/openstack/test_versions.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 4f5024c87..f69dbd316 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -499,8 +499,6 @@ class VersionsTest(test.TestCase): self.assertTrue(common.compare_links(links, [{'rel': 'self', 'href': 'http://localhost/v1.0/images/1'}])) - self.assertEqual(expected, res.body) - def test_multi_choice_server_atom(self): """ Make sure multi choice responses do not have content-type -- cgit From de3d94726c980f40181693256c0f650d492451ef Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 13 Sep 2011 16:38:46 -0700 Subject: makes sure floating addresses are associated with host on associate so they come back --- nova/db/api.py | 8 +++++--- nova/db/sqlalchemy/api.py | 5 ++++- nova/network/manager.py | 3 ++- nova/tests/db/fakes.py | 4 +++- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/nova/db/api.py b/nova/db/api.py index c03a86671..5b57b178e 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -261,11 +261,13 @@ def floating_ip_disassociate(context, address): return IMPL.floating_ip_disassociate(context, address) -def floating_ip_fixed_ip_associate(context, floating_address, fixed_address): +def floating_ip_fixed_ip_associate(context, floating_address, + fixed_address, host): """Associate an floating ip to a fixed_ip by address.""" return IMPL.floating_ip_fixed_ip_associate(context, floating_address, - fixed_address) + fixed_address, + host) def floating_ip_get_all(context): @@ -365,7 +367,7 @@ def fixed_ip_get_all(context): def fixed_ip_get_all_by_instance_host(context, host): """Get all allocated fixed ips filtered by instance host.""" - return IMPL.fixed_ip_get_all_instance_by_host(context, host) + return IMPL.fixed_ip_get_all_by_instance_host(context, host) def fixed_ip_get_by_address(context, address): diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 523258841..9c3d8413e 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -529,7 +529,8 @@ def floating_ip_count_by_project(context, project_id): @require_context -def floating_ip_fixed_ip_associate(context, floating_address, fixed_address): +def floating_ip_fixed_ip_associate(context, floating_address, + fixed_address, host): session = get_session() with session.begin(): # TODO(devcamcar): How to ensure floating_id belongs to user? @@ -540,6 +541,7 @@ def floating_ip_fixed_ip_associate(context, floating_address, fixed_address): fixed_address, session=session) floating_ip_ref.fixed_ip = fixed_ip_ref + floating_ip_ref.host = host floating_ip_ref.save(session=session) @@ -583,6 +585,7 @@ def floating_ip_disassociate(context, address): else: fixed_ip_address = None floating_ip_ref.fixed_ip = None + floating_ip_ref.host = None floating_ip_ref.save(session=session) return fixed_ip_address diff --git a/nova/network/manager.py b/nova/network/manager.py index 05d928fab..aa6711b48 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -289,7 +289,8 @@ class FloatingIP(object): self.db.floating_ip_fixed_ip_associate(context, floating_address, - fixed_address) + fixed_address, + self.host) self.driver.bind_floating_ip(floating_address) self.driver.ensure_floating_forward(floating_address, fixed_address) diff --git a/nova/tests/db/fakes.py b/nova/tests/db/fakes.py index 19028a451..cdbfba63a 100644 --- a/nova/tests/db/fakes.py +++ b/nova/tests/db/fakes.py @@ -125,10 +125,11 @@ def stub_out_db_network_api(stubs): if ips[0]['fixed_ip']: fixed_ip_address = ips[0]['fixed_ip']['address'] ips[0]['fixed_ip'] = None + ips[0]['host'] = None return fixed_ip_address def fake_floating_ip_fixed_ip_associate(context, floating_address, - fixed_address): + fixed_address, host): float = filter(lambda i: i['address'] == floating_address, floating_ips) fixed = filter(lambda i: i['address'] == fixed_address, @@ -136,6 +137,7 @@ def stub_out_db_network_api(stubs): if float and fixed: float[0]['fixed_ip'] = fixed[0] float[0]['fixed_ip_id'] = fixed[0]['id'] + float[0]['host'] = host def fake_floating_ip_get_all_by_host(context, host): # TODO(jkoelker): Once we get the patches that remove host from -- cgit From a85a2c2e82fa8820b04f669c92a3500c7c6cebe2 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 13 Sep 2011 20:38:26 -0400 Subject: pep8 fixes. --- nova/tests/test_libvirt.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py index 8193d6ec2..a30b00dbe 100644 --- a/nova/tests/test_libvirt.py +++ b/nova/tests/test_libvirt.py @@ -57,7 +57,7 @@ class FakeVirtDomain(object): if fake_xml: self._fake_dom_xml = fake_xml else: - self._fake_dom_xml=""" + self._fake_dom_xml = """ @@ -188,7 +188,8 @@ class LibvirtConnTestCase(test.TestCase): fake.__setattr__(key, val) self.flags(image_service='nova.image.fake.FakeImageService') - self.flags(firewall_driver="nova.tests.fake_network.FakeIptablesFirewallDriver") + fw_driver = "nova.tests.fake_network.FakeIptablesFirewallDriver" + self.flags(firewall_driver=fw_driver) self.flags(libvirt_vif_driver="nova.tests.fake_network.FakeVIFDriver") self.mox.StubOutWithMock(connection.LibvirtConnection, '_conn') -- cgit From 4eb704e9024111c80f6f7c83810a08d7eec5c4af Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 13 Sep 2011 21:13:58 -0400 Subject: last of the api.openstack.test_images merge fixes. --- nova/tests/api/openstack/test_images.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 6890e0e9e..e054ffd03 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -821,7 +821,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): }], }, { - 'id': 128, + 'id': '128', 'name': 'deleted snapshot', 'metadata': { u'instance_ref': u'http://localhost/v1.1/servers/42', @@ -832,7 +832,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'status': 'DELETED', 'progress': 0, 'server': { - 'id': 42, + 'id': '42', "links": [{ "rel": "self", "href": server_href, @@ -852,7 +852,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): }], }, { - 'id': 129, + 'id': '129', 'name': 'pending_delete snapshot', 'metadata': { u'instance_ref': u'http://localhost/v1.1/servers/42', @@ -863,7 +863,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'status': 'DELETED', 'progress': 0, 'server': { - 'id': 42, + 'id': '42', "links": [{ "rel": "self", "href": server_href, @@ -914,7 +914,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): }], }, { - 'id': 132, + 'id': '132', 'name': None, 'metadata': {}, 'updated': self.NOW_API_FORMAT, @@ -1224,7 +1224,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): # Snapshots for User 1 server_ref = 'http://localhost/v1.1/servers/42' snapshot_properties = {'instance_ref': server_ref, 'user_id': 'fake'} - statuses = ('queued', 'saving', 'active','killed', + statuses = ('queued', 'saving', 'active', 'killed', 'deleted', 'pending_delete') for status in statuses: add_fixture(id=image_id, name='%s snapshot' % status, -- cgit From 7a02394009aae85f722430682f8360371121504b Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Wed, 14 Sep 2011 11:33:36 -0400 Subject: Make tests pass. --- nova/tests/api/openstack/test_images.py | 38 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 45ca73073..27ae8f2bd 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -77,14 +77,14 @@ class ImagesTest(test.TestCase): response_dict = json.loads(response.body) response_list = response_dict["images"] - expected = [{'id': '123', 'name': 'public image'}, - {'id': '124', 'name': 'queued snapshot'}, - {'id': '125', 'name': 'saving snapshot'}, - {'id': '126', 'name': 'active snapshot'}, - {'id': '127', 'name': 'killed snapshot'}, - {'id': '128', 'name': 'deleted snapshot'}, - {'id': '129', 'name': 'pending_delete snapshot'}, - {'id': '130', 'name': None}] + expected = [{'id': 123, 'name': 'public image'}, + {'id': 124, 'name': 'queued snapshot'}, + {'id': 125, 'name': 'saving snapshot'}, + {'id': 126, 'name': 'active snapshot'}, + {'id': 127, 'name': 'killed snapshot'}, + {'id': 128, 'name': 'deleted snapshot'}, + {'id': 129, 'name': 'pending_delete snapshot'}, + {'id': 130, 'name': None}] self.assertDictListMatch(response_list, expected) @@ -99,7 +99,7 @@ class ImagesTest(test.TestCase): expected_image = { "image": { - "id": "123", + "id": 123, "name": "public image", "updated": NOW_API_FORMAT, "created": NOW_API_FORMAT, @@ -406,7 +406,7 @@ class ImagesTest(test.TestCase): response_list = response_dict["images"] expected = [{ - 'id': '123', + 'id': 123, 'name': 'public image', 'updated': NOW_API_FORMAT, 'created': NOW_API_FORMAT, @@ -414,7 +414,7 @@ class ImagesTest(test.TestCase): 'progress': 100, }, { - 'id': '124', + 'id': 124, 'name': 'queued snapshot', 'updated': NOW_API_FORMAT, 'created': NOW_API_FORMAT, @@ -422,7 +422,7 @@ class ImagesTest(test.TestCase): 'progress': 0, }, { - 'id': '125', + 'id': 125, 'name': 'saving snapshot', 'updated': NOW_API_FORMAT, 'created': NOW_API_FORMAT, @@ -430,7 +430,7 @@ class ImagesTest(test.TestCase): 'progress': 0, }, { - 'id': '126', + 'id': 126, 'name': 'active snapshot', 'updated': NOW_API_FORMAT, 'created': NOW_API_FORMAT, @@ -438,7 +438,7 @@ class ImagesTest(test.TestCase): 'progress': 100, }, { - 'id': '127', + 'id': 127, 'name': 'killed snapshot', 'updated': NOW_API_FORMAT, 'created': NOW_API_FORMAT, @@ -446,7 +446,7 @@ class ImagesTest(test.TestCase): 'progress': 0, }, { - 'id': '128', + 'id': 128, 'name': 'deleted snapshot', 'updated': NOW_API_FORMAT, 'created': NOW_API_FORMAT, @@ -454,7 +454,7 @@ class ImagesTest(test.TestCase): 'progress': 0, }, { - 'id': '129', + 'id': 129, 'name': 'pending_delete snapshot', 'updated': NOW_API_FORMAT, 'created': NOW_API_FORMAT, @@ -462,7 +462,7 @@ class ImagesTest(test.TestCase): 'progress': 0, }, { - 'id': '130', + 'id': 130, 'name': None, 'updated': NOW_API_FORMAT, 'created': NOW_API_FORMAT, @@ -914,7 +914,7 @@ class ImagesTest(test.TestCase): app = fakes.wsgi_app(fake_auth_context=self._get_fake_context()) res = req.get_response(app) image_meta = json.loads(res.body)['image'] - expected = {'id': '123', 'name': 'public image', + expected = {'id': 123, 'name': 'public image', 'updated': NOW_API_FORMAT, 'created': NOW_API_FORMAT, 'status': 'ACTIVE', 'progress': 100} @@ -934,7 +934,7 @@ class ImagesTest(test.TestCase): response = req.get_response(fakes.wsgi_app()) self.assertEqual(200, response.status_int) image_meta = json.loads(response.body)['image'] - self.assertEqual('123', image_meta['serverId']) + self.assertEqual(123, image_meta['serverId']) self.assertEqual('Snapshot 1', image_meta['name']) def test_create_snapshot_no_name(self): -- cgit From d36e59f4480265741018a1fd5160f5262b7e9331 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Wed, 14 Sep 2011 11:54:13 -0400 Subject: add extension description for FlavorExtraData --- nova/api/openstack/contrib/flavorextradata.py | 46 +++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 nova/api/openstack/contrib/flavorextradata.py diff --git a/nova/api/openstack/contrib/flavorextradata.py b/nova/api/openstack/contrib/flavorextradata.py new file mode 100644 index 000000000..d0554c7b4 --- /dev/null +++ b/nova/api/openstack/contrib/flavorextradata.py @@ -0,0 +1,46 @@ +# Copyright 2011 Canonical Ltd. +# 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 Flavor extra data extension +Openstack API version 1.1 lists "name", "ram", "disk", "vcpus" as flavor +attributes. This extension adds to that list: + rxtx_cap + rxtx_quota + swap +""" + +from nova.api.openstack import extensions + + +class Flavorextradata(extensions.ExtensionDescriptor): + """The Flavor extra data extension for the OpenStack API.""" + + def get_name(self): + return "FlavorExtraData" + + def get_alias(self): + return "os-flavor-extra-data" + + def get_description(self): + return "Provide additional data for flavors" + + def get_namespace(self): + return "http://docs.openstack.org/ext/flavor_extra_data/api/v1.1" + + def get_updated(self): + return "2011-09-14T00:00:00+00:00" + +# vim: tabstop=4 shiftwidth=4 softtabstop=4 -- cgit From efaa0e6590985e3d4bdeeb18e1d41a037856ea89 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Wed, 14 Sep 2011 11:54:56 -0400 Subject: fix test_extensions test to know of new extension FlavorExtraData --- nova/tests/api/openstack/test_extensions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 31443242b..44f4eb055 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -87,6 +87,7 @@ class ExtensionControllerTest(test.TestCase): self.ext_list = [ "Createserverext", "FlavorExtraSpecs", + "FlavorExtraData", "Floating_ips", "Fox In Socks", "Hosts", -- cgit From c20228123c1774a1e2aa1b4ee7155a62336f5934 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Wed, 14 Sep 2011 11:55:18 -0400 Subject: fix white space for pep8 --- nova/api/openstack/views/flavors.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/views/flavors.py b/nova/api/openstack/views/flavors.py index 4c5e2c1e6..def969a6c 100644 --- a/nova/api/openstack/views/flavors.py +++ b/nova/api/openstack/views/flavors.py @@ -50,8 +50,8 @@ class ViewBuilder(object): "disk": flavor_obj["local_gb"], } - for key in ( "vcpus", "swap", "rxtx_quota", "rxtx_cap"): - detail[key] = flavor_obj.get(key,"") + for key in ("vcpus", "swap", "rxtx_quota", "rxtx_cap"): + detail[key] = flavor_obj.get(key, "") detail.update(simple) -- cgit From 9f83b51ae2afeb45ed9bdcb8c3b63ced78f8050e Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 14 Sep 2011 16:19:18 +0000 Subject: Remove debugging --- nova/api/openstack/wsgi.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 6eb1953b4..c342f6267 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -29,7 +29,6 @@ class Request(webob.Request): Based on the query extension then the Accept header. """ - LOG.info('supported = %s' % repr(supported_content_types)) supported_content_types = supported_content_types or \ ('application/json', 'application/vnd.openstack.compute+json', -- cgit From 092ff28b9f141368aed0d719140212e5fc8652f8 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Wed, 14 Sep 2011 12:25:47 -0400 Subject: add attributes to xml api --- nova/api/openstack/flavors.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index fd36060da..c6c9f096f 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -90,6 +90,9 @@ class FlavorXMLSerializer(wsgi.XMLDictSerializer): flavor_node.setAttribute('ram', str(flavor['ram'])) flavor_node.setAttribute('disk', str(flavor['disk'])) + for attr in ("vcpus", "swap", "rxtx_quota", "rxtx_cap"): + flavor_node.setAttribute(attr, str(flavor.get(attr,""))) + link_nodes = self._create_link_nodes(xml_doc, flavor['links']) for link_node in link_nodes: flavor_node.appendChild(link_node) -- cgit From 34a08d831418b934f4cceaae69dbf17d90ecd5e0 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 14 Sep 2011 12:32:00 -0400 Subject: removing toprettyxml --- nova/api/openstack/wsgi.py | 2 +- nova/tests/api/openstack/test_images.py | 16 ++++++---------- nova/tests/api/openstack/test_limits.py | 14 ++++++-------- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index bdcadcb99..588fc030b 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -316,7 +316,7 @@ class XMLDictSerializer(DictSerializer): def to_xml_string(self, node, has_atom=False): self._add_xmlns(node, has_atom) - return node.toprettyxml(indent=' ', encoding='UTF-8') + return node.toxml('UTF-8') #NOTE (ameade): the has_atom should be removed after all of the # xml serializers and view builders have been updated to the current diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 2aee1bc14..32ae7eeec 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -224,12 +224,10 @@ class ImagesTest(test.TestCase): expected = minidom.parseString(""" - - Image not found. - + xmlns="http://docs.rackspacecloud.com/servers/api/v1.0"> + Image not found. - """.replace(" ", "")) + """.replace(" ", "").replace("\n", "")) actual = minidom.parseString(response.body.replace(" ", "")) @@ -261,12 +259,10 @@ class ImagesTest(test.TestCase): # because the element hasn't changed definition expected = minidom.parseString(""" - - Image not found. - + xmlns="http://docs.openstack.org/compute/api/v1.1"> + Image not found. - """.replace(" ", "")) + """.replace(" ", "").replace("\n", "")) actual = minidom.parseString(response.body.replace(" ", "")) diff --git a/nova/tests/api/openstack/test_limits.py b/nova/tests/api/openstack/test_limits.py index 3db57ee86..6f0210c27 100644 --- a/nova/tests/api/openstack/test_limits.py +++ b/nova/tests/api/openstack/test_limits.py @@ -174,12 +174,11 @@ class LimitsControllerV10Test(BaseLimitTestSuite): response = request.get_response(self.controller) expected = minidom.parseString(""" - + - """.replace(" ", "")) + """.replace(" ", "").replace("\n", "")) body = minidom.parseString(response.body.replace(" ", "")) @@ -192,17 +191,16 @@ class LimitsControllerV10Test(BaseLimitTestSuite): response = request.get_response(self.controller) expected = minidom.parseString(""" - + + unit="MINUTE" value="10" verb="GET"/> + unit="HOUR" value="5" verb="POST"/> - """.replace(" ", "")) + """.replace(" ", "").replace("\n", "")) body = minidom.parseString(response.body.replace(" ", "")) self.assertEqual(expected.toxml(), body.toxml()) -- cgit From 95e06c5d88f5500805fb8d9505a4db61560bf8e1 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Wed, 14 Sep 2011 12:59:10 -0400 Subject: update variable name after merge: flavor_node -> flavor_elem --- nova/api/openstack/flavors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index dd77f1a05..a419b7ec2 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -94,7 +94,7 @@ class FlavorXMLSerializer(wsgi.XMLDictSerializer): flavor_elem.set('disk', str(flavor_dict['disk'])) for attr in ("vcpus", "swap", "rxtx_quota", "rxtx_cap"): - flavor_node.setAttribute(attr, str(flavor.get(attr,""))) + flavor_elem.setAttribute(attr, str(flavor.get(attr,""))) for link in flavor_dict.get('links', []): elem = etree.SubElement(flavor_elem, -- cgit From f9cac7659a761fa32fac0153f8ecb0334149a486 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Wed, 14 Sep 2011 13:10:23 -0400 Subject: make xml-api tests pass --- nova/tests/api/openstack/test_flavors.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/nova/tests/api/openstack/test_flavors.py b/nova/tests/api/openstack/test_flavors.py index fa4e548ac..348042bfe 100644 --- a/nova/tests/api/openstack/test_flavors.py +++ b/nova/tests/api/openstack/test_flavors.py @@ -300,6 +300,10 @@ class FlavorsXMLSerializationTest(test.TestCase): "name": "asdf", "ram": "256", "disk": "10", + "rxtx_cap": "", + "rxtx_quota": "", + "swap": "", + "vcpus": "", "links": [ { "rel": "self", @@ -327,6 +331,10 @@ class FlavorsXMLSerializationTest(test.TestCase): "name": "asdf", "ram": "256", "disk": "10", + "rxtx_cap": "", + "rxtx_quota": "", + "swap": "", + "vcpus": "", "links": [ { "rel": "self", @@ -364,6 +372,10 @@ class FlavorsXMLSerializationTest(test.TestCase): "name": "asdf", "ram": 256, "disk": 10, + "rxtx_cap": "", + "rxtx_quota": "", + "swap": "", + "vcpus": "", "links": [ { "rel": "self", @@ -402,6 +414,10 @@ class FlavorsXMLSerializationTest(test.TestCase): "name": "flavor 23", "ram": "512", "disk": "20", + "rxtx_cap": "", + "rxtx_quota": "", + "swap": "", + "vcpus": "", "links": [ { "rel": "self", @@ -417,6 +433,10 @@ class FlavorsXMLSerializationTest(test.TestCase): "name": "flavor 13", "ram": "256", "disk": "10", + "rxtx_cap": "", + "rxtx_quota": "", + "swap": "", + "vcpus": "", "links": [ { "rel": "self", @@ -459,6 +479,10 @@ class FlavorsXMLSerializationTest(test.TestCase): "name": "flavor 23", "ram": "512", "disk": "20", + "rxtx_cap": "", + "rxtx_quota": "", + "swap": "", + "vcpus": "", "links": [ { "rel": "self", @@ -474,6 +498,10 @@ class FlavorsXMLSerializationTest(test.TestCase): "name": "flavor 13", "ram": "256", "disk": "10", + "rxtx_cap": "", + "rxtx_quota": "", + "swap": "", + "vcpus": "", "links": [ { "rel": "self", -- cgit From 2b41dd235b50e3003a42e0b860c5915d06d86071 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 14 Sep 2011 17:23:40 +0000 Subject: Map vendor content types to their standard content type before serializing or deserializing. This is so we don't have to litter the code with both types when they are treated identically --- nova/api/openstack/wsgi.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index c342f6267..1c4724c8f 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -19,6 +19,21 @@ XMLNS_ATOM = 'http://www.w3.org/2005/Atom' LOG = logging.getLogger('nova.api.openstack.wsgi') +# The vendor content types should serialize identically to the non-vendor +# content types. So to avoid littering the code with both options, we +# map the vendor to the other when looking up the type +_CONTENT_TYPE_MAP = { + 'application/vnd.openstack.compute+json': 'application/json', + 'application/vnd.openstack.compute+xml': 'application/xml', +} + +_SUPPORTED_CONTENT_TYPES = ( + 'application/json', + 'application/vnd.openstack.compute+json', + 'application/xml', + 'application/vnd.openstack.compute+xml', +) + class Request(webob.Request): """Add some Openstack API-specific logic to the base webob.Request.""" @@ -30,10 +45,7 @@ class Request(webob.Request): """ supported_content_types = supported_content_types or \ - ('application/json', - 'application/vnd.openstack.compute+json', - 'application/xml', - 'application/vnd.openstack.compute+xml') + _SUPPORTED_CONTENT_TYPES parts = self.path.rsplit('.', 1) if len(parts) > 1: @@ -55,10 +67,7 @@ class Request(webob.Request): if not "Content-Type" in self.headers: return None - allowed_types = ('application/json', - 'application/vnd.openstack.compute+json', - 'application/xml', - 'application/vnd.openstack.compute+xml') + allowed_types = _SUPPORTED_CONTENT_TYPES content_type = self.content_type if content_type not in allowed_types: @@ -198,16 +207,11 @@ class RequestDeserializer(object): supported_content_types=None): self.supported_content_types = supported_content_types or \ - ('application/json', - 'application/vnd.openstack.compute+json', - 'application/xml', - 'application/vnd.openstack.compute+xml') + _SUPPORTED_CONTENT_TYPES self.body_deserializers = { 'application/xml': XMLDeserializer(), - 'application/vnd.openstack.compute+xml': XMLDeserializer(), 'application/json': JSONDeserializer(), - 'application/vnd.openstack.compute+json': JSONDeserializer(), } self.body_deserializers.update(body_deserializers or {}) @@ -261,6 +265,7 @@ class RequestDeserializer(object): def get_body_deserializer(self, content_type): try: + content_type = _CONTENT_TYPE_MAP.get(content_type, content_type) return self.body_deserializers[content_type] except (KeyError, TypeError): raise exception.InvalidContentType(content_type=content_type) @@ -425,9 +430,7 @@ class ResponseSerializer(object): def __init__(self, body_serializers=None, headers_serializer=None): self.body_serializers = { 'application/xml': XMLDictSerializer(), - 'application/vnd.openstack.compute+xml': XMLDictSerializer(), 'application/json': JSONDictSerializer(), - 'application/vnd.openstack.compute+json': JSONDictSerializer(), } self.body_serializers.update(body_serializers or {}) @@ -452,6 +455,7 @@ class ResponseSerializer(object): def serialize_body(self, response, data, content_type, action): response.headers['Content-Type'] = content_type if data is not None: + content_type = _CONTENT_TYPE_MAP.get(content_type, content_type) serializer = self.get_body_serializer(content_type) response.body = serializer.serialize(data, action) -- cgit From 1b836a4159bd324572f71dab4abcef5a8d3292e5 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 14 Sep 2011 17:29:28 +0000 Subject: Add copyright --- nova/api/openstack/wsgi.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 1c4724c8f..c68a57cb5 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -1,3 +1,19 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. import json from lxml import etree -- cgit From d678d8c4d024a4154ecd2ea77a42063ad1253364 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 14 Sep 2011 17:32:33 +0000 Subject: Remove unnecessary vendor content types now that they are mapped to standard content types automatically --- nova/api/openstack/versions.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index f4ae498b4..75a1d0ba4 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -101,8 +101,6 @@ class Versions(wsgi.Resource): body_serializers = { 'application/atom+xml': VersionsAtomSerializer(metadata=metadata), 'application/xml': VersionsXMLSerializer(metadata=metadata), - 'application/vnd.openstack.compute+xml': - VersionsXMLSerializer(metadata=metadata), } serializer = wsgi.ResponseSerializer( body_serializers=body_serializers, @@ -307,7 +305,6 @@ def create_resource(version='1.0'): body_serializers = { 'application/xml': VersionsXMLSerializer(), - 'application/vnd.openstack.compute+xml': VersionsXMLSerializer(), 'application/atom+xml': VersionsAtomSerializer(), } serializer = wsgi.ResponseSerializer(body_serializers) -- cgit From 4fa96895f1c32e09db31532886d67a675fe66208 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 14 Sep 2011 17:44:22 +0000 Subject: Test both content types for JSON and XML --- nova/tests/api/openstack/test_wsgi.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/nova/tests/api/openstack/test_wsgi.py b/nova/tests/api/openstack/test_wsgi.py index b73027643..74b9ce853 100644 --- a/nova/tests/api/openstack/test_wsgi.py +++ b/nova/tests/api/openstack/test_wsgi.py @@ -250,11 +250,21 @@ class ResponseSerializerTest(test.TestCase): self.serializer.get_body_serializer, 'application/unknown') - def test_serialize_response(self): - response = self.serializer.serialize({}, 'application/json') - self.assertEqual(response.headers['Content-Type'], 'application/json') - self.assertEqual(response.body, 'pew_json') - self.assertEqual(response.status_int, 404) + def test_serialize_response_json(self): + for content_type in ('application/json', + 'application/vnd.openstack.compute+json'): + response = self.serializer.serialize({}, content_type) + self.assertEqual(response.headers['Content-Type'], content_type) + self.assertEqual(response.body, 'pew_json') + self.assertEqual(response.status_int, 404) + + def test_serialize_response_xml(self): + for content_type in ('application/xml', + 'application/vnd.openstack.compute+xml'): + response = self.serializer.serialize({}, content_type) + self.assertEqual(response.headers['Content-Type'], content_type) + self.assertEqual(response.body, 'pew_xml') + self.assertEqual(response.status_int, 404) def test_serialize_response_None(self): response = self.serializer.serialize(None, 'application/json') -- cgit From 8638db07c4ad2177249a70708969c7a1cba09037 Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 14 Sep 2011 17:55:15 +0000 Subject: Don't report the wrong content type if a mapped type doesn't exist --- nova/api/openstack/wsgi.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index c68a57cb5..fad516d4d 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -281,8 +281,8 @@ class RequestDeserializer(object): def get_body_deserializer(self, content_type): try: - content_type = _CONTENT_TYPE_MAP.get(content_type, content_type) - return self.body_deserializers[content_type] + ctype = _CONTENT_TYPE_MAP.get(content_type, content_type) + return self.body_deserializers[ctype] except (KeyError, TypeError): raise exception.InvalidContentType(content_type=content_type) @@ -471,13 +471,13 @@ class ResponseSerializer(object): def serialize_body(self, response, data, content_type, action): response.headers['Content-Type'] = content_type if data is not None: - content_type = _CONTENT_TYPE_MAP.get(content_type, content_type) serializer = self.get_body_serializer(content_type) response.body = serializer.serialize(data, action) def get_body_serializer(self, content_type): try: - return self.body_serializers[content_type] + ctype = _CONTENT_TYPE_MAP.get(content_type, content_type) + return self.body_serializers[ctype] except (KeyError, TypeError): raise exception.InvalidContentType(content_type=content_type) -- cgit From 9ce2e4f80f249d58622b7235aec55e823f9cd6c8 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Wed, 14 Sep 2011 14:37:32 -0400 Subject: flavor_elem.setAttribute -> flavor_elem.set, flavor -> flavor_dict --- nova/api/openstack/flavors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index a419b7ec2..0c435ebeb 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -94,7 +94,7 @@ class FlavorXMLSerializer(wsgi.XMLDictSerializer): flavor_elem.set('disk', str(flavor_dict['disk'])) for attr in ("vcpus", "swap", "rxtx_quota", "rxtx_cap"): - flavor_elem.setAttribute(attr, str(flavor.get(attr,""))) + flavor_elem.set(attr, str(flavor_dict.get(attr,""))) for link in flavor_dict.get('links', []): elem = etree.SubElement(flavor_elem, -- cgit From 74a1045dd133fe708cb0f42bd4fae4198ee337ff Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Wed, 14 Sep 2011 14:51:55 -0400 Subject: add necessary fields to flavor.rng schema --- nova/api/openstack/schemas/v1.1/flavor.rng | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nova/api/openstack/schemas/v1.1/flavor.rng b/nova/api/openstack/schemas/v1.1/flavor.rng index a00e4e9ee..6d3adc8dc 100644 --- a/nova/api/openstack/schemas/v1.1/flavor.rng +++ b/nova/api/openstack/schemas/v1.1/flavor.rng @@ -4,6 +4,10 @@ + + + + -- cgit From aa0361c41d2e2feff18915ac93107727f52b15ca Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Wed, 14 Sep 2011 14:58:47 -0400 Subject: fix pep8 whitespace error --- nova/api/openstack/flavors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index 0c435ebeb..8a310c900 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -94,7 +94,7 @@ class FlavorXMLSerializer(wsgi.XMLDictSerializer): flavor_elem.set('disk', str(flavor_dict['disk'])) for attr in ("vcpus", "swap", "rxtx_quota", "rxtx_cap"): - flavor_elem.set(attr, str(flavor_dict.get(attr,""))) + flavor_elem.set(attr, str(flavor_dict.get(attr, ""))) for link in flavor_dict.get('links', []): elem = etree.SubElement(flavor_elem, -- cgit From cecc822c0bc10c8d5cc5168329ae04172c6e609e Mon Sep 17 00:00:00 2001 From: Josh Durgin Date: Wed, 14 Sep 2011 12:33:51 -0700 Subject: Use the correct method to get a builder. --- nova/api/openstack/contrib/volumes.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nova/api/openstack/contrib/volumes.py b/nova/api/openstack/contrib/volumes.py index d62225e58..9d4254f1f 100644 --- a/nova/api/openstack/contrib/volumes.py +++ b/nova/api/openstack/contrib/volumes.py @@ -372,8 +372,7 @@ class BootFromVolumeController(servers.ControllerV11): for key in ['instance_type', 'image_ref']: inst[key] = extra_values[key] - builder = self._get_view_builder(req) - server = builder.build(inst, is_detail=True) + server = self._build_view(req, inst, is_detail=True) server['server']['adminPass'] = extra_values['password'] return server -- cgit From 6cd4e1dadda93d2e8fa4ed26f3e8d83ea22292d3 Mon Sep 17 00:00:00 2001 From: Josh Durgin Date: Thu, 15 Sep 2011 11:05:57 -0700 Subject: Add a simple test for the OS boot from volume api. This would have detected lp:850305. --- nova/tests/api/openstack/contrib/test_volumes.py | 73 ++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 nova/tests/api/openstack/contrib/test_volumes.py diff --git a/nova/tests/api/openstack/contrib/test_volumes.py b/nova/tests/api/openstack/contrib/test_volumes.py new file mode 100644 index 000000000..443ec399f --- /dev/null +++ b/nova/tests/api/openstack/contrib/test_volumes.py @@ -0,0 +1,73 @@ +# Copyright 2011 Josh Durgin +# 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 datetime +import json +import webob + +import nova +from nova import context +from nova import test +from nova.api.openstack.contrib.volumes import BootFromVolumeController +from nova.compute import instance_types +from nova.tests.api.openstack import fakes +from nova.tests.api.openstack.test_servers import fake_gen_uuid + + +def fake_compute_api_create(cls, context, instance_type, image_href, **kwargs): + inst_type = instance_types.get_instance_type_by_flavor_id(2) + return [{'id': 1, + 'display_name': 'test_server', + 'uuid': fake_gen_uuid(), + 'instance_type': dict(inst_type), + 'access_ip_v4': '1.2.3.4', + 'access_ip_v6': 'fead::1234', + 'image_ref': 3, + 'user_id': 'fake', + 'project_id': 'fake', + 'created_at': datetime.datetime(2010, 10, 10, 12, 0, 0), + 'updated_at': datetime.datetime(2010, 11, 11, 11, 0, 0), + }] + + +class BootFromVolumeTest(test.TestCase): + + def setUp(self): + super(BootFromVolumeTest, self).setUp() + self.stubs.Set(nova.compute.API, 'create', fake_compute_api_create) + + def test_create_root_volume(self): + body = dict(server=dict( + name='test_server', imageRef=3, + flavorRef=2, min_count=1, max_count=1, + block_device_mapping=[dict( + volume_id=1, + device_name='/dev/vda', + virtual='root', + delete_on_termination=False, + )] + )) + req = webob.Request.blank('/v1.1/fake/os-volumes_boot') + 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(1, server['id']) + self.assertEqual(2, int(server['flavor']['id'])) + self.assertEqual(u'test_server', server['name']) + self.assertEqual(3, int(server['image']['id'])) + self.assertEqual(16, len(server['adminPass'])) -- cgit From caf0e0f10167fd31ea16a56d471380537a6ee03a Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Thu, 15 Sep 2011 16:58:22 -0500 Subject: NoMoreFixedIps now subclasses NovaException instead of Error --- nova/exception.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/exception.py b/nova/exception.py index a3cbb98cf..4f25d3721 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -506,7 +506,7 @@ class FixedIpInvalid(Invalid): message = _("Fixed IP address %(address)s is invalid.") -class NoMoreFixedIps(Error): +class NoMoreFixedIps(NovaException): message = _("Zero fixed ips available.") -- cgit