From 1b508d80dd76810f6183df50b9d9b324831875be Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Wed, 16 Feb 2011 12:01:22 +0000 Subject: First commit for xenapi-vlan-networking. Totally untested --- nova/network/manager.py | 1 + nova/virt/xenapi/network_utils.py | 17 +++++++++++++++-- nova/virt/xenapi/vm_utils.py | 1 + nova/virt/xenapi/vmops.py | 15 +++++++++++++-- 4 files changed, 30 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/network/manager.py b/nova/network/manager.py index 8eb9f041b..6ba0f2e18 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -499,6 +499,7 @@ class VlanManager(NetworkManager): def setup_compute_network(self, context, instance_id): """Sets up matching network for compute hosts.""" network_ref = db.network_get_by_instance(context, instance_id) + #TODO: the xenapi driver will create a xenserver network if necessary here self.driver.ensure_vlan_bridge(network_ref['vlan'], network_ref['bridge']) diff --git a/nova/virt/xenapi/network_utils.py b/nova/virt/xenapi/network_utils.py index c0406d8f0..4c2a81260 100644 --- a/nova/virt/xenapi/network_utils.py +++ b/nova/virt/xenapi/network_utils.py @@ -28,7 +28,20 @@ class NetworkHelper(HelperBase): """ The class that wraps the helper methods together. """ - + + + @classmethod + def find_network_with_name_label(cls,session,name_label): + networks = session.call_xenapi('network.get_by_name_label', name_label) + if len(networks) == 1: + return networks.keys()[0] + elif len(networks) > 1: + raise Exception(_('Found non-unique network' + ' for name_label %s') % name_label) + else: + return None + + @classmethod def find_network_with_bridge(cls, session, bridge): """Return the network on which the bridge is attached, if found.""" @@ -40,4 +53,4 @@ class NetworkHelper(HelperBase): raise Exception(_('Found non-unique network' ' for bridge %s') % bridge) else: - raise Exception(_('Found no network for bridge %s') % bridge) + raise Exception(_('Found no network for bridge %s') % bridge) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index f5c19099a..76257946e 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -204,6 +204,7 @@ class VMHelper(HelperBase): VIF reference.""" vif_rec = {} vif_rec['device'] = '0' + #network_ref should be the appropriate reference (network with VLAN) vif_rec['network'] = network_ref vif_rec['VM'] = vm_ref vif_rec['MAC'] = mac_address diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index fe95d881b..b5c5e082e 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -67,10 +67,21 @@ class VMOps(object): raise exception.Duplicate(_('Attempted to create' ' non-unique name %s') % instance.name) + #this will return the bridge name in nova db bridge = db.network_get_by_instance(context.get_admin_context(), instance['id'])['bridge'] - network_ref = \ - NetworkHelper.find_network_with_bridge(self._session, bridge) + + #this will return the appropriate network + #TODO: avoid unnecessary call to find_network_with_bridge + #when VLAN manager is being used (and not just use an if) + network_ref = None + try: + network_ref = \ + NetworkHelper.find_network_with_bridge(self._session, bridge) + except: + #try to get name with name_label + network_ref = \ + NetworkHelper.find_network_with_name_label(self._session,bridge) user = AuthManager().get_user(instance.user_id) project = AuthManager().get_project(instance.project_id) -- cgit From 01e340f98765cc434624b3b4da49447f950f07ae Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Wed, 16 Feb 2011 17:16:31 +0000 Subject: First commit of working code --- nova/network/linux_net.py | 1 + nova/virt/xenapi/network_utils.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index c1cbff7d8..f5efac0ae 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -291,6 +291,7 @@ def update_dhcp(context, network_id): if a dnsmasq instance is already running then send a HUP signal causing it to reload, otherwise spawn a new instance """ + LOG.debug("ENTERING update_dhcp - DHCP script:%s",FLAGS.dhcpbridge) network_ref = db.network_get(context, network_id) conffile = _dhcp_file(network_ref['bridge'], 'conf') diff --git a/nova/virt/xenapi/network_utils.py b/nova/virt/xenapi/network_utils.py index 4c2a81260..8f7806e6c 100644 --- a/nova/virt/xenapi/network_utils.py +++ b/nova/virt/xenapi/network_utils.py @@ -34,7 +34,7 @@ class NetworkHelper(HelperBase): def find_network_with_name_label(cls,session,name_label): networks = session.call_xenapi('network.get_by_name_label', name_label) if len(networks) == 1: - return networks.keys()[0] + return networks[0] elif len(networks) > 1: raise Exception(_('Found non-unique network' ' for name_label %s') % name_label) -- cgit From 441beee908d2534c4fa1d85523dbc87770efeb17 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Thu, 17 Feb 2011 16:54:42 +0000 Subject: Supporting networks with multiple PIFs. pep8 fixes unit tests passed --- nova/network/linux_net.py | 1 - nova/network/manager.py | 2 +- nova/network/xenapi_net.py | 130 ++++++++++++++++++++++++++++++++++++++ nova/virt/xenapi/network_utils.py | 12 ++-- nova/virt/xenapi/vmops.py | 11 ++-- 5 files changed, 142 insertions(+), 14 deletions(-) create mode 100644 nova/network/xenapi_net.py (limited to 'nova') diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index f5efac0ae..c1cbff7d8 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -291,7 +291,6 @@ def update_dhcp(context, network_id): if a dnsmasq instance is already running then send a HUP signal causing it to reload, otherwise spawn a new instance """ - LOG.debug("ENTERING update_dhcp - DHCP script:%s",FLAGS.dhcpbridge) network_ref = db.network_get(context, network_id) conffile = _dhcp_file(network_ref['bridge'], 'conf') diff --git a/nova/network/manager.py b/nova/network/manager.py index 6ba0f2e18..85209e69f 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -499,7 +499,7 @@ class VlanManager(NetworkManager): def setup_compute_network(self, context, instance_id): """Sets up matching network for compute hosts.""" network_ref = db.network_get_by_instance(context, instance_id) - #TODO: the xenapi driver will create a xenserver network if necessary here + #xenapi driver will create a xen network if necessary here self.driver.ensure_vlan_bridge(network_ref['vlan'], network_ref['bridge']) diff --git a/nova/network/xenapi_net.py b/nova/network/xenapi_net.py new file mode 100644 index 000000000..289dbd71f --- /dev/null +++ b/nova/network/xenapi_net.py @@ -0,0 +1,130 @@ +# 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. +""" +Implements vlans, bridges, and iptables rules using linux utilities. +""" + +import os + +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.xenapi_conn import XenAPISession +from nova.virt.xenapi.network_utils import NetworkHelper + +LOG = logging.getLogger("nova.xenapi_net") + +FLAGS = flags.FLAGS +flags.DEFINE_string('vlan_interface', 'eth0', + 'network device for vlans') + + +def metadata_forward(): + pass + + +def init_host(): + pass + + +def bind_floating_ip(floating_ip, check_exit_code=True): + pass + + +def unbind_floating_ip(floating_ip): + pass + + +def ensure_vlan_forward(public_ip, port, private_ip): + pass + + +def ensure_floating_forward(floating_ip, fixed_ip): + pass + + +def remove_floating_forward(floating_ip, fixed_ip): + pass + + +def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None): + """Create a vlan and bridge unless they already exist""" + #open xenapi session + url = FLAGS.xenapi_connection_url + username = FLAGS.xenapi_connection_username + password = FLAGS.xenapi_connection_password + session = XenAPISession(url, username, password) + #check whether bridge already exists + #retrieve network whose name_label is "bridge" + network_ref = NetworkHelper.find_network_with_name_label(session, bridge) + if network_ref == None: + #if bridge does not exists + #1 - create network + description = "network for nova bridge %s" % bridge + network_rec = { + 'name_label': bridge, + 'name_description': description, + 'other_config': {}, + } + network_ref = session.call_xenapi('network.create', network_rec) + #2 - find PIF for VLAN + expr = 'field "device" = "%s" and \ + field "VLAN" = "-1"' % FLAGS.vlan_interface + pifs = session.call_xenapi('PIF.get_all_records_where', expr) + pif_ref = None + #multiple PIF are ok: we are dealing with a pool + if len(pifs) == 0: + raise Exception( + _('Found no PID for device %s') % FLAGS.vlan_interface) + #3 - create vlan for network + for pif_ref in pifs.keys(): + session.call_xenapi('VLAN.create', pif_ref, + str(vlan_num), network_ref) + else: + #check VLAN tag is appropriate + network_rec = session.call_xenapi('network.get_record', network_ref) + #retrieve PIFs from network + for pif_ref in network_rec['PIFs']: + #retrieve VLAN from PIF + pif_rec = session.call_xenapi('PIF.get_record', pif_ref) + pif_vlan = int(pif_rec['VLAN']) + #raise an exception if VLAN <> vlan_num + if pif_vlan != vlan_num: + raise Exception(_("PIF %(pif_rec['uuid'])s for network " + "%(bridge)s has VLAN id %(pif_vlan)d." + "Expected %(vlan_num)d") % locals()) + + +def ensure_vlan(vlan_num): + pass + + +def ensure_bridge(bridge, interface, net_attrs=None): + pass + + +def get_dhcp_hosts(context, network_id): + pass + + +def update_dhcp(context, network_id): + pass + + +def update_ra(context, network_id): + pass diff --git a/nova/virt/xenapi/network_utils.py b/nova/virt/xenapi/network_utils.py index 8f7806e6c..c4ee8a6be 100644 --- a/nova/virt/xenapi/network_utils.py +++ b/nova/virt/xenapi/network_utils.py @@ -28,10 +28,9 @@ class NetworkHelper(HelperBase): """ The class that wraps the helper methods together. """ - - + @classmethod - def find_network_with_name_label(cls,session,name_label): + def find_network_with_name_label(cls, session, name_label): networks = session.call_xenapi('network.get_by_name_label', name_label) if len(networks) == 1: return networks[0] @@ -39,9 +38,8 @@ class NetworkHelper(HelperBase): raise Exception(_('Found non-unique network' ' for name_label %s') % name_label) else: - return None - - + return None + @classmethod def find_network_with_bridge(cls, session, bridge): """Return the network on which the bridge is attached, if found.""" @@ -53,4 +51,4 @@ class NetworkHelper(HelperBase): raise Exception(_('Found non-unique network' ' for bridge %s') % bridge) else: - raise Exception(_('Found no network for bridge %s') % bridge) + raise Exception(_('Found no network for bridge %s') % bridge) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index b5c5e082e..a0dbd1411 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -70,18 +70,19 @@ class VMOps(object): #this will return the bridge name in nova db bridge = db.network_get_by_instance(context.get_admin_context(), instance['id'])['bridge'] - + #this will return the appropriate network #TODO: avoid unnecessary call to find_network_with_bridge #when VLAN manager is being used (and not just use an if) - network_ref = None - try: + network_ref = None + try: network_ref = \ NetworkHelper.find_network_with_bridge(self._session, bridge) except: - #try to get name with name_label + #try to get name with name_label network_ref = \ - NetworkHelper.find_network_with_name_label(self._session,bridge) + NetworkHelper.find_network_with_name_label(self._session, + bridge) user = AuthManager().get_user(instance.user_id) project = AuthManager().get_project(instance.project_id) -- cgit From e263a37f83ec5f8a1d81b0f4ec7a91464b2bc022 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Mon, 21 Feb 2011 11:02:19 +0000 Subject: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ NOVA-CORE DEVELOPERS SHOULD NOT REVIEW THIS MERGE PROPOSAL ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This is for Citrix OpenStack team only. We propose for merge into a cache of lp:nova to generate diffs for our internal peer review. --- nova/virt/xenapi/vmops.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 9bd671045..09f5a1c94 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -141,6 +141,7 @@ class VMOps(object): #this will return the appropriate network #TODO(salvatore-orlando): avoid unnecessary call to #find_network_with_bridge when VLAN manager is being used + network_ref = None try: network_ref = \ -- cgit From 1278af43c16daea1bcbd2d47cd2d81919fe2c37e Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Sun, 27 Feb 2011 12:51:19 -0500 Subject: Add lxc libvirt driver --- nova/virt/libvirt.xml.template | 14 ++++++++++++++ nova/virt/libvirt_conn.py | 13 +++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template index 88bfbc668..87dea9039 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -2,6 +2,12 @@ ${name} ${memory_kb} +#if $type == 'lxc' + #set $disk_prefix = '' + #set $disk_bus = '' + exe + /sbin/init +#else #if $type == 'uml' #set $disk_prefix = 'ubd' #set $disk_bus = 'uml' @@ -37,6 +43,7 @@ #end if #end if + #end if #end if @@ -44,6 +51,12 @@ ${vcpus} +#if $type == 'lxc' + + + + +#else #if $getVar('rescue', False) @@ -68,6 +81,7 @@ #end if + #end if #end if diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 4e0fd106f..f97c10765 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -20,7 +20,7 @@ """ A connection to a hypervisor through libvirt. -Supports KVM, QEMU, UML, and XEN. +Supports KVM, LXC, QEMU, UML, and XEN. **Related Flags** @@ -83,7 +83,7 @@ flags.DEFINE_string('libvirt_xml_template', flags.DEFINE_string('libvirt_type', 'kvm', 'Libvirt domain type (valid options are: ' - 'kvm, qemu, uml, xen)') + 'kvm, lxc, qemu, uml, xen)') flags.DEFINE_string('libvirt_uri', '', 'Override the default libvirt URI (which is dependent' @@ -202,6 +202,8 @@ class LibvirtConnection(object): uri = FLAGS.libvirt_uri or 'uml:///system' elif FLAGS.libvirt_type == 'xen': uri = FLAGS.libvirt_uri or 'xen:///' + elif FLAGS.libvirt_type == 'lxc': + uri = FLAGS.libvirt_uri or 'lxc:///' else: uri = FLAGS.libvirt_uri or 'qemu:///system' return uri @@ -565,6 +567,10 @@ class LibvirtConnection(object): f.write(libvirt_xml) f.close() + if FLAGS.libvirt_type == 'lxc': + container_dir = '%s/rootfs' % basepath(suffix='') + utils.execute('mkdir -p %s' % container_dir) + # NOTE(vish): No need add the suffix to console.log os.close(os.open(basepath('console.log', ''), os.O_CREAT | os.O_WRONLY, 0660)) @@ -622,6 +628,9 @@ class LibvirtConnection(object): if not inst['kernel_id']: target_partition = "1" + if FLAGS.libvirt_type == 'lxc': + target_partition = None + key = str(inst['key_data']) net = None network_ref = db.network_get_by_instance(context.get_admin_context(), -- cgit From 2d6987d8c33477af19179e7664bbedf689bc08dc Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Sun, 27 Feb 2011 13:05:26 -0500 Subject: Add ability to mount containers --- nova/virt/disk.py | 26 ++++++++++++++++++++++++++ nova/virt/libvirt_conn.py | 6 ++++++ 2 files changed, 32 insertions(+) (limited to 'nova') diff --git a/nova/virt/disk.py b/nova/virt/disk.py index 2bded07a4..7cbc4a3be 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -114,6 +114,32 @@ def inject_data(image, key=None, net=None, partition=None, nbd=False): _unlink_device(device, nbd) +def setup_container(image, container_dir=None, partition=None, nbd=False): + """Setup the LXC container + + It will mount the loopback image to the container directory in order + to create the root filesystem for the container + """ + device = _link_device(image, nbd) + try: + if not partition is None: + # create partition + utils.execute('sudo kpartx -a %s' % device) + mapped_device = '/dev/mapper/%p%s' % (device.split('/')[-1], + partition) + else: + mapped_device = device + + utils.execute('sudo mount %s %s' %(mapped_device, container_dir)) + + except Exception as e: + LOG.warn(_('Unable to mount container')) + if not partition is None: + # remove partitions + utils.execute('sudo kpartx -s %s' % device) + _unlink_device(device, nbd) + + def _link_device(image, nbd): """Link image to device using loopback or nbd""" if nbd: diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index f97c10765..f7807a851 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -661,6 +661,12 @@ class LibvirtConnection(object): disk.inject_data(basepath('disk'), key, net, partition=target_partition, nbd=FLAGS.use_cow_images) + + if FLAGS.libvirt_type == 'lxc': + disk.setup_container(basepath('disk'), + container_dir=container_dir, + partition=target_partition, + nbd=FLAGS.use_cow_images) except Exception as e: # This could be a windows image, or a vmdk format disk LOG.warn(_('instance %(inst_name)s: ignoring error injecting' -- cgit From 33d7edb9d02d8f4dcb0b6ee85caf10434f060e1b Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Sun, 27 Feb 2011 13:29:05 -0500 Subject: Clean up the mount points when it shutsdown --- nova/virt/disk.py | 1 - nova/virt/libvirt_conn.py | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/disk.py b/nova/virt/disk.py index 7cbc4a3be..28a4c11fb 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -139,7 +139,6 @@ def setup_container(image, container_dir=None, partition=None, nbd=False): utils.execute('sudo kpartx -s %s' % device) _unlink_device(device, nbd) - def _link_device(image, nbd): """Link image to device using loopback or nbd""" if nbd: diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index f7807a851..5e76edb0b 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -259,6 +259,8 @@ class LibvirtConnection(object): instance_name = instance['name'] LOG.info(_('instance %(instance_name)s: deleting instance files' ' %(target)s') % locals()) + if FLAGS.libvirt_type == 'lxc': + utils.execute('sudo umount %s/rootfs' % target) if os.path.exists(target): shutil.rmtree(target) -- cgit From 031205a26ae6fe906a47eefa716bbd575687c479 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Sun, 27 Feb 2011 13:35:10 -0500 Subject: Add lxc to the libvirt tests --- nova/tests/test_virt.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index f151ae911..04f943a1f 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -112,12 +112,15 @@ class LibvirtConnTestCase(test.TestCase): 'uml': ('uml:///system', [(lambda t: t.find('.').get('type'), 'uml'), (lambda t: t.find('./os/type').text, 'uml')]), + 'lxc': ('lxc://', + [(lambda t: t.find('.').get('type'), 'lxc'), + (lambda t: t.find('./os/type').text, 'lxc')]), 'xen': ('xen:///', [(lambda t: t.find('.').get('type'), 'xen'), (lambda t: t.find('./os/type').text, 'linux')]), } - for hypervisor_type in ['qemu', 'kvm', 'xen']: + for hypervisor_type in ['qemu', 'kvm', 'lxc', 'xen']: check_list = type_uri_map[hypervisor_type][1] if rescue: -- cgit From 7825b7ce81dec97e997d296c3e30b5d143948abc Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 2 Mar 2011 01:21:54 -0800 Subject: initial commit of vnc support --- nova/api/ec2/cloud.py | 6 ++++++ nova/compute/api.py | 17 +++++++++++++++++ nova/compute/manager.py | 9 +++++++++ nova/flags.py | 6 ++++++ nova/virt/libvirt.xml.template | 1 + nova/virt/libvirt_conn.py | 17 +++++++++++++++++ 6 files changed, 56 insertions(+) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 844ccbe5e..aa9c6824e 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -539,6 +539,12 @@ class CloudController(object): return self.compute_api.get_ajax_console(context, instance_id=instance_id) + def get_vnc_console(self, context, instance_id, **kwargs): + ec2_id = instance_id + instance_id = ec2_id_to_id(ec2_id) + return self.compute_api.get_vnc_console(context, + instance_id=instance_id) + def describe_volumes(self, context, volume_id=None, **kwargs): if volume_id: volumes = [] diff --git a/nova/compute/api.py b/nova/compute/api.py index 625778b66..cec978d75 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -476,6 +476,23 @@ class API(base.Base): return {'url': '%s/?token=%s' % (FLAGS.ajax_console_proxy_url, output['token'])} + def get_vnc_console(self, context, instance_id): + """Get a url to an AJAX Console""" + instance = self.get(context, instance_id) + output = self._call_compute_message('get_vnc_console', + context, + instance_id) + rpc.cast(context, '%s' % FLAGS.vnc_console_proxy_topic, + {'method': 'authorize_vnc_console', + 'args': {'token': output['token'], 'host': output['host'], + 'port': output['port']}}) + + time.sleep(1) + + return {'url': '%s/vnc_auto.html?token=%s&host=%s&port=%s' % + (FLAGS.vnc_console_proxy_url, + output['token'], 'hostignore', 'portignore')} + def get_console_output(self, context, instance_id): """Get console output for an an instance""" return self._call_compute_message('get_console_output', diff --git a/nova/compute/manager.py b/nova/compute/manager.py index d659712ad..e53b36b34 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -557,6 +557,15 @@ class ComputeManager(manager.Manager): return self.driver.get_ajax_console(instance_ref) + @exception.wrap_exception + def get_vnc_console(self, context, instance_id): + """Return connection information for an vnc console""" + context = context.elevated() + LOG.debug(_("instance %s: getting vnc console"), instance_id) + instance_ref = self.db.instance_get(context, instance_id) + + return self.driver.get_vnc_console(instance_ref) + @checks_instance_lock def attach_volume(self, context, instance_id, volume_id, mountpoint): """Attach a volume to an instance.""" diff --git a/nova/flags.py b/nova/flags.py index 8cf199b2f..4f2be82b6 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -281,6 +281,12 @@ DEFINE_string('ajax_console_proxy_url', in the form "http://127.0.0.1:8000"') DEFINE_string('ajax_console_proxy_port', 8000, 'port that ajax_console_proxy binds') +DEFINE_string('vnc_console_proxy_topic', 'vnc_proxy', + 'the topic vnc proxy nodes listen on') +DEFINE_string('vnc_console_proxy_url', + 'http://127.0.0.1:6080', + 'location of vnc console proxy, \ + in the form "http://127.0.0.1:6080"') DEFINE_bool('verbose', False, 'show debug output') DEFINE_boolean('fake_rabbit', False, 'use a fake rabbit') DEFINE_bool('fake_network', False, diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template index 88bfbc668..7b4c23211 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -101,5 +101,6 @@ + diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 4e0fd106f..4fca84639 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -511,6 +511,23 @@ class LibvirtConnection(object): subprocess.Popen(cmd, shell=True) return {'token': token, 'host': host, 'port': port} + @exception.wrap_exception + def get_vnc_console(self, instance): + def get_vnc_port_for_instance(instance_name): + virt_dom = self._conn.lookupByName(instance_name) + xml = virt_dom.XMLDesc(0) + dom = minidom.parseString(xml) + + for graphic in dom.getElementsByTagName('graphics'): + if graphic.getAttribute('type') == 'vnc': + return graphic.getAttribute('port') + + port = get_vnc_port_for_instance(instance['name']) + token = str(uuid.uuid4()) + host = instance['host'] + + return {'token': token, 'host': host, 'port': port} + def _cache_image(self, fn, target, fname, cow=False, *args, **kwargs): """Wrapper for a method that creates an image that caches the image. -- cgit From cb30c80c922a09ccca18645670ea5b1cdc70f1f2 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Fri, 4 Mar 2011 12:31:59 +0000 Subject: fixed wrong local variable name in vmops --- nova/virt/xenapi/vmops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index a7838dbba..417f40da8 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -519,7 +519,7 @@ class VMOps(object): NetworkHelper.find_network_with_name_label(self._session, bridge) if network_ref: - VMHelper.create_vif(self._session, vm_ref, + VMHelper.create_vif(self._session, vm_opaque_ref, network_ref, instance.mac_address) -- cgit From c4142835981eb9b2d5a55517d975dbda029986e2 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Tue, 8 Mar 2011 10:09:34 +0000 Subject: Removed excess comment lines --- nova/network/manager.py | 1 - nova/network/xenapi_net.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/network/manager.py b/nova/network/manager.py index 05d677bc9..b36dd59cf 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -518,7 +518,6 @@ class VlanManager(NetworkManager): def setup_compute_network(self, context, instance_id): """Sets up matching network for compute hosts.""" network_ref = db.network_get_by_instance(context, instance_id) - #xenapi driver will create a xen network if necessary here self.driver.ensure_vlan_bridge(network_ref['vlan'], network_ref['bridge']) diff --git a/nova/network/xenapi_net.py b/nova/network/xenapi_net.py index d31a1352f..314638bcd 100644 --- a/nova/network/xenapi_net.py +++ b/nova/network/xenapi_net.py @@ -31,7 +31,7 @@ LOG = logging.getLogger("nova.xenapi_net") FLAGS = flags.FLAGS flags.DEFINE_string('vlan_interface', 'eth0', - 'network device for vlans') + 'Physical network interface for vlans') def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None): -- cgit From e502ad0243962aca98cc28bfa5cf69f8cd08991c Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Wed, 9 Mar 2011 21:43:45 -0500 Subject: Moved umount container to disk.py and try to remove loopback when destroying the container --- nova/virt/disk.py | 8 ++++++++ nova/virt/libvirt_conn.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/disk.py b/nova/virt/disk.py index e1b0171b5..484317dd8 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -140,6 +140,14 @@ def setup_container(image, container_dir=None, partition=None, nbd=False): utils.execute('sudo kpartx -s %s' % device) _unlink_device(device, nbd) +def destroy_container(target, instance, nbd=False): + """Destroy the container once it terminates""" + try: + utils.execute('sudo umount %s/rootfs' % target) + image = os.path.join(FLAGS.instances_path, instance['name'], '' + 'disk') + except Exception as e: + LOG.warn(_('Unable to umount contianer')) + def _link_device(image, nbd): """Link image to device using loopback or nbd""" if nbd: diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 97997513b..ef0eb20cd 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -260,7 +260,7 @@ class LibvirtConnection(object): LOG.info(_('instance %(instance_name)s: deleting instance files' ' %(target)s') % locals()) if FLAGS.libvirt_type == 'lxc': - utils.execute('sudo umount %s/rootfs' % target) + disk.destroy_container(target, instance, nbd=FLAGS.use_cow_images) if os.path.exists(target): shutil.rmtree(target) -- cgit From 977fc1be4ea8af93b63975c5538462a776fbe168 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Fri, 11 Mar 2011 11:42:42 +0000 Subject: Moved vlan_interface flag in network.manager removed needless carriage return in vm_ops --- nova/network/linux_net.py | 5 ++--- nova/network/manager.py | 3 +++ nova/network/xenapi_net.py | 5 ----- nova/tests/test_xenapi.py | 12 ++++++++++++ nova/virt/xenapi/vmops.py | 3 +-- 5 files changed, 18 insertions(+), 10 deletions(-) (limited to 'nova') diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 535ce87bc..6dcecdadb 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -41,13 +41,12 @@ flags.DEFINE_string('dhcpbridge_flagfile', flags.DEFINE_string('dhcp_domain', 'novalocal', 'domain to use for building the hostnames') - +#flags.DEFINE_string('vlan_interface', 'eth0', +# 'network device for vlans') flags.DEFINE_string('networks_path', '$state_path/networks', 'Location to keep network config files') flags.DEFINE_string('public_interface', 'eth0', 'Interface for public IP addresses') -flags.DEFINE_string('vlan_interface', 'eth0', - 'network device for vlans') flags.DEFINE_string('dhcpbridge', _bin_file('nova-dhcpbridge'), 'location of nova-dhcpbridge') flags.DEFINE_string('routing_source_ip', '$my_ip', diff --git a/nova/network/manager.py b/nova/network/manager.py index b36dd59cf..15ce36940 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -85,6 +85,8 @@ flags.DEFINE_string('fixed_range', '10.0.0.0/8', 'Fixed IP address block') flags.DEFINE_string('fixed_range_v6', 'fd00::/48', 'Fixed IPv6 address block') flags.DEFINE_integer('cnt_vpn_clients', 0, 'Number of addresses reserved for vpn clients') +flags.DEFINE_string('vlan_interface', 'eth0', + 'network device for vlans') flags.DEFINE_string('network_driver', 'nova.network.linux_net', 'Driver to use for network creation') flags.DEFINE_bool('update_dhcp_on_disassociate', False, @@ -517,6 +519,7 @@ class VlanManager(NetworkManager): def setup_compute_network(self, context, instance_id): """Sets up matching network for compute hosts.""" + LOG.debug("ENTERING SETUP COMPUTE NETWORK") network_ref = db.network_get_by_instance(context, instance_id) self.driver.ensure_vlan_bridge(network_ref['vlan'], network_ref['bridge']) diff --git a/nova/network/xenapi_net.py b/nova/network/xenapi_net.py index 314638bcd..01889f94d 100644 --- a/nova/network/xenapi_net.py +++ b/nova/network/xenapi_net.py @@ -29,11 +29,6 @@ from nova.virt.xenapi.network_utils import NetworkHelper LOG = logging.getLogger("nova.xenapi_net") -FLAGS = flags.FLAGS -flags.DEFINE_string('vlan_interface', 'eth0', - 'Physical network interface for vlans') - - def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None): """Create a vlan and bridge unless they already exist""" #open xenapi session diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 7f437c2b8..0f3b9ce20 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -38,6 +38,8 @@ from nova.tests.db import fakes as db_fakes from nova.tests.xenapi import stubs from nova.tests.glance import stubs as glance_stubs +from nova import log as LOG + FLAGS = flags.FLAGS @@ -297,6 +299,16 @@ class XenAPIVMTestCase(test.TestCase): glance_stubs.FakeGlance.IMAGE_KERNEL, glance_stubs.FakeGlance.IMAGE_RAMDISK) + def test_spawn_vlanmanager(self): + self.flags(xenapi_image_service = 'glance', + network_manager='nova.network.manager.VlanManager', + network_driver='nova.network.xenapi_net') + LOG.debug("Self.network:%s",self.network) + self._test_spawn(glance_stubs.FakeGlance.IMAGE_MACHINE, + glance_stubs.FakeGlance.IMAGE_KERNEL, + glance_stubs.FakeGlance.IMAGE_RAMDISK) + pass + def tearDown(self): super(XenAPIVMTestCase, self).tearDown() self.manager.delete_project(self.project) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 9140774c5..66df3c28e 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -236,8 +236,7 @@ class VMOps(object): """Create snapshot from a running VM instance :param instance: instance to be snapshotted - :param image_id: id of - image to upload to + :param image_id: id of image to upload to Steps involved in a XenServer snapshot: -- cgit From b76b61dbec03455824b90c427eb816c15e284013 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Fri, 11 Mar 2011 10:32:09 -0800 Subject: Added volume api from previous megapatch --- nova/api/openstack/__init__.py | 6 ++ nova/api/openstack/volumes.py | 160 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 nova/api/openstack/volumes.py (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index ab9dbb780..a7b639669 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -34,6 +34,7 @@ from nova.api.openstack import flavors from nova.api.openstack import images from nova.api.openstack import servers from nova.api.openstack import shared_ip_groups +from nova.api.openstack import volumes from nova.api.openstack import zones @@ -111,6 +112,11 @@ class APIRouter(wsgi.Router): collection={'detail': 'GET'}, controller=shared_ip_groups.Controller()) + #NOTE(justinsb): volumes is not yet part of the official API + mapper.resource("volume", "volumes", + controller=volumes.Controller(), + collection={'detail': 'GET'}) + super(APIRouter, self).__init__(mapper) diff --git a/nova/api/openstack/volumes.py b/nova/api/openstack/volumes.py new file mode 100644 index 000000000..99300421e --- /dev/null +++ b/nova/api/openstack/volumes.py @@ -0,0 +1,160 @@ +# Copyright 2011 Justin Santa Barbara +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from webob import exc + +from nova import exception +from nova import flags +from nova import log as logging +from nova import volume +from nova import wsgi +from nova.api.openstack import common +from nova.api.openstack import faults + + +LOG = logging.getLogger("nova.api.volumes") + +FLAGS = flags.FLAGS + + +def _translate_detail_view(context, inst): + """ Maps keys for details view""" + + inst_dict = _translate_summary_view(context, inst) + + # No additional data / lookups at the moment + + return inst_dict + + +def _translate_summary_view(context, volume): + """ Maps keys for summary view""" + v = {} + + instance_id = None + # instance_data = None + attached_to = volume.get('instance') + if attached_to: + instance_id = attached_to['id'] + # instance_data = '%s[%s]' % (instance_ec2_id, + # attached_to['host']) + v['id'] = volume['id'] + v['status'] = volume['status'] + v['size'] = volume['size'] + v['availabilityZone'] = volume['availability_zone'] + v['createdAt'] = volume['created_at'] + # if context.is_admin: + # v['status'] = '%s (%s, %s, %s, %s)' % ( + # volume['status'], + # volume['user_id'], + # volume['host'], + # instance_data, + # volume['mountpoint']) + if volume['attach_status'] == 'attached': + v['attachments'] = [{'attachTime': volume['attach_time'], + 'deleteOnTermination': False, + 'mountpoint': volume['mountpoint'], + 'instanceId': instance_id, + 'status': 'attached', + 'volumeId': volume['id']}] + else: + v['attachments'] = [{}] + + v['displayName'] = volume['display_name'] + v['displayDescription'] = volume['display_description'] + return v + + +class Controller(wsgi.Controller): + """ The Volumes API controller for the OpenStack API """ + + _serialization_metadata = { + 'application/xml': { + "attributes": { + "volume": [ + "id", + "status", + "size", + "availabilityZone", + "createdAt", + "displayName", + "displayDescription", + ]}}} + + def __init__(self): + self.volume_api = volume.API() + super(Controller, self).__init__() + + def show(self, req, id): + """Return data about the given volume""" + context = req.environ['nova.context'] + + try: + volume = self.volume_api.get(context, id) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + + return {'volume': _translate_detail_view(context, volume)} + + def delete(self, req, id): + """ Delete a volume """ + context = req.environ['nova.context'] + + LOG.audit(_("Delete volume with id: %s"), id, context=context) + + try: + self.volume_api.delete(context, volume_id=id) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + return exc.HTTPAccepted() + + def index(self, req): + """ Returns a summary list of volumes""" + return self._items(req, entity_maker=_translate_summary_view) + + def detail(self, req): + """ Returns a detailed list of volumes """ + return self._items(req, entity_maker=_translate_detail_view) + + def _items(self, req, entity_maker): + """Returns a list of volumes, transformed through entity_maker""" + context = req.environ['nova.context'] + + volumes = self.volume_api.get_all(context) + limited_list = common.limited(volumes, req) + res = [entity_maker(context, inst) for inst in limited_list] + return {'volumes': res} + + def create(self, req): + """Creates a new volume""" + context = req.environ['nova.context'] + + env = self._deserialize(req.body, req) + if not env: + return faults.Fault(exc.HTTPUnprocessableEntity()) + + vol = env['volume'] + size = vol['size'] + LOG.audit(_("Create volume of %s GB"), size, context=context) + volume = self.volume_api.create(context, size, + vol.get('display_name'), + vol.get('display_description')) + + # Work around problem that instance is lazy-loaded... + volume['instance'] = None + + retval = _translate_detail_view(context, volume) + + return {'volume': retval} -- cgit From 743e82c0acac0fda78a55a8bbb65e601c4cb652c Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Mon, 14 Mar 2011 21:14:39 -0400 Subject: Refactor setup contianer/destroy container --- nova/virt/disk.py | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) (limited to 'nova') diff --git a/nova/virt/disk.py b/nova/virt/disk.py index f0b391efb..a3db1d882 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -122,31 +122,26 @@ def setup_container(image, container_dir=None, partition=None, nbd=False): to create the root filesystem for the container """ device = _link_device(image, nbd) - try: - if not partition is None: - # create partition - utils.execute('sudo kpartx -a %s' % device) - mapped_device = '/dev/mapper/%p%s' % (device.split('/')[-1], - partition) - else: - mapped_device = device - - utils.execute('sudo mount %s %s' %(mapped_device, container_dir)) - - except Exception as e: - LOG.warn(_('Unable to mount container')) - if not partition is None: - # remove partitions - utils.execute('sudo kpartx -s %s' % device) + err = utils.execute('sudo', 'mount', mapped_device, container_dir) + if err: + raise exception.Error(_('Failed to mount filesystem: %s') + % err) _unlink_device(device, nbd) def destroy_container(target, instance, nbd=False): """Destroy the container once it terminates""" try: - utils.execute('sudo umount %s/rootfs' % target) + container_dir = '%s/rootfs' % target + utils.execute('sudo', 'umount', container_dir) + finally: image = os.path.join(FLAGS.instances_path, instance['name'], '' + 'disk') - except Exception as e: - LOG.warn(_('Unable to umount contianer')) + out, err = utils.execute('sudo', 'losetup', '--find', '--show', image) + device = out.strip() + if err: + raise execption.Error(_('Could not find loopback image: %s') + %err) + utils.execute('sudo', 'losetup', '--detach', device) + def _link_device(image, nbd): """Link image to device using loopback or nbd""" -- cgit From 48d3dd7f9d2633d8955080b6dccc7c97bc8ef7c3 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Tue, 15 Mar 2011 07:56:26 -0400 Subject: Mount the right device --- nova/virt/disk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/disk.py b/nova/virt/disk.py index a3db1d882..2c0460f39 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -122,7 +122,7 @@ def setup_container(image, container_dir=None, partition=None, nbd=False): to create the root filesystem for the container """ device = _link_device(image, nbd) - err = utils.execute('sudo', 'mount', mapped_device, container_dir) + err = utils.execute('sudo', 'mount', device, container_dir) if err: raise exception.Error(_('Failed to mount filesystem: %s') % err) -- cgit From f60c9d0da8171b09bd7971fea52e9e032f98a143 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Tue, 15 Mar 2011 08:05:45 -0400 Subject: Add comments about the destroy container function --- nova/virt/disk.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/disk.py b/nova/virt/disk.py index 2c0460f39..dd4352957 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -129,7 +129,11 @@ def setup_container(image, container_dir=None, partition=None, nbd=False): _unlink_device(device, nbd) def destroy_container(target, instance, nbd=False): - """Destroy the container once it terminates""" + """Destroy the container once it terminates + + It will umount the container that is mounted, try to find the loopback + device associated with the container and delete it. + """ try: container_dir = '%s/rootfs' % target utils.execute('sudo', 'umount', container_dir) -- cgit From 3c10c1ee1bcc3f3aad90e4e28761d1413ab203a9 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Tue, 15 Mar 2011 09:36:02 -0400 Subject: Really delete the loop --- nova/virt/disk.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) (limited to 'nova') diff --git a/nova/virt/disk.py b/nova/virt/disk.py index dd4352957..a44995613 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -23,6 +23,7 @@ Includes injection of SSH PGP keys into authorized_keys file. """ import os +import string import tempfile import time @@ -138,13 +139,10 @@ def destroy_container(target, instance, nbd=False): container_dir = '%s/rootfs' % target utils.execute('sudo', 'umount', container_dir) finally: - image = os.path.join(FLAGS.instances_path, instance['name'], '' + 'disk') - out, err = utils.execute('sudo', 'losetup', '--find', '--show', image) - device = out.strip() - if err: - raise execption.Error(_('Could not find loopback image: %s') - %err) - utils.execute('sudo', 'losetup', '--detach', device) + for loop in os.popen('sudo losetup -a').readlines(): + if instance['name'] in loop: + device = string.split(loop, ':') + utils.execute('sudo', 'losetup', '--detach', device) def _link_device(image, nbd): -- cgit From ceb8cd14f968aa063bd6a19999340f77c5603568 Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Tue, 15 Mar 2011 21:04:38 -0700 Subject: Fixed DescribeUser in ec2 admin client --- nova/adminclient.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/adminclient.py b/nova/adminclient.py index fc3c5c5fe..f570e12c2 100644 --- a/nova/adminclient.py +++ b/nova/adminclient.py @@ -324,14 +324,11 @@ class NovaAdminClient(object): def get_user(self, name): """Grab a single user by name.""" - try: - return self.apiconn.get_object('DescribeUser', - {'Name': name}, - UserInfo) - except boto.exception.BotoServerError, e: - if e.status == 400 and e.error_code == 'NotFound': - return None - raise + user = self.apiconn.get_object('DescribeUser', + {'Name': name}, + UserInfo) + if user.username != None: + return user def has_user(self, username): """Determine if user exists.""" -- cgit From bb52b51d0e4f9b297dcc489562f38d1647e10856 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Wed, 16 Mar 2011 12:34:39 +0000 Subject: Adding unit test --- nova/network/manager.py | 2 ++ nova/network/xenapi_net.py | 3 +++ nova/tests/db/fakes.py | 45 +++++++++++++++++++++++++++---- nova/tests/test_xenapi.py | 22 ++++++++++++--- nova/virt/xenapi/fake.py | 67 ++++++++++++++++++++++++++++++++++++++++++---- nova/virt/xenapi/vmops.py | 2 +- 6 files changed, 127 insertions(+), 14 deletions(-) (limited to 'nova') diff --git a/nova/network/manager.py b/nova/network/manager.py index 4baea482b..3b53d5d05 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -115,6 +115,7 @@ class NetworkManager(manager.Manager): timeout_fixed_ips = True def __init__(self, network_driver=None, *args, **kwargs): + LOG.debug("INIT - network driver:%s", network_driver) if not network_driver: network_driver = FLAGS.network_driver self.driver = utils.import_object(network_driver) @@ -520,6 +521,7 @@ class VlanManager(NetworkManager): def setup_compute_network(self, context, instance_id): """Sets up matching network for compute hosts.""" LOG.debug("ENTERING SETUP COMPUTE NETWORK") + LOG.debug("DRIVER:%s",self.driver) network_ref = db.network_get_by_instance(context, instance_id) self.driver.ensure_vlan_bridge(network_ref['vlan'], network_ref['bridge']) diff --git a/nova/network/xenapi_net.py b/nova/network/xenapi_net.py index 01889f94d..49214764e 100644 --- a/nova/network/xenapi_net.py +++ b/nova/network/xenapi_net.py @@ -29,9 +29,12 @@ from nova.virt.xenapi.network_utils import NetworkHelper LOG = logging.getLogger("nova.xenapi_net") +FLAGS = flags.FLAGS + def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None): """Create a vlan and bridge unless they already exist""" #open xenapi session + LOG.debug("ENTERING ensure_vlan_bridge in xenapi net") url = FLAGS.xenapi_connection_url username = FLAGS.xenapi_connection_username password = FLAGS.xenapi_connection_password diff --git a/nova/tests/db/fakes.py b/nova/tests/db/fakes.py index d760dc456..88daa82c3 100644 --- a/nova/tests/db/fakes.py +++ b/nova/tests/db/fakes.py @@ -23,8 +23,9 @@ from nova import db from nova import test from nova import utils +from nova import log as LOG -def stub_out_db_instance_api(stubs): +def stub_out_db_instance_api(stubs, injected=True): """ Stubs out the db API for creating Instances """ INSTANCE_TYPES = { @@ -36,6 +37,29 @@ def stub_out_db_instance_api(stubs): 'm1.xlarge': dict(memory_mb=16384, vcpus=8, local_gb=160, flavorid=5)} + flat_network_fields = { + 'id': 'fake_flat', + 'bridge': 'xenbr0', + 'label': 'fake_flat_network', + 'netmask': '255.255.255.0', + 'gateway': '10.0.0.1', + 'broadcast': '10.0.0.255', + 'dns': '10.0.0.2', + 'ra_server': None, + 'injected': injected} + + vlan_network_fields = { + 'id': 'fake_vlan', + 'bridge': 'br111', + 'label': 'fake_vlan_network', + 'netmask': '255.255.255.0', + 'gateway': '10.0.0.1', + 'broadcast': '10.0.0.255', + 'dns': '10.0.0.2', + 'ra_server': None, + 'vlan': 111, + 'injected': False} + class FakeModel(object): """ Stubs out for model """ def __init__(self, values): @@ -81,12 +105,23 @@ def stub_out_db_instance_api(stubs): return FakeModel(base_options) def fake_network_get_by_instance(context, instance_id): - fields = { - 'bridge': 'xenbr0', - } - return FakeModel(fields) + #even instance numbers are on vlan networks + if instance_id % 2 == 0: + return FakeModel(vlan_network_fields) + else: + return FakeModel(flat_network_fields) + + def fake_network_get_all_by_instance(context, instance_id): + l = [] + #even instance numbers are on vlan networks + if instance_id % 2 == 0: + l.append(FakeModel(vlan_network_fields)) + else: + l.append(FakeModel(flat_network_fields)) + return l stubs.Set(db, 'instance_create', fake_instance_create) stubs.Set(db, 'network_get_by_instance', fake_network_get_by_instance) + stubs.Set(db, 'network_get_all_by_instance', fake_network_get_all_by_instance) stubs.Set(db, 'instance_type_get_all', fake_instance_type_get_all) stubs.Set(db, 'instance_type_get_by_name', fake_instance_type_get_by_name) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 26fad39d1..2cdc84882 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -165,6 +165,7 @@ class XenAPIVMTestCase(test.TestCase): FLAGS.xenapi_connection_password = 'test_pass' xenapi_fake.reset() xenapi_fake.create_local_srs() + xenapi_fake.create_local_pifs() db_fakes.stub_out_db_instance_api(self.stubs) xenapi_fake.create_network('fake', FLAGS.flat_network_bridge) stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) @@ -252,6 +253,9 @@ class XenAPIVMTestCase(test.TestCase): # Check that the VM is running according to XenAPI. self.assertEquals(vm['power_state'], 'Running') + + # Check that VM network is consistent with nova network + LOG.debug("VM INFO - NETWORK:%s", vm_info) def _test_spawn(self, image_id, kernel_id, ramdisk_id, instance_type="m1.large"): @@ -301,13 +305,25 @@ class XenAPIVMTestCase(test.TestCase): def test_spawn_vlanmanager(self): self.flags(xenapi_image_service = 'glance', - network_manager='nova.network.manager.VlanManager', - network_driver='nova.network.xenapi_net') + network_manager = 'nova.network.manager.VlanManager', + network_driver = 'nova.network.xenapi_net', + vlan_interface = 'fake0') LOG.debug("Self.network:%s",self.network) + LOG.debug("network driver:%s",FLAGS.network_driver) + fake_instance_id = 2 + network_bk=self.network + #ensure we use xenapi_net driver + self.network = utils.import_object(FLAGS.network_manager) + self.network.setup_compute_network(None, fake_instance_id) self._test_spawn(glance_stubs.FakeGlance.IMAGE_MACHINE, glance_stubs.FakeGlance.IMAGE_KERNEL, glance_stubs.FakeGlance.IMAGE_RAMDISK) - pass + url = FLAGS.xenapi_connection_url + username = FLAGS.xenapi_connection_username + password = FLAGS.xenapi_connection_password + session = xenapi_conn.XenAPISession(url, username, password) + + self.network = network_bk def tearDown(self): super(XenAPIVMTestCase, self).tearDown() diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index ba12d4d3a..2e8cd9c5c 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -61,7 +61,7 @@ from nova import log as logging _CLASSES = ['host', 'network', 'session', 'SR', 'VBD',\ - 'PBD', 'VDI', 'VIF', 'VM', 'task'] + 'PBD', 'VDI', 'VIF', 'PIF', 'VM', 'VLAN', 'task'] _db_content = {} @@ -103,7 +103,6 @@ def create_vm(name_label, status, 'is_control_domain': is_control_domain, }) - def destroy_vm(vm_ref): vm_rec = _db_content['VM'][vm_ref] @@ -178,6 +177,12 @@ def create_task(name_label): }) +def create_local_pifs(): + """Adds a PIF for each to the local database with VLAN=-1. + Do this one per host.""" + for host_ref in _db_content['host'].keys(): + _create_local_pif(host_ref) + def create_local_srs(): """Create an SR that looks like the one created on the local disk by default by the XenServer installer. Do this one per host.""" @@ -204,8 +209,18 @@ def _create_local_sr(host_ref): _db_content['SR'][sr_ref]['PBDs'] = [pbd_ref] return sr_ref +def _create_local_pif(host_ref): + pif_ref= _create_object('PIF', { + 'name-label': 'Fake PIF', + 'MAC': '00:11:22:33:44:55', + 'physical': True, + 'VLAN': -1, + 'device': 'fake0', + 'host_uuid': host_ref, + }) def _create_object(table, obj): + LOG.debug("ENTERING _create_object:%s", obj) ref = str(uuid.uuid4()) obj['uuid'] = str(uuid.uuid4()) _db_content[table][ref] = obj @@ -228,6 +243,24 @@ def _create_sr(table, obj): return sr_ref +def _create_vlan(pif_ref, vlan_num, network_ref): + LOG.debug("ENTERING FAKE CREATE VLAN") + pif_rec = get_record('PIF', pif_ref) + vlan_pif_ref = _create_object('PIF', { + 'name-label': 'Fake VLAN PIF', + 'MAC': '00:11:22:33:44:55', + 'physical': True, + 'VLAN': vlan_num, + 'device': pif_rec['device'], + 'host_uuid': pif_rec['host_uuid'], + }) + return _create_object('VLAN', { + 'tagged-pif': pif_ref, + 'untagged-pif': vlan_pif_ref, + 'tag': vlan_num + }) + + def get_all(table): return _db_content[table].keys() @@ -235,7 +268,6 @@ def get_all(table): def get_all_records(table): return _db_content[table] - def get_record(table, ref): if ref in _db_content[table]: return _db_content[table].get(ref) @@ -286,6 +318,26 @@ class SessionBase(object): rec['currently_attached'] = False rec['device'] = '' + def PIF_get_all_records_where(self, _1,_2): + # TODO (salvatore-orlando):filter table on _2 + return _db_content['PIF'] + + def VM_get_xenstore_data(self, _1, vm_ref): + return _db_content['VM'][vm_ref].get('xenstore_data', '') + + def VM_remove_from_xenstore_data(self, _1, vm_ref, key): + db_ref = _db_content['VM'][vm_ref] + if not 'xenstore_data' in db_ref: + return + db_ref['xenstore_data'][key] = None + + + def VM_add_to_xenstore_data(self, _1, vm_ref, key, value): + db_ref = _db_content['VM'][vm_ref] + if not 'xenstore_data' in db_ref: + db_ref['xenstore_data'] = {} + db_ref['xenstore_data'][key] = value + def host_compute_free_memory(self, _1, ref): #Always return 12GB available return 12 * 1024 * 1024 * 1024 @@ -431,12 +483,17 @@ class SessionBase(object): def _create(self, name, params): self._check_session(params) is_sr_create = name == 'SR.create' + LOG.debug("NAME:%s",name) + is_vlan_create = name == 'VLAN.create' # Storage Repositories have a different API - expected = is_sr_create and 10 or 2 + expected = is_sr_create and 10 or is_vlan_create and 4 or 2 self._check_arg_count(params, expected) (cls, _) = name.split('.') ref = is_sr_create and \ - _create_sr(cls, params) or _create_object(cls, params[1]) + _create_sr(cls, params) or \ + is_vlan_create and \ + _create_vlan(params[1],params[2],params[3]) or \ + _create_object(cls, params[1]) # Call hook to provide any fixups needed (ex. creating backrefs) after_hook = 'after_%s_create' % cls diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 0813c3db4..4bfef20f3 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -772,7 +772,7 @@ class VMOps(object): if network_ref: try: device = "1" if instance._rescue else "0" - except AttributeError: + except (AttributeError, KeyError): device = "0" VMHelper.create_vif( -- cgit From 7fbf061666516705e74592c3660155e86d3da895 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Wed, 16 Mar 2011 09:15:46 -0400 Subject: Fix up testsuite for lxc --- nova/tests/test_virt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index c149c9307..b3ca241cb 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -256,9 +256,9 @@ class LibvirtConnTestCase(test.TestCase): 'uml': ('uml:///system', [(lambda t: t.find('.').get('type'), 'uml'), (lambda t: t.find('./os/type').text, 'uml')]), - 'lxc': ('lxc://', + 'lxc': ('lxc://;', [(lambda t: t.find('.').get('type'), 'lxc'), - (lambda t: t.find('./os/type').text, 'lxc')]), + (lambda t: t.find('./os/type').text, 'exe')]), 'xen': ('xen:///', [(lambda t: t.find('.').get('type'), 'xen'), (lambda t: t.find('./os/type').text, 'linux')]), -- cgit From 8f9a5ecb7d3907456b9a77f3321ed09feb5c5f2f Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Wed, 16 Mar 2011 09:24:17 -0400 Subject: More execvp fallout --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index af2cbdce5..a79e0a065 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -599,7 +599,7 @@ class LibvirtConnection(object): if FLAGS.libvirt_type == 'lxc': container_dir = '%s/rootfs' % basepath(suffix='') - utils.execute('mkdir -p %s' % container_dir) + utils.execute('mkdir', '-p', container_dir) # NOTE(vish): No need add the suffix to console.log os.close(os.open(basepath('console.log', ''), -- cgit From a21efc63be6bad3bbde41eb96d6a1752e6d8174d Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Wed, 16 Mar 2011 09:26:37 -0400 Subject: Really fix testcase --- nova/tests/test_virt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index b3ca241cb..fed8ff803 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -256,7 +256,7 @@ class LibvirtConnTestCase(test.TestCase): 'uml': ('uml:///system', [(lambda t: t.find('.').get('type'), 'uml'), (lambda t: t.find('./os/type').text, 'uml')]), - 'lxc': ('lxc://;', + 'lxc': ('lxc:///', [(lambda t: t.find('.').get('type'), 'lxc'), (lambda t: t.find('./os/type').text, 'exe')]), 'xen': ('xen:///', -- cgit From c44ab013f5f5a078b27c4965e2e3c4abbfe30c59 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Wed, 16 Mar 2011 20:42:39 -0400 Subject: Revert testsuite changes --- nova/tests/test_virt.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index fed8ff803..b214f5ce7 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -256,15 +256,12 @@ class LibvirtConnTestCase(test.TestCase): 'uml': ('uml:///system', [(lambda t: t.find('.').get('type'), 'uml'), (lambda t: t.find('./os/type').text, 'uml')]), - 'lxc': ('lxc:///', - [(lambda t: t.find('.').get('type'), 'lxc'), - (lambda t: t.find('./os/type').text, 'exe')]), 'xen': ('xen:///', [(lambda t: t.find('.').get('type'), 'xen'), (lambda t: t.find('./os/type').text, 'linux')]), } - for hypervisor_type in ['qemu', 'kvm', 'lxc', 'xen']: + for hypervisor_type in ['qemu', 'kvm', 'xen']: check_list = type_uri_map[hypervisor_type][1] if rescue: -- cgit From fea850245835f867aa4cc741b612445e56e64236 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Wed, 16 Mar 2011 20:52:14 -0400 Subject: Add basic tests for lxc containers. --- nova/tests/test_virt.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'nova') diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index b214f5ce7..fab05de10 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -227,6 +227,42 @@ class LibvirtConnTestCase(test.TestCase): self._check_xml_and_uri(instance_data, expect_kernel=True, expect_ramdisk=True, rescue=True) + def test_lxc_container_and_uri(self): + instance_data = dict(self.test_instace) + self._check_xml_and_container(instance_data) + + def _check_xml_and_container(self, instance): + user_context = context.RequestContext(project=self.project, + user=self.user) + instance_ref = db.instance_create(user_context,instance) + host = self.network.get_network_host(user_context.elevated()) + network_ref= db.project_get_network(context.get_admin_context(), + self.project.id) + + fixed_ip = {'address': self.test_ip, + 'network_id': network_ref['id']} + + ctxt = context.get_admin_context() + fixed_ip_ref = db.fixed_ip_create(ctxt, fixed_ip) + db.fixed_ip_update(ctxt, self.test_ip, + {'allocated': True, + 'instance_id': instance_ref['id']}) + + FLAGS.libvirt_type = 'lxc' + self.assertEquals(uri, 'lxc:///') + + xml = conn.to_xml(instance_ref) + tree = xml_to_tree(xml) + + check = [ + (lambda t: t.find('.').get('type'), 'lxc'), + (lambda t: t.find('./os/type').text, 'exe') + ] + + for i (check, expected_result) in enumerate(check): + self.aseertEqual(check(time), + expected_result, + '%s failed common check %d' % (xml, i)) def _check_xml_and_uri(self, instance, expect_ramdisk, expect_kernel, rescue=False): user_context = context.RequestContext(project=self.project, -- cgit From 7f837b1f22922cb968b0ffb42bdb4d56c0d9f3c3 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Thu, 17 Mar 2011 07:06:58 -0400 Subject: Update Authors and testsuite --- nova/tests/test_virt.py | 41 ++++++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 17 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index fab05de10..92c80272b 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -228,16 +228,16 @@ class LibvirtConnTestCase(test.TestCase): expect_ramdisk=True, rescue=True) def test_lxc_container_and_uri(self): - instance_data = dict(self.test_instace) + instance_data = dict(self.test_instance) self._check_xml_and_container(instance_data) def _check_xml_and_container(self, instance): user_context = context.RequestContext(project=self.project, user=self.user) - instance_ref = db.instance_create(user_context,instance) + instance_ref = db.instance_create(user_context, instance) host = self.network.get_network_host(user_context.elevated()) - network_ref= db.project_get_network(context.get_admin_context(), - self.project.id) + network_ref = db.project_get_network(context.get_admin_context(), + self.project.id) fixed_ip = {'address': self.test_ip, 'network_id': network_ref['id']} @@ -245,24 +245,28 @@ class LibvirtConnTestCase(test.TestCase): ctxt = context.get_admin_context() fixed_ip_ref = db.fixed_ip_create(ctxt, fixed_ip) db.fixed_ip_update(ctxt, self.test_ip, - {'allocated': True, - 'instance_id': instance_ref['id']}) + {'allocated': True, + 'instance_id': instance_ref['id']}) FLAGS.libvirt_type = 'lxc' + conn = libvirt_conn.LibvirtConnection(True) + + uri = conn.get_uri() self.assertEquals(uri, 'lxc:///') xml = conn.to_xml(instance_ref) tree = xml_to_tree(xml) check = [ - (lambda t: t.find('.').get('type'), 'lxc'), - (lambda t: t.find('./os/type').text, 'exe') + (lambda t: t.find('.').get('type'), 'lxc'), + (lambda t: t.find('./os/type').text, 'exe'), ] - for i (check, expected_result) in enumerate(check): - self.aseertEqual(check(time), + for i, (check, expected_result) in enumerate(check): + self.assertEqual(check(tree), expected_result, '%s failed common check %d' % (xml, i)) + def _check_xml_and_uri(self, instance, expect_ramdisk, expect_kernel, rescue=False): user_context = context.RequestContext(project=self.project, @@ -322,6 +326,7 @@ class LibvirtConnTestCase(test.TestCase): check = (lambda t: t.find('./os/initrd'), None) check_list.append(check) + common_checks = [ (lambda t: t.find('.').tag, 'domain'), (lambda t: t.find( @@ -338,8 +343,9 @@ class LibvirtConnTestCase(test.TestCase): (lambda t: t.find('./devices/serial/source').get( 'path').split('/')[1], 'console.log'), (lambda t: t.find('./memory').text, '2097152')] + if rescue: - common_checks += [ + common_checks = [ (lambda t: t.findall('./devices/disk/source')[0].get( 'file').split('/')[1], 'disk.rescue'), (lambda t: t.findall('./devices/disk/source')[1].get( @@ -362,14 +368,15 @@ class LibvirtConnTestCase(test.TestCase): xml = conn.to_xml(instance_ref, rescue) tree = xml_to_tree(xml) for i, (check, expected_result) in enumerate(checks): - self.assertEqual(check(tree), - expected_result, - '%s failed check %d' % (xml, i)) + self.assertEqual(check(tree), + expected_result, + '%s failed check %d' % (xml, i)) + for i, (check, expected_result) in enumerate(common_checks): - self.assertEqual(check(tree), - expected_result, - '%s failed common check %d' % (xml, i)) + self.assertEqual(check(tree), + expected_result, + '%s failed common check %d' % (xml, i)) # This test is supposed to make sure we don't # override a specifically set uri -- cgit From 7701edd34f1fc9fa26b3dfcc77ff87018622bedc Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Thu, 17 Mar 2011 07:13:31 -0400 Subject: get_console_output is not supported by lxc and libvirt --- nova/virt/libvirt_conn.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index d08ca8b6a..9bfd3f841 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -490,6 +490,9 @@ class LibvirtConnection(object): instance['name']) data = self._flush_xen_console(virsh_output) fpath = self._append_to_file(data, console_log) + elif FLAGS.libvirt_type == 'lxc': + # LXC is also special + LOG.info(_("Unable to read LXC console")) else: fpath = console_log -- cgit From 9ba500c304595dff037da296f26cb13d02bfbc04 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Thu, 17 Mar 2011 07:57:06 -0400 Subject: Fix pep8 errors --- nova/tests/test_virt.py | 2 -- nova/virt/disk.py | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 92c80272b..7d50960a3 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -326,7 +326,6 @@ class LibvirtConnTestCase(test.TestCase): check = (lambda t: t.find('./os/initrd'), None) check_list.append(check) - common_checks = [ (lambda t: t.find('.').tag, 'domain'), (lambda t: t.find( @@ -372,7 +371,6 @@ class LibvirtConnTestCase(test.TestCase): expected_result, '%s failed check %d' % (xml, i)) - for i, (check, expected_result) in enumerate(common_checks): self.assertEqual(check(tree), expected_result, diff --git a/nova/virt/disk.py b/nova/virt/disk.py index a44995613..26976940e 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -129,6 +129,7 @@ def setup_container(image, container_dir=None, partition=None, nbd=False): % err) _unlink_device(device, nbd) + def destroy_container(target, instance, nbd=False): """Destroy the container once it terminates -- cgit From c98cead470f33041e928a6f82be801efeb94ccc3 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Thu, 17 Mar 2011 08:52:52 -0400 Subject: Remove nbd=FLAGS.use_cow_images for destroy container --- nova/virt/disk.py | 7 +++---- nova/virt/libvirt_conn.py | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/virt/disk.py b/nova/virt/disk.py index 26976940e..90d3cf499 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -23,7 +23,6 @@ Includes injection of SSH PGP keys into authorized_keys file. """ import os -import string import tempfile import time @@ -130,7 +129,7 @@ def setup_container(image, container_dir=None, partition=None, nbd=False): _unlink_device(device, nbd) -def destroy_container(target, instance, nbd=False): +def destroy_container(target, instance): """Destroy the container once it terminates It will umount the container that is mounted, try to find the loopback @@ -140,9 +139,9 @@ def destroy_container(target, instance, nbd=False): container_dir = '%s/rootfs' % target utils.execute('sudo', 'umount', container_dir) finally: - for loop in os.popen('sudo losetup -a').readlines(): + for loop in utils.popen('sudo losetup -a').readlines(): if instance['name'] in loop: - device = string.split(loop, ':') + device = loop.split(loop, ':') utils.execute('sudo', 'losetup', '--detach', device) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 9bfd3f841..9e0d0538d 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -275,7 +275,7 @@ class LibvirtConnection(object): LOG.info(_('instance %(instance_name)s: deleting instance files' ' %(target)s') % locals()) if FLAGS.libvirt_type == 'lxc': - disk.destroy_container(target, instance, nbd=FLAGS.use_cow_images) + disk.destroy_container(target, instance) if os.path.exists(target): shutil.rmtree(target) -- cgit From 6bd017262a5c61d915ede2e58ef2758f1f190ff3 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Thu, 17 Mar 2011 08:54:05 -0400 Subject: Remove target_partition for setup_container but still hardcode because its needed when you inject the keys into the image. --- nova/virt/libvirt_conn.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 9e0d0538d..0ca27d629 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -700,8 +700,7 @@ class LibvirtConnection(object): if FLAGS.libvirt_type == 'lxc': disk.setup_container(basepath('disk'), container_dir=container_dir, - partition=target_partition, - nbd=FLAGS.use_cow_images) + partition=target_partition) except Exception as e: # This could be a windows image, or a vmdk format disk LOG.warn(_('instance %(inst_name)s: ignoring error injecting' -- cgit From cc716f9648355bc3737dd749a35dc327ebda1e6f Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Thu, 17 Mar 2011 09:15:33 -0400 Subject: Fixed typo when I was trying to add test cases for lxc --- nova/tests/test_virt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 7d50960a3..c2b8ba0a1 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -344,7 +344,7 @@ class LibvirtConnTestCase(test.TestCase): (lambda t: t.find('./memory').text, '2097152')] if rescue: - common_checks = [ + common_checks += [ (lambda t: t.findall('./devices/disk/source')[0].get( 'file').split('/')[1], 'disk.rescue'), (lambda t: t.findall('./devices/disk/source')[1].get( -- cgit From 174d8ca2da7e2e53c9105ccc5e5d9a97bc12c0b8 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Thu, 17 Mar 2011 09:17:42 -0400 Subject: Set nbd to false when mounting the image --- nova/virt/disk.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/virt/disk.py b/nova/virt/disk.py index 90d3cf499..a84425de7 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -121,6 +121,7 @@ def setup_container(image, container_dir=None, partition=None, nbd=False): It will mount the loopback image to the container directory in order to create the root filesystem for the container """ + nbd=False device = _link_device(image, nbd) err = utils.execute('sudo', 'mount', device, container_dir) if err: -- cgit From bc0ef2c7aead759504eedcb4e2ab6d96dba7c266 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Thu, 17 Mar 2011 10:02:40 -0400 Subject: Fix up setup container --- nova/virt/disk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/disk.py b/nova/virt/disk.py index a84425de7..2fa7819d7 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -115,7 +115,7 @@ def inject_data(image, key=None, net=None, partition=None, nbd=False): _unlink_device(device, nbd) -def setup_container(image, container_dir=None, partition=None, nbd=False): +def setup_container(image, container_dir=None, partition=None): """Setup the LXC container It will mount the loopback image to the container directory in order -- cgit From 36285d3acb940c39dc1827699c1e3c0cc9846529 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Thu, 17 Mar 2011 10:22:57 -0400 Subject: Fix more pep8 errors --- nova/tests/test_virt.py | 12 ++++++------ nova/virt/disk.py | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index c2b8ba0a1..222975adc 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -367,14 +367,14 @@ class LibvirtConnTestCase(test.TestCase): xml = conn.to_xml(instance_ref, rescue) tree = xml_to_tree(xml) for i, (check, expected_result) in enumerate(checks): - self.assertEqual(check(tree), - expected_result, - '%s failed check %d' % (xml, i)) + self.assertEqual(check(tree), + expected_result, + '%s failed check %d' % (xml, i)) for i, (check, expected_result) in enumerate(common_checks): - self.assertEqual(check(tree), - expected_result, - '%s failed common check %d' % (xml, i)) + self.assertEqual(check(tree), + expected_result, + '%s failed common check %d' % (xml, i)) # This test is supposed to make sure we don't # override a specifically set uri diff --git a/nova/virt/disk.py b/nova/virt/disk.py index 2fa7819d7..6c5f126bd 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -121,7 +121,7 @@ def setup_container(image, container_dir=None, partition=None): It will mount the loopback image to the container directory in order to create the root filesystem for the container """ - nbd=False + nbd = False device = _link_device(image, nbd) err = utils.execute('sudo', 'mount', device, container_dir) if err: @@ -132,7 +132,7 @@ def setup_container(image, container_dir=None, partition=None): def destroy_container(target, instance): """Destroy the container once it terminates - + It will umount the container that is mounted, try to find the loopback device associated with the container and delete it. """ -- cgit From a06203c66af05c96c161b80511f4a6607ffe4905 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Thu, 17 Mar 2011 10:41:55 -0400 Subject: Fix up tests --- nova/tests/test_virt.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 222975adc..fefc11f0d 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -239,8 +239,8 @@ class LibvirtConnTestCase(test.TestCase): network_ref = db.project_get_network(context.get_admin_context(), self.project.id) - fixed_ip = {'address': self.test_ip, - 'network_id': network_ref['id']} + fixed_ip = {'address': self.test_ip, + 'network_id': network_ref['id']} ctxt = context.get_admin_context() fixed_ip_ref = db.fixed_ip_create(ctxt, fixed_ip) @@ -259,13 +259,13 @@ class LibvirtConnTestCase(test.TestCase): check = [ (lambda t: t.find('.').get('type'), 'lxc'), - (lambda t: t.find('./os/type').text, 'exe'), + (lambda t: t.find('./os/type').text, 'exe') ] for i, (check, expected_result) in enumerate(check): - self.assertEqual(check(tree), - expected_result, - '%s failed common check %d' % (xml, i)) + self.assertEqual(check(tree), + expected_result, + '%s failed common check %d' % (xml, i)) def _check_xml_and_uri(self, instance, expect_ramdisk, expect_kernel, rescue=False): @@ -342,7 +342,6 @@ class LibvirtConnTestCase(test.TestCase): (lambda t: t.find('./devices/serial/source').get( 'path').split('/')[1], 'console.log'), (lambda t: t.find('./memory').text, '2097152')] - if rescue: common_checks += [ (lambda t: t.findall('./devices/disk/source')[0].get( -- cgit From dee8a59b5d575a0327464e27115d0d870cde97be Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Thu, 17 Mar 2011 10:43:48 -0400 Subject: more pep8 fixes --- nova/tests/test_virt.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index fefc11f0d..2510525fc 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -263,9 +263,9 @@ class LibvirtConnTestCase(test.TestCase): ] for i, (check, expected_result) in enumerate(check): - self.assertEqual(check(tree), - expected_result, - '%s failed common check %d' % (xml, i)) + self.assertEqual(check(tree), + expected_result, + '%s failed common check %d' % (xml, i)) def _check_xml_and_uri(self, instance, expect_ramdisk, expect_kernel, rescue=False): -- cgit From 4364a158fd31bdfcfa3ae835a2fd9c0f47d3632f Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Thu, 17 Mar 2011 10:45:31 -0400 Subject: pep8 fixes --- nova/tests/test_virt.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 2510525fc..45f98fcde 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -259,8 +259,7 @@ class LibvirtConnTestCase(test.TestCase): check = [ (lambda t: t.find('.').get('type'), 'lxc'), - (lambda t: t.find('./os/type').text, 'exe') - ] + (lambda t: t.find('./os/type').text, 'exe')] for i, (check, expected_result) in enumerate(check): self.assertEqual(check(tree), -- cgit From b0e3b8e58a925ebf52fa741883f757ed2bc4383c Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Thu, 17 Mar 2011 10:47:19 -0400 Subject: more pep8 fixes --- nova/virt/disk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/disk.py b/nova/virt/disk.py index 6c5f126bd..f6e6795d6 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -132,7 +132,7 @@ def setup_container(image, container_dir=None, partition=None): def destroy_container(target, instance): """Destroy the container once it terminates - + It will umount the container that is mounted, try to find the loopback device associated with the container and delete it. """ -- cgit From af67fba36436feeede4dcc5720e51d2b66c3094a Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 17 Mar 2011 22:30:34 -0400 Subject: Images now v1.1 supported...mostly. --- nova/api/openstack/__init__.py | 11 +- nova/api/openstack/images.py | 214 ++++++++++++++------------------ nova/api/openstack/views/images.py | 63 ++++++++-- nova/tests/api/openstack/test_images.py | 171 +++++++++++++++++++------ 4 files changed, 285 insertions(+), 174 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 0b50d17d0..0ac67fdba 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -113,8 +113,6 @@ class APIRouter(wsgi.Router): parent_resource=dict(member_name='server', collection_name='servers')) - mapper.resource("image", "images", controller=images.Controller(), - collection={'detail': 'GET'}) mapper.resource("flavor", "flavors", controller=flavors.Controller(), collection={'detail': 'GET'}) mapper.resource("shared_ip_group", "shared_ip_groups", @@ -130,6 +128,10 @@ class APIRouterV10(APIRouter): collection={'detail': 'GET'}, member=self.server_members) + mapper.resource("image", "images", + controller=images.Controller_v1_0(), + collection={'detail': 'GET'}) + class APIRouterV11(APIRouter): def _setup_routes(self, mapper): @@ -139,6 +141,11 @@ class APIRouterV11(APIRouter): collection={'detail': 'GET'}, member=self.server_members) + mapper.resource("image", "images", + controller=images.Controller_v1_1(), + collection={'detail': 'GET'}) + + class Versions(wsgi.Application): @webob.dec.wsgify(RequestClass=wsgi.Request) diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 98f0dd96b..2357bfd3d 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -1,6 +1,4 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 OpenStack LLC. +# Copyright 2011 OpenStack LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -19,92 +17,19 @@ from webob import exc from nova import compute from nova import flags +from nova import log from nova import utils from nova import wsgi -import nova.api.openstack -from nova.api.openstack import common -from nova.api.openstack import faults -import nova.image.service - - -FLAGS = flags.FLAGS - - -def _translate_keys(item): - """ - Maps key names to Rackspace-like attributes for return - also pares down attributes to those we want - item is a dict - - Note: should be removed when the set of keys expected by the api - and the set of keys returned by the image service are equivalent - - """ - # TODO(tr3buchet): this map is specific to s3 object store, - # replace with a list of keys for _filter_keys later - mapped_keys = {'status': 'imageState', - 'id': 'imageId', - 'name': 'imageLocation'} - - mapped_item = {} - # TODO(tr3buchet): - # this chunk of code works with s3 and the local image service/glance - # when we switch to glance/local image service it can be replaced with - # a call to _filter_keys, and mapped_keys can be changed to a list - try: - for k, v in mapped_keys.iteritems(): - # map s3 fields - mapped_item[k] = item[v] - except KeyError: - # return only the fields api expects - mapped_item = _filter_keys(item, mapped_keys.keys()) - - return mapped_item - - -def _translate_status(item): - """ - Translates status of image to match current Rackspace api bindings - item is a dict +from nova.api.openstack.views import images as images_view - Note: should be removed when the set of statuses expected by the api - and the set of statuses returned by the image service are equivalent +class Controller(wsgi.Controller): """ - status_mapping = { - 'pending': 'queued', - 'decrypting': 'preparing', - 'untarring': 'saving', - 'available': 'active'} - try: - item['status'] = status_mapping[item['status']] - except KeyError: - # TODO(sirp): Performing translation of status (if necessary) here for - # now. Perhaps this should really be done in EC2 API and - # S3ImageService - pass - - return item - - -def _filter_keys(item, keys): - """ - Filters all model attributes except for keys - item is a dict - + Base `wsgi.Controller` for retrieving and displaying images in the + OpenStack API. Version-inspecific code goes here. """ - return dict((k, v) for k, v in item.iteritems() if k in keys) - - -def _convert_image_id_to_hash(image): - if 'imageId' in image: - # Convert EC2-style ID (i-blah) to Rackspace-style (int) - image_id = abs(hash(image['imageId'])) - image['imageId'] = image_id - image['id'] = image_id - -class Controller(wsgi.Controller): + _builder = images_view.Builder_v1_0() _serialization_metadata = { 'application/xml': { @@ -112,55 +37,96 @@ class Controller(wsgi.Controller): "image": ["id", "name", "updated", "created", "status", "serverId", "progress"]}}} - def __init__(self): - self._service = utils.import_object(FLAGS.image_service) + def __init__(self, image_service=None, compute_service=None): + """ + Initialize new `ImageController`. + + @param compute_service: `nova.compute.api:API` + @param image_service: `nova.image.service:BaseImageService` + """ + _default_service = utils.import_object(flags.FLAGS.image_service) + + self.__compute = compute_service or compute.API() + self.__image = image_service or _default_service + self.__log = log.getLogger(self.__class__.__name__) def index(self, req): - """Return all public images in brief""" - items = self._service.index(req.environ['nova.context']) - items = common.limited(items, req) - items = [_filter_keys(item, ('id', 'name')) for item in items] - return dict(images=items) + """ + Return an index listing of images available to the request. + + @param req: `webob.Request` object + """ + context = req.environ['nova.context'] + images = self.__image.index(context) + build = self._builder.build + return dict(images=[build(req, image, False) for image in images]) def detail(self, req): - """Return all public images in detail""" - try: - items = self._service.detail(req.environ['nova.context']) - except NotImplementedError: - items = self._service.index(req.environ['nova.context']) - for image in items: - _convert_image_id_to_hash(image) - - items = common.limited(items, req) - items = [_translate_keys(item) for item in items] - items = [_translate_status(item) for item in items] - return dict(images=items) - - def show(self, req, id): - """Return data about the given image id""" - image_id = common.get_image_id_from_image_hash(self._service, - req.environ['nova.context'], id) - - image = self._service.show(req.environ['nova.context'], image_id) - _convert_image_id_to_hash(image) - return dict(image=image) - - def delete(self, req, id): - # Only public images are supported for now. - raise faults.Fault(exc.HTTPNotFound()) + """ + Return a detailed index listing of images available to the request. + + @param req: `webob.Request` object. + """ + context = req.environ['nova.context'] + images = self.__image.detail(context) + build = self._builder.build + return dict(images=[build(req, image, True) for image in images]) + + def show(self, req, image_id): + """ + Return detailed information about a specific image. + + @param req: `webob.Request` object + @param image_id: Image identifier (integer) + """ + context = req.environ['nova.context'] + image = self.__image.show(context, image_id) + return self._builder.build(req, image, True) + + def delete(self, req, image_id): + """ + Delete an image, if allowed. + + @param req: `webob.Request` object + @param image_id: Image identifier (integer) + """ + context = req.environ['nova.context'] + self.__image.delete(context, image_id) + return exc.HTTPNoContent() def create(self, req): + """ + Snapshot a server instance and save the image. + + @param req: `webob.Request` object + """ context = req.environ['nova.context'] - env = self._deserialize(req.body, req.get_content_type()) - instance_id = env["image"]["serverId"] - name = env["image"]["name"] + body = req.body + content_type = req.get_content_type() + image = self._deserialize(body, content_type) + + if not image: + raise exc.HTTPBadRequest() + + try: + server_id = image["serverId"] + image_name = image["name"] + except KeyError: + raise exc.HTTPBadRequest() + + image = self.__compute.snapshot(context, server_id, image_name) + return self._builder.build(req, image, True) - image_meta = compute.API().snapshot( - context, instance_id, name) - return dict(image=image_meta) +class Controller_v1_0(Controller): + """ + Version 1.0 specific controller logic. + """ + _builder = images_view.Builder_v1_0() + - def update(self, req, id): - # Users may not modify public images, and that's all that - # we support for now. - raise faults.Fault(exc.HTTPNotFound()) +class Controller_v1_1(Controller): + """ + Version 1.1 specific controller logic. + """ + _builder = images_view.Builder_v1_1() diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index a6c6ad7d1..1631d1fe3 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -15,20 +15,61 @@ # License for the specific language governing permissions and limitations # under the License. -from nova.api.openstack import common +class Builder(object): + """ + Base class for generating responses to OpenStack API requests for + information about images. + """ -class ViewBuilder(object): - def __init__(self): - pass + def build(self, request, image_obj, detail=False): + """ + Return a standardized image structure for display by the API. + """ + image = { + "id": image_obj["id"], + "name": image_obj["name"], + } - def build(self, image_obj): - raise NotImplementedError() + if detail: + image.update({ + "created": image_obj["created_at"], + "updated": image_obj["updated_at"], + "status": image_obj["status"], + }) + return image -class ViewBuilderV11(ViewBuilder): - def __init__(self, base_url): - self.base_url = base_url - def generate_href(self, image_id): - return "%s/images/%s" % (self.base_url, image_id) +class Builder_v1_0(Builder): + pass + + +class Builder_v1_1(Builder): + """ + OpenStack API v1.1 Image Builder + """ + + def build(self, request, image_obj, detail=False): + """ + Return a standardized image structure for display by the API. + """ + image = Builder.build(self, request, image_obj, detail) + href = "%s/images/%s" % (request.application_url, image_obj["id"]) + + image["links"] = [{ + "rel": "self", + "href": href, + }, + { + "rel": "bookmark", + "type": "application/json", + "href": href, + }, + { + "rel": "bookmark", + "type": "application/xml", + "href": href, + }] + + return image diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 76f758929..c313192b7 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -42,8 +42,9 @@ FLAGS = flags.FLAGS class BaseImageServiceTests(object): - - """Tasks to test for all image services""" + """ + Tasks to test for all image services. + """ def test_create(self): @@ -173,10 +174,9 @@ class GlanceImageServiceTest(test.TestCase, class ImageControllerWithGlanceServiceTest(test.TestCase): - - """Test of the OpenStack API /images application controller""" - - # Registered images at start of each test. + """ + Test of the OpenStack API /images application controller w/Glance. + """ IMAGE_FIXTURES = [ {'id': '23g2ogk23k4hhkk4k42l', @@ -198,7 +198,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'deleted': False, 'is_public': True, 'status': 'available', - 'image_type': 'ramdisk'}] + 'image_type': 'ramdisk'}, + ] def setUp(self): super(ImageControllerWithGlanceServiceTest, self).setUp() @@ -219,36 +220,132 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): super(ImageControllerWithGlanceServiceTest, self).tearDown() def test_get_image_index(self): - req = webob.Request.blank('/v1.0/images') - res = req.get_response(fakes.wsgi_app()) - res_dict = json.loads(res.body) + request = webob.Request.blank('/v1.0/images') + response = request.get_response(fakes.wsgi_app()) + + response_dict = json.loads(response.body) + response_list = response_dict["images"] + + for image in self.IMAGE_FIXTURES: + test_image = { + "id": image["id"], + "name": image["name"], + } + self.assertTrue(test_image in response_list) + + self.assertEqual(len(response_list), len(self.IMAGE_FIXTURES)) + + def test_get_image_index_v1_1(self): + request = webob.Request.blank('/v1.1/images') + response = request.get_response(fakes.wsgi_app()) + + response_dict = json.loads(response.body) + response_list = response_dict["images"] + + for image in self.IMAGE_FIXTURES: + href = "http://localhost/v1.1/images/%s" % image["id"] + test_image = { + "id": image["id"], + "name": image["name"], + "links": [{ + "rel": "self", + "href": "http://localhost/v1.1/images/%s" % image["id"], + }, + { + "rel": "bookmark", + "type": "application/json", + "href": href, + }, + { + "rel": "bookmark", + "type": "application/xml", + "href": href, + }], + } + print test_image + print + print response_list + self.assertTrue(test_image in response_list) + + self.assertEqual(len(response_list), len(self.IMAGE_FIXTURES)) - fixture_index = [dict(id=f['id'], name=f['name']) for f - in self.IMAGE_FIXTURES] + def test_get_image_details(self): + request = webob.Request.blank('/v1.0/images/detail') + response = request.get_response(fakes.wsgi_app()) + + response_dict = json.loads(response.body) + response_list = response_dict["images"] + + for image in self.IMAGE_FIXTURES: + test_image = { + "id": image["id"], + "name": image["name"], + "updated": image["updated_at"], + "created": image["created_at"], + "status": image["status"], + } + self.assertTrue(test_image in response_list) + + self.assertEqual(len(response_list), len(self.IMAGE_FIXTURES)) + + def test_get_image_details_v1_1(self): + request = webob.Request.blank('/v1.1/images/detail') + response = request.get_response(fakes.wsgi_app()) + + response_dict = json.loads(response.body) + response_list = response_dict["images"] + + for image in self.IMAGE_FIXTURES: + href = "http://localhost/v1.1/images/%s" % image["id"] + test_image = { + "id": image["id"], + "name": image["name"], + "updated": image["updated_at"], + "created": image["created_at"], + "status": image["status"], + "links": [{ + "rel": "self", + "href": href, + }, + { + "rel": "bookmark", + "type": "application/json", + "href": href, + }, + { + "rel": "bookmark", + "type": "application/xml", + "href": href, + }], + } + self.assertTrue(test_image in response_list) + + self.assertEqual(len(response_list), len(self.IMAGE_FIXTURES)) + + def test_get_image_create_empty(self): + request = webob.Request.blank('/v1.1/images') + request.method = "POST" + response = request.get_response(fakes.wsgi_app()) + self.assertEqual(400, response.status_int) + + def test_get_image_create_bad_no_name(self): + request = webob.Request.blank('/v1.1/images') + request.method = "POST" + request.content_type = "application/json" + request.body = json.dumps({ + "serverId": 1, + }) + response = request.get_response(fakes.wsgi_app()) + self.assertEqual(400, response.status_int) + + def test_get_image_create_bad_no_id(self): + request = webob.Request.blank('/v1.1/images') + request.method = "POST" + request.content_type = "application/json" + request.body = json.dumps({ + "name" : "Snapshot Test", + }) + response = request.get_response(fakes.wsgi_app()) + self.assertEqual(400, response.status_int) - for image in res_dict['images']: - self.assertEquals(1, fixture_index.count(image), - "image %s not in fixture index!" % str(image)) - def test_get_image_details(self): - req = webob.Request.blank('/v1.0/images/detail') - res = req.get_response(fakes.wsgi_app()) - res_dict = json.loads(res.body) - - def _is_equivalent_subset(x, y): - if set(x) <= set(y): - for k, v in x.iteritems(): - if x[k] != y[k]: - if x[k] == 'active' and y[k] == 'available': - continue - return False - return True - return False - - for image in res_dict['images']: - for image_fixture in self.IMAGE_FIXTURES: - if _is_equivalent_subset(image, image_fixture): - break - else: - self.assertEquals(1, 2, "image %s not in fixtures!" % - str(image)) -- cgit From febbfd45a0c1dbd16093ab38897e94ddb331e9ea Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 17 Mar 2011 23:34:12 -0400 Subject: Updated naming, removed some prints, and removed some invalid tests. --- nova/api/openstack/__init__.py | 4 ++-- nova/api/openstack/images.py | 20 +++++++++++--------- nova/api/openstack/views/images.py | 8 ++++---- nova/tests/api/openstack/test_images.py | 31 ------------------------------- 4 files changed, 17 insertions(+), 46 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 0ac67fdba..1ec943f55 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -129,7 +129,7 @@ class APIRouterV10(APIRouter): member=self.server_members) mapper.resource("image", "images", - controller=images.Controller_v1_0(), + controller=images.ControllerV10(), collection={'detail': 'GET'}) @@ -142,7 +142,7 @@ class APIRouterV11(APIRouter): member=self.server_members) mapper.resource("image", "images", - controller=images.Controller_v1_1(), + controller=images.ControllerV11(), collection={'detail': 'GET'}) diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 2357bfd3d..bc7338699 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -29,13 +29,15 @@ class Controller(wsgi.Controller): OpenStack API. Version-inspecific code goes here. """ - _builder = images_view.Builder_v1_0() - _serialization_metadata = { 'application/xml': { "attributes": { "image": ["id", "name", "updated", "created", "status", - "serverId", "progress"]}}} + "serverId", "progress"], + "link": ["rel", "type", "href"], + }, + }, + } def __init__(self, image_service=None, compute_service=None): """ @@ -109,8 +111,8 @@ class Controller(wsgi.Controller): raise exc.HTTPBadRequest() try: - server_id = image["serverId"] - image_name = image["name"] + server_id = image["image"]["serverId"] + image_name = image["image"]["name"] except KeyError: raise exc.HTTPBadRequest() @@ -118,15 +120,15 @@ class Controller(wsgi.Controller): return self._builder.build(req, image, True) -class Controller_v1_0(Controller): +class ControllerV10(Controller): """ Version 1.0 specific controller logic. """ - _builder = images_view.Builder_v1_0() + _builder = images_view.ViewBuilderV10() -class Controller_v1_1(Controller): +class ControllerV11(Controller): """ Version 1.1 specific controller logic. """ - _builder = images_view.Builder_v1_1() + _builder = images_view.ViewBuilderV11() diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index 1631d1fe3..c41e60546 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -16,7 +16,7 @@ # under the License. -class Builder(object): +class ViewBuilder(object): """ Base class for generating responses to OpenStack API requests for information about images. @@ -41,11 +41,11 @@ class Builder(object): return image -class Builder_v1_0(Builder): +class ViewBuilderV10(ViewBuilder): pass -class Builder_v1_1(Builder): +class ViewBuilderV11(ViewBuilder): """ OpenStack API v1.1 Image Builder """ @@ -54,7 +54,7 @@ class Builder_v1_1(Builder): """ Return a standardized image structure for display by the API. """ - image = Builder.build(self, request, image_obj, detail) + image = ViewBuilder.build(self, request, image_obj, detail) href = "%s/images/%s" % (request.application_url, image_obj["id"]) image["links"] = [{ diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index c313192b7..c5a866bc7 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -262,9 +262,6 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): "href": href, }], } - print test_image - print - print response_list self.assertTrue(test_image in response_list) self.assertEqual(len(response_list), len(self.IMAGE_FIXTURES)) @@ -321,31 +318,3 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): self.assertTrue(test_image in response_list) self.assertEqual(len(response_list), len(self.IMAGE_FIXTURES)) - - def test_get_image_create_empty(self): - request = webob.Request.blank('/v1.1/images') - request.method = "POST" - response = request.get_response(fakes.wsgi_app()) - self.assertEqual(400, response.status_int) - - def test_get_image_create_bad_no_name(self): - request = webob.Request.blank('/v1.1/images') - request.method = "POST" - request.content_type = "application/json" - request.body = json.dumps({ - "serverId": 1, - }) - response = request.get_response(fakes.wsgi_app()) - self.assertEqual(400, response.status_int) - - def test_get_image_create_bad_no_id(self): - request = webob.Request.blank('/v1.1/images') - request.method = "POST" - request.content_type = "application/json" - request.body = json.dumps({ - "name" : "Snapshot Test", - }) - response = request.get_response(fakes.wsgi_app()) - self.assertEqual(400, response.status_int) - - -- cgit From fce6b6f8f39378f5f31b8aa432922374c744544e Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Fri, 18 Mar 2011 00:05:58 -0400 Subject: Become compatible with ironcamel and bcwaldon's implementations for standardness. --- nova/api/openstack/images.py | 31 +++++++++++++++++++++++-------- nova/api/openstack/views/images.py | 18 +++++++++++++++--- 2 files changed, 38 insertions(+), 11 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index bc7338699..a192883fc 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -17,9 +17,9 @@ from webob import exc from nova import compute from nova import flags -from nova import log from nova import utils from nova import wsgi +from nova.api.openstack import common from nova.api.openstack.views import images as images_view @@ -39,6 +39,11 @@ class Controller(wsgi.Controller): }, } + _builder_dispatch = { + "1.0": images_view.ViewBuilderV10, + "1.1": images_view.ViewBuilderV11, + } + def __init__(self, image_service=None, compute_service=None): """ Initialize new `ImageController`. @@ -50,7 +55,17 @@ class Controller(wsgi.Controller): self.__compute = compute_service or compute.API() self.__image = image_service or _default_service - self.__log = log.getLogger(self.__class__.__name__) + + def get_builder(self, request): + """ + Property to get the ViewBuilder class we need to use. + """ + version = common.get_api_version(request) + base_url = request.application_url + try: + return self._builder_dispatch[version](base_url) + except KeyError: + raise exc.HTTPNotFound() def index(self, req): """ @@ -60,7 +75,7 @@ class Controller(wsgi.Controller): """ context = req.environ['nova.context'] images = self.__image.index(context) - build = self._builder.build + build = self.get_builder(req).build return dict(images=[build(req, image, False) for image in images]) def detail(self, req): @@ -71,7 +86,7 @@ class Controller(wsgi.Controller): """ context = req.environ['nova.context'] images = self.__image.detail(context) - build = self._builder.build + build = self.get_builder(req).build return dict(images=[build(req, image, True) for image in images]) def show(self, req, image_id): @@ -83,7 +98,7 @@ class Controller(wsgi.Controller): """ context = req.environ['nova.context'] image = self.__image.show(context, image_id) - return self._builder.build(req, image, True) + return self.get_builder(req).build(req, image, True) def delete(self, req, image_id): """ @@ -117,18 +132,18 @@ class Controller(wsgi.Controller): raise exc.HTTPBadRequest() image = self.__compute.snapshot(context, server_id, image_name) - return self._builder.build(req, image, True) + return self.get_builder(req).build(req, image, True) class ControllerV10(Controller): """ Version 1.0 specific controller logic. """ - _builder = images_view.ViewBuilderV10() + pass class ControllerV11(Controller): """ Version 1.1 specific controller logic. """ - _builder = images_view.ViewBuilderV11() + pass diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index c41e60546..313ba2eba 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -22,7 +22,19 @@ class ViewBuilder(object): information about images. """ - def build(self, request, image_obj, detail=False): + def __init__(self, base_url): + """ + Initialize new `ViewBuilder`. + """ + self._url = base_url + + def generate_href(self, image_id): + """ + Return an href string pointing to this object. + """ + return "%s/images/%s" % (self._url, image_id) + + def build(self, image_obj, detail=False): """ Return a standardized image structure for display by the API. """ @@ -50,12 +62,12 @@ class ViewBuilderV11(ViewBuilder): OpenStack API v1.1 Image Builder """ - def build(self, request, image_obj, detail=False): + def build(self, image_obj, detail=False): """ Return a standardized image structure for display by the API. """ image = ViewBuilder.build(self, request, image_obj, detail) - href = "%s/images/%s" % (request.application_url, image_obj["id"]) + href = self.generate_url(image_obj["id"]) image["links"] = [{ "rel": "self", -- cgit From c28ec048a56a3ead96dc7528ca50865945d40646 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Fri, 18 Mar 2011 00:19:53 -0400 Subject: Final touches and bug/pep8 fixes. --- nova/api/openstack/__init__.py | 1 - nova/api/openstack/images.py | 35 ++++++++++++++++++----------------- nova/api/openstack/servers.py | 1 + nova/api/openstack/views/images.py | 4 ++-- 4 files changed, 21 insertions(+), 20 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 1ec943f55..14cb6b331 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -146,7 +146,6 @@ class APIRouterV11(APIRouter): collection={'detail': 'GET'}) - class Versions(wsgi.Application): @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index a192883fc..4cd989054 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -56,17 +56,6 @@ class Controller(wsgi.Controller): self.__compute = compute_service or compute.API() self.__image = image_service or _default_service - def get_builder(self, request): - """ - Property to get the ViewBuilder class we need to use. - """ - version = common.get_api_version(request) - base_url = request.application_url - try: - return self._builder_dispatch[version](base_url) - except KeyError: - raise exc.HTTPNotFound() - def index(self, req): """ Return an index listing of images available to the request. @@ -76,7 +65,7 @@ class Controller(wsgi.Controller): context = req.environ['nova.context'] images = self.__image.index(context) build = self.get_builder(req).build - return dict(images=[build(req, image, False) for image in images]) + return dict(images=[build(image, False) for image in images]) def detail(self, req): """ @@ -87,7 +76,7 @@ class Controller(wsgi.Controller): context = req.environ['nova.context'] images = self.__image.detail(context) build = self.get_builder(req).build - return dict(images=[build(req, image, True) for image in images]) + return dict(images=[build(image, True) for image in images]) def show(self, req, image_id): """ @@ -98,7 +87,7 @@ class Controller(wsgi.Controller): """ context = req.environ['nova.context'] image = self.__image.show(context, image_id) - return self.get_builder(req).build(req, image, True) + return self.get_builder().build(req, image, True) def delete(self, req, image_id): """ @@ -132,18 +121,30 @@ class Controller(wsgi.Controller): raise exc.HTTPBadRequest() image = self.__compute.snapshot(context, server_id, image_name) - return self.get_builder(req).build(req, image, True) + return self.get_builder(req).build(image, True) class ControllerV10(Controller): """ Version 1.0 specific controller logic. """ - pass + + def get_builder(self, request): + """ + Property to get the ViewBuilder class we need to use. + """ + base_url = request.application_url + return images_view.ViewBuilderV10(base_url) class ControllerV11(Controller): """ Version 1.1 specific controller logic. """ - pass + + 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) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 5f6fbd96c..73843f63e 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -516,6 +516,7 @@ class Controller(wsgi.Controller): return kernel_id, ramdisk_id + class ControllerV10(Controller): def _image_id_from_req_data(self, data): return data['server']['imageId'] diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index 313ba2eba..7daa6fe26 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -66,8 +66,8 @@ class ViewBuilderV11(ViewBuilder): """ Return a standardized image structure for display by the API. """ - image = ViewBuilder.build(self, request, image_obj, detail) - href = self.generate_url(image_obj["id"]) + image = ViewBuilder.build(self, image_obj, detail) + href = self.generate_href(image_obj["id"]) image["links"] = [{ "rel": "self", -- cgit From 4ba57654ca03d687da3b994c127665c7118ab9a5 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 22 Mar 2011 13:26:23 -0700 Subject: intermediate progress on vnc-nova integration. checking in to show vish. --- nova/flags.py | 4 ++++ nova/virt/libvirt.xml.template | 4 +++- nova/virt/libvirt_conn.py | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/flags.py b/nova/flags.py index 4f2be82b6..0360b1e3a 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -287,6 +287,10 @@ DEFINE_string('vnc_console_proxy_url', 'http://127.0.0.1:6080', 'location of vnc console proxy, \ in the form "http://127.0.0.1:6080"') +DEFINE_string('vnc_host_iface', '0.0.0.0', + 'the compute host interface on which vnc server should listen') +DEFINE_bool('vnc_enabled', True, + 'enable vnc related features') DEFINE_bool('verbose', False, 'show debug output') DEFINE_boolean('fake_rabbit', False, 'use a fake rabbit') DEFINE_bool('fake_network', False, diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template index 7b4c23211..037cd0902 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -101,6 +101,8 @@ - +#if $getVar('vnc_host_iface', False) + +#end if diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 4fca84639..51f263ce9 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -734,6 +734,8 @@ class LibvirtConnection(object): 'local': instance_type['local_gb'], 'driver_type': driver_type} + if FLAGS.vnc_enabled: + xml_info['vnc_host_iface'] = FLAGS.vnc_host_iface if ra_server: xml_info['ra_server'] = ra_server + "/128" if not rescue: -- cgit From 92f076c981343e09c240533acf49a6fdd0384555 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 22 Mar 2011 16:39:04 -0700 Subject: Set XML namespace when returning XML --- nova/wsgi.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'nova') diff --git a/nova/wsgi.py b/nova/wsgi.py index ba0819466..f0a60582d 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -478,6 +478,9 @@ class Serializer(object): root_key = data.keys()[0] doc = minidom.Document() node = self._to_xml_node(doc, metadata, root_key, data[root_key]) + node.setAttribute('xmlns', + 'http://docs.rackspacecloud.com/servers/api/v1.0') + return node.toprettyxml(indent=' ') def _to_xml_node(self, doc, metadata, nodename, data): -- cgit From 5c31b423ba5b5347aac62559c4e5c0a02f264213 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 22 Mar 2011 16:59:03 -0700 Subject: Support setting the xmlns intelligently --- nova/wsgi.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/wsgi.py b/nova/wsgi.py index f0a60582d..5d286bb3b 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -355,7 +355,8 @@ class Controller(object): if type(result) is dict: content_type = req.best_match_content_type() - body = self._serialize(result, content_type) + default_xmlns = self.get_default_xmlns(req) + body = self._serialize(result, content_type, default_xmlns) response = webob.Response() response.headers["Content-Type"] = content_type @@ -365,14 +366,15 @@ class Controller(object): else: return result - def _serialize(self, data, content_type): + def _serialize(self, data, content_type, default_xmlns): """ Serialize the given dict to the provided content_type. Uses self._serialization_metadata if it exists, which is a dict mapping MIME types to information needed to serialize to that type. """ _metadata = getattr(type(self), "_serialization_metadata", {}) - serializer = Serializer(_metadata) + + serializer = Serializer(_metadata, default_xmlns) try: return serializer.serialize(data, content_type) except exception.InvalidContentType: @@ -388,19 +390,23 @@ class Controller(object): serializer = Serializer(_metadata) return serializer.deserialize(data, content_type) + def get_default_xmlns(self, req): + return 'http://docs.rackspacecloud.com/servers/api/v1.0' + class Serializer(object): """ Serializes and deserializes dictionaries to certain MIME types. """ - def __init__(self, metadata=None): + def __init__(self, metadata=None, default_xmlns=None): """ Create a serializer based on the given WSGI environment. 'metadata' is an optional dict mapping MIME types to information needed to serialize a dictionary to that type. """ self.metadata = metadata or {} + self.default_xmlns = default_xmlns def _get_serialize_handler(self, content_type): handlers = { @@ -478,14 +484,23 @@ class Serializer(object): root_key = data.keys()[0] doc = minidom.Document() node = self._to_xml_node(doc, metadata, root_key, data[root_key]) - node.setAttribute('xmlns', - 'http://docs.rackspacecloud.com/servers/api/v1.0') + + xmlns = node.getAttribute('xmlns') + if not xmlns and self.default_xmlns: + node.setAttribute('xmlns', self.default_xmlns) return node.toprettyxml(indent=' ') def _to_xml_node(self, doc, metadata, nodename, data): """Recursive method to convert data members to XML nodes.""" result = doc.createElement(nodename) + + # Set the xml namespace if one is specified + # TODO(justinsb): We could also use prefixes on the keys + xmlns = metadata.get('xmlns', None) + if xmlns: + result.setAttribute('xmlns', xmlns) + if type(data) is list: singular = metadata.get('plurals', {}).get(nodename, None) if singular is None: -- cgit From 45d28dfb035b4e219845d44e00073d70211e8175 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 22 Mar 2011 21:14:26 -0700 Subject: Fixed up unit tests and direct api that was also calling _serialize (naughty!) --- nova/api/direct.py | 4 +++- nova/tests/api/openstack/test_limits.py | 16 ++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/api/direct.py b/nova/api/direct.py index dfca250e0..153871e9f 100644 --- a/nova/api/direct.py +++ b/nova/api/direct.py @@ -206,7 +206,9 @@ class ServiceWrapper(wsgi.Controller): params = dict([(str(k), v) for (k, v) in params.iteritems()]) result = method(context, **params) if type(result) is dict or type(result) is list: - return self._serialize(result, req.best_match_content_type()) + content_type = req.best_match_content_type() + default_xmlns = self.get_default_xmlns(req) + return self._serialize(result, content_type, default_xmlns) else: return result diff --git a/nova/tests/api/openstack/test_limits.py b/nova/tests/api/openstack/test_limits.py index 05cfacc60..df367005d 100644 --- a/nova/tests/api/openstack/test_limits.py +++ b/nova/tests/api/openstack/test_limits.py @@ -136,10 +136,17 @@ class LimitsControllerTest(BaseLimitTestSuite): request = self._get_index_request("application/xml") response = request.get_response(self.controller) - expected = "" - body = response.body.replace("\n", "").replace(" ", "") + expected = parseString(""" + + + + + """.replace(" ", "")) - self.assertEqual(expected, body) + body = parseString(response.body.replace(" ", "")) + + self.assertEqual(expected.toxml(), body.toxml()) def test_index_xml(self): """Test getting limit details in XML.""" @@ -148,7 +155,8 @@ class LimitsControllerTest(BaseLimitTestSuite): response = request.get_response(self.controller) expected = parseString(""" - + -- cgit From 9686b3a296c53486a64a949ae2f7430e25df2dcb Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 22 Mar 2011 21:18:31 -0700 Subject: Added note agreeing with Brian Lamar that the namespace doesn't belong in wsgi --- nova/wsgi.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'nova') diff --git a/nova/wsgi.py b/nova/wsgi.py index 5d286bb3b..1bcc08f7f 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -391,6 +391,10 @@ class Controller(object): return serializer.deserialize(data, content_type) def get_default_xmlns(self, req): + # NOTE(justinsb): This doesn't really belong here.. + # We'll probably end up moving this into a new OpenstackApiController + # class or something like that, once we know what's going to happen + # with v1.1 return 'http://docs.rackspacecloud.com/servers/api/v1.0' -- cgit From 9c75878e5f6f1b90695e725d7bc8e6e9002cabbb Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 23 Mar 2011 01:57:38 -0700 Subject: separating out components of vnc console --- nova/vnc/__init__.py | 0 nova/vnc/auth.py | 83 ++++++++++++++++++++++++++++++++++++++ nova/vnc/proxy.py | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 194 insertions(+) create mode 100644 nova/vnc/__init__.py create mode 100644 nova/vnc/auth.py create mode 100644 nova/vnc/proxy.py (limited to 'nova') diff --git a/nova/vnc/__init__.py b/nova/vnc/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/nova/vnc/auth.py b/nova/vnc/auth.py new file mode 100644 index 000000000..2596bdd24 --- /dev/null +++ b/nova/vnc/auth.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# pylint: disable-msg=C0103 +# 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. + +"""Auth Components for VNC Console""" + +import time +from webob import Request +from nova import flags +from nova import log as logging +from nova import rpc +from nova import utils +from nova import wsgi + + +class NovaAuthMiddleware(object): + """Implementation of Middleware to Handle Nova Auth""" + + def __init__(self, app): + self.app = app + self.register_listeners() + + def __call__(self, environ, start_response): + req = Request(environ) + + if req.path == '/data': + token = req.params.get('token') + if not token in self.tokens: + start_response('403 Forbidden', + [('content-type', 'text/html')]) + return 'Not Authorized' + + environ['vnc_host'] = self.tokens[token]['args']['host'] + environ['vnc_port'] = int(self.tokens[token]['args']['port']) + + resp = req.get_response(self.app) + return resp(environ, start_response) + + def register_listeners(self): + middleware = self + middleware.tokens = {} + + class Callback: + def __call__(self, data, message): + if data['method'] == 'authorize_vnc_console': + middleware.tokens[data['args']['token']] = \ + {'args': data['args'], 'last_activity_at': time.time()} + + def delete_expired_tokens(): + now = time.time() + to_delete = [] + for k, v in middleware.tokens.items(): + if now - v['last_activity_at'] > 600: + to_delete.append(k) + + for k in to_delete: + del middleware.tokens[k] + + conn = rpc.Connection.instance(new=True) + consumer = rpc.TopicConsumer( + connection=conn, + topic=FLAGS.vnc_console_proxy_topic) + consumer.register_callback(Callback()) + + utils.LoopingCall(consumer.fetch, auto_ack=True, + enable_callbacks=True).start(0.1) + utils.LoopingCall(delete_expired_tokens).start(1) diff --git a/nova/vnc/proxy.py b/nova/vnc/proxy.py new file mode 100644 index 000000000..3f218e744 --- /dev/null +++ b/nova/vnc/proxy.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python +# pylint: disable-msg=C0103 +# 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. + +"""Eventlet WSGI Services to proxy VNC. No nova deps.""" + +from base64 import b64encode, b64decode +import eventlet +from eventlet import wsgi +from eventlet import websocket +import os +from webob import Request +import webob + + +class WebsocketVNCProxy(object): + """Class to proxy from websocket to vnc server""" + + def __init__(self, wwwroot): + self.wwwroot = wwwroot + + def sock2ws(self, source, dest): + try: + while True: + d = source.recv(32384) + if d == '': + break + d = b64encode(d) + dest.send(d) + except: + source.close() + dest.close() + + def ws2sock(self, source, dest): + try: + while True: + d = source.wait() + if d is None: + break + d = b64decode(d) + dest.sendall(d) + except: + source.close() + dest.close() + + def proxy_connection(self, environ, start_response): + @websocket.WebSocketWSGI + def _handle(client): + server = eventlet.connect((client.environ['vnc_host'], + client.environ['vnc_port'])) + t1 = eventlet.spawn(self.ws2sock, client, server) + t2 = eventlet.spawn(self.sock2ws, server, client) + t1.wait() + t2.wait() + _handle(environ, start_response) + + def serve(self, environ, start_response): + req = Request(environ) + if req.path == '/data': + return self.proxy_connection(environ, start_response) + else: + if req.path == '/': + fname = '/vnc_auto.html' + else: + fname = req.path + + fname = self.wwwroot + fname + + base, ext = os.path.splitext(fname) + if ext == '.js': + mimetype = 'application/javascript' + elif ext == '.css': + mimetype = 'text/css' + elif ext in ['.svg', '.jpg', '.png', '.gif']: + mimetype = 'image' + else: + mimetype = 'text/html' + + start_response('200 OK', [('content-type', mimetype)]) + return open(os.path.join(fname)).read() + + +class DebugMiddleware(object): + """Debug middleware. Skip auth, get vnc port and host from query string""" + + def __init__(self, app): + self.app = app + + def __call__(self, environ, start_response): + req = Request(environ) + if req.path == '/data': + environ['vnc_host'] = req.params.get('host') + environ['vnc_port'] = int(req.params.get('port')) + resp = req.get_response(self.app) + return resp(environ, start_response) -- cgit From e2f085eae874012784e53416f6e6213dcfde4859 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 23 Mar 2011 02:06:16 -0700 Subject: use the nova Server object --- nova/vnc/proxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/vnc/proxy.py b/nova/vnc/proxy.py index 3f218e744..5dc83fcb1 100644 --- a/nova/vnc/proxy.py +++ b/nova/vnc/proxy.py @@ -70,7 +70,7 @@ class WebsocketVNCProxy(object): t2.wait() _handle(environ, start_response) - def serve(self, environ, start_response): + def __call__(self, environ, start_response): req = Request(environ) if req.path == '/data': return self.proxy_connection(environ, start_response) -- cgit From 5cdf8f63fb2dbccea0152d17f00bf80352f8fa1a Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 23 Mar 2011 02:33:11 -0700 Subject: more progress --- nova/vnc/auth.py | 35 +++++++++++++++++++++++++++-------- nova/vnc/proxy.py | 11 +++++------ 2 files changed, 32 insertions(+), 14 deletions(-) (limited to 'nova') diff --git a/nova/vnc/auth.py b/nova/vnc/auth.py index 2596bdd24..9b30b08b8 100644 --- a/nova/vnc/auth.py +++ b/nova/vnc/auth.py @@ -27,6 +27,10 @@ from nova import log as logging from nova import rpc from nova import utils from nova import wsgi +import webob + +LOG = logging.getLogger('nova.vnc-proxy') +FLAGS = flags.FLAGS class NovaAuthMiddleware(object): @@ -36,9 +40,8 @@ class NovaAuthMiddleware(object): self.app = app self.register_listeners() - def __call__(self, environ, start_response): - req = Request(environ) - + @webob.dec.wsgify + def __call__(self, req): if req.path == '/data': token = req.params.get('token') if not token in self.tokens: @@ -46,11 +49,10 @@ class NovaAuthMiddleware(object): [('content-type', 'text/html')]) return 'Not Authorized' - environ['vnc_host'] = self.tokens[token]['args']['host'] - environ['vnc_port'] = int(self.tokens[token]['args']['port']) + req.environ['vnc_host'] = self.tokens[token]['args']['host'] + req.environ['vnc_port'] = int(self.tokens[token]['args']['port']) - resp = req.get_response(self.app) - return resp(environ, start_response) + return req.get_response(self.app) def register_listeners(self): middleware = self @@ -59,7 +61,9 @@ class NovaAuthMiddleware(object): class Callback: def __call__(self, data, message): if data['method'] == 'authorize_vnc_console': - middleware.tokens[data['args']['token']] = \ + token = data['args']['token'] + LOG.info(_("Received Token: %s)"), token) + middleware.tokens[token] = \ {'args': data['args'], 'last_activity_at': time.time()} def delete_expired_tokens(): @@ -81,3 +85,18 @@ class NovaAuthMiddleware(object): utils.LoopingCall(consumer.fetch, auto_ack=True, enable_callbacks=True).start(0.1) utils.LoopingCall(delete_expired_tokens).start(1) + + +class LoggingMiddleware(object): + def __init__(self, app): + self.app = app + + @webob.dec.wsgify + def __call__(self, req): + + if req.path == '/data': + LOG.info(_("Received Websocket Request: %s)"), req.url) + else: + LOG.info(_("Received Request: %s)"), req.url) + + return req.get_response(self.app) diff --git a/nova/vnc/proxy.py b/nova/vnc/proxy.py index 5dc83fcb1..354c2405f 100644 --- a/nova/vnc/proxy.py +++ b/nova/vnc/proxy.py @@ -102,10 +102,9 @@ class DebugMiddleware(object): def __init__(self, app): self.app = app - def __call__(self, environ, start_response): - req = Request(environ) + @webob.dec.wsgify + def __call__(self, req): if req.path == '/data': - environ['vnc_host'] = req.params.get('host') - environ['vnc_port'] = int(req.params.get('port')) - resp = req.get_response(self.app) - return resp(environ, start_response) + req.environ['vnc_host'] = req.params.get('host') + req.environ['vnc_port'] = int(req.params.get('port')) + return req.get_response(self.app) -- cgit From f0bb48fc2f2e7d9326c51b4b57e73e0258930909 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Wed, 23 Mar 2011 09:34:34 +0000 Subject: removed excess debug line --- nova/virt/xenapi/network_utils.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi/network_utils.py b/nova/virt/xenapi/network_utils.py index 52ad0e1a5..546f6bea9 100644 --- a/nova/virt/xenapi/network_utils.py +++ b/nova/virt/xenapi/network_utils.py @@ -28,7 +28,6 @@ class NetworkHelper(HelperBase): """ The class that wraps the helper methods together. """ - @classmethod def find_network_with_name_label(cls, session, name_label): networks = session.call_xenapi('network.get_by_name_label', name_label) -- cgit From ff9e29e3ef56ec8b28f28d328ca010ce25f0c7b0 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 23 Mar 2011 09:47:22 -0400 Subject: Removed some un-needed code, and started adding tests for show(), which I forgot\! --- nova/api/openstack/images.py | 37 +++++++++++--------- nova/tests/api/openstack/test_images.py | 61 +++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 17 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 4cd989054..d8606e3c2 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -13,9 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. -from webob import exc +import webob.exc from nova import compute +from nova import exception from nova import flags from nova import utils from nova import wsgi @@ -39,11 +40,6 @@ class Controller(wsgi.Controller): }, } - _builder_dispatch = { - "1.0": images_view.ViewBuilderV10, - "1.1": images_view.ViewBuilderV11, - } - def __init__(self, image_service=None, compute_service=None): """ Initialize new `ImageController`. @@ -60,7 +56,7 @@ class Controller(wsgi.Controller): """ Return an index listing of images available to the request. - @param req: `webob.Request` object + @param req: `wsgi.Request` object """ context = req.environ['nova.context'] images = self.__image.index(context) @@ -71,31 +67,38 @@ class Controller(wsgi.Controller): """ Return a detailed index listing of images available to the request. - @param req: `webob.Request` object. + @param req: `wsgi.Request` object. """ context = req.environ['nova.context'] images = self.__image.detail(context) build = self.get_builder(req).build return dict(images=[build(image, True) for image in images]) - def show(self, req, image_id): + def show(self, req, id): """ Return detailed information about a specific image. - @param req: `webob.Request` object - @param image_id: Image identifier (integer) + @param req: `wsgi.Request` object + @param id: Image identifier (integer) """ + image_id = id context = req.environ['nova.context'] - image = self.__image.show(context, image_id) - return self.get_builder().build(req, image, True) - def delete(self, req, image_id): + try: + image = self.__image.show(context, image_id) + except exception.NotFound: + raise webob.exc.HTTPNotFound + + return self.get_builder(req).build(image, True) + + def delete(self, req, id): """ Delete an image, if allowed. - @param req: `webob.Request` object - @param image_id: Image identifier (integer) + @param req: `wsgi.Request` object + @param id: Image identifier (integer) """ + image_id = id context = req.environ['nova.context'] self.__image.delete(context, image_id) return exc.HTTPNoContent() @@ -104,7 +107,7 @@ class Controller(wsgi.Controller): """ Snapshot a server instance and save the image. - @param req: `webob.Request` object + @param req: `wsgi.Request` object """ context = req.environ['nova.context'] body = req.body diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index c5a866bc7..8828b0e34 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -235,6 +235,67 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): self.assertEqual(len(response_list), len(self.IMAGE_FIXTURES)) + def test_get_image(self): + request = webob.Request.blank('/v1.0/images/23g2ogk23k4hhkk4k42l') + response = request.get_response(fakes.wsgi_app()) + + actual_image = json.loads(response.body) + + expected = self.IMAGE_FIXTURES[0] + expected_image = { + "id": expected["id"], + "name": expected["name"], + "updated": expected["updated_at"], + "created": expected["created_at"], + "status": expected["status"], + } + + self.assertEqual(expected_image, actual_image) + + def test_get_image_v1_1(self): + request = webob.Request.blank('/v1.1/images/23g2ogk23k4hhkk4k42l') + response = request.get_response(fakes.wsgi_app()) + + actual_image = json.loads(response.body) + + expected = self.IMAGE_FIXTURES[0] + href = "http://localhost/v1.1/images/%s" % expected["id"] + + expected_image = { + "id": expected["id"], + "name": expected["name"], + "updated": expected["updated_at"], + "created": expected["created_at"], + "status": expected["status"], + "links": [{ + "rel": "self", + "href": href, + }, + { + "rel": "bookmark", + "type": "application/json", + "href": href, + }, + { + "rel": "bookmark", + "type": "application/xml", + "href": href, + }], + } + + self.assertEqual(expected_image, actual_image) + + def test_get_image_404(self): + request = webob.Request.blank('/v1.0/images/NonExistantImage') + response = request.get_response(fakes.wsgi_app()) + self.assertEqual(404, response.status_int) + self.assertEqual("", response.body) + + def test_get_image_v1_1_404(self): + request = webob.Request.blank('/v1.1/images/NonExistantImage') + response = request.get_response(fakes.wsgi_app()) + self.assertEqual(404, response.status_int) + def test_get_image_index_v1_1(self): request = webob.Request.blank('/v1.1/images') response = request.get_response(fakes.wsgi_app()) -- cgit From e9800364853078115cfb205bae263c3a55410b02 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 23 Mar 2011 11:04:20 -0400 Subject: Fixed tests. --- nova/tests/api/openstack/fakes.py | 4 ++-- nova/tests/api/openstack/test_images.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index 16c7bc163..911eeaaad 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -79,9 +79,9 @@ def wsgi_app(inner_app10=None, inner_app11=None): inner_app11 = openstack.APIRouterV11() mapper = urlmap.URLMap() api10 = openstack.FaultWrapper(auth.AuthMiddleware( - ratelimiting.RateLimitingMiddleware(inner_app10))) + limits.RateLimitingMiddleware(inner_app10))) api11 = openstack.FaultWrapper(auth.AuthMiddleware( - ratelimiting.RateLimitingMiddleware(inner_app11))) + limits.RateLimitingMiddleware(inner_app11))) mapper['/v1.0'] = api10 mapper['/v1.1'] = api11 mapper['/'] = openstack.FaultWrapper(openstack.Versions()) diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index a6ee23e1d..e93a1ea40 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -301,7 +301,6 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): request = webob.Request.blank('/v1.0/images/NonExistantImage') response = request.get_response(fakes.wsgi_app()) self.assertEqual(404, response.status_int) - self.assertEqual("", response.body) def test_get_image_v1_1_404(self): request = webob.Request.blank('/v1.1/images/NonExistantImage') -- cgit From 572b6d30c809af6e117d96de9a5a2d845c1eeda0 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 23 Mar 2011 11:52:43 -0400 Subject: Testing of XML and JSON for show(), and conformance to API spec for JSON. --- nova/api/openstack/images.py | 6 +- nova/tests/api/openstack/test_images.py | 159 +++++++++++++++++++++++++++----- 2 files changed, 138 insertions(+), 27 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index d8606e3c2..38c8a7306 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -21,6 +21,7 @@ from nova import flags from nova import utils from nova import wsgi from nova.api.openstack import common +from nova.api.openstack import faults from nova.api.openstack.views import images as images_view @@ -87,9 +88,10 @@ class Controller(wsgi.Controller): try: image = self.__image.show(context, image_id) except exception.NotFound: - raise webob.exc.HTTPNotFound + ex = webob.exc.HTTPNotFound(explanation="Image not found.") + raise faults.Fault(ex) - return self.get_builder(req).build(image, True) + return dict(image=self.get_builder(req).build(image, True)) def delete(self, req, id): """ diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index e93a1ea40..deb8f1744 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -26,6 +26,8 @@ import os import shutil import tempfile +from xml.dom.minidom import parseString + import stubout import webob @@ -255,11 +257,13 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): expected = self.IMAGE_FIXTURES[0] expected_image = { - "id": expected["id"], - "name": expected["name"], - "updated": expected["updated_at"], - "created": expected["created_at"], - "status": expected["status"], + "image": { + "id": expected["id"], + "name": expected["name"], + "updated": expected["updated_at"], + "created": expected["created_at"], + "status": expected["status"], + }, } self.assertEqual(expected_image, actual_image) @@ -274,39 +278,142 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): href = "http://localhost/v1.1/images/%s" % expected["id"] expected_image = { - "id": expected["id"], - "name": expected["name"], - "updated": expected["updated_at"], - "created": expected["created_at"], - "status": expected["status"], - "links": [{ - "rel": "self", - "href": href, - }, - { - "rel": "bookmark", - "type": "application/json", - "href": href, + "image": { + "id": expected["id"], + "name": expected["name"], + "updated": expected["updated_at"], + "created": expected["created_at"], + "status": expected["status"], + "links": [{ + "rel": "self", + "href": href, + }, + { + "rel": "bookmark", + "type": "application/json", + "href": href, + }, + { + "rel": "bookmark", + "type": "application/xml", + "href": href, + }], }, - { - "rel": "bookmark", - "type": "application/xml", - "href": href, - }], } self.assertEqual(expected_image, actual_image) - def test_get_image_404(self): + def test_get_image_xml(self): + request = webob.Request.blank('/v1.0/images/23g2ogk23k4hhkk4k42l') + request.accept = "application/xml" + response = request.get_response(fakes.wsgi_app()) + + actual_image = parseString(response.body.replace(" ", "")) + + expected = self.IMAGE_FIXTURES[0] + expected_image = parseString(""" + + """ % (expected)) + + self.assertEqual(expected_image.toxml(), actual_image.toxml()) + + def test_get_image_v1_1_xml(self): + request = webob.Request.blank('/v1.1/images/23g2ogk23k4hhkk4k42l') + request.accept = "application/xml" + response = request.get_response(fakes.wsgi_app()) + + actual_image = parseString(response.body.replace(" ", "")) + + expected = self.IMAGE_FIXTURES[0] + expected["href"] = "http://localhost/v1.1/images/23g2ogk23k4hhkk4k42l" + expected_image = parseString(""" + + + + + + + + """.replace(" ", "") % (expected)) + + self.assertEqual(expected_image.toxml(), actual_image.toxml()) + + def test_get_image_404_json(self): + request = webob.Request.blank('/v1.0/images/NonExistantImage') + response = request.get_response(fakes.wsgi_app()) + self.assertEqual(404, response.status_int) + + expected = { + "itemNotFound": { + "message": "Image not found.", + "code": 404, + }, + } + + actual = json.loads(response.body) + + self.assertEqual(expected, actual) + + def test_get_image_404_xml(self): request = webob.Request.blank('/v1.0/images/NonExistantImage') + request.accept = "application/xml" response = request.get_response(fakes.wsgi_app()) self.assertEqual(404, response.status_int) - def test_get_image_v1_1_404(self): + expected = parseString(""" + + + Image not found. + + + """.replace(" ", "")) + + actual = parseString(response.body.replace(" ", "")) + + self.assertEqual(expected.toxml(), actual.toxml()) + + def test_get_image_404_v1_1_json(self): request = webob.Request.blank('/v1.1/images/NonExistantImage') response = request.get_response(fakes.wsgi_app()) self.assertEqual(404, response.status_int) + expected = { + "itemNotFound": { + "message": "Image not found.", + "code": 404, + }, + } + + actual = json.loads(response.body) + + self.assertEqual(expected, actual) + + def test_get_image_404_v1_1_xml(self): + request = webob.Request.blank('/v1.1/images/NonExistantImage') + request.accept = "application/xml" + response = request.get_response(fakes.wsgi_app()) + self.assertEqual(404, response.status_int) + + expected = parseString(""" + + + Image not found. + + + """.replace(" ", "")) + + actual = parseString(response.body.replace(" ", "")) + + self.assertEqual(expected.toxml(), actual.toxml()) + def test_get_image_index_v1_1(self): request = webob.Request.blank('/v1.1/images') response = request.get_response(fakes.wsgi_app()) @@ -338,6 +445,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): self.assertEqual(len(response_list), len(self.IMAGE_FIXTURES)) + + def test_get_image_details(self): request = webob.Request.blank('/v1.0/images/detail') response = request.get_response(fakes.wsgi_app()) -- cgit From 48c04eb35fae704913e9ed05868d1334ee5458fa Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Wed, 23 Mar 2011 12:17:48 -0400 Subject: add changePassword action to os api v1.1 --- nova/api/openstack/servers.py | 13 +++++++++ nova/tests/api/openstack/test_servers.py | 46 ++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 73843f63e..90f709a47 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -256,6 +256,7 @@ class Controller(wsgi.Controller): resize a server""" actions = { + 'changePassword': self._action_change_password, 'reboot': self._action_reboot, 'resize': self._action_resize, 'confirmResize': self._action_confirm_resize, @@ -269,6 +270,9 @@ class Controller(wsgi.Controller): return actions[key](input_dict, req, id) return faults.Fault(exc.HTTPNotImplemented()) + def _action_change_password(self, input_dict, req, id): + return exc.HTTPNotImplemented() + def _action_confirm_resize(self, input_dict, req, id): try: self.compute_api.confirm_resize(req.environ['nova.context'], id) @@ -555,6 +559,15 @@ class ControllerV11(Controller): def _get_addresses_view_builder(self, req): return nova.api.openstack.views.addresses.ViewBuilderV11(req) + def _action_change_password(self, input_dict, req, id): + context = req.environ['nova.context'] + if not 'changePassword' in input_dict \ + or not 'adminPass' in input_dict['changePassword']: + return exc.HTTPBadRequest() + password = input_dict['changePassword']['adminPass'] + self.compute_api.set_admin_password(context, id, password) + return exc.HTTPAccepted() + class ServerCreateRequestXMLDeserializer(object): """ diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index e21637ea4..dc5fedb8c 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -558,6 +558,52 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 404) + def test_server_change_password(self): + body = {'changePassword': {'adminPass': '1234pass'}} + req = webob.Request.blank('/v1.0/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 501) + + def test_server_change_password_v1_1(self): + + class MockSetAdminPassword(object): + + def __init__(self): + self.called = False + self.instance_id = None + self.password = None + + def __call__(self, context, instance_id, password): + self.called = True + self.instance_id = instance_id + self.password = password + + 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.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + self.assertTrue(mock_method.called) + self.assertEqual(mock_method.instance_id, '1') + self.assertEqual(mock_method.password, '1234pass') + + def test_server_change_password_bad_request_v1_1(self): + body = {'changePassword': {'pass': '12345'}} + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + def test_server_reboot(self): body = dict(server=dict( name='server_test', imageId=2, flavorId=2, metadata={}, -- cgit From e0289dd26821545a6ef2ca91eb2dba7c11c2cc9f Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 23 Mar 2011 15:53:46 -0700 Subject: general cleanup, use whitelist for webserver security --- nova/flags.py | 2 +- nova/virt/libvirt.xml.template | 2 +- nova/virt/libvirt_conn.py | 2 +- nova/vnc/auth.py | 34 ++++++++++++++++++++++------------ nova/vnc/proxy.py | 28 +++++++++++++++++++++++++--- 5 files changed, 50 insertions(+), 18 deletions(-) (limited to 'nova') diff --git a/nova/flags.py b/nova/flags.py index 0360b1e3a..a0ea10795 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -287,7 +287,7 @@ DEFINE_string('vnc_console_proxy_url', 'http://127.0.0.1:6080', 'location of vnc console proxy, \ in the form "http://127.0.0.1:6080"') -DEFINE_string('vnc_host_iface', '0.0.0.0', +DEFINE_string('vnc_compute_host_iface', '0.0.0.0', 'the compute host interface on which vnc server should listen') DEFINE_bool('vnc_enabled', True, 'enable vnc related features') diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template index 037cd0902..bcc6b3aed 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -101,7 +101,7 @@ -#if $getVar('vnc_host_iface', False) +#if $getVar('vnc_compute_host_iface', False) #end if diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 51f263ce9..c3529f512 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -735,7 +735,7 @@ class LibvirtConnection(object): 'driver_type': driver_type} if FLAGS.vnc_enabled: - xml_info['vnc_host_iface'] = FLAGS.vnc_host_iface + xml_info['vnc_compute_host_iface'] = FLAGS.vnc_compute_host_iface if ra_server: xml_info['ra_server'] = ra_server + "/128" if not rescue: diff --git a/nova/vnc/auth.py b/nova/vnc/auth.py index 9b30b08b8..676cb2360 100644 --- a/nova/vnc/auth.py +++ b/nova/vnc/auth.py @@ -21,13 +21,16 @@ """Auth Components for VNC Console""" import time +import urlparse +import webob from webob import Request + from nova import flags from nova import log as logging from nova import rpc from nova import utils from nova import wsgi -import webob +from nova import vnc LOG = logging.getLogger('nova.vnc-proxy') FLAGS = flags.FLAGS @@ -42,13 +45,19 @@ class NovaAuthMiddleware(object): @webob.dec.wsgify def __call__(self, req): - if req.path == '/data': - token = req.params.get('token') - if not token in self.tokens: - start_response('403 Forbidden', - [('content-type', 'text/html')]) - return 'Not Authorized' + token = req.params.get('token') + + if not token: + referrer = req.environ.get('HTTP_REFERER') + auth_params = urlparse.parse_qs(urlparse.urlparse(referrer).query) + if 'token' in auth_params: + token = auth_params['token'][0] + + if not token in self.tokens: + LOG.audit(_("Unauthorized Access: (%s)"), req.environ) + return webob.exc.HTTPForbidden(detail='Unauthorized') + if req.path == vnc.proxy.WS_ENDPOINT: req.environ['vnc_host'] = self.tokens[token]['args']['host'] req.environ['vnc_port'] = int(self.tokens[token]['args']['port']) @@ -62,7 +71,7 @@ class NovaAuthMiddleware(object): def __call__(self, data, message): if data['method'] == 'authorize_vnc_console': token = data['args']['token'] - LOG.info(_("Received Token: %s)"), token) + LOG.audit(_("Received Token: %s)"), token) middleware.tokens[token] = \ {'args': data['args'], 'last_activity_at': time.time()} @@ -70,10 +79,11 @@ class NovaAuthMiddleware(object): now = time.time() to_delete = [] for k, v in middleware.tokens.items(): - if now - v['last_activity_at'] > 600: + if now - v['last_activity_at'] > FLAGS.vnc_token_ttl: to_delete.append(k) for k in to_delete: + LOG.audit(_("Deleting Token: %s)"), k) del middleware.tokens[k] conn = rpc.Connection.instance(new=True) @@ -94,9 +104,9 @@ class LoggingMiddleware(object): @webob.dec.wsgify def __call__(self, req): - if req.path == '/data': - LOG.info(_("Received Websocket Request: %s)"), req.url) + if req.path == vnc.proxy.WS_ENDPOINT: + LOG.info(_("Received Websocket Request: %s"), req.url) else: - LOG.info(_("Received Request: %s)"), req.url) + LOG.info(_("Received Request: %s"), req.url) return req.get_response(self.app) diff --git a/nova/vnc/proxy.py b/nova/vnc/proxy.py index 354c2405f..70ebd022a 100644 --- a/nova/vnc/proxy.py +++ b/nova/vnc/proxy.py @@ -28,12 +28,30 @@ import os from webob import Request import webob +WS_ENDPOINT = '/data' + class WebsocketVNCProxy(object): """Class to proxy from websocket to vnc server""" def __init__(self, wwwroot): self.wwwroot = wwwroot + self.whitelist = {} + for root, dirs, files in os.walk(wwwroot): + hidden_dirs = [] + for d in dirs: + if d.startswith('.'): + hidden_dirs.append(d) + for d in hidden_dirs: + dirs.remove(d) + for name in files: + if not str(name).startswith('.'): + filename = os.path.join(root, name) + self.whitelist[filename] = True + + + def get_whitelist(self): + return self.whitelist.keys() def sock2ws(self, source, dest): try: @@ -72,7 +90,7 @@ class WebsocketVNCProxy(object): def __call__(self, environ, start_response): req = Request(environ) - if req.path == '/data': + if req.path == WS_ENDPOINT: return self.proxy_connection(environ, start_response) else: if req.path == '/': @@ -80,7 +98,11 @@ class WebsocketVNCProxy(object): else: fname = req.path - fname = self.wwwroot + fname + fname = (self.wwwroot + fname).replace('//','/') + if not fname in self.whitelist: + start_response('404 Not Found', + [('content-type', 'text/html')]) + return "Not Found" base, ext = os.path.splitext(fname) if ext == '.js': @@ -104,7 +126,7 @@ class DebugMiddleware(object): @webob.dec.wsgify def __call__(self, req): - if req.path == '/data': + if req.path == WS_ENDPOINT: req.environ['vnc_host'] = req.params.get('host') req.environ['vnc_port'] = int(req.params.get('port')) return req.get_response(self.app) -- cgit From 3b381792c2cce1e43f68e39f2fc9c73ba2760024 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Wed, 23 Mar 2011 15:55:37 -0700 Subject: clean some pep8 issues --- nova/vnc/proxy.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/vnc/proxy.py b/nova/vnc/proxy.py index 70ebd022a..dea838e3d 100644 --- a/nova/vnc/proxy.py +++ b/nova/vnc/proxy.py @@ -48,7 +48,6 @@ class WebsocketVNCProxy(object): if not str(name).startswith('.'): filename = os.path.join(root, name) self.whitelist[filename] = True - def get_whitelist(self): return self.whitelist.keys() @@ -98,7 +97,7 @@ class WebsocketVNCProxy(object): else: fname = req.path - fname = (self.wwwroot + fname).replace('//','/') + fname = (self.wwwroot + fname).replace('//', '/') if not fname in self.whitelist: start_response('404 Not Found', [('content-type', 'text/html')]) -- cgit From 1894937e1ef6769a5f76c0a382931480e2547ce8 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Thu, 24 Mar 2011 01:03:41 -0700 Subject: Added volume_attachments --- nova/api/openstack/__init__.py | 6 ++ nova/api/openstack/volume_attachments.py | 154 +++++++++++++++++++++++++++++++ nova/api/openstack/volumes.py | 60 ++++++------ 3 files changed, 190 insertions(+), 30 deletions(-) create mode 100644 nova/api/openstack/volume_attachments.py (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 474c1d0e6..af3f8c5ce 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -38,6 +38,7 @@ from nova.api.openstack import servers from nova.api.openstack import shared_ip_groups from nova.api.openstack import users from nova.api.openstack import volumes +from nova.api.openstack import volume_attachments from nova.api.openstack import zones @@ -109,6 +110,11 @@ class APIRouter(wsgi.Router): parent_resource=dict(member_name='server', collection_name='servers')) + mapper.resource("volume_attachment", "volume_attachment", + controller=volume_attachments.Controller(), + parent_resource=dict(member_name='server', + collection_name='servers')) + mapper.resource("console", "consoles", controller=consoles.Controller(), parent_resource=dict(member_name='server', diff --git a/nova/api/openstack/volume_attachments.py b/nova/api/openstack/volume_attachments.py new file mode 100644 index 000000000..fbcec7c29 --- /dev/null +++ b/nova/api/openstack/volume_attachments.py @@ -0,0 +1,154 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Justin Santa Barbara +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from webob import exc + +from nova import compute +from nova import exception +from nova import flags +from nova import log as logging +from nova import volume +from nova import wsgi +from nova.api.openstack import common +from nova.api.openstack import faults + + +LOG = logging.getLogger("nova.api.volumes") + +FLAGS = flags.FLAGS + + +def _translate_detail_view(context, volume): + """ Maps keys for details view""" + + v = _translate_summary_view(context, volume) + + # No additional data / lookups at the moment + + return v + + +def _translate_summary_view(context, volume): + """ Maps keys for summary view""" + v = {} + + volume_id = volume['id'] + + # NOTE(justinsb): We use the volume id as the id of the attachment object + v['id'] = volume_id + + v['volumeId'] = volume_id + v['serverId'] = volume['instance_id'] + v['device'] = volume['mountpoint'] + + return v + + +class Controller(wsgi.Controller): + """ The volume attachment API controller for the Openstack API + + A child resource of the server. Note that we use the volume id + as the ID of the attachment (though this is not guaranteed externally)""" + + _serialization_metadata = { + 'application/xml': { + 'attributes': { + 'volumeAttachment': [ 'id', + 'serverId', + 'volumeId', + 'device' ]}}} + + def __init__(self): + self.compute_api = compute.API() + self.volume_api = volume.API() + super(Controller, self).__init__() + + def index(self, req, server_id): + """ Returns the list of volume attachments for a given instance """ + return self._items(req, server_id, + entity_maker=_translate_summary_view) + + def show(self, req, id): + """Return data about the given volume""" + context = req.environ['nova.context'] + + try: + vol = self.volume_api.get(context, id) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + + return {'volume': _translate_detail_view(context, vol)} + + def create(self, req, server_id): + """ Attach a volume to an instance """ + context = req.environ['nova.context'] + + env = self._deserialize(req.body, req) + if not env: + return faults.Fault(exc.HTTPUnprocessableEntity()) + + instance_id = server_id + volume_id = env['volumeAttachment']['volumeId'] + device = env['volumeAttachment']['device'] + + msg = _("Attach volume %(volume_id)s to instance %(server_id)s" + " at %(device)s") % locals() + LOG.audit(msg, context=context) + + self.compute_api.attach_volume(context, + instance_id=instance_id, + volume_id=volume_id, + device=device) + vol = self.volume_api.get(context, volume_id) + + retval = _translate_detail_view(context, vol) + + return {'volumeAttachment': retval} + + def update(self, _req, _server_id, _id): + """ Update a volume attachment. We don't currently support this.""" + return faults.Fault(exc.HTTPBadRequest()) + + def delete(self, req, server_id, id): + """ Detach a volume from an instance """ + context = req.environ['nova.context'] + + volume_id = id + LOG.audit(_("Detach volume %s"), volume_id, context=context) + + vol = self.volume_api.get(context, volume_id) + if vol['instance_id'] != server_id: + return faults.Fault(exc.HTTPNotFound()) + + self.compute_api.detach_volume(context, + volume_id=volume_id) + + return exc.HTTPAccepted() + + def _items(self, req, server_id, entity_maker): + """Returns a list of attachments, 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()) + + volumes = instance['volumes'] + limited_list = common.limited(volumes, req) + res = [entity_maker(context, vol) for vol in limited_list] + return {'volumeAttachments': res} diff --git a/nova/api/openstack/volumes.py b/nova/api/openstack/volumes.py index 99300421e..ea2dc4aab 100644 --- a/nova/api/openstack/volumes.py +++ b/nova/api/openstack/volumes.py @@ -29,52 +29,52 @@ LOG = logging.getLogger("nova.api.volumes") FLAGS = flags.FLAGS -def _translate_detail_view(context, inst): +def _translate_detail_view(context, vol): """ Maps keys for details view""" - inst_dict = _translate_summary_view(context, inst) + d = _translate_summary_view(context, vol) # No additional data / lookups at the moment - return inst_dict + return d -def _translate_summary_view(context, volume): +def _translate_summary_view(_context, vol): """ Maps keys for summary view""" - v = {} + d = {} instance_id = None # instance_data = None - attached_to = volume.get('instance') + attached_to = vol.get('instance') if attached_to: instance_id = attached_to['id'] # instance_data = '%s[%s]' % (instance_ec2_id, # attached_to['host']) - v['id'] = volume['id'] - v['status'] = volume['status'] - v['size'] = volume['size'] - v['availabilityZone'] = volume['availability_zone'] - v['createdAt'] = volume['created_at'] + d['id'] = vol['id'] + d['status'] = vol['status'] + d['size'] = vol['size'] + d['availabilityZone'] = vol['availability_zone'] + d['createdAt'] = vol['created_at'] # if context.is_admin: # v['status'] = '%s (%s, %s, %s, %s)' % ( - # volume['status'], - # volume['user_id'], - # volume['host'], + # vol['status'], + # vol['user_id'], + # vol['host'], # instance_data, - # volume['mountpoint']) - if volume['attach_status'] == 'attached': - v['attachments'] = [{'attachTime': volume['attach_time'], + # vol['mountpoint']) + if vol['attach_status'] == 'attached': + d['attachments'] = [{'attachTime': vol['attach_time'], 'deleteOnTermination': False, - 'mountpoint': volume['mountpoint'], + 'mountpoint': vol['mountpoint'], 'instanceId': instance_id, 'status': 'attached', - 'volumeId': volume['id']}] + 'volumeId': vol['id']}] else: - v['attachments'] = [{}] + d['attachments'] = [{}] - v['displayName'] = volume['display_name'] - v['displayDescription'] = volume['display_description'] - return v + d['displayName'] = vol['display_name'] + d['displayDescription'] = vol['display_description'] + return d class Controller(wsgi.Controller): @@ -102,11 +102,11 @@ class Controller(wsgi.Controller): context = req.environ['nova.context'] try: - volume = self.volume_api.get(context, id) + vol = self.volume_api.get(context, id) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - return {'volume': _translate_detail_view(context, volume)} + return {'volume': _translate_detail_view(context, vol)} def delete(self, req, id): """ Delete a volume """ @@ -134,7 +134,7 @@ class Controller(wsgi.Controller): volumes = self.volume_api.get_all(context) limited_list = common.limited(volumes, req) - res = [entity_maker(context, inst) for inst in limited_list] + res = [entity_maker(context, vol) for vol in limited_list] return {'volumes': res} def create(self, req): @@ -148,13 +148,13 @@ class Controller(wsgi.Controller): vol = env['volume'] size = vol['size'] LOG.audit(_("Create volume of %s GB"), size, context=context) - volume = self.volume_api.create(context, size, - vol.get('display_name'), - vol.get('display_description')) + new_volume = self.volume_api.create(context, size, + vol.get('display_name'), + vol.get('display_description')) # Work around problem that instance is lazy-loaded... volume['instance'] = None - retval = _translate_detail_view(context, volume) + retval = _translate_detail_view(context, new_volume) return {'volume': retval} -- cgit From 694c2cfd2afdc0ed293f205890bda977968dc079 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Thu, 24 Mar 2011 01:13:20 -0700 Subject: Created simple test case for server creation, so that we can have something to attach to... --- nova/image/fake.py | 109 ++++++++++++++ nova/tests/integrated/integrated_helpers.py | 22 +++ nova/tests/integrated/test_servers.py | 218 ++++++++++++++++++++++++++++ 3 files changed, 349 insertions(+) create mode 100644 nova/image/fake.py create mode 100644 nova/tests/integrated/test_servers.py (limited to 'nova') diff --git a/nova/image/fake.py b/nova/image/fake.py new file mode 100644 index 000000000..bc63780d3 --- /dev/null +++ b/nova/image/fake.py @@ -0,0 +1,109 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Justin Santa Barbara +# 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. +"""Implementation of an fake image service""" + +from nova import exception +from nova import flags +from nova import log as logging +from nova.image import service + + +LOG = logging.getLogger('nova.image.fake') + +FLAGS = flags.FLAGS + + +class MockImageService(service.BaseImageService): + """Mock (fake) image service for unit testing""" + + def __init__(self): + self.images = {} + # NOTE(justinsb): The OpenStack API can't upload an image??? + # So, make sure we've got one.. + image = {'id': '123456', + 'status': 'active', + 'type': 'machine', + 'disk_format': 'ami', + 'properties': {'kernel_id': FLAGS.null_kernel, + 'ramdisk_id': FLAGS.null_kernel, + } + } + self.create(None, image) + super(MockImageService, self).__init__() + + def index(self, context): + """Returns list of images""" + return self.images.values() + + def detail(self, context): + """Return list of detailed image information""" + return self.images.values() + + def show(self, context, image_id): + """ + Returns a dict containing image data for the given opaque image id. + """ + image_id = int(image_id) + image = self.images.get(image_id) + if image: + return image + LOG.warn("Unable to find image id %s. Have images: %s", + image_id, self.images) + raise exception.NotFound + + def create(self, context, data): + """ + Store the image data and return the new image id. + + :raises AlreadyExists if the image already exist. + + """ + image_id = int(data['id']) + if self.images.get(image_id): + #TODO(justinsb): Where is this AlreadyExists exception?? + raise exception.Error("AlreadyExists") + + self.images[image_id] = data + + def update(self, context, image_id, data): + """Replace the contents of the given image with the new data. + + :raises NotFound if the image does not exist. + + """ + image_id = int(image_id) + if not self.images.get(image_id): + raise exception.NotFound + self.images[image_id] = data + + def delete(self, context, image_id): + """ + Delete the given image. + + :raises NotFound if the image does not exist. + + """ + image_id = int(image_id) + removed = self.images.pop(image_id, None) + if not removed: + raise exception.NotFound + + def delete_all(self): + """ + Clears out all images + """ + self.images.clear() diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py index 47093636e..dc6897e08 100644 --- a/nova/tests/integrated/integrated_helpers.py +++ b/nova/tests/integrated/integrated_helpers.py @@ -73,6 +73,28 @@ class TestUser(object): 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 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): __INSTANCE = None diff --git a/nova/tests/integrated/test_servers.py b/nova/tests/integrated/test_servers.py new file mode 100644 index 000000000..3c38295c5 --- /dev/null +++ b/nova/tests/integrated/test_servers.py @@ -0,0 +1,218 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Justin Santa Barbara +# 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 time +import unittest + +from nova import flags +from nova import test +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') + +FLAGS = flags.FLAGS +FLAGS.verbose = True + + +class ServersTest(test.TestCase): + def setUp(self): + super(ServersTest, self).setUp() + + self.flags(image_service='nova.image.fake.MockImageService') + + context = integrated_helpers.IntegratedUnitTestContext.startup() + self.user = context.test_user + self.api = self.user.openstack_api + + def tearDown(self): + integrated_helpers.IntegratedUnitTestContext.shutdown() + super(ServersTest, self).tearDown() + + def test_get_servers(self): + """Simple check that listing servers works.""" + servers = self.api.get_servers() + for server in servers: + LOG.debug("server: %s" % server) + + def test_create_and_delete_server(self): + """Creates and deletes a server""" + + # Create server + + # Build the server data gradually, checking errors along the way + server = {} + good_server = self._build_minimal_create_server_request() + + post = {'server': server} + + # Without an imageId, this throws 500. + # TODO(justinsb): Check whatever the spec says should be thrown here + self.assertRaises(client.OpenStackApiException, + self.api.post_server, post) + + # With an invalid imageId, this throws 500. + server['imageId'] = self.user.get_invalid_image() + # TODO(justinsb): Check whatever the spec says should be thrown here + self.assertRaises(client.OpenStackApiException, + self.api.post_server, post) + + # Add a valid imageId + server['imageId'] = good_server['imageId'] + + # Without flavorId, this throws 500 + # TODO(justinsb): Check whatever the spec says should be thrown here + self.assertRaises(client.OpenStackApiException, + self.api.post_server, post) + + # Set a valid flavorId + server['flavorId'] = good_server['flavorId'] + + # Without a name, this throws 500 + # TODO(justinsb): Check whatever the spec says should be thrown here + self.assertRaises(client.OpenStackApiException, + self.api.post_server, post) + + # Set a valid server name + server['name'] = good_server['name'] + + 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']) + + # It should also be in the all-servers list + servers = self.api.get_servers() + server_ids = [server['id'] for server in servers] + self.assertTrue(created_server_id in server_ids) + + # Wait (briefly) for creation + retries = 0 + while found_server['status'] == 'build': + LOG.debug("found server: %s" % found_server) + time.sleep(1) + found_server = self.api.get_server(created_server_id) + retries = retries + 1 + if retries > 5: + break + + # It should be available... + # TODO(justinsb): Mock doesn't yet do this... + #self.assertEqual('available', found_server['status']) + + self._delete_server(created_server_id) + + def _delete_server(self, server_id): + # Delete the server + self.api.delete_server(server_id) + + # Wait (briefly) for deletion + for _retries in range(5): + try: + found_server = self.api.get_server(server_id) + except client.OpenStackApiNotFoundException: + found_server = None + LOG.debug("Got 404, proceeding") + break + + LOG.debug("Found_server=%s" % found_server) + + # TODO(justinsb): Mock doesn't yet do accurate state changes + #if found_server['status'] != 'deleting': + # break + time.sleep(1) + + # Should be gone + self.assertFalse(found_server) + + def _build_minimal_create_server_request(self): + server = {} + + image = self.user.get_valid_image(create=True) + image_id = image['id'] + + #TODO(justinsb): This is FUBAR + image_id = abs(hash(image_id)) + + # We now have a valid imageId + server['imageId'] = image_id + + # Set a valid flavorId + flavor = self.api.get_flavors()[0] + LOG.debug("Using flavor: %s" % flavor) + server['flavorId'] = flavor['id'] + + # Set a valid server name + server_name = self.user.get_unused_server_name() + server['name'] = server_name + + return server + +# TODO(justinsb): Enable this unit test when the metadata bug is fixed +# def test_create_server_with_metadata(self): +# """Creates a server with metadata""" +# +# # Build the server data gradually, checking errors along the way +# server = self._build_minimal_create_server_request() +# +# for metadata_count in range(30): +# metadata = {} +# for i in range(metadata_count): +# metadata['key_%s' % i] = 'value_%s' % i +# server['metadata'] = metadata +# +# 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'] +# # Reenable when bug fixed +# # self.assertEqual(metadata, created_server.get('metadata')) +# +# # Check it's there +# found_server = self.api.get_server(created_server_id) +# self.assertEqual(created_server_id, found_server['id']) +# self.assertEqual(metadata, found_server.get('metadata')) +# +# # The server should also be in the all-servers details list +# servers = self.api.get_servers(detail=True) +# server_map = dict((server['id'], server) for server in servers) +# found_server = server_map.get(created_server_id) +# self.assertTrue(found_server) +# # Details do include metadata +# self.assertEqual(metadata, found_server.get('metadata')) +# +# # The server should also be in the all-servers summary list +# servers = self.api.get_servers(detail=False) +# server_map = dict((server['id'], server) for server in servers) +# found_server = server_map.get(created_server_id) +# self.assertTrue(found_server) +# # Summary should not include metadata +# self.assertFalse(found_server.get('metadata')) +# +# # Cleanup +# self._delete_server(created_server_id) + + +if __name__ == "__main__": + unittest.main() -- cgit From 699adb4311fdd86525fae022f4119401fd1c0168 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Thu, 24 Mar 2011 01:37:14 -0700 Subject: Added simple nova volume tests --- nova/api/openstack/volume_attachments.py | 2 +- nova/api/openstack/volumes.py | 4 +- nova/tests/integrated/api/client.py | 15 +++ nova/tests/integrated/integrated_helpers.py | 17 +++- nova/tests/integrated/test_volumes.py | 137 ++++++++++++++++++++++++++++ nova/volume/driver.py | 67 ++++++++++++++ 6 files changed, 238 insertions(+), 4 deletions(-) create mode 100644 nova/tests/integrated/test_volumes.py (limited to 'nova') diff --git a/nova/api/openstack/volume_attachments.py b/nova/api/openstack/volume_attachments.py index fbcec7c29..1cb2c9494 100644 --- a/nova/api/openstack/volume_attachments.py +++ b/nova/api/openstack/volume_attachments.py @@ -97,7 +97,7 @@ class Controller(wsgi.Controller): """ Attach a volume to an instance """ context = req.environ['nova.context'] - env = self._deserialize(req.body, req) + env = self._deserialize(req.body, req.get_content_type()) if not env: return faults.Fault(exc.HTTPUnprocessableEntity()) diff --git a/nova/api/openstack/volumes.py b/nova/api/openstack/volumes.py index ea2dc4aab..ec3b9a6c8 100644 --- a/nova/api/openstack/volumes.py +++ b/nova/api/openstack/volumes.py @@ -141,7 +141,7 @@ class Controller(wsgi.Controller): """Creates a new volume""" context = req.environ['nova.context'] - env = self._deserialize(req.body, req) + env = self._deserialize(req.body, req.get_content_type()) if not env: return faults.Fault(exc.HTTPUnprocessableEntity()) @@ -153,7 +153,7 @@ class Controller(wsgi.Controller): vol.get('display_description')) # Work around problem that instance is lazy-loaded... - volume['instance'] = None + new_volume['instance'] = None retval = _translate_detail_view(context, new_volume) diff --git a/nova/tests/integrated/api/client.py b/nova/tests/integrated/api/client.py index fc7c344e7..7a4c3198e 100644 --- a/nova/tests/integrated/api/client.py +++ b/nova/tests/integrated/api/client.py @@ -90,6 +90,7 @@ class TestOpenStackClient(object): LOG.info(_("Doing %(method)s on %(relative_url)s") % locals()) if body: LOG.info(_("Body: %s") % body) + headers.setdefault('Content-Type', 'application/json') conn.request(method, relative_url, body, headers) response = conn.getresponse() @@ -208,3 +209,17 @@ class TestOpenStackClient(object): def delete_flavor(self, flavor_id): return self.api_delete('/flavors/%s' % flavor_id) + + def get_volume(self, volume_id): + return self.api_get('/volumes/%s' % volume_id)['volume'] + + def get_volumes(self, detail=True): + rel_url = '/volumes/detail' if detail else '/volumes' + return self.api_get(rel_url)['volumes'] + + def post_volume(self, volume): + return self.api_post('/volumes', volume)['volume'] + + def delete_volume(self, volume_id): + return self.api_delete('/volumes/%s' % volume_id) + diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py index dc6897e08..f24759032 100644 --- a/nova/tests/integrated/integrated_helpers.py +++ b/nova/tests/integrated/integrated_helpers.py @@ -58,7 +58,7 @@ def generate_new_element(items, prefix, numeric=False): candidate = prefix + generate_random_alphanumeric(8) if not candidate in items: return candidate - print "Random collision on %s" % candidate + LOG.debug("Random collision on %s" % candidate) class TestUser(object): @@ -125,11 +125,26 @@ class IntegratedUnitTestContext(object): self._configure_project(self.project_name, self.test_user) def _start_services(self): + self._start_volume_service() + self._start_scheduler_service() + # WSGI shutdown broken :-( # bug731668 if not self.api_service: self._start_api_service() + def _start_volume_service(self): + volume_service = service.Service.create(binary='nova-volume') + volume_service.start() + self.services.append(volume_service) + return volume_service + + def _start_scheduler_service(self): + scheduler_service = service.Service.create(binary='nova-scheduler') + scheduler_service.start() + self.services.append(scheduler_service) + return scheduler_service + def cleanup(self): for service in self.services: service.kill() diff --git a/nova/tests/integrated/test_volumes.py b/nova/tests/integrated/test_volumes.py new file mode 100644 index 000000000..66b773db2 --- /dev/null +++ b/nova/tests/integrated/test_volumes.py @@ -0,0 +1,137 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Justin Santa Barbara +# 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 unittest +import time + +from nova import flags +from nova import test +from nova.log import logging +from nova.tests.integrated import integrated_helpers +from nova.tests.integrated.api import client +from nova.volume import driver + + +LOG = logging.getLogger('nova.tests.integrated') + +FLAGS = flags.FLAGS +FLAGS.verbose = True + + +class VolumesTest(test.TestCase): + def setUp(self): + super(VolumesTest, self).setUp() + + self.flags(image_service='nova.image.fake.MockImageService', + volume_driver='nova.volume.driver.LoggingVolumeDriver') + + context = integrated_helpers.IntegratedUnitTestContext.startup() + self.user = context.test_user + self.api = self.user.openstack_api + + def tearDown(self): + integrated_helpers.IntegratedUnitTestContext.shutdown() + super(VolumesTest, self).tearDown() + + def test_get_volumes(self): + """Simple check that listing volumes works""" + volumes = self.api.get_volumes() + for volume in volumes: + LOG.debug("volume: %s" % volume) + + def test_create_and_delete_volume(self): + """Creates and deletes a volume""" + + # Create volume with name + created_volume = self.api.post_volume({'volume': {'size': 1}}) + LOG.debug("created_volume: %s" % created_volume) + self.assertTrue(created_volume['id']) + created_volume_id = created_volume['id'] + + # Check it's there + found_volume = self.api.get_volume(created_volume_id) + self.assertEqual(created_volume_id, found_volume['id']) + + # It should also be in the all-volume list + volumes = self.api.get_volumes() + volume_names = [volume['id'] for volume in volumes] + self.assertTrue(created_volume_id in volume_names) + + # Wait (briefly) for creation. Delay is due to the 'message queue' + retries = 0 + while found_volume['status'] == 'creating': + LOG.debug("Found %s" % found_volume) + time.sleep(1) + found_volume = self.api.get_volume(created_volume_id) + retries = retries + 1 + if retries > 5: + break + + # It should be available... + self.assertEqual('available', found_volume['status']) + + # Delete the volume + self.api.delete_volume(created_volume_id) + + # Wait (briefly) for deletion. Delay is due to the 'message queue' + for retries in range(5): + try: + found_volume = self.api.get_volume(created_volume_id) + except client.OpenStackApiNotFoundException: + found_volume = None + LOG.debug("Got 404, proceeding") + break + + LOG.debug("Found_volume=%s" % found_volume) + if found_volume['status'] != 'deleting': + break + time.sleep(1) + + # Should be gone + self.assertFalse(found_volume) + + LOG.debug("Logs: %s" % driver.LoggingVolumeDriver.all_logs()) + + create_actions = driver.LoggingVolumeDriver.logs_like( + 'create_volume', + id=created_volume_id) + LOG.debug("Create_Actions: %s" % create_actions) + + self.assertEquals(1, len(create_actions)) + create_action = create_actions[0] + self.assertEquals(create_action['id'], created_volume_id) + self.assertEquals(create_action['availability_zone'], 'nova') + self.assertEquals(create_action['size'], 1) + + export_actions = driver.LoggingVolumeDriver.logs_like( + 'create_export', + id=created_volume_id) + self.assertEquals(1, len(export_actions)) + export_action = export_actions[0] + self.assertEquals(export_action['id'], created_volume_id) + self.assertEquals(export_action['availability_zone'], 'nova') + + delete_actions = driver.LoggingVolumeDriver.logs_like( + 'delete_volume', + id=created_volume_id) + self.assertEquals(1, len(delete_actions)) + delete_action = export_actions[0] + self.assertEquals(delete_action['id'], created_volume_id) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/nova/volume/driver.py b/nova/volume/driver.py index 779b46755..148e5facd 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -629,3 +629,70 @@ class SheepdogDriver(VolumeDriver): def undiscover_volume(self, volume): """Undiscover volume on a remote host""" pass + + +class LoggingVolumeDriver(VolumeDriver): + """Logs and records calls, for unit tests.""" + + def check_for_setup_error(self): + pass + + def create_volume(self, volume): + self.log_action('create_volume', volume) + + def delete_volume(self, volume): + self.log_action('delete_volume', volume) + + def local_path(self, volume): + print "local_path not implemented" + raise NotImplementedError() + + def ensure_export(self, context, volume): + self.log_action('ensure_export', volume) + + def create_export(self, context, volume): + self.log_action('create_export', volume) + + def remove_export(self, context, volume): + self.log_action('remove_export', volume) + + def discover_volume(self, volume): + self.log_action('discover_volume', volume) + + def undiscover_volume(self, volume): + self.log_action('undiscover_volume', volume) + + def check_for_export(self, context, volume_id): + self.log_action('check_for_export', volume_id) + + _LOGS = [] + + @staticmethod + def log_action(action, parameters): + """Logs the command.""" + LOG.debug(_("LoggingVolumeDriver: %s") % (action)) + log_dictionary = {} + if parameters: + log_dictionary = dict(parameters) + log_dictionary['action'] = action + LOG.debug(_("LoggingVolumeDriver: %s") % (log_dictionary)) + LoggingVolumeDriver._LOGS.append(log_dictionary) + + @staticmethod + def all_logs(): + return LoggingVolumeDriver._LOGS + + @staticmethod + def logs_like(action, **kwargs): + matches = [] + for entry in LoggingVolumeDriver._LOGS: + if entry['action'] != action: + continue + match = True + for k, v in kwargs.iteritems(): + if entry.get(k) != v: + match = False + break + if match: + matches.append(entry) + return matches -- cgit From 230d07e9002371bdb0030c9199df35fc6360a0a2 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Thu, 24 Mar 2011 03:26:32 -0700 Subject: Test for attach / detach (and associated fixes) --- nova/api/openstack/__init__.py | 2 +- nova/api/openstack/volume_attachments.py | 77 ++++++---- nova/tests/integrated/api/client.py | 15 ++ nova/tests/integrated/integrated_helpers.py | 49 +++++++ nova/tests/integrated/test_login.py | 12 +- nova/tests/integrated/test_servers.py | 37 +---- nova/tests/integrated/test_volumes.py | 215 +++++++++++++++++++++++----- nova/volume/driver.py | 12 +- 8 files changed, 312 insertions(+), 107 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 54d8a738d..e8aa4821b 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -134,7 +134,7 @@ class APIRouter(wsgi.Router): controller=volumes.Controller(), collection={'detail': 'GET'}) - mapper.resource("volume_attachment", "volume_attachment", + mapper.resource("volume_attachment", "volume_attachments", controller=volume_attachments.Controller(), parent_resource=dict(member_name='server', collection_name='servers')) diff --git a/nova/api/openstack/volume_attachments.py b/nova/api/openstack/volume_attachments.py index 1cb2c9494..2ce681e19 100644 --- a/nova/api/openstack/volume_attachments.py +++ b/nova/api/openstack/volume_attachments.py @@ -35,27 +35,29 @@ FLAGS = flags.FLAGS def _translate_detail_view(context, volume): """ Maps keys for details view""" - v = _translate_summary_view(context, volume) + d = _translate_summary_view(context, volume) # No additional data / lookups at the moment - return v + return d -def _translate_summary_view(context, volume): +def _translate_summary_view(context, vol): """ Maps keys for summary view""" - v = {} + d = {} + + volume_id = vol['id'] - volume_id = volume['id'] - # NOTE(justinsb): We use the volume id as the id of the attachment object - v['id'] = volume_id - - v['volumeId'] = volume_id - v['serverId'] = volume['instance_id'] - v['device'] = volume['mountpoint'] + d['id'] = volume_id - return v + d['volumeId'] = volume_id + if vol.get('instance_id'): + d['serverId'] = vol['instance_id'] + if vol.get('mountpoint'): + d['device'] = vol['mountpoint'] + + return d class Controller(wsgi.Controller): @@ -82,16 +84,22 @@ class Controller(wsgi.Controller): return self._items(req, server_id, entity_maker=_translate_summary_view) - def show(self, req, id): + def show(self, req, server_id, id): """Return data about the given volume""" context = req.environ['nova.context'] + volume_id = id try: - vol = self.volume_api.get(context, id) + vol = self.volume_api.get(context, volume_id) except exception.NotFound: + LOG.debug("volume_id not found") return faults.Fault(exc.HTTPNotFound()) - return {'volume': _translate_detail_view(context, vol)} + if str(vol['instance_id']) != server_id: + LOG.debug("instance_id != server_id") + return faults.Fault(exc.HTTPNotFound()) + + return {'volumeAttachment': _translate_detail_view(context, vol)} def create(self, req, server_id): """ Attach a volume to an instance """ @@ -109,15 +117,29 @@ class Controller(wsgi.Controller): " at %(device)s") % locals() LOG.audit(msg, context=context) - self.compute_api.attach_volume(context, - instance_id=instance_id, - volume_id=volume_id, - device=device) - vol = self.volume_api.get(context, volume_id) + try: + self.compute_api.attach_volume(context, + instance_id=instance_id, + volume_id=volume_id, + device=device) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + + # The attach is async + attachment = {} + attachment['id'] = volume_id + attachment['volumeId'] = volume_id - retval = _translate_detail_view(context, vol) + # NOTE(justinsb): And now, we have a problem... + # The attach is async, so there's a window in which we don't see + # the attachment (until the attachment completes). We could also + # get problems with concurrent requests. I think we need an + # attachment state, and to write to the DB here, but that's a bigger + # change. + # For now, we'll probably have to rely on libraries being smart - return {'volumeAttachment': retval} + # TODO: How do I return "accepted" here?? + return {'volumeAttachment': attachment} def update(self, _req, _server_id, _id): """ Update a volume attachment. We don't currently support this.""" @@ -130,10 +152,15 @@ class Controller(wsgi.Controller): volume_id = id LOG.audit(_("Detach volume %s"), volume_id, context=context) - vol = self.volume_api.get(context, volume_id) - if vol['instance_id'] != server_id: + try: + vol = self.volume_api.get(context, volume_id) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + + if str(vol['instance_id']) != server_id: + LOG.debug("instance_id != server_id") return faults.Fault(exc.HTTPNotFound()) - + self.compute_api.detach_volume(context, volume_id=volume_id) diff --git a/nova/tests/integrated/api/client.py b/nova/tests/integrated/api/client.py index 7a4c3198e..deb7fd981 100644 --- a/nova/tests/integrated/api/client.py +++ b/nova/tests/integrated/api/client.py @@ -223,3 +223,18 @@ class TestOpenStackClient(object): def delete_volume(self, volume_id): return self.api_delete('/volumes/%s' % volume_id) + def get_server_volume(self, server_id, attachment_id): + return self.api_get('/servers/%s/volume_attachments/%s' % + (server_id, attachment_id))['volumeAttachment'] + + def get_server_volumes(self, server_id): + return self.api_get('/servers/%s/volume_attachments' % + (server_id))['volumeAttachments'] + + def post_server_volume(self, server_id, volume_attachment): + return self.api_post('/servers/%s/volume_attachments' % + (server_id), volume_attachment)['volumeAttachment'] + + def delete_server_volume(self, server_id, attachment_id): + return self.api_delete('/servers/%s/volume_attachments/%s' % + (server_id, attachment_id)) diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py index f24759032..b520cc5d3 100644 --- a/nova/tests/integrated/integrated_helpers.py +++ b/nova/tests/integrated/integrated_helpers.py @@ -125,6 +125,7 @@ class IntegratedUnitTestContext(object): self._configure_project(self.project_name, self.test_user) def _start_services(self): + self._start_compute_service() self._start_volume_service() self._start_scheduler_service() @@ -133,6 +134,12 @@ class IntegratedUnitTestContext(object): if not self.api_service: self._start_api_service() + def _start_compute_service(self): + compute_service = service.Service.create(binary='nova-compute') + compute_service.start() + self.services.append(compute_service) + return compute_service + def _start_volume_service(self): volume_service = service.Service.create(binary='nova-volume') volume_service.start() @@ -223,3 +230,45 @@ class IntegratedUnitTestContext(object): # WSGI shutdown broken :-( # bug731668 #IntegratedUnitTestContext.__INSTANCE = None + + +class _IntegratedTestBase(test.TestCase): + def setUp(self): + super(_IntegratedTestBase, self).setUp() + + self._setup_flags() + + context = IntegratedUnitTestContext.startup() + self.user = context.test_user + self.api = self.user.openstack_api + + def tearDown(self): + IntegratedUnitTestContext.shutdown() + super(_IntegratedTestBase, self).tearDown() + + def _setup_flags(self): + """An opportunity to setup flags, before the services are started""" + pass + + def _build_minimal_create_server_request(self): + server = {} + + image = self.user.get_valid_image(create=True) + image_id = image['id'] + + #TODO(justinsb): This is FUBAR + image_id = abs(hash(image_id)) + + # We now have a valid imageId + server['imageId'] = image_id + + # Set a valid flavorId + flavor = self.api.get_flavors()[0] + LOG.debug("Using flavor: %s" % flavor) + server['flavorId'] = flavor['id'] + + # Set a valid server name + server_name = self.user.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 501f8c919..cc3d555d0 100644 --- a/nova/tests/integrated/test_login.py +++ b/nova/tests/integrated/test_login.py @@ -30,17 +30,7 @@ FLAGS = flags.FLAGS FLAGS.verbose = True -class LoginTest(test.TestCase): - def setUp(self): - super(LoginTest, self).setUp() - context = integrated_helpers.IntegratedUnitTestContext.startup() - self.user = context.test_user - self.api = self.user.openstack_api - - def tearDown(self): - integrated_helpers.IntegratedUnitTestContext.shutdown() - super(LoginTest, self).tearDown() - +class LoginTest(integrated_helpers._IntegratedTestBase): def test_login(self): """Simple check - we list flavors - so we know we're logged in""" flavors = self.api.get_flavors() diff --git a/nova/tests/integrated/test_servers.py b/nova/tests/integrated/test_servers.py index 3c38295c5..2b5d3324a 100644 --- a/nova/tests/integrated/test_servers.py +++ b/nova/tests/integrated/test_servers.py @@ -31,20 +31,10 @@ FLAGS = flags.FLAGS FLAGS.verbose = True -class ServersTest(test.TestCase): - def setUp(self): - super(ServersTest, self).setUp() - +class ServersTest(integrated_helpers._IntegratedTestBase): + def _setup_flags(self): self.flags(image_service='nova.image.fake.MockImageService') - context = integrated_helpers.IntegratedUnitTestContext.startup() - self.user = context.test_user - self.api = self.user.openstack_api - - def tearDown(self): - integrated_helpers.IntegratedUnitTestContext.shutdown() - super(ServersTest, self).tearDown() - def test_get_servers(self): """Simple check that listing servers works.""" servers = self.api.get_servers() @@ -145,29 +135,6 @@ class ServersTest(test.TestCase): # Should be gone self.assertFalse(found_server) - def _build_minimal_create_server_request(self): - server = {} - - image = self.user.get_valid_image(create=True) - image_id = image['id'] - - #TODO(justinsb): This is FUBAR - image_id = abs(hash(image_id)) - - # We now have a valid imageId - server['imageId'] = image_id - - # Set a valid flavorId - flavor = self.api.get_flavors()[0] - LOG.debug("Using flavor: %s" % flavor) - server['flavorId'] = flavor['id'] - - # Set a valid server name - server_name = self.user.get_unused_server_name() - server['name'] = server_name - - return server - # TODO(justinsb): Enable this unit test when the metadata bug is fixed # def test_create_server_with_metadata(self): # """Creates a server with metadata""" diff --git a/nova/tests/integrated/test_volumes.py b/nova/tests/integrated/test_volumes.py index 66b773db2..f69361fb0 100644 --- a/nova/tests/integrated/test_volumes.py +++ b/nova/tests/integrated/test_volumes.py @@ -32,20 +32,15 @@ FLAGS = flags.FLAGS FLAGS.verbose = True -class VolumesTest(test.TestCase): +class VolumesTest(integrated_helpers._IntegratedTestBase): def setUp(self): super(VolumesTest, self).setUp() + driver.LoggingVolumeDriver.clear_logs() + def _setup_flags(self): self.flags(image_service='nova.image.fake.MockImageService', volume_driver='nova.volume.driver.LoggingVolumeDriver') - - context = integrated_helpers.IntegratedUnitTestContext.startup() - self.user = context.test_user - self.api = self.user.openstack_api - - def tearDown(self): - integrated_helpers.IntegratedUnitTestContext.shutdown() - super(VolumesTest, self).tearDown() + self.flags(use_local_volumes=False) # Avoids calling local_path def test_get_volumes(self): """Simple check that listing volumes works""" @@ -53,10 +48,34 @@ class VolumesTest(test.TestCase): for volume in volumes: LOG.debug("volume: %s" % volume) + def _poll_while(self, volume_id, continue_states, max_retries=5): + """ Poll (briefly) while the state is in continue_states""" + retries = 0 + while True: + try: + found_volume = self.api.get_volume(volume_id) + except client.OpenStackApiNotFoundException: + found_volume = None + LOG.debug("Got 404, proceeding") + break + + LOG.debug("Found %s" % found_volume) + + self.assertEqual(volume_id, found_volume['id']) + + if not found_volume['status'] in continue_states: + break + + time.sleep(1) + retries = retries + 1 + if retries > max_retries: + break + return found_volume + def test_create_and_delete_volume(self): """Creates and deletes a volume""" - # Create volume with name + # Create volume created_volume = self.api.post_volume({'volume': {'size': 1}}) LOG.debug("created_volume: %s" % created_volume) self.assertTrue(created_volume['id']) @@ -72,14 +91,7 @@ class VolumesTest(test.TestCase): self.assertTrue(created_volume_id in volume_names) # Wait (briefly) for creation. Delay is due to the 'message queue' - retries = 0 - while found_volume['status'] == 'creating': - LOG.debug("Found %s" % found_volume) - time.sleep(1) - found_volume = self.api.get_volume(created_volume_id) - retries = retries + 1 - if retries > 5: - break + found_volume = self._poll_while(created_volume_id, ['creating']) # It should be available... self.assertEqual('available', found_volume['status']) @@ -88,18 +100,7 @@ class VolumesTest(test.TestCase): self.api.delete_volume(created_volume_id) # Wait (briefly) for deletion. Delay is due to the 'message queue' - for retries in range(5): - try: - found_volume = self.api.get_volume(created_volume_id) - except client.OpenStackApiNotFoundException: - found_volume = None - LOG.debug("Got 404, proceeding") - break - - LOG.debug("Found_volume=%s" % found_volume) - if found_volume['status'] != 'deleting': - break - time.sleep(1) + found_volume = self._poll_while(created_volume_id, ['deleting']) # Should be gone self.assertFalse(found_volume) @@ -110,7 +111,7 @@ class VolumesTest(test.TestCase): 'create_volume', id=created_volume_id) LOG.debug("Create_Actions: %s" % create_actions) - + self.assertEquals(1, len(create_actions)) create_action = create_actions[0] self.assertEquals(create_action['id'], created_volume_id) @@ -132,6 +133,156 @@ class VolumesTest(test.TestCase): delete_action = export_actions[0] self.assertEquals(delete_action['id'], created_volume_id) + def test_attach_and_detach_volume(self): + """Creates, attaches, detaches and deletes a volume""" + + # Create server + server_req = {'server': self._build_minimal_create_server_request()} + # NOTE(justinsb): Create an extra server so that server_id != volume_id + self.api.post_server(server_req) + created_server = self.api.post_server(server_req) + LOG.debug("created_server: %s" % created_server) + server_id = created_server['id'] + + # Create volume + created_volume = self.api.post_volume({'volume': {'size': 1}}) + LOG.debug("created_volume: %s" % created_volume) + volume_id = created_volume['id'] + self._poll_while(volume_id, ['creating']) + + # Check we've got different IDs + self.assertNotEqual(server_id, volume_id) + + # List current server attachments - should be none + attachments = self.api.get_server_volumes(server_id) + self.assertEquals([], attachments) + + # Template attach request + device = '/dev/sdc' + attach_req = { 'device': device } + post_req = { 'volumeAttachment': attach_req } + + # Try to attach to a non-existent volume; should fail + attach_req['volumeId'] = 3405691582 + self.assertRaises(client.OpenStackApiNotFoundException, + self.api.post_server_volume, server_id, post_req) + + # Try to attach to a non-existent server; should fail + attach_req['volumeId'] = volume_id + self.assertRaises(client.OpenStackApiNotFoundException, + self.api.post_server_volume, 3405691582, post_req) + + # Should still be no attachments... + attachments = self.api.get_server_volumes(server_id) + self.assertEquals([], attachments) + + # Do a real attach + attach_req['volumeId'] = volume_id + attach_result = self.api.post_server_volume(server_id, post_req) + LOG.debug(_("Attachment = %s") % attach_result) + + attachment_id = attach_result['id'] + self.assertEquals(volume_id, attach_result['volumeId']) + + # These fields aren't set because it's async + #self.assertEquals(server_id, attach_result['serverId']) + #self.assertEquals(device, attach_result['device']) + + # This is just an implementation detail, but let's check it... + self.assertEquals(volume_id, attachment_id) + + # NOTE(justinsb): There's an issue with the attach code, in that + # it's currently asynchronous and not recorded until the attach + # completes. So the caller must be 'smart', like this... + attach_done = None + retries = 0 + while True: + try: + attach_done = self.api.get_server_volume(server_id, + attachment_id) + break + except client.OpenStackApiNotFoundException: + LOG.debug("Got 404, waiting") + + time.sleep(1) + retries = retries + 1 + if retries > 10: + break + + expect_attach = {} + expect_attach['id'] = volume_id + expect_attach['volumeId'] = volume_id + expect_attach['serverId'] = server_id + expect_attach['device'] = device + + self.assertEqual(expect_attach, attach_done) + + # Should be one attachemnt + attachments = self.api.get_server_volumes(server_id) + self.assertEquals([expect_attach], attachments) + + # Should be able to get details + attachment_info = self.api.get_server_volume(server_id, attachment_id) + self.assertEquals(expect_attach, attachment_info) + + # Getting details on a different id should fail + self.assertRaises(client.OpenStackApiNotFoundException, + self.api.get_server_volume, server_id, 3405691582) + self.assertRaises(client.OpenStackApiNotFoundException, + self.api.get_server_volume, + 3405691582, attachment_id) + + # Trying to detach a different id should fail + self.assertRaises(client.OpenStackApiNotFoundException, + self.api.delete_server_volume, server_id, 3405691582) + + # Detach should work + self.api.delete_server_volume(server_id, attachment_id) + + # Again, it's async, so wait... + retries = 0 + while True: + try: + attachment = self.api.get_server_volume(server_id, + attachment_id) + LOG.debug("Attachment still there: %s" % attachment) + except client.OpenStackApiNotFoundException: + LOG.debug("Got 404, delete done") + break + + time.sleep(1) + retries = retries + 1 + self.assertTrue(retries < 10) + + # Should be no attachments again + attachments = self.api.get_server_volumes(server_id) + self.assertEquals([], attachments) + + LOG.debug("Logs: %s" % driver.LoggingVolumeDriver.all_logs()) + + # Discover_volume and undiscover_volume are called from compute + # on attach/detach + + disco_moves = driver.LoggingVolumeDriver.logs_like( + 'discover_volume', + id=volume_id) + LOG.debug("discover_volume actions: %s" % disco_moves) + + self.assertEquals(1, len(disco_moves)) + disco_move = disco_moves[0] + self.assertEquals(disco_move['id'], volume_id) + + last_days_of_disco_moves = driver.LoggingVolumeDriver.logs_like( + 'undiscover_volume', + id=volume_id) + LOG.debug("undiscover_volume actions: %s" % last_days_of_disco_moves) + + self.assertEquals(1, len(last_days_of_disco_moves)) + undisco_move = last_days_of_disco_moves[0] + self.assertEquals(undisco_move['id'], volume_id) + self.assertEquals(undisco_move['mountpoint'], device) + self.assertEquals(undisco_move['instance_id'], server_id) + if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/nova/volume/driver.py b/nova/volume/driver.py index 148e5facd..045974fa3 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -135,7 +135,7 @@ class VolumeDriver(object): """Removes an export for a logical volume.""" raise NotImplementedError() - def discover_volume(self, volume): + def discover_volume(self, context, volume): """Discover volume on a remote host.""" raise NotImplementedError() @@ -574,6 +574,8 @@ class RBDDriver(VolumeDriver): def discover_volume(self, volume): """Discover volume on a remote host""" + #NOTE(justinsb): This is messed up... discover_volume takes 3 args + # but then that would break local_path return "rbd:%s/%s" % (FLAGS.rbd_pool, volume['name']) def undiscover_volume(self, volume): @@ -622,7 +624,7 @@ class SheepdogDriver(VolumeDriver): """Removes an export for a logical volume""" pass - def discover_volume(self, volume): + def discover_volume(self, context, volume): """Discover volume on a remote host""" return "sheepdog:%s" % volume['name'] @@ -656,7 +658,7 @@ class LoggingVolumeDriver(VolumeDriver): def remove_export(self, context, volume): self.log_action('remove_export', volume) - def discover_volume(self, volume): + def discover_volume(self, context, volume): self.log_action('discover_volume', volume) def undiscover_volume(self, volume): @@ -667,6 +669,10 @@ class LoggingVolumeDriver(VolumeDriver): _LOGS = [] + @staticmethod + def clear_logs(): + LoggingVolumeDriver._LOGS = [] + @staticmethod def log_action(action, parameters): """Logs the command.""" -- cgit From d49219f8b6dd626b868b99bee8a22c4ac5495af1 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Thu, 24 Mar 2011 03:28:59 -0700 Subject: pep8 fixes --- nova/api/openstack/__init__.py | 1 + nova/api/openstack/volume_attachments.py | 10 +++++----- nova/tests/integrated/api/client.py | 2 +- nova/tests/integrated/test_volumes.py | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index e8aa4821b..030974482 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -141,6 +141,7 @@ class APIRouter(wsgi.Router): super(APIRouter, self).__init__(mapper) + class APIRouterV10(APIRouter): """Define routes specific to OpenStack API V1.0.""" diff --git a/nova/api/openstack/volume_attachments.py b/nova/api/openstack/volume_attachments.py index 2ce681e19..58a9a727b 100644 --- a/nova/api/openstack/volume_attachments.py +++ b/nova/api/openstack/volume_attachments.py @@ -62,17 +62,17 @@ def _translate_summary_view(context, vol): class Controller(wsgi.Controller): """ The volume attachment API controller for the Openstack API - + A child resource of the server. Note that we use the volume id as the ID of the attachment (though this is not guaranteed externally)""" _serialization_metadata = { 'application/xml': { 'attributes': { - 'volumeAttachment': [ 'id', - 'serverId', - 'volumeId', - 'device' ]}}} + 'volumeAttachment': ['id', + 'serverId', + 'volumeId', + 'device']}}} def __init__(self): self.compute_api = compute.API() diff --git a/nova/tests/integrated/api/client.py b/nova/tests/integrated/api/client.py index deb7fd981..023871bda 100644 --- a/nova/tests/integrated/api/client.py +++ b/nova/tests/integrated/api/client.py @@ -226,7 +226,7 @@ class TestOpenStackClient(object): def get_server_volume(self, server_id, attachment_id): return self.api_get('/servers/%s/volume_attachments/%s' % (server_id, attachment_id))['volumeAttachment'] - + def get_server_volumes(self, server_id): return self.api_get('/servers/%s/volume_attachments' % (server_id))['volumeAttachments'] diff --git a/nova/tests/integrated/test_volumes.py b/nova/tests/integrated/test_volumes.py index f69361fb0..aa90301a5 100644 --- a/nova/tests/integrated/test_volumes.py +++ b/nova/tests/integrated/test_volumes.py @@ -159,8 +159,8 @@ class VolumesTest(integrated_helpers._IntegratedTestBase): # Template attach request device = '/dev/sdc' - attach_req = { 'device': device } - post_req = { 'volumeAttachment': attach_req } + attach_req = {'device': device} + post_req = {'volumeAttachment': attach_req} # Try to attach to a non-existent volume; should fail attach_req['volumeId'] = 3405691582 -- cgit From bebc9504bb34934147705512413267d1ae4af170 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Thu, 24 Mar 2011 04:02:31 -0700 Subject: Fake out network service as well, otherwise we can't terminate the instance in test_servers now that we've started a compute service --- nova/tests/integrated/integrated_helpers.py | 17 ++++++++++++++--- nova/tests/integrated/test_login.py | 1 - nova/tests/integrated/test_servers.py | 7 ++++--- nova/tests/integrated/test_volumes.py | 11 ++++++----- nova/virt/fake.py | 8 ++++++++ 5 files changed, 32 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py index b520cc5d3..3b7caecd6 100644 --- a/nova/tests/integrated/integrated_helpers.py +++ b/nova/tests/integrated/integrated_helpers.py @@ -128,6 +128,7 @@ class IntegratedUnitTestContext(object): self._start_compute_service() self._start_volume_service() self._start_scheduler_service() + self._start_network_service() # WSGI shutdown broken :-( # bug731668 @@ -140,6 +141,12 @@ class IntegratedUnitTestContext(object): self.services.append(compute_service) return compute_service + def _start_network_service(self): + network_service = service.Service.create(binary='nova-network') + network_service.start() + self.services.append(network_service) + return network_service + def _start_volume_service(self): volume_service = service.Service.create(binary='nova-volume') volume_service.start() @@ -236,7 +243,8 @@ class _IntegratedTestBase(test.TestCase): def setUp(self): super(_IntegratedTestBase, self).setUp() - self._setup_flags() + f = self._get_flags() + self.flags(**f) context = IntegratedUnitTestContext.startup() self.user = context.test_user @@ -246,9 +254,12 @@ class _IntegratedTestBase(test.TestCase): IntegratedUnitTestContext.shutdown() super(_IntegratedTestBase, self).tearDown() - def _setup_flags(self): + def _get_flags(self): """An opportunity to setup flags, before the services are started""" - pass + f = {} + #f['network_driver'] = 'nova.network.fake.FakeNetworkDriver' + f['fake_network'] = True + return f def _build_minimal_create_server_request(self): server = {} diff --git a/nova/tests/integrated/test_login.py b/nova/tests/integrated/test_login.py index cc3d555d0..d6e067c29 100644 --- a/nova/tests/integrated/test_login.py +++ b/nova/tests/integrated/test_login.py @@ -18,7 +18,6 @@ import unittest from nova import flags -from nova import test from nova.log import logging from nova.tests.integrated import integrated_helpers from nova.tests.integrated.api import client diff --git a/nova/tests/integrated/test_servers.py b/nova/tests/integrated/test_servers.py index 2b5d3324a..ed6522c38 100644 --- a/nova/tests/integrated/test_servers.py +++ b/nova/tests/integrated/test_servers.py @@ -19,7 +19,6 @@ import time import unittest from nova import flags -from nova import test from nova.log import logging from nova.tests.integrated import integrated_helpers from nova.tests.integrated.api import client @@ -32,8 +31,10 @@ FLAGS.verbose = True class ServersTest(integrated_helpers._IntegratedTestBase): - def _setup_flags(self): - self.flags(image_service='nova.image.fake.MockImageService') + def _get_flags(self): + f = super(ServersTest, self)._get_flags() + f['image_service'] = 'nova.image.fake.MockImageService' + return f def test_get_servers(self): """Simple check that listing servers works.""" diff --git a/nova/tests/integrated/test_volumes.py b/nova/tests/integrated/test_volumes.py index aa90301a5..701e9fe3c 100644 --- a/nova/tests/integrated/test_volumes.py +++ b/nova/tests/integrated/test_volumes.py @@ -19,7 +19,6 @@ import unittest import time from nova import flags -from nova import test from nova.log import logging from nova.tests.integrated import integrated_helpers from nova.tests.integrated.api import client @@ -37,10 +36,12 @@ class VolumesTest(integrated_helpers._IntegratedTestBase): super(VolumesTest, self).setUp() driver.LoggingVolumeDriver.clear_logs() - def _setup_flags(self): - self.flags(image_service='nova.image.fake.MockImageService', - volume_driver='nova.volume.driver.LoggingVolumeDriver') - self.flags(use_local_volumes=False) # Avoids calling local_path + def _get_flags(self): + f = super(VolumesTest, self)._get_flags() + f['use_local_volumes'] = False # Avoids calling local_path + f['image_service'] = 'nova.image.fake.MockImageService' + f['volume_driver'] = 'nova.volume.driver.LoggingVolumeDriver' + return f def test_get_volumes(self): """Simple check that listing volumes works""" diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 3a06284a1..505ce0959 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -26,9 +26,13 @@ semantics of real hypervisor connections. """ from nova import exception +from nova import log as logging from nova.compute import power_state +LOG = logging.getLogger('nova.compute.disk') + + def get_connection(_): # The read_only parameter is ignored. return FakeConnection.instance() @@ -244,6 +248,10 @@ class FakeConnection(object): The work will be done asynchronously. This function returns a task that allows the caller to detect when it is complete. """ + key = instance.name + if not key in self.instances: + LOG.warning("Key '%s' not in instances '%s'" % + (key, self.instances)) del self.instances[instance.name] def attach_volume(self, instance_name, device_path, mountpoint): -- cgit From 83b25c2c8214462ab7f6b6ba76efdfba8c1de937 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Thu, 24 Mar 2011 04:37:21 -0700 Subject: Grrr... because we're not recycling the API yet, we have to configure flags the first time it's called. --- nova/tests/integrated/integrated_helpers.py | 7 +++++-- nova/tests/integrated/test_volumes.py | 1 - 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py index 3b7caecd6..953af2e75 100644 --- a/nova/tests/integrated/integrated_helpers.py +++ b/nova/tests/integrated/integrated_helpers.py @@ -19,6 +19,7 @@ Provides common functionality for integrated unit tests """ +import os import random import string @@ -255,9 +256,11 @@ class _IntegratedTestBase(test.TestCase): super(_IntegratedTestBase, self).tearDown() def _get_flags(self): - """An opportunity to setup flags, before the services are started""" + """An opportunity to setup flags, before the services are started + + Warning - this is a bit flaky till the WSGI recycle code lands""" f = {} - #f['network_driver'] = 'nova.network.fake.FakeNetworkDriver' + f['image_service'] = 'nova.image.fake.MockImageService' f['fake_network'] = True return f diff --git a/nova/tests/integrated/test_volumes.py b/nova/tests/integrated/test_volumes.py index 701e9fe3c..f173efea7 100644 --- a/nova/tests/integrated/test_volumes.py +++ b/nova/tests/integrated/test_volumes.py @@ -39,7 +39,6 @@ class VolumesTest(integrated_helpers._IntegratedTestBase): def _get_flags(self): f = super(VolumesTest, self)._get_flags() f['use_local_volumes'] = False # Avoids calling local_path - f['image_service'] = 'nova.image.fake.MockImageService' f['volume_driver'] = 'nova.volume.driver.LoggingVolumeDriver' return f -- cgit From 1378db7ac86b69b8a966448b63415b2136b6b5bc Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Thu, 24 Mar 2011 09:07:57 -0400 Subject: Fix up formatting of libvirt.xml.template --- nova/virt/libvirt.xml.template | 132 ++++++++++++++++++++--------------------- 1 file changed, 66 insertions(+), 66 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template index 77c1b1997..26f528cb1 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -3,47 +3,47 @@ ${memory_kb} #if $type == 'lxc' - #set $disk_prefix = '' + #set $disk prefix = '' #set $disk_bus = '' exe /sbin/init #else -#if $type == 'uml' - #set $disk_prefix = 'ubd' - #set $disk_bus = 'uml' - uml - /usr/bin/linux - /dev/ubda -#else - #if $type == 'xen' - #set $disk_prefix = 'sd' - #set $disk_bus = 'scsi' - linux - /dev/xvda - #else - #set $disk_prefix = 'vd' - #set $disk_bus = 'virtio' - hvm - #end if - #if $getVar('rescue', False) - ${basepath}/kernel.rescue - ${basepath}/ramdisk.rescue - #else - #if $getVar('kernel', None) - ${kernel} - #if $type == 'xen' - ro - #else - root=/dev/vda console=ttyS0 - #end if - #if $getVar('ramdisk', None) - ${ramdisk} - #end if - #else - - #end if - #end if - #end if + #if $type == 'uml' + #set $disk_prefix = 'ubd' + #set $disk_bus = 'uml' + uml + /usr/bin/linux + /dev/ubda + #else + #if $type == 'xen' + #set $disk_prefix = 'sd' + #set $disk_bus = 'scsi' + linux + /dev/xvda + #else + #set $disk_prefix = 'vd' + #set $disk_bus = 'virtio' + hvm + #end if + #if $getVar('rescue', False) + ${basepath}/kernel.rescue + ${basepath}/ramdisk.rescue + #else + #if $getVar('kernel', None) + ${kernel} + #if $type == 'xen' + ro + #else + root=/dev/vda console=ttyS0 + #end if + #if $getVar('ramdisk', None) + ${ramdisk} + #end if + #else + + #end if + #end if + #end if #end if @@ -52,36 +52,36 @@ ${vcpus} #if $type == 'lxc' - - - - -#else -#if $getVar('rescue', False) - - - - - - - - - - + + + + #else - - - - - - #if $getVar('local', False) - - - - - - #end if - #end if + #if $getVar('rescue', False) + + + + + + + + + + + #else + + + + + + #if $getVar('local', False) + + + + + + #end if +#end if #end if -- cgit From da159d18b56af44f93cbf2c5e80b6aa3c98d5187 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Thu, 24 Mar 2011 09:12:24 -0400 Subject: Dont use popen in dettaching the lxc loop --- nova/virt/disk.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/disk.py b/nova/virt/disk.py index f6e6795d6..0bdb04cde 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -140,7 +140,8 @@ def destroy_container(target, instance): container_dir = '%s/rootfs' % target utils.execute('sudo', 'umount', container_dir) finally: - for loop in utils.popen('sudo losetup -a').readlines(): + out, err = utils('sudo', 'losetup', '-a') + for loop in out.splitlines(): if instance['name'] in loop: device = loop.split(loop, ':') utils.execute('sudo', 'losetup', '--detach', device) -- cgit From fa6b969a2a7c9252aaebc4a56d82f9b04e46910c Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 24 Mar 2011 10:34:34 -0400 Subject: Merged trunk and fixed tests. --- nova/tests/api/openstack/test_images.py | 40 +++++++++++++++------------------ 1 file changed, 18 insertions(+), 22 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index f64ee16a1..3a5d821d3 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -20,6 +20,7 @@ Tests of the new image services, both as a service layer, and as a WSGI layer """ +import copy import json import datetime import os @@ -193,6 +194,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): """ # Registered images at start of each test. now = datetime.datetime.utcnow() + formated_date = now.strftime('%Y-%m-%dT%H:%M:%SZ') IMAGE_FIXTURES = [ {'id': '23g2ogk23k4hhkk4k42l', 'imageId': '23g2ogk23k4hhkk4k42l', @@ -256,13 +258,13 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): actual_image = json.loads(response.body) - expected = self.IMAGE_FIXTURES[0] + expected = copy.copy(self.IMAGE_FIXTURES[0]) expected_image = { "image": { "id": expected["id"], "name": expected["name"], - "updated": expected["updated_at"], - "created": expected["created_at"], + "updated": self.formated_date, + "created": self.formated_date, "status": expected["status"], }, } @@ -275,15 +277,15 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): actual_image = json.loads(response.body) - expected = self.IMAGE_FIXTURES[0] + expected = copy.copy(self.IMAGE_FIXTURES[0]) href = "http://localhost/v1.1/images/%s" % expected["id"] expected_image = { "image": { "id": expected["id"], "name": expected["name"], - "updated": expected["updated_at"], - "created": expected["created_at"], + "updated": self.formated_date, + "created": self.formated_date, "status": expected["status"], "links": [{ "rel": "self", @@ -311,7 +313,9 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): actual_image = parseString(response.body.replace(" ", "")) - expected = self.IMAGE_FIXTURES[0] + expected = copy.copy(self.IMAGE_FIXTURES[0]) + expected["updated_at"] = self.formated_date + expected["created_at"] = self.formated_date expected_image = parseString(""" Date: Thu, 24 Mar 2011 12:15:33 -0400 Subject: Renamed __image and __compute to better describe their purposes. Use os.path.join to create href as per suggestion. Added base get_builder as per pychecker suggestion. --- nova/api/openstack/images.py | 24 ++++++++++++++---------- nova/api/openstack/views/images.py | 4 +++- 2 files changed, 17 insertions(+), 11 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index dc07348e4..b01d991e8 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -50,8 +50,8 @@ class Controller(wsgi.Controller): """ _default_service = utils.import_object(flags.FLAGS.image_service) - self.__compute = compute_service or compute.API() - self.__image = image_service or _default_service + self._compute_service = compute_service or compute.API() + self._image_service = image_service or _default_service def index(self, req): """ @@ -60,7 +60,7 @@ class Controller(wsgi.Controller): @param req: `wsgi.Request` object """ context = req.environ['nova.context'] - images = self.__image.index(context) + images = self._image_service.index(context) build = self.get_builder(req).build return dict(images=[build(image, False) for image in images]) @@ -71,7 +71,7 @@ class Controller(wsgi.Controller): @param req: `wsgi.Request` object. """ context = req.environ['nova.context'] - images = self.__image.detail(context) + images = self._image_service.detail(context) build = self.get_builder(req).build return dict(images=[build(image, True) for image in images]) @@ -86,7 +86,7 @@ class Controller(wsgi.Controller): context = req.environ['nova.context'] try: - image = self.__image.show(context, image_id) + image = self._image_service.show(context, image_id) except exception.NotFound: ex = webob.exc.HTTPNotFound(explanation="Image not found.") raise faults.Fault(ex) @@ -102,8 +102,8 @@ class Controller(wsgi.Controller): """ image_id = id context = req.environ['nova.context'] - self.__image.delete(context, image_id) - return exc.HTTPNoContent() + self._image_service.delete(context, image_id) + return webob.exc.HTTPNoContent() def create(self, req): """ @@ -116,17 +116,21 @@ class Controller(wsgi.Controller): image = self._deserialize(req.body, content_type) if not image: - raise exc.HTTPBadRequest() + raise webob.exc.HTTPBadRequest() try: server_id = image["image"]["serverId"] image_name = image["image"]["name"] except KeyError: - raise exc.HTTPBadRequest() + raise webob.exc.HTTPBadRequest() - image = self.__compute.snapshot(context, server_id, image_name) + image = self._compute_service.snapshot(context, server_id, image_name) return self.get_builder(req).build(image, True) + def get_builder(self, request): + """Indicates that you must use a Controller subclass.""" + raise NotImplementedError + class ControllerV10(Controller): """ diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index 9d0bad095..bab1f0bac 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -15,6 +15,8 @@ # License for the specific language governing permissions and limitations # under the License. +import os.path + class ViewBuilder(object): """ @@ -37,7 +39,7 @@ class ViewBuilder(object): """ Return an href string pointing to this object. """ - return "%s/images/%s" % (self._url, image_id) + return os.path.join(self._url, "images", str(image_id)) def build(self, image_obj, detail=False): """ -- cgit From a10f719cdbc666171d8923ae1fd65bac3d6ebda7 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Thu, 24 Mar 2011 10:49:19 -0700 Subject: Forgot one set of flags --- nova/tests/integrated/test_servers.py | 5 ----- 1 file changed, 5 deletions(-) (limited to 'nova') diff --git a/nova/tests/integrated/test_servers.py b/nova/tests/integrated/test_servers.py index ed6522c38..c4676adc8 100644 --- a/nova/tests/integrated/test_servers.py +++ b/nova/tests/integrated/test_servers.py @@ -31,11 +31,6 @@ FLAGS.verbose = True class ServersTest(integrated_helpers._IntegratedTestBase): - def _get_flags(self): - f = super(ServersTest, self)._get_flags() - f['image_service'] = 'nova.image.fake.MockImageService' - return f - def test_get_servers(self): """Simple check that listing servers works.""" servers = self.api.get_servers() -- cgit From 71bd388a6c04df68e4392dbb7354cc8b14f596fe Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Thu, 24 Mar 2011 15:41:02 -0700 Subject: fix typo --- nova/virt/libvirt.xml.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template index bcc6b3aed..ff98275fc 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -102,7 +102,7 @@ #if $getVar('vnc_compute_host_iface', False) - + #end if -- cgit From b01742ddb5bfec7e89ccc4cee17800614a0fce3c Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Thu, 24 Mar 2011 15:55:29 -0700 Subject: incorporate feedback from termie --- nova/compute/api.py | 15 ++++++++------- nova/compute/manager.py | 2 +- nova/flags.py | 4 ++-- nova/virt/libvirt.xml.template | 4 ++-- nova/virt/libvirt_conn.py | 3 ++- nova/vnc/auth.py | 22 +++++++++++----------- nova/vnc/proxy.py | 15 ++++++++------- 7 files changed, 34 insertions(+), 31 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index cec978d75..cb3898f72 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -477,21 +477,22 @@ class API(base.Base): output['token'])} def get_vnc_console(self, context, instance_id): - """Get a url to an AJAX Console""" + """Get a url to a VNC Console.""" instance = self.get(context, instance_id) output = self._call_compute_message('get_vnc_console', context, instance_id) rpc.cast(context, '%s' % FLAGS.vnc_console_proxy_topic, {'method': 'authorize_vnc_console', - 'args': {'token': output['token'], 'host': output['host'], + 'args': {'token': output['token'], + 'host': output['host'], 'port': output['port']}}) - time.sleep(1) - - return {'url': '%s/vnc_auto.html?token=%s&host=%s&port=%s' % - (FLAGS.vnc_console_proxy_url, - output['token'], 'hostignore', 'portignore')} + return {'url': '%s/vnc_auto.html?token=%s&host=%s&port=%s' % ( + FLAGS.vnc_console_proxy_url, + output['token'], + 'hostignore', + 'portignore')} def get_console_output(self, context, instance_id): """Get console output for an an instance""" diff --git a/nova/compute/manager.py b/nova/compute/manager.py index e53b36b34..64982d8ff 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -559,7 +559,7 @@ class ComputeManager(manager.Manager): @exception.wrap_exception def get_vnc_console(self, context, instance_id): - """Return connection information for an vnc console""" + """Return connection information for an vnc console.""" context = context.elevated() LOG.debug(_("instance %s: getting vnc console"), instance_id) instance_ref = self.db.instance_get(context, instance_id) diff --git a/nova/flags.py b/nova/flags.py index a0ea10795..1d2469206 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -287,8 +287,8 @@ DEFINE_string('vnc_console_proxy_url', 'http://127.0.0.1:6080', 'location of vnc console proxy, \ in the form "http://127.0.0.1:6080"') -DEFINE_string('vnc_compute_host_iface', '0.0.0.0', - 'the compute host interface on which vnc server should listen') +DEFINE_string('vnc_server_host', '0.0.0.0', + 'the host interface on which vnc server should listen') DEFINE_bool('vnc_enabled', True, 'enable vnc related features') DEFINE_bool('verbose', False, 'show debug output') diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template index ff98275fc..609784982 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -101,8 +101,8 @@ -#if $getVar('vnc_compute_host_iface', False) - +#if $getVar('vnc_server_host', False) + #end if diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index c3529f512..cb6384e01 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -516,6 +516,7 @@ class LibvirtConnection(object): def get_vnc_port_for_instance(instance_name): virt_dom = self._conn.lookupByName(instance_name) xml = virt_dom.XMLDesc(0) + # TODO: use etree instead of minidom dom = minidom.parseString(xml) for graphic in dom.getElementsByTagName('graphics'): @@ -735,7 +736,7 @@ class LibvirtConnection(object): 'driver_type': driver_type} if FLAGS.vnc_enabled: - xml_info['vnc_compute_host_iface'] = FLAGS.vnc_compute_host_iface + xml_info['vnc_server_host'] = FLAGS.vnc_server_host if ra_server: xml_info['ra_server'] = ra_server + "/128" if not rescue: diff --git a/nova/vnc/auth.py b/nova/vnc/auth.py index 676cb2360..1c6a638fc 100644 --- a/nova/vnc/auth.py +++ b/nova/vnc/auth.py @@ -18,11 +18,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Auth Components for VNC Console""" +"""Auth Components for VNC Console.""" import time import urlparse import webob + from webob import Request from nova import flags @@ -32,12 +33,13 @@ from nova import utils from nova import wsgi from nova import vnc + LOG = logging.getLogger('nova.vnc-proxy') FLAGS = flags.FLAGS class NovaAuthMiddleware(object): - """Implementation of Middleware to Handle Nova Auth""" + """Implementation of Middleware to Handle Nova Auth.""" def __init__(self, app): self.app = app @@ -67,13 +69,12 @@ class NovaAuthMiddleware(object): middleware = self middleware.tokens = {} - class Callback: - def __call__(self, data, message): - if data['method'] == 'authorize_vnc_console': - token = data['args']['token'] - LOG.audit(_("Received Token: %s)"), token) - middleware.tokens[token] = \ - {'args': data['args'], 'last_activity_at': time.time()} + def callback(self, data, message): + if data['method'] == 'authorize_vnc_console': + token = data['args']['token'] + LOG.audit(_("Received Token: %s)"), token) + middleware.tokens[token] = \ + {'args': data['args'], 'last_activity_at': time.time()} def delete_expired_tokens(): now = time.time() @@ -90,7 +91,7 @@ class NovaAuthMiddleware(object): consumer = rpc.TopicConsumer( connection=conn, topic=FLAGS.vnc_console_proxy_topic) - consumer.register_callback(Callback()) + consumer.register_callback(callback) utils.LoopingCall(consumer.fetch, auto_ack=True, enable_callbacks=True).start(0.1) @@ -103,7 +104,6 @@ class LoggingMiddleware(object): @webob.dec.wsgify def __call__(self, req): - if req.path == vnc.proxy.WS_ENDPOINT: LOG.info(_("Received Websocket Request: %s"), req.url) else: diff --git a/nova/vnc/proxy.py b/nova/vnc/proxy.py index dea838e3d..49379d9ae 100644 --- a/nova/vnc/proxy.py +++ b/nova/vnc/proxy.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# pylint: disable-msg=C0103 # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2010 United States Government as represented by the @@ -20,11 +19,13 @@ """Eventlet WSGI Services to proxy VNC. No nova deps.""" -from base64 import b64encode, b64decode +import base64 +import os + import eventlet from eventlet import wsgi from eventlet import websocket -import os + from webob import Request import webob @@ -32,7 +33,7 @@ WS_ENDPOINT = '/data' class WebsocketVNCProxy(object): - """Class to proxy from websocket to vnc server""" + """Class to proxy from websocket to vnc server.""" def __init__(self, wwwroot): self.wwwroot = wwwroot @@ -58,7 +59,7 @@ class WebsocketVNCProxy(object): d = source.recv(32384) if d == '': break - d = b64encode(d) + d = base64.b64encode(d) dest.send(d) except: source.close() @@ -70,7 +71,7 @@ class WebsocketVNCProxy(object): d = source.wait() if d is None: break - d = b64decode(d) + d = base64.b64decode(d) dest.sendall(d) except: source.close() @@ -118,7 +119,7 @@ class WebsocketVNCProxy(object): class DebugMiddleware(object): - """Debug middleware. Skip auth, get vnc port and host from query string""" + """Debug middleware. Skip auth, get vnc connect info from query string.""" def __init__(self, app): self.app = app -- cgit From 3d06c636537374557ee6ff77b7c0bc7718eafcdb Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 24 Mar 2011 18:59:11 -0400 Subject: Added detail keywork and i18n as per suggestions. --- nova/api/openstack/images.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index b01d991e8..be33bb656 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -62,7 +62,7 @@ class Controller(wsgi.Controller): context = req.environ['nova.context'] images = self._image_service.index(context) build = self.get_builder(req).build - return dict(images=[build(image, False) for image in images]) + return dict(images=[build(image, detail=False) for image in images]) def detail(self, req): """ @@ -73,7 +73,7 @@ class Controller(wsgi.Controller): context = req.environ['nova.context'] images = self._image_service.detail(context) build = self.get_builder(req).build - return dict(images=[build(image, True) for image in images]) + return dict(images=[build(image, detail=True) for image in images]) def show(self, req, id): """ @@ -88,10 +88,10 @@ class Controller(wsgi.Controller): try: image = self._image_service.show(context, image_id) except exception.NotFound: - ex = webob.exc.HTTPNotFound(explanation="Image not found.") + ex = webob.exc.HTTPNotFound(explanation=_("Image not found.")) raise faults.Fault(ex) - return dict(image=self.get_builder(req).build(image, True)) + return dict(image=self.get_builder(req).build(image, detail=True)) def delete(self, req, id): """ @@ -125,7 +125,7 @@ class Controller(wsgi.Controller): raise webob.exc.HTTPBadRequest() image = self._compute_service.snapshot(context, server_id, image_name) - return self.get_builder(req).build(image, True) + return self.get_builder(req).build(image, detail=True) def get_builder(self, request): """Indicates that you must use a Controller subclass.""" -- cgit From f2f08a5b0309876bb312c9124e75bd89331c4816 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Thu, 24 Mar 2011 17:04:55 -0700 Subject: make everything work with trunk again --- nova/api/ec2/cloud.py | 2 +- nova/virt/libvirt_conn.py | 2 +- nova/vnc/auth.py | 20 +++++++++++--------- 3 files changed, 13 insertions(+), 11 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 6b08f98c2..eb0428c2c 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -538,7 +538,7 @@ class CloudController(object): def get_vnc_console(self, context, instance_id, **kwargs): ec2_id = instance_id - instance_id = ec2_id_to_id(ec2_id) + instance_id = ec2utils.ec2_id_to_id(ec2_id) return self.compute_api.get_vnc_console(context, instance_id=instance_id) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 9cd0e8ac9..41adbfe27 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -626,7 +626,7 @@ class LibvirtConnection(driver.ComputeDriver): return {'token': token, 'host': host, 'port': port} - _image_sems = {} # FIXME: why is this here? (anthony) + _image_sems = {} # FIXME: why is this here? (anthony) @staticmethod def _cache_image(fn, target, fname, cow=False, *args, **kwargs): diff --git a/nova/vnc/auth.py b/nova/vnc/auth.py index 1c6a638fc..4161f3666 100644 --- a/nova/vnc/auth.py +++ b/nova/vnc/auth.py @@ -69,12 +69,14 @@ class NovaAuthMiddleware(object): middleware = self middleware.tokens = {} - def callback(self, data, message): - if data['method'] == 'authorize_vnc_console': - token = data['args']['token'] + class Proxy(): + @staticmethod + def authorize_vnc_console(context, **kwargs): + data = kwargs + token = kwargs['token'] LOG.audit(_("Received Token: %s)"), token) middleware.tokens[token] = \ - {'args': data['args'], 'last_activity_at': time.time()} + {'args': kwargs, 'last_activity_at': time.time()} def delete_expired_tokens(): now = time.time() @@ -88,12 +90,12 @@ class NovaAuthMiddleware(object): del middleware.tokens[k] conn = rpc.Connection.instance(new=True) - consumer = rpc.TopicConsumer( - connection=conn, - topic=FLAGS.vnc_console_proxy_topic) - consumer.register_callback(callback) + consumer = rpc.TopicAdapterConsumer( + connection=conn, + proxy=Proxy, + topic=FLAGS.vnc_console_proxy_topic) - utils.LoopingCall(consumer.fetch, auto_ack=True, + utils.LoopingCall(consumer.fetch, enable_callbacks=True).start(0.1) utils.LoopingCall(delete_expired_tokens).start(1) -- cgit From 06c0eff8ec7eef33933da9bd8adbf7b70a977889 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Thu, 24 Mar 2011 17:44:27 -0700 Subject: add hook for osapi --- nova/api/ec2/cloud.py | 1 + nova/api/openstack/servers.py | 10 ++++++++++ nova/vnc/auth.py | 4 ++-- 3 files changed, 13 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index eb0428c2c..fa4624ff1 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -537,6 +537,7 @@ class CloudController(object): instance_id=instance_id) def get_vnc_console(self, context, instance_id, **kwargs): + """Returns vnc browser url to the dashboard.""" ec2_id = instance_id instance_id = ec2utils.ec2_id_to_id(ec2_id) return self.compute_api.get_vnc_console(context, diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 0dad46268..88cc790c1 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -481,6 +481,16 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPNotFound()) return exc.HTTPAccepted() + @scheduler_api.redirect_handler + def get_vnc_console(self, req, id): + """ Returns a url to an instance's ajaxterm console. """ + try: + self.compute_api.get_vnc_console(req.environ['nova.context'], + int(id)) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + return exc.HTTPAccepted() + @scheduler_api.redirect_handler def diagnostics(self, req, id): """Permit Admins to retrieve server diagnostics.""" diff --git a/nova/vnc/auth.py b/nova/vnc/auth.py index 4161f3666..dff9b376f 100644 --- a/nova/vnc/auth.py +++ b/nova/vnc/auth.py @@ -69,7 +69,7 @@ class NovaAuthMiddleware(object): middleware = self middleware.tokens = {} - class Proxy(): + class TopicProxy(): @staticmethod def authorize_vnc_console(context, **kwargs): data = kwargs @@ -92,7 +92,7 @@ class NovaAuthMiddleware(object): conn = rpc.Connection.instance(new=True) consumer = rpc.TopicAdapterConsumer( connection=conn, - proxy=Proxy, + proxy=TopicProxy, topic=FLAGS.vnc_console_proxy_topic) utils.LoopingCall(consumer.fetch, -- cgit From e722803067e6386e98f29aa867d4cf98ce6e0cc2 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Thu, 24 Mar 2011 18:38:28 -0700 Subject: clarify comment --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index fa4624ff1..e5a957b83 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -537,7 +537,7 @@ class CloudController(object): instance_id=instance_id) def get_vnc_console(self, context, instance_id, **kwargs): - """Returns vnc browser url to the dashboard.""" + """Returns vnc browser url. Used by OS dashboard.""" ec2_id = instance_id instance_id = ec2utils.ec2_id_to_id(ec2_id) return self.compute_api.get_vnc_console(context, -- cgit From 91458f29f8b04407471d9d94a5ad165817f72a43 Mon Sep 17 00:00:00 2001 From: Muneyuki Noguchi Date: Fri, 25 Mar 2011 11:47:17 +0900 Subject: Restore volume state on migration failure. --- nova/compute/manager.py | 19 +++++++++++++++++-- nova/virt/libvirt_conn.py | 2 +- 2 files changed, 18 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 468771f46..356d20285 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1003,7 +1003,7 @@ class ComputeManager(manager.SchedulerDependentManager): "Domain not found: no domain with matching name.\" " "This error can be safely ignored.")) - def recover_live_migration(self, ctxt, instance_ref, host=None): + def recover_live_migration(self, ctxt, instance_ref, host=None, dest=None): """Recovers Instance/volume state from migrating -> running. :param ctxt: security context @@ -1011,6 +1011,7 @@ class ComputeManager(manager.SchedulerDependentManager): :param host: DB column value is updated by this hostname. if none, the host instance currently running is selected. + :param dest: destination host """ @@ -1024,7 +1025,21 @@ class ComputeManager(manager.SchedulerDependentManager): 'host': host}) for volume in instance_ref['volumes']: - self.db.volume_update(ctxt, volume['id'], {'status': 'in-use'}) + volume_id = volume['id'] + self.db.volume_update(ctxt, volume_id, {'status': 'in-use'}) + if dest: + topic = self.db.queue_get_for(ctxt, FLAGS.compute_topic, dest) + rpc.call(ctxt, topic, + {"method": "restore_volume_state", + "args": {'volume_id': volume_id}}) + + def restore_volume_state(self, context, volume_id): + """Restore volume state on migration failure. + + :param context: security context + :param volume_id: nova.db.sqlalchemy.models.Volume.id + """ + self.volume_manager.remove_compute_volume(context, volume_id) def periodic_tasks(self, context=None): """Tasks to be run at a periodic interval.""" diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 2cecb010d..5c6baa36e 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1370,7 +1370,7 @@ class LibvirtConnection(driver.ComputeDriver): FLAGS.live_migration_bandwidth) except Exception: - recover_method(ctxt, instance_ref) + recover_method(ctxt, instance_ref, None, dest) raise # Waiting for completion of live_migration. -- cgit From 9632db75d229eac16970af1dfabbb047c2b71a4e Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Fri, 25 Mar 2011 08:20:56 -0400 Subject: Removed partition from setup_container --- nova/virt/disk.py | 4 ++-- nova/virt/libvirt_conn.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/virt/disk.py b/nova/virt/disk.py index 0bdb04cde..bbdcc8ddf 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -115,13 +115,13 @@ def inject_data(image, key=None, net=None, partition=None, nbd=False): _unlink_device(device, nbd) -def setup_container(image, container_dir=None, partition=None): +def setup_container(image, container_dir=None): """Setup the LXC container It will mount the loopback image to the container directory in order to create the root filesystem for the container """ - nbd = False + nbd = "False" device = _link_device(image, nbd) err = utils.execute('sudo', 'mount', device, container_dir) if err: diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 9fb8ba955..bfb0a75f1 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -798,8 +798,7 @@ class LibvirtConnection(driver.ComputeDriver): if FLAGS.libvirt_type == 'lxc': disk.setup_container(basepath('disk'), - container_dir=container_dir, - partition=target_partition) + container_dir=container_dir) except Exception as e: # This could be a windows image, or a vmdk format disk LOG.warn(_('instance %(inst_name)s: ignoring error injecting' -- cgit From 47b54662c17c7af0bca9cf96dc5d4a498706fe8b Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Fri, 25 Mar 2011 08:37:47 -0400 Subject: Dont always assume qemu --- nova/virt/libvirt_conn.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index bfb0a75f1..840eaceeb 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1053,7 +1053,8 @@ class LibvirtConnection(driver.ComputeDriver): total = 0 for dom_id in self._conn.listDomainsID(): dom = self._conn.lookupByID(dom_id) - total += len(dom.vcpus()[1]) + if dom is None: + total += len(dom.vcpus()[1]) return total def get_memory_mb_used(self): -- cgit From 3d8b55294702b531a570b279fb29db8d4ea104d3 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Fri, 25 Mar 2011 08:52:18 -0400 Subject: Fix up templating --- nova/virt/libvirt.xml.template | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template index c2c5384bb..26f528cb1 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -83,24 +83,21 @@ #end if #end if #end if - -#for $nic in $nics - - + + - - - -#if $getVar('nic.extra_params', False) - ${nic.extra_params} + + + +#if $getVar('extra_params', False) + ${extra_params} #end if -#if $getVar('nic.gateway_v6', False) - +#if $getVar('gateway_v6', False) + #end if -#end for -- cgit From 6ce4f9c6ae00138184e79cdcfb6f78fc3474580e Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Fri, 25 Mar 2011 08:52:54 -0400 Subject: Fix up destroy container --- nova/virt/disk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/disk.py b/nova/virt/disk.py index bbdcc8ddf..b9a8fb7e7 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -140,7 +140,7 @@ def destroy_container(target, instance): container_dir = '%s/rootfs' % target utils.execute('sudo', 'umount', container_dir) finally: - out, err = utils('sudo', 'losetup', '-a') + out, err = utils.execute('sudo', 'losetup', '-a') for loop in out.splitlines(): if instance['name'] in loop: device = loop.split(loop, ':') -- cgit From c8fb0c5a16852afc98349edf89bb31afac166749 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Fri, 25 Mar 2011 09:39:20 -0400 Subject: Revert dom check --- nova/virt/libvirt_conn.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 840eaceeb..bfb0a75f1 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1053,8 +1053,7 @@ class LibvirtConnection(driver.ComputeDriver): total = 0 for dom_id in self._conn.listDomainsID(): dom = self._conn.lookupByID(dom_id) - if dom is None: - total += len(dom.vcpus()[1]) + total += len(dom.vcpus()[1]) return total def get_memory_mb_used(self): -- cgit From ec524aae3224a806fa41f6ae6c2975a1ba124f15 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Fri, 25 Mar 2011 15:18:57 +0100 Subject: Toss an __init__ in the test extensions dir. This gets it included in the tarball. --- nova/tests/api/openstack/extensions/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 nova/tests/api/openstack/extensions/__init__.py (limited to 'nova') diff --git a/nova/tests/api/openstack/extensions/__init__.py b/nova/tests/api/openstack/extensions/__init__.py new file mode 100644 index 000000000..e69de29bb -- cgit From af8aa36ca07c5e51016df68c0acc7449378fac2f Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Fri, 25 Mar 2011 18:23:36 +0100 Subject: Add license and copyright to nova/tests/api/openstack/extensions/__init__.py --- nova/tests/api/openstack/extensions/__init__.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'nova') diff --git a/nova/tests/api/openstack/extensions/__init__.py b/nova/tests/api/openstack/extensions/__init__.py index e69de29bb..848908a95 100644 --- a/nova/tests/api/openstack/extensions/__init__.py +++ b/nova/tests/api/openstack/extensions/__init__.py @@ -0,0 +1,15 @@ +# 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. -- cgit From 51c07f77686473bc73c700aacc7baeecf278a948 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Fri, 25 Mar 2011 16:57:21 -0400 Subject: Removed print. --- nova/tests/api/openstack/test_images.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index b51a52cfe..3bf710d1a 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -642,7 +642,6 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): def test_get_image_found(self): req = webob.Request.blank('/v1.0/images/123') res = req.get_response(fakes.wsgi_app()) - print self.fixtures image_meta = json.loads(res.body)['image'] expected = {'id': 123, 'name': 'public image', 'updated': self.NOW_API_FORMAT, -- cgit From cd1bac4deff367131d43f87cdfbc3b6b34bbdc1e Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Fri, 25 Mar 2011 16:39:43 -0700 Subject: Initial extensification of volumes --- nova/api/openstack/__init__.py | 10 -- nova/api/openstack/extensions.py | 151 ++++++++++++++--- nova/api/openstack/incubator/__init__.py | 20 +++ nova/api/openstack/incubator/volumes/__init__.py | 18 ++ .../incubator/volumes/volume_attachments.py | 181 +++++++++++++++++++++ nova/api/openstack/incubator/volumes/volumes.py | 160 ++++++++++++++++++ .../api/openstack/incubator/volumes/volumes_ext.py | 55 +++++++ nova/api/openstack/volume_attachments.py | 181 --------------------- nova/api/openstack/volumes.py | 160 ------------------ nova/tests/api/openstack/extensions/foxinsocks.py | 98 ----------- .../openstack/extensions/foxinsocks/__init__.py | 19 +++ .../openstack/extensions/foxinsocks/foxinsocks.py | 98 +++++++++++ 12 files changed, 682 insertions(+), 469 deletions(-) create mode 100644 nova/api/openstack/incubator/__init__.py create mode 100644 nova/api/openstack/incubator/volumes/__init__.py create mode 100644 nova/api/openstack/incubator/volumes/volume_attachments.py create mode 100644 nova/api/openstack/incubator/volumes/volumes.py create mode 100644 nova/api/openstack/incubator/volumes/volumes_ext.py delete mode 100644 nova/api/openstack/volume_attachments.py delete mode 100644 nova/api/openstack/volumes.py delete mode 100644 nova/tests/api/openstack/extensions/foxinsocks.py create mode 100644 nova/tests/api/openstack/extensions/foxinsocks/__init__.py create mode 100644 nova/tests/api/openstack/extensions/foxinsocks/foxinsocks.py (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 0e5b2a071..731e16a58 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -128,16 +128,6 @@ class APIRouter(wsgi.Router): _limits = limits.LimitsController() mapper.resource("limit", "limits", controller=_limits) - #NOTE(justinsb): volumes is not yet part of the official API - mapper.resource("volume", "volumes", - controller=volumes.Controller(), - collection={'detail': 'GET'}) - - mapper.resource("volume_attachment", "volume_attachments", - controller=volume_attachments.Controller(), - parent_resource=dict(member_name='server', - collection_name='servers')) - super(APIRouter, self).__init__(mapper) diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 9d98d849a..6a8ce9669 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -1,6 +1,7 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack LLC. +# Copyright 2011 Justin Santa Barbara # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -16,12 +17,14 @@ # under the License. import imp +import inspect import os import sys import routes import webob.dec import webob.exc +from nova import exception from nova import flags from nova import log as logging from nova import wsgi @@ -34,6 +37,63 @@ LOG = logging.getLogger('extensions') FLAGS = flags.FLAGS +class ExtensionDescriptor(object): + """This is the base class that defines the contract for extensions""" + + def get_name(self): + """The name of the extension + + e.g. 'Fox In Socks' """ + raise NotImplementedError() + + def get_alias(self): + """The alias for the extension + + e.g. 'FOXNSOX'""" + raise NotImplementedError() + + def get_description(self): + """Friendly description for the extension + + e.g. 'The Fox In Socks Extension'""" + raise NotImplementedError() + + def get_namespace(self): + """The XML namespace for the extension + + e.g. 'http://www.fox.in.socks/api/ext/pie/v1.0'""" + raise NotImplementedError() + + def get_updated(self): + """The timestamp when the extension was last updated + + e.g. '2011-01-22T13:25:27-06:00'""" + #NOTE(justinsb): Huh? Isn't this defined by the namespace? + raise NotImplementedError() + + def get_resources(self): + """List of extensions.ResourceExtension extension objects + + Resources define new nouns, and are accessible through URLs""" + resources = [] + return resources + + def get_actions(self): + """List of extensions.ActionExtension extension objects + + Actions are verbs callable from the API""" + actions = [] + return actions + + def get_response_extensions(self): + """List of extensions.ResponseExtension extension objects + + Response extensions are used to insert information into existing + response data""" + response_exts = [] + return response_exts + + class ActionExtensionController(wsgi.Controller): def __init__(self, application): @@ -109,13 +169,10 @@ class ExtensionController(wsgi.Controller): return self._translate(ext) def delete(self, req, id): - raise faults.Fault(exc.HTTPNotFound()) + raise faults.Fault(webob.exc.HTTPNotFound()) def create(self, req): - raise faults.Fault(exc.HTTPNotFound()) - - def delete(self, req, id): - raise faults.Fault(exc.HTTPNotFound()) + raise faults.Fault(webob.exc.HTTPNotFound()) class ExtensionMiddleware(wsgi.Middleware): @@ -235,16 +292,19 @@ class ExtensionMiddleware(wsgi.Middleware): class ExtensionManager(object): """ Load extensions from the configured extension path. - See nova/tests/api/openstack/extensions/foxinsocks.py for an example - extension implementation. + + See nova/tests/api/openstack/extensions/foxinsocks/extension.py for an + example extension implementation. """ def __init__(self, path): LOG.audit(_('Initializing extension manager.')) + self.super_verbose = False + self.path = path self.extensions = {} - self._load_extensions() + self._load_all_extensions() def get_resources(self): """ @@ -300,7 +360,7 @@ class ExtensionManager(object): except AttributeError as ex: LOG.exception(_("Exception loading extension: %s"), unicode(ex)) - def _load_extensions(self): + def _load_all_extensions(self): """ Load extensions from the configured path. The extension name is constructed from the module_name. If your extension module was named @@ -310,23 +370,74 @@ class ExtensionManager(object): See nova/tests/api/openstack/extensions/foxinsocks.py for an example extension implementation. """ - if not os.path.exists(self.path): + self._load_extensions_under_path(self.path) + + incubator_path = os.path.join(os.path.dirname(__file__), "incubator") + self._load_extensions_under_path(incubator_path) + + def _load_extensions_under_path(self, path): + if not os.path.isdir(path): + LOG.warning(_('Extensions directory not found: %s') % path) return - for f in os.listdir(self.path): - LOG.audit(_('Loading extension file: %s'), f) + LOG.debug(_('Looking for extensions in: %s') % path) + + for child in os.listdir(path): + child_path = os.path.join(path, child) + if not os.path.isdir(child_path): + continue + self._load_extension(child_path) + + def _load_extension(self, path): + if not os.path.isdir(path): + return + + for f in os.listdir(path): mod_name, file_ext = os.path.splitext(os.path.split(f)[-1]) - ext_path = os.path.join(self.path, f) - if file_ext.lower() == '.py': - mod = imp.load_source(mod_name, ext_path) - ext_name = mod_name[0].upper() + mod_name[1:] + if file_ext.startswith('_'): + continue + if file_ext.lower() != '.py': + continue + + ext_path = os.path.join(path, f) + if self.super_verbose: + LOG.debug(_('Checking extension file: %s'), ext_path) + + mod = imp.load_source(mod_name, ext_path) + for _name, cls in inspect.getmembers(mod): try: - new_ext = getattr(mod, ext_name)() - self._check_extension(new_ext) - self.extensions[new_ext.get_alias()] = new_ext + if not inspect.isclass(cls): + continue + + #NOTE(justinsb): It seems that python modules aren't great + # If you have two identically named modules, the classes + # from both are mixed in. So name your extension based + # on the alias, not 'extension.py'! + #TODO(justinsb): Any way to work around this? + + if self.super_verbose: + LOG.debug(_('Checking class: %s'), cls) + + if not ExtensionDescriptor in cls.__bases__: + if self.super_verbose: + LOG.debug(_('Not a ExtensionDescriptor: %s'), cls) + continue + + obj = cls() + self._add_extension(obj) except AttributeError as ex: LOG.exception(_("Exception loading extension: %s"), - unicode(ex)) + unicode(ex)) + + def _add_extension(self, ext): + alias = ext.get_alias() + LOG.audit(_('Loaded extension: %s'), alias) + + self._check_extension(ext) + + if alias in self.extensions: + raise exception.Error("Found duplicate extension: %s" % alias) + self.extensions[alias] = ext class ResponseExtension(object): diff --git a/nova/api/openstack/incubator/__init__.py b/nova/api/openstack/incubator/__init__.py new file mode 100644 index 000000000..cded38174 --- /dev/null +++ b/nova/api/openstack/incubator/__init__.py @@ -0,0 +1,20 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Justin Santa Barbara +# 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 + +"""Incubator contains extensions that are shipped with nova. + +It can't be called 'extensions' because that causes namespacing problems.""" diff --git a/nova/api/openstack/incubator/volumes/__init__.py b/nova/api/openstack/incubator/volumes/__init__.py new file mode 100644 index 000000000..2a9c93210 --- /dev/null +++ b/nova/api/openstack/incubator/volumes/__init__.py @@ -0,0 +1,18 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Justin Santa Barbara +# 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 + +"""The volumes extension adds volumes and attachments to the API.""" diff --git a/nova/api/openstack/incubator/volumes/volume_attachments.py b/nova/api/openstack/incubator/volumes/volume_attachments.py new file mode 100644 index 000000000..58a9a727b --- /dev/null +++ b/nova/api/openstack/incubator/volumes/volume_attachments.py @@ -0,0 +1,181 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Justin Santa Barbara +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from webob import exc + +from nova import compute +from nova import exception +from nova import flags +from nova import log as logging +from nova import volume +from nova import wsgi +from nova.api.openstack import common +from nova.api.openstack import faults + + +LOG = logging.getLogger("nova.api.volumes") + +FLAGS = flags.FLAGS + + +def _translate_detail_view(context, volume): + """ Maps keys for details view""" + + d = _translate_summary_view(context, volume) + + # No additional data / lookups at the moment + + return d + + +def _translate_summary_view(context, vol): + """ Maps keys for summary view""" + d = {} + + volume_id = vol['id'] + + # NOTE(justinsb): We use the volume id as the id of the attachment object + d['id'] = volume_id + + d['volumeId'] = volume_id + if vol.get('instance_id'): + d['serverId'] = vol['instance_id'] + if vol.get('mountpoint'): + d['device'] = vol['mountpoint'] + + return d + + +class Controller(wsgi.Controller): + """ The volume attachment API controller for the Openstack API + + A child resource of the server. Note that we use the volume id + as the ID of the attachment (though this is not guaranteed externally)""" + + _serialization_metadata = { + 'application/xml': { + 'attributes': { + 'volumeAttachment': ['id', + 'serverId', + 'volumeId', + 'device']}}} + + def __init__(self): + self.compute_api = compute.API() + self.volume_api = volume.API() + super(Controller, self).__init__() + + def index(self, req, server_id): + """ Returns the list of volume attachments for a given instance """ + return self._items(req, server_id, + entity_maker=_translate_summary_view) + + def show(self, req, server_id, id): + """Return data about the given volume""" + context = req.environ['nova.context'] + + volume_id = id + try: + vol = self.volume_api.get(context, volume_id) + except exception.NotFound: + LOG.debug("volume_id not found") + return faults.Fault(exc.HTTPNotFound()) + + if str(vol['instance_id']) != server_id: + LOG.debug("instance_id != server_id") + return faults.Fault(exc.HTTPNotFound()) + + return {'volumeAttachment': _translate_detail_view(context, vol)} + + def create(self, req, server_id): + """ Attach a volume to an instance """ + context = req.environ['nova.context'] + + env = self._deserialize(req.body, req.get_content_type()) + if not env: + return faults.Fault(exc.HTTPUnprocessableEntity()) + + instance_id = server_id + volume_id = env['volumeAttachment']['volumeId'] + device = env['volumeAttachment']['device'] + + msg = _("Attach volume %(volume_id)s to instance %(server_id)s" + " at %(device)s") % locals() + LOG.audit(msg, context=context) + + try: + self.compute_api.attach_volume(context, + instance_id=instance_id, + volume_id=volume_id, + device=device) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + + # The attach is async + attachment = {} + attachment['id'] = volume_id + attachment['volumeId'] = volume_id + + # NOTE(justinsb): And now, we have a problem... + # The attach is async, so there's a window in which we don't see + # the attachment (until the attachment completes). We could also + # get problems with concurrent requests. I think we need an + # attachment state, and to write to the DB here, but that's a bigger + # change. + # For now, we'll probably have to rely on libraries being smart + + # TODO: How do I return "accepted" here?? + return {'volumeAttachment': attachment} + + def update(self, _req, _server_id, _id): + """ Update a volume attachment. We don't currently support this.""" + return faults.Fault(exc.HTTPBadRequest()) + + def delete(self, req, server_id, id): + """ Detach a volume from an instance """ + context = req.environ['nova.context'] + + volume_id = id + LOG.audit(_("Detach volume %s"), volume_id, context=context) + + try: + vol = self.volume_api.get(context, volume_id) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + + if str(vol['instance_id']) != server_id: + LOG.debug("instance_id != server_id") + return faults.Fault(exc.HTTPNotFound()) + + self.compute_api.detach_volume(context, + volume_id=volume_id) + + return exc.HTTPAccepted() + + def _items(self, req, server_id, entity_maker): + """Returns a list of attachments, 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()) + + volumes = instance['volumes'] + limited_list = common.limited(volumes, req) + res = [entity_maker(context, vol) for vol in limited_list] + return {'volumeAttachments': res} diff --git a/nova/api/openstack/incubator/volumes/volumes.py b/nova/api/openstack/incubator/volumes/volumes.py new file mode 100644 index 000000000..ec3b9a6c8 --- /dev/null +++ b/nova/api/openstack/incubator/volumes/volumes.py @@ -0,0 +1,160 @@ +# Copyright 2011 Justin Santa Barbara +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from webob import exc + +from nova import exception +from nova import flags +from nova import log as logging +from nova import volume +from nova import wsgi +from nova.api.openstack import common +from nova.api.openstack import faults + + +LOG = logging.getLogger("nova.api.volumes") + +FLAGS = flags.FLAGS + + +def _translate_detail_view(context, vol): + """ Maps keys for details view""" + + d = _translate_summary_view(context, vol) + + # No additional data / lookups at the moment + + return d + + +def _translate_summary_view(_context, vol): + """ Maps keys for summary view""" + d = {} + + instance_id = None + # instance_data = None + attached_to = vol.get('instance') + if attached_to: + instance_id = attached_to['id'] + # instance_data = '%s[%s]' % (instance_ec2_id, + # attached_to['host']) + d['id'] = vol['id'] + d['status'] = vol['status'] + d['size'] = vol['size'] + d['availabilityZone'] = vol['availability_zone'] + d['createdAt'] = vol['created_at'] + # if context.is_admin: + # v['status'] = '%s (%s, %s, %s, %s)' % ( + # vol['status'], + # vol['user_id'], + # vol['host'], + # instance_data, + # vol['mountpoint']) + if vol['attach_status'] == 'attached': + d['attachments'] = [{'attachTime': vol['attach_time'], + 'deleteOnTermination': False, + 'mountpoint': vol['mountpoint'], + 'instanceId': instance_id, + 'status': 'attached', + 'volumeId': vol['id']}] + else: + d['attachments'] = [{}] + + d['displayName'] = vol['display_name'] + d['displayDescription'] = vol['display_description'] + return d + + +class Controller(wsgi.Controller): + """ The Volumes API controller for the OpenStack API """ + + _serialization_metadata = { + 'application/xml': { + "attributes": { + "volume": [ + "id", + "status", + "size", + "availabilityZone", + "createdAt", + "displayName", + "displayDescription", + ]}}} + + def __init__(self): + self.volume_api = volume.API() + super(Controller, self).__init__() + + def show(self, req, id): + """Return data about the given volume""" + context = req.environ['nova.context'] + + try: + vol = self.volume_api.get(context, id) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + + return {'volume': _translate_detail_view(context, vol)} + + def delete(self, req, id): + """ Delete a volume """ + context = req.environ['nova.context'] + + LOG.audit(_("Delete volume with id: %s"), id, context=context) + + try: + self.volume_api.delete(context, volume_id=id) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + return exc.HTTPAccepted() + + def index(self, req): + """ Returns a summary list of volumes""" + return self._items(req, entity_maker=_translate_summary_view) + + def detail(self, req): + """ Returns a detailed list of volumes """ + return self._items(req, entity_maker=_translate_detail_view) + + def _items(self, req, entity_maker): + """Returns a list of volumes, transformed through entity_maker""" + context = req.environ['nova.context'] + + volumes = self.volume_api.get_all(context) + limited_list = common.limited(volumes, req) + res = [entity_maker(context, vol) for vol in limited_list] + return {'volumes': res} + + def create(self, req): + """Creates a new volume""" + context = req.environ['nova.context'] + + env = self._deserialize(req.body, req.get_content_type()) + if not env: + return faults.Fault(exc.HTTPUnprocessableEntity()) + + vol = env['volume'] + size = vol['size'] + LOG.audit(_("Create volume of %s GB"), size, context=context) + new_volume = self.volume_api.create(context, size, + vol.get('display_name'), + vol.get('display_description')) + + # Work around problem that instance is lazy-loaded... + new_volume['instance'] = None + + retval = _translate_detail_view(context, new_volume) + + return {'volume': retval} diff --git a/nova/api/openstack/incubator/volumes/volumes_ext.py b/nova/api/openstack/incubator/volumes/volumes_ext.py new file mode 100644 index 000000000..87a57320d --- /dev/null +++ b/nova/api/openstack/incubator/volumes/volumes_ext.py @@ -0,0 +1,55 @@ +# Copyright 2011 Justin Santa Barbara +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from nova.api.openstack import extensions +from nova.api.openstack.incubator.volumes import volumes +from nova.api.openstack.incubator.volumes import volume_attachments + + +class VolumesExtension(extensions.ExtensionDescriptor): + def get_name(self): + return "Volumes" + + def get_alias(self): + return "VOLUMES" + + def get_description(self): + return "Volumes support" + + def get_namespace(self): + return "http://docs.openstack.org/ext/volumes/api/v1.1" + + def get_updated(self): + return "2011-03-25T00:00:00+00:00" + + def get_resources(self): + resources = [] + + #NOTE(justinsb): No way to provide singular name ('volume') + # Does this matter? + res = extensions.ResourceExtension('volumes', + volumes.Controller(), + collection_actions={'detail': 'GET'} + ) + resources.append(res) + + res = extensions.ResourceExtension('volume_attachments', + volume_attachments.Controller(), + parent=dict( + member_name='server', + collection_name='servers')) + resources.append(res) + + return resources diff --git a/nova/api/openstack/volume_attachments.py b/nova/api/openstack/volume_attachments.py deleted file mode 100644 index 58a9a727b..000000000 --- a/nova/api/openstack/volume_attachments.py +++ /dev/null @@ -1,181 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 Justin Santa Barbara -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from webob import exc - -from nova import compute -from nova import exception -from nova import flags -from nova import log as logging -from nova import volume -from nova import wsgi -from nova.api.openstack import common -from nova.api.openstack import faults - - -LOG = logging.getLogger("nova.api.volumes") - -FLAGS = flags.FLAGS - - -def _translate_detail_view(context, volume): - """ Maps keys for details view""" - - d = _translate_summary_view(context, volume) - - # No additional data / lookups at the moment - - return d - - -def _translate_summary_view(context, vol): - """ Maps keys for summary view""" - d = {} - - volume_id = vol['id'] - - # NOTE(justinsb): We use the volume id as the id of the attachment object - d['id'] = volume_id - - d['volumeId'] = volume_id - if vol.get('instance_id'): - d['serverId'] = vol['instance_id'] - if vol.get('mountpoint'): - d['device'] = vol['mountpoint'] - - return d - - -class Controller(wsgi.Controller): - """ The volume attachment API controller for the Openstack API - - A child resource of the server. Note that we use the volume id - as the ID of the attachment (though this is not guaranteed externally)""" - - _serialization_metadata = { - 'application/xml': { - 'attributes': { - 'volumeAttachment': ['id', - 'serverId', - 'volumeId', - 'device']}}} - - def __init__(self): - self.compute_api = compute.API() - self.volume_api = volume.API() - super(Controller, self).__init__() - - def index(self, req, server_id): - """ Returns the list of volume attachments for a given instance """ - return self._items(req, server_id, - entity_maker=_translate_summary_view) - - def show(self, req, server_id, id): - """Return data about the given volume""" - context = req.environ['nova.context'] - - volume_id = id - try: - vol = self.volume_api.get(context, volume_id) - except exception.NotFound: - LOG.debug("volume_id not found") - return faults.Fault(exc.HTTPNotFound()) - - if str(vol['instance_id']) != server_id: - LOG.debug("instance_id != server_id") - return faults.Fault(exc.HTTPNotFound()) - - return {'volumeAttachment': _translate_detail_view(context, vol)} - - def create(self, req, server_id): - """ Attach a volume to an instance """ - context = req.environ['nova.context'] - - env = self._deserialize(req.body, req.get_content_type()) - if not env: - return faults.Fault(exc.HTTPUnprocessableEntity()) - - instance_id = server_id - volume_id = env['volumeAttachment']['volumeId'] - device = env['volumeAttachment']['device'] - - msg = _("Attach volume %(volume_id)s to instance %(server_id)s" - " at %(device)s") % locals() - LOG.audit(msg, context=context) - - try: - self.compute_api.attach_volume(context, - instance_id=instance_id, - volume_id=volume_id, - device=device) - except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) - - # The attach is async - attachment = {} - attachment['id'] = volume_id - attachment['volumeId'] = volume_id - - # NOTE(justinsb): And now, we have a problem... - # The attach is async, so there's a window in which we don't see - # the attachment (until the attachment completes). We could also - # get problems with concurrent requests. I think we need an - # attachment state, and to write to the DB here, but that's a bigger - # change. - # For now, we'll probably have to rely on libraries being smart - - # TODO: How do I return "accepted" here?? - return {'volumeAttachment': attachment} - - def update(self, _req, _server_id, _id): - """ Update a volume attachment. We don't currently support this.""" - return faults.Fault(exc.HTTPBadRequest()) - - def delete(self, req, server_id, id): - """ Detach a volume from an instance """ - context = req.environ['nova.context'] - - volume_id = id - LOG.audit(_("Detach volume %s"), volume_id, context=context) - - try: - vol = self.volume_api.get(context, volume_id) - except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) - - if str(vol['instance_id']) != server_id: - LOG.debug("instance_id != server_id") - return faults.Fault(exc.HTTPNotFound()) - - self.compute_api.detach_volume(context, - volume_id=volume_id) - - return exc.HTTPAccepted() - - def _items(self, req, server_id, entity_maker): - """Returns a list of attachments, 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()) - - volumes = instance['volumes'] - limited_list = common.limited(volumes, req) - res = [entity_maker(context, vol) for vol in limited_list] - return {'volumeAttachments': res} diff --git a/nova/api/openstack/volumes.py b/nova/api/openstack/volumes.py deleted file mode 100644 index ec3b9a6c8..000000000 --- a/nova/api/openstack/volumes.py +++ /dev/null @@ -1,160 +0,0 @@ -# Copyright 2011 Justin Santa Barbara -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from webob import exc - -from nova import exception -from nova import flags -from nova import log as logging -from nova import volume -from nova import wsgi -from nova.api.openstack import common -from nova.api.openstack import faults - - -LOG = logging.getLogger("nova.api.volumes") - -FLAGS = flags.FLAGS - - -def _translate_detail_view(context, vol): - """ Maps keys for details view""" - - d = _translate_summary_view(context, vol) - - # No additional data / lookups at the moment - - return d - - -def _translate_summary_view(_context, vol): - """ Maps keys for summary view""" - d = {} - - instance_id = None - # instance_data = None - attached_to = vol.get('instance') - if attached_to: - instance_id = attached_to['id'] - # instance_data = '%s[%s]' % (instance_ec2_id, - # attached_to['host']) - d['id'] = vol['id'] - d['status'] = vol['status'] - d['size'] = vol['size'] - d['availabilityZone'] = vol['availability_zone'] - d['createdAt'] = vol['created_at'] - # if context.is_admin: - # v['status'] = '%s (%s, %s, %s, %s)' % ( - # vol['status'], - # vol['user_id'], - # vol['host'], - # instance_data, - # vol['mountpoint']) - if vol['attach_status'] == 'attached': - d['attachments'] = [{'attachTime': vol['attach_time'], - 'deleteOnTermination': False, - 'mountpoint': vol['mountpoint'], - 'instanceId': instance_id, - 'status': 'attached', - 'volumeId': vol['id']}] - else: - d['attachments'] = [{}] - - d['displayName'] = vol['display_name'] - d['displayDescription'] = vol['display_description'] - return d - - -class Controller(wsgi.Controller): - """ The Volumes API controller for the OpenStack API """ - - _serialization_metadata = { - 'application/xml': { - "attributes": { - "volume": [ - "id", - "status", - "size", - "availabilityZone", - "createdAt", - "displayName", - "displayDescription", - ]}}} - - def __init__(self): - self.volume_api = volume.API() - super(Controller, self).__init__() - - def show(self, req, id): - """Return data about the given volume""" - context = req.environ['nova.context'] - - try: - vol = self.volume_api.get(context, id) - except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) - - return {'volume': _translate_detail_view(context, vol)} - - def delete(self, req, id): - """ Delete a volume """ - context = req.environ['nova.context'] - - LOG.audit(_("Delete volume with id: %s"), id, context=context) - - try: - self.volume_api.delete(context, volume_id=id) - except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) - return exc.HTTPAccepted() - - def index(self, req): - """ Returns a summary list of volumes""" - return self._items(req, entity_maker=_translate_summary_view) - - def detail(self, req): - """ Returns a detailed list of volumes """ - return self._items(req, entity_maker=_translate_detail_view) - - def _items(self, req, entity_maker): - """Returns a list of volumes, transformed through entity_maker""" - context = req.environ['nova.context'] - - volumes = self.volume_api.get_all(context) - limited_list = common.limited(volumes, req) - res = [entity_maker(context, vol) for vol in limited_list] - return {'volumes': res} - - def create(self, req): - """Creates a new volume""" - context = req.environ['nova.context'] - - env = self._deserialize(req.body, req.get_content_type()) - if not env: - return faults.Fault(exc.HTTPUnprocessableEntity()) - - vol = env['volume'] - size = vol['size'] - LOG.audit(_("Create volume of %s GB"), size, context=context) - new_volume = self.volume_api.create(context, size, - vol.get('display_name'), - vol.get('display_description')) - - # Work around problem that instance is lazy-loaded... - new_volume['instance'] = None - - retval = _translate_detail_view(context, new_volume) - - return {'volume': retval} diff --git a/nova/tests/api/openstack/extensions/foxinsocks.py b/nova/tests/api/openstack/extensions/foxinsocks.py deleted file mode 100644 index 0860b51ac..000000000 --- a/nova/tests/api/openstack/extensions/foxinsocks.py +++ /dev/null @@ -1,98 +0,0 @@ -# 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 nova import wsgi - -from nova.api.openstack import extensions - - -class FoxInSocksController(wsgi.Controller): - - def index(self, req): - return "Try to say this Mr. Knox, sir..." - - -class Foxinsocks(object): - - def __init__(self): - pass - - def get_name(self): - return "Fox In Socks" - - def get_alias(self): - return "FOXNSOX" - - def get_description(self): - return "The Fox In Socks Extension" - - def get_namespace(self): - return "http://www.fox.in.socks/api/ext/pie/v1.0" - - def get_updated(self): - return "2011-01-22T13:25:27-06:00" - - def get_resources(self): - resources = [] - resource = extensions.ResourceExtension('foxnsocks', - FoxInSocksController()) - resources.append(resource) - return resources - - def get_actions(self): - actions = [] - actions.append(extensions.ActionExtension('servers', 'add_tweedle', - self._add_tweedle)) - actions.append(extensions.ActionExtension('servers', 'delete_tweedle', - self._delete_tweedle)) - return actions - - def get_response_extensions(self): - response_exts = [] - - def _goose_handler(res): - #NOTE: This only handles JSON responses. - # You can use content type header to test for XML. - data = json.loads(res.body) - data['flavor']['googoose'] = "Gooey goo for chewy chewing!" - return data - - resp_ext = extensions.ResponseExtension('GET', '/v1.1/flavors/:(id)', - _goose_handler) - response_exts.append(resp_ext) - - def _bands_handler(res): - #NOTE: This only handles JSON responses. - # You can use content type header to test for XML. - data = json.loads(res.body) - data['big_bands'] = 'Pig Bands!' - return data - - resp_ext2 = extensions.ResponseExtension('GET', '/v1.1/flavors/:(id)', - _bands_handler) - response_exts.append(resp_ext2) - return response_exts - - def _add_tweedle(self, input_dict, req, id): - - return "Tweedle Beetle Added." - - def _delete_tweedle(self, input_dict, req, id): - - return "Tweedle Beetle Deleted." diff --git a/nova/tests/api/openstack/extensions/foxinsocks/__init__.py b/nova/tests/api/openstack/extensions/foxinsocks/__init__.py new file mode 100644 index 000000000..fe505359d --- /dev/null +++ b/nova/tests/api/openstack/extensions/foxinsocks/__init__.py @@ -0,0 +1,19 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# Copyright 2011 Justin Santa Barbara +# 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 + +"""Example Fox-in-Socks extension.""" diff --git a/nova/tests/api/openstack/extensions/foxinsocks/foxinsocks.py b/nova/tests/api/openstack/extensions/foxinsocks/foxinsocks.py new file mode 100644 index 000000000..442707714 --- /dev/null +++ b/nova/tests/api/openstack/extensions/foxinsocks/foxinsocks.py @@ -0,0 +1,98 @@ +# 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 nova import wsgi + +from nova.api.openstack import extensions + + +class FoxInSocksController(wsgi.Controller): + + def index(self, req): + return "Try to say this Mr. Knox, sir..." + + +class Foxinsocks(extensions.ExtensionDescriptor): + + def __init__(self): + pass + + def get_name(self): + return "Fox In Socks" + + def get_alias(self): + return "FOXNSOX" + + def get_description(self): + return "The Fox In Socks Extension" + + def get_namespace(self): + return "http://www.fox.in.socks/api/ext/pie/v1.0" + + def get_updated(self): + return "2011-01-22T13:25:27-06:00" + + def get_resources(self): + resources = [] + resource = extensions.ResourceExtension('foxnsocks', + FoxInSocksController()) + resources.append(resource) + return resources + + def get_actions(self): + actions = [] + actions.append(extensions.ActionExtension('servers', 'add_tweedle', + self._add_tweedle)) + actions.append(extensions.ActionExtension('servers', 'delete_tweedle', + self._delete_tweedle)) + return actions + + def get_response_extensions(self): + response_exts = [] + + def _goose_handler(res): + #NOTE: This only handles JSON responses. + # You can use content type header to test for XML. + data = json.loads(res.body) + data['flavor']['googoose'] = "Gooey goo for chewy chewing!" + return data + + resp_ext = extensions.ResponseExtension('GET', '/v1.1/flavors/:(id)', + _goose_handler) + response_exts.append(resp_ext) + + def _bands_handler(res): + #NOTE: This only handles JSON responses. + # You can use content type header to test for XML. + data = json.loads(res.body) + data['big_bands'] = 'Pig Bands!' + return data + + resp_ext2 = extensions.ResponseExtension('GET', '/v1.1/flavors/:(id)', + _bands_handler) + response_exts.append(resp_ext2) + return response_exts + + def _add_tweedle(self, input_dict, req, id): + + return "Tweedle Beetle Added." + + def _delete_tweedle(self, input_dict, req, id): + + return "Tweedle Beetle Deleted." -- cgit From 5936449d99b852897fddbbb140465db0ad9a330c Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Fri, 25 Mar 2011 17:48:59 -0700 Subject: Now that it's an extension, it has to be v1.1. Also fixed up all the things that changed in v1.1 --- nova/api/openstack/__init__.py | 2 -- nova/api/openstack/common.py | 5 ++++ nova/tests/integrated/integrated_helpers.py | 21 ++++++++++---- nova/tests/integrated/test_extensions.py | 43 +++++++++++++++++++++++++++++ nova/tests/integrated/test_servers.py | 16 ++++++----- nova/tests/integrated/test_volumes.py | 6 ++++ 6 files changed, 78 insertions(+), 15 deletions(-) create mode 100644 nova/tests/integrated/test_extensions.py (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 731e16a58..7f2bb1155 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -39,8 +39,6 @@ from nova.api.openstack import servers from nova.api.openstack import server_metadata from nova.api.openstack import shared_ip_groups from nova.api.openstack import users -from nova.api.openstack import volumes -from nova.api.openstack import volume_attachments from nova.api.openstack import zones diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 8cad1273a..4ab6b7a81 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -21,6 +21,10 @@ import webob from nova import exception from nova import flags +from nova import log as logging + + +LOG = logging.getLogger('common') FLAGS = flags.FLAGS @@ -121,4 +125,5 @@ def get_id_from_href(href): try: return int(urlparse(href).path.split('/')[-1]) except: + LOG.debug(_("Error extracting id from href: %s") % href) raise webob.exc.HTTPBadRequest(_('could not parse id from href')) diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py index 99d597f9a..c64f6945d 100644 --- a/nova/tests/integrated/integrated_helpers.py +++ b/nova/tests/integrated/integrated_helpers.py @@ -196,7 +196,7 @@ class IntegratedUnitTestContext(object): # so we treat it separately self.api_service = api_service - self.auth_url = 'http://localhost:8774/v1.0' + self.auth_url = 'http://localhost:8774/v1.1' return api_service @@ -229,18 +229,27 @@ class _IntegratedTestBase(test.TestCase): server = {} image = self.user.get_valid_image(create=True) - image_id = image['id'] + LOG.debug("Image: %s" % image) - #TODO(justinsb): This is FUBAR - image_id = abs(hash(image_id)) + if 'imageRef' in image: + image_ref = image['imageRef'] + else: + #NOTE(justinsb): The imageRef code hasn't yet landed + LOG.warning("imageRef not yet in images output") + image_ref = image['id'] + + #TODO(justinsb): This is FUBAR + image_ref = abs(hash(image_ref)) + + image_ref = 'http://fake.server/%s' % image_ref # We now have a valid imageId - server['imageId'] = image_id + server['imageRef'] = image_ref # Set a valid flavorId flavor = self.api.get_flavors()[0] LOG.debug("Using flavor: %s" % flavor) - server['flavorId'] = flavor['id'] + server['flavorRef'] = 'http://fake.server/%s' % flavor['id'] # Set a valid server name server_name = self.user.get_unused_server_name() diff --git a/nova/tests/integrated/test_extensions.py b/nova/tests/integrated/test_extensions.py new file mode 100644 index 000000000..39906e8d0 --- /dev/null +++ b/nova/tests/integrated/test_extensions.py @@ -0,0 +1,43 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Justin Santa Barbara +# 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 os + +from nova import flags +from nova.log import logging +from nova.tests.integrated import integrated_helpers + + +LOG = logging.getLogger('nova.tests.integrated') + +FLAGS = flags.FLAGS +FLAGS.verbose = True + + +class ExtensionsTest(integrated_helpers._IntegratedTestBase): + def _get_flags(self): + f = super(ExtensionsTest, self)._get_flags() + f['osapi_extensions_path'] = os.path.join(os.path.dirname(__file__), + "../api/openstack/extensions") + return f + + def test_get_foxnsocks(self): + """Simple check that fox-n-socks works""" + response = self.api.api_request('/foxnsocks') + foxnsocks = response.read() + LOG.debug("foxnsocks: %s" % foxnsocks) + self.assertEqual('Try to say this Mr. Knox, sir...', foxnsocks) diff --git a/nova/tests/integrated/test_servers.py b/nova/tests/integrated/test_servers.py index c4676adc8..a0a6e333a 100644 --- a/nova/tests/integrated/test_servers.py +++ b/nova/tests/integrated/test_servers.py @@ -48,27 +48,29 @@ class ServersTest(integrated_helpers._IntegratedTestBase): post = {'server': server} - # Without an imageId, this throws 500. + # Without an imageRef, this throws 500. # TODO(justinsb): Check whatever the spec says should be thrown here self.assertRaises(client.OpenStackApiException, self.api.post_server, post) - # With an invalid imageId, this throws 500. - server['imageId'] = self.user.get_invalid_image() + # With an invalid imageRef, this throws 500. + server['imageRef'] = self.user.get_invalid_image() # TODO(justinsb): Check whatever the spec says should be thrown here self.assertRaises(client.OpenStackApiException, self.api.post_server, post) - # Add a valid imageId - server['imageId'] = good_server['imageId'] + # Add a valid imageId/imageRef + server['imageId'] = good_server.get('imageId') + server['imageRef'] = good_server.get('imageRef') # Without flavorId, this throws 500 # TODO(justinsb): Check whatever the spec says should be thrown here self.assertRaises(client.OpenStackApiException, self.api.post_server, post) - # Set a valid flavorId - server['flavorId'] = good_server['flavorId'] + # Set a valid flavorId/flavorRef + server['flavorRef'] = good_server.get('flavorRef') + server['flavorId'] = good_server.get('flavorId') # Without a name, this throws 500 # TODO(justinsb): Check whatever the spec says should be thrown here diff --git a/nova/tests/integrated/test_volumes.py b/nova/tests/integrated/test_volumes.py index f173efea7..9ddfe85f7 100644 --- a/nova/tests/integrated/test_volumes.py +++ b/nova/tests/integrated/test_volumes.py @@ -42,6 +42,12 @@ class VolumesTest(integrated_helpers._IntegratedTestBase): f['volume_driver'] = 'nova.volume.driver.LoggingVolumeDriver' return f + def test_get_volumes_summary(self): + """Simple check that listing volumes works""" + volumes = self.api.get_volumes(False) + for volume in volumes: + LOG.debug("volume: %s" % volume) + def test_get_volumes(self): """Simple check that listing volumes works""" volumes = self.api.get_volumes() -- cgit From a78c1bd3e862700dbab68cc5011197270abd4b38 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Fri, 25 Mar 2011 21:46:14 -0400 Subject: Replaced import of an object with module import as per suggestion. --- nova/tests/api/openstack/test_images.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 3bf710d1a..5279ca77c 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -26,8 +26,7 @@ import datetime import os import shutil import tempfile - -from xml.dom.minidom import parseString +import xml.dom.minidom as minidom import stubout import webob @@ -326,10 +325,10 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): request.accept = "application/xml" response = request.get_response(fakes.wsgi_app()) - actual_image = parseString(response.body.replace(" ", "")) + actual_image = minidom.parseString(response.body.replace(" ", "")) expected_now = self.NOW_API_FORMAT - expected_image = parseString(""" + expected_image = minidom.parseString(""" Image not found. @@ -396,7 +395,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): """.replace(" ", "")) - actual = parseString(response.body.replace(" ", "")) + actual = minidom.parseString(response.body.replace(" ", "")) self.assertEqual(expected.toxml(), actual.toxml()) @@ -422,7 +421,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): response = request.get_response(fakes.wsgi_app()) self.assertEqual(404, response.status_int) - expected = parseString(""" + expected = minidom.parseString(""" Image not found. @@ -430,7 +429,7 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): """.replace(" ", "")) - actual = parseString(response.body.replace(" ", "")) + actual = minidom.parseString(response.body.replace(" ", "")) self.assertEqual(expected.toxml(), actual.toxml()) -- cgit From cb8a13e3751cc12f7157094d094c7a26d6f583f0 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Sun, 27 Mar 2011 15:30:47 -0400 Subject: Fix utils checking --- nova/virt/disk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/disk.py b/nova/virt/disk.py index fd45e2c51..15cd49789 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -124,7 +124,7 @@ def setup_container(image, container_dir=None): """ nbd = "False" device = _link_device(image, nbd) - err = utils.execute('sudo', 'mount', device, container_dir) + out, err = utils.execute('sudo', 'mount', device, container_dir) if err: raise exception.Error(_('Failed to mount filesystem: %s') % err) -- cgit From b3f8e9fb546c621946563af0908e43cb01c50431 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Sun, 27 Mar 2011 18:48:32 -0700 Subject: Bunch of style fixes --- nova/api/openstack/common.py | 1 + nova/api/openstack/extensions.py | 2 +- .../incubator/volumes/volume_attachments.py | 21 +++++++++++---------- nova/api/openstack/incubator/volumes/volumes.py | 13 +++++++------ nova/compute/manager.py | 2 +- nova/image/fake.py | 1 + nova/tests/integrated/api/client.py | 4 +++- nova/tests/integrated/integrated_helpers.py | 12 +++++------- nova/tests/integrated/test_extensions.py | 1 + nova/tests/integrated/test_servers.py | 15 ++++++++------- nova/tests/integrated/test_volumes.py | 7 ++++--- nova/virt/driver.py | 13 ++++++------- 12 files changed, 49 insertions(+), 43 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 4ab6b7a81..75aeb0a5f 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -26,6 +26,7 @@ from nova import log as logging LOG = logging.getLogger('common') + FLAGS = flags.FLAGS diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 6a8ce9669..e81ffb3d3 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -68,7 +68,7 @@ class ExtensionDescriptor(object): """The timestamp when the extension was last updated e.g. '2011-01-22T13:25:27-06:00'""" - #NOTE(justinsb): Huh? Isn't this defined by the namespace? + #NOTE(justinsb): Not sure of the purpose of this is, vs the XML NS raise NotImplementedError() def get_resources(self): diff --git a/nova/api/openstack/incubator/volumes/volume_attachments.py b/nova/api/openstack/incubator/volumes/volume_attachments.py index 58a9a727b..93786d1c7 100644 --- a/nova/api/openstack/incubator/volumes/volume_attachments.py +++ b/nova/api/openstack/incubator/volumes/volume_attachments.py @@ -29,11 +29,12 @@ from nova.api.openstack import faults LOG = logging.getLogger("nova.api.volumes") + FLAGS = flags.FLAGS def _translate_detail_view(context, volume): - """ Maps keys for details view""" + """Maps keys for details view""" d = _translate_summary_view(context, volume) @@ -43,12 +44,12 @@ def _translate_detail_view(context, volume): def _translate_summary_view(context, vol): - """ Maps keys for summary view""" + """Maps keys for summary view""" d = {} volume_id = vol['id'] - # NOTE(justinsb): We use the volume id as the id of the attachment object + #NOTE(justinsb): We use the volume id as the id of the attachment object d['id'] = volume_id d['volumeId'] = volume_id @@ -61,7 +62,7 @@ def _translate_summary_view(context, vol): class Controller(wsgi.Controller): - """ The volume attachment API controller for the Openstack API + """The volume attachment API controller for the Openstack API A child resource of the server. Note that we use the volume id as the ID of the attachment (though this is not guaranteed externally)""" @@ -80,7 +81,7 @@ class Controller(wsgi.Controller): super(Controller, self).__init__() def index(self, req, server_id): - """ Returns the list of volume attachments for a given instance """ + """Returns the list of volume attachments for a given instance """ return self._items(req, server_id, entity_maker=_translate_summary_view) @@ -102,7 +103,7 @@ class Controller(wsgi.Controller): return {'volumeAttachment': _translate_detail_view(context, vol)} def create(self, req, server_id): - """ Attach a volume to an instance """ + """Attach a volume to an instance """ context = req.environ['nova.context'] env = self._deserialize(req.body, req.get_content_type()) @@ -130,7 +131,7 @@ class Controller(wsgi.Controller): attachment['id'] = volume_id attachment['volumeId'] = volume_id - # NOTE(justinsb): And now, we have a problem... + #NOTE(justinsb): And now, we have a problem... # The attach is async, so there's a window in which we don't see # the attachment (until the attachment completes). We could also # get problems with concurrent requests. I think we need an @@ -138,15 +139,15 @@ class Controller(wsgi.Controller): # change. # For now, we'll probably have to rely on libraries being smart - # TODO: How do I return "accepted" here?? + #TODO(justinsb): How do I return "accepted" here? return {'volumeAttachment': attachment} def update(self, _req, _server_id, _id): - """ Update a volume attachment. We don't currently support this.""" + """Update a volume attachment. We don't currently support this.""" return faults.Fault(exc.HTTPBadRequest()) def delete(self, req, server_id, id): - """ Detach a volume from an instance """ + """Detach a volume from an instance """ context = req.environ['nova.context'] volume_id = id diff --git a/nova/api/openstack/incubator/volumes/volumes.py b/nova/api/openstack/incubator/volumes/volumes.py index ec3b9a6c8..e122bb465 100644 --- a/nova/api/openstack/incubator/volumes/volumes.py +++ b/nova/api/openstack/incubator/volumes/volumes.py @@ -26,11 +26,12 @@ from nova.api.openstack import faults LOG = logging.getLogger("nova.api.volumes") + FLAGS = flags.FLAGS def _translate_detail_view(context, vol): - """ Maps keys for details view""" + """Maps keys for details view""" d = _translate_summary_view(context, vol) @@ -40,7 +41,7 @@ def _translate_detail_view(context, vol): def _translate_summary_view(_context, vol): - """ Maps keys for summary view""" + """Maps keys for summary view""" d = {} instance_id = None @@ -78,7 +79,7 @@ def _translate_summary_view(_context, vol): class Controller(wsgi.Controller): - """ The Volumes API controller for the OpenStack API """ + """The Volumes API controller for the OpenStack API""" _serialization_metadata = { 'application/xml': { @@ -109,7 +110,7 @@ class Controller(wsgi.Controller): return {'volume': _translate_detail_view(context, vol)} def delete(self, req, id): - """ Delete a volume """ + """Delete a volume """ context = req.environ['nova.context'] LOG.audit(_("Delete volume with id: %s"), id, context=context) @@ -121,11 +122,11 @@ class Controller(wsgi.Controller): return exc.HTTPAccepted() def index(self, req): - """ Returns a summary list of volumes""" + """Returns a summary list of volumes""" return self._items(req, entity_maker=_translate_summary_view) def detail(self, req): - """ Returns a detailed list of volumes """ + """Returns a detailed list of volumes """ return self._items(req, entity_maker=_translate_detail_view) def _items(self, req, entity_maker): diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 468771f46..10636f602 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1078,6 +1078,6 @@ class ComputeManager(manager.SchedulerDependentManager): # Are there VMs not in the DB? for vm_not_found_in_db in vms_not_found_in_db: name = vm_not_found_in_db - # TODO(justinsb): What to do here? Adopt it? Shut it down? + #TODO(justinsb): What to do here? Adopt it? Shut it down? LOG.warning(_("Found VM not in DB: '%(name)s'. Ignoring") % locals()) diff --git a/nova/image/fake.py b/nova/image/fake.py index 9d28e316d..29159f337 100644 --- a/nova/image/fake.py +++ b/nova/image/fake.py @@ -24,6 +24,7 @@ from nova.image import service LOG = logging.getLogger('nova.image.fake') + FLAGS = flags.FLAGS diff --git a/nova/tests/integrated/api/client.py b/nova/tests/integrated/api/client.py index 023871bda..688ba8778 100644 --- a/nova/tests/integrated/api/client.py +++ b/nova/tests/integrated/api/client.py @@ -56,7 +56,9 @@ class OpenStackApiNotFoundException(OpenStackApiException): class TestOpenStackClient(object): - """ A really basic OpenStack API client that is under our control, + """Simple OpenStack API Client. + + This is a really basic OpenStack API client that is under our control, so we can make changes / insert hooks for testing""" def __init__(self, auth_user, auth_key, auth_uri): diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py index c64f6945d..7aed69099 100644 --- a/nova/tests/integrated/integrated_helpers.py +++ b/nova/tests/integrated/integrated_helpers.py @@ -85,10 +85,10 @@ class TestUser(object): def get_valid_image(self, create=False): images = self.openstack_api.get_images() if create and not images: - # TODO(justinsb): No way to create an image through API??? + #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??") + raise exception.Error("No way to create an image through API") if images: return images[0] @@ -124,7 +124,7 @@ class IntegratedUnitTestContext(object): self._start_volume_service() self._start_scheduler_service() - # NOTE(justinsb): There's a bug here which is eluding me... + #NOTE(justinsb): There's a bug here which is eluding me... # If we start the network_service, all is good, but then subsequent # tests fail: CloudTestCase.test_ajax_console in particular. #self._start_network_service() @@ -192,7 +192,7 @@ class IntegratedUnitTestContext(object): if not api_service: raise Exception("API Service was None") - # NOTE(justinsb): The API service doesn't have a kill method yet, + #NOTE(justinsb): The API service doesn't have a kill method yet, # so we treat it separately self.api_service = api_service @@ -217,9 +217,7 @@ class _IntegratedTestBase(test.TestCase): super(_IntegratedTestBase, self).tearDown() def _get_flags(self): - """An opportunity to setup flags, before the services are started - - Warning - this is a bit flaky till the WSGI recycle code lands""" + """An opportunity to setup flags, before the services are started""" f = {} f['image_service'] = 'nova.image.fake.MockImageService' f['fake_network'] = True diff --git a/nova/tests/integrated/test_extensions.py b/nova/tests/integrated/test_extensions.py index 39906e8d0..1aa471f49 100644 --- a/nova/tests/integrated/test_extensions.py +++ b/nova/tests/integrated/test_extensions.py @@ -24,6 +24,7 @@ from nova.tests.integrated import integrated_helpers LOG = logging.getLogger('nova.tests.integrated') + FLAGS = flags.FLAGS FLAGS.verbose = True diff --git a/nova/tests/integrated/test_servers.py b/nova/tests/integrated/test_servers.py index a0a6e333a..59e5dde14 100644 --- a/nova/tests/integrated/test_servers.py +++ b/nova/tests/integrated/test_servers.py @@ -26,6 +26,7 @@ from nova.tests.integrated.api import client LOG = logging.getLogger('nova.tests.integrated') + FLAGS = flags.FLAGS FLAGS.verbose = True @@ -49,13 +50,13 @@ class ServersTest(integrated_helpers._IntegratedTestBase): post = {'server': server} # Without an imageRef, this throws 500. - # TODO(justinsb): Check whatever the spec says should be thrown here + #TODO(justinsb): Check whatever the spec says should be thrown here self.assertRaises(client.OpenStackApiException, self.api.post_server, post) # With an invalid imageRef, this throws 500. server['imageRef'] = self.user.get_invalid_image() - # TODO(justinsb): Check whatever the spec says should be thrown here + #TODO(justinsb): Check whatever the spec says should be thrown here self.assertRaises(client.OpenStackApiException, self.api.post_server, post) @@ -64,7 +65,7 @@ class ServersTest(integrated_helpers._IntegratedTestBase): server['imageRef'] = good_server.get('imageRef') # Without flavorId, this throws 500 - # TODO(justinsb): Check whatever the spec says should be thrown here + #TODO(justinsb): Check whatever the spec says should be thrown here self.assertRaises(client.OpenStackApiException, self.api.post_server, post) @@ -73,7 +74,7 @@ class ServersTest(integrated_helpers._IntegratedTestBase): server['flavorId'] = good_server.get('flavorId') # Without a name, this throws 500 - # TODO(justinsb): Check whatever the spec says should be thrown here + #TODO(justinsb): Check whatever the spec says should be thrown here self.assertRaises(client.OpenStackApiException, self.api.post_server, post) @@ -105,7 +106,7 @@ class ServersTest(integrated_helpers._IntegratedTestBase): break # It should be available... - # TODO(justinsb): Mock doesn't yet do this... + #TODO(justinsb): Mock doesn't yet do this... #self.assertEqual('available', found_server['status']) self._delete_server(created_server_id) @@ -125,7 +126,7 @@ class ServersTest(integrated_helpers._IntegratedTestBase): LOG.debug("Found_server=%s" % found_server) - # TODO(justinsb): Mock doesn't yet do accurate state changes + #TODO(justinsb): Mock doesn't yet do accurate state changes #if found_server['status'] != 'deleting': # break time.sleep(1) @@ -133,7 +134,7 @@ class ServersTest(integrated_helpers._IntegratedTestBase): # Should be gone self.assertFalse(found_server) -# TODO(justinsb): Enable this unit test when the metadata bug is fixed +#TODO(justinsb): Enable this unit test when the metadata bug is fixed # def test_create_server_with_metadata(self): # """Creates a server with metadata""" # diff --git a/nova/tests/integrated/test_volumes.py b/nova/tests/integrated/test_volumes.py index 9ddfe85f7..89480618d 100644 --- a/nova/tests/integrated/test_volumes.py +++ b/nova/tests/integrated/test_volumes.py @@ -27,6 +27,7 @@ from nova.volume import driver LOG = logging.getLogger('nova.tests.integrated') + FLAGS = flags.FLAGS FLAGS.verbose = True @@ -55,7 +56,7 @@ class VolumesTest(integrated_helpers._IntegratedTestBase): LOG.debug("volume: %s" % volume) def _poll_while(self, volume_id, continue_states, max_retries=5): - """ Poll (briefly) while the state is in continue_states""" + """Poll (briefly) while the state is in continue_states""" retries = 0 while True: try: @@ -144,7 +145,7 @@ class VolumesTest(integrated_helpers._IntegratedTestBase): # Create server server_req = {'server': self._build_minimal_create_server_request()} - # NOTE(justinsb): Create an extra server so that server_id != volume_id + #NOTE(justinsb): Create an extra server so that server_id != volume_id self.api.post_server(server_req) created_server = self.api.post_server(server_req) LOG.debug("created_server: %s" % created_server) @@ -197,7 +198,7 @@ class VolumesTest(integrated_helpers._IntegratedTestBase): # This is just an implementation detail, but let's check it... self.assertEquals(volume_id, attachment_id) - # NOTE(justinsb): There's an issue with the attach code, in that + #NOTE(justinsb): There's an issue with the attach code, in that # it's currently asynchronous and not recorded until the attach # completes. So the caller must be 'smart', like this... attach_done = None diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 1bc3e4dfc..eafede17a 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -66,8 +66,7 @@ class ComputeDriver(object): raise NotImplementedError() def destroy(self, instance, cleanup=True): - """ - Destroy (shutdown and delete) the specified instance. + """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. @@ -89,12 +88,12 @@ class ComputeDriver(object): raise NotImplementedError() def get_console_pool_info(self, console_type): - """??? + """? Returns a dict containing: - :address: ??? - :username: ??? - :password: ??? + :address: ? + :username: ? + :password: ? """ raise NotImplementedError() @@ -126,7 +125,7 @@ class ComputeDriver(object): raise NotImplementedError() def snapshot(self, instance, image_id): - """ Create snapshot from a running VM instance """ + """Create snapshot from a running VM instance """ raise NotImplementedError() def finish_resize(self, instance, disk_info): -- cgit From a1accc23427347205f7f6c49402a4f366e5256b6 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Sun, 27 Mar 2011 19:38:20 -0700 Subject: pep8 fixes --- nova/tests/integrated/api/client.py | 2 +- nova/tests/integrated/integrated_helpers.py | 85 +++++++++-------------------- 2 files changed, 26 insertions(+), 61 deletions(-) (limited to 'nova') diff --git a/nova/tests/integrated/api/client.py b/nova/tests/integrated/api/client.py index 688ba8778..b9ed7b03b 100644 --- a/nova/tests/integrated/api/client.py +++ b/nova/tests/integrated/api/client.py @@ -57,7 +57,7 @@ class OpenStackApiNotFoundException(OpenStackApiException): class TestOpenStackClient(object): """Simple OpenStack API Client. - + This is a really basic OpenStack API client that is under our control, so we can make changes / insert hooks for testing""" diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py index 7aed69099..bc516b4b3 100644 --- a/nova/tests/integrated/integrated_helpers.py +++ b/nova/tests/integrated/integrated_helpers.py @@ -96,12 +96,10 @@ class TestUser(object): class IntegratedUnitTestContext(object): - def __init__(self): + def __init__(self, auth_url): self.auth_manager = manager.AuthManager() - self.api_service = None - self.services = [] - self.auth_url = None + self.auth_url = auth_url self.project_name = None self.test_user = None @@ -109,7 +107,6 @@ class IntegratedUnitTestContext(object): self.setup() def setup(self): - self._start_services() self._create_test_user() def _create_test_user(self): @@ -119,47 +116,8 @@ class IntegratedUnitTestContext(object): self.project_name = 'openstack' self._configure_project(self.project_name, self.test_user) - def _start_services(self): - self._start_compute_service() - self._start_volume_service() - self._start_scheduler_service() - - #NOTE(justinsb): There's a bug here which is eluding me... - # If we start the network_service, all is good, but then subsequent - # tests fail: CloudTestCase.test_ajax_console in particular. - #self._start_network_service() - - self._start_api_service() - - def _start_compute_service(self): - compute_service = service.Service.create(binary='nova-compute') - compute_service.start() - self.services.append(compute_service) - return compute_service - - def _start_network_service(self): - network_service = service.Service.create(binary='nova-network') - network_service.start() - self.services.append(network_service) - return network_service - - def _start_volume_service(self): - volume_service = service.Service.create(binary='nova-volume') - volume_service.start() - self.services.append(volume_service) - return volume_service - - def _start_scheduler_service(self): - scheduler_service = service.Service.create(binary='nova-scheduler') - scheduler_service.start() - self.services.append(scheduler_service) - return scheduler_service - def cleanup(self): self.test_user = None - for svc in self.services: - svc.kill() - self.services = [] def _create_unittest_user(self): users = self.auth_manager.get_users() @@ -185,21 +143,6 @@ class IntegratedUnitTestContext(object): else: self.auth_manager.add_to_project(user.name, project_name) - def _start_api_service(self): - api_service = service.ApiService.create() - api_service.start() - - if not api_service: - raise Exception("API Service was None") - - #NOTE(justinsb): The API service doesn't have a kill method yet, - # so we treat it separately - self.api_service = api_service - - self.auth_url = 'http://localhost:8774/v1.1' - - return api_service - class _IntegratedTestBase(test.TestCase): def setUp(self): @@ -208,10 +151,32 @@ class _IntegratedTestBase(test.TestCase): f = self._get_flags() self.flags(**f) - self.context = IntegratedUnitTestContext() + # set up services + self.start_service('compute') + self.start_service('volume') + #NOTE(justinsb): There's a bug here which is eluding me... + # If we start the network_service, all is good, but then subsequent + # tests fail: CloudTestCase.test_ajax_console in particular. + #self.start_service('network') + self.start_service('scheduler') + + self.auth_url = self._start_api_service() + + self.context = IntegratedUnitTestContext(self.auth_url) + self.user = self.context.test_user self.api = self.user.openstack_api + def _start_api_service(self): + api_service = service.ApiService.create() + api_service.start() + + if not api_service: + raise Exception("API Service was None") + + auth_url = 'http://localhost:8774/v1.1' + return auth_url + def tearDown(self): self.context.cleanup() super(_IntegratedTestBase, self).tearDown() -- cgit From 8957914ad9dd7691b2a43d977d845e00f7dd48c4 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Mon, 28 Mar 2011 10:54:29 +0100 Subject: addressed termies review (first round) --- nova/network/xenapi_net.py | 26 +++---- nova/tests/db/fakes.py | 8 +- nova/tests/fake_utils.py | 11 +-- nova/tests/fake_utils.py.moved | 106 --------------------------- nova/tests/test_xenapi.py | 37 +++++----- nova/virt/xenapi/network_utils.py | 2 +- nova/virt/xenapi/vmops.py | 149 ++++++++++++++++++++++---------------- 7 files changed, 125 insertions(+), 214 deletions(-) delete mode 100644 nova/tests/fake_utils.py.moved (limited to 'nova') diff --git a/nova/network/xenapi_net.py b/nova/network/xenapi_net.py index 1725a4b7a..7a9da8046 100644 --- a/nova/network/xenapi_net.py +++ b/nova/network/xenapi_net.py @@ -35,19 +35,19 @@ FLAGS = flags.FLAGS def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None): - """Create a vlan and bridge unless they already exist""" - #open xenapi session + """Create a vlan and bridge unless they already exist.""" + # Open xenapi session LOG.debug("ENTERING ensure_vlan_bridge in xenapi net") url = FLAGS.xenapi_connection_url username = FLAGS.xenapi_connection_username password = FLAGS.xenapi_connection_password session = XenAPISession(url, username, password) - #check whether bridge already exists - #retrieve network whose name_label is "bridge" + # Check whether bridge already exists + # Retrieve network whose name_label is "bridge" network_ref = NetworkHelper.find_network_with_name_label(session, bridge) if network_ref == None: - #if bridge does not exists - #1 - create network + # If bridge does not exists + # 1 - create network description = "network for nova bridge %s" % bridge network_rec = { 'name_label': bridge, @@ -55,28 +55,28 @@ def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None): 'other_config': {}, } network_ref = session.call_xenapi('network.create', network_rec) - #2 - find PIF for VLAN + # 2 - find PIF for VLAN expr = 'field "device" = "%s" and \ field "VLAN" = "-1"' % FLAGS.vlan_interface pifs = session.call_xenapi('PIF.get_all_records_where', expr) pif_ref = None - #multiple PIF are ok: we are dealing with a pool + # Multiple PIF are ok: we are dealing with a pool if len(pifs) == 0: raise Exception( _('Found no PIF for device %s') % FLAGS.vlan_interface) - #3 - create vlan for network + # 3 - create vlan for network for pif_ref in pifs.keys(): session.call_xenapi('VLAN.create', pif_ref, str(vlan_num), network_ref) else: - #check VLAN tag is appropriate + # Check VLAN tag is appropriate network_rec = session.call_xenapi('network.get_record', network_ref) - #retrieve PIFs from network + # Retrieve PIFs from network for pif_ref in network_rec['PIFs']: - #retrieve VLAN from PIF + # Retrieve VLAN from PIF pif_rec = session.call_xenapi('PIF.get_record', pif_ref) pif_vlan = int(pif_rec['VLAN']) - #raise an exception if VLAN <> vlan_num + # Raise an exception if VLAN <> vlan_num if pif_vlan != vlan_num: raise Exception(_("PIF %(pif_rec['uuid'])s for network " "%(bridge)s has VLAN id %(pif_vlan)d. " diff --git a/nova/tests/db/fakes.py b/nova/tests/db/fakes.py index 05a47d4c9..f7610aa56 100644 --- a/nova/tests/db/fakes.py +++ b/nova/tests/db/fakes.py @@ -25,7 +25,7 @@ from nova import utils def stub_out_db_instance_api(stubs, injected=True): - """ Stubs out the db API for creating Instances """ + """Stubs out the db API for creating Instances.""" INSTANCE_TYPES = { 'm1.tiny': dict(memory_mb=512, @@ -91,7 +91,7 @@ def stub_out_db_instance_api(stubs, injected=True): 'network_id': 'fake_flat'} class FakeModel(object): - """ Stubs out for model """ + """Stubs out for model.""" def __init__(self, values): self.values = values @@ -111,7 +111,7 @@ def stub_out_db_instance_api(stubs, injected=True): return INSTANCE_TYPES[name] def fake_network_get_by_instance(context, instance_id): - #even instance numbers are on vlan networks + # Even instance numbers are on vlan networks if instance_id % 2 == 0: return FakeModel(vlan_network_fields) else: @@ -119,7 +119,7 @@ def stub_out_db_instance_api(stubs, injected=True): return FakeModel(network_fields) def fake_network_get_all_by_instance(context, instance_id): - #even instance numbers are on vlan networks + # Even instance numbers are on vlan networks if instance_id % 2 == 0: return [FakeModel(vlan_network_fields)] else: diff --git a/nova/tests/fake_utils.py b/nova/tests/fake_utils.py index 823c775cb..23996ba95 100644 --- a/nova/tests/fake_utils.py +++ b/nova/tests/fake_utils.py @@ -14,8 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. -"""This modules stubs out functions in nova.utils -""" +"""This modules stubs out functions in nova.utils.""" import re import types @@ -42,22 +41,20 @@ def fake_execute_clear_log(): def fake_execute_set_repliers(repliers): - """Allows the client to configure replies to commands""" + """Allows the client to configure replies to commands.""" global _fake_execute_repliers _fake_execute_repliers = repliers def fake_execute_default_reply_handler(*ignore_args, **ignore_kwargs): """A reply handler for commands that haven't been added to the reply - list. Returns empty strings for stdout and stderr - """ + list. Returns empty strings for stdout and stderr.""" return '', '' def fake_execute(*cmd_parts, **kwargs): """This function stubs out execute, optionally executing - a preconfigued function to return expected data - """ + a preconfigued function to return expected data.""" global _fake_execute_repliers process_input = kwargs.get('process_input', None) diff --git a/nova/tests/fake_utils.py.moved b/nova/tests/fake_utils.py.moved deleted file mode 100644 index 8982f50be..000000000 --- a/nova/tests/fake_utils.py.moved +++ /dev/null @@ -1,106 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2011 Citrix Systems, 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. - -"""This modules stubs out functions in nova.utils -""" - -import re -import types - -from eventlet import greenthread - -from nova import exception -from nova import log as logging -from nova import utils - -LOG = logging.getLogger('nova.tests.fake_utils') - -_fake_execute_repliers = [] -_fake_execute_log = [] - - -def fake_execute_get_log(): - global _fake_execute_log - return _fake_execute_log - - -def fake_execute_clear_log(): - global _fake_execute_log - _fake_execute_log = [] - - -def fake_execute_set_repliers(repliers): - """Allows the client to configure replies to commands""" - global _fake_execute_repliers - _fake_execute_repliers = repliers - - -def fake_execute_default_reply_handler(*ignore_args, **ignore_kwargs): - """A reply handler for commands that haven't been added to the reply - list. Returns empty strings for stdout and stderr - """ - return '', '' - - -def fake_execute(*cmd, **kwargs): - """This function stubs out execute, optionally executing - a preconfigued function to return expected data - """ - global _fake_execute_repliers - - process_input = kwargs.get('process_input', None) - addl_env = kwargs.get('addl_env', None) - check_exit_code = kwargs.get('check_exit_code', 0) - cmd_map = map(str, cmd) - cmd_str = ' '.join(cmd_map) - - LOG.debug(_("Faking execution of cmd (subprocess): %s"), cmd_str) - _fake_execute_log.append(cmd_str) - - reply_handler = fake_execute_default_reply_handler - - for fake_replier in _fake_execute_repliers: - if re.match(fake_replier[0], cmd_str): - reply_handler = fake_replier[1] - LOG.debug(_('Faked command matched %s') % fake_replier[0]) - break - - if isinstance(reply_handler, types.StringTypes): - # If the reply handler is a string, return it as stdout - reply = reply_handler, '' - else: - try: - # Alternative is a function, so call it - reply = reply_handler(cmd, - process_input=process_input, - addl_env=addl_env, - check_exit_code=check_exit_code) - except exception.ProcessExecutionError as e: - LOG.debug(_('Faked command raised an exception %s' % str(e))) - raise - - LOG.debug(_("Reply to faked command is stdout='%(0)s' stderr='%(1)s'") % - {'0': reply[0], '1': reply[1]}) - - # Replicate the sleep call in the real function - greenthread.sleep(0) - return reply - - -def stub_out_utils_execute(stubs): - fake_execute_set_repliers([]) - fake_execute_clear_log() - stubs.Set(utils, 'execute', fake_execute) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index f91f37d4b..6ec0525a7 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -15,7 +15,7 @@ # under the License. """ -Test suite for XenAPI +Test suite for XenAPI. """ import functools @@ -66,7 +66,7 @@ def stub_vm_utils_with_vdi_attached_here(function, should_return=True): class XenAPIVolumeTestCase(test.TestCase): """ - Unit tests for Volume operations + Unit tests for Volume operations. """ def setUp(self): super(XenAPIVolumeTestCase, self).setUp() @@ -76,7 +76,6 @@ class XenAPIVolumeTestCase(test.TestCase): FLAGS.xenapi_connection_url = 'test_url' FLAGS.xenapi_connection_password = 'test_pass' db_fakes.stub_out_db_instance_api(self.stubs) - #db_fakes.stub_out_db_network_api(self.stubs) stubs.stub_out_get_target(self.stubs) xenapi_fake.reset() self.values = {'id': 1, @@ -102,7 +101,7 @@ class XenAPIVolumeTestCase(test.TestCase): return db.volume_create(self.context, vol) def test_create_iscsi_storage(self): - """ This shows how to test helper classes' methods """ + """This shows how to test helper classes' methods.""" stubs.stubout_session(self.stubs, stubs.FakeSessionForVolumeTests) session = xenapi_conn.XenAPISession('test_url', 'root', 'test_pass') helper = volume_utils.VolumeHelper @@ -117,7 +116,7 @@ class XenAPIVolumeTestCase(test.TestCase): db.volume_destroy(context.get_admin_context(), vol['id']) def test_parse_volume_info_raise_exception(self): - """ This shows how to test helper classes' methods """ + """This shows how to test helper classes' methods.""" stubs.stubout_session(self.stubs, stubs.FakeSessionForVolumeTests) session = xenapi_conn.XenAPISession('test_url', 'root', 'test_pass') helper = volume_utils.VolumeHelper @@ -131,7 +130,7 @@ class XenAPIVolumeTestCase(test.TestCase): db.volume_destroy(context.get_admin_context(), vol['id']) def test_attach_volume(self): - """ This shows how to test Ops classes' methods """ + """This shows how to test Ops classes' methods.""" stubs.stubout_session(self.stubs, stubs.FakeSessionForVolumeTests) conn = xenapi_conn.get_connection(False) volume = self._create_volume() @@ -150,7 +149,7 @@ class XenAPIVolumeTestCase(test.TestCase): check() def test_attach_volume_raise_exception(self): - """ This shows how to test when exceptions are raised """ + """This shows how to test when exceptions are raised.""" stubs.stubout_session(self.stubs, stubs.FakeSessionForVolumeFailedTests) conn = xenapi_conn.get_connection(False) @@ -174,7 +173,7 @@ def reset_network(*args): class XenAPIVMTestCase(test.TestCase): """ - Unit tests for VM operations + Unit tests for VM operations. """ def setUp(self): super(XenAPIVMTestCase, self).setUp() @@ -475,21 +474,21 @@ class XenAPIVMTestCase(test.TestCase): network_manager='nova.network.manager.VlanManager', network_driver='nova.network.xenapi_net', vlan_interface='fake0') - #reset network table + # Reset network table xenapi_fake.reset_table('network') - #instance id = 2 will use vlan network (see db/fakes.py) + # Instance id = 2 will use vlan network (see db/fakes.py) fake_instance_id = 2 network_bk = self.network - #ensure we use xenapi_net driver + # Ensure we use xenapi_net driver self.network = utils.import_object(FLAGS.network_manager) self.network.setup_compute_network(None, fake_instance_id) self._test_spawn(glance_stubs.FakeGlance.IMAGE_MACHINE, glance_stubs.FakeGlance.IMAGE_KERNEL, glance_stubs.FakeGlance.IMAGE_RAMDISK, instance_id=fake_instance_id) - #TODO(salvatore-orlando): a complete test here would require - #a check for making sure the bridge for the VM's VIF is - #consistent with bridge specified in nova db + # TODO(salvatore-orlando): a complete test here would require + # A check for making sure the bridge for the VM's VIF is + # consistent with bridge specified in nova db self.network = network_bk def test_spawn_with_network_qos(self): @@ -521,7 +520,7 @@ class XenAPIVMTestCase(test.TestCase): self.stubs.UnsetAll() def _create_instance(self): - """Creates and spawns a test instance""" + """Creates and spawns a test instance.""" stubs.stubout_loopingcall_start(self.stubs) values = { 'id': 1, @@ -540,7 +539,7 @@ class XenAPIVMTestCase(test.TestCase): class XenAPIDiffieHellmanTestCase(test.TestCase): """ - Unit tests for Diffie-Hellman code + Unit tests for Diffie-Hellman code. """ def setUp(self): super(XenAPIDiffieHellmanTestCase, self).setUp() @@ -566,7 +565,7 @@ class XenAPIDiffieHellmanTestCase(test.TestCase): class XenAPIMigrateInstance(test.TestCase): """ - Unit test for verifying migration-related actions + Unit test for verifying migration-related actions. """ def setUp(self): @@ -623,7 +622,7 @@ class XenAPIMigrateInstance(test.TestCase): class XenAPIDetermineDiskImageTestCase(test.TestCase): """ - Unit tests for code that detects the ImageType + Unit tests for code that detects the ImageType. """ def setUp(self): super(XenAPIDetermineDiskImageTestCase, self).setUp() @@ -644,7 +643,7 @@ class XenAPIDetermineDiskImageTestCase(test.TestCase): def test_instance_disk(self): """ - If a kernel is specified then the image type is DISK (aka machine) + If a kernel is specified then the image type is DISK (aka machine). """ FLAGS.xenapi_image_service = 'objectstore' self.fake_instance.image_id = glance_stubs.FakeGlance.IMAGE_MACHINE diff --git a/nova/virt/xenapi/network_utils.py b/nova/virt/xenapi/network_utils.py index 546f6bea9..94d8e5199 100644 --- a/nova/virt/xenapi/network_utils.py +++ b/nova/virt/xenapi/network_utils.py @@ -44,7 +44,7 @@ class NetworkHelper(HelperBase): """ Return the network on which the bridge is attached, if found. The bridge is defined in the nova db and can be found either in the - 'bridge' or 'name_label' fields of the XenAPI network record + 'bridge' or 'name_label' fields of the XenAPI network record. """ expr = 'field "name__label" = "%s" or ' \ 'field "bridge" = "%s"' % (bridge, bridge) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 52e1f9eba..9b18ac732 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -58,7 +58,7 @@ class VMOps(object): VMHelper.XenAPI = self.XenAPI def list_instances(self): - """List VM instances""" + """List VM instances.""" # TODO(justinsb): Should we just always use the details method? # Seems to be the same number of API calls.. vm_refs = [] @@ -69,7 +69,7 @@ class VMOps(object): return vm_refs def list_instances_detail(self): - """List VM instances, returning InstanceInfo objects""" + """List VM instances, returning InstanceInfo objects.""" instance_infos = [] for vm_ref in self._session.get_xenapi().VM.get_all(): vm_rec = self._session.get_xenapi().VM.get_record(vm_ref) @@ -119,11 +119,11 @@ class VMOps(object): self._spawn(instance, vm_ref) def spawn_rescue(self, instance): - """Spawn a rescue instance""" + """Spawn a rescue instance.""" self.spawn(instance) def _create_vm(self, instance, vdi_uuid, network_info=None): - """Create VM instance""" + """Create VM instance.""" instance_name = instance.name vm_ref = VMHelper.lookup(self._session, instance_name) if vm_ref is not None: @@ -180,7 +180,7 @@ class VMOps(object): return vm_ref def _spawn(self, instance, vm_ref): - """Spawn a new instance""" + """Spawn a new instance.""" LOG.debug(_('Starting VM %s...'), vm_ref) self._start(instance, vm_ref) instance_name = instance.name @@ -236,7 +236,8 @@ class VMOps(object): return timer.start(interval=0.5, now=True) def _get_vm_opaque_ref(self, instance_or_vm): - """Refactored out the common code of many methods that receive either + """ + Refactored out the common code of many methods that receive either a vm name or a vm instance, and want a vm instance in return. """ # if instance_or_vm is a string it must be opaque ref or instance name @@ -264,21 +265,22 @@ class VMOps(object): return vm_ref def _acquire_bootlock(self, vm): - """Prevent an instance from booting""" + """Prevent an instance from booting.""" self._session.call_xenapi( "VM.set_blocked_operations", vm, {"start": ""}) def _release_bootlock(self, vm): - """Allow an instance to boot""" + """Allow an instance to boot.""" self._session.call_xenapi( "VM.remove_from_blocked_operations", vm, "start") def snapshot(self, instance, image_id): - """Create snapshot from a running VM instance + """ + Create snapshot from a running VM instance :param instance: instance to be snapshotted :param image_id: id of image to upload to @@ -330,11 +332,12 @@ class VMOps(object): return def migrate_disk_and_power_off(self, instance, dest): - """Copies a VHD from one host machine to another + """ + Copies a VHD from one host machine to another - :param instance: the instance that owns the VHD in question - :param dest: the destination host machine - :param disk_type: values are 'primary' or 'cow' + :param instance: the instance that owns the VHD in question. + :param dest: the destination host machine. + :param disk_type: values are 'primary' or 'cow'. """ vm_ref = VMHelper.lookup(self._session, instance.name) @@ -383,7 +386,7 @@ class VMOps(object): return {'base_copy': base_copy_uuid, 'cow': cow_uuid} def link_disks(self, instance, base_copy_uuid, cow_uuid): - """Links the base copy VHD to the COW via the XAPI plugin""" + """Links the base copy VHD to the COW via the XAPI plugin.""" vm_ref = VMHelper.lookup(self._session, instance.name) new_base_copy_uuid = str(uuid.uuid4()) new_cow_uuid = str(uuid.uuid4()) @@ -404,7 +407,7 @@ class VMOps(object): return new_cow_uuid def resize_instance(self, instance, vdi_uuid): - """Resize a running instance by changing it's RAM and disk size """ + """Resize a running instance by changing it's RAM and disk size.""" #TODO(mdietz): this will need to be adjusted for swap later #The new disk size must be in bytes @@ -418,13 +421,14 @@ class VMOps(object): LOG.debug(_("Resize instance %s complete") % (instance.name)) def reboot(self, instance): - """Reboot VM instance""" + """Reboot VM instance.""" vm_ref = self._get_vm_opaque_ref(instance) task = self._session.call_xenapi('Async.VM.clean_reboot', vm_ref) self._session.wait_for_task(task, instance.id) def set_admin_password(self, instance, new_pass): - """Set the root/admin password on the VM instance. This is done via + """ + Set the root/admin password on the VM instance. This is done via an agent running on the VM. Communication between nova and the agent is done via writing xenstore records. Since communication is done over the XenAPI RPC calls, we need to encrypt the password. We're using a @@ -462,7 +466,8 @@ class VMOps(object): return resp_dict['message'] def inject_file(self, instance, path, contents): - """Write a file to the VM instance. The path to which it is to be + """ + Write a file to the VM instance. The path to which it is to be written and the contents of the file need to be supplied; both will be base64-encoded to prevent errors with non-ASCII characters being transmitted. If the agent does not support file injection, or the user @@ -487,7 +492,7 @@ class VMOps(object): return resp_dict['message'] def _shutdown(self, instance, vm_ref, hard=True): - """Shutdown an instance""" + """Shutdown an instance.""" state = self.get_info(instance['name'])['state'] if state == power_state.SHUTDOWN: instance_name = instance.name @@ -511,11 +516,11 @@ class VMOps(object): LOG.exception(exc) def _shutdown_rescue(self, rescue_vm_ref): - """Shutdown a rescue instance""" + """Shutdown a rescue instance.""" self._session.call_xenapi("Async.VM.hard_shutdown", rescue_vm_ref) def _destroy_vdis(self, instance, vm_ref): - """Destroys all VDIs associated with a VM""" + """Destroys all VDIs associated with a VM.""" instance_id = instance.id LOG.debug(_("Destroying VDIs for Instance %(instance_id)s") % locals()) @@ -532,7 +537,7 @@ class VMOps(object): LOG.exception(exc) def _destroy_rescue_vdis(self, rescue_vm_ref): - """Destroys all VDIs associated with a rescued VM""" + """Destroys all VDIs associated with a rescued VM.""" vdi_refs = VMHelper.lookup_vm_vdis(self._session, rescue_vm_ref) for vdi_ref in vdi_refs: try: @@ -541,7 +546,7 @@ class VMOps(object): continue def _destroy_rescue_vbds(self, rescue_vm_ref): - """Destroys all VBDs tied to a rescue VM""" + """Destroys all VBDs tied to a rescue VM.""" vbd_refs = self._session.get_xenapi().VM.get_VBDs(rescue_vm_ref) for vbd_ref in vbd_refs: vbd_rec = self._session.get_xenapi().VBD.get_record(vbd_ref) @@ -589,7 +594,7 @@ class VMOps(object): LOG.debug(_("kernel/ramdisk files removed")) def _destroy_vm(self, instance, vm_ref): - """Destroys a VM record""" + """Destroys a VM record.""" instance_id = instance.id try: task = self._session.call_xenapi('Async.VM.destroy', vm_ref) @@ -600,7 +605,7 @@ class VMOps(object): LOG.debug(_("Instance %(instance_id)s VM destroyed") % locals()) def _destroy_rescue_instance(self, rescue_vm_ref): - """Destroy a rescue instance""" + """Destroy a rescue instance.""" self._destroy_rescue_vbds(rescue_vm_ref) self._shutdown_rescue(rescue_vm_ref) self._destroy_rescue_vdis(rescue_vm_ref) @@ -624,10 +629,10 @@ class VMOps(object): """ Destroys VM instance by performing: - 1. A shutdown if requested - 2. Destroying associated VDIs - 3. Destroying kernel and ramdisk files (if necessary) - 4. Destroying that actual VM record + 1. A shutdown if requested. + 2. Destroying associated VDIs. + 3. Destroying kernel and ramdisk files (if necessary). + 4. Destroying that actual VM record. """ if vm_ref is None: LOG.warning(_("VM is not present, skipping destroy...")) @@ -650,36 +655,36 @@ class VMOps(object): callback(ret) def pause(self, instance, callback): - """Pause VM instance""" + """Pause VM instance.""" vm_ref = self._get_vm_opaque_ref(instance) task = self._session.call_xenapi('Async.VM.pause', vm_ref) self._wait_with_callback(instance.id, task, callback) def unpause(self, instance, callback): - """Unpause VM instance""" + """Unpause VM instance.""" vm_ref = self._get_vm_opaque_ref(instance) task = self._session.call_xenapi('Async.VM.unpause', vm_ref) self._wait_with_callback(instance.id, task, callback) def suspend(self, instance, callback): - """suspend the specified instance""" + """Suspend the specified instance""" vm_ref = self._get_vm_opaque_ref(instance) task = self._session.call_xenapi('Async.VM.suspend', vm_ref) self._wait_with_callback(instance.id, task, callback) def resume(self, instance, callback): - """resume the specified instance""" + """Resume the specified instance.""" vm_ref = self._get_vm_opaque_ref(instance) task = self._session.call_xenapi('Async.VM.resume', vm_ref, False, True) self._wait_with_callback(instance.id, task, callback) def rescue(self, instance, callback): - """Rescue the specified instance - - shutdown the instance VM - - set 'bootlock' to prevent the instance from starting in rescue - - spawn a rescue VM (the vm name-label will be instance-N-rescue) - + """ + Rescue the specified instance + - shutdown the instance VM. + - set 'bootlock' to prevent the instance from starting in rescue. + - spawn a rescue VM (the vm name-label will be instance-N-rescue). """ rescue_vm_ref = VMHelper.lookup(self._session, "%s-rescue" % instance.name) @@ -703,9 +708,9 @@ class VMOps(object): def unrescue(self, instance, callback): """Unrescue the specified instance - - unplug the instance VM's disk from the rescue VM - - teardown the rescue VM - - release the bootlock to allow the instance VM to start + - unplug the instance VM's disk from the rescue VM. + - teardown the rescue VM. + - release the bootlock to allow the instance VM to start. """ rescue_vm_ref = VMHelper.lookup(self._session, @@ -723,7 +728,8 @@ class VMOps(object): self._start(instance, original_vm_ref) def poll_rescued_instances(self, timeout): - """Look for expirable rescued instances + """ + Look for expirable rescued instances - forcibly exit rescue mode for any instances that have been in rescue mode for >= the provided timeout """ @@ -761,30 +767,30 @@ class VMOps(object): False) def get_info(self, instance): - """Return data about VM instance""" + """Return data about VM instance.""" vm_ref = self._get_vm_opaque_ref(instance) vm_rec = self._session.get_xenapi().VM.get_record(vm_ref) return VMHelper.compile_info(vm_rec) def get_diagnostics(self, instance): - """Return data about VM diagnostics""" + """Return data about VM diagnostics.""" vm_ref = self._get_vm_opaque_ref(instance) vm_rec = self._session.get_xenapi().VM.get_record(vm_ref) return VMHelper.compile_diagnostics(self._session, vm_rec) def get_console_output(self, instance): - """Return snapshot of console""" + """Return snapshot of console.""" # TODO: implement this to fix pylint! return 'FAKE CONSOLE OUTPUT of instance' def get_ajax_console(self, instance): - """Return link to instance's ajax console""" + """Return link to instance's ajax console.""" # TODO: implement this! return 'http://fakeajaxconsole/fake_url' # TODO(tr3buchet) - remove this function after nova multi-nic def _get_network_info(self, instance): - """creates network info list for instance""" + """Creates network info list for instance.""" admin_context = context.get_admin_context() IPs = db.fixed_ip_get_all_by_instance(admin_context, instance['id']) @@ -826,7 +832,7 @@ class VMOps(object): def inject_network_info(self, instance, vm_ref, network_info): """ Generate the network info and make calls to place it into the - xenstore and the xenstore param list + xenstore and the xenstore param list. """ logging.debug(_("injecting network info to xs for vm: |%s|"), vm_ref) @@ -847,7 +853,7 @@ class VMOps(object): pass def create_vifs(self, vm_ref, network_info): - """Creates vifs for an instance""" + """Creates vifs for an instance.""" logging.debug(_("creating vif(s) for vm: |%s|"), vm_ref) # this function raises if vm_ref is not a vm_opaque_ref @@ -872,7 +878,8 @@ class VMOps(object): args, vm_ref) def list_from_xenstore(self, vm, path): - """Runs the xenstore-ls command to get a listing of all records + """ + Runs the xenstore-ls command to get a listing of all records from 'path' downward. Returns a dict with the sub-paths as keys, and the value stored in those paths as values. If nothing is found at that path, returns None. @@ -881,7 +888,8 @@ class VMOps(object): return json.loads(ret) def read_from_xenstore(self, vm, path): - """Returns the value stored in the xenstore record for the given VM + """ + Returns the value stored in the xenstore record for the given VM at the specified location. A XenAPIPlugin.PluginError will be raised if any error is encountered in the read process. """ @@ -897,7 +905,8 @@ class VMOps(object): return ret def write_to_xenstore(self, vm, path, value): - """Writes the passed value to the xenstore record for the given VM + """ + Writes the passed value to the xenstore record for the given VM at the specified location. A XenAPIPlugin.PluginError will be raised if any error is encountered in the write process. """ @@ -905,7 +914,8 @@ class VMOps(object): {'value': json.dumps(value)}) def clear_xenstore(self, vm, path): - """Deletes the VM's xenstore record for the specified path. + """ + Deletes the VM's xenstore record for the specified path. If there is no such record, the request is ignored. """ self._make_xenstore_call('delete_record', vm, path) @@ -922,7 +932,8 @@ class VMOps(object): def _make_plugin_call(self, plugin, method, vm, path, addl_args=None, vm_ref=None): - """Abstracts out the process of calling a method of a xenapi plugin. + """ + Abstracts out the process of calling a method of a xenapi plugin. Any errors raised by the plugin will in turn raise a RuntimeError here. """ instance_id = vm.id @@ -952,7 +963,8 @@ class VMOps(object): return ret def add_to_xenstore(self, vm, path, key, value): - """Adds the passed key/value pair to the xenstore record for + """ + Adds the passed key/value pair to the xenstore record for the given VM at the specified location. A XenAPIPlugin.PluginError will be raised if any error is encountered in the write process. """ @@ -965,7 +977,8 @@ class VMOps(object): self.write_to_xenstore(vm, path, current) def remove_from_xenstore(self, vm, path, key_or_keys): - """Takes either a single key or a list of keys and removes + """ + Takes either a single key or a list of keys and removes them from the xenstoreirecord data for the given VM. If the key doesn't exist, the request is ignored. """ @@ -992,7 +1005,8 @@ class VMOps(object): ###### names to distinguish them. (dabo) ######################################################################## def read_partial_from_param_xenstore(self, instance_or_vm, key_prefix): - """Returns a dict of all the keys in the xenstore parameter record + """ + Returns a dict of all the keys in the xenstore parameter record for the given instance that begin with the key_prefix. """ data = self.read_from_param_xenstore(instance_or_vm) @@ -1003,7 +1017,8 @@ class VMOps(object): return data def read_from_param_xenstore(self, instance_or_vm, keys=None): - """Returns the xenstore parameter record data for the specified VM + """ + Returns the xenstore parameter record data for the specified VM instance as a dict. Accepts an optional key or list of keys; if a value for 'keys' is passed, the returned dict is filtered to only return the values for those keys. @@ -1025,9 +1040,11 @@ class VMOps(object): return ret def add_to_param_xenstore(self, instance_or_vm, key, val): - """Takes a key/value pair and adds it to the xenstore parameter + """ + Takes a key/value pair and adds it to the xenstore parameter record for the given vm instance. If the key exists in xenstore, - it is overwritten""" + it is overwritten + """ vm_ref = self._get_vm_opaque_ref(instance_or_vm) self.remove_from_param_xenstore(instance_or_vm, key) jsonval = json.dumps(val) @@ -1035,7 +1052,8 @@ class VMOps(object): (vm_ref, key, jsonval)) def write_to_param_xenstore(self, instance_or_vm, mapping): - """Takes a dict and writes each key/value pair to the xenstore + """ + Takes a dict and writes each key/value pair to the xenstore parameter record for the given vm instance. Any existing data for those keys is overwritten. """ @@ -1043,7 +1061,8 @@ class VMOps(object): self.add_to_param_xenstore(instance_or_vm, k, v) def remove_from_param_xenstore(self, instance_or_vm, key_or_keys): - """Takes either a single key or a list of keys and removes + """ + Takes either a single key or a list of keys and removes them from the xenstore parameter record data for the given VM. If the key doesn't exist, the request is ignored. """ @@ -1069,7 +1088,8 @@ def _runproc(cmd): class SimpleDH(object): - """This class wraps all the functionality needed to implement + """ + This class wraps all the functionality needed to implement basic Diffie-Hellman-Merkle key exchange in Python. It features intelligent defaults for the prime and base numbers needed for the calculation, while allowing you to supply your own. It requires that @@ -1078,7 +1098,8 @@ class SimpleDH(object): is not available, a RuntimeError will be raised. """ def __init__(self, prime=None, base=None, secret=None): - """You can specify the values for prime and base if you wish; + """ + You can specify the values for prime and base if you wish; otherwise, reasonable default values will be used. """ if prime is None: -- cgit From 7cdc3add34b109e3f956f785b60a5aa5cf273e53 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Mon, 28 Mar 2011 12:24:41 +0200 Subject: Do not load extensions that start with a "_" --- nova/api/openstack/extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 9d98d849a..439612faa 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -317,7 +317,7 @@ class ExtensionManager(object): LOG.audit(_('Loading extension file: %s'), f) mod_name, file_ext = os.path.splitext(os.path.split(f)[-1]) ext_path = os.path.join(self.path, f) - if file_ext.lower() == '.py': + if file_ext.lower() == '.py' and not mod_name.startswith('_'): mod = imp.load_source(mod_name, ext_path) ext_name = mod_name[0].upper() + mod_name[1:] try: -- cgit From df946c08acba6fe1234b13f04d3c46c3973647c2 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Mon, 28 Mar 2011 11:52:28 +0100 Subject: addressed termie's review (second round) --- nova/network/xenapi_net.py | 14 ++-- nova/tests/test_xenapi.py | 2 +- nova/virt/xenapi/fake.py | 156 +++++++++++++++++++++------------------------ nova/virt/xenapi/vmops.py | 2 +- 4 files changed, 82 insertions(+), 92 deletions(-) (limited to 'nova') diff --git a/nova/network/xenapi_net.py b/nova/network/xenapi_net.py index 7a9da8046..8603fd842 100644 --- a/nova/network/xenapi_net.py +++ b/nova/network/xenapi_net.py @@ -49,11 +49,9 @@ def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None): # If bridge does not exists # 1 - create network description = "network for nova bridge %s" % bridge - network_rec = { - 'name_label': bridge, + network_rec = {'name_label': bridge, 'name_description': description, - 'other_config': {}, - } + 'other_config': {}} network_ref = session.call_xenapi('network.create', network_rec) # 2 - find PIF for VLAN expr = 'field "device" = "%s" and \ @@ -66,8 +64,10 @@ def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None): _('Found no PIF for device %s') % FLAGS.vlan_interface) # 3 - create vlan for network for pif_ref in pifs.keys(): - session.call_xenapi('VLAN.create', pif_ref, - str(vlan_num), network_ref) + session.call_xenapi('VLAN.create', + pif_ref, + str(vlan_num), + network_ref) else: # Check VLAN tag is appropriate network_rec = session.call_xenapi('network.get_record', network_ref) @@ -76,7 +76,7 @@ def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None): # Retrieve VLAN from PIF pif_rec = session.call_xenapi('PIF.get_record', pif_ref) pif_vlan = int(pif_rec['VLAN']) - # Raise an exception if VLAN <> vlan_num + # Raise an exception if VLAN != vlan_num if pif_vlan != vlan_num: raise Exception(_("PIF %(pif_rec['uuid'])s for network " "%(bridge)s has VLAN id %(pif_vlan)d. " diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 6ec0525a7..9fdd1feeb 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -487,7 +487,7 @@ class XenAPIVMTestCase(test.TestCase): glance_stubs.FakeGlance.IMAGE_RAMDISK, instance_id=fake_instance_id) # TODO(salvatore-orlando): a complete test here would require - # A check for making sure the bridge for the VM's VIF is + # a check for making sure the bridge for the VM's VIF is # consistent with bridge specified in nova db self.network = network_bk diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index 6d17a642e..d084c725f 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -78,7 +78,10 @@ def reset(): for c in _CLASSES: _db_content[c] = {} create_host('fake') - create_vm('fake', 'Running', is_a_template=False, is_control_domain=True) + create_vm('fake', + 'Running', + is_a_template=False, + is_control_domain=True) def reset_table(table): @@ -88,26 +91,23 @@ def reset_table(table): def create_host(name_label): - return _create_object('host', { - 'name_label': name_label, - }) + return _create_object('host', + {'name_label': name_label}) def create_network(name_label, bridge): - return _create_object('network', { - 'name_label': name_label, - 'bridge': bridge, - }) + return _create_object('network', + {'name_label': name_label, + 'bridge': bridge}) def create_vm(name_label, status, is_a_template=False, is_control_domain=False): - return _create_object('VM', { - 'name_label': name_label, - 'power-state': status, - 'is_a_template': is_a_template, - 'is_control_domain': is_control_domain, - }) + return _create_object('VM', + {'name_label': name_label, + 'power-state': status, + 'is_a_template': is_a_template, + 'is_control_domain': is_control_domain}) def destroy_vm(vm_ref): @@ -129,27 +129,24 @@ def destroy_vdi(vdi_ref): def create_vdi(name_label, read_only, sr_ref, sharable): - return _create_object('VDI', { - 'name_label': name_label, - 'read_only': read_only, - 'SR': sr_ref, - 'type': '', - 'name_description': '', - 'sharable': sharable, - 'other_config': {}, - 'location': '', - 'xenstore_data': '', - 'sm_config': {}, - 'VBDs': {}, - }) + return _create_object('VDI', + {'name_label': name_label, + 'read_only': read_only, + 'SR': sr_ref, + 'type': '', + 'name_description': '', + 'sharable': sharable, + 'other_config': {}, + 'location': '', + 'xenstore_data': '', + 'sm_config': {}, + 'VBDs': {}}) def create_vbd(vm_ref, vdi_ref): - vbd_rec = { - 'VM': vm_ref, - 'VDI': vdi_ref, - 'currently_attached': False, - } + vbd_rec = {'VM': vm_ref, + 'VDI': vdi_ref, + 'currently_attached': False} vbd_ref = _create_object('VBD', vbd_rec) after_VBD_create(vbd_ref, vbd_rec) return vbd_ref @@ -175,19 +172,17 @@ def after_VM_create(vm_ref, vm_rec): def create_pbd(config, host_ref, sr_ref, attached): - return _create_object('PBD', { - 'device-config': config, - 'host': host_ref, - 'SR': sr_ref, - 'currently-attached': attached, - }) + return _create_object('PBD', + {'device-config': config, + 'host': host_ref, + 'SR': sr_ref, + 'currently-attached': attached}) def create_task(name_label): - return _create_object('task', { - 'name_label': name_label, - 'status': 'pending', - }) + return _create_object('task', + {'name_label': name_label, + 'status': 'pending'}) def create_local_pifs(): @@ -205,34 +200,32 @@ def create_local_srs(): def _create_local_sr(host_ref): - sr_ref = _create_object('SR', { - 'name_label': 'Local storage', - 'type': 'lvm', - 'content_type': 'user', - 'shared': False, - 'physical_size': str(1 << 30), - 'physical_utilisation': str(0), - 'virtual_allocation': str(0), - 'other_config': { - 'i18n-original-value-name_label': 'Local storage', - 'i18n-key': 'local-storage', - }, - 'VDIs': [] - }) + sr_ref = \ + _create_object('SR', + {'name_label': 'Local storage', + 'type': 'lvm', + 'content_type': 'user', + 'shared': False, + 'physical_size': str(1 << 30), + 'physical_utilisation': str(0), + 'virtual_allocation': str(0), + 'other_config': {'i18n-original-value-name_label': \ + 'Local storage', + 'i18n-key': 'local-storage'}, + 'VDIs': []}) pbd_ref = create_pbd('', host_ref, sr_ref, True) _db_content['SR'][sr_ref]['PBDs'] = [pbd_ref] return sr_ref def _create_local_pif(host_ref): - pif_ref = _create_object('PIF', { - 'name-label': 'Fake PIF', - 'MAC': '00:11:22:33:44:55', - 'physical': True, - 'VLAN': -1, - 'device': 'fake0', - 'host_uuid': host_ref, - }) + pif_ref = _create_object('PIF', + {'name-label': 'Fake PIF', + 'MAC': '00:11:22:33:44:55', + 'physical': True, + 'VLAN': -1, + 'device': 'fake0', + 'host_uuid': host_ref}) def _create_object(table, obj): @@ -260,19 +253,17 @@ def _create_sr(table, obj): def _create_vlan(pif_ref, vlan_num, network_ref): pif_rec = get_record('PIF', pif_ref) - vlan_pif_ref = _create_object('PIF', { - 'name-label': 'Fake VLAN PIF', - 'MAC': '00:11:22:33:44:55', - 'physical': True, - 'VLAN': vlan_num, - 'device': pif_rec['device'], - 'host_uuid': pif_rec['host_uuid'], - }) - return _create_object('VLAN', { - 'tagged-pif': pif_ref, - 'untagged-pif': vlan_pif_ref, - 'tag': vlan_num, - }) + vlan_pif_ref = _create_object('PIF', + {'name-label': 'Fake VLAN PIF', + 'MAC': '00:11:22:33:44:55', + 'physical': True, + 'VLAN': vlan_num, + 'device': pif_rec['device'], + 'host_uuid': pif_rec['host_uuid']}) + return _create_object('VLAN', + {'tagged-pif': pif_ref, + 'untagged-pif': vlan_pif_ref, + 'tag': vlan_num}) def get_all(table): @@ -334,7 +325,7 @@ class SessionBase(object): rec['device'] = '' def PIF_get_all_records_where(self, _1, _2): - # TODO (salvatore-orlando):filter table on _2 + # TODO (salvatore-orlando): filter table on _2 return _db_content['PIF'] def VM_get_xenstore_data(self, _1, vm_ref): @@ -347,7 +338,7 @@ class SessionBase(object): db_ref['xenstore_data'][key] = None def network_get_all_records_where(self, _1, _2): - # TODO (salvatore-orlando):filter table on _2 + # TODO (salvatore-orlando): filter table on _2 return _db_content['network'] def VM_add_to_xenstore_data(self, _1, vm_ref, key, value): @@ -385,10 +376,9 @@ class SessionBase(object): def _login(self, method, params): self._session = str(uuid.uuid4()) - _db_content['session'][self._session] = { - 'uuid': str(uuid.uuid4()), - 'this_host': _db_content['host'].keys()[0], - } + _db_content['session'][self._session] = \ + {'uuid': str(uuid.uuid4()), + 'this_host': _db_content['host'].keys()[0]} def _logout(self): s = self._session diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 9b18ac732..08046318e 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -338,6 +338,7 @@ class VMOps(object): :param instance: the instance that owns the VHD in question. :param dest: the destination host machine. :param disk_type: values are 'primary' or 'cow'. + """ vm_ref = VMHelper.lookup(self._session, instance.name) @@ -711,7 +712,6 @@ class VMOps(object): - unplug the instance VM's disk from the rescue VM. - teardown the rescue VM. - release the bootlock to allow the instance VM to start. - """ rescue_vm_ref = VMHelper.lookup(self._session, "%s-rescue" % instance.name) -- cgit From 184fa8239d54d20ff294cdb019d07989ed3d6c4d Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Mon, 28 Mar 2011 12:08:43 +0100 Subject: addressed termies review (third round) --- nova/tests/db/fakes.py | 63 ++++++++++++++++++++++------------------------- nova/tests/test_xenapi.py | 26 +++++++++---------- nova/virt/xenapi/fake.py | 2 +- nova/virt/xenapi/vmops.py | 3 ++- 4 files changed, 46 insertions(+), 48 deletions(-) (limited to 'nova') diff --git a/nova/tests/db/fakes.py b/nova/tests/db/fakes.py index f7610aa56..7ddfe377a 100644 --- a/nova/tests/db/fakes.py +++ b/nova/tests/db/fakes.py @@ -56,39 +56,36 @@ def stub_out_db_instance_api(stubs, injected=True): flavorid=5, rxtx_cap=5)} - flat_network_fields = { - 'id': 'fake_flat', - 'bridge': 'xenbr0', - 'label': 'fake_flat_network', - 'netmask': '255.255.255.0', - 'cidr_v6': 'fe80::a00:0/120', - 'netmask_v6': '120', - 'gateway': '10.0.0.1', - 'gateway_v6': 'fe80::a00:1', - 'broadcast': '10.0.0.255', - 'dns': '10.0.0.2', - 'ra_server': None, - 'injected': injected} - - vlan_network_fields = { - 'id': 'fake_vlan', - 'bridge': 'br111', - 'label': 'fake_vlan_network', - 'netmask': '255.255.255.0', - 'cidr_v6': 'fe80::a00:0/120', - 'netmask_v6': '120', - 'gateway': '10.0.0.1', - 'gateway_v6': 'fe80::a00:1', - 'broadcast': '10.0.0.255', - 'dns': '10.0.0.2', - 'ra_server': None, - 'vlan': 111, - 'injected': False} - - fixed_ip_fields = { - 'address': '10.0.0.3', - 'address_v6': 'fe80::a00:3', - 'network_id': 'fake_flat'} + flat_network_fields = {'id': 'fake_flat', + 'bridge': 'xenbr0', + 'label': 'fake_flat_network', + 'netmask': '255.255.255.0', + 'cidr_v6': 'fe80::a00:0/120', + 'netmask_v6': '120', + 'gateway': '10.0.0.1', + 'gateway_v6': 'fe80::a00:1', + 'broadcast': '10.0.0.255', + 'dns': '10.0.0.2', + 'ra_server': None, + 'injected': injected} + + vlan_network_fields = {'id': 'fake_vlan', + 'bridge': 'br111', + 'label': 'fake_vlan_network', + 'netmask': '255.255.255.0', + 'cidr_v6': 'fe80::a00:0/120', + 'netmask_v6': '120', + 'gateway': '10.0.0.1', + 'gateway_v6': 'fe80::a00:1', + 'broadcast': '10.0.0.255', + 'dns': '10.0.0.2', + 'ra_server': None, + 'vlan': 111, + 'injected': False} + + fixed_ip_fields = {'address': '10.0.0.3', + 'address_v6': 'fe80::a00:3', + 'network_id': 'fake_flat'} class FakeModel(object): """Stubs out for model.""" diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 9fdd1feeb..bc1469223 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -287,19 +287,19 @@ class XenAPIVMTestCase(test.TestCase): key = 'vm-data/networking/aabbccddeeff' xenstore_value = xenstore_data[key] tcpip_data = ast.literal_eval(xenstore_value) - self.assertEquals(tcpip_data, { - 'label': 'fake_flat_network', - 'broadcast': '10.0.0.255', - 'ips': [{'ip': '10.0.0.3', - 'netmask':'255.255.255.0', - 'enabled':'1'}], - 'ip6s': [{'ip': 'fe80::a8bb:ccff:fedd:eeff', - 'netmask': '120', - 'enabled': '1', - 'gateway': 'fe80::a00:1'}], - 'mac': 'aa:bb:cc:dd:ee:ff', - 'dns': ['10.0.0.2'], - 'gateway': '10.0.0.1'}) + self.assertEquals(tcpip_data, + {'label': 'fake_flat_network', + 'broadcast': '10.0.0.255', + 'ips': [{'ip': '10.0.0.3', + 'netmask':'255.255.255.0', + 'enabled':'1'}], + 'ip6s': [{'ip': 'fe80::a8bb:ccff:fedd:eeff', + 'netmask': '120', + 'enabled': '1', + 'gateway': 'fe80::a00:1'}], + 'mac': 'aa:bb:cc:dd:ee:ff', + 'dns': ['10.0.0.2'], + 'gateway': '10.0.0.1'}) def check_vm_params_for_windows(self): self.assertEquals(self.vm['platform']['nx'], 'true') diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index d084c725f..d79312a60 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -60,7 +60,7 @@ from nova import exception from nova import log as logging -_CLASSES = ['host', 'network', 'session', 'SR', 'VBD',\ +_CLASSES = ['host', 'network', 'session', 'SR', 'VBD', \ 'PBD', 'VDI', 'VIF', 'PIF', 'VM', 'VLAN', 'task'] _db_content = {} diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 08046318e..147419f90 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -668,7 +668,7 @@ class VMOps(object): self._wait_with_callback(instance.id, task, callback) def suspend(self, instance, callback): - """Suspend the specified instance""" + """Suspend the specified instance.""" vm_ref = self._get_vm_opaque_ref(instance) task = self._session.call_xenapi('Async.VM.suspend', vm_ref) self._wait_with_callback(instance.id, task, callback) @@ -686,6 +686,7 @@ class VMOps(object): - shutdown the instance VM. - set 'bootlock' to prevent the instance from starting in rescue. - spawn a rescue VM (the vm name-label will be instance-N-rescue). + """ rescue_vm_ref = VMHelper.lookup(self._session, "%s-rescue" % instance.name) -- cgit From d25968ab494f65ed90981e440169e31a7488befe Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Mon, 28 Mar 2011 15:21:53 +0200 Subject: Add friendlier message if an extension fails to include a correctly named class or factory. --- nova/api/openstack/extensions.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 439612faa..4a7236863 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -321,7 +321,14 @@ class ExtensionManager(object): mod = imp.load_source(mod_name, ext_path) ext_name = mod_name[0].upper() + mod_name[1:] try: - new_ext = getattr(mod, ext_name)() + new_ext_class = getattr(mod, ext_name, None) + if not new_ext_class: + LOG.warning(_('Did not find expected name ' + '"%(ext_name)" in %(file)s'), + { 'ext_name': ext_name, + 'file': ext_path }) + continue + new_ext = new_ext_class() self._check_extension(new_ext) self.extensions[new_ext.get_alias()] = new_ext except AttributeError as ex: -- cgit From 9786a19ec0bc5176cc01b56d473a977b85800977 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Mon, 28 Mar 2011 15:34:20 +0200 Subject: Spell "warn" correctly. --- nova/api/openstack/extensions.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 4a7236863..259d24a7d 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -323,10 +323,10 @@ class ExtensionManager(object): try: new_ext_class = getattr(mod, ext_name, None) if not new_ext_class: - LOG.warning(_('Did not find expected name ' - '"%(ext_name)" in %(file)s'), - { 'ext_name': ext_name, - 'file': ext_path }) + LOG.warn(_('Did not find expected name ' + '"%(ext_name)" in %(file)s'), + { 'ext_name': ext_name, + 'file': ext_path }) continue new_ext = new_ext_class() self._check_extension(new_ext) -- cgit From 8922630e70e97b52e363a861c76fe4a01b8418ff Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Mon, 28 Mar 2011 09:37:05 -0400 Subject: Fix typo in libvirt xml template --- nova/virt/libvirt.xml.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template index 26f528cb1..a62c3b6a8 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -3,7 +3,7 @@ ${memory_kb} #if $type == 'lxc' - #set $disk prefix = '' + #set $disk_prefix = '' #set $disk_bus = '' exe /sbin/init -- cgit From 4aad5721bff628ef8b34e0c536e0e2415f2b63f4 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Mon, 28 Mar 2011 11:16:59 -0400 Subject: Removed 'is not None' to do more general truth-checking. Added rather verbose testing. --- nova/image/glance.py | 2 +- nova/tests/api/openstack/test_images.py | 69 +++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/image/glance.py b/nova/image/glance.py index be9805b69..f0f1ecf57 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -220,7 +220,7 @@ def _convert_timestamps_to_datetimes(image_meta): Returns image with known timestamp fields converted to datetime objects """ for attr in ['created_at', 'updated_at', 'deleted_at']: - if image_meta.get(attr) is not None: + if image_meta.get(attr): image_meta[attr] = _parse_glance_iso8601_timestamp( image_meta[attr]) return image_meta diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 1cdccadd6..88dd8f506 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -197,6 +197,73 @@ class GlanceImageServiceTest(test.TestCase, image_metas = self.service.detail(self.context) self.assertDictMatch(image_metas[0], expected) + def test_image_valid_date_format(self): + """ + Ensure 'created_at', 'updated_at', and 'deleted_at' can be valid + ISO strings. + """ + fixture = { + 'name': 'test image', + 'is_public': False, + 'properties': { + 'instance_id': '42', + 'user_id': '1', + }, + } + + valid_dates = ['2010-10-11T10:30:22', '2012-12-21T00:00:00'] + + for date in valid_dates: + fixture["created_at"] = date + fixture["updated_at"] = date + fixture["deleted_at"] = date + image_id = self.service.create(self.context, fixture)['id'] + self.assertDictMatch(self.sent_to_glance['metadata'], fixture) + + def test_image_invalid_date_format(self): + """ + Ensure `created_at`, `modified_at` can't be invalid dates. + """ + fixture = { + 'name': 'test image', + 'is_public': False, + 'properties': { + 'instance_id': '42', + 'user_id': '1', + }, + } + + invalid_dates = ['Not a date.', 4] + + for date in invalid_dates: + fixture["created_at"] = date + fixture["updated_at"] = date + fixture["deleted_at"] = date + self.assertRaises((TypeError, ValueError), self.service.create, + self.context, fixture) + + def test_image_blank_date_format(self): + """ + Ensure `created_at`, `modified_at` can be blank dates. + """ + fixture = { + 'name': 'test image', + 'is_public': False, + 'properties': { + 'instance_id': '42', + 'user_id': '1', + }, + } + + blank_dates = [None, ''] + + for date in blank_dates: + fixture["created_at"] = date + fixture["updated_at"] = date + fixture["deleted_at"] = date + image_id = self.service.create(self.context, fixture)['id'] + self.assertDictMatch(self.sent_to_glance['metadata'], fixture) + def test_create_without_instance_id(self): """ Ensure we can create an image without having to specify an @@ -235,6 +302,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): FLAGS.image_service = self.orig_image_service super(ImageControllerWithGlanceServiceTest, self).tearDown() + + def test_get_image_index(self): req = webob.Request.blank('/v1.0/images') res = req.get_response(fakes.wsgi_app()) -- cgit From 23bed216dbbd512e733ecf6065105b2d20703531 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Mon, 28 Mar 2011 13:04:02 -0400 Subject: Added MUCH more flexiable iso8601 parser dep for added stability. --- nova/image/glance.py | 8 ++-- nova/tests/api/openstack/test_images.py | 69 --------------------------------- nova/tests/image/test_glance.py | 50 ++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 74 deletions(-) (limited to 'nova') diff --git a/nova/image/glance.py b/nova/image/glance.py index f0f1ecf57..c08e2e0e8 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -20,6 +20,8 @@ from __future__ import absolute_import import datetime +import iso8601 + from glance.common import exception as glance_exception from nova import exception @@ -230,8 +232,4 @@ def _parse_glance_iso8601_timestamp(timestamp): """ Parse a subset of iso8601 timestamps into datetime objects """ - GLANCE_FMT = "%Y-%m-%dT%H:%M:%S" - ISO_FMT = "%Y-%m-%dT%H:%M:%S.%f" - # FIXME(sirp): Glance is not returning in ISO format, we should fix Glance - # to do so, and then switch to parsing it here - return datetime.datetime.strptime(timestamp, GLANCE_FMT) + return iso8601.parse_date(timestamp).replace(tzinfo=None) diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 88dd8f506..1cdccadd6 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -197,73 +197,6 @@ class GlanceImageServiceTest(test.TestCase, image_metas = self.service.detail(self.context) self.assertDictMatch(image_metas[0], expected) - def test_image_valid_date_format(self): - """ - Ensure 'created_at', 'updated_at', and 'deleted_at' can be valid - ISO strings. - """ - fixture = { - 'name': 'test image', - 'is_public': False, - 'properties': { - 'instance_id': '42', - 'user_id': '1', - }, - } - - valid_dates = ['2010-10-11T10:30:22', '2012-12-21T00:00:00'] - - for date in valid_dates: - fixture["created_at"] = date - fixture["updated_at"] = date - fixture["deleted_at"] = date - image_id = self.service.create(self.context, fixture)['id'] - self.assertDictMatch(self.sent_to_glance['metadata'], fixture) - - def test_image_invalid_date_format(self): - """ - Ensure `created_at`, `modified_at` can't be invalid dates. - """ - fixture = { - 'name': 'test image', - 'is_public': False, - 'properties': { - 'instance_id': '42', - 'user_id': '1', - }, - } - - invalid_dates = ['Not a date.', 4] - - for date in invalid_dates: - fixture["created_at"] = date - fixture["updated_at"] = date - fixture["deleted_at"] = date - self.assertRaises((TypeError, ValueError), self.service.create, - self.context, fixture) - - def test_image_blank_date_format(self): - """ - Ensure `created_at`, `modified_at` can be blank dates. - """ - fixture = { - 'name': 'test image', - 'is_public': False, - 'properties': { - 'instance_id': '42', - 'user_id': '1', - }, - } - - blank_dates = [None, ''] - - for date in blank_dates: - fixture["created_at"] = date - fixture["updated_at"] = date - fixture["deleted_at"] = date - image_id = self.service.create(self.context, fixture)['id'] - self.assertDictMatch(self.sent_to_glance['metadata'], fixture) - def test_create_without_instance_id(self): """ Ensure we can create an image without having to specify an @@ -302,8 +235,6 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): FLAGS.image_service = self.orig_image_service super(ImageControllerWithGlanceServiceTest, self).tearDown() - - def test_get_image_index(self): req = webob.Request.blank('/v1.0/images') res = req.get_response(fakes.wsgi_app()) diff --git a/nova/tests/image/test_glance.py b/nova/tests/image/test_glance.py index d03aa9cc8..dfdce7584 100644 --- a/nova/tests/image/test_glance.py +++ b/nova/tests/image/test_glance.py @@ -57,6 +57,7 @@ class NullWriter(object): class BaseGlanceTest(unittest.TestCase): NOW_GLANCE_FORMAT = "2010-10-11T10:30:22" NOW_DATETIME = datetime.datetime(2010, 10, 11, 10, 30, 22) + NOW_ISO_FORMAT = "2010-10-11T10:30:22.000000" def setUp(self): # FIXME(sirp): we can probably use stubs library here rather than @@ -74,6 +75,10 @@ class BaseGlanceTest(unittest.TestCase): self.assertEqual(image_meta['updated_at'], None) self.assertEqual(image_meta['deleted_at'], None) + def assertDateTimesBlank(self, image_meta): + self.assertEqual(image_meta['updated_at'], '') + self.assertEqual(image_meta['deleted_at'], '') + class TestGlanceImageServiceProperties(BaseGlanceTest): def test_show_passes_through_to_client(self): @@ -108,33 +113,65 @@ class TestGetterDateTimeNoneTests(BaseGlanceTest): image_meta = self.service.show(self.context, 'image1') self.assertDateTimesEmpty(image_meta) + def test_show_handles_blank_datetimes(self): + self.client.images = self._make_blank_datetime_fixtures() + image_meta = self.service.show(self.context, 'image1') + self.assertDateTimesBlank(image_meta) + def test_detail_handles_none_datetimes(self): self.client.images = self._make_none_datetime_fixtures() image_meta = self.service.detail(self.context)[0] self.assertDateTimesEmpty(image_meta) + def test_detail_handles_blank_datetimes(self): + self.client.images = self._make_blank_datetime_fixtures() + image_meta = self.service.detail(self.context)[0] + self.assertDateTimesBlank(image_meta) + def test_get_handles_none_datetimes(self): self.client.images = self._make_none_datetime_fixtures() writer = NullWriter() image_meta = self.service.get(self.context, 'image1', writer) self.assertDateTimesEmpty(image_meta) + def test_get_handles_blank_datetimes(self): + self.client.images = self._make_blank_datetime_fixtures() + writer = NullWriter() + image_meta = self.service.get(self.context, 'image1', writer) + self.assertDateTimesBlank(image_meta) + def test_show_makes_datetimes(self): self.client.images = self._make_datetime_fixtures() image_meta = self.service.show(self.context, 'image1') self.assertDateTimesFilled(image_meta) + def test_show_makes_datetimes_iso(self): + self.client.images = self._make_iso_fixtures() + image_meta = self.service.show(self.context, 'image1') + self.assertDateTimesFilled(image_meta) + def test_detail_makes_datetimes(self): self.client.images = self._make_datetime_fixtures() image_meta = self.service.detail(self.context)[0] self.assertDateTimesFilled(image_meta) + def test_detail_makes_datetimes_iso(self): + self.client.images = self._make_iso_fixtures() + image_meta = self.service.detail(self.context)[0] + self.assertDateTimesFilled(image_meta) + def test_get_makes_datetimes(self): self.client.images = self._make_datetime_fixtures() writer = NullWriter() image_meta = self.service.get(self.context, 'image1', writer) self.assertDateTimesFilled(image_meta) + def test_get_makes_datetimes_iso(self): + self.client.images = self._make_iso_fixtures() + writer = NullWriter() + image_meta = self.service.get(self.context, 'image1', writer) + self.assertDateTimesFilled(image_meta) + def _make_datetime_fixtures(self): fixtures = {'image1': {'name': 'image1', 'is_public': True, 'created_at': self.NOW_GLANCE_FORMAT, @@ -142,12 +179,25 @@ class TestGetterDateTimeNoneTests(BaseGlanceTest): 'deleted_at': self.NOW_GLANCE_FORMAT}} return fixtures + def _make_iso_fixtures(self): + fixtures = {'image1': {'name': 'image1', 'is_public': True, + 'created_at': self.NOW_ISO_FORMAT, + 'updated_at': self.NOW_ISO_FORMAT, + 'deleted_at': self.NOW_ISO_FORMAT}} + return fixtures + def _make_none_datetime_fixtures(self): fixtures = {'image1': {'name': 'image1', 'is_public': True, 'updated_at': None, 'deleted_at': None}} return fixtures + def _make_blank_datetime_fixtures(self): + fixtures = {'image1': {'name': 'image1', 'is_public': True, + 'updated_at': '', + 'deleted_at': ''}} + return fixtures + class TestMutatorDateTimeTests(BaseGlanceTest): """Tests create(), update()""" -- cgit From 56b4dd3929448585c15c8d11c5fe1569ce21ea7d Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Mon, 28 Mar 2011 13:18:47 -0400 Subject: Removed extra dependency as per suggestion, although it fixes the issue much better IMO, we should be safe sticking with using the format from python's isoformat() --- nova/image/glance.py | 5 ++--- nova/tests/image/test_glance.py | 26 +------------------------- 2 files changed, 3 insertions(+), 28 deletions(-) (limited to 'nova') diff --git a/nova/image/glance.py b/nova/image/glance.py index c08e2e0e8..32c9fa6be 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -20,8 +20,6 @@ from __future__ import absolute_import import datetime -import iso8601 - from glance.common import exception as glance_exception from nova import exception @@ -232,4 +230,5 @@ def _parse_glance_iso8601_timestamp(timestamp): """ Parse a subset of iso8601 timestamps into datetime objects """ - return iso8601.parse_date(timestamp).replace(tzinfo=None) + ISO_FMT = "%Y-%m-%dT%H:%M:%S.%f" + return datetime.datetime.strptime(timestamp, ISO_FMT) diff --git a/nova/tests/image/test_glance.py b/nova/tests/image/test_glance.py index dfdce7584..dfa754b89 100644 --- a/nova/tests/image/test_glance.py +++ b/nova/tests/image/test_glance.py @@ -55,9 +55,8 @@ class NullWriter(object): class BaseGlanceTest(unittest.TestCase): - NOW_GLANCE_FORMAT = "2010-10-11T10:30:22" + NOW_GLANCE_FORMAT = "2010-10-11T10:30:22.000000" NOW_DATETIME = datetime.datetime(2010, 10, 11, 10, 30, 22) - NOW_ISO_FORMAT = "2010-10-11T10:30:22.000000" def setUp(self): # FIXME(sirp): we can probably use stubs library here rather than @@ -145,33 +144,17 @@ class TestGetterDateTimeNoneTests(BaseGlanceTest): image_meta = self.service.show(self.context, 'image1') self.assertDateTimesFilled(image_meta) - def test_show_makes_datetimes_iso(self): - self.client.images = self._make_iso_fixtures() - image_meta = self.service.show(self.context, 'image1') - self.assertDateTimesFilled(image_meta) - def test_detail_makes_datetimes(self): self.client.images = self._make_datetime_fixtures() image_meta = self.service.detail(self.context)[0] self.assertDateTimesFilled(image_meta) - def test_detail_makes_datetimes_iso(self): - self.client.images = self._make_iso_fixtures() - image_meta = self.service.detail(self.context)[0] - self.assertDateTimesFilled(image_meta) - def test_get_makes_datetimes(self): self.client.images = self._make_datetime_fixtures() writer = NullWriter() image_meta = self.service.get(self.context, 'image1', writer) self.assertDateTimesFilled(image_meta) - def test_get_makes_datetimes_iso(self): - self.client.images = self._make_iso_fixtures() - writer = NullWriter() - image_meta = self.service.get(self.context, 'image1', writer) - self.assertDateTimesFilled(image_meta) - def _make_datetime_fixtures(self): fixtures = {'image1': {'name': 'image1', 'is_public': True, 'created_at': self.NOW_GLANCE_FORMAT, @@ -179,13 +162,6 @@ class TestGetterDateTimeNoneTests(BaseGlanceTest): 'deleted_at': self.NOW_GLANCE_FORMAT}} return fixtures - def _make_iso_fixtures(self): - fixtures = {'image1': {'name': 'image1', 'is_public': True, - 'created_at': self.NOW_ISO_FORMAT, - 'updated_at': self.NOW_ISO_FORMAT, - 'deleted_at': self.NOW_ISO_FORMAT}} - return fixtures - def _make_none_datetime_fixtures(self): fixtures = {'image1': {'name': 'image1', 'is_public': True, 'updated_at': None, -- cgit From 5977a511ed202fcf396e7c60d713eb5329d6883b Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Mon, 28 Mar 2011 13:30:15 -0400 Subject: style changes --- nova/api/openstack/servers.py | 12 ++++++------ nova/tests/api/openstack/test_servers.py | 5 ----- 2 files changed, 6 insertions(+), 11 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index a8e3e7900..a98f81d98 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -267,11 +267,11 @@ class Controller(wsgi.Controller): actions = { 'changePassword': self._action_change_password, - 'reboot': self._action_reboot, - 'resize': self._action_resize, + 'reboot': self._action_reboot, + 'resize': self._action_resize, 'confirmResize': self._action_confirm_resize, - 'revertResize': self._action_revert_resize, - 'rebuild': self._action_rebuild, + 'revertResize': self._action_revert_resize, + 'rebuild': self._action_rebuild, } input_dict = self._deserialize(req.body, req.get_content_type()) @@ -595,8 +595,8 @@ class ControllerV11(Controller): def _action_change_password(self, input_dict, req, id): context = req.environ['nova.context'] - if not 'changePassword' in input_dict \ - or not 'adminPass' in input_dict['changePassword']: + if (not 'changePassword' in input_dict + or not 'adminPass' in input_dict['changePassword']): return exc.HTTPBadRequest() password = input_dict['changePassword']['adminPass'] self.compute_api.set_admin_password(context, id, password) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index d2a72dd10..25d69401d 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -655,20 +655,16 @@ class ServersTest(test.TestCase): def test_server_change_password_v1_1(self): class MockSetAdminPassword(object): - def __init__(self): - self.called = False self.instance_id = None self.password = None def __call__(self, context, instance_id, password): - self.called = True self.instance_id = instance_id self.password = password 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.method = 'POST' @@ -676,7 +672,6 @@ class ServersTest(test.TestCase): req.body = json.dumps(body) res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) - self.assertTrue(mock_method.called) self.assertEqual(mock_method.instance_id, '1') self.assertEqual(mock_method.password, '1234pass') -- cgit From 71347f2e9d6195a25cabff782c7058bed006e286 Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Mon, 28 Mar 2011 13:40:16 -0400 Subject: lock down requirements for change password --- nova/api/openstack/servers.py | 2 ++ nova/tests/api/openstack/test_servers.py | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index a98f81d98..b5727a7e1 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -599,6 +599,8 @@ class ControllerV11(Controller): or not 'adminPass' in input_dict['changePassword']): return exc.HTTPBadRequest() password = input_dict['changePassword']['adminPass'] + if not isinstance(password, basestring) or password == '': + return exc.HTTPBadRequest() self.compute_api.set_admin_password(context, id, password) return exc.HTTPAccepted() diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 25d69401d..6d6be817a 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -684,6 +684,33 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) + def test_server_change_password_empty_string_v1_1(self): + body = {'changePassword': {'adminPass': ''}} + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_server_change_password_none_v1_1(self): + body = {'changePassword': {'adminPass': None}} + req = webob.Request.blank('/v1.1/servers/1/action') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + 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.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + def test_server_reboot(self): body = dict(server=dict( name='server_test', imageId=2, flavorId=2, metadata={}, -- cgit From 805cb3609379827d1643785be83f75b69b602d74 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Mon, 28 Mar 2011 13:44:33 -0400 Subject: Fix libvirt merge mistake --- nova/virt/libvirt.xml.template | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template index a62c3b6a8..894b216e9 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -83,22 +83,24 @@ #end if #end if #end if + +#for $nic in $nics - - + + - - - -#if $getVar('extra_params', False) - ${extra_params} + + + +#if $getVar('nic.extra_params', False) + ${nic.extra_params} #end if -#if $getVar('gateway_v6', False) - +#if $getVar('nic.gateway_v6', False) + #end if - +#end for -- cgit From dbf14e9b6cc337233ef95b03fd1c2fdba8ebf8a7 Mon Sep 17 00:00:00 2001 From: termie Date: Mon, 28 Mar 2011 10:47:08 -0700 Subject: add snapshot support for libvirt --- nova/virt/libvirt_conn.py | 71 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 36457ee87..4456ffc12 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -38,12 +38,15 @@ Supports KVM, QEMU, UML, and XEN. import multiprocessing import os -import shutil -import sys import random +import shutil import subprocess +import sys +import tempfile +import time import uuid from xml.dom import minidom +from xml.etree import ElementTree from eventlet import greenthread from eventlet import tpool @@ -111,6 +114,8 @@ flags.DEFINE_string('live_migration_flag', 'Define live migration behavior.') flags.DEFINE_integer('live_migration_bandwidth', 0, 'Define live migration behavior') +flags.DEFINE_string('qemu_img', 'qemu-img', + 'binary to use for qemu-img commands') def get_connection(read_only): @@ -397,10 +402,64 @@ class LibvirtConnection(driver.ComputeDriver): @exception.wrap_exception def snapshot(self, instance, image_id): - """ Create snapshot from a running VM instance """ - raise NotImplementedError( - _("Instance snapshotting is not supported for libvirt" - "at this time")) + """Create snapshot from a running VM instance. + + This command only works with qemu 0.14+, the qemu_img flag is + provided so that a locally compiled binary of qemu-img can be used + to support this command. + + """ + image_service = utils.import_object(FLAGS.image_service) + virt_dom = self._conn.lookupByName(instance['name']) + elevated = context.get_admin_context() + + base = image_service.show(elevated, instance['image_id']) + + metadata = {'type': 'machine', + 'is_public': False, + 'properties': {'architecture': base['architecture'], + 'kernel_id': instance['kernel_id'], + 'image_location': 'snapshot', + 'image_state': 'available', + 'owner_id': instance['project_id'], + 'ramdisk_id': instance['ramdisk_id'], + } + } + + # Make the snapshot + snapshot_name = uuid.uuid4().hex + snapshot_xml = """ + + %s + + """ % snapshot_name + snapshot_ptr = virt_dom.snapshotCreateXML(snapshot_xml, 0) + + # Find the disk + xml_desc = virt_dom.XMLDesc(0) + domain = ElementTree.fromstring(xml_desc) + source = domain.find('devices/disk/source') + disk_path = source.get('file') + + # Export the snapshot to a raw image + temp_dir = tempfile.mkdtemp() + out_path = os.path.join(temp_dir, snapshot_name) + qemu_img_cmd = '%s convert -f qcow2 -O raw -s %s %s %s' % ( + FLAGS.qemu_img, + snapshot_name, + disk_path, + out_path) + utils.execute(qemu_img_cmd) + + # Upload that image to the image service + with open(out_path) as image_file: + image_service.update(elevated, + image_id, + metadata, + image_file) + + # Clean up + shutil.rmtree(temp_dir) @exception.wrap_exception def reboot(self, instance): -- cgit From 1e4024b72218a07d1e535878337547cf16406dd8 Mon Sep 17 00:00:00 2001 From: termie Date: Mon, 28 Mar 2011 10:47:11 -0700 Subject: update glance params per review --- nova/virt/libvirt_conn.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 4456ffc12..80eb64f3c 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -415,9 +415,12 @@ class LibvirtConnection(driver.ComputeDriver): base = image_service.show(elevated, instance['image_id']) - metadata = {'type': 'machine', + metadata = {'disk_format': base['disk_format'], + 'container_format': base['container_format'], 'is_public': False, 'properties': {'architecture': base['architecture'], + 'type': base['type'], + 'name': '%s.%s' % (base['name'], image_id), 'kernel_id': instance['kernel_id'], 'image_location': 'snapshot', 'image_state': 'available', -- cgit From de07a6409d575af5db748bdbfa2cc57881136d66 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Mon, 28 Mar 2011 13:47:18 -0400 Subject: Decided to not break old format so this should work with the way Glance used to work and the way glace works now..The best of both worlds? --- nova/image/glance.py | 12 ++++++++++-- nova/tests/image/test_glance.py | 27 +++++++++++++++++++++++---- 2 files changed, 33 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/image/glance.py b/nova/image/glance.py index 32c9fa6be..34fc78e71 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -230,5 +230,13 @@ def _parse_glance_iso8601_timestamp(timestamp): """ Parse a subset of iso8601 timestamps into datetime objects """ - ISO_FMT = "%Y-%m-%dT%H:%M:%S.%f" - return datetime.datetime.strptime(timestamp, ISO_FMT) + ISO_FORMATS = ["%Y-%m-%dT%H:%M:%S.%f", "%Y-%m-%dT%H:%M:%S"] + + for iso_format in ISO_FORMATS: + try: + return datetime.datetime.strptime(timestamp, iso_format) + except ValueError: + pass + + raise ValueError(_("""%(timestamp)s does not follow any of the \ +signatures: %(ISO_FORMATS)s""") % (locals())) diff --git a/nova/tests/image/test_glance.py b/nova/tests/image/test_glance.py index dfa754b89..9d0b14613 100644 --- a/nova/tests/image/test_glance.py +++ b/nova/tests/image/test_glance.py @@ -55,6 +55,7 @@ class NullWriter(object): class BaseGlanceTest(unittest.TestCase): + NOW_GLANCE_OLD_FORMAT = "2010-10-11T10:30:22" NOW_GLANCE_FORMAT = "2010-10-11T10:30:22.000000" NOW_DATETIME = datetime.datetime(2010, 10, 11, 10, 30, 22) @@ -143,23 +144,41 @@ class TestGetterDateTimeNoneTests(BaseGlanceTest): self.client.images = self._make_datetime_fixtures() image_meta = self.service.show(self.context, 'image1') self.assertDateTimesFilled(image_meta) + image_meta = self.service.show(self.context, 'image2') + self.assertDateTimesFilled(image_meta) def test_detail_makes_datetimes(self): self.client.images = self._make_datetime_fixtures() image_meta = self.service.detail(self.context)[0] self.assertDateTimesFilled(image_meta) + image_meta = self.service.detail(self.context)[1] + self.assertDateTimesFilled(image_meta) def test_get_makes_datetimes(self): self.client.images = self._make_datetime_fixtures() writer = NullWriter() image_meta = self.service.get(self.context, 'image1', writer) self.assertDateTimesFilled(image_meta) + image_meta = self.service.get(self.context, 'image2', writer) + self.assertDateTimesFilled(image_meta) def _make_datetime_fixtures(self): - fixtures = {'image1': {'name': 'image1', 'is_public': True, - 'created_at': self.NOW_GLANCE_FORMAT, - 'updated_at': self.NOW_GLANCE_FORMAT, - 'deleted_at': self.NOW_GLANCE_FORMAT}} + fixtures = { + 'image1': { + 'name': 'image1', + 'is_public': True, + 'created_at': self.NOW_GLANCE_FORMAT, + 'updated_at': self.NOW_GLANCE_FORMAT, + 'deleted_at': self.NOW_GLANCE_FORMAT, + }, + 'image2': { + 'name': 'image2', + 'is_public': True, + 'created_at': self.NOW_GLANCE_OLD_FORMAT, + 'updated_at': self.NOW_GLANCE_OLD_FORMAT, + 'deleted_at': self.NOW_GLANCE_OLD_FORMAT, + }, + } return fixtures def _make_none_datetime_fixtures(self): -- cgit From 9c61085ea9612be24e9975ac3fba456874b89f08 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Mon, 28 Mar 2011 13:49:51 -0400 Subject: Add more unit tests for lxc --- nova/tests/test_virt.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index cee044279..ed7943ace 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -257,7 +257,9 @@ class LibvirtConnTestCase(test.TestCase): check = [ (lambda t: t.find('.').get('type'), 'lxc'), - (lambda t: t.find('./os/type').text, 'exe')] + (lambda t: t.find('./os/type').text, 'exe'), + (lambda t: t.find('./devices/filesystem/source').get('dir'), 'rootfs'), + (lambda t: t.find('./devices/filesystem/target').get('dir'), '/')] for i, (check, expected_result) in enumerate(check): self.assertEqual(check(tree), -- cgit From 63747d35929a1df0a29792f41657b4821c5787a3 Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Mon, 28 Mar 2011 13:50:24 -0400 Subject: pep8 whitespace --- nova/api/openstack/servers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index b5727a7e1..aaae17a39 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -595,7 +595,7 @@ class ControllerV11(Controller): def _action_change_password(self, input_dict, req, id): context = req.environ['nova.context'] - if (not 'changePassword' in input_dict + if (not 'changePassword' in input_dict or not 'adminPass' in input_dict['changePassword']): return exc.HTTPBadRequest() password = input_dict['changePassword']['adminPass'] -- cgit From 14337b0a31c8f04d8044e234eb295b41a9a9c5ce Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 28 Mar 2011 14:02:53 -0400 Subject: adding shared_ip_groups testing; replacing all shared_ip_groups contoller code with HTTPNotImplemented; moving shared_ip_groups controller to APIRouterV10 --- nova/api/openstack/__init__.py | 8 +++--- nova/api/openstack/shared_ip_groups.py | 6 ++--- nova/tests/api/openstack/test_shared_ip_groups.py | 30 ++++++++++++++++++++--- 3 files changed, 34 insertions(+), 10 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 8fabbce8e..cf53ffcd6 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -119,10 +119,6 @@ class APIRouter(wsgi.Router): mapper.resource("image", "images", controller=images.Controller(), collection={'detail': 'GET'}) - mapper.resource("shared_ip_group", "shared_ip_groups", - collection={'detail': 'GET'}, - controller=shared_ip_groups.Controller()) - _limits = limits.LimitsController() mapper.resource("limit", "limits", controller=_limits) @@ -141,6 +137,10 @@ class APIRouterV10(APIRouter): controller=flavors.ControllerV10(), collection={'detail': 'GET'}) + mapper.resource("shared_ip_group", "shared_ip_groups", + collection={'detail': 'GET'}, + controller=shared_ip_groups.Controller()) + class APIRouterV11(APIRouter): """Define routes specific to OpenStack API V1.1.""" diff --git a/nova/api/openstack/shared_ip_groups.py b/nova/api/openstack/shared_ip_groups.py index 5d78f9377..ee7991d7f 100644 --- a/nova/api/openstack/shared_ip_groups.py +++ b/nova/api/openstack/shared_ip_groups.py @@ -42,11 +42,11 @@ class Controller(wsgi.Controller): def index(self, req): """ Returns a list of Shared IP Groups for the user """ - return dict(sharedIpGroups=[]) + raise faults.Fault(exc.HTTPNotImplemented()) def show(self, req, id): """ Shows in-depth information on a specific Shared IP Group """ - return _translate_keys({}) + raise faults.Fault(exc.HTTPNotImplemented()) def update(self, req, id): """ You can't update a Shared IP Group """ @@ -58,7 +58,7 @@ class Controller(wsgi.Controller): def detail(self, req): """ Returns a complete list of Shared IP Groups """ - return _translate_detail_keys({}) + raise faults.Fault(exc.HTTPNotImplemented()) def create(self, req): """ Creates a new Shared IP group """ diff --git a/nova/tests/api/openstack/test_shared_ip_groups.py b/nova/tests/api/openstack/test_shared_ip_groups.py index b4de2ef41..c2bd7e45a 100644 --- a/nova/tests/api/openstack/test_shared_ip_groups.py +++ b/nova/tests/api/openstack/test_shared_ip_groups.py @@ -16,25 +16,49 @@ # under the License. import stubout +import webob from nova import test from nova.api.openstack import shared_ip_groups +from nova.tests.api.openstack import fakes class SharedIpGroupsTest(test.TestCase): def setUp(self): super(SharedIpGroupsTest, self).setUp() self.stubs = stubout.StubOutForTesting() + fakes.FakeAuthManager.reset_fake_data() + fakes.FakeAuthDatabase.data = {} + fakes.stub_out_auth(self.stubs) def tearDown(self): self.stubs.UnsetAll() super(SharedIpGroupsTest, self).tearDown() def test_get_shared_ip_groups(self): - pass + req = webob.Request.blank('/v1.0/shared_ip_groups') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 501) def test_create_shared_ip_group(self): - pass + req = webob.Request.blank('/v1.0/shared_ip_groups') + req.method = 'POST' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 501) + + def test_update_shared_ip_group(self): + req = webob.Request.blank('/v1.0/shared_ip_groups/12') + req.method = 'PUT' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 501) def test_delete_shared_ip_group(self): - pass + req = webob.Request.blank('/v1.0/shared_ip_groups/12') + req.method = 'DELETE' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 501) + + def test_deprecated_v11(self): + req = webob.Request.blank('/v1.1/shared_ip_groups') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 404) -- cgit From e44ce1a95baddd4f6d511d8be253167436395cb2 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Mon, 28 Mar 2011 14:07:33 -0400 Subject: More pep8 corrections --- nova/tests/test_virt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index ed7943ace..8cee55576 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -258,8 +258,8 @@ class LibvirtConnTestCase(test.TestCase): check = [ (lambda t: t.find('.').get('type'), 'lxc'), (lambda t: t.find('./os/type').text, 'exe'), - (lambda t: t.find('./devices/filesystem/source').get('dir'), 'rootfs'), - (lambda t: t.find('./devices/filesystem/target').get('dir'), '/')] + (lambda t: t.find('./devices/filesystem/source').get('dir'), 'rootfs'), + (lambda t: t.find('./devices/filesystem/target').get('dir'), '/')] for i, (check, expected_result) in enumerate(check): self.assertEqual(check(tree), -- cgit From b6df504c33cfa0fe02e31962578b77d841e1e6d8 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 28 Mar 2011 14:31:12 -0400 Subject: backup_schedule tests corrected; controller moved to APIRouterV10; making controller fully HTTPNotImplemented --- nova/api/openstack/__init__.py | 10 +++++----- nova/api/openstack/backup_schedules.py | 6 +++++- nova/tests/api/openstack/test_servers.py | 22 ++++++++++++++++------ 3 files changed, 26 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 8fabbce8e..149abfeb8 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -106,11 +106,6 @@ class APIRouter(wsgi.Router): controller=accounts.Controller(), collection={'detail': 'GET'}) - mapper.resource("backup_schedule", "backup_schedule", - controller=backup_schedules.Controller(), - parent_resource=dict(member_name='server', - collection_name='servers')) - mapper.resource("console", "consoles", controller=consoles.Controller(), parent_resource=dict(member_name='server', @@ -141,6 +136,11 @@ class APIRouterV10(APIRouter): controller=flavors.ControllerV10(), collection={'detail': 'GET'}) + mapper.resource("backup_schedule", "backup_schedule", + controller=backup_schedules.Controller(), + parent_resource=dict(member_name='server', + collection_name='servers')) + class APIRouterV11(APIRouter): """Define routes specific to OpenStack API V1.1.""" diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index 7abb5f884..f2d2d86e8 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -42,7 +42,11 @@ class Controller(wsgi.Controller): def index(self, req, server_id): """ Returns the list of backup schedules for a given instance """ - return _translate_keys({}) + return faults.Fault(exc.HTTPNotImplemented()) + + def show(self, req, server_id, id): + """ Returns a single backup schedule for a given instance """ + return faults.Fault(exc.HTTPNotImplemented()) def create(self, req, server_id): """ No actual update method required, since the existing API allows diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 737b43c7b..989385a8c 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -483,21 +483,31 @@ class ServersTest(test.TestCase): req.get_response(fakes.wsgi_app()) def test_create_backup_schedules(self): - req = webob.Request.blank('/v1.0/servers/1/backup_schedules') + req = webob.Request.blank('/v1.0/servers/1/backup_schedule') req.method = 'POST' res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status, '404 Not Found') + self.assertEqual(res.status_int, 501) def test_delete_backup_schedules(self): - req = webob.Request.blank('/v1.0/servers/1/backup_schedules') + req = webob.Request.blank('/v1.0/servers/1/backup_schedule/1') req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status, '404 Not Found') + self.assertEqual(res.status_int, 501) def test_get_server_backup_schedules(self): - req = webob.Request.blank('/v1.0/servers/1/backup_schedules') + req = webob.Request.blank('/v1.0/servers/1/backup_schedule') res = req.get_response(fakes.wsgi_app()) - self.assertEqual(res.status, '404 Not Found') + self.assertEqual(res.status_int, 501) + + def test_get_server_backup_schedule(self): + req = webob.Request.blank('/v1.0/servers/1/backup_schedule/1') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 501) + + def test_server_backup_schedule_deprecated_v11(self): + req = webob.Request.blank('/v1.1/servers/1/backup_schedule') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 404) def test_get_all_server_details_v1_0(self): req = webob.Request.blank('/v1.0/servers/detail') -- cgit From c8e708af1789fda97674fb4c3904d86de8473a7e Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Mon, 28 Mar 2011 14:32:03 -0400 Subject: Dont make the test fail --- nova/tests/test_virt.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 8cee55576..df29e69c2 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -258,7 +258,6 @@ class LibvirtConnTestCase(test.TestCase): check = [ (lambda t: t.find('.').get('type'), 'lxc'), (lambda t: t.find('./os/type').text, 'exe'), - (lambda t: t.find('./devices/filesystem/source').get('dir'), 'rootfs'), (lambda t: t.find('./devices/filesystem/target').get('dir'), '/')] for i, (check, expected_result) in enumerate(check): @@ -266,6 +265,9 @@ class LibvirtConnTestCase(test.TestCase): expected_result, '%s failed common check %d' % (xml, i)) + target = tree.find('./devices/filesystem/source').get('dir') + self.assertTrue(len(target) > 0) + def _check_xml_and_uri(self, instance, expect_ramdisk, expect_kernel, rescue=False): user_context = context.RequestContext(project=self.project, -- cgit From dea3af64186ff204de7d5ca9852af267e648823e Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Mon, 28 Mar 2011 20:36:07 +0200 Subject: Remove now useless try/except block. --- nova/api/openstack/extensions.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 259d24a7d..e2f833d57 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -320,20 +320,16 @@ class ExtensionManager(object): if file_ext.lower() == '.py' and not mod_name.startswith('_'): mod = imp.load_source(mod_name, ext_path) ext_name = mod_name[0].upper() + mod_name[1:] - try: - new_ext_class = getattr(mod, ext_name, None) - if not new_ext_class: - LOG.warn(_('Did not find expected name ' - '"%(ext_name)" in %(file)s'), - { 'ext_name': ext_name, - 'file': ext_path }) - continue - new_ext = new_ext_class() - self._check_extension(new_ext) - self.extensions[new_ext.get_alias()] = new_ext - except AttributeError as ex: - LOG.exception(_("Exception loading extension: %s"), - unicode(ex)) + new_ext_class = getattr(mod, ext_name, None) + if not new_ext_class: + LOG.warn(_('Did not find expected name ' + '"%(ext_name)" in %(file)s'), + { 'ext_name': ext_name, + 'file': ext_path }) + continue + new_ext = new_ext_class() + self._check_extension(new_ext) + self.extensions[new_ext.get_alias()] = new_ext class ResponseExtension(object): -- cgit From bb7ed6cb9cf625b675a666866a7f9fb762ca6bd2 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Mon, 28 Mar 2011 14:47:25 -0400 Subject: use self.flags in virt test --- nova/tests/test_virt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index df29e69c2..e6beb8e2e 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -246,7 +246,7 @@ class LibvirtConnTestCase(test.TestCase): {'allocated': True, 'instance_id': instance_ref['id']}) - FLAGS.libvirt_type = 'lxc' + self.flags(libvirt_type='lxc') conn = libvirt_conn.LibvirtConnection(True) uri = conn.get_uri() -- cgit From 9fdf9967234d8553c3548ad03fc3b2691285fa7d Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Mon, 28 Mar 2011 11:56:19 -0700 Subject: Added image name and description mapping to ec2 api --- nova/api/ec2/cloud.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 0da642318..9e34d3317 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -890,6 +890,8 @@ class CloudController(object): i['imageOwnerId'] = image['properties'].get('owner_id') i['imageLocation'] = image['properties'].get('image_location') i['imageState'] = image['properties'].get('image_state') + i['displayName'] = image.get('name') + i['description'] = image.get('description') i['type'] = image_type i['isPublic'] = str(image['properties'].get('is_public', '')) == 'True' i['architecture'] = image['properties'].get('architecture') -- cgit From 78a9ec232cde1172fa4c639ebdcbf88967bf8e9c Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Mon, 28 Mar 2011 15:10:34 -0400 Subject: Fixed superfluous parentheses around locals(). --- nova/image/glance.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/image/glance.py b/nova/image/glance.py index 34fc78e71..d8f51429e 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -230,13 +230,13 @@ def _parse_glance_iso8601_timestamp(timestamp): """ Parse a subset of iso8601 timestamps into datetime objects """ - ISO_FORMATS = ["%Y-%m-%dT%H:%M:%S.%f", "%Y-%m-%dT%H:%M:%S"] + iso_formats = ["%Y-%m-%dT%H:%M:%S.%f", "%Y-%m-%dT%H:%M:%S"] - for iso_format in ISO_FORMATS: + for iso_format in iso_formats: try: return datetime.datetime.strptime(timestamp, iso_format) except ValueError: pass raise ValueError(_("""%(timestamp)s does not follow any of the \ -signatures: %(ISO_FORMATS)s""") % (locals())) +signatures: %(ISO_FORMATS)s""") % locals()) -- cgit From dbbceaebec3ca2a729582f9851f718b2b7c3f3b9 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Mon, 28 Mar 2011 15:57:18 -0400 Subject: Fix up libvirt.xml.template --- nova/virt/libvirt.xml.template | 138 ++++++++++++++++++++--------------------- 1 file changed, 68 insertions(+), 70 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template index 894b216e9..36d18ed95 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -3,47 +3,45 @@ ${memory_kb} #if $type == 'lxc' - #set $disk_prefix = '' - #set $disk_bus = '' - exe - /sbin/init + #set $disk_prefix = '' + #set $disk_bus = '' + exe + /sbin/init +#else if $type == 'uml' + #set $disk_prefix = 'ubd' + #set $disk_bus = 'uml' + uml + /usr/bin/linux + /dev/ubda #else - #if $type == 'uml' - #set $disk_prefix = 'ubd' - #set $disk_bus = 'uml' - uml - /usr/bin/linux - /dev/ubda - #else - #if $type == 'xen' - #set $disk_prefix = 'sd' - #set $disk_bus = 'scsi' - linux - /dev/xvda - #else - #set $disk_prefix = 'vd' - #set $disk_bus = 'virtio' - hvm - #end if - #if $getVar('rescue', False) - ${basepath}/kernel.rescue - ${basepath}/ramdisk.rescue - #else - #if $getVar('kernel', None) - ${kernel} - #if $type == 'xen' - ro - #else - root=/dev/vda console=ttyS0 - #end if - #if $getVar('ramdisk', None) - ${ramdisk} - #end if - #else - - #end if - #end if - #end if + #if $type == 'xen' + #set $disk_prefix = 'sd' + #set $disk_bus = 'scsi' + linux + /dev/xvda + #else + #set $disk_prefix = 'vd' + #set $disk_bus = 'virtio' + hvm + #end if + #if $getVar('rescue', False) + ${basepath}/kernel.rescue + ${basepath}/ramdisk.rescue + #else + #if $getVar('kernel', None) + ${kernel} + #if $type == 'xen' + ro + #else + root=/dev/vda console=ttyS0 + #end if + #if $getVar('ramdisk', None) + ${ramdisk} + #end if + #else + + #end if + #end if #end if @@ -52,36 +50,36 @@ ${vcpus} #if $type == 'lxc' - - - - + + + + #else - #if $getVar('rescue', False) - - - - - - - - - - - #else - - - - - - #if $getVar('local', False) - - - - - - #end if -#end if + #if $getVar('rescue', False) + + + + + + + + + + + #else + + + + + + #if $getVar('local', False) + + + + + + #end if + #end if #end if #for $nic in $nics @@ -91,7 +89,7 @@ - + #if $getVar('nic.extra_params', False) ${nic.extra_params} #end if -- cgit From 7040eadcc7e86d063c5c69391dedafa181711913 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Mon, 28 Mar 2011 22:21:18 +0200 Subject: pep8 --- nova/api/openstack/extensions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index e2f833d57..b9b7f998d 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -324,8 +324,8 @@ class ExtensionManager(object): if not new_ext_class: LOG.warn(_('Did not find expected name ' '"%(ext_name)" in %(file)s'), - { 'ext_name': ext_name, - 'file': ext_path }) + {'ext_name': ext_name, + 'file': ext_path}) continue new_ext = new_ext_class() self._check_extension(new_ext) -- cgit From 633917f56200cc11ef26717b8305ef0ccbe76569 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Mon, 28 Mar 2011 16:42:09 -0400 Subject: Made param descriptions sphinx compatible. --- nova/api/openstack/images.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 54e08438a..5fd1f0163 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -51,8 +51,8 @@ class Controller(wsgi.Controller): """ Initialize new `ImageController`. - @param compute_service: `nova.compute.api:API` - @param image_service: `nova.image.service:BaseImageService` + :param compute_service: `nova.compute.api:API` + :param image_service: `nova.image.service:BaseImageService` """ _default_service = utils.import_object(flags.FLAGS.image_service) @@ -63,7 +63,7 @@ class Controller(wsgi.Controller): """ Return an index listing of images available to the request. - @param req: `wsgi.Request` object + :param req: `wsgi.Request` object """ context = req.environ['nova.context'] images = self._image_service.index(context) @@ -75,7 +75,7 @@ class Controller(wsgi.Controller): """ Return a detailed index listing of images available to the request. - @param req: `wsgi.Request` object. + :param req: `wsgi.Request` object. """ context = req.environ['nova.context'] images = self._image_service.detail(context) @@ -87,8 +87,8 @@ class Controller(wsgi.Controller): """ Return detailed information about a specific image. - @param req: `wsgi.Request` object - @param id: Image identifier (integer) + :param req: `wsgi.Request` object + :param id: Image identifier (integer) """ context = req.environ['nova.context'] @@ -110,8 +110,8 @@ class Controller(wsgi.Controller): """ Delete an image, if allowed. - @param req: `wsgi.Request` object - @param id: Image identifier (integer) + :param req: `wsgi.Request` object + :param id: Image identifier (integer) """ image_id = id context = req.environ['nova.context'] @@ -122,7 +122,7 @@ class Controller(wsgi.Controller): """ Snapshot a server instance and save the image. - @param req: `wsgi.Request` object + :param req: `wsgi.Request` object """ context = req.environ['nova.context'] content_type = req.get_content_type() -- cgit From c1ed5fc3dfeecef281df45cd2e6779fa21c63bf5 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Mon, 28 Mar 2011 22:00:17 +0100 Subject: style fixes --- nova/network/xenapi_net.py | 6 ++++-- nova/tests/fake_utils.py | 14 ++++++++++---- nova/tests/test_xenapi.py | 28 +++++++--------------------- nova/virt/xenapi/fake.py | 28 ++++++++++++++-------------- 4 files changed, 35 insertions(+), 41 deletions(-) (limited to 'nova') diff --git a/nova/network/xenapi_net.py b/nova/network/xenapi_net.py index 8603fd842..9a99602d9 100644 --- a/nova/network/xenapi_net.py +++ b/nova/network/xenapi_net.py @@ -27,7 +27,7 @@ from nova import flags from nova import log as logging from nova import utils from nova.virt.xenapi_conn import XenAPISession -from nova.virt.xenapi.network_utils import NetworkHelper +from nova.virt.xenapi import network_utils LOG = logging.getLogger("nova.xenapi_net") @@ -44,7 +44,9 @@ def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None): session = XenAPISession(url, username, password) # Check whether bridge already exists # Retrieve network whose name_label is "bridge" - network_ref = NetworkHelper.find_network_with_name_label(session, bridge) + network_ref = network_utils.NetworkHelper.find_network_with_name_label( + session, + bridge) if network_ref == None: # If bridge does not exists # 1 - create network diff --git a/nova/tests/fake_utils.py b/nova/tests/fake_utils.py index 23996ba95..be59970c9 100644 --- a/nova/tests/fake_utils.py +++ b/nova/tests/fake_utils.py @@ -47,14 +47,20 @@ def fake_execute_set_repliers(repliers): def fake_execute_default_reply_handler(*ignore_args, **ignore_kwargs): - """A reply handler for commands that haven't been added to the reply - list. Returns empty strings for stdout and stderr.""" + """A reply handler for commands that haven't been added to the reply list. + + Returns empty strings for stdout and stderr. + + """ return '', '' def fake_execute(*cmd_parts, **kwargs): - """This function stubs out execute, optionally executing - a preconfigued function to return expected data.""" + """This function stubs out execute. + + It optionally executes a preconfigued function to return expected data. + + """ global _fake_execute_repliers process_input = kwargs.get('process_input', None) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index bc1469223..17e3f55e9 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -14,9 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Test suite for XenAPI. -""" +"""Test suite for XenAPI.""" import functools import os @@ -65,9 +63,7 @@ def stub_vm_utils_with_vdi_attached_here(function, should_return=True): class XenAPIVolumeTestCase(test.TestCase): - """ - Unit tests for Volume operations. - """ + """Unit tests for Volume operations.""" def setUp(self): super(XenAPIVolumeTestCase, self).setUp() self.stubs = stubout.StubOutForTesting() @@ -172,9 +168,7 @@ def reset_network(*args): class XenAPIVMTestCase(test.TestCase): - """ - Unit tests for VM operations. - """ + """Unit tests for VM operations.""" def setUp(self): super(XenAPIVMTestCase, self).setUp() self.manager = manager.AuthManager() @@ -538,9 +532,7 @@ class XenAPIVMTestCase(test.TestCase): class XenAPIDiffieHellmanTestCase(test.TestCase): - """ - Unit tests for Diffie-Hellman code. - """ + """Unit tests for Diffie-Hellman code.""" def setUp(self): super(XenAPIDiffieHellmanTestCase, self).setUp() self.alice = SimpleDH() @@ -564,9 +556,7 @@ class XenAPIDiffieHellmanTestCase(test.TestCase): class XenAPIMigrateInstance(test.TestCase): - """ - Unit test for verifying migration-related actions. - """ + """Unit test for verifying migration-related actions.""" def setUp(self): super(XenAPIMigrateInstance, self).setUp() @@ -621,9 +611,7 @@ class XenAPIMigrateInstance(test.TestCase): class XenAPIDetermineDiskImageTestCase(test.TestCase): - """ - Unit tests for code that detects the ImageType. - """ + """Unit tests for code that detects the ImageType.""" def setUp(self): super(XenAPIDetermineDiskImageTestCase, self).setUp() glance_stubs.stubout_glance_client(self.stubs, @@ -642,9 +630,7 @@ class XenAPIDetermineDiskImageTestCase(test.TestCase): self.assertEqual(disk_type, dt) def test_instance_disk(self): - """ - If a kernel is specified then the image type is DISK (aka machine). - """ + """If a kernel is specified, the image type is DISK (aka machine).""" FLAGS.xenapi_image_service = 'objectstore' self.fake_instance.image_id = glance_stubs.FakeGlance.IMAGE_MACHINE self.fake_instance.kernel_id = glance_stubs.FakeGlance.IMAGE_KERNEL diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index d79312a60..4434dbf0b 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -60,7 +60,7 @@ from nova import exception from nova import log as logging -_CLASSES = ['host', 'network', 'session', 'SR', 'VBD', \ +_CLASSES = ['host', 'network', 'session', 'SR', 'VBD', 'PBD', 'VDI', 'VIF', 'PIF', 'VM', 'VLAN', 'task'] _db_content = {} @@ -200,19 +200,19 @@ def create_local_srs(): def _create_local_sr(host_ref): - sr_ref = \ - _create_object('SR', - {'name_label': 'Local storage', - 'type': 'lvm', - 'content_type': 'user', - 'shared': False, - 'physical_size': str(1 << 30), - 'physical_utilisation': str(0), - 'virtual_allocation': str(0), - 'other_config': {'i18n-original-value-name_label': \ - 'Local storage', - 'i18n-key': 'local-storage'}, - 'VDIs': []}) + sr_ref = _create_object( + 'SR', + {'name_label': 'Local storage', + 'type': 'lvm', + 'content_type': 'user', + 'shared': False, + 'physical_size': str(1 << 30), + 'physical_utilisation': str(0), + 'virtual_allocation': str(0), + 'other_config': { + 'i18n-original-value-name_label': 'Local storage', + 'i18n-key': 'local-storage'}, + 'VDIs': []}) pbd_ref = create_pbd('', host_ref, sr_ref, True) _db_content['SR'][sr_ref]['PBDs'] = [pbd_ref] return sr_ref -- cgit From 45e3deee1581580bed56d1bdfaaf9f4814fe7963 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Mon, 28 Mar 2011 17:13:37 -0400 Subject: Updated docstrings to satisfy. --- nova/api/openstack/images.py | 40 ++++++++++++---------------------------- 1 file changed, 12 insertions(+), 28 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 5fd1f0163..ad748b4ca 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -32,10 +32,8 @@ FLAGS = flags.FLAGS class Controller(wsgi.Controller): - """ - Base `wsgi.Controller` for retrieving and displaying images in the - OpenStack API. Version-inspecific code goes here. - """ + """Base `wsgi.Controller` for retrieving and displaying images in the + OpenStack API. Version-inspecific code goes here.""" _serialization_metadata = { 'application/xml': { @@ -48,8 +46,7 @@ class Controller(wsgi.Controller): } def __init__(self, image_service=None, compute_service=None): - """ - Initialize new `ImageController`. + """Initialize new `ImageController`. :param compute_service: `nova.compute.api:API` :param image_service: `nova.image.service:BaseImageService` @@ -60,8 +57,7 @@ class Controller(wsgi.Controller): self._image_service = image_service or _default_service def index(self, req): - """ - Return an index listing of images available to the request. + """Return an index listing of images available to the request. :param req: `wsgi.Request` object """ @@ -72,8 +68,7 @@ class Controller(wsgi.Controller): return dict(images=[builder(image, detail=False) for image in images]) def detail(self, req): - """ - Return a detailed index listing of images available to the request. + """Return a detailed index listing of images available to the request. :param req: `wsgi.Request` object. """ @@ -84,8 +79,7 @@ class Controller(wsgi.Controller): return dict(images=[builder(image, detail=True) for image in images]) def show(self, req, id): - """ - Return detailed information about a specific image. + """Return detailed information about a specific image. :param req: `wsgi.Request` object :param id: Image identifier (integer) @@ -107,8 +101,7 @@ class Controller(wsgi.Controller): return dict(image=self.get_builder(req).build(image, detail=True)) def delete(self, req, id): - """ - Delete an image, if allowed. + """Delete an image, if allowed. :param req: `wsgi.Request` object :param id: Image identifier (integer) @@ -119,8 +112,7 @@ class Controller(wsgi.Controller): return webob.exc.HTTPNoContent() def create(self, req): - """ - Snapshot a server instance and save the image. + """Snapshot a server instance and save the image. :param req: `wsgi.Request` object """ @@ -146,26 +138,18 @@ class Controller(wsgi.Controller): class ControllerV10(Controller): - """ - Version 1.0 specific controller logic. - """ + """Version 1.0 specific controller logic.""" def get_builder(self, request): - """ - Property to get the ViewBuilder class we need to use. - """ + """Property to get the ViewBuilder class we need to use.""" base_url = request.application_url return images_view.ViewBuilderV10(base_url) class ControllerV11(Controller): - """ - Version 1.1 specific controller logic. - """ + """Version 1.1 specific controller logic.""" def get_builder(self, request): - """ - Property to get the ViewBuilder class we need to use. - """ + """Property to get the ViewBuilder class we need to use.""" base_url = request.application_url return images_view.ViewBuilderV11(base_url) -- cgit From 6efd9dc30870008750c9754de4672d3eea656cce Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Mon, 28 Mar 2011 17:15:54 -0400 Subject: Updated docstrings to satisfy. --- nova/api/openstack/views/images.py | 35 ++++++++++------------------------- 1 file changed, 10 insertions(+), 25 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index 9db73bd4b..8f5568f6c 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -19,29 +19,21 @@ import os.path class ViewBuilder(object): - """ - Base class for generating responses to OpenStack API requests for - information about images. - """ + """Base class for generating responses to OpenStack API requests for + information about images.""" def __init__(self, base_url): - """ - Initialize new `ViewBuilder`. - """ + """Initialize new `ViewBuilder`.""" self._url = base_url def _format_dates(self, image): - """ - Update all date fields to ensure standardized formatting. - """ + """Update all date fields to ensure standardized formatting.""" for attr in ['created_at', 'updated_at', 'deleted_at']: if image.get(attr) is not None: image[attr] = image[attr].strftime('%Y-%m-%dT%H:%M:%SZ') def _format_status(self, image): - """ - Update the status field to standardize format. - """ + """Update the status field to standardize format.""" status_mapping = { 'pending': 'queued', 'decrypting': 'preparing', @@ -56,15 +48,11 @@ class ViewBuilder(object): image['status'] = image['status'].upper() def generate_href(self, image_id): - """ - Return an href string pointing to this object. - """ + """Return an href string pointing to this object.""" return os.path.join(self._url, "images", str(image_id)) def build(self, image_obj, detail=False): - """ - Return a standardized image structure for display by the API. - """ + """Return a standardized image structure for display by the API.""" properties = image_obj.get("properties", {}) self._format_dates(image_obj) @@ -97,18 +85,15 @@ class ViewBuilder(object): class ViewBuilderV10(ViewBuilder): + """OpenStack API v1.0 Image Builder""" pass class ViewBuilderV11(ViewBuilder): - """ - OpenStack API v1.1 Image Builder - """ + """OpenStack API v1.1 Image Builder""" def build(self, image_obj, detail=False): - """ - Return a standardized image structure for display by the API. - """ + """Return a standardized image structure for display by the API.""" image = ViewBuilder.build(self, image_obj, detail) href = self.generate_href(image_obj["id"]) -- cgit From cd81e06c19893b44568a8cef37a1de30b726e236 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Mon, 28 Mar 2011 22:25:11 +0100 Subject: fix docstrings --- nova/virt/xenapi/vmops.py | 61 +++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 28 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 147419f90..a9514bd61 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -279,8 +279,7 @@ class VMOps(object): "start") def snapshot(self, instance, image_id): - """ - Create snapshot from a running VM instance + """Create snapshot from a running VM instance. :param instance: instance to be snapshotted :param image_id: id of image to upload to @@ -300,6 +299,7 @@ class VMOps(object): 3. Push-to-glance: Once coalesced, we call a plugin on the XenServer that will bundle the VHDs together and then push the bundle into Glance. + """ template_vm_ref = None try: @@ -332,8 +332,7 @@ class VMOps(object): return def migrate_disk_and_power_off(self, instance, dest): - """ - Copies a VHD from one host machine to another + """Copies a VHD from one host machine to another. :param instance: the instance that owns the VHD in question. :param dest: the destination host machine. @@ -428,13 +427,14 @@ class VMOps(object): self._session.wait_for_task(task, instance.id) def set_admin_password(self, instance, new_pass): - """ - Set the root/admin password on the VM instance. This is done via - an agent running on the VM. Communication between nova and the agent - is done via writing xenstore records. Since communication is done over - the XenAPI RPC calls, we need to encrypt the password. We're using a - simple Diffie-Hellman class instead of the more advanced one in - M2Crypto for compatibility with the agent code. + """Set the root/admin password on the VM instance. + + This is done via an agent running on the VM. Communication between nova + and the agent is done via writing xenstore records. Since communication + is done over the XenAPI RPC calls, we need to encrypt the password. + We're using a simple Diffie-Hellman class instead of the more advanced + one in M2Crypto for compatibility with the agent code. + """ # Need to uniquely identify this request. transaction_id = str(uuid.uuid4()) @@ -467,12 +467,14 @@ class VMOps(object): return resp_dict['message'] def inject_file(self, instance, path, contents): - """ - Write a file to the VM instance. The path to which it is to be - written and the contents of the file need to be supplied; both will - be base64-encoded to prevent errors with non-ASCII characters being - transmitted. If the agent does not support file injection, or the user - has disabled it, a NotImplementedError will be raised. + """Write a file to the VM instance. + + The path to which it is to be written and the contents of the file + need to be supplied; both will be base64-encoded to prevent errors + with non-ASCII characters being transmitted. If the agent does not + support file injection, or the user has disabled it, a + NotImplementedError will be raised. + """ # Files/paths must be base64-encoded for transmission to agent b64_path = base64.b64encode(path) @@ -556,8 +558,7 @@ class VMOps(object): VMHelper.destroy_vbd(self._session, vbd_ref) def _destroy_kernel_ramdisk(self, instance, vm_ref): - """ - Three situations can occur: + """Three situations can occur: 1. We have neither a ramdisk nor a kernel, in which case we are a RAW image and can omit this step @@ -567,6 +568,7 @@ class VMOps(object): 3. We have both, in which case we safely remove both the kernel and the ramdisk. + """ instance_id = instance.id if not instance.kernel_id and not instance.ramdisk_id: @@ -614,11 +616,11 @@ class VMOps(object): self._session.call_xenapi("Async.VM.destroy", rescue_vm_ref) def destroy(self, instance): - """ - Destroy VM instance + """Destroy VM instance. This is the method exposed by xenapi_conn.destroy(). The rest of the destroy_* methods are internal. + """ instance_id = instance.id LOG.info(_("Destroying VM for Instance %(instance_id)s") % locals()) @@ -627,13 +629,13 @@ class VMOps(object): def _destroy(self, instance, vm_ref, shutdown=True, destroy_kernel_ramdisk=True): - """ - Destroys VM instance by performing: + """Destroys VM instance by performing: 1. A shutdown if requested. 2. Destroying associated VDIs. 3. Destroying kernel and ramdisk files (if necessary). 4. Destroying that actual VM record. + """ if vm_ref is None: LOG.warning(_("VM is not present, skipping destroy...")) @@ -681,8 +683,8 @@ class VMOps(object): self._wait_with_callback(instance.id, task, callback) def rescue(self, instance, callback): - """ - Rescue the specified instance + """Rescue the specified instance. + - shutdown the instance VM. - set 'bootlock' to prevent the instance from starting in rescue. - spawn a rescue VM (the vm name-label will be instance-N-rescue). @@ -709,10 +711,12 @@ class VMOps(object): self._session.call_xenapi("Async.VBD.plug", rescue_vbd_ref) def unrescue(self, instance, callback): - """Unrescue the specified instance + """Unrescue the specified instance. + - unplug the instance VM's disk from the rescue VM. - teardown the rescue VM. - release the bootlock to allow the instance VM to start. + """ rescue_vm_ref = VMHelper.lookup(self._session, "%s-rescue" % instance.name) @@ -729,10 +733,11 @@ class VMOps(object): self._start(instance, original_vm_ref) def poll_rescued_instances(self, timeout): - """ - Look for expirable rescued instances + """Look for expirable rescued instances. + - forcibly exit rescue mode for any instances that have been in rescue mode for >= the provided timeout + """ last_ran = self.poll_rescue_last_ran if not last_ran: -- cgit From c439309fddb7e6ebc14ab6b82ac9960f459c5aed Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 28 Mar 2011 17:40:45 -0400 Subject: osapi servers update tests actually assert now; enforcing server name being a string of length > 0; moving server update adminPass support to be v1.0-specific --- nova/api/openstack/servers.py | 30 +++++++++++++++++++++------- nova/tests/api/openstack/test_servers.py | 34 +++++++++++++++++++++++--------- 2 files changed, 48 insertions(+), 16 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 75a305a14..80617cf1c 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -246,20 +246,27 @@ class Controller(wsgi.Controller): ctxt = req.environ['nova.context'] update_dict = {} - if 'adminPass' in inst_dict['server']: - update_dict['admin_pass'] = inst_dict['server']['adminPass'] - try: - self.compute_api.set_admin_password(ctxt, id) - except exception.TimeoutException: - return exc.HTTPRequestTimeout() + if 'name' in inst_dict['server']: - update_dict['display_name'] = inst_dict['server']['name'] + name = inst_dict['server']['name'] + + if not isinstance(name, basestring) or name == '': + return exc.HTTPBadRequest() + + update_dict['display_name'] = name + + self._parse_update(ctxt, id, inst_dict, update_dict) + try: self.compute_api.update(ctxt, id, **update_dict) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) + return exc.HTTPNoContent() + def _parse_update(self, context, id, inst_dict, update_dict): + pass + @scheduler_api.redirect_handler def action(self, req, id): """Multi-purpose method used to reboot, rebuild, or @@ -566,6 +573,15 @@ class ControllerV10(Controller): def _limit_items(self, items, req): return common.limited(items, req) + def _parse_update(self, context, server_id, inst_dict, update_dict): + if 'adminPass' in inst_dict['server']: + update_dict['admin_pass'] = inst_dict['server']['adminPass'] + try: + self.compute_api.set_admin_password(context, server_id) + except exception.TimeoutException: + return exc.HTTPRequestTimeout() + + class ControllerV11(Controller): def _image_id_from_req_data(self, data): diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 989385a8c..1043838eb 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -448,39 +448,55 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 422) - def test_update_bad_params(self): + def test_update_bad_name(self): """ Confirm that update is filtering params """ - inst_dict = dict(cat='leopard', name='server_test', adminPass='bacon') + inst_dict = dict(name='', adminPass='bacon') + self.body = json.dumps(dict(server=inst_dict)) + + req = webob.Request.blank('/v1.0/servers/1') + req.method = 'PUT' + req.content_type = "application/json" + req.body = self.body + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_update_server_v10(self): + inst_dict = dict(name='server_test', adminPass='bacon') self.body = json.dumps(dict(server=inst_dict)) def server_update(context, id, params): - self.update_called = True - filtered_dict = dict(name='server_test', admin_pass='bacon') + filtered_dict = dict(display_name='server_test', admin_pass='bacon') self.assertEqual(params, filtered_dict) + return filtered_dict self.stubs.Set(nova.db.api, 'instance_update', server_update) req = webob.Request.blank('/v1.0/servers/1') req.method = 'PUT' + req.content_type = "application/json" req.body = self.body - req.get_response(fakes.wsgi_app()) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 204) - def test_update_server(self): + def test_update_server_adminPass_ignored_v11(self): inst_dict = dict(name='server_test', adminPass='bacon') self.body = json.dumps(dict(server=inst_dict)) def server_update(context, id, params): - filtered_dict = dict(name='server_test', admin_pass='bacon') + filtered_dict = dict(display_name='server_test') self.assertEqual(params, filtered_dict) + return filtered_dict self.stubs.Set(nova.db.api, 'instance_update', server_update) - req = webob.Request.blank('/v1.0/servers/1') + req = webob.Request.blank('/v1.1/servers/1') req.method = 'PUT' + req.content_type = "application/json" req.body = self.body - req.get_response(fakes.wsgi_app()) + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 204) def test_create_backup_schedules(self): req = webob.Request.blank('/v1.0/servers/1/backup_schedule') -- cgit From f460d75ae355ee76b6c51d884162f00076140716 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 28 Mar 2011 17:45:48 -0400 Subject: pep8 --- nova/api/openstack/servers.py | 3 +-- nova/tests/api/openstack/test_servers.py | 5 ++++- 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 80617cf1c..e9bc0a797 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -253,7 +253,7 @@ class Controller(wsgi.Controller): if not isinstance(name, basestring) or name == '': return exc.HTTPBadRequest() - update_dict['display_name'] = name + update_dict['display_name'] = name self._parse_update(ctxt, id, inst_dict, update_dict) @@ -582,7 +582,6 @@ class ControllerV10(Controller): return exc.HTTPRequestTimeout() - class ControllerV11(Controller): def _image_id_from_req_data(self, data): href = data['server']['imageRef'] diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 1043838eb..506b24b8b 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -465,7 +465,10 @@ class ServersTest(test.TestCase): self.body = json.dumps(dict(server=inst_dict)) def server_update(context, id, params): - filtered_dict = dict(display_name='server_test', admin_pass='bacon') + filtered_dict = dict( + display_name='server_test', + admin_pass='bacon', + ) self.assertEqual(params, filtered_dict) return filtered_dict -- cgit From 94092e3d896732fa1a97627f0fa504c3af70b3c5 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Mon, 28 Mar 2011 15:38:09 -0700 Subject: address some of termie's recommendations --- nova/api/openstack/servers.py | 4 ++-- nova/compute/api.py | 2 +- nova/tests/test_compute.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index c0fba4bb9..822342149 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -473,7 +473,7 @@ class Controller(wsgi.Controller): @scheduler_api.redirect_handler def get_ajax_console(self, req, id): - """ Returns a url to an instance's ajaxterm console. """ + """Returns a url to an instance's ajaxterm console.""" try: self.compute_api.get_ajax_console(req.environ['nova.context'], int(id)) @@ -483,7 +483,7 @@ class Controller(wsgi.Controller): @scheduler_api.redirect_handler def get_vnc_console(self, req, id): - """ Returns a url to an instance's ajaxterm console. """ + """Returns a url to an instance's ajaxterm console.""" try: self.compute_api.get_vnc_console(req.environ['nova.context'], int(id)) diff --git a/nova/compute/api.py b/nova/compute/api.py index f19c552e9..5470f40dc 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -618,7 +618,7 @@ class API(base.Base): {'method': 'authorize_vnc_console', 'args': {'token': output['token'], 'host': output['host'], - 'port': output['port']}}) + 'port': output['port']}}) return {'url': '%s/vnc_auto.html?token=%s&host=%s&port=%s' % ( FLAGS.vnc_console_proxy_url, diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 0be08a778..038824ef8 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -290,7 +290,7 @@ class ComputeTestCase(test.TestCase): self.compute.terminate_instance(self.context, instance_id) def test_vnc_console(self): - """Make sure we can a vnc console for an instance""" + """Make sure we can a vnc console for an instance.""" instance_id = self._create_instance() self.compute.run_instance(self.context, instance_id) -- cgit From 57a4864e30df604612a347ba069ccc8499b04f1f Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Mon, 28 Mar 2011 16:30:31 -0700 Subject: Code cleanup to keep the termie-bot happy --- nova/api/openstack/extensions.py | 131 +++++++++++++++------------------------ 1 file changed, 50 insertions(+), 81 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index e81ffb3d3..9566d3250 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -38,55 +38,55 @@ FLAGS = flags.FLAGS class ExtensionDescriptor(object): - """This is the base class that defines the contract for extensions""" + """This is the base class that defines the contract for extensions.""" def get_name(self): - """The name of the extension + """The name of the extension. e.g. 'Fox In Socks' """ raise NotImplementedError() def get_alias(self): - """The alias for the extension + """The alias for the extension. e.g. 'FOXNSOX'""" raise NotImplementedError() def get_description(self): - """Friendly description for the extension + """Friendly description for the extension. e.g. 'The Fox In Socks Extension'""" raise NotImplementedError() def get_namespace(self): - """The XML namespace for the extension + """The XML namespace for the extension. e.g. 'http://www.fox.in.socks/api/ext/pie/v1.0'""" raise NotImplementedError() def get_updated(self): - """The timestamp when the extension was last updated + """The timestamp when the extension was last updated. e.g. '2011-01-22T13:25:27-06:00'""" - #NOTE(justinsb): Not sure of the purpose of this is, vs the XML NS + # NOTE(justinsb): Not sure of the purpose of this is, vs the XML NS raise NotImplementedError() def get_resources(self): - """List of extensions.ResourceExtension extension objects + """List of extensions.ResourceExtension extension objects. Resources define new nouns, and are accessible through URLs""" resources = [] return resources def get_actions(self): - """List of extensions.ActionExtension extension objects + """List of extensions.ActionExtension extension objects. Actions are verbs callable from the API""" actions = [] return actions def get_response_extensions(self): - """List of extensions.ResponseExtension extension objects + """List of extensions.ResponseExtension extension objects. Response extensions are used to insert information into existing response data""" @@ -154,17 +154,17 @@ class ExtensionController(wsgi.Controller): ext_data['description'] = ext.get_description() ext_data['namespace'] = ext.get_namespace() ext_data['updated'] = ext.get_updated() - ext_data['links'] = [] # TODO: implement extension links + ext_data['links'] = [] # TODO(dprince): implement extension links return ext_data def index(self, req): extensions = [] - for alias, ext in self.extension_manager.extensions.iteritems(): + for _alias, ext in self.extension_manager.extensions.iteritems(): extensions.append(self._translate(ext)) return dict(extensions=extensions) def show(self, req, id): - # NOTE: the extensions alias is used as the 'id' for show + # NOTE(dprince): the extensions alias is used as the 'id' for show ext = self.extension_manager.extensions[id] return self._translate(ext) @@ -176,20 +176,16 @@ class ExtensionController(wsgi.Controller): class ExtensionMiddleware(wsgi.Middleware): - """ - Extensions middleware that intercepts configured routes for extensions. - """ + """Extensions middleware for WSGI.""" @classmethod def factory(cls, global_config, **local_config): - """ paste factory """ + """Paste factory.""" def _factory(app): return cls(app, **local_config) return _factory def _action_ext_controllers(self, application, ext_mgr, mapper): - """ - Return a dict of ActionExtensionController objects by collection - """ + """Return a dict of ActionExtensionController-s by collection.""" action_controllers = {} for action in ext_mgr.get_actions(): if not action.collection in action_controllers.keys(): @@ -208,9 +204,7 @@ class ExtensionMiddleware(wsgi.Middleware): return action_controllers def _response_ext_controllers(self, application, ext_mgr, mapper): - """ - Return a dict of ResponseExtensionController objects by collection - """ + """Returns a dict of ResponseExtensionController-s by collection.""" response_ext_controllers = {} for resp_ext in ext_mgr.get_response_extensions(): if not resp_ext.key in response_ext_controllers.keys(): @@ -269,16 +263,15 @@ class ExtensionMiddleware(wsgi.Middleware): @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): - """ - Route the incoming request with router. - """ + """Route the incoming request with router.""" req.environ['extended.app'] = self.application return self._router @staticmethod @webob.dec.wsgify(RequestClass=wsgi.Request) def _dispatch(req): - """ + """Dispatch the request. + Returns the routed WSGI app's response or defers to the extended application. """ @@ -290,8 +283,7 @@ class ExtensionMiddleware(wsgi.Middleware): class ExtensionManager(object): - """ - Load extensions from the configured extension path. + """Load extensions from the configured extension path. See nova/tests/api/openstack/extensions/foxinsocks/extension.py for an example extension implementation. @@ -307,50 +299,30 @@ class ExtensionManager(object): self._load_all_extensions() def get_resources(self): - """ - returns a list of ResourceExtension objects - """ + """Returns a list of ResourceExtension objects.""" resources = [] resources.append(ResourceExtension('extensions', ExtensionController(self))) - for alias, ext in self.extensions.iteritems(): - try: - resources.extend(ext.get_resources()) - except AttributeError: - # NOTE: Extension aren't required to have resource extensions - pass + for _alias, ext in self.extensions.iteritems(): + resources.extend(ext.get_resources()) return resources def get_actions(self): - """ - returns a list of ActionExtension objects - """ + """Returns a list of ActionExtension objects.""" actions = [] - for alias, ext in self.extensions.iteritems(): - try: - actions.extend(ext.get_actions()) - except AttributeError: - # NOTE: Extension aren't required to have action extensions - pass + for _alias, ext in self.extensions.iteritems(): + actions.extend(ext.get_actions()) return actions def get_response_extensions(self): - """ - returns a list of ResponseExtension objects - """ + """Returns a list of ResponseExtension objects.""" response_exts = [] - for alias, ext in self.extensions.iteritems(): - try: - response_exts.extend(ext.get_response_extensions()) - except AttributeError: - # NOTE: Extension aren't required to have response extensions - pass + for _alias, ext in self.extensions.iteritems(): + response_exts.extend(ext.get_response_extensions()) return response_exts def _check_extension(self, extension): - """ - Checks for required methods in extension objects. - """ + """Checks for required methods in extension objects.""" try: LOG.debug(_('Ext name: %s'), extension.get_name()) LOG.debug(_('Ext alias: %s'), extension.get_alias()) @@ -361,11 +333,17 @@ class ExtensionManager(object): LOG.exception(_("Exception loading extension: %s"), unicode(ex)) def _load_all_extensions(self): - """ - Load extensions from the configured path. The extension name is - constructed from the module_name. If your extension module was named - widgets.py the extension class within that module should be - 'Widgets'. + """Load extensions from the configured path. + + An extension consists of a directory of related files, with a class + that defines a class that inherits from ExtensionDescriptor. + + Because of some oddities involving identically named modules, it's + probably best to name your file after the name of your extension, + rather than something likely to clash like 'extension.py'. + + The name of your directory should be the same as the alias your + extension uses, for everyone's sanity. See nova/tests/api/openstack/extensions/foxinsocks.py for an example extension implementation. @@ -409,11 +387,11 @@ class ExtensionManager(object): if not inspect.isclass(cls): continue - #NOTE(justinsb): It seems that python modules aren't great - # If you have two identically named modules, the classes - # from both are mixed in. So name your extension based - # on the alias, not 'extension.py'! - #TODO(justinsb): Any way to work around this? + # NOTE(justinsb): It seems that python modules are odd. + # If you have two identically named modules, the classes + # from both are mixed in. So name your extension based + # on the alias, not 'extension.py'! + # TODO(justinsb): Any way to work around this? if self.super_verbose: LOG.debug(_('Checking class: %s'), cls) @@ -441,10 +419,7 @@ class ExtensionManager(object): class ResponseExtension(object): - """ - ResponseExtension objects can be used to add data to responses from - core nova OpenStack API controllers. - """ + """Add data to responses from core nova OpenStack API controllers.""" def __init__(self, method, url_route, handler): self.url_route = url_route @@ -454,10 +429,7 @@ class ResponseExtension(object): class ActionExtension(object): - """ - ActionExtension objects can be used to add custom actions to core nova - nova OpenStack API controllers. - """ + """Add custom actions to core nova OpenStack API controllers.""" def __init__(self, collection, action_name, handler): self.collection = collection @@ -466,10 +438,7 @@ class ActionExtension(object): class ResourceExtension(object): - """ - ResourceExtension objects can be used to add top level resources - to the OpenStack API in nova. - """ + """Add top level resources to the OpenStack API in nova.""" def __init__(self, collection, controller, parent=None, collection_actions={}, member_actions={}): -- cgit From a6e8c83196cb4b2d8a292a99cb1feb22ed9b21db Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Mon, 28 Mar 2011 16:36:25 -0700 Subject: Cleaned up images/fake.py, including move to Duplicate exception --- .../incubator/volumes/volume_attachments.py | 30 +++++++++++----------- nova/api/openstack/incubator/volumes/volumes.py | 18 ++++++------- .../api/openstack/incubator/volumes/volumes_ext.py | 4 +-- nova/compute/manager.py | 2 +- nova/image/fake.py | 29 +++++++++------------ 5 files changed, 39 insertions(+), 44 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/incubator/volumes/volume_attachments.py b/nova/api/openstack/incubator/volumes/volume_attachments.py index 93786d1c7..1e260b34d 100644 --- a/nova/api/openstack/incubator/volumes/volume_attachments.py +++ b/nova/api/openstack/incubator/volumes/volume_attachments.py @@ -34,7 +34,7 @@ FLAGS = flags.FLAGS def _translate_detail_view(context, volume): - """Maps keys for details view""" + """Maps keys for details view.""" d = _translate_summary_view(context, volume) @@ -44,12 +44,12 @@ def _translate_detail_view(context, volume): def _translate_summary_view(context, vol): - """Maps keys for summary view""" + """Maps keys for summary view.""" d = {} volume_id = vol['id'] - #NOTE(justinsb): We use the volume id as the id of the attachment object + # NOTE(justinsb): We use the volume id as the id of the attachment object d['id'] = volume_id d['volumeId'] = volume_id @@ -62,7 +62,7 @@ def _translate_summary_view(context, vol): class Controller(wsgi.Controller): - """The volume attachment API controller for the Openstack API + """The volume attachment API controller for the Openstack API. A child resource of the server. Note that we use the volume id as the ID of the attachment (though this is not guaranteed externally)""" @@ -81,12 +81,12 @@ class Controller(wsgi.Controller): super(Controller, self).__init__() def index(self, req, server_id): - """Returns the list of volume attachments for a given instance """ + """Returns the list of volume attachments for a given instance.""" return self._items(req, server_id, entity_maker=_translate_summary_view) def show(self, req, server_id, id): - """Return data about the given volume""" + """Return data about the given volume.""" context = req.environ['nova.context'] volume_id = id @@ -103,7 +103,7 @@ class Controller(wsgi.Controller): return {'volumeAttachment': _translate_detail_view(context, vol)} def create(self, req, server_id): - """Attach a volume to an instance """ + """Attach a volume to an instance.""" context = req.environ['nova.context'] env = self._deserialize(req.body, req.get_content_type()) @@ -131,15 +131,15 @@ class Controller(wsgi.Controller): attachment['id'] = volume_id attachment['volumeId'] = volume_id - #NOTE(justinsb): And now, we have a problem... + # NOTE(justinsb): And now, we have a problem... # The attach is async, so there's a window in which we don't see - # the attachment (until the attachment completes). We could also - # get problems with concurrent requests. I think we need an - # attachment state, and to write to the DB here, but that's a bigger - # change. + # the attachment (until the attachment completes). We could also + # get problems with concurrent requests. I think we need an + # attachment state, and to write to the DB here, but that's a bigger + # change. # For now, we'll probably have to rely on libraries being smart - #TODO(justinsb): How do I return "accepted" here? + # TODO(justinsb): How do I return "accepted" here? return {'volumeAttachment': attachment} def update(self, _req, _server_id, _id): @@ -147,7 +147,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPBadRequest()) def delete(self, req, server_id, id): - """Detach a volume from an instance """ + """Detach a volume from an instance.""" context = req.environ['nova.context'] volume_id = id @@ -168,7 +168,7 @@ class Controller(wsgi.Controller): return exc.HTTPAccepted() def _items(self, req, server_id, entity_maker): - """Returns a list of attachments, transformed through entity_maker""" + """Returns a list of attachments, transformed through entity_maker.""" context = req.environ['nova.context'] try: diff --git a/nova/api/openstack/incubator/volumes/volumes.py b/nova/api/openstack/incubator/volumes/volumes.py index e122bb465..a7d5fbaa6 100644 --- a/nova/api/openstack/incubator/volumes/volumes.py +++ b/nova/api/openstack/incubator/volumes/volumes.py @@ -31,7 +31,7 @@ FLAGS = flags.FLAGS def _translate_detail_view(context, vol): - """Maps keys for details view""" + """Maps keys for details view.""" d = _translate_summary_view(context, vol) @@ -41,7 +41,7 @@ def _translate_detail_view(context, vol): def _translate_summary_view(_context, vol): - """Maps keys for summary view""" + """Maps keys for summary view.""" d = {} instance_id = None @@ -79,7 +79,7 @@ def _translate_summary_view(_context, vol): class Controller(wsgi.Controller): - """The Volumes API controller for the OpenStack API""" + """The Volumes API controller for the OpenStack API.""" _serialization_metadata = { 'application/xml': { @@ -99,7 +99,7 @@ class Controller(wsgi.Controller): super(Controller, self).__init__() def show(self, req, id): - """Return data about the given volume""" + """Return data about the given volume.""" context = req.environ['nova.context'] try: @@ -110,7 +110,7 @@ class Controller(wsgi.Controller): return {'volume': _translate_detail_view(context, vol)} def delete(self, req, id): - """Delete a volume """ + """Delete a volume.""" context = req.environ['nova.context'] LOG.audit(_("Delete volume with id: %s"), id, context=context) @@ -122,15 +122,15 @@ class Controller(wsgi.Controller): return exc.HTTPAccepted() def index(self, req): - """Returns a summary list of volumes""" + """Returns a summary list of volumes.""" return self._items(req, entity_maker=_translate_summary_view) def detail(self, req): - """Returns a detailed list of volumes """ + """Returns a detailed list of volumes.""" return self._items(req, entity_maker=_translate_detail_view) def _items(self, req, entity_maker): - """Returns a list of volumes, transformed through entity_maker""" + """Returns a list of volumes, transformed through entity_maker.""" context = req.environ['nova.context'] volumes = self.volume_api.get_all(context) @@ -139,7 +139,7 @@ class Controller(wsgi.Controller): return {'volumes': res} def create(self, req): - """Creates a new volume""" + """Creates a new volume.""" context = req.environ['nova.context'] env = self._deserialize(req.body, req.get_content_type()) diff --git a/nova/api/openstack/incubator/volumes/volumes_ext.py b/nova/api/openstack/incubator/volumes/volumes_ext.py index 87a57320d..6a3bb0265 100644 --- a/nova/api/openstack/incubator/volumes/volumes_ext.py +++ b/nova/api/openstack/incubator/volumes/volumes_ext.py @@ -37,8 +37,8 @@ class VolumesExtension(extensions.ExtensionDescriptor): def get_resources(self): resources = [] - #NOTE(justinsb): No way to provide singular name ('volume') - # Does this matter? + # NOTE(justinsb): No way to provide singular name ('volume') + # Does this matter? res = extensions.ResourceExtension('volumes', volumes.Controller(), collection_actions={'detail': 'GET'} diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 10636f602..468771f46 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1078,6 +1078,6 @@ class ComputeManager(manager.SchedulerDependentManager): # Are there VMs not in the DB? for vm_not_found_in_db in vms_not_found_in_db: name = vm_not_found_in_db - #TODO(justinsb): What to do here? Adopt it? Shut it down? + # TODO(justinsb): What to do here? Adopt it? Shut it down? LOG.warning(_("Found VM not in DB: '%(name)s'. Ignoring") % locals()) diff --git a/nova/image/fake.py b/nova/image/fake.py index 29159f337..cdb650165 100644 --- a/nova/image/fake.py +++ b/nova/image/fake.py @@ -29,11 +29,11 @@ FLAGS = flags.FLAGS class MockImageService(service.BaseImageService): - """Mock (fake) image service for unit testing""" + """Mock (fake) image service for unit testing.""" def __init__(self): self.images = {} - # NOTE(justinsb): The OpenStack API can't upload an image??? + # NOTE(justinsb): The OpenStack API can't upload an image? # So, make sure we've got one.. image = {'id': '123456', 'status': 'active', @@ -46,17 +46,17 @@ class MockImageService(service.BaseImageService): super(MockImageService, self).__init__() def index(self, context): - """Returns list of images""" + """Returns list of images.""" return self.images.values() def detail(self, context): - """Return list of detailed image information""" + """Return list of detailed image information.""" return self.images.values() def show(self, context, image_id): - """ - Returns a dict containing image data for the given opaque image id. - """ + """Get data about specified image. + + Returns a dict containing image data for the given opaque image id.""" image_id = int(image_id) image = self.images.get(image_id) if image: @@ -66,16 +66,14 @@ class MockImageService(service.BaseImageService): raise exception.NotFound def create(self, context, data): - """ - Store the image data and return the new image id. + """Store the image data and return the new image id. - :raises AlreadyExists if the image already exist. + :raises Duplicate if the image already exist. """ image_id = int(data['id']) if self.images.get(image_id): - #TODO(justinsb): Where is this AlreadyExists exception?? - raise exception.Error("AlreadyExists") + raise exception.Duplicate() self.images[image_id] = data @@ -91,8 +89,7 @@ class MockImageService(service.BaseImageService): self.images[image_id] = data def delete(self, context, image_id): - """ - Delete the given image. + """Delete the given image. :raises NotFound if the image does not exist. @@ -103,7 +100,5 @@ class MockImageService(service.BaseImageService): raise exception.NotFound def delete_all(self): - """ - Clears out all images - """ + """Clears out all images.""" self.images.clear() -- cgit From 276c153f44734e78cae25deb9fc9e79a604c6219 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Mon, 28 Mar 2011 16:42:28 -0700 Subject: More fixes to keep the stylebot happy --- nova/tests/integrated/api/client.py | 2 +- nova/tests/integrated/integrated_helpers.py | 20 ++++++++++---------- nova/tests/integrated/test_extensions.py | 2 +- nova/tests/integrated/test_login.py | 8 ++++---- nova/tests/integrated/test_servers.py | 16 ++++++++-------- nova/tests/integrated/test_volumes.py | 18 +++++++++--------- nova/virt/driver.py | 2 +- nova/volume/driver.py | 2 +- 8 files changed, 35 insertions(+), 35 deletions(-) (limited to 'nova') diff --git a/nova/tests/integrated/api/client.py b/nova/tests/integrated/api/client.py index b9ed7b03b..b40ff58e3 100644 --- a/nova/tests/integrated/api/client.py +++ b/nova/tests/integrated/api/client.py @@ -124,7 +124,7 @@ class TestOpenStackClient(object): def api_request(self, relative_uri, check_response_status=None, **kwargs): auth_result = self._authenticate() - #NOTE(justinsb): httplib 'helpfully' converts headers to lower case + # NOTE(justinsb): httplib 'helpfully' converts headers to lower case base_uri = auth_result['x-server-management-url'] full_uri = base_uri + relative_uri diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py index bc516b4b3..e0fe2d2a2 100644 --- a/nova/tests/integrated/integrated_helpers.py +++ b/nova/tests/integrated/integrated_helpers.py @@ -37,19 +37,19 @@ LOG = logging.getLogger('nova.tests.integrated') def generate_random_alphanumeric(length): - """Creates a random alphanumeric string of specified length""" + """Creates a random alphanumeric string of specified length.""" return ''.join(random.choice(string.ascii_uppercase + string.digits) for _x in range(length)) def generate_random_numeric(length): - """Creates a random numeric string of specified length""" + """Creates a random numeric string of specified length.""" return ''.join(random.choice(string.digits) for _x in range(length)) def generate_new_element(items, prefix, numeric=False): - """Creates a random string with prefix, that is not in 'items' list""" + """Creates a random string with prefix, that is not in 'items' list.""" while True: if numeric: candidate = prefix + generate_random_numeric(8) @@ -85,7 +85,7 @@ class TestUser(object): 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 + # 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") @@ -154,9 +154,9 @@ class _IntegratedTestBase(test.TestCase): # set up services self.start_service('compute') self.start_service('volume') - #NOTE(justinsb): There's a bug here which is eluding me... - # If we start the network_service, all is good, but then subsequent - # tests fail: CloudTestCase.test_ajax_console in particular. + # NOTE(justinsb): There's a bug here which is eluding me... + # If we start the network_service, all is good, but then subsequent + # tests fail: CloudTestCase.test_ajax_console in particular. #self.start_service('network') self.start_service('scheduler') @@ -182,7 +182,7 @@ class _IntegratedTestBase(test.TestCase): super(_IntegratedTestBase, self).tearDown() def _get_flags(self): - """An opportunity to setup flags, before the services are started""" + """An opportunity to setup flags, before the services are started.""" f = {} f['image_service'] = 'nova.image.fake.MockImageService' f['fake_network'] = True @@ -197,11 +197,11 @@ class _IntegratedTestBase(test.TestCase): if 'imageRef' in image: image_ref = image['imageRef'] else: - #NOTE(justinsb): The imageRef code hasn't yet landed + # NOTE(justinsb): The imageRef code hasn't yet landed LOG.warning("imageRef not yet in images output") image_ref = image['id'] - #TODO(justinsb): This is FUBAR + # TODO(justinsb): This is FUBAR image_ref = abs(hash(image_ref)) image_ref = 'http://fake.server/%s' % image_ref diff --git a/nova/tests/integrated/test_extensions.py b/nova/tests/integrated/test_extensions.py index 1aa471f49..0d4ee8cab 100644 --- a/nova/tests/integrated/test_extensions.py +++ b/nova/tests/integrated/test_extensions.py @@ -37,7 +37,7 @@ class ExtensionsTest(integrated_helpers._IntegratedTestBase): return f def test_get_foxnsocks(self): - """Simple check that fox-n-socks works""" + """Simple check that fox-n-socks works.""" response = self.api.api_request('/foxnsocks') foxnsocks = response.read() LOG.debug("foxnsocks: %s" % foxnsocks) diff --git a/nova/tests/integrated/test_login.py b/nova/tests/integrated/test_login.py index d6e067c29..a5180b6bc 100644 --- a/nova/tests/integrated/test_login.py +++ b/nova/tests/integrated/test_login.py @@ -31,13 +31,13 @@ FLAGS.verbose = True class LoginTest(integrated_helpers._IntegratedTestBase): def test_login(self): - """Simple check - we list flavors - so we know we're logged in""" + """Simple check - we list flavors - so we know we're logged in.""" 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""" + """Test that I get a 401 with a bad username.""" bad_credentials_api = client.TestOpenStackClient(self.user.name, "notso_password", self.user.auth_url) @@ -46,7 +46,7 @@ class LoginTest(integrated_helpers._IntegratedTestBase): bad_credentials_api.get_flavors) def test_bad_login_username(self): - """Test that I get a 401 with a bad password""" + """Test that I get a 401 with a bad password.""" bad_credentials_api = client.TestOpenStackClient("notso_username", self.user.secret, self.user.auth_url) @@ -55,7 +55,7 @@ class LoginTest(integrated_helpers._IntegratedTestBase): 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""" + """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) diff --git a/nova/tests/integrated/test_servers.py b/nova/tests/integrated/test_servers.py index 59e5dde14..749ea8955 100644 --- a/nova/tests/integrated/test_servers.py +++ b/nova/tests/integrated/test_servers.py @@ -39,7 +39,7 @@ class ServersTest(integrated_helpers._IntegratedTestBase): LOG.debug("server: %s" % server) def test_create_and_delete_server(self): - """Creates and deletes a server""" + """Creates and deletes a server.""" # Create server @@ -50,13 +50,13 @@ class ServersTest(integrated_helpers._IntegratedTestBase): post = {'server': server} # Without an imageRef, this throws 500. - #TODO(justinsb): Check whatever the spec says should be thrown here + # TODO(justinsb): Check whatever the spec says should be thrown here self.assertRaises(client.OpenStackApiException, self.api.post_server, post) # With an invalid imageRef, this throws 500. server['imageRef'] = self.user.get_invalid_image() - #TODO(justinsb): Check whatever the spec says should be thrown here + # TODO(justinsb): Check whatever the spec says should be thrown here self.assertRaises(client.OpenStackApiException, self.api.post_server, post) @@ -65,7 +65,7 @@ class ServersTest(integrated_helpers._IntegratedTestBase): server['imageRef'] = good_server.get('imageRef') # Without flavorId, this throws 500 - #TODO(justinsb): Check whatever the spec says should be thrown here + # TODO(justinsb): Check whatever the spec says should be thrown here self.assertRaises(client.OpenStackApiException, self.api.post_server, post) @@ -74,7 +74,7 @@ class ServersTest(integrated_helpers._IntegratedTestBase): server['flavorId'] = good_server.get('flavorId') # Without a name, this throws 500 - #TODO(justinsb): Check whatever the spec says should be thrown here + # TODO(justinsb): Check whatever the spec says should be thrown here self.assertRaises(client.OpenStackApiException, self.api.post_server, post) @@ -106,7 +106,7 @@ class ServersTest(integrated_helpers._IntegratedTestBase): break # It should be available... - #TODO(justinsb): Mock doesn't yet do this... + # TODO(justinsb): Mock doesn't yet do this... #self.assertEqual('available', found_server['status']) self._delete_server(created_server_id) @@ -126,7 +126,7 @@ class ServersTest(integrated_helpers._IntegratedTestBase): LOG.debug("Found_server=%s" % found_server) - #TODO(justinsb): Mock doesn't yet do accurate state changes + # TODO(justinsb): Mock doesn't yet do accurate state changes #if found_server['status'] != 'deleting': # break time.sleep(1) @@ -134,7 +134,7 @@ class ServersTest(integrated_helpers._IntegratedTestBase): # Should be gone self.assertFalse(found_server) -#TODO(justinsb): Enable this unit test when the metadata bug is fixed +# TODO(justinsb): Enable this unit test when the metadata bug is fixed # def test_create_server_with_metadata(self): # """Creates a server with metadata""" # diff --git a/nova/tests/integrated/test_volumes.py b/nova/tests/integrated/test_volumes.py index 89480618d..e9fb3c4d1 100644 --- a/nova/tests/integrated/test_volumes.py +++ b/nova/tests/integrated/test_volumes.py @@ -44,19 +44,19 @@ class VolumesTest(integrated_helpers._IntegratedTestBase): return f def test_get_volumes_summary(self): - """Simple check that listing volumes works""" + """Simple check that listing volumes works.""" volumes = self.api.get_volumes(False) for volume in volumes: LOG.debug("volume: %s" % volume) def test_get_volumes(self): - """Simple check that listing volumes works""" + """Simple check that listing volumes works.""" volumes = self.api.get_volumes() for volume in volumes: LOG.debug("volume: %s" % volume) def _poll_while(self, volume_id, continue_states, max_retries=5): - """Poll (briefly) while the state is in continue_states""" + """Poll (briefly) while the state is in continue_states.""" retries = 0 while True: try: @@ -80,7 +80,7 @@ class VolumesTest(integrated_helpers._IntegratedTestBase): return found_volume def test_create_and_delete_volume(self): - """Creates and deletes a volume""" + """Creates and deletes a volume.""" # Create volume created_volume = self.api.post_volume({'volume': {'size': 1}}) @@ -141,11 +141,11 @@ class VolumesTest(integrated_helpers._IntegratedTestBase): self.assertEquals(delete_action['id'], created_volume_id) def test_attach_and_detach_volume(self): - """Creates, attaches, detaches and deletes a volume""" + """Creates, attaches, detaches and deletes a volume.""" # Create server server_req = {'server': self._build_minimal_create_server_request()} - #NOTE(justinsb): Create an extra server so that server_id != volume_id + # NOTE(justinsb): Create an extra server so that server_id != volume_id self.api.post_server(server_req) created_server = self.api.post_server(server_req) LOG.debug("created_server: %s" % created_server) @@ -198,9 +198,9 @@ class VolumesTest(integrated_helpers._IntegratedTestBase): # This is just an implementation detail, but let's check it... self.assertEquals(volume_id, attachment_id) - #NOTE(justinsb): There's an issue with the attach code, in that - # it's currently asynchronous and not recorded until the attach - # completes. So the caller must be 'smart', like this... + # NOTE(justinsb): There's an issue with the attach code, in that + # it's currently asynchronous and not recorded until the attach + # completes. So the caller must be 'smart', like this... attach_done = None retries = 0 while True: diff --git a/nova/virt/driver.py b/nova/virt/driver.py index eafede17a..f85796a97 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -125,7 +125,7 @@ class ComputeDriver(object): raise NotImplementedError() def snapshot(self, instance, image_id): - """Create snapshot from a running VM instance """ + """Create snapshot from a running VM instance.""" raise NotImplementedError() def finish_resize(self, instance, disk_info): diff --git a/nova/volume/driver.py b/nova/volume/driver.py index 161b35d1d..850893914 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -573,7 +573,7 @@ class RBDDriver(VolumeDriver): def discover_volume(self, volume): """Discover volume on a remote host""" - #NOTE(justinsb): This is messed up... discover_volume takes 3 args + # NOTE(justinsb): This is messed up... discover_volume takes 3 args # but then that would break local_path return "rbd:%s/%s" % (FLAGS.rbd_pool, volume['name']) -- cgit From 87bc3bca7904135656ed3a99efc19952be95dcbf Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Mon, 28 Mar 2011 16:54:17 -0700 Subject: Multi-line comments should end in a blankline --- nova/api/openstack/incubator/volumes/volume_attachments.py | 4 +++- nova/image/fake.py | 4 +++- nova/tests/integrated/api/client.py | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/incubator/volumes/volume_attachments.py b/nova/api/openstack/incubator/volumes/volume_attachments.py index 1e260b34d..aec4ea8f3 100644 --- a/nova/api/openstack/incubator/volumes/volume_attachments.py +++ b/nova/api/openstack/incubator/volumes/volume_attachments.py @@ -65,7 +65,9 @@ class Controller(wsgi.Controller): """The volume attachment API controller for the Openstack API. A child resource of the server. Note that we use the volume id - as the ID of the attachment (though this is not guaranteed externally)""" + as the ID of the attachment (though this is not guaranteed externally) + + """ _serialization_metadata = { 'application/xml': { diff --git a/nova/image/fake.py b/nova/image/fake.py index cdb650165..4caf68d63 100644 --- a/nova/image/fake.py +++ b/nova/image/fake.py @@ -56,7 +56,9 @@ class MockImageService(service.BaseImageService): def show(self, context, image_id): """Get data about specified image. - Returns a dict containing image data for the given opaque image id.""" + Returns a dict containing image data for the given opaque image id. + + """ image_id = int(image_id) image = self.images.get(image_id) if image: diff --git a/nova/tests/integrated/api/client.py b/nova/tests/integrated/api/client.py index b40ff58e3..7e20c9b00 100644 --- a/nova/tests/integrated/api/client.py +++ b/nova/tests/integrated/api/client.py @@ -59,7 +59,9 @@ class TestOpenStackClient(object): """Simple OpenStack API Client. This is a really basic OpenStack API client that is under our control, - so we can make changes / insert hooks for testing""" + so we can make changes / insert hooks for testing + + """ def __init__(self, auth_user, auth_key, auth_uri): super(TestOpenStackClient, self).__init__() -- cgit From 55b801db77b9d631e746e79bd84a7866d1877fb2 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Mon, 28 Mar 2011 17:09:39 -0700 Subject: More style changes --- nova/api/openstack/extensions.py | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 1c39dd0e2..d0b4d378a 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -43,45 +43,59 @@ class ExtensionDescriptor(object): def get_name(self): """The name of the extension. - e.g. 'Fox In Socks' """ + e.g. 'Fox In Socks' + + """ raise NotImplementedError() def get_alias(self): """The alias for the extension. - e.g. 'FOXNSOX'""" + e.g. 'FOXNSOX' + + """ raise NotImplementedError() def get_description(self): """Friendly description for the extension. - e.g. 'The Fox In Socks Extension'""" + e.g. 'The Fox In Socks Extension' + + """ raise NotImplementedError() def get_namespace(self): """The XML namespace for the extension. - e.g. 'http://www.fox.in.socks/api/ext/pie/v1.0'""" + e.g. 'http://www.fox.in.socks/api/ext/pie/v1.0' + + """ raise NotImplementedError() def get_updated(self): """The timestamp when the extension was last updated. - e.g. '2011-01-22T13:25:27-06:00'""" + e.g. '2011-01-22T13:25:27-06:00' + + """ # NOTE(justinsb): Not sure of the purpose of this is, vs the XML NS raise NotImplementedError() def get_resources(self): """List of extensions.ResourceExtension extension objects. - Resources define new nouns, and are accessible through URLs""" + Resources define new nouns, and are accessible through URLs. + + """ resources = [] return resources def get_actions(self): """List of extensions.ActionExtension extension objects. - Actions are verbs callable from the API""" + Actions are verbs callable from the API. + + """ actions = [] return actions @@ -89,7 +103,9 @@ class ExtensionDescriptor(object): """List of extensions.ResponseExtension extension objects. Response extensions are used to insert information into existing - response data""" + response data. + + """ response_exts = [] return response_exts @@ -274,6 +290,7 @@ class ExtensionMiddleware(wsgi.Middleware): Returns the routed WSGI app's response or defers to the extended application. + """ match = req.environ['wsgiorg.routing_args'][1] if not match: @@ -287,6 +304,7 @@ class ExtensionManager(object): See nova/tests/api/openstack/extensions/foxinsocks/extension.py for an example extension implementation. + """ def __init__(self, path): @@ -347,6 +365,7 @@ class ExtensionManager(object): See nova/tests/api/openstack/extensions/foxinsocks.py for an example extension implementation. + """ self._load_extensions_under_path(self.path) -- cgit From 699f82e7e14146feb272d61a98b89ad53c93bf08 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Mon, 28 Mar 2011 17:11:16 -0700 Subject: Yet more docstring fixes --- nova/virt/driver.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nova') diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 55281b741..c6c28b2ba 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -77,6 +77,7 @@ class ComputeDriver(object): 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. + """ raise NotImplementedError() @@ -94,6 +95,7 @@ class ComputeDriver(object): :address: ? :username: ? :password: ? + """ raise NotImplementedError() -- cgit From 0e81c4175cad97359e848c993efd9a91eb981174 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Mon, 28 Mar 2011 21:22:53 -0400 Subject: Pass along the nbd flags although we dont support it just yet --- nova/virt/disk.py | 13 ++++++++----- nova/virt/libvirt_conn.py | 5 +++-- 2 files changed, 11 insertions(+), 7 deletions(-) (limited to 'nova') diff --git a/nova/virt/disk.py b/nova/virt/disk.py index 15cd49789..11a7a969e 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -116,13 +116,14 @@ def inject_data(image, key=None, net=None, partition=None, nbd=False): _unlink_device(device, nbd) -def setup_container(image, container_dir=None): +def setup_container(image, container_dir=None, nbd=False): """Setup the LXC container It will mount the loopback image to the container directory in order - to create the root filesystem for the container + to create the root filesystem for the container. + + LXC does not support qcow2 images yet. """ - nbd = "False" device = _link_device(image, nbd) out, err = utils.execute('sudo', 'mount', device, container_dir) if err: @@ -131,11 +132,13 @@ def setup_container(image, container_dir=None): _unlink_device(device, nbd) -def destroy_container(target, instance): +def destroy_container(target, instance, nbd=False): """Destroy the container once it terminates It will umount the container that is mounted, try to find the loopback device associated with the container and delete it. + + LXC does not support qcow2 images yet. """ try: container_dir = '%s/rootfs' % target @@ -145,7 +148,7 @@ def destroy_container(target, instance): for loop in out.splitlines(): if instance['name'] in loop: device = loop.split(loop, ':') - utils.execute('sudo', 'losetup', '--detach', device) + _unlink_device(device, nbd) def _link_device(image, nbd): diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 66d0d195b..af2dac9e8 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -342,7 +342,7 @@ class LibvirtConnection(driver.ComputeDriver): LOG.info(_('instance %(instance_name)s: deleting instance files' ' %(target)s') % locals()) if FLAGS.libvirt_type == 'lxc': - disk.destroy_container(target, instance) + disk.destroy_container(target, instance,nbd=FLAGS.use_cow_images) if os.path.exists(target): shutil.rmtree(target) @@ -797,7 +797,8 @@ class LibvirtConnection(driver.ComputeDriver): if FLAGS.libvirt_type == 'lxc': disk.setup_container(basepath('disk'), - container_dir=container_dir) + container_dir=container_dir, + nbd=FLAGS.use_cow_images) except Exception as e: # This could be a windows image, or a vmdk format disk LOG.warn(_('instance %(inst_name)s: ignoring error injecting' -- cgit From e5b0f3921331b5c0acbe321b00e2a9fa8d27be4e Mon Sep 17 00:00:00 2001 From: Muneyuki Noguchi Date: Tue, 29 Mar 2011 10:40:48 +0900 Subject: Add remove_volume to compute API. --- nova/compute/api.py | 7 +++++++ nova/compute/manager.py | 26 ++++++++++++++------------ 2 files changed, 21 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 266cbe677..0cdb8f4b7 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -668,6 +668,13 @@ class API(base.Base): "volume_id": volume_id}}) return instance + def remove_volume(self, context, volume_id, host): + """Remove volume on specified compute host.""" + rpc.call(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "remove_volume", + "args": {'volume_id': volume_id}}) + def associate_floating_ip(self, context, instance_id, address): instance = self.get(context, instance_id) self.network_api.associate_floating_ip(context, diff --git a/nova/compute/manager.py b/nova/compute/manager.py index aa612b137..bbd1fed1a 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -46,6 +46,7 @@ import functools from eventlet import greenthread +from nova import compute from nova import exception from nova import flags from nova import log as logging @@ -772,6 +773,14 @@ class ComputeManager(manager.SchedulerDependentManager): self.db.volume_detached(context, volume_id) return True + def remove_volume(self, context, volume_id): + """Remove volume on compute host. + + :param context: security context + :param volume_id: nova.db.sqlalchemy.models.Volume.id + """ + self.volume_manager.remove_compute_volume(context, volume_id) + @exception.wrap_exception def compare_cpu(self, context, cpu_info): """Checks the host cpu is compatible to a cpu given by xml. @@ -1018,22 +1027,15 @@ class ComputeManager(manager.SchedulerDependentManager): 'state': power_state.RUNNING, 'host': host}) + if dest: + # NOTE(noguchimn): We set image_service here + # not to import an image service object. + compute_api = compute.API(image_service=1) for volume in instance_ref['volumes']: volume_id = volume['id'] self.db.volume_update(ctxt, volume_id, {'status': 'in-use'}) if dest: - topic = self.db.queue_get_for(ctxt, FLAGS.compute_topic, dest) - rpc.call(ctxt, topic, - {"method": "restore_volume_state", - "args": {'volume_id': volume_id}}) - - def restore_volume_state(self, context, volume_id): - """Restore volume state on migration failure. - - :param context: security context - :param volume_id: nova.db.sqlalchemy.models.Volume.id - """ - self.volume_manager.remove_compute_volume(context, volume_id) + compute_api.remove_volume(ctxt, volume_id, dest) def periodic_tasks(self, context=None): """Tasks to be run at a periodic interval.""" -- cgit From 5c74862a08a82b7db3e11fbcbec63293ea903e00 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Mon, 28 Mar 2011 23:11:42 -0700 Subject: make all openstack status uppercase --- nova/api/openstack/views/servers.py | 4 ++-- nova/tests/api/openstack/test_servers.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 4e7f62eb3..6b471a0f4 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -72,12 +72,12 @@ class ViewBuilder(object): 'id': int(inst['id']), 'name': inst['display_name'], 'addresses': self.addresses_builder.build(inst), - 'status': power_mapping[inst.get('state')]} + 'status': power_mapping[inst.get('state')].upper()} ctxt = nova.context.get_admin_context() compute_api = nova.compute.API() if compute_api.has_finished_migration(ctxt, inst['id']): - inst_dict['status'] = 'resize-confirm' + inst_dict['status'] = 'resize-confirm'.upper() # Return the metadata as a dictionary metadata = {} diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 989385a8c..27cbd28c1 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -738,7 +738,7 @@ class ServersTest(test.TestCase): fake_migration_get) res = req.get_response(fakes.wsgi_app()) body = json.loads(res.body) - self.assertEqual(body['server']['status'], 'resize-confirm') + self.assertEqual(body['server']['status'], 'RESIZE-CONFIRM') def test_confirm_resize_server(self): req = self.webreq('/1/action', 'POST', dict(confirmResize=None)) -- cgit From 73f05da6400fe7f4324cf98c7d0706fb68a62870 Mon Sep 17 00:00:00 2001 From: Armando Migliaccio Date: Tue, 29 Mar 2011 11:20:16 +0100 Subject: sorted pep8 errors that were introduced during previous fixes --- nova/virt/xenapi/vmops.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index a9514bd61..c96c35a6e 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -430,7 +430,7 @@ class VMOps(object): """Set the root/admin password on the VM instance. This is done via an agent running on the VM. Communication between nova - and the agent is done via writing xenstore records. Since communication + and the agent is done via writing xenstore records. Since communication is done over the XenAPI RPC calls, we need to encrypt the password. We're using a simple Diffie-Hellman class instead of the more advanced one in M2Crypto for compatibility with the agent code. @@ -467,12 +467,12 @@ class VMOps(object): return resp_dict['message'] def inject_file(self, instance, path, contents): - """Write a file to the VM instance. + """Write a file to the VM instance. The path to which it is to be written and the contents of the file need to be supplied; both will be base64-encoded to prevent errors with non-ASCII characters being transmitted. If the agent does not - support file injection, or the user has disabled it, a + support file injection, or the user has disabled it, a NotImplementedError will be raised. """ -- cgit From ad6735df0de8676768353516eee4af62af2c993c Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Tue, 29 Mar 2011 08:56:42 -0400 Subject: Catch the error that mount might through a bit better --- nova/virt/disk.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/virt/disk.py b/nova/virt/disk.py index 11a7a969e..8adef744f 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -124,11 +124,11 @@ def setup_container(image, container_dir=None, nbd=False): LXC does not support qcow2 images yet. """ - device = _link_device(image, nbd) - out, err = utils.execute('sudo', 'mount', device, container_dir) - if err: - raise exception.Error(_('Failed to mount filesystem: %s') - % err) + try: + device = _link_device(image, nbd) + utils.execute('sudo', 'mount', device, container_dir) + except Exception, exn: + LOG.exception(_('Failed to mount filesystem: %s'), exn) _unlink_device(device, nbd) -- cgit From c4c9c0e5b70305ac06494bde35fcd18fdf60798e Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 29 Mar 2011 09:51:02 -0400 Subject: Tweaking docstrings just in case. --- nova/api/openstack/images.py | 3 +-- nova/api/openstack/views/images.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index ad748b4ca..d672e3a0e 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -32,8 +32,7 @@ FLAGS = flags.FLAGS class Controller(wsgi.Controller): - """Base `wsgi.Controller` for retrieving and displaying images in the - OpenStack API. Version-inspecific code goes here.""" + """Base `wsgi.Controller` for retrieving/displaying images.""" _serialization_metadata = { 'application/xml': { diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index 8f5568f6c..3807fa95f 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -19,8 +19,7 @@ import os.path class ViewBuilder(object): - """Base class for generating responses to OpenStack API requests for - information about images.""" + """Base class for generating responses to OpenStack API image requests.""" def __init__(self, base_url): """Initialize new `ViewBuilder`.""" -- cgit From beec33e8dbffbe3ea02eccec4952705698db377a Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Tue, 29 Mar 2011 10:01:54 -0400 Subject: Fix pep8 error --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index c4d41881d..ae3a967a4 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -347,7 +347,7 @@ class LibvirtConnection(driver.ComputeDriver): LOG.info(_('instance %(instance_name)s: deleting instance files' ' %(target)s') % locals()) if FLAGS.libvirt_type == 'lxc': - disk.destroy_container(target, instance,nbd=FLAGS.use_cow_images) + disk.destroy_container(target, instance, nbd=FLAGS.use_cow_images) if os.path.exists(target): shutil.rmtree(target) -- cgit From 793de5cef9fb539a4fb3ba976d83adde38a589c1 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 29 Mar 2011 10:40:35 -0400 Subject: adding more tests; making name checks more robust --- nova/api/openstack/servers.py | 11 +++++++++-- nova/tests/api/openstack/test_servers.py | 26 +++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index e9bc0a797..29c491716 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -250,8 +250,15 @@ class Controller(wsgi.Controller): if 'name' in inst_dict['server']: name = inst_dict['server']['name'] - if not isinstance(name, basestring) or name == '': - return exc.HTTPBadRequest() + if not isinstance(name, basestring): + msg = _("Server name is not a string or unicode") + return exc.HTTPBadRequest(msg) + + name = name.strip() + + if name == '': + msg = _("Server name is an empty string") + return exc.HTTPBadRequest(msg) update_dict['display_name'] = name diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 506b24b8b..ef4cf55b8 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -448,7 +448,31 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 422) - def test_update_bad_name(self): + def test_update_nonstring_name(self): + """ Confirm that update is filtering params """ + inst_dict = dict(name=12, adminPass='bacon') + self.body = json.dumps(dict(server=inst_dict)) + + req = webob.Request.blank('/v1.0/servers/1') + req.method = 'PUT' + req.content_type = "application/json" + req.body = self.body + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_update_whitespace_name(self): + """ Confirm that update is filtering params """ + inst_dict = dict(name=' ', adminPass='bacon') + self.body = json.dumps(dict(server=inst_dict)) + + req = webob.Request.blank('/v1.0/servers/1') + req.method = 'PUT' + req.content_type = "application/json" + req.body = self.body + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + def test_update_null_name(self): """ Confirm that update is filtering params """ inst_dict = dict(name='', adminPass='bacon') self.body = json.dumps(dict(server=inst_dict)) -- cgit From c512bae72859b8583731886011e8f9a4310d69f8 Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Tue, 29 Mar 2011 10:53:44 -0400 Subject: use informative error messages --- nova/api/openstack/servers.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index aaae17a39..31b9bc2f1 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -597,10 +597,12 @@ class ControllerV11(Controller): context = req.environ['nova.context'] if (not 'changePassword' in input_dict or not 'adminPass' in input_dict['changePassword']): - return exc.HTTPBadRequest() + msg = _("No adminPass was specified") + return exc.HTTPBadRequest(msg) password = input_dict['changePassword']['adminPass'] if not isinstance(password, basestring) or password == '': - return exc.HTTPBadRequest() + msg = _("Invalid adminPass") + return exc.HTTPBadRequest(msg) self.compute_api.set_admin_password(context, id, password) return exc.HTTPAccepted() -- cgit From f624d2e35dab0d87a289a346999c0cb01ed0aa55 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 29 Mar 2011 11:11:57 -0400 Subject: adding server name validation to create method; adding tests --- nova/api/openstack/servers.py | 36 ++++++++++------- nova/tests/api/openstack/test_servers.py | 68 ++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 14 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 29c491716..d564b37c1 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -150,6 +150,15 @@ class Controller(wsgi.Controller): injected_files = self._get_injected_files(personality) flavor_id = self._flavor_id_from_req_data(env) + + if not 'name' in env['server']: + msg = _("Server name is not defined") + return exc.HTTPBadRequest(msg) + + name = env['server']['name'] + self._validate_server_name(name) + name = name.strip() + try: (inst,) = self.compute_api.create( context, @@ -157,8 +166,8 @@ class Controller(wsgi.Controller): image_id, kernel_id=kernel_id, ramdisk_id=ramdisk_id, - display_name=env['server']['name'], - display_description=env['server']['name'], + display_name=name, + display_description=name, key_name=key_name, key_data=key_data, metadata=metadata, @@ -249,18 +258,8 @@ class Controller(wsgi.Controller): if 'name' in inst_dict['server']: name = inst_dict['server']['name'] - - if not isinstance(name, basestring): - msg = _("Server name is not a string or unicode") - return exc.HTTPBadRequest(msg) - - name = name.strip() - - if name == '': - msg = _("Server name is an empty string") - return exc.HTTPBadRequest(msg) - - update_dict['display_name'] = name + self._validate_server_name(name) + update_dict['display_name'] = name.strip() self._parse_update(ctxt, id, inst_dict, update_dict) @@ -271,6 +270,15 @@ class Controller(wsgi.Controller): return exc.HTTPNoContent() + def _validate_server_name(self, value): + if not isinstance(value, basestring): + msg = _("Server name is not a string or unicode") + raise exc.HTTPBadRequest(msg) + + if value.strip() == '': + msg = _("Server name is an empty string") + raise exc.HTTPBadRequest(msg) + def _parse_update(self, context, id, inst_dict, update_dict): pass diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index ef4cf55b8..130b8c5d5 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -392,6 +392,74 @@ class ServersTest(test.TestCase): fakes.stub_out_key_pair_funcs(self.stubs, have_key_pair=False) self._test_create_instance_helper() + def test_create_instance_no_name(self): + self._setup_for_create_instance() + + body = { + 'server': { + '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_nonstring_name(self): + self._setup_for_create_instance() + + body = { + 'server': { + 'name': 12, + '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_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_v11(self): self._setup_for_create_instance() -- cgit From a070b8861ccc01b485b109855f44a36cd6ebdbd6 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 29 Mar 2011 11:16:42 -0400 Subject: pep8 --- nova/api/openstack/servers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index d564b37c1..4400a68a1 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -154,7 +154,7 @@ class Controller(wsgi.Controller): if not 'name' in env['server']: msg = _("Server name is not defined") return exc.HTTPBadRequest(msg) - + name = env['server']['name'] self._validate_server_name(name) name = name.strip() -- cgit From d1ef69edb8da18c5c7e56b6006e22022d55d6664 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 29 Mar 2011 11:41:33 -0400 Subject: adding code to explicitly set the content-type in versions controller; updating test --- nova/api/openstack/versions.py | 10 ++++++++-- nova/tests/api/openstack/test_versions.py | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 33f1dd628..3f9d91934 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -15,8 +15,8 @@ # License for the specific language governing permissions and limitations # under the License. +import webob import webob.dec -import webob.exc from nova import wsgi import nova.api.openstack.views.versions @@ -51,4 +51,10 @@ class Versions(wsgi.Application): } content_type = req.best_match_content_type() - return wsgi.Serializer(metadata).serialize(response, content_type) + body = wsgi.Serializer(metadata).serialize(response, content_type) + + response = webob.Response() + response.content_type = content_type + response.body = body + + return response diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index ebb59a9a6..ee922a8d3 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -34,8 +34,10 @@ class VersionsTest(test.TestCase): def test_get_version_list(self): req = webob.Request.blank('/') + req.accept = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) + self.assertEqual(res.content_type, "application/json") versions = json.loads(res.body)["versions"] expected = [ { -- cgit From 343b969f7d790282b7b76bcb23b9d0d578d716b9 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 29 Mar 2011 12:04:43 -0400 Subject: adding xml test case --- nova/tests/api/openstack/test_versions.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index ee922a8d3..2640a4ddb 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -63,6 +63,30 @@ class VersionsTest(test.TestCase): ] self.assertEqual(versions, expected) + def test_get_version_list_xml(self): + req = webob.Request.blank('/') + req.accept = "application/xml" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + self.assertEqual(res.content_type, "application/xml") + + expected = """ + + + + + + + + + + + """.replace(" ", "").replace("\n", "") + + actual = res.body.replace(" ", "").replace("\n", "") + + self.assertEqual(expected, actual) + def test_view_builder(self): base_url = "http://example.org/" -- cgit From 07d985f8db30863bb9171e14fccbdb51d7b35f11 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 29 Mar 2011 12:06:52 -0400 Subject: Switch string concat style. --- nova/image/glance.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/image/glance.py b/nova/image/glance.py index d8f51429e..fdf468594 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -238,5 +238,5 @@ def _parse_glance_iso8601_timestamp(timestamp): except ValueError: pass - raise ValueError(_("""%(timestamp)s does not follow any of the \ -signatures: %(ISO_FORMATS)s""") % locals()) + raise ValueError(_("%(timestamp)s does not follow any of the " + "signatures: %(ISO_FORMATS)s") % locals()) -- cgit From 2f89d5541aa11b8654b197ffe24d3fd13e945da6 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 29 Mar 2011 12:12:26 -0400 Subject: Import order. --- nova/api/openstack/images.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index d672e3a0e..e77100d7b 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -13,9 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. -import webob.exc import datetime +import webob.exc + from nova import compute from nova import exception from nova import flags -- cgit From 3b8aa1fed7cd6d1deb580eec0af283947060c04d Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Tue, 29 Mar 2011 21:26:17 +0400 Subject: Added checks that exists at least one network marked inhected in libvirt and xenapi --- nova/virt/libvirt_conn.py | 5 ++++- nova/virt/xenapi/vm_utils.py | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 80eb64f3c..7dd09813d 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -803,6 +803,7 @@ class LibvirtConnection(driver.ComputeDriver): nets = [] ifc_template = open(FLAGS.injected_network_template).read() ifc_num = -1 + have_injected_networks = False admin_context = context.get_admin_context() for (network_ref, mapping) in network_info: ifc_num += 1 @@ -810,6 +811,7 @@ class LibvirtConnection(driver.ComputeDriver): if not 'injected' in network_ref: continue + have_injected_networks = True address = mapping['ips'][0]['ip'] address_v6 = None if FLAGS.use_ipv6: @@ -825,7 +827,8 @@ class LibvirtConnection(driver.ComputeDriver): 'netmask_v6': network_ref['netmask_v6']} nets.append(net_info) - net = str(Template(ifc_template, + if have_injected_networks: + net = str(Template(ifc_template, searchList=[{'interfaces': nets, 'use_ipv6': FLAGS.use_ipv6}])) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 2288ea8a5..18c29134d 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -1108,11 +1108,13 @@ def _prepare_injectables(inst, networks_info): if networks_info: ifc_num = -1 interfaces_info = [] + have_injected_networks = False for (network_ref, info) in networks_info: ifc_num += 1 if not network_ref['injected']: continue + have_injected_networks = True ip_v4 = ip_v6 = None if 'ips' in info and len(info['ips']) > 0: ip_v4 = info['ips'][0] @@ -1131,7 +1133,9 @@ def _prepare_injectables(inst, networks_info): 'gateway_v6': ip_v6 and ip_v6['gateway'] or '', 'use_ipv6': FLAGS.use_ipv6} interfaces_info.append(interface_info) - net = str(template(template_data, + + if have_injected_networks: + net = str(template(template_data, searchList=[{'interfaces': interfaces_info, 'use_ipv6': FLAGS.use_ipv6}])) return key, net -- cgit From 2af6fb2a4d3659e9882a6f6d1c8e71bc8f040aba Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 29 Mar 2011 14:56:18 -0400 Subject: Added content_type to OSAPI faults. --- nova/api/openstack/faults.py | 1 + nova/tests/api/openstack/test_faults.py | 122 ++++++++++++++++++++++++++------ 2 files changed, 103 insertions(+), 20 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py index 0e9c4b26f..940bd8771 100644 --- a/nova/api/openstack/faults.py +++ b/nova/api/openstack/faults.py @@ -60,6 +60,7 @@ class Fault(webob.exc.HTTPException): serializer = wsgi.Serializer(metadata) content_type = req.best_match_content_type() self.wrapped_exc.body = serializer.serialize(fault_data, content_type) + self.wrapped_exc.content_type = content_type return self.wrapped_exc diff --git a/nova/tests/api/openstack/test_faults.py b/nova/tests/api/openstack/test_faults.py index 7667753f4..0cda542de 100644 --- a/nova/tests/api/openstack/test_faults.py +++ b/nova/tests/api/openstack/test_faults.py @@ -15,6 +15,8 @@ # License for the specific language governing permissions and limitations # under the License. +import json + import webob import webob.dec import webob.exc @@ -24,35 +26,115 @@ from nova.api.openstack import faults class TestFaults(test.TestCase): + """Tests covering `nova.api.openstack.faults:Fault` class.""" - def test_fault_parts(self): - req = webob.Request.blank('/.xml') - f = faults.Fault(webob.exc.HTTPBadRequest(explanation='scram')) - resp = req.get_response(f) + def _prepare_xml(self, xml_string): + """Remove characters from string which hinder XML equality testing.""" + xml_string = xml_string.replace(" ", "") + xml_string = xml_string.replace("\n", "") + xml_string = xml_string.replace("\t", "") + return xml_string - first_two_words = resp.body.strip().split()[:2] - self.assertEqual(first_two_words, ['']) - body_without_spaces = ''.join(resp.body.split()) - self.assertTrue('scram' in body_without_spaces) + def test_400_fault_xml(self): + """Test fault serialized to XML via file-extension and/or header.""" + requests = [ + webob.Request.blank('/.xml'), + webob.Request.blank('/', headers={"Accept": "application/xml"}), + ] - def test_retry_header(self): - req = webob.Request.blank('/.xml') - exc = webob.exc.HTTPRequestEntityTooLarge(explanation='sorry', - headers={'Retry-After': 4}) - f = faults.Fault(exc) - resp = req.get_response(f) - first_two_words = resp.body.strip().split()[:2] - self.assertEqual(first_two_words, ['']) - body_sans_spaces = ''.join(resp.body.split()) - self.assertTrue('sorry' in body_sans_spaces) - self.assertTrue('4' in body_sans_spaces) - self.assertEqual(resp.headers['Retry-After'], 4) + for request in requests: + fault = faults.Fault(webob.exc.HTTPBadRequest(explanation='scram')) + response = request.get_response(fault) + + expected = self._prepare_xml(""" + + scram + + """) + actual = self._prepare_xml(response.body) + + self.assertEqual(response.content_type, "application/xml") + self.assertEqual(expected, actual) + + def test_400_fault_json(self): + """Test fault serialized to JSON via file-extension and/or header.""" + requests = [ + webob.Request.blank('/.json'), + webob.Request.blank('/', headers={"Accept": "application/json"}), + ] + + for request in requests: + fault = faults.Fault(webob.exc.HTTPBadRequest(explanation='scram')) + response = request.get_response(fault) + + expected = { + "badRequest": { + "message": "scram", + "code": 400, + }, + } + actual = json.loads(response.body) + + self.assertEqual(response.content_type, "application/json") + self.assertEqual(expected, actual) + + def test_413_fault_xml(self): + requests = [ + webob.Request.blank('/.xml'), + webob.Request.blank('/', headers={"Accept": "application/xml"}), + ] + + for request in requests: + exc = webob.exc.HTTPRequestEntityTooLarge + fault = faults.Fault(exc(explanation='sorry', + headers={'Retry-After': 4})) + response = request.get_response(fault) + + expected = self._prepare_xml(""" + + sorry + 4 + + """) + actual = self._prepare_xml(response.body) + + self.assertEqual(expected, actual) + self.assertEqual(response.content_type, "application/xml") + self.assertEqual(response.headers['Retry-After'], 4) + + def test_413_fault_json(self): + """Test fault serialized to JSON via file-extension and/or header.""" + requests = [ + webob.Request.blank('/.json'), + webob.Request.blank('/', headers={"Accept": "application/json"}), + ] + + for request in requests: + exc = webob.exc.HTTPRequestEntityTooLarge + fault = faults.Fault(exc(explanation='sorry', + headers={'Retry-After': 4})) + response = request.get_response(fault) + + expected = { + "overLimit": { + "message": "sorry", + "code": 413, + "retryAfter": 4, + }, + } + actual = json.loads(response.body) + + self.assertEqual(response.content_type, "application/json") + self.assertEqual(expected, actual) def test_raise(self): + """Ensure the ability to raise exceptions in WSGI-ified methods.""" @webob.dec.wsgify def raiser(req): raise faults.Fault(webob.exc.HTTPNotFound(explanation='whut?')) + req = webob.Request.blank('/.xml') resp = req.get_response(raiser) + self.assertEqual(resp.content_type, "application/xml") self.assertEqual(resp.status_int, 404) self.assertTrue('whut?' in resp.body) -- cgit From 8f4176f289142e48d4a2c584ad1ce270dfa53d82 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Tue, 29 Mar 2011 14:56:46 -0400 Subject: Fix up docstring --- nova/virt/disk.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/disk.py b/nova/virt/disk.py index 8adef744f..ddea1a1f7 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -117,7 +117,7 @@ def inject_data(image, key=None, net=None, partition=None, nbd=False): def setup_container(image, container_dir=None, nbd=False): - """Setup the LXC container + """Setup the LXC container. It will mount the loopback image to the container directory in order to create the root filesystem for the container. @@ -133,7 +133,7 @@ def setup_container(image, container_dir=None, nbd=False): def destroy_container(target, instance, nbd=False): - """Destroy the container once it terminates + """Destroy the container once it terminates. It will umount the container that is mounted, try to find the loopback device associated with the container and delete it. -- cgit From b161ae983edbd9e8c302907e8951075546eafc48 Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Tue, 29 Mar 2011 23:30:06 +0400 Subject: style fix --- nova/virt/libvirt_conn.py | 4 ++-- nova/virt/xenapi/vm_utils.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 7dd09813d..aaa0fe8d3 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -829,8 +829,8 @@ class LibvirtConnection(driver.ComputeDriver): if have_injected_networks: net = str(Template(ifc_template, - searchList=[{'interfaces': nets, - 'use_ipv6': FLAGS.use_ipv6}])) + searchList=[{'interfaces': nets, + 'use_ipv6': FLAGS.use_ipv6}])) if key or net: inst_name = inst['name'] diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 18c29134d..d07d60800 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -1136,6 +1136,6 @@ def _prepare_injectables(inst, networks_info): if have_injected_networks: net = str(template(template_data, - searchList=[{'interfaces': interfaces_info, - 'use_ipv6': FLAGS.use_ipv6}])) + searchList=[{'interfaces': interfaces_info, + 'use_ipv6': FLAGS.use_ipv6}])) return key, net -- cgit From 05a654211ab902cbb5b1b345dd3285efb1c6bf71 Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Tue, 29 Mar 2011 15:48:02 -0400 Subject: Style fixes --- nova/tests/test_virt.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index e6beb8e2e..958c8e3e2 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -237,13 +237,13 @@ class LibvirtConnTestCase(test.TestCase): network_ref = db.project_get_network(context.get_admin_context(), self.project.id) - fixed_ip = {'address': self.test_ip, - 'network_id': network_ref['id']} + fixed_ip = {'address': self.test_ip, + 'network_id': network_ref['id']} ctxt = context.get_admin_context() fixed_ip_ref = db.fixed_ip_create(ctxt, fixed_ip) db.fixed_ip_update(ctxt, self.test_ip, - {'allocated': True, + {'allocated': True, 'instance_id': instance_ref['id']}) self.flags(libvirt_type='lxc') -- cgit From 3e9b5977137c430d218ec8c00e286b691ea8367d Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 29 Mar 2011 12:54:35 -0700 Subject: use manager pattern for auth token proxy --- nova/flags.py | 2 +- nova/vnc/auth.py | 103 ++++++++++++++++++++++++++++++++---------------------- nova/vnc/proxy.py | 3 +- 3 files changed, 64 insertions(+), 44 deletions(-) (limited to 'nova') diff --git a/nova/flags.py b/nova/flags.py index ba543f46d..b5c0cd380 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -281,7 +281,7 @@ DEFINE_string('ajax_console_proxy_url', in the form "http://127.0.0.1:8000"') DEFINE_string('ajax_console_proxy_port', 8000, 'port that ajax_console_proxy binds') -DEFINE_string('vnc_console_proxy_topic', 'vnc_proxy', +DEFINE_string('vnc_console_proxy_topic', 'vncproxy', 'the topic vnc proxy nodes listen on') DEFINE_string('vnc_console_proxy_url', 'http://127.0.0.1:6080', diff --git a/nova/vnc/auth.py b/nova/vnc/auth.py index dff9b376f..105b68fe2 100644 --- a/nova/vnc/auth.py +++ b/nova/vnc/auth.py @@ -1,9 +1,7 @@ #!/usr/bin/env python -# pylint: disable-msg=C0103 # 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 (c) 2010 Openstack, LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,8 +24,10 @@ import webob from webob import Request +from nova import context from nova import flags from nova import log as logging +from nova import manager from nova import rpc from nova import utils from nova import wsgi @@ -38,12 +38,24 @@ LOG = logging.getLogger('nova.vnc-proxy') FLAGS = flags.FLAGS -class NovaAuthMiddleware(object): +class VNCNovaAuthMiddleware(object): """Implementation of Middleware to Handle Nova Auth.""" def __init__(self, app): self.app = app - self.register_listeners() + self.token_cache = {} + utils.LoopingCall(self._delete_expired_tokens).start(1) + + def get_token_info(self, token): + if token in self.token_cache: + return self.token_cache[token] + + rval = rpc.call(context.get_admin_context(), + FLAGS.vnc_console_proxy_topic, + {"method": "check_token", "args": {'token': token}}) + if rval: + self.token_cache[token] = rval + return rval @webob.dec.wsgify def __call__(self, req): @@ -55,49 +67,27 @@ class NovaAuthMiddleware(object): if 'token' in auth_params: token = auth_params['token'][0] - if not token in self.tokens: + connection_info = self.get_token_info(token) + if not connection_info: LOG.audit(_("Unauthorized Access: (%s)"), req.environ) return webob.exc.HTTPForbidden(detail='Unauthorized') if req.path == vnc.proxy.WS_ENDPOINT: - req.environ['vnc_host'] = self.tokens[token]['args']['host'] - req.environ['vnc_port'] = int(self.tokens[token]['args']['port']) + req.environ['vnc_host'] = connection_info['host'] + req.environ['vnc_port'] = int(connection_info['port']) return req.get_response(self.app) - def register_listeners(self): - middleware = self - middleware.tokens = {} - - class TopicProxy(): - @staticmethod - def authorize_vnc_console(context, **kwargs): - data = kwargs - token = kwargs['token'] - LOG.audit(_("Received Token: %s)"), token) - middleware.tokens[token] = \ - {'args': kwargs, 'last_activity_at': time.time()} - - def delete_expired_tokens(): - now = time.time() - to_delete = [] - for k, v in middleware.tokens.items(): - if now - v['last_activity_at'] > FLAGS.vnc_token_ttl: - to_delete.append(k) - - for k in to_delete: - LOG.audit(_("Deleting Token: %s)"), k) - del middleware.tokens[k] - - conn = rpc.Connection.instance(new=True) - consumer = rpc.TopicAdapterConsumer( - connection=conn, - proxy=TopicProxy, - topic=FLAGS.vnc_console_proxy_topic) - - utils.LoopingCall(consumer.fetch, - enable_callbacks=True).start(0.1) - utils.LoopingCall(delete_expired_tokens).start(1) + def _delete_expired_tokens(self): + now = time.time() + to_delete = [] + for k, v in self.token_cache.items(): + if now - v['last_activity_at'] > FLAGS.vnc_token_ttl: + to_delete.append(k) + + for k in to_delete: + del self.token_cache[k] + class LoggingMiddleware(object): @@ -112,3 +102,34 @@ class LoggingMiddleware(object): LOG.info(_("Received Request: %s"), req.url) return req.get_response(self.app) + + +class VNCProxyAuthManager(manager.Manager): + """Manages token based authentication.""" + + def __init__(self, scheduler_driver=None, *args, **kwargs): + super(VNCProxyAuthManager, self).__init__(*args, **kwargs) + self.tokens = {} + utils.LoopingCall(self._delete_expired_tokens).start(1) + + def authorize_vnc_console(self, context, token, host, port): + self.tokens[token] = {'host': host, + 'port': port, + 'last_activity_at': time.time()} + LOG.audit(_("Received Token: %s, %s)"), token, self.tokens[token]) + + def check_token(self, context, token): + LOG.audit(_("Checking Token: %s, %s)"), token, (token in self.tokens)) + if token in self.tokens: + return self.tokens[token] + + def _delete_expired_tokens(self): + now = time.time() + to_delete = [] + for k, v in self.tokens.items(): + if now - v['last_activity_at'] > FLAGS.vnc_token_ttl: + to_delete.append(k) + + for k in to_delete: + LOG.audit(_("Deleting Expired Token: %s)"), k) + del self.tokens[k] diff --git a/nova/vnc/proxy.py b/nova/vnc/proxy.py index 49379d9ae..c6e46396b 100644 --- a/nova/vnc/proxy.py +++ b/nova/vnc/proxy.py @@ -1,8 +1,7 @@ #!/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. +# Copyright (c) 2010 Openstack, LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); -- cgit From dbef49203985b4eea82912d010df7204ec68586c Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 29 Mar 2011 13:32:03 -0700 Subject: fix flag names --- nova/compute/api.py | 4 ++-- nova/flags.py | 6 +++--- nova/virt/libvirt.xml.template | 4 ++-- nova/virt/libvirt_conn.py | 2 +- nova/vnc/auth.py | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 5470f40dc..8f5803649 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -614,14 +614,14 @@ class API(base.Base): output = self._call_compute_message('get_vnc_console', context, instance_id) - rpc.cast(context, '%s' % FLAGS.vnc_console_proxy_topic, + rpc.cast(context, '%s' % FLAGS.vncproxy_topic, {'method': 'authorize_vnc_console', 'args': {'token': output['token'], 'host': output['host'], 'port': output['port']}}) return {'url': '%s/vnc_auto.html?token=%s&host=%s&port=%s' % ( - FLAGS.vnc_console_proxy_url, + FLAGS.vncproxy_url, output['token'], 'hostignore', 'portignore')} diff --git a/nova/flags.py b/nova/flags.py index b5c0cd380..b0c116f6b 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -281,13 +281,13 @@ DEFINE_string('ajax_console_proxy_url', in the form "http://127.0.0.1:8000"') DEFINE_string('ajax_console_proxy_port', 8000, 'port that ajax_console_proxy binds') -DEFINE_string('vnc_console_proxy_topic', 'vncproxy', +DEFINE_string('vncproxy_topic', 'vncproxy', 'the topic vnc proxy nodes listen on') -DEFINE_string('vnc_console_proxy_url', +DEFINE_string('vncproxy_url', 'http://127.0.0.1:6080', 'location of vnc console proxy, \ in the form "http://127.0.0.1:6080"') -DEFINE_string('vnc_server_host', '0.0.0.0', +DEFINE_string('vncserver_host', '0.0.0.0', 'the host interface on which vnc server should listen') DEFINE_bool('vnc_enabled', True, 'enable vnc related features') diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template index c492e488c..cf6bed530 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -104,8 +104,8 @@ -#if $getVar('vnc_server_host', False) - +#if $getVar('vncserver_host', False) + #end if diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 41adbfe27..ec09aca28 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -885,7 +885,7 @@ class LibvirtConnection(driver.ComputeDriver): 'nics': nics} if FLAGS.vnc_enabled: - xml_info['vnc_server_host'] = FLAGS.vnc_server_host + xml_info['vncserver_host'] = FLAGS.vncserver_host if not rescue: if instance['kernel_id']: xml_info['kernel'] = xml_info['basepath'] + "/kernel" diff --git a/nova/vnc/auth.py b/nova/vnc/auth.py index 105b68fe2..45f77dd59 100644 --- a/nova/vnc/auth.py +++ b/nova/vnc/auth.py @@ -51,7 +51,7 @@ class VNCNovaAuthMiddleware(object): return self.token_cache[token] rval = rpc.call(context.get_admin_context(), - FLAGS.vnc_console_proxy_topic, + FLAGS.vncproxy_topic, {"method": "check_token", "args": {'token': token}}) if rval: self.token_cache[token] = rval -- cgit From 3987547248e07719dbc63752100b695ef0be1a9c Mon Sep 17 00:00:00 2001 From: John Tran Date: Tue, 29 Mar 2011 13:44:38 -0700 Subject: initial unit test for describe images --- nova/tests/test_cloud.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'nova') diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 00803d0ad..791498f89 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -216,6 +216,16 @@ class CloudTestCase(test.TestCase): db.service_destroy(self.context, comp1['id']) db.service_destroy(self.context, comp2['id']) + def test_describe_images(self): + def fake_detail(meh, context): + return [{'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, + 'type':'machine'}}] + + self.stubs.Set(local.LocalImageService, 'detail', fake_detail) + result = self.cloud.describe_images(self.context) + result = result['imagesSet'][0] + self.assertEqual(result['imageId'], 'ami-00000001') + def test_console_output(self): instance_type = FLAGS.default_instance_type max_count = 1 -- cgit From cc7ba9a7a4ed8a38f217ad7f33fc33254f80ead7 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 29 Mar 2011 13:47:47 -0700 Subject: move flags per termie's feedback --- nova/flags.py | 10 ---------- nova/virt/libvirt_conn.py | 1 + nova/vnc/__init__.py | 35 +++++++++++++++++++++++++++++++++++ nova/vnc/auth.py | 3 ++- 4 files changed, 38 insertions(+), 11 deletions(-) (limited to 'nova') diff --git a/nova/flags.py b/nova/flags.py index b0c116f6b..f011ab383 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -281,16 +281,6 @@ DEFINE_string('ajax_console_proxy_url', in the form "http://127.0.0.1:8000"') DEFINE_string('ajax_console_proxy_port', 8000, 'port that ajax_console_proxy binds') -DEFINE_string('vncproxy_topic', 'vncproxy', - 'the topic vnc proxy nodes listen on') -DEFINE_string('vncproxy_url', - 'http://127.0.0.1:6080', - 'location of vnc console proxy, \ - in the form "http://127.0.0.1:6080"') -DEFINE_string('vncserver_host', '0.0.0.0', - 'the host interface on which vnc server should listen') -DEFINE_bool('vnc_enabled', True, - 'enable vnc related features') DEFINE_bool('verbose', False, 'show debug output') DEFINE_boolean('fake_rabbit', False, 'use a fake rabbit') DEFINE_bool('fake_network', False, diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index ec09aca28..8c948950b 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -57,6 +57,7 @@ from nova import flags from nova import log as logging #from nova import test from nova import utils +from nova import vnc from nova.auth import manager from nova.compute import instance_types from nova.compute import power_state diff --git a/nova/vnc/__init__.py b/nova/vnc/__init__.py index e69de29bb..1642295ec 100644 --- a/nova/vnc/__init__.py +++ b/nova/vnc/__init__.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 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. + +"""Module for VNC Proxying.""" + +from nova import flags + + +FLAGS = flags.FLAGS + +flags.DEFINE_string('vncproxy_topic', 'vncproxy', + 'the topic vnc proxy nodes listen on') +flags.DEFINE_string('vncproxy_url', + 'http://127.0.0.1:6080', + 'location of vnc console proxy, \ + in the form "http://127.0.0.1:6080"') +flags.DEFINE_string('vncserver_host', '0.0.0.0', + 'the host interface on which vnc server should listen') +flags.DEFINE_bool('vnc_enabled', True, + 'enable vnc related features') diff --git a/nova/vnc/auth.py b/nova/vnc/auth.py index 45f77dd59..fd8d351f1 100644 --- a/nova/vnc/auth.py +++ b/nova/vnc/auth.py @@ -89,8 +89,9 @@ class VNCNovaAuthMiddleware(object): del self.token_cache[k] - class LoggingMiddleware(object): + """Middleware for basic vnc-specific request logging.""" + def __init__(self, app): self.app = app -- cgit From 817572265871fd2cfd1252dd0cffb167f0e2ccdb Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 29 Mar 2011 13:49:49 -0700 Subject: move functions around --- nova/vnc/auth.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'nova') diff --git a/nova/vnc/auth.py b/nova/vnc/auth.py index fd8d351f1..86efffbe6 100644 --- a/nova/vnc/auth.py +++ b/nova/vnc/auth.py @@ -44,18 +44,7 @@ class VNCNovaAuthMiddleware(object): def __init__(self, app): self.app = app self.token_cache = {} - utils.LoopingCall(self._delete_expired_tokens).start(1) - - def get_token_info(self, token): - if token in self.token_cache: - return self.token_cache[token] - - rval = rpc.call(context.get_admin_context(), - FLAGS.vncproxy_topic, - {"method": "check_token", "args": {'token': token}}) - if rval: - self.token_cache[token] = rval - return rval + utils.LoopingCall(self.delete_expired_tokens).start(1) @webob.dec.wsgify def __call__(self, req): @@ -78,7 +67,18 @@ class VNCNovaAuthMiddleware(object): return req.get_response(self.app) - def _delete_expired_tokens(self): + def get_token_info(self, token): + if token in self.token_cache: + return self.token_cache[token] + + rval = rpc.call(context.get_admin_context(), + FLAGS.vncproxy_topic, + {"method": "check_token", "args": {'token': token}}) + if rval: + self.token_cache[token] = rval + return rval + + def delete_expired_tokens(self): now = time.time() to_delete = [] for k, v in self.token_cache.items(): -- cgit From 2c533ca5a4cd74907b3238ec65ab29c4f686dfcc Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 29 Mar 2011 13:55:01 -0700 Subject: switch cast to a call --- nova/compute/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 8f5803649..89d821d44 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -614,7 +614,7 @@ class API(base.Base): output = self._call_compute_message('get_vnc_console', context, instance_id) - rpc.cast(context, '%s' % FLAGS.vncproxy_topic, + rpc.call(context, '%s' % FLAGS.vncproxy_topic, {'method': 'authorize_vnc_console', 'args': {'token': output['token'], 'host': output['host'], -- cgit From 8cdad1ab8343eb038f119a92e28d77c731b61793 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 29 Mar 2011 13:59:46 -0700 Subject: add comment --- nova/compute/api.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 89d821d44..7977b07a2 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -620,6 +620,7 @@ class API(base.Base): 'host': output['host'], 'port': output['port']}}) + # hostignore and portignore are compatability params for noVNC return {'url': '%s/vnc_auto.html?token=%s&host=%s&port=%s' % ( FLAGS.vncproxy_url, output['token'], -- cgit From f5c072de1edddc4ddab89be8146a81d361397c45 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 29 Mar 2011 14:53:38 -0700 Subject: incorporate feedback from termie --- nova/api/openstack/servers.py | 2 +- nova/virt/libvirt_conn.py | 2 -- nova/vnc/__init__.py | 2 -- nova/vnc/auth.py | 4 ++-- nova/vnc/proxy.py | 4 ++-- 5 files changed, 5 insertions(+), 9 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 822342149..8170ab4a1 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -486,7 +486,7 @@ class Controller(wsgi.Controller): """Returns a url to an instance's ajaxterm console.""" try: self.compute_api.get_vnc_console(req.environ['nova.context'], - int(id)) + int(id)) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) return exc.HTTPAccepted() diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 8c948950b..502e61395 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -627,8 +627,6 @@ class LibvirtConnection(driver.ComputeDriver): return {'token': token, 'host': host, 'port': port} - _image_sems = {} # FIXME: why is this here? (anthony) - @staticmethod def _cache_image(fn, target, fname, cow=False, *args, **kwargs): """Wrapper for a method that creates an image that caches the image. diff --git a/nova/vnc/__init__.py b/nova/vnc/__init__.py index 1642295ec..2733c81d6 100644 --- a/nova/vnc/__init__.py +++ b/nova/vnc/__init__.py @@ -20,9 +20,7 @@ from nova import flags - FLAGS = flags.FLAGS - flags.DEFINE_string('vncproxy_topic', 'vncproxy', 'the topic vnc proxy nodes listen on') flags.DEFINE_string('vncproxy_url', diff --git a/nova/vnc/auth.py b/nova/vnc/auth.py index 86efffbe6..c7df13450 100644 --- a/nova/vnc/auth.py +++ b/nova/vnc/auth.py @@ -44,7 +44,7 @@ class VNCNovaAuthMiddleware(object): def __init__(self, app): self.app = app self.token_cache = {} - utils.LoopingCall(self.delete_expired_tokens).start(1) + utils.LoopingCall(self.delete_expired_cache_items).start(1) @webob.dec.wsgify def __call__(self, req): @@ -78,7 +78,7 @@ class VNCNovaAuthMiddleware(object): self.token_cache[token] = rval return rval - def delete_expired_tokens(self): + def delete_expired_cache_items(self): now = time.time() to_delete = [] for k, v in self.token_cache.items(): diff --git a/nova/vnc/proxy.py b/nova/vnc/proxy.py index c6e46396b..c4603803b 100644 --- a/nova/vnc/proxy.py +++ b/nova/vnc/proxy.py @@ -25,9 +25,9 @@ import eventlet from eventlet import wsgi from eventlet import websocket -from webob import Request import webob + WS_ENDPOINT = '/data' @@ -88,7 +88,7 @@ class WebsocketVNCProxy(object): _handle(environ, start_response) def __call__(self, environ, start_response): - req = Request(environ) + req = webob.Request(environ) if req.path == WS_ENDPOINT: return self.proxy_connection(environ, start_response) else: -- cgit From 3cdc2a90f0a7a066a231b0590aeb3d51d8ec699a Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 29 Mar 2011 14:58:10 -0700 Subject: add line --- nova/vnc/__init__.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/vnc/__init__.py b/nova/vnc/__init__.py index 2733c81d6..b5b00e44e 100644 --- a/nova/vnc/__init__.py +++ b/nova/vnc/__init__.py @@ -20,6 +20,7 @@ from nova import flags + FLAGS = flags.FLAGS flags.DEFINE_string('vncproxy_topic', 'vncproxy', 'the topic vnc proxy nodes listen on') -- cgit From 93a7a7b94a0d9e4100abb3a4309a3546ab532535 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 29 Mar 2011 15:22:16 -0700 Subject: clarify test --- nova/tests/test_compute.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 038824ef8..1b0f426d2 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -286,7 +286,7 @@ class ComputeTestCase(test.TestCase): console = self.compute.get_ajax_console(self.context, instance_id) - self.assert_(console) + self.assert_(set(['token', 'host', 'port']).issubset(console.keys())) self.compute.terminate_instance(self.context, instance_id) def test_vnc_console(self): -- cgit From e86f58261ee6acb8705106d3de61be0de488d94b Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 29 Mar 2011 15:37:45 -0700 Subject: Reverted extension loading tweaks --- nova/api/openstack/extensions.py | 116 +++---- nova/api/openstack/incubator/__init__.py | 4 +- nova/api/openstack/incubator/volumes.py | 355 +++++++++++++++++++++ nova/api/openstack/incubator/volumes/__init__.py | 18 -- .../incubator/volumes/volume_attachments.py | 184 ----------- nova/api/openstack/incubator/volumes/volumes.py | 161 ---------- .../api/openstack/incubator/volumes/volumes_ext.py | 55 ---- nova/tests/api/openstack/extensions/foxinsocks.py | 98 ++++++ .../openstack/extensions/foxinsocks/__init__.py | 19 -- .../openstack/extensions/foxinsocks/foxinsocks.py | 98 ------ 10 files changed, 505 insertions(+), 603 deletions(-) create mode 100644 nova/api/openstack/incubator/volumes.py delete mode 100644 nova/api/openstack/incubator/volumes/__init__.py delete mode 100644 nova/api/openstack/incubator/volumes/volume_attachments.py delete mode 100644 nova/api/openstack/incubator/volumes/volumes.py delete mode 100644 nova/api/openstack/incubator/volumes/volumes_ext.py create mode 100644 nova/tests/api/openstack/extensions/foxinsocks.py delete mode 100644 nova/tests/api/openstack/extensions/foxinsocks/__init__.py delete mode 100644 nova/tests/api/openstack/extensions/foxinsocks/foxinsocks.py (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index d0b4d378a..d1d479313 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -38,7 +38,10 @@ FLAGS = flags.FLAGS class ExtensionDescriptor(object): - """This is the base class that defines the contract for extensions.""" + """Base class that defines the contract for extensions. + + Note that you don't have to derive from this class to have a valid + extension; it is purely a convenience.""" def get_name(self): """The name of the extension. @@ -321,22 +324,37 @@ class ExtensionManager(object): resources = [] resources.append(ResourceExtension('extensions', ExtensionController(self))) - for _alias, ext in self.extensions.iteritems(): - resources.extend(ext.get_resources()) + for alias, ext in self.extensions.iteritems(): + try: + resources.extend(ext.get_resources()) + except AttributeError: + # NOTE(dprince): Extension aren't required to have resource + # extensions + pass return resources def get_actions(self): """Returns a list of ActionExtension objects.""" actions = [] - for _alias, ext in self.extensions.iteritems(): - actions.extend(ext.get_actions()) + for alias, ext in self.extensions.iteritems(): + try: + actions.extend(ext.get_actions()) + except AttributeError: + # NOTE(dprince): Extension aren't required to have action + # extensions + pass return actions def get_response_extensions(self): """Returns a list of ResponseExtension objects.""" response_exts = [] - for _alias, ext in self.extensions.iteritems(): - response_exts.extend(ext.get_response_extensions()) + for alias, ext in self.extensions.iteritems(): + try: + response_exts.extend(ext.get_response_extensions()) + except AttributeError: + # NOTE(dprince): Extension aren't required to have response + # extensions + pass return response_exts def _check_extension(self, extension): @@ -353,78 +371,42 @@ class ExtensionManager(object): def _load_all_extensions(self): """Load extensions from the configured path. - An extension consists of a directory of related files, with a class - that defines a class that inherits from ExtensionDescriptor. + Load extensions from the configured path. The extension name is + constructed from the module_name. If your extension module was named + widgets.py the extension class within that module should be + 'Widgets'. - Because of some oddities involving identically named modules, it's - probably best to name your file after the name of your extension, - rather than something likely to clash like 'extension.py'. - - The name of your directory should be the same as the alias your - extension uses, for everyone's sanity. + In addition, extensions are loaded from the 'incubator' directory. See nova/tests/api/openstack/extensions/foxinsocks.py for an example extension implementation. """ - self._load_extensions_under_path(self.path) + if os.path.exists(self.path): + self._load_all_extensions_from_path(self.path) incubator_path = os.path.join(os.path.dirname(__file__), "incubator") - self._load_extensions_under_path(incubator_path) - - def _load_extensions_under_path(self, path): - if not os.path.isdir(path): - LOG.warning(_('Extensions directory not found: %s') % path) - return - - LOG.debug(_('Looking for extensions in: %s') % path) - - for child in os.listdir(path): - child_path = os.path.join(path, child) - if not os.path.isdir(child_path): - continue - self._load_extension(child_path) - - def _load_extension(self, path): - if not os.path.isdir(path): - return + if os.path.exists(incubator_path): + self._load_all_extensions_from_path(incubator_path) + def _load_all_extensions_from_path(self, path): for f in os.listdir(path): + LOG.audit(_('Loading extension file: %s'), f) mod_name, file_ext = os.path.splitext(os.path.split(f)[-1]) - if mod_name.startswith('_'): - continue - if file_ext.lower() != '.py': - continue - ext_path = os.path.join(path, f) - if self.super_verbose: - LOG.debug(_('Checking extension file: %s'), ext_path) - - mod = imp.load_source(mod_name, ext_path) - for _name, cls in inspect.getmembers(mod): - try: - if not inspect.isclass(cls): - continue - - # NOTE(justinsb): It seems that python modules are odd. - # If you have two identically named modules, the classes - # from both are mixed in. So name your extension based - # on the alias, not 'extension.py'! - # TODO(justinsb): Any way to work around this? - - if self.super_verbose: - LOG.debug(_('Checking class: %s'), cls) - - if not ExtensionDescriptor in cls.__bases__: - if self.super_verbose: - LOG.debug(_('Not a ExtensionDescriptor: %s'), cls) - continue - - obj = cls() - self._add_extension(obj) - except AttributeError as ex: - LOG.exception(_("Exception loading extension: %s"), - unicode(ex)) + if file_ext.lower() == '.py' and not mod_name.startswith('_'): + mod = imp.load_source(mod_name, ext_path) + ext_name = mod_name[0].upper() + mod_name[1:] + new_ext_class = getattr(mod, ext_name, None) + if not new_ext_class: + LOG.warn(_('Did not find expected name ' + '"%(ext_name)s" in %(file)s'), + {'ext_name': ext_name, + 'file': ext_path}) + continue + new_ext = new_ext_class() + self._check_extension(new_ext) + self._add_extension(new_ext) def _add_extension(self, ext): alias = ext.get_alias() diff --git a/nova/api/openstack/incubator/__init__.py b/nova/api/openstack/incubator/__init__.py index cded38174..e115f1ab9 100644 --- a/nova/api/openstack/incubator/__init__.py +++ b/nova/api/openstack/incubator/__init__.py @@ -17,4 +17,6 @@ """Incubator contains extensions that are shipped with nova. -It can't be called 'extensions' because that causes namespacing problems.""" +It can't be called 'extensions' because that causes namespacing problems. + +""" diff --git a/nova/api/openstack/incubator/volumes.py b/nova/api/openstack/incubator/volumes.py new file mode 100644 index 000000000..7a5b2c1d0 --- /dev/null +++ b/nova/api/openstack/incubator/volumes.py @@ -0,0 +1,355 @@ +# Copyright 2011 Justin Santa Barbara +# 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 volumes extension.""" + +from webob import exc + +from nova import compute +from nova import exception +from nova import flags +from nova import log as logging +from nova import volume +from nova import wsgi +from nova.api.openstack import common +from nova.api.openstack import extensions +from nova.api.openstack import faults + + +LOG = logging.getLogger("nova.api.volumes") + + +FLAGS = flags.FLAGS + + +def _translate_volume_detail_view(context, vol): + """Maps keys for volumes details view.""" + + d = _translate_volume_summary_view(context, vol) + + # No additional data / lookups at the moment + + return d + + +def _translate_volume_summary_view(_context, vol): + """Maps keys for volumes summary view.""" + d = {} + + instance_id = None + # instance_data = None + attached_to = vol.get('instance') + if attached_to: + instance_id = attached_to['id'] + # instance_data = '%s[%s]' % (instance_ec2_id, + # attached_to['host']) + d['id'] = vol['id'] + d['status'] = vol['status'] + d['size'] = vol['size'] + d['availabilityZone'] = vol['availability_zone'] + d['createdAt'] = vol['created_at'] + # if context.is_admin: + # v['status'] = '%s (%s, %s, %s, %s)' % ( + # vol['status'], + # vol['user_id'], + # vol['host'], + # instance_data, + # vol['mountpoint']) + if vol['attach_status'] == 'attached': + d['attachments'] = [{'attachTime': vol['attach_time'], + 'deleteOnTermination': False, + 'mountpoint': vol['mountpoint'], + 'instanceId': instance_id, + 'status': 'attached', + 'volumeId': vol['id']}] + else: + d['attachments'] = [{}] + + d['displayName'] = vol['display_name'] + d['displayDescription'] = vol['display_description'] + return d + + +class VolumeController(wsgi.Controller): + """The Volumes API controller for the OpenStack API.""" + + _serialization_metadata = { + 'application/xml': { + "attributes": { + "volume": [ + "id", + "status", + "size", + "availabilityZone", + "createdAt", + "displayName", + "displayDescription", + ]}}} + + def __init__(self): + self.volume_api = volume.API() + super(VolumeController, self).__init__() + + def show(self, req, id): + """Return data about the given volume.""" + context = req.environ['nova.context'] + + try: + vol = self.volume_api.get(context, id) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + + return {'volume': _translate_volume_detail_view(context, vol)} + + def delete(self, req, id): + """Delete a volume.""" + context = req.environ['nova.context'] + + LOG.audit(_("Delete volume with id: %s"), id, context=context) + + try: + self.volume_api.delete(context, volume_id=id) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + return exc.HTTPAccepted() + + def index(self, req): + """Returns a summary list of volumes.""" + return self._items(req, entity_maker=_translate_volume_summary_view) + + def detail(self, req): + """Returns a detailed list of volumes.""" + return self._items(req, entity_maker=_translate_volume_detail_view) + + def _items(self, req, entity_maker): + """Returns a list of volumes, transformed through entity_maker.""" + context = req.environ['nova.context'] + + volumes = self.volume_api.get_all(context) + limited_list = common.limited(volumes, req) + res = [entity_maker(context, vol) for vol in limited_list] + return {'volumes': res} + + def create(self, req): + """Creates a new volume.""" + context = req.environ['nova.context'] + + env = self._deserialize(req.body, req.get_content_type()) + if not env: + return faults.Fault(exc.HTTPUnprocessableEntity()) + + vol = env['volume'] + size = vol['size'] + LOG.audit(_("Create volume of %s GB"), size, context=context) + new_volume = self.volume_api.create(context, size, + vol.get('display_name'), + vol.get('display_description')) + + # Work around problem that instance is lazy-loaded... + new_volume['instance'] = None + + retval = _translate_volume_detail_view(context, new_volume) + + return {'volume': retval} + + +def _translate_attachment_detail_view(_context, vol): + """Maps keys for attachment details view.""" + + d = _translate_attachment_summary_view(_context, vol) + + # No additional data / lookups at the moment + + return d + + +def _translate_attachment_summary_view(_context, vol): + """Maps keys for attachment summary view.""" + d = {} + + volume_id = vol['id'] + + # NOTE(justinsb): We use the volume id as the id of the attachment object + d['id'] = volume_id + + d['volumeId'] = volume_id + if vol.get('instance_id'): + d['serverId'] = vol['instance_id'] + if vol.get('mountpoint'): + d['device'] = vol['mountpoint'] + + return d + + +class VolumeAttachmentController(wsgi.Controller): + """The volume attachment API controller for the Openstack API. + + A child resource of the server. Note that we use the volume id + as the ID of the attachment (though this is not guaranteed externally) + + """ + + _serialization_metadata = { + 'application/xml': { + 'attributes': { + 'volumeAttachment': ['id', + 'serverId', + 'volumeId', + 'device']}}} + + def __init__(self): + self.compute_api = compute.API() + self.volume_api = volume.API() + super(VolumeAttachmentController, self).__init__() + + def index(self, req, server_id): + """Returns the list of volume attachments for a given instance.""" + return self._items(req, server_id, + entity_maker=_translate_attachment_summary_view) + + def show(self, req, server_id, id): + """Return data about the given volume.""" + context = req.environ['nova.context'] + + volume_id = id + try: + vol = self.volume_api.get(context, volume_id) + except exception.NotFound: + LOG.debug("volume_id not found") + return faults.Fault(exc.HTTPNotFound()) + + if str(vol['instance_id']) != server_id: + LOG.debug("instance_id != server_id") + return faults.Fault(exc.HTTPNotFound()) + + return {'volumeAttachment': _translate_attachment_detail_view(context, + vol)} + + def create(self, req, server_id): + """Attach a volume to an instance.""" + context = req.environ['nova.context'] + + env = self._deserialize(req.body, req.get_content_type()) + if not env: + return faults.Fault(exc.HTTPUnprocessableEntity()) + + instance_id = server_id + volume_id = env['volumeAttachment']['volumeId'] + device = env['volumeAttachment']['device'] + + msg = _("Attach volume %(volume_id)s to instance %(server_id)s" + " at %(device)s") % locals() + LOG.audit(msg, context=context) + + try: + self.compute_api.attach_volume(context, + instance_id=instance_id, + volume_id=volume_id, + device=device) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + + # The attach is async + attachment = {} + attachment['id'] = volume_id + attachment['volumeId'] = volume_id + + # NOTE(justinsb): And now, we have a problem... + # The attach is async, so there's a window in which we don't see + # the attachment (until the attachment completes). We could also + # get problems with concurrent requests. I think we need an + # attachment state, and to write to the DB here, but that's a bigger + # change. + # For now, we'll probably have to rely on libraries being smart + + # TODO(justinsb): How do I return "accepted" here? + return {'volumeAttachment': attachment} + + def update(self, _req, _server_id, _id): + """Update a volume attachment. We don't currently support this.""" + return faults.Fault(exc.HTTPBadRequest()) + + def delete(self, req, server_id, id): + """Detach a volume from an instance.""" + context = req.environ['nova.context'] + + volume_id = id + LOG.audit(_("Detach volume %s"), volume_id, context=context) + + try: + vol = self.volume_api.get(context, volume_id) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + + if str(vol['instance_id']) != server_id: + LOG.debug("instance_id != server_id") + return faults.Fault(exc.HTTPNotFound()) + + self.compute_api.detach_volume(context, + volume_id=volume_id) + + return exc.HTTPAccepted() + + def _items(self, req, server_id, entity_maker): + """Returns a list of attachments, 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()) + + volumes = instance['volumes'] + limited_list = common.limited(volumes, req) + res = [entity_maker(context, vol) for vol in limited_list] + return {'volumeAttachments': res} + + +class Volumes(extensions.ExtensionDescriptor): + def get_name(self): + return "Volumes" + + def get_alias(self): + return "VOLUMES" + + def get_description(self): + return "Volumes support" + + def get_namespace(self): + return "http://docs.openstack.org/ext/volumes/api/v1.1" + + def get_updated(self): + return "2011-03-25T00:00:00+00:00" + + def get_resources(self): + resources = [] + + # NOTE(justinsb): No way to provide singular name ('volume') + # Does this matter? + res = extensions.ResourceExtension('volumes', + VolumeController(), + collection_actions= + {'detail': 'GET'} + ) + resources.append(res) + + res = extensions.ResourceExtension('volume_attachments', + VolumeAttachmentController(), + parent=dict( + member_name='server', + collection_name='servers')) + resources.append(res) + + return resources diff --git a/nova/api/openstack/incubator/volumes/__init__.py b/nova/api/openstack/incubator/volumes/__init__.py deleted file mode 100644 index 2a9c93210..000000000 --- a/nova/api/openstack/incubator/volumes/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 Justin Santa Barbara -# 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 - -"""The volumes extension adds volumes and attachments to the API.""" diff --git a/nova/api/openstack/incubator/volumes/volume_attachments.py b/nova/api/openstack/incubator/volumes/volume_attachments.py deleted file mode 100644 index aec4ea8f3..000000000 --- a/nova/api/openstack/incubator/volumes/volume_attachments.py +++ /dev/null @@ -1,184 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 Justin Santa Barbara -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from webob import exc - -from nova import compute -from nova import exception -from nova import flags -from nova import log as logging -from nova import volume -from nova import wsgi -from nova.api.openstack import common -from nova.api.openstack import faults - - -LOG = logging.getLogger("nova.api.volumes") - - -FLAGS = flags.FLAGS - - -def _translate_detail_view(context, volume): - """Maps keys for details view.""" - - d = _translate_summary_view(context, volume) - - # No additional data / lookups at the moment - - return d - - -def _translate_summary_view(context, vol): - """Maps keys for summary view.""" - d = {} - - volume_id = vol['id'] - - # NOTE(justinsb): We use the volume id as the id of the attachment object - d['id'] = volume_id - - d['volumeId'] = volume_id - if vol.get('instance_id'): - d['serverId'] = vol['instance_id'] - if vol.get('mountpoint'): - d['device'] = vol['mountpoint'] - - return d - - -class Controller(wsgi.Controller): - """The volume attachment API controller for the Openstack API. - - A child resource of the server. Note that we use the volume id - as the ID of the attachment (though this is not guaranteed externally) - - """ - - _serialization_metadata = { - 'application/xml': { - 'attributes': { - 'volumeAttachment': ['id', - 'serverId', - 'volumeId', - 'device']}}} - - def __init__(self): - self.compute_api = compute.API() - self.volume_api = volume.API() - super(Controller, self).__init__() - - def index(self, req, server_id): - """Returns the list of volume attachments for a given instance.""" - return self._items(req, server_id, - entity_maker=_translate_summary_view) - - def show(self, req, server_id, id): - """Return data about the given volume.""" - context = req.environ['nova.context'] - - volume_id = id - try: - vol = self.volume_api.get(context, volume_id) - except exception.NotFound: - LOG.debug("volume_id not found") - return faults.Fault(exc.HTTPNotFound()) - - if str(vol['instance_id']) != server_id: - LOG.debug("instance_id != server_id") - return faults.Fault(exc.HTTPNotFound()) - - return {'volumeAttachment': _translate_detail_view(context, vol)} - - def create(self, req, server_id): - """Attach a volume to an instance.""" - context = req.environ['nova.context'] - - env = self._deserialize(req.body, req.get_content_type()) - if not env: - return faults.Fault(exc.HTTPUnprocessableEntity()) - - instance_id = server_id - volume_id = env['volumeAttachment']['volumeId'] - device = env['volumeAttachment']['device'] - - msg = _("Attach volume %(volume_id)s to instance %(server_id)s" - " at %(device)s") % locals() - LOG.audit(msg, context=context) - - try: - self.compute_api.attach_volume(context, - instance_id=instance_id, - volume_id=volume_id, - device=device) - except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) - - # The attach is async - attachment = {} - attachment['id'] = volume_id - attachment['volumeId'] = volume_id - - # NOTE(justinsb): And now, we have a problem... - # The attach is async, so there's a window in which we don't see - # the attachment (until the attachment completes). We could also - # get problems with concurrent requests. I think we need an - # attachment state, and to write to the DB here, but that's a bigger - # change. - # For now, we'll probably have to rely on libraries being smart - - # TODO(justinsb): How do I return "accepted" here? - return {'volumeAttachment': attachment} - - def update(self, _req, _server_id, _id): - """Update a volume attachment. We don't currently support this.""" - return faults.Fault(exc.HTTPBadRequest()) - - def delete(self, req, server_id, id): - """Detach a volume from an instance.""" - context = req.environ['nova.context'] - - volume_id = id - LOG.audit(_("Detach volume %s"), volume_id, context=context) - - try: - vol = self.volume_api.get(context, volume_id) - except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) - - if str(vol['instance_id']) != server_id: - LOG.debug("instance_id != server_id") - return faults.Fault(exc.HTTPNotFound()) - - self.compute_api.detach_volume(context, - volume_id=volume_id) - - return exc.HTTPAccepted() - - def _items(self, req, server_id, entity_maker): - """Returns a list of attachments, 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()) - - volumes = instance['volumes'] - limited_list = common.limited(volumes, req) - res = [entity_maker(context, vol) for vol in limited_list] - return {'volumeAttachments': res} diff --git a/nova/api/openstack/incubator/volumes/volumes.py b/nova/api/openstack/incubator/volumes/volumes.py deleted file mode 100644 index a7d5fbaa6..000000000 --- a/nova/api/openstack/incubator/volumes/volumes.py +++ /dev/null @@ -1,161 +0,0 @@ -# Copyright 2011 Justin Santa Barbara -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from webob import exc - -from nova import exception -from nova import flags -from nova import log as logging -from nova import volume -from nova import wsgi -from nova.api.openstack import common -from nova.api.openstack import faults - - -LOG = logging.getLogger("nova.api.volumes") - - -FLAGS = flags.FLAGS - - -def _translate_detail_view(context, vol): - """Maps keys for details view.""" - - d = _translate_summary_view(context, vol) - - # No additional data / lookups at the moment - - return d - - -def _translate_summary_view(_context, vol): - """Maps keys for summary view.""" - d = {} - - instance_id = None - # instance_data = None - attached_to = vol.get('instance') - if attached_to: - instance_id = attached_to['id'] - # instance_data = '%s[%s]' % (instance_ec2_id, - # attached_to['host']) - d['id'] = vol['id'] - d['status'] = vol['status'] - d['size'] = vol['size'] - d['availabilityZone'] = vol['availability_zone'] - d['createdAt'] = vol['created_at'] - # if context.is_admin: - # v['status'] = '%s (%s, %s, %s, %s)' % ( - # vol['status'], - # vol['user_id'], - # vol['host'], - # instance_data, - # vol['mountpoint']) - if vol['attach_status'] == 'attached': - d['attachments'] = [{'attachTime': vol['attach_time'], - 'deleteOnTermination': False, - 'mountpoint': vol['mountpoint'], - 'instanceId': instance_id, - 'status': 'attached', - 'volumeId': vol['id']}] - else: - d['attachments'] = [{}] - - d['displayName'] = vol['display_name'] - d['displayDescription'] = vol['display_description'] - return d - - -class Controller(wsgi.Controller): - """The Volumes API controller for the OpenStack API.""" - - _serialization_metadata = { - 'application/xml': { - "attributes": { - "volume": [ - "id", - "status", - "size", - "availabilityZone", - "createdAt", - "displayName", - "displayDescription", - ]}}} - - def __init__(self): - self.volume_api = volume.API() - super(Controller, self).__init__() - - def show(self, req, id): - """Return data about the given volume.""" - context = req.environ['nova.context'] - - try: - vol = self.volume_api.get(context, id) - except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) - - return {'volume': _translate_detail_view(context, vol)} - - def delete(self, req, id): - """Delete a volume.""" - context = req.environ['nova.context'] - - LOG.audit(_("Delete volume with id: %s"), id, context=context) - - try: - self.volume_api.delete(context, volume_id=id) - except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) - return exc.HTTPAccepted() - - def index(self, req): - """Returns a summary list of volumes.""" - return self._items(req, entity_maker=_translate_summary_view) - - def detail(self, req): - """Returns a detailed list of volumes.""" - return self._items(req, entity_maker=_translate_detail_view) - - def _items(self, req, entity_maker): - """Returns a list of volumes, transformed through entity_maker.""" - context = req.environ['nova.context'] - - volumes = self.volume_api.get_all(context) - limited_list = common.limited(volumes, req) - res = [entity_maker(context, vol) for vol in limited_list] - return {'volumes': res} - - def create(self, req): - """Creates a new volume.""" - context = req.environ['nova.context'] - - env = self._deserialize(req.body, req.get_content_type()) - if not env: - return faults.Fault(exc.HTTPUnprocessableEntity()) - - vol = env['volume'] - size = vol['size'] - LOG.audit(_("Create volume of %s GB"), size, context=context) - new_volume = self.volume_api.create(context, size, - vol.get('display_name'), - vol.get('display_description')) - - # Work around problem that instance is lazy-loaded... - new_volume['instance'] = None - - retval = _translate_detail_view(context, new_volume) - - return {'volume': retval} diff --git a/nova/api/openstack/incubator/volumes/volumes_ext.py b/nova/api/openstack/incubator/volumes/volumes_ext.py deleted file mode 100644 index 6a3bb0265..000000000 --- a/nova/api/openstack/incubator/volumes/volumes_ext.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2011 Justin Santa Barbara -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from nova.api.openstack import extensions -from nova.api.openstack.incubator.volumes import volumes -from nova.api.openstack.incubator.volumes import volume_attachments - - -class VolumesExtension(extensions.ExtensionDescriptor): - def get_name(self): - return "Volumes" - - def get_alias(self): - return "VOLUMES" - - def get_description(self): - return "Volumes support" - - def get_namespace(self): - return "http://docs.openstack.org/ext/volumes/api/v1.1" - - def get_updated(self): - return "2011-03-25T00:00:00+00:00" - - def get_resources(self): - resources = [] - - # NOTE(justinsb): No way to provide singular name ('volume') - # Does this matter? - res = extensions.ResourceExtension('volumes', - volumes.Controller(), - collection_actions={'detail': 'GET'} - ) - resources.append(res) - - res = extensions.ResourceExtension('volume_attachments', - volume_attachments.Controller(), - parent=dict( - member_name='server', - collection_name='servers')) - resources.append(res) - - return resources diff --git a/nova/tests/api/openstack/extensions/foxinsocks.py b/nova/tests/api/openstack/extensions/foxinsocks.py new file mode 100644 index 000000000..0860b51ac --- /dev/null +++ b/nova/tests/api/openstack/extensions/foxinsocks.py @@ -0,0 +1,98 @@ +# 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 nova import wsgi + +from nova.api.openstack import extensions + + +class FoxInSocksController(wsgi.Controller): + + def index(self, req): + return "Try to say this Mr. Knox, sir..." + + +class Foxinsocks(object): + + def __init__(self): + pass + + def get_name(self): + return "Fox In Socks" + + def get_alias(self): + return "FOXNSOX" + + def get_description(self): + return "The Fox In Socks Extension" + + def get_namespace(self): + return "http://www.fox.in.socks/api/ext/pie/v1.0" + + def get_updated(self): + return "2011-01-22T13:25:27-06:00" + + def get_resources(self): + resources = [] + resource = extensions.ResourceExtension('foxnsocks', + FoxInSocksController()) + resources.append(resource) + return resources + + def get_actions(self): + actions = [] + actions.append(extensions.ActionExtension('servers', 'add_tweedle', + self._add_tweedle)) + actions.append(extensions.ActionExtension('servers', 'delete_tweedle', + self._delete_tweedle)) + return actions + + def get_response_extensions(self): + response_exts = [] + + def _goose_handler(res): + #NOTE: This only handles JSON responses. + # You can use content type header to test for XML. + data = json.loads(res.body) + data['flavor']['googoose'] = "Gooey goo for chewy chewing!" + return data + + resp_ext = extensions.ResponseExtension('GET', '/v1.1/flavors/:(id)', + _goose_handler) + response_exts.append(resp_ext) + + def _bands_handler(res): + #NOTE: This only handles JSON responses. + # You can use content type header to test for XML. + data = json.loads(res.body) + data['big_bands'] = 'Pig Bands!' + return data + + resp_ext2 = extensions.ResponseExtension('GET', '/v1.1/flavors/:(id)', + _bands_handler) + response_exts.append(resp_ext2) + return response_exts + + def _add_tweedle(self, input_dict, req, id): + + return "Tweedle Beetle Added." + + def _delete_tweedle(self, input_dict, req, id): + + return "Tweedle Beetle Deleted." diff --git a/nova/tests/api/openstack/extensions/foxinsocks/__init__.py b/nova/tests/api/openstack/extensions/foxinsocks/__init__.py deleted file mode 100644 index fe505359d..000000000 --- a/nova/tests/api/openstack/extensions/foxinsocks/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 OpenStack LLC. -# Copyright 2011 Justin Santa Barbara -# 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 - -"""Example Fox-in-Socks extension.""" diff --git a/nova/tests/api/openstack/extensions/foxinsocks/foxinsocks.py b/nova/tests/api/openstack/extensions/foxinsocks/foxinsocks.py deleted file mode 100644 index 442707714..000000000 --- a/nova/tests/api/openstack/extensions/foxinsocks/foxinsocks.py +++ /dev/null @@ -1,98 +0,0 @@ -# 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 nova import wsgi - -from nova.api.openstack import extensions - - -class FoxInSocksController(wsgi.Controller): - - def index(self, req): - return "Try to say this Mr. Knox, sir..." - - -class Foxinsocks(extensions.ExtensionDescriptor): - - def __init__(self): - pass - - def get_name(self): - return "Fox In Socks" - - def get_alias(self): - return "FOXNSOX" - - def get_description(self): - return "The Fox In Socks Extension" - - def get_namespace(self): - return "http://www.fox.in.socks/api/ext/pie/v1.0" - - def get_updated(self): - return "2011-01-22T13:25:27-06:00" - - def get_resources(self): - resources = [] - resource = extensions.ResourceExtension('foxnsocks', - FoxInSocksController()) - resources.append(resource) - return resources - - def get_actions(self): - actions = [] - actions.append(extensions.ActionExtension('servers', 'add_tweedle', - self._add_tweedle)) - actions.append(extensions.ActionExtension('servers', 'delete_tweedle', - self._delete_tweedle)) - return actions - - def get_response_extensions(self): - response_exts = [] - - def _goose_handler(res): - #NOTE: This only handles JSON responses. - # You can use content type header to test for XML. - data = json.loads(res.body) - data['flavor']['googoose'] = "Gooey goo for chewy chewing!" - return data - - resp_ext = extensions.ResponseExtension('GET', '/v1.1/flavors/:(id)', - _goose_handler) - response_exts.append(resp_ext) - - def _bands_handler(res): - #NOTE: This only handles JSON responses. - # You can use content type header to test for XML. - data = json.loads(res.body) - data['big_bands'] = 'Pig Bands!' - return data - - resp_ext2 = extensions.ResponseExtension('GET', '/v1.1/flavors/:(id)', - _bands_handler) - response_exts.append(resp_ext2) - return response_exts - - def _add_tweedle(self, input_dict, req, id): - - return "Tweedle Beetle Added." - - def _delete_tweedle(self, input_dict, req, id): - - return "Tweedle Beetle Deleted." -- cgit From 034a841cbac8e73c55e9525df7360a068fe9d892 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 29 Mar 2011 15:43:38 -0700 Subject: pep8 fixes --- nova/api/openstack/incubator/volumes.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/incubator/volumes.py b/nova/api/openstack/incubator/volumes.py index 7a5b2c1d0..47b86216e 100644 --- a/nova/api/openstack/incubator/volumes.py +++ b/nova/api/openstack/incubator/volumes.py @@ -339,9 +339,8 @@ class Volumes(extensions.ExtensionDescriptor): # NOTE(justinsb): No way to provide singular name ('volume') # Does this matter? res = extensions.ResourceExtension('volumes', - VolumeController(), - collection_actions= - {'detail': 'GET'} + VolumeController(), + collection_actions={'detail': 'GET'} ) resources.append(res) -- cgit From 11d258e1d8a4a78a699aa564b5f8139bf0b73db2 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 29 Mar 2011 15:52:04 -0700 Subject: Added missing blank line at end of multiline docstring --- nova/api/openstack/extensions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index d1d479313..631275235 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -41,7 +41,9 @@ class ExtensionDescriptor(object): """Base class that defines the contract for extensions. Note that you don't have to derive from this class to have a valid - extension; it is purely a convenience.""" + extension; it is purely a convenience. + + """ def get_name(self): """The name of the extension. -- cgit From 5e6c69bc7a7e5ddaa1bf0fa83f64da116343dba8 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 29 Mar 2011 16:35:39 -0700 Subject: Narrowly focused bugfix - don't lose libvirt instances on host reboot or if they crash --- nova/compute/manager.py | 14 ++++++----- nova/virt/libvirt_conn.py | 59 +++++++++++++++++++++++++++++++++++++---------- 2 files changed, 55 insertions(+), 18 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index e0a5e2b3f..72f04ecb1 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1088,12 +1088,14 @@ class ComputeManager(manager.SchedulerDependentManager): db_instance['id'], vm_state) - if vm_state == power_state.SHUTOFF: - # TODO(soren): This is what the compute manager does when you - # terminate an instance. At some point I figure we'll have a - # "terminated" state and some sort of cleanup job that runs - # occasionally, cleaning them out. - self.db.instance_destroy(context, db_instance['id']) + # NOTE(justinsb): We no longer auto-remove SHUTOFF instances + # It's quite hard to get them back when we do. + #if vm_state == power_state.SHUTOFF: + # # TODO(soren): This is what the compute manager does when you + # # terminate an instance. At some point I figure we'll have a + # # "terminated" state and some sort of cleanup job that runs + # # occasionally, cleaning them out. + # self.db.instance_destroy(context, db_instance['id']) # Are there VMs not in the DB? for vm_not_found_in_db in vms_not_found_in_db: diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index c144e827e..533ff9394 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -116,6 +116,8 @@ flags.DEFINE_integer('live_migration_bandwidth', 0, 'Define live migration behavior') 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') def get_connection(read_only): @@ -230,12 +232,14 @@ class LibvirtConnection(driver.ComputeDriver): {'name': instance['name'], 'state': state}) db.instance_set_state(ctxt, instance['id'], state) - if state == power_state.SHUTOFF: - # TODO(soren): This is what the compute manager does when you - # terminate # an instance. At some point I figure we'll have a - # "terminated" state and some sort of cleanup job that runs - # occasionally, cleaning them out. - db.instance_destroy(ctxt, instance['id']) + # NOTE(justinsb): We no longer delete these instances, + # the user may want to power them back on + #if state == power_state.SHUTOFF: + # # TODO(soren): This is what the compute manager does when you + # # terminate # an instance. At some point I figure we'll have a + # # "terminated" state and some sort of cleanup job that runs + # # occasionally, cleaning them out. + # db.instance_destroy(ctxt, instance['id']) if state != power_state.RUNNING: continue @@ -474,7 +478,7 @@ class LibvirtConnection(driver.ComputeDriver): xml = self.to_xml(instance) self.firewall_driver.setup_basic_filtering(instance) self.firewall_driver.prepare_instance_filter(instance) - self._conn.createXML(xml, 0) + self._create_new_domain(xml) self.firewall_driver.apply_instance_filter(instance) timer = utils.LoopingCall(f=None) @@ -522,7 +526,7 @@ class LibvirtConnection(driver.ComputeDriver): 'kernel_id': FLAGS.rescue_kernel_id, 'ramdisk_id': FLAGS.rescue_ramdisk_id} self._create_image(instance, xml, '.rescue', rescue_images) - self._conn.createXML(xml, 0) + self._create_new_domain(xml) timer = utils.LoopingCall(f=None) @@ -565,10 +569,15 @@ class LibvirtConnection(driver.ComputeDriver): self.firewall_driver.setup_basic_filtering(instance, network_info) self.firewall_driver.prepare_instance_filter(instance, network_info) self._create_image(instance, xml, network_info) - self._conn.createXML(xml, 0) + domain = self._create_new_domain(xml) LOG.debug(_("instance %s: is running"), instance['name']) self.firewall_driver.apply_instance_filter(instance) + if FLAGS.start_guests_on_host_boot: + LOG.debug(_("instance %s: setting autostart ON") % + instance['name']) + domain.setAutostart(1) + timer = utils.LoopingCall(f=None) def _wait_for_boot(): @@ -964,11 +973,19 @@ class LibvirtConnection(driver.ComputeDriver): return xml def get_info(self, instance_name): + # NOTE(justinsb): When libvirt isn't running / can't connect, we get: + # libvir: Remote error : unable to connect to + # '/var/run/libvirt/libvirt-sock', libvirtd may need to be started: + # No such file or directory try: virt_dom = self._conn.lookupByName(instance_name) - except: - raise exception.NotFound(_("Instance %s not found") - % instance_name) + except libvirt.libvirtError as e: + if e.get_error_code() == libvirt.VIR_ERR_UNKNOWN_HOST: + raise exception.NotFound(_("Instance %s not found") + % instance_name) + LOG.warning(_("Error from libvirt during lookup: %s") % e) + raise + (state, max_mem, mem, num_cpu, cpu_time) = virt_dom.info() return {'state': state, 'max_mem': max_mem, @@ -976,6 +993,24 @@ class LibvirtConnection(driver.ComputeDriver): 'num_cpu': num_cpu, 'cpu_time': cpu_time} + def _create_new_domain(self, xml, persistent=True, launch_flags=0): + # NOTE(justinsb): libvirt has two types of domain: + # * a transient domain disappears when the guest is shutdown + # or the host is rebooted. + # * a permanent domain is not automatically deleted + # NOTE(justinsb): Even for ephemeral instances, transient seems risky + + if persistent: + # To create a persistent domain, first define it, then launch it. + domain = self._conn.defineXML(xml) + + domain.createWithFlags(launch_flags) + else: + # createXML call creates a transient domain + domain = self._conn.createXML(xml, launch_flags) + + return domain + def get_diagnostics(self, instance_name): raise exception.ApiError(_("diagnostics are not supported " "for libvirt")) -- cgit From 399056300a0be228ec4c56587ec0d9c0d09d927c Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 29 Mar 2011 16:54:37 -0700 Subject: Fix unit test to reflect fact that instance is no longer deleted, just marked SHUTOFF --- nova/tests/test_compute.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index d1ef68de4..b7f08dfbe 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -654,4 +654,5 @@ class ComputeTestCase(test.TestCase): instances = db.instance_get_all(context.get_admin_context()) LOG.info(_("After force-killing instances: %s"), instances) - self.assertEqual(len(instances), 0) + self.assertEqual(len(instances), 1) + self.assertEqual(power_state.SHUTOFF, instances[0]['state']) -- cgit From d92322400c31f1cad933da5117b24376d60a5798 Mon Sep 17 00:00:00 2001 From: John Tran Date: Tue, 29 Mar 2011 17:07:59 -0700 Subject: adding unit tests for describe_images --- nova/tests/test_cloud.py | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 791498f89..5cb969979 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -41,6 +41,7 @@ from nova.compute import power_state from nova.api.ec2 import cloud from nova.api.ec2 import ec2utils from nova.image import local +from nova.exception import NotFound FLAGS = flags.FLAGS @@ -71,7 +72,8 @@ class CloudTestCase(test.TestCase): host = self.network.get_network_host(self.context.elevated()) def fake_show(meh, context, id): - return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1}} + return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, + 'type': 'machine'}} self.stubs.Set(local.LocalImageService, 'show', fake_show) self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show) @@ -217,14 +219,33 @@ class CloudTestCase(test.TestCase): db.service_destroy(self.context, comp2['id']) def test_describe_images(self): + describe_images = self.cloud.describe_images + def fake_detail(meh, context): return [{'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, - 'type':'machine'}}] + 'type': 'machine'}}] + + def fake_show_none(meh, context, id): + raise NotFound self.stubs.Set(local.LocalImageService, 'detail', fake_detail) - result = self.cloud.describe_images(self.context) - result = result['imagesSet'][0] - self.assertEqual(result['imageId'], 'ami-00000001') + # list all + result1 = describe_images(self.context) + result1 = result1['imagesSet'][0] + self.assertEqual(result1['imageId'], 'ami-00000001') + # provided a valid image_id + result2 = describe_images(self.context, ['ami-00000001']) + self.assertEqual(1, len(result2['imagesSet'])) + # provide more than 1 valid image_id + result3 = describe_images(self.context, ['ami-00000001', + 'ami-00000002']) + self.assertEqual(2, len(result3['imagesSet'])) + # provide an non-existing image_id + self.stubs.UnsetAll() + self.stubs.Set(local.LocalImageService, 'show', fake_show_none) + self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show_none) + self.assertRaises(NotFound, describe_images, + self.context, ['ami-fake']) def test_console_output(self): instance_type = FLAGS.default_instance_type -- cgit From ded3416d48980c32eb20f95665f281ffc2927517 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 29 Mar 2011 17:10:40 -0700 Subject: Removed commented-out EC2 code from volumes.py --- nova/api/openstack/incubator/volumes.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/incubator/volumes.py b/nova/api/openstack/incubator/volumes.py index 47b86216e..0e6a1d0e9 100644 --- a/nova/api/openstack/incubator/volumes.py +++ b/nova/api/openstack/incubator/volumes.py @@ -49,24 +49,16 @@ def _translate_volume_summary_view(_context, vol): d = {} instance_id = None - # instance_data = None attached_to = vol.get('instance') if attached_to: instance_id = attached_to['id'] - # instance_data = '%s[%s]' % (instance_ec2_id, - # attached_to['host']) + d['id'] = vol['id'] d['status'] = vol['status'] d['size'] = vol['size'] d['availabilityZone'] = vol['availability_zone'] d['createdAt'] = vol['created_at'] - # if context.is_admin: - # v['status'] = '%s (%s, %s, %s, %s)' % ( - # vol['status'], - # vol['user_id'], - # vol['host'], - # instance_data, - # vol['mountpoint']) + if vol['attach_status'] == 'attached': d['attachments'] = [{'attachTime': vol['attach_time'], 'deleteOnTermination': False, -- cgit From 27b92e509c71a8b79dc6240aecdf598bf9d608f1 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 29 Mar 2011 17:13:40 -0700 Subject: Change volume so that it returns attachments in the same format as is used for the attachment object --- nova/api/openstack/incubator/volumes.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/incubator/volumes.py b/nova/api/openstack/incubator/volumes.py index 0e6a1d0e9..f96e20ed4 100644 --- a/nova/api/openstack/incubator/volumes.py +++ b/nova/api/openstack/incubator/volumes.py @@ -44,15 +44,10 @@ def _translate_volume_detail_view(context, vol): return d -def _translate_volume_summary_view(_context, vol): +def _translate_volume_summary_view(context, vol): """Maps keys for volumes summary view.""" d = {} - instance_id = None - attached_to = vol.get('instance') - if attached_to: - instance_id = attached_to['id'] - d['id'] = vol['id'] d['status'] = vol['status'] d['size'] = vol['size'] @@ -60,12 +55,7 @@ def _translate_volume_summary_view(_context, vol): d['createdAt'] = vol['created_at'] if vol['attach_status'] == 'attached': - d['attachments'] = [{'attachTime': vol['attach_time'], - 'deleteOnTermination': False, - 'mountpoint': vol['mountpoint'], - 'instanceId': instance_id, - 'status': 'attached', - 'volumeId': vol['id']}] + d['attachments'] = [_translate_attachment_detail_view(context, vol)] else: d['attachments'] = [{}] -- cgit From 6d3c31df757f65da7b29aaed1fb4d6e2b29126a0 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 29 Mar 2011 17:17:20 -0700 Subject: Found a better (?) docstring from get_console_pool_info --- nova/virt/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/driver.py b/nova/virt/driver.py index c6c28b2ba..2f52f9cf1 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -89,7 +89,7 @@ class ComputeDriver(object): raise NotImplementedError() def get_console_pool_info(self, console_type): - """? + """Get info about the host on which the VM resides. Returns a dict containing: :address: ? -- cgit From 86914566436d778cdae2244cb9b277e25e21cb21 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 29 Mar 2011 17:22:20 -0700 Subject: Fix a docstring --- nova/api/openstack/incubator/volumes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/incubator/volumes.py b/nova/api/openstack/incubator/volumes.py index f96e20ed4..6efacce52 100644 --- a/nova/api/openstack/incubator/volumes.py +++ b/nova/api/openstack/incubator/volumes.py @@ -202,7 +202,7 @@ class VolumeAttachmentController(wsgi.Controller): entity_maker=_translate_attachment_summary_view) def show(self, req, server_id, id): - """Return data about the given volume.""" + """Return data about the given volume attachment.""" context = req.environ['nova.context'] volume_id = id -- cgit From 60685eabcde99140f36e1ffbd16dbbbacc87baff Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 29 Mar 2011 17:23:09 -0700 Subject: use project key for decrypting images --- nova/image/s3.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/image/s3.py b/nova/image/s3.py index 85a2c651c..ddec5f3aa 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -31,6 +31,7 @@ from xml.etree import ElementTree import boto.s3.connection +from nova import crypto from nova import exception from nova import flags from nova import utils @@ -210,7 +211,7 @@ class S3ImageService(service.BaseImageService): # FIXME(vish): grab key from common service so this can run on # any host. - cloud_pk = os.path.join(FLAGS.ca_path, "private/cakey.pem") + cloud_pk = crypto.key_path(context.project_id) decrypted_filename = os.path.join(image_path, 'image.tar.gz') self._decrypt_image(encrypted_filename, encrypted_key, -- cgit From f0cd8a993d235fddbfb9478b69a77f4ed32f6dff Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 29 Mar 2011 17:35:24 -0700 Subject: Wipe out the bad docstring on get_console_pool_info --- nova/virt/driver.py | 8 -------- 1 file changed, 8 deletions(-) (limited to 'nova') diff --git a/nova/virt/driver.py b/nova/virt/driver.py index 2f52f9cf1..eb9626d08 100644 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -89,14 +89,6 @@ class ComputeDriver(object): raise NotImplementedError() def get_console_pool_info(self, console_type): - """Get info about the host on which the VM resides. - - Returns a dict containing: - :address: ? - :username: ? - :password: ? - - """ raise NotImplementedError() def get_console_output(self, instance): -- cgit From 9c8300c98239c181cc66740bf18717f0488a0743 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 29 Mar 2011 18:08:17 -0700 Subject: Renamed incubator => contrib --- nova/api/openstack/contrib/__init__.py | 22 ++ nova/api/openstack/contrib/volumes.py | 336 +++++++++++++++++++++++++++++++ nova/api/openstack/extensions.py | 4 +- nova/api/openstack/incubator/__init__.py | 22 -- nova/api/openstack/incubator/volumes.py | 336 ------------------------------- 5 files changed, 360 insertions(+), 360 deletions(-) create mode 100644 nova/api/openstack/contrib/__init__.py create mode 100644 nova/api/openstack/contrib/volumes.py delete mode 100644 nova/api/openstack/incubator/__init__.py delete mode 100644 nova/api/openstack/incubator/volumes.py (limited to 'nova') diff --git a/nova/api/openstack/contrib/__init__.py b/nova/api/openstack/contrib/__init__.py new file mode 100644 index 000000000..e115f1ab9 --- /dev/null +++ b/nova/api/openstack/contrib/__init__.py @@ -0,0 +1,22 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Justin Santa Barbara +# 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 + +"""Incubator contains extensions that are shipped with nova. + +It can't be called 'extensions' because that causes namespacing problems. + +""" diff --git a/nova/api/openstack/contrib/volumes.py b/nova/api/openstack/contrib/volumes.py new file mode 100644 index 000000000..6efacce52 --- /dev/null +++ b/nova/api/openstack/contrib/volumes.py @@ -0,0 +1,336 @@ +# Copyright 2011 Justin Santa Barbara +# 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 volumes extension.""" + +from webob import exc + +from nova import compute +from nova import exception +from nova import flags +from nova import log as logging +from nova import volume +from nova import wsgi +from nova.api.openstack import common +from nova.api.openstack import extensions +from nova.api.openstack import faults + + +LOG = logging.getLogger("nova.api.volumes") + + +FLAGS = flags.FLAGS + + +def _translate_volume_detail_view(context, vol): + """Maps keys for volumes details view.""" + + d = _translate_volume_summary_view(context, vol) + + # No additional data / lookups at the moment + + return d + + +def _translate_volume_summary_view(context, vol): + """Maps keys for volumes summary view.""" + d = {} + + d['id'] = vol['id'] + d['status'] = vol['status'] + d['size'] = vol['size'] + d['availabilityZone'] = vol['availability_zone'] + d['createdAt'] = vol['created_at'] + + if vol['attach_status'] == 'attached': + d['attachments'] = [_translate_attachment_detail_view(context, vol)] + else: + d['attachments'] = [{}] + + d['displayName'] = vol['display_name'] + d['displayDescription'] = vol['display_description'] + return d + + +class VolumeController(wsgi.Controller): + """The Volumes API controller for the OpenStack API.""" + + _serialization_metadata = { + 'application/xml': { + "attributes": { + "volume": [ + "id", + "status", + "size", + "availabilityZone", + "createdAt", + "displayName", + "displayDescription", + ]}}} + + def __init__(self): + self.volume_api = volume.API() + super(VolumeController, self).__init__() + + def show(self, req, id): + """Return data about the given volume.""" + context = req.environ['nova.context'] + + try: + vol = self.volume_api.get(context, id) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + + return {'volume': _translate_volume_detail_view(context, vol)} + + def delete(self, req, id): + """Delete a volume.""" + context = req.environ['nova.context'] + + LOG.audit(_("Delete volume with id: %s"), id, context=context) + + try: + self.volume_api.delete(context, volume_id=id) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + return exc.HTTPAccepted() + + def index(self, req): + """Returns a summary list of volumes.""" + return self._items(req, entity_maker=_translate_volume_summary_view) + + def detail(self, req): + """Returns a detailed list of volumes.""" + return self._items(req, entity_maker=_translate_volume_detail_view) + + def _items(self, req, entity_maker): + """Returns a list of volumes, transformed through entity_maker.""" + context = req.environ['nova.context'] + + volumes = self.volume_api.get_all(context) + limited_list = common.limited(volumes, req) + res = [entity_maker(context, vol) for vol in limited_list] + return {'volumes': res} + + def create(self, req): + """Creates a new volume.""" + context = req.environ['nova.context'] + + env = self._deserialize(req.body, req.get_content_type()) + if not env: + return faults.Fault(exc.HTTPUnprocessableEntity()) + + vol = env['volume'] + size = vol['size'] + LOG.audit(_("Create volume of %s GB"), size, context=context) + new_volume = self.volume_api.create(context, size, + vol.get('display_name'), + vol.get('display_description')) + + # Work around problem that instance is lazy-loaded... + new_volume['instance'] = None + + retval = _translate_volume_detail_view(context, new_volume) + + return {'volume': retval} + + +def _translate_attachment_detail_view(_context, vol): + """Maps keys for attachment details view.""" + + d = _translate_attachment_summary_view(_context, vol) + + # No additional data / lookups at the moment + + return d + + +def _translate_attachment_summary_view(_context, vol): + """Maps keys for attachment summary view.""" + d = {} + + volume_id = vol['id'] + + # NOTE(justinsb): We use the volume id as the id of the attachment object + d['id'] = volume_id + + d['volumeId'] = volume_id + if vol.get('instance_id'): + d['serverId'] = vol['instance_id'] + if vol.get('mountpoint'): + d['device'] = vol['mountpoint'] + + return d + + +class VolumeAttachmentController(wsgi.Controller): + """The volume attachment API controller for the Openstack API. + + A child resource of the server. Note that we use the volume id + as the ID of the attachment (though this is not guaranteed externally) + + """ + + _serialization_metadata = { + 'application/xml': { + 'attributes': { + 'volumeAttachment': ['id', + 'serverId', + 'volumeId', + 'device']}}} + + def __init__(self): + self.compute_api = compute.API() + self.volume_api = volume.API() + super(VolumeAttachmentController, self).__init__() + + def index(self, req, server_id): + """Returns the list of volume attachments for a given instance.""" + return self._items(req, server_id, + entity_maker=_translate_attachment_summary_view) + + def show(self, req, server_id, id): + """Return data about the given volume attachment.""" + context = req.environ['nova.context'] + + volume_id = id + try: + vol = self.volume_api.get(context, volume_id) + except exception.NotFound: + LOG.debug("volume_id not found") + return faults.Fault(exc.HTTPNotFound()) + + if str(vol['instance_id']) != server_id: + LOG.debug("instance_id != server_id") + return faults.Fault(exc.HTTPNotFound()) + + return {'volumeAttachment': _translate_attachment_detail_view(context, + vol)} + + def create(self, req, server_id): + """Attach a volume to an instance.""" + context = req.environ['nova.context'] + + env = self._deserialize(req.body, req.get_content_type()) + if not env: + return faults.Fault(exc.HTTPUnprocessableEntity()) + + instance_id = server_id + volume_id = env['volumeAttachment']['volumeId'] + device = env['volumeAttachment']['device'] + + msg = _("Attach volume %(volume_id)s to instance %(server_id)s" + " at %(device)s") % locals() + LOG.audit(msg, context=context) + + try: + self.compute_api.attach_volume(context, + instance_id=instance_id, + volume_id=volume_id, + device=device) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + + # The attach is async + attachment = {} + attachment['id'] = volume_id + attachment['volumeId'] = volume_id + + # NOTE(justinsb): And now, we have a problem... + # The attach is async, so there's a window in which we don't see + # the attachment (until the attachment completes). We could also + # get problems with concurrent requests. I think we need an + # attachment state, and to write to the DB here, but that's a bigger + # change. + # For now, we'll probably have to rely on libraries being smart + + # TODO(justinsb): How do I return "accepted" here? + return {'volumeAttachment': attachment} + + def update(self, _req, _server_id, _id): + """Update a volume attachment. We don't currently support this.""" + return faults.Fault(exc.HTTPBadRequest()) + + def delete(self, req, server_id, id): + """Detach a volume from an instance.""" + context = req.environ['nova.context'] + + volume_id = id + LOG.audit(_("Detach volume %s"), volume_id, context=context) + + try: + vol = self.volume_api.get(context, volume_id) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + + if str(vol['instance_id']) != server_id: + LOG.debug("instance_id != server_id") + return faults.Fault(exc.HTTPNotFound()) + + self.compute_api.detach_volume(context, + volume_id=volume_id) + + return exc.HTTPAccepted() + + def _items(self, req, server_id, entity_maker): + """Returns a list of attachments, 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()) + + volumes = instance['volumes'] + limited_list = common.limited(volumes, req) + res = [entity_maker(context, vol) for vol in limited_list] + return {'volumeAttachments': res} + + +class Volumes(extensions.ExtensionDescriptor): + def get_name(self): + return "Volumes" + + def get_alias(self): + return "VOLUMES" + + def get_description(self): + return "Volumes support" + + def get_namespace(self): + return "http://docs.openstack.org/ext/volumes/api/v1.1" + + def get_updated(self): + return "2011-03-25T00:00:00+00:00" + + def get_resources(self): + resources = [] + + # NOTE(justinsb): No way to provide singular name ('volume') + # Does this matter? + res = extensions.ResourceExtension('volumes', + VolumeController(), + collection_actions={'detail': 'GET'} + ) + resources.append(res) + + res = extensions.ResourceExtension('volume_attachments', + VolumeAttachmentController(), + parent=dict( + member_name='server', + collection_name='servers')) + resources.append(res) + + return resources diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 631275235..cba151fb6 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -378,7 +378,7 @@ class ExtensionManager(object): widgets.py the extension class within that module should be 'Widgets'. - In addition, extensions are loaded from the 'incubator' directory. + In addition, extensions are loaded from the 'contrib' directory. See nova/tests/api/openstack/extensions/foxinsocks.py for an example extension implementation. @@ -387,7 +387,7 @@ class ExtensionManager(object): if os.path.exists(self.path): self._load_all_extensions_from_path(self.path) - incubator_path = os.path.join(os.path.dirname(__file__), "incubator") + incubator_path = os.path.join(os.path.dirname(__file__), "contrib") if os.path.exists(incubator_path): self._load_all_extensions_from_path(incubator_path) diff --git a/nova/api/openstack/incubator/__init__.py b/nova/api/openstack/incubator/__init__.py deleted file mode 100644 index e115f1ab9..000000000 --- a/nova/api/openstack/incubator/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 Justin Santa Barbara -# 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 - -"""Incubator contains extensions that are shipped with nova. - -It can't be called 'extensions' because that causes namespacing problems. - -""" diff --git a/nova/api/openstack/incubator/volumes.py b/nova/api/openstack/incubator/volumes.py deleted file mode 100644 index 6efacce52..000000000 --- a/nova/api/openstack/incubator/volumes.py +++ /dev/null @@ -1,336 +0,0 @@ -# Copyright 2011 Justin Santa Barbara -# 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 volumes extension.""" - -from webob import exc - -from nova import compute -from nova import exception -from nova import flags -from nova import log as logging -from nova import volume -from nova import wsgi -from nova.api.openstack import common -from nova.api.openstack import extensions -from nova.api.openstack import faults - - -LOG = logging.getLogger("nova.api.volumes") - - -FLAGS = flags.FLAGS - - -def _translate_volume_detail_view(context, vol): - """Maps keys for volumes details view.""" - - d = _translate_volume_summary_view(context, vol) - - # No additional data / lookups at the moment - - return d - - -def _translate_volume_summary_view(context, vol): - """Maps keys for volumes summary view.""" - d = {} - - d['id'] = vol['id'] - d['status'] = vol['status'] - d['size'] = vol['size'] - d['availabilityZone'] = vol['availability_zone'] - d['createdAt'] = vol['created_at'] - - if vol['attach_status'] == 'attached': - d['attachments'] = [_translate_attachment_detail_view(context, vol)] - else: - d['attachments'] = [{}] - - d['displayName'] = vol['display_name'] - d['displayDescription'] = vol['display_description'] - return d - - -class VolumeController(wsgi.Controller): - """The Volumes API controller for the OpenStack API.""" - - _serialization_metadata = { - 'application/xml': { - "attributes": { - "volume": [ - "id", - "status", - "size", - "availabilityZone", - "createdAt", - "displayName", - "displayDescription", - ]}}} - - def __init__(self): - self.volume_api = volume.API() - super(VolumeController, self).__init__() - - def show(self, req, id): - """Return data about the given volume.""" - context = req.environ['nova.context'] - - try: - vol = self.volume_api.get(context, id) - except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) - - return {'volume': _translate_volume_detail_view(context, vol)} - - def delete(self, req, id): - """Delete a volume.""" - context = req.environ['nova.context'] - - LOG.audit(_("Delete volume with id: %s"), id, context=context) - - try: - self.volume_api.delete(context, volume_id=id) - except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) - return exc.HTTPAccepted() - - def index(self, req): - """Returns a summary list of volumes.""" - return self._items(req, entity_maker=_translate_volume_summary_view) - - def detail(self, req): - """Returns a detailed list of volumes.""" - return self._items(req, entity_maker=_translate_volume_detail_view) - - def _items(self, req, entity_maker): - """Returns a list of volumes, transformed through entity_maker.""" - context = req.environ['nova.context'] - - volumes = self.volume_api.get_all(context) - limited_list = common.limited(volumes, req) - res = [entity_maker(context, vol) for vol in limited_list] - return {'volumes': res} - - def create(self, req): - """Creates a new volume.""" - context = req.environ['nova.context'] - - env = self._deserialize(req.body, req.get_content_type()) - if not env: - return faults.Fault(exc.HTTPUnprocessableEntity()) - - vol = env['volume'] - size = vol['size'] - LOG.audit(_("Create volume of %s GB"), size, context=context) - new_volume = self.volume_api.create(context, size, - vol.get('display_name'), - vol.get('display_description')) - - # Work around problem that instance is lazy-loaded... - new_volume['instance'] = None - - retval = _translate_volume_detail_view(context, new_volume) - - return {'volume': retval} - - -def _translate_attachment_detail_view(_context, vol): - """Maps keys for attachment details view.""" - - d = _translate_attachment_summary_view(_context, vol) - - # No additional data / lookups at the moment - - return d - - -def _translate_attachment_summary_view(_context, vol): - """Maps keys for attachment summary view.""" - d = {} - - volume_id = vol['id'] - - # NOTE(justinsb): We use the volume id as the id of the attachment object - d['id'] = volume_id - - d['volumeId'] = volume_id - if vol.get('instance_id'): - d['serverId'] = vol['instance_id'] - if vol.get('mountpoint'): - d['device'] = vol['mountpoint'] - - return d - - -class VolumeAttachmentController(wsgi.Controller): - """The volume attachment API controller for the Openstack API. - - A child resource of the server. Note that we use the volume id - as the ID of the attachment (though this is not guaranteed externally) - - """ - - _serialization_metadata = { - 'application/xml': { - 'attributes': { - 'volumeAttachment': ['id', - 'serverId', - 'volumeId', - 'device']}}} - - def __init__(self): - self.compute_api = compute.API() - self.volume_api = volume.API() - super(VolumeAttachmentController, self).__init__() - - def index(self, req, server_id): - """Returns the list of volume attachments for a given instance.""" - return self._items(req, server_id, - entity_maker=_translate_attachment_summary_view) - - def show(self, req, server_id, id): - """Return data about the given volume attachment.""" - context = req.environ['nova.context'] - - volume_id = id - try: - vol = self.volume_api.get(context, volume_id) - except exception.NotFound: - LOG.debug("volume_id not found") - return faults.Fault(exc.HTTPNotFound()) - - if str(vol['instance_id']) != server_id: - LOG.debug("instance_id != server_id") - return faults.Fault(exc.HTTPNotFound()) - - return {'volumeAttachment': _translate_attachment_detail_view(context, - vol)} - - def create(self, req, server_id): - """Attach a volume to an instance.""" - context = req.environ['nova.context'] - - env = self._deserialize(req.body, req.get_content_type()) - if not env: - return faults.Fault(exc.HTTPUnprocessableEntity()) - - instance_id = server_id - volume_id = env['volumeAttachment']['volumeId'] - device = env['volumeAttachment']['device'] - - msg = _("Attach volume %(volume_id)s to instance %(server_id)s" - " at %(device)s") % locals() - LOG.audit(msg, context=context) - - try: - self.compute_api.attach_volume(context, - instance_id=instance_id, - volume_id=volume_id, - device=device) - except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) - - # The attach is async - attachment = {} - attachment['id'] = volume_id - attachment['volumeId'] = volume_id - - # NOTE(justinsb): And now, we have a problem... - # The attach is async, so there's a window in which we don't see - # the attachment (until the attachment completes). We could also - # get problems with concurrent requests. I think we need an - # attachment state, and to write to the DB here, but that's a bigger - # change. - # For now, we'll probably have to rely on libraries being smart - - # TODO(justinsb): How do I return "accepted" here? - return {'volumeAttachment': attachment} - - def update(self, _req, _server_id, _id): - """Update a volume attachment. We don't currently support this.""" - return faults.Fault(exc.HTTPBadRequest()) - - def delete(self, req, server_id, id): - """Detach a volume from an instance.""" - context = req.environ['nova.context'] - - volume_id = id - LOG.audit(_("Detach volume %s"), volume_id, context=context) - - try: - vol = self.volume_api.get(context, volume_id) - except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) - - if str(vol['instance_id']) != server_id: - LOG.debug("instance_id != server_id") - return faults.Fault(exc.HTTPNotFound()) - - self.compute_api.detach_volume(context, - volume_id=volume_id) - - return exc.HTTPAccepted() - - def _items(self, req, server_id, entity_maker): - """Returns a list of attachments, 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()) - - volumes = instance['volumes'] - limited_list = common.limited(volumes, req) - res = [entity_maker(context, vol) for vol in limited_list] - return {'volumeAttachments': res} - - -class Volumes(extensions.ExtensionDescriptor): - def get_name(self): - return "Volumes" - - def get_alias(self): - return "VOLUMES" - - def get_description(self): - return "Volumes support" - - def get_namespace(self): - return "http://docs.openstack.org/ext/volumes/api/v1.1" - - def get_updated(self): - return "2011-03-25T00:00:00+00:00" - - def get_resources(self): - resources = [] - - # NOTE(justinsb): No way to provide singular name ('volume') - # Does this matter? - res = extensions.ResourceExtension('volumes', - VolumeController(), - collection_actions={'detail': 'GET'} - ) - resources.append(res) - - res = extensions.ResourceExtension('volume_attachments', - VolumeAttachmentController(), - parent=dict( - member_name='server', - collection_name='servers')) - resources.append(res) - - return resources -- cgit From be8bf22f90e322823cb3cf4963f5c7313ef727ec Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 29 Mar 2011 18:08:36 -0700 Subject: Removed unused super_verbose argument left over from previous code --- nova/api/openstack/extensions.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index cba151fb6..6813d85c9 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -315,8 +315,6 @@ class ExtensionManager(object): def __init__(self, path): LOG.audit(_('Initializing extension manager.')) - self.super_verbose = False - self.path = path self.extensions = {} self._load_all_extensions() -- cgit From 4c16db6ad330b0c3a1bdde098bbdcf958fc23bdf Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 29 Mar 2011 18:13:04 -0700 Subject: Rename MockImageService -> FakeImageService --- nova/image/fake.py | 4 ++-- nova/tests/integrated/integrated_helpers.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/image/fake.py b/nova/image/fake.py index 4caf68d63..c84f7ec02 100644 --- a/nova/image/fake.py +++ b/nova/image/fake.py @@ -28,7 +28,7 @@ LOG = logging.getLogger('nova.image.fake') FLAGS = flags.FLAGS -class MockImageService(service.BaseImageService): +class FakeImageService(service.BaseImageService): """Mock (fake) image service for unit testing.""" def __init__(self): @@ -43,7 +43,7 @@ class MockImageService(service.BaseImageService): 'disk_format': 'ami'} } self.create(None, image) - super(MockImageService, self).__init__() + super(FakeImageService, self).__init__() def index(self, context): """Returns list of images.""" diff --git a/nova/tests/integrated/integrated_helpers.py b/nova/tests/integrated/integrated_helpers.py index e0fe2d2a2..2e5d67017 100644 --- a/nova/tests/integrated/integrated_helpers.py +++ b/nova/tests/integrated/integrated_helpers.py @@ -184,7 +184,7 @@ class _IntegratedTestBase(test.TestCase): def _get_flags(self): """An opportunity to setup flags, before the services are started.""" f = {} - f['image_service'] = 'nova.image.fake.MockImageService' + f['image_service'] = 'nova.image.fake.FakeImageService' f['fake_network'] = True return f -- cgit From 2315682856f420ff0b781bead142e1aff82071a4 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 29 Mar 2011 18:16:09 -0700 Subject: "Incubator" is no more. Long live "contrib" --- nova/api/openstack/contrib/__init__.py | 2 +- nova/api/openstack/extensions.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/contrib/__init__.py b/nova/api/openstack/contrib/__init__.py index e115f1ab9..b42a1d89d 100644 --- a/nova/api/openstack/contrib/__init__.py +++ b/nova/api/openstack/contrib/__init__.py @@ -15,7 +15,7 @@ # License for the specific language governing permissions and limitations # under the License.import datetime -"""Incubator contains extensions that are shipped with nova. +"""Contrib contains extensions that are shipped with nova. It can't be called 'extensions' because that causes namespacing problems. diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 6813d85c9..fb1dccb28 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -385,9 +385,9 @@ class ExtensionManager(object): if os.path.exists(self.path): self._load_all_extensions_from_path(self.path) - incubator_path = os.path.join(os.path.dirname(__file__), "contrib") - if os.path.exists(incubator_path): - self._load_all_extensions_from_path(incubator_path) + contrib_path = os.path.join(os.path.dirname(__file__), "contrib") + if os.path.exists(contrib_path): + self._load_all_extensions_from_path(contrib_path) def _load_all_extensions_from_path(self, path): for f in os.listdir(path): -- cgit From f4b9e95bd143d46cd4939a3ea31de10a3e66fd90 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 29 Mar 2011 19:10:23 -0700 Subject: name, created_at, updated_at are required. I think some of the other image services might also break because of this, but that's a different issue... --- nova/image/fake.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'nova') diff --git a/nova/image/fake.py b/nova/image/fake.py index c84f7ec02..16c628091 100644 --- a/nova/image/fake.py +++ b/nova/image/fake.py @@ -16,6 +16,8 @@ # under the License. """Implementation of an fake image service""" +import datetime + from nova import exception from nova import flags from nova import log as logging @@ -35,7 +37,11 @@ class FakeImageService(service.BaseImageService): self.images = {} # NOTE(justinsb): The OpenStack API can't upload an image? # So, make sure we've got one.. + timestamp = datetime.datetime(2011, 01, 01, 01, 02, 03) image = {'id': '123456', + 'name': 'fakeimage123456', + 'created_at': timestamp, + 'updated_at': timestamp, 'status': 'active', 'type': 'machine', 'properties': {'kernel_id': FLAGS.null_kernel, -- cgit From 9397766990b00167071bca6392096abfb93af982 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Tue, 29 Mar 2011 19:11:12 -0700 Subject: Deepcopy the images, because the string formatting transforms them in-place --- nova/image/fake.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/image/fake.py b/nova/image/fake.py index 16c628091..08302d6eb 100644 --- a/nova/image/fake.py +++ b/nova/image/fake.py @@ -16,6 +16,7 @@ # under the License. """Implementation of an fake image service""" +import copy import datetime from nova import exception @@ -53,11 +54,11 @@ class FakeImageService(service.BaseImageService): def index(self, context): """Returns list of images.""" - return self.images.values() + return copy.deepcopy(self.images.values()) def detail(self, context): """Return list of detailed image information.""" - return self.images.values() + return copy.deepcopy(self.images.values()) def show(self, context, image_id): """Get data about specified image. @@ -68,7 +69,7 @@ class FakeImageService(service.BaseImageService): image_id = int(image_id) image = self.images.get(image_id) if image: - return image + return copy.deepcopy(image) LOG.warn("Unable to find image id %s. Have images: %s", image_id, self.images) raise exception.NotFound @@ -83,7 +84,7 @@ class FakeImageService(service.BaseImageService): if self.images.get(image_id): raise exception.Duplicate() - self.images[image_id] = data + self.images[image_id] = copy.deepcopy(data) def update(self, context, image_id, data): """Replace the contents of the given image with the new data. @@ -94,7 +95,7 @@ class FakeImageService(service.BaseImageService): image_id = int(image_id) if not self.images.get(image_id): raise exception.NotFound - self.images[image_id] = data + self.images[image_id] = copy.deepcopy(data) def delete(self, context, image_id): """Delete the given image. -- cgit From 7856df88b22e6ff3bd0f124e3d71f130e3e9c205 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 29 Mar 2011 22:41:15 -0700 Subject: fix localization for multiple replacement strings --- nova/vnc/auth.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/vnc/auth.py b/nova/vnc/auth.py index c7df13450..ce5e10388 100644 --- a/nova/vnc/auth.py +++ b/nova/vnc/auth.py @@ -117,11 +117,13 @@ class VNCProxyAuthManager(manager.Manager): self.tokens[token] = {'host': host, 'port': port, 'last_activity_at': time.time()} - LOG.audit(_("Received Token: %s, %s)"), token, self.tokens[token]) + token_dict = self.tokens[token] + LOG.audit(_("Received Token: %(token)s, %(token_dict)s)"), locals()) def check_token(self, context, token): - LOG.audit(_("Checking Token: %s, %s)"), token, (token in self.tokens)) - if token in self.tokens: + token_valid = token in self.tokens + LOG.audit(_("Checking Token: %(token)s, %(token_valid)s)"), locals()) + if token_valid: return self.tokens[token] def _delete_expired_tokens(self): -- cgit From 7aa0102b2d451ffa87a095ac4471a65260aff3fe Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 29 Mar 2011 22:55:16 -0700 Subject: make sure that flag is there in compute api --- nova/compute/api.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 7977b07a2..7f358fdfd 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -39,6 +39,7 @@ from nova.db import base FLAGS = flags.FLAGS LOG = logging.getLogger('nova.compute.api') +flags.DECLARE('vncproxy_topic', 'nova.vnc') def generate_default_hostname(instance_id): -- cgit From 047af2c506374aa44bb896a7df0cb5813bf3a123 Mon Sep 17 00:00:00 2001 From: Thierry Carrez Date: Wed, 30 Mar 2011 15:02:59 +0200 Subject: Add missing method that prevent HyperV compute nodes from starting up --- nova/virt/hyperv.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'nova') diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index a1ed5ebbf..13f403a66 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -485,3 +485,7 @@ class HyperVConnection(driver.ComputeDriver): def poll_rescued_instances(self, timeout): pass + + def update_available_resource(self, ctxt, host): + """This method is supported only by libvirt.""" + return -- cgit From 6f274d0a5818633b072e432ba7182650f0d30001 Mon Sep 17 00:00:00 2001 From: Thierry Carrez Date: Wed, 30 Mar 2011 15:28:21 +0200 Subject: Do not push 'None' to authorized_keys when no key is specified --- nova/virt/libvirt_conn.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index b28584cb6..f34ea7225 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -828,7 +828,10 @@ class LibvirtConnection(driver.ComputeDriver): if FLAGS.libvirt_type == 'lxc': target_partition = None - key = str(inst['key_data']) + if inst['key_data']: + key = str(inst['key_data']) + else: + key = None net = None nets = [] -- cgit From b1589b5f034db95b1d18910e27cae516258a4311 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 30 Mar 2011 09:39:35 -0400 Subject: exception -> Fault --- nova/tests/api/openstack/test_faults.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_faults.py b/nova/tests/api/openstack/test_faults.py index 0cda542de..9746e8168 100644 --- a/nova/tests/api/openstack/test_faults.py +++ b/nova/tests/api/openstack/test_faults.py @@ -128,7 +128,7 @@ class TestFaults(test.TestCase): self.assertEqual(expected, actual) def test_raise(self): - """Ensure the ability to raise exceptions in WSGI-ified methods.""" + """Ensure the ability to raise `Fault`s in WSGI-ified methods.""" @webob.dec.wsgify def raiser(req): raise faults.Fault(webob.exc.HTTPNotFound(explanation='whut?')) -- cgit From de9091a74107827b8f7157d6b89c2fb5dcf92dd2 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 30 Mar 2011 08:29:17 -0700 Subject: queues properly reconnect if rabbitmq is restarted --- nova/rpc.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/rpc.py b/nova/rpc.py index 388f78d69..be7cb3f37 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -74,7 +74,12 @@ class Connection(carrot_connection.BrokerConnection): """Recreates the connection instance This is necessary to recover from some network errors/disconnects""" - del cls._instance + try: + del cls._instance + except AttributeError, e: + # The _instance stuff is for testing purposes. Usually we don't use + # it. So don't freak out if it doesn't exist. + pass return cls.instance() @@ -125,10 +130,12 @@ class Consumer(messaging.Consumer): # NOTE(vish): This is catching all errors because we really don't # want exceptions to be logged 10 times a second if some # persistent failure occurs. - except Exception: # pylint: disable=W0703 + except Exception, e: # pylint: disable=W0703 if not self.failed_connection: - LOG.exception(_("Failed to fetch message from queue")) + LOG.exception(_("Failed to fetch message from queue: %s" % e)) self.failed_connection = True + else: + LOG.exception(_("Unhandled exception %s" % e)) def attach_to_eventlet(self): """Only needed for unit tests!""" -- cgit From 951ec0d0fb2711e5d5ef4d6e9e78fe74d6c62360 Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Thu, 31 Mar 2011 00:45:59 +0900 Subject: Added synchronize_session parameter to a query in fixed_ip_disassociate_all_by_timeout() and fix #735974. --- nova/db/sqlalchemy/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index b2a13a01b..08eb0b7b2 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -660,7 +660,7 @@ def fixed_ip_disassociate_all_by_timeout(_context, host, time): filter(models.FixedIp.instance_id != None).\ filter_by(allocated=0).\ update({'instance_id': None, - 'leased': 0}) + 'leased': 0}, synchronize_session='fetch') return result -- cgit From 12505c6bae61e72b6c75ac647323c3ec15997df1 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Wed, 30 Mar 2011 10:05:06 -0700 Subject: Add XML namespaces to the OpenStack API --- nova/api/openstack/accounts.py | 5 ++- nova/api/openstack/backup_schedules.py | 4 +-- nova/api/openstack/common.py | 11 +++++++ nova/api/openstack/consoles.py | 4 +-- nova/api/openstack/extensions.py | 7 +++-- nova/api/openstack/flavors.py | 7 +++-- nova/api/openstack/image_metadata.py | 3 +- nova/api/openstack/images.py | 5 ++- nova/api/openstack/limits.py | 4 +-- nova/api/openstack/server_metadata.py | 3 +- nova/api/openstack/servers.py | 5 ++- nova/api/openstack/shared_ip_groups.py | 4 +-- nova/api/openstack/users.py | 3 +- nova/api/openstack/zones.py | 6 ++-- nova/tests/integrated/test_xml.py | 56 ++++++++++++++++++++++++++++++++++ nova/wsgi.py | 8 ++--- 16 files changed, 103 insertions(+), 32 deletions(-) create mode 100644 nova/tests/integrated/test_xml.py (limited to 'nova') diff --git a/nova/api/openstack/accounts.py b/nova/api/openstack/accounts.py index 86066fa20..6e3763e47 100644 --- a/nova/api/openstack/accounts.py +++ b/nova/api/openstack/accounts.py @@ -13,15 +13,14 @@ # License for the specific language governing permissions and limitations # under the License. -import common import webob.exc from nova import exception from nova import flags from nova import log as logging -from nova import wsgi from nova.auth import manager +from nova.api.openstack import common from nova.api.openstack import faults FLAGS = flags.FLAGS @@ -35,7 +34,7 @@ def _translate_keys(account): manager=account.project_manager_id) -class Controller(wsgi.Controller): +class Controller(common.OpenstackController): _serialization_metadata = { 'application/xml': { diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index f2d2d86e8..4bf744046 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -19,7 +19,7 @@ import time from webob import exc -from nova import wsgi +from nova.api.openstack import common from nova.api.openstack import faults import nova.image.service @@ -29,7 +29,7 @@ def _translate_keys(inst): return dict(backupSchedule=inst) -class Controller(wsgi.Controller): +class Controller(common.OpenstackController): """ The backup schedule API controller for the Openstack API """ _serialization_metadata = { diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 75aeb0a5f..234f921ab 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -22,6 +22,7 @@ import webob from nova import exception from nova import flags from nova import log as logging +from nova import wsgi LOG = logging.getLogger('common') @@ -30,6 +31,10 @@ LOG = logging.getLogger('common') FLAGS = flags.FLAGS +XML_NS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0' +XML_NS_V11 = 'http://docs.openstack.org/compute/api/v1.1' + + def limited(items, request, max_limit=FLAGS.osapi_max_limit): """ Return a slice of items according to requested offset and limit. @@ -128,3 +133,9 @@ def get_id_from_href(href): except: LOG.debug(_("Error extracting id from href: %s") % href) raise webob.exc.HTTPBadRequest(_('could not parse id from href')) + + +class OpenstackController(wsgi.Controller): + def get_default_xmlns(self, req): + # Use V10 by default + return XML_NS_V10 diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index 8c291c2eb..1a77f25d7 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -19,7 +19,7 @@ from webob import exc from nova import console from nova import exception -from nova import wsgi +from nova.api.openstack import common from nova.api.openstack import faults @@ -43,7 +43,7 @@ def _translate_detail_keys(cons): return dict(console=info) -class Controller(wsgi.Controller): +class Controller(common.OpenstackController): """The Consoles Controller for the Openstack API""" _serialization_metadata = { diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index fb1dccb28..75f369cac 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -28,6 +28,7 @@ from nova import exception from nova import flags from nova import log as logging from nova import wsgi +from nova.api.openstack import common from nova.api.openstack import faults @@ -115,7 +116,7 @@ class ExtensionDescriptor(object): return response_exts -class ActionExtensionController(wsgi.Controller): +class ActionExtensionController(common.OpenstackController): def __init__(self, application): @@ -136,7 +137,7 @@ class ActionExtensionController(wsgi.Controller): return res -class ResponseExtensionController(wsgi.Controller): +class ResponseExtensionController(common.OpenstackController): def __init__(self, application): self.application = application @@ -163,7 +164,7 @@ class ResponseExtensionController(wsgi.Controller): return res -class ExtensionController(wsgi.Controller): +class ExtensionController(common.OpenstackController): def __init__(self, extension_manager): self.extension_manager = extension_manager diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index 5b99b5a6f..40787bd17 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -19,11 +19,11 @@ import webob from nova import db from nova import exception -from nova import wsgi +from nova.api.openstack import common from nova.api.openstack import views -class Controller(wsgi.Controller): +class Controller(common.OpenstackController): """Flavor controller for the OpenStack API.""" _serialization_metadata = { @@ -76,3 +76,6 @@ class ControllerV11(Controller): def _get_view_builder(self, req): base_url = req.application_url return views.flavors.ViewBuilderV11(base_url) + + def get_default_xmlns(self, req): + return common.XML_NS_V11 diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index c9d6ac532..e673e5f7b 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -20,13 +20,14 @@ from webob import exc from nova import flags from nova import utils from nova import wsgi +from nova.api.openstack import common from nova.api.openstack import faults FLAGS = flags.FLAGS -class Controller(wsgi.Controller): +class Controller(common.OpenstackController): """The image metadata API controller for the Openstack API""" def __init__(self): diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index e77100d7b..5550ee532 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -32,7 +32,7 @@ LOG = log.getLogger('nova.api.openstack.images') FLAGS = flags.FLAGS -class Controller(wsgi.Controller): +class Controller(common.OpenstackController): """Base `wsgi.Controller` for retrieving/displaying images.""" _serialization_metadata = { @@ -153,3 +153,6 @@ class ControllerV11(Controller): """Property to get the ViewBuilder class we need to use.""" base_url = request.application_url return images_view.ViewBuilderV11(base_url) + + def get_default_xmlns(self, req): + return common.XML_NS_V11 diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index efc7d193d..9877af191 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -31,8 +31,8 @@ from collections import defaultdict from webob.dec import wsgify from nova import wsgi +from nova.api.openstack import common from nova.api.openstack import faults -from nova.wsgi import Controller from nova.wsgi import Middleware @@ -43,7 +43,7 @@ PER_HOUR = 60 * 60 PER_DAY = 60 * 60 * 24 -class LimitsController(Controller): +class LimitsController(common.OpenstackController): """ Controller for accessing limits in the OpenStack API. """ diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index 45bbac99d..5c1390b9c 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -19,10 +19,11 @@ from webob import exc from nova import compute from nova import wsgi +from nova.api.openstack import common from nova.api.openstack import faults -class Controller(wsgi.Controller): +class Controller(common.OpenstackController): """ The server metadata API controller for the Openstack API """ def __init__(self): diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index f7696d918..64090d830 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -44,7 +44,7 @@ LOG = logging.getLogger('server') FLAGS = flags.FLAGS -class Controller(wsgi.Controller): +class Controller(common.OpenstackController): """ The Server API controller for the OpenStack API """ _serialization_metadata = { @@ -632,6 +632,9 @@ class ControllerV11(Controller): def _limit_items(self, items, req): return common.limited_by_marker(items, req) + def get_default_xmlns(self, req): + return common.XML_NS_V11 + class ServerCreateRequestXMLDeserializer(object): """ diff --git a/nova/api/openstack/shared_ip_groups.py b/nova/api/openstack/shared_ip_groups.py index ee7991d7f..996db3648 100644 --- a/nova/api/openstack/shared_ip_groups.py +++ b/nova/api/openstack/shared_ip_groups.py @@ -17,7 +17,7 @@ from webob import exc -from nova import wsgi +from nova.api.openstack import common from nova.api.openstack import faults @@ -32,7 +32,7 @@ def _translate_detail_keys(inst): return dict(sharedIpGroups=inst) -class Controller(wsgi.Controller): +class Controller(common.OpenstackController): """ The Shared IP Groups Controller for the Openstack API """ _serialization_metadata = { diff --git a/nova/api/openstack/users.py b/nova/api/openstack/users.py index d3ab3d553..077ccfc79 100644 --- a/nova/api/openstack/users.py +++ b/nova/api/openstack/users.py @@ -18,7 +18,6 @@ from webob import exc from nova import exception from nova import flags from nova import log as logging -from nova import wsgi from nova.api.openstack import common from nova.api.openstack import faults from nova.auth import manager @@ -35,7 +34,7 @@ def _translate_keys(user): admin=user.admin) -class Controller(wsgi.Controller): +class Controller(common.OpenstackController): _serialization_metadata = { 'application/xml': { diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index 846cb48a1..227ffecdc 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -13,12 +13,10 @@ # License for the specific language governing permissions and limitations # under the License. -import common - from nova import db from nova import flags from nova import log as logging -from nova import wsgi +from nova.api.openstack import common from nova.scheduler import api @@ -43,7 +41,7 @@ def _scrub_zone(zone): 'deleted', 'deleted_at', 'updated_at')) -class Controller(wsgi.Controller): +class Controller(common.OpenstackController): _serialization_metadata = { 'application/xml': { diff --git a/nova/tests/integrated/test_xml.py b/nova/tests/integrated/test_xml.py new file mode 100644 index 000000000..8a9754777 --- /dev/null +++ b/nova/tests/integrated/test_xml.py @@ -0,0 +1,56 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Justin Santa Barbara +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from nova import flags +from nova.log import logging +from nova.tests.integrated import integrated_helpers +from nova.api.openstack import common + + +LOG = logging.getLogger('nova.tests.integrated') + + +FLAGS = flags.FLAGS +FLAGS.verbose = True + + +class XmlTests(integrated_helpers._IntegratedTestBase): + """"Some basic XML sanity checks.""" + + def test_namespace_limits(self): + """/limits should have v1.0 namespace (hasn't changed in 1.1).""" + headers = {} + headers['Accept'] = 'application/xml' + + response = self.api.api_request('/limits', headers=headers) + data = response.read() + LOG.debug("data: %s" % data) + + prefix = ' Date: Wed, 30 Mar 2011 13:18:47 -0400 Subject: Implement quotas for the new v1.1 server metadata controller. Modified the compute API so that metadata is a dict (not an array) to ensure we are using unique key values for metadata. This is isn't explicit in the SPECs but it is implied by the new v1.1 spec since PUT requests modify individual items. Re-enabled the metadata integration test. --- nova/api/openstack/server_metadata.py | 30 ++++++-- nova/api/openstack/servers.py | 12 +--- nova/compute/api.py | 52 ++++++++------ nova/db/sqlalchemy/api.py | 5 +- nova/tests/api/openstack/test_server_metadata.py | 38 ++++++++++ nova/tests/integrated/test_servers.py | 88 ++++++++++++------------ 6 files changed, 139 insertions(+), 86 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index 45bbac99d..2a1249b10 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -18,6 +18,7 @@ from webob import exc from nova import compute +from nova import quota from nova import wsgi from nova.api.openstack import faults @@ -43,10 +44,13 @@ class Controller(wsgi.Controller): def create(self, req, server_id): context = req.environ['nova.context'] - body = self._deserialize(req.body, req.get_content_type()) - self.compute_api.update_or_create_instance_metadata(context, - server_id, - body['metadata']) + data = self._deserialize(req.body, req.get_content_type())['metadata'] + try: + self.compute_api.update_or_create_instance_metadata(context, + server_id, + data) + except quota.QuotaError as error: + self._handle_quota_error(error) return req.body def update(self, req, server_id, id): @@ -58,9 +62,13 @@ class Controller(wsgi.Controller): if len(body) > 1: expl = _('Request body contains too many items') raise exc.HTTPBadRequest(explanation=expl) - self.compute_api.update_or_create_instance_metadata(context, - server_id, - body) + try: + self.compute_api.update_or_create_instance_metadata(context, + server_id, + body) + except quota.QuotaError as error: + self._handle_quota_error(error) + return req.body def show(self, req, server_id, id): @@ -76,3 +84,11 @@ class Controller(wsgi.Controller): """ Deletes an existing metadata """ context = req.environ['nova.context'] self.compute_api.delete_instance_metadata(context, server_id, id) + + def _handle_quota_error(self, error): + """ + Reraise quota errors as api-specific http exceptions + """ + if error.code == "MetadataLimitExceeded": + raise exc.HTTPBadRequest(explanation=error.message) + raise error diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 6bd173bb8..966680c41 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -134,16 +134,6 @@ class Controller(wsgi.Controller): kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image( req, image_id) - # Metadata is a list, not a Dictionary, because we allow duplicate keys - # (even though JSON can't encode this) - # In future, we may not allow duplicate keys. - # However, the CloudServers API is not definitive on this front, - # and we want to be compatible. - metadata = [] - if env['server'].get('metadata'): - for k, v in env['server']['metadata'].items(): - metadata.append({'key': k, 'value': v}) - personality = env['server'].get('personality') injected_files = [] if personality: @@ -161,7 +151,7 @@ class Controller(wsgi.Controller): display_description=env['server']['name'], key_name=key_name, key_data=key_data, - metadata=metadata, + metadata=env['server'].get('metadata', {}), injected_files=injected_files) except quota.QuotaError as error: self._handle_quota_error(error) diff --git a/nova/compute/api.py b/nova/compute/api.py index 1dbd73f8f..678900fd9 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -100,26 +100,12 @@ class API(base.Base): if len(content) > content_limit: raise quota.QuotaError(code="OnsetFileContentLimitExceeded") - def create(self, context, instance_type, - image_id, kernel_id=None, ramdisk_id=None, - min_count=1, max_count=1, - display_name='', display_description='', - key_name=None, key_data=None, security_group='default', - availability_zone=None, user_data=None, metadata=[], - injected_files=None): - """Create the number of instances requested if quota and - other arguments check out ok.""" - - type_data = instance_types.get_instance_type(instance_type) - num_instances = quota.allowed_instances(context, max_count, type_data) - if num_instances < min_count: - pid = context.project_id - LOG.warn(_("Quota exceeeded for %(pid)s," - " tried to run %(min_count)s instances") % locals()) - raise quota.QuotaError(_("Instance quota exceeded. You can only " - "run %s more instances of this type.") % - num_instances, "InstanceLimitExceeded") + def _check_metadata_properties_quota(self, context, metadata={}): + """ + Enforce quota limits on metadata properties + Raises a QuotaError if any limit is exceeded + """ num_metadata = len(metadata) quota_metadata = quota.allowed_metadata_items(context, num_metadata) if quota_metadata < num_metadata: @@ -133,9 +119,7 @@ class API(base.Base): # Because metadata is stored in the DB, we hard-code the size limits # In future, we may support more variable length strings, so we act # as if this is quota-controlled for forwards compatibility - for metadata_item in metadata: - k = metadata_item['key'] - v = metadata_item['value'] + for k, v in metadata.iteritems(): if len(k) > 255 or len(v) > 255: pid = context.project_id msg = (_("Quota exceeeded for %(pid)s," @@ -144,6 +128,27 @@ class API(base.Base): LOG.warn(msg) raise quota.QuotaError(msg, "MetadataLimitExceeded") + def create(self, context, instance_type, + image_id, kernel_id=None, ramdisk_id=None, + min_count=1, max_count=1, + display_name='', display_description='', + key_name=None, key_data=None, security_group='default', + availability_zone=None, user_data=None, metadata={}, + injected_files=None): + """Create the number of instances requested if quota and + other arguments check out ok.""" + + type_data = instance_types.get_instance_type(instance_type) + num_instances = quota.allowed_instances(context, max_count, type_data) + if num_instances < min_count: + pid = context.project_id + LOG.warn(_("Quota exceeeded for %(pid)s," + " tried to run %(min_count)s instances") % locals()) + raise quota.QuotaError(_("Instance quota exceeded. You can only " + "run %s more instances of this type.") % + num_instances, "InstanceLimitExceeded") + + self._check_metadata_properties_quota(context, metadata) self._check_injected_file_quota(context, injected_files) image = self.image_service.show(context, image_id) @@ -705,5 +710,8 @@ class API(base.Base): def update_or_create_instance_metadata(self, context, instance_id, metadata): """Updates or creates instance metadata""" + combined_metadata = self.get_instance_metadata(context, instance_id) + combined_metadata.update(metadata) + self._check_metadata_properties_quota(context, combined_metadata) self.db.instance_metadata_update_or_create(context, instance_id, metadata) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index b2a13a01b..b591eb871 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -768,9 +768,10 @@ def instance_create(context, values): metadata = values.get('metadata') metadata_refs = [] if metadata: - for metadata_item in metadata: + for k, v in metadata.iteritems(): metadata_ref = models.InstanceMetadata() - metadata_ref.update(metadata_item) + metadata_ref['key'] = k + metadata_ref['value'] = v metadata_refs.append(metadata_ref) values['metadata'] = metadata_refs diff --git a/nova/tests/api/openstack/test_server_metadata.py b/nova/tests/api/openstack/test_server_metadata.py index c8d456472..3ae97c2ef 100644 --- a/nova/tests/api/openstack/test_server_metadata.py +++ b/nova/tests/api/openstack/test_server_metadata.py @@ -21,11 +21,17 @@ import unittest import webob +from nova import flags from nova.api import openstack from nova.tests.api.openstack import fakes import nova.wsgi +FLAGS = flags.FLAGS + +def return_create_instance_metadata_max(context, server_id, metadata): + return stub_max_server_metadata() + def return_create_instance_metadata(context, server_id, metadata): return stub_server_metadata() @@ -53,6 +59,12 @@ def stub_server_metadata(): return metadata +def stub_max_server_metadata(): + metadata = {"metadata": {}} + for num in range(FLAGS.quota_metadata_items): + metadata['metadata']['key%i' % num] = "blah" + return metadata + class ServerMetaDataTest(unittest.TestCase): def setUp(self): @@ -162,3 +174,29 @@ class ServerMetaDataTest(unittest.TestCase): req.headers["content-type"] = "application/json" res = req.get_response(fakes.wsgi_app()) self.assertEqual(400, res.status_int) + + def test_too_many_metadata_items_on_create(self): + self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + return_create_instance_metadata) + data = {"metadata": {}} + 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/meta') + req.environ['api.version'] = '1.1' + req.method = 'POST' + req.body = json_string + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) + + def test_to_many_metadata_items_on_update_item(self): + self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', + return_create_instance_metadata_max) + req = webob.Request.blank('/v1.1/servers/1/meta/key1') + req.environ['api.version'] = '1.1' + req.method = 'PUT' + req.body = '{"a new key": "a new value"}' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) diff --git a/nova/tests/integrated/test_servers.py b/nova/tests/integrated/test_servers.py index 749ea8955..8175659a3 100644 --- a/nova/tests/integrated/test_servers.py +++ b/nova/tests/integrated/test_servers.py @@ -134,50 +134,50 @@ class ServersTest(integrated_helpers._IntegratedTestBase): # Should be gone self.assertFalse(found_server) -# TODO(justinsb): Enable this unit test when the metadata bug is fixed -# def test_create_server_with_metadata(self): -# """Creates a server with metadata""" -# -# # Build the server data gradually, checking errors along the way -# server = self._build_minimal_create_server_request() -# -# for metadata_count in range(30): -# metadata = {} -# for i in range(metadata_count): -# metadata['key_%s' % i] = 'value_%s' % i -# server['metadata'] = metadata -# -# 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'] -# # Reenable when bug fixed -# # self.assertEqual(metadata, created_server.get('metadata')) -# -# # Check it's there -# found_server = self.api.get_server(created_server_id) -# self.assertEqual(created_server_id, found_server['id']) -# self.assertEqual(metadata, found_server.get('metadata')) -# -# # The server should also be in the all-servers details list -# servers = self.api.get_servers(detail=True) -# server_map = dict((server['id'], server) for server in servers) -# found_server = server_map.get(created_server_id) -# self.assertTrue(found_server) -# # Details do include metadata -# self.assertEqual(metadata, found_server.get('metadata')) -# -# # The server should also be in the all-servers summary list -# servers = self.api.get_servers(detail=False) -# server_map = dict((server['id'], server) for server in servers) -# found_server = server_map.get(created_server_id) -# self.assertTrue(found_server) -# # Summary should not include metadata -# self.assertFalse(found_server.get('metadata')) -# -# # Cleanup -# self._delete_server(created_server_id) + def test_create_server_with_metadata(self): + """Creates a server with metadata""" + + # Build the server data gradually, checking errors along the way + server = self._build_minimal_create_server_request() + + metadata = {} + for i in range(30): + metadata['key_%s' % i] = 'value_%s' % i + + server['metadata'] = metadata + + 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'] + + # Reenable when bug fixed + self.assertEqual(metadata, created_server.get('metadata')) + # Check it's there + + found_server = self.api.get_server(created_server_id) + self.assertEqual(created_server_id, found_server['id']) + self.assertEqual(metadata, found_server.get('metadata')) + + # The server should also be in the all-servers details list + servers = self.api.get_servers(detail=True) + server_map = dict((server['id'], server) for server in servers) + found_server = server_map.get(created_server_id) + self.assertTrue(found_server) + # Details do include metadata + self.assertEqual(metadata, found_server.get('metadata')) + + # The server should also be in the all-servers summary list + servers = self.api.get_servers(detail=False) + server_map = dict((server['id'], server) for server in servers) + found_server = server_map.get(created_server_id) + self.assertTrue(found_server) + # Summary should not include metadata + self.assertFalse(found_server.get('metadata')) + + # Cleanup + self._delete_server(created_server_id) if __name__ == "__main__": -- cgit From 23776e5b2bdb73df10be590b589c34788c28023a Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Wed, 30 Mar 2011 12:28:09 -0500 Subject: Avoid hard dependencies --- nova/virt/vmwareapi_conn.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py index 87c3fa299..34d29a4e5 100644 --- a/nova/virt/vmwareapi_conn.py +++ b/nova/virt/vmwareapi_conn.py @@ -43,10 +43,12 @@ from nova import flags from nova import log as logging from nova import utils from nova.virt.vmwareapi import error_util -from nova.virt.vmwareapi import vim from nova.virt.vmwareapi import vim_util from nova.virt.vmwareapi.vmops import VMWareVMOps + +vim = None + LOG = logging.getLogger("nova.virt.vmwareapi_conn") FLAGS = flags.FLAGS @@ -78,6 +80,13 @@ flags.DEFINE_string('vmwareapi_vlan_interface', TIME_BETWEEN_API_CALL_RETRIES = 2.0 +def get_imported_vim(): + """Avoid any hard dependencies.""" + global vim + if vim is None: + vim = __import__("nova.virt.vmwareapi.vim") + + class Failure(Exception): """Base Exception class for handling task failures.""" @@ -109,7 +118,7 @@ class VMWareESXConnection(object): def __init__(self, host_ip, host_username, host_password, api_retry_count, scheme="https"): session = VMWareAPISession(host_ip, host_username, host_password, - api_retry_count, scheme=scheme) + api_retry_count, scheme=scheme) self._vmops = VMWareVMOps(session) def init_host(self, host): @@ -204,6 +213,7 @@ class VMWareAPISession(object): self._session_id = None self.vim = None self._create_session() + get_imported_vim() def _get_vim_object(self): """Create the VIM Object instance.""" -- cgit From 82759db16019509b9ca77fb7d86015d29c598f70 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Wed, 30 Mar 2011 13:34:45 -0400 Subject: pep8 fixes. --- nova/tests/api/openstack/test_server_metadata.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_server_metadata.py b/nova/tests/api/openstack/test_server_metadata.py index 3ae97c2ef..fc51370f6 100644 --- a/nova/tests/api/openstack/test_server_metadata.py +++ b/nova/tests/api/openstack/test_server_metadata.py @@ -29,9 +29,11 @@ import nova.wsgi FLAGS = flags.FLAGS + def return_create_instance_metadata_max(context, server_id, metadata): return stub_max_server_metadata() + def return_create_instance_metadata(context, server_id, metadata): return stub_server_metadata() @@ -65,6 +67,7 @@ def stub_max_server_metadata(): metadata['metadata']['key%i' % num] = "blah" return metadata + class ServerMetaDataTest(unittest.TestCase): def setUp(self): -- cgit From ee709d39e6c66ec4aad722fb20951ae9d714f259 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Wed, 30 Mar 2011 10:41:22 -0700 Subject: Some tests actually tested for the lack of a namespace :-) --- nova/api/openstack/extensions.py | 3 ++- nova/api/openstack/faults.py | 5 +++-- nova/api/openstack/images.py | 3 --- nova/tests/api/openstack/test_images.py | 16 +++++++++++----- 4 files changed, 16 insertions(+), 11 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 75f369cac..7ea7afef6 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -156,7 +156,8 @@ class ResponseExtensionController(common.OpenstackController): body = res.body headers = res.headers except AttributeError: - body = self._serialize(res, content_type) + default_xmlns = None + body = self._serialize(res, content_type, default_xmlns) headers = {"Content-Type": content_type} res = webob.Response() res.body = body diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py index 0e9c4b26f..a05e97021 100644 --- a/nova/api/openstack/faults.py +++ b/nova/api/openstack/faults.py @@ -20,10 +20,10 @@ import webob.dec import webob.exc from nova import wsgi +from nova.api.openstack import common class Fault(webob.exc.HTTPException): - """An RS API fault response.""" _fault_names = { @@ -57,7 +57,8 @@ class Fault(webob.exc.HTTPException): fault_data[fault_name]['retryAfter'] = retry # 'code' is an attribute on the fault tag itself metadata = {'application/xml': {'attributes': {fault_name: 'code'}}} - serializer = wsgi.Serializer(metadata) + default_xmlns = common.XML_NS_V10 + serializer = wsgi.Serializer(metadata, default_xmlns) content_type = req.best_match_content_type() self.wrapped_exc.body = serializer.serialize(fault_data, content_type) return self.wrapped_exc diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 5550ee532..77baf5947 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -13,8 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -import datetime - import webob.exc from nova import compute @@ -22,7 +20,6 @@ from nova import exception from nova import flags from nova import log from nova import utils -from nova import wsgi from nova.api.openstack import common from nova.api.openstack import faults from nova.api.openstack.views import images as images_view diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 57e447dce..ff3fe8976 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -146,7 +146,7 @@ class LocalImageServiceTest(_BaseImageServiceTests): for x in [1, 2, 3]: tempfile.mkstemp(prefix='ami-', dir=self.tempdir) # create some valid image directories names - for x in ["1485baed", "1a60f0ee", "3123a73d"]: + for x in ["1485baed", "1a60f0ee", "3123a73d"]: os.makedirs(os.path.join(self.tempdir, x)) found_image_ids = self.service._ids() self.assertEqual(True, isinstance(found_image_ids, list)) @@ -334,7 +334,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): name="public image" updated="%(expected_now)s" created="%(expected_now)s" - status="ACTIVE" /> + status="ACTIVE" + xmlns="http://docs.rackspacecloud.com/servers/api/v1.0" /> """ % (locals())) self.assertEqual(expected_image.toxml(), actual_image.toxml()) @@ -353,7 +354,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): name="public image" updated="%(expected_now)s" created="%(expected_now)s" - status="ACTIVE"> + status="ACTIVE" + xmlns="http://docs.openstack.org/compute/api/v1.1"> + Image not found. @@ -422,8 +425,11 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): response = request.get_response(fakes.wsgi_app()) self.assertEqual(404, response.status_int) + # NOTE(justinsb): I believe this should still use the v1.0 XSD, + # because the element hasn't changed definition expected = minidom.parseString(""" - + Image not found. -- cgit From d2aa73efed6f4eb731c4fe8dca3f4ceb5b36caeb Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Wed, 30 Mar 2011 10:49:09 -0700 Subject: More tests that were checking for no-namespace --- nova/tests/api/openstack/test_faults.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_faults.py b/nova/tests/api/openstack/test_faults.py index 7667753f4..505e7e308 100644 --- a/nova/tests/api/openstack/test_faults.py +++ b/nova/tests/api/openstack/test_faults.py @@ -20,6 +20,7 @@ import webob.dec import webob.exc from nova import test +from nova.api.openstack import common from nova.api.openstack import faults @@ -30,8 +31,11 @@ class TestFaults(test.TestCase): f = faults.Fault(webob.exc.HTTPBadRequest(explanation='scram')) resp = req.get_response(f) - first_two_words = resp.body.strip().split()[:2] - self.assertEqual(first_two_words, ['']) + first_three_words = resp.body.strip().split()[:3] + self.assertEqual(first_three_words, + ['' % common.XML_NS_V10]) body_without_spaces = ''.join(resp.body.split()) self.assertTrue('scram' in body_without_spaces) @@ -41,8 +45,11 @@ class TestFaults(test.TestCase): headers={'Retry-After': 4}) f = faults.Fault(exc) resp = req.get_response(f) - first_two_words = resp.body.strip().split()[:2] - self.assertEqual(first_two_words, ['']) + first_three_words = resp.body.strip().split()[:3] + self.assertEqual(first_three_words, + ['' % common.XML_NS_V10]) body_sans_spaces = ''.join(resp.body.split()) self.assertTrue('sorry' in body_sans_spaces) self.assertTrue('4' in body_sans_spaces) -- cgit From 168fdb09f369287f3ec8c19ef73915280ff79d20 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Wed, 30 Mar 2011 11:15:16 -0700 Subject: Refixed unit test to check XML ns --- nova/tests/api/openstack/test_faults.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_faults.py b/nova/tests/api/openstack/test_faults.py index b3433f45f..4d86ffb26 100644 --- a/nova/tests/api/openstack/test_faults.py +++ b/nova/tests/api/openstack/test_faults.py @@ -48,10 +48,10 @@ class TestFaults(test.TestCase): response = request.get_response(fault) expected = self._prepare_xml(""" - + scram - """) + """ % common.XML_NS_V10) actual = self._prepare_xml(response.body) self.assertEqual(response.content_type, "application/xml") @@ -92,11 +92,11 @@ class TestFaults(test.TestCase): response = request.get_response(fault) expected = self._prepare_xml(""" - + sorry 4 - """) + """ % common.XML_NS_V10) actual = self._prepare_xml(response.body) self.assertEqual(expected, actual) -- cgit From be4be55bf03c907f46e76fffe3457743915d734a Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Wed, 30 Mar 2011 13:50:02 -0500 Subject: Handle in vim.py --- nova/virt/vmwareapi/vim.py | 15 +++++++++++---- nova/virt/vmwareapi_conn.py | 10 ---------- 2 files changed, 11 insertions(+), 14 deletions(-) (limited to 'nova') diff --git a/nova/virt/vmwareapi/vim.py b/nova/virt/vmwareapi/vim.py index ba14f1512..1c850595d 100644 --- a/nova/virt/vmwareapi/vim.py +++ b/nova/virt/vmwareapi/vim.py @@ -21,10 +21,14 @@ Classes for making VMware VI SOAP calls. import httplib -from suds import WebFault -from suds.client import Client -from suds.plugin import MessagePlugin -from suds.sudsobject import Property +try: + suds = True + from suds import WebFault + from suds.client import Client + from suds.plugin import MessagePlugin + from suds.sudsobject import Property +except ImportError: + suds = False from nova import flags from nova.virt.vmwareapi import error_util @@ -75,6 +79,9 @@ class Vim: protocol: http or https host : ESX IPAddress[:port] or ESX Hostname[:port] """ + if not suds: + raise Exception(_("Unable to import suds.")) + self._protocol = protocol self._host_name = host wsdl_url = FLAGS.vmwareapi_wsdl_loc diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py index 34d29a4e5..b909e659d 100644 --- a/nova/virt/vmwareapi_conn.py +++ b/nova/virt/vmwareapi_conn.py @@ -47,8 +47,6 @@ from nova.virt.vmwareapi import vim_util from nova.virt.vmwareapi.vmops import VMWareVMOps -vim = None - LOG = logging.getLogger("nova.virt.vmwareapi_conn") FLAGS = flags.FLAGS @@ -80,13 +78,6 @@ flags.DEFINE_string('vmwareapi_vlan_interface', TIME_BETWEEN_API_CALL_RETRIES = 2.0 -def get_imported_vim(): - """Avoid any hard dependencies.""" - global vim - if vim is None: - vim = __import__("nova.virt.vmwareapi.vim") - - class Failure(Exception): """Base Exception class for handling task failures.""" @@ -213,7 +204,6 @@ class VMWareAPISession(object): self._session_id = None self.vim = None self._create_session() - get_imported_vim() def _get_vim_object(self): """Create the VIM Object instance.""" -- cgit From 2eadc55dbd66af7b5adb3c21fe9cc91cd04b0f8b Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Wed, 30 Mar 2011 13:56:02 -0500 Subject: Whoops --- nova/virt/vmwareapi_conn.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py index b909e659d..20c1b2b45 100644 --- a/nova/virt/vmwareapi_conn.py +++ b/nova/virt/vmwareapi_conn.py @@ -43,6 +43,7 @@ from nova import flags from nova import log as logging from nova import utils from nova.virt.vmwareapi import error_util +from nova.virt.vmwareapi import vim from nova.virt.vmwareapi import vim_util from nova.virt.vmwareapi.vmops import VMWareVMOps -- cgit From 323eb60884cf8736448d997068d32f252d22e7f3 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 30 Mar 2011 12:05:25 -0700 Subject: Key type values in ec2_api off of container format --- nova/api/ec2/cloud.py | 44 +++++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 7ba8dfbea..1e6c4d1db 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -146,7 +146,7 @@ class CloudController(object): floating_ip = db.instance_get_floating_address(ctxt, instance_ref['id']) ec2_id = ec2utils.id_to_ec2_id(instance_ref['id']) - image_ec2_id = self._image_ec2_id(instance_ref['image_id'], 'machine') + image_ec2_id = self._image_ec2_id(instance_ref['image_id'], 'ami') data = { 'user-data': base64.b64decode(instance_ref['user_data']), 'meta-data': { @@ -176,7 +176,7 @@ class CloudController(object): for image_type in ['kernel', 'ramdisk']: if '%s_id' % image_type in instance_ref: ec2_id = self._image_ec2_id(instance_ref['%s_id' % image_type], - image_type) + self._image_type(image_type)) data['meta-data']['%s-id' % image_type] = ec2_id if False: # TODO(vish): store ancestor ids @@ -862,13 +862,27 @@ class CloudController(object): self.compute_api.update(context, instance_id=instance_id, **kwargs) return True - _type_prefix_map = {'machine': 'ami', - 'kernel': 'aki', - 'ramdisk': 'ari'} + @staticmethod + def _image_type(image_type): + """Converts to a three letter image type. - def _image_ec2_id(self, image_id, image_type='machine'): - prefix = self._type_prefix_map[image_type] - template = prefix + '-%08x' + aki, kernel => aki + ari, ramdisk => ari + anything else => ami + + """ + if image_type == 'kernel': + return 'aki' + if image_type == 'ramdisk': + return 'ari' + if image_type not in ['aki', 'ari']: + return 'ami' + return image_type + + @staticmethod + def _image_ec2_id(image_id, image_type='ami'): + """Returns image ec2_id using id and three letter type""" + template = image_type + '-%08x' return ec2utils.id_to_ec2_id(int(image_id), template=template) def _get_image(self, context, ec2_id): @@ -881,7 +895,7 @@ class CloudController(object): def _format_image(self, image): """Convert from format defined by BaseImageService to S3 format.""" i = {} - image_type = image['properties'].get('type') + image_type = self._image_type(image.get('container_format')) ec2_id = self._image_ec2_id(image.get('id'), image_type) name = image.get('name') if name: @@ -890,16 +904,19 @@ class CloudController(object): i['imageId'] = ec2_id kernel_id = image['properties'].get('kernel_id') if kernel_id: - i['kernelId'] = self._image_ec2_id(kernel_id, 'kernel') + i['kernelId'] = self._image_ec2_id(kernel_id, 'aki') ramdisk_id = image['properties'].get('ramdisk_id') if ramdisk_id: - i['ramdiskId'] = self._image_ec2_id(ramdisk_id, 'ramdisk') + i['ramdiskId'] = self._image_ec2_id(ramdisk_id, 'ari') i['imageOwnerId'] = image['properties'].get('owner_id') i['imageLocation'] = image['properties'].get('image_location') i['imageState'] = image['properties'].get('image_state') i['displayName'] = image.get('name') i['description'] = image.get('description') - i['type'] = image_type + display_mapping = {'aki': 'kernel', + 'ari': 'ramdisk', + 'ami': 'machine'} + i['type'] = display_mapping.get(image_type) i['isPublic'] = str(image['properties'].get('is_public', '')) == 'True' i['architecture'] = image['properties'].get('architecture') return i @@ -932,8 +949,9 @@ class CloudController(object): image_location = kwargs['name'] metadata = {'properties': {'image_location': image_location}} image = self.image_service.create(context, metadata) + image_type = self._image_type(image.get('container_format')) image_id = self._image_ec2_id(image['id'], - image['properties']['type']) + image_type) msg = _("Registered image %(image_location)s with" " id %(image_id)s") % locals() LOG.audit(msg, context=context) -- cgit From 1703592992ebdd5bbf19952f79f05022a4cdc849 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 30 Mar 2011 12:34:10 -0700 Subject: remove all references to image_type and change nova-manage upload to set container format more intelligently --- nova/api/openstack/servers.py | 2 +- nova/image/fake.py | 6 +++--- nova/image/s3.py | 1 - nova/tests/api/openstack/test_image_metadata.py | 2 -- nova/virt/libvirt_conn.py | 1 - 5 files changed, 4 insertions(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index f7696d918..d640e8684 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -562,7 +562,7 @@ class Controller(wsgi.Controller): _("Cannot build from image %(image_id)s, status not active") % locals()) - if image_meta['properties']['disk_format'] != 'ami': + if image_meta.get('container_format') != 'ami': return None, None try: diff --git a/nova/image/fake.py b/nova/image/fake.py index 08302d6eb..d1c62757f 100644 --- a/nova/image/fake.py +++ b/nova/image/fake.py @@ -44,10 +44,10 @@ class FakeImageService(service.BaseImageService): 'created_at': timestamp, 'updated_at': timestamp, 'status': 'active', - 'type': 'machine', + 'container_format': 'ami', + 'disk_format': 'raw', 'properties': {'kernel_id': FLAGS.null_kernel, - 'ramdisk_id': FLAGS.null_kernel, - 'disk_format': 'ami'} + 'ramdisk_id': FLAGS.null_kernel} } self.create(None, image) super(FakeImageService, self).__init__() diff --git a/nova/image/s3.py b/nova/image/s3.py index ddec5f3aa..203bedc49 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -177,7 +177,6 @@ class S3ImageService(service.BaseImageService): properties['ramdisk_id'] = ec2utils.ec2_id_to_id(ramdisk_id) properties['is_public'] = False - properties['type'] = image_type metadata.update({'disk_format': image_format, 'container_format': image_format, 'status': 'queued', diff --git a/nova/tests/api/openstack/test_image_metadata.py b/nova/tests/api/openstack/test_image_metadata.py index 9be753f84..7c3287006 100644 --- a/nova/tests/api/openstack/test_image_metadata.py +++ b/nova/tests/api/openstack/test_image_metadata.py @@ -45,7 +45,6 @@ class ImageMetaDataTest(unittest.TestCase): 'is_public': True, 'deleted_at': None, 'properties': { - 'type': 'ramdisk', 'key1': 'value1', 'key2': 'value2' }, @@ -62,7 +61,6 @@ class ImageMetaDataTest(unittest.TestCase): 'is_public': True, 'deleted_at': None, 'properties': { - 'type': 'ramdisk', 'key1': 'value1', 'key2': 'value2' }, diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index f34ea7225..adcb2ffa3 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -424,7 +424,6 @@ class LibvirtConnection(driver.ComputeDriver): 'container_format': base['container_format'], 'is_public': False, 'properties': {'architecture': base['architecture'], - 'type': base['type'], 'name': '%s.%s' % (base['name'], image_id), 'kernel_id': instance['kernel_id'], 'image_location': 'snapshot', -- cgit From c29dc77c6f42c5a345ee6b510a373236d7988440 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 30 Mar 2011 12:46:22 -0700 Subject: fixed ordering and spacing --- nova/compute/api.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index a5f6afb02..996955fe3 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -37,8 +37,11 @@ from nova.compute import instance_types from nova.scheduler import api as scheduler_api from nova.db import base -FLAGS = flags.FLAGS + LOG = logging.getLogger('nova.compute.api') + + +FLAGS = flags.FLAGS flags.DECLARE('vncproxy_topic', 'nova.vnc') -- cgit From 176d98aa179b174c4e4a5621f723ef7b9145e3cb Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Wed, 30 Mar 2011 15:47:12 -0400 Subject: Update docstrings and spacing. --- nova/api/openstack/server_metadata.py | 4 +--- nova/compute/api.py | 6 +----- nova/tests/api/openstack/test_server_metadata.py | 22 +++++++++++----------- 3 files changed, 13 insertions(+), 19 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index 2a1249b10..618665082 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -86,9 +86,7 @@ class Controller(wsgi.Controller): self.compute_api.delete_instance_metadata(context, server_id, id) def _handle_quota_error(self, error): - """ - Reraise quota errors as api-specific http exceptions - """ + """Reraise quota errors as api-specific http exceptions""" if error.code == "MetadataLimitExceeded": raise exc.HTTPBadRequest(explanation=error.message) raise error diff --git a/nova/compute/api.py b/nova/compute/api.py index 678900fd9..c4aa581be 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -101,11 +101,7 @@ class API(base.Base): raise quota.QuotaError(code="OnsetFileContentLimitExceeded") def _check_metadata_properties_quota(self, context, metadata={}): - """ - Enforce quota limits on metadata properties - - Raises a QuotaError if any limit is exceeded - """ + """Enforce quota limits on metadata properties""" num_metadata = len(metadata) quota_metadata = quota.allowed_metadata_items(context, num_metadata) if quota_metadata < num_metadata: diff --git a/nova/tests/api/openstack/test_server_metadata.py b/nova/tests/api/openstack/test_server_metadata.py index fc51370f6..862e14b7c 100644 --- a/nova/tests/api/openstack/test_server_metadata.py +++ b/nova/tests/api/openstack/test_server_metadata.py @@ -84,7 +84,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_index(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', - return_server_metadata) + return_server_metadata) req = webob.Request.blank('/v1.1/servers/1/meta') req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) @@ -94,7 +94,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_index_no_data(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', - return_empty_server_metadata) + return_empty_server_metadata) req = webob.Request.blank('/v1.1/servers/1/meta') req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) @@ -104,7 +104,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_show(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', - return_server_metadata) + return_server_metadata) req = webob.Request.blank('/v1.1/servers/1/meta/key5') req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) @@ -114,7 +114,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_show_meta_not_found(self): self.stubs.Set(nova.db.api, 'instance_metadata_get', - return_empty_server_metadata) + return_empty_server_metadata) req = webob.Request.blank('/v1.1/servers/1/meta/key6') req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) @@ -123,7 +123,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_delete(self): self.stubs.Set(nova.db.api, 'instance_metadata_delete', - delete_server_metadata) + delete_server_metadata) req = webob.Request.blank('/v1.1/servers/1/meta/key5') req.environ['api.version'] = '1.1' req.method = 'DELETE' @@ -132,7 +132,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_create(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', - return_create_instance_metadata) + return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/meta') req.environ['api.version'] = '1.1' req.method = 'POST' @@ -145,7 +145,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_update_item(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', - return_create_instance_metadata) + return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/meta/key1') req.environ['api.version'] = '1.1' req.method = 'PUT' @@ -158,7 +158,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_update_item_too_many_keys(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', - return_create_instance_metadata) + return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/meta/key1') req.environ['api.version'] = '1.1' req.method = 'PUT' @@ -169,7 +169,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_update_item_body_uri_mismatch(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', - return_create_instance_metadata) + return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/meta/bad') req.environ['api.version'] = '1.1' req.method = 'PUT' @@ -180,7 +180,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_too_many_metadata_items_on_create(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', - return_create_instance_metadata) + return_create_instance_metadata) data = {"metadata": {}} for num in range(FLAGS.quota_metadata_items + 1): data['metadata']['key%i' % num] = "blah" @@ -195,7 +195,7 @@ class ServerMetaDataTest(unittest.TestCase): def test_to_many_metadata_items_on_update_item(self): self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', - return_create_instance_metadata_max) + return_create_instance_metadata_max) req = webob.Request.blank('/v1.1/servers/1/meta/key1') req.environ['api.version'] = '1.1' req.method = 'PUT' -- cgit From b54b6c200092054e38af1fa1e5885fe915e53149 Mon Sep 17 00:00:00 2001 From: John Tran Date: Wed, 30 Mar 2011 13:10:11 -0700 Subject: submitting a unit test for terminate_instance --- nova/tests/test_cloud.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'nova') diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 00803d0ad..be1939193 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -36,6 +36,7 @@ from nova import rpc from nova import service from nova import test from nova import utils +from nova import exception from nova.auth import manager from nova.compute import power_state from nova.api.ec2 import cloud @@ -310,6 +311,19 @@ class CloudTestCase(test.TestCase): LOG.debug(_("Terminating instance %s"), instance_id) rv = self.compute.terminate_instance(instance_id) + def test_terminate_instances(self): + inst1 = db.instance_create(self.context, {'reservation_id': 'a', + 'image_id': 1, + 'host': 'host1'}) + terminate_instances = self.cloud.terminate_instances + # valid instance_id + result = terminate_instances(self.context, ['i-00000001']) + self.assertTrue(result) + # non-existing instance_id + self.assertRaises(exception.InstanceNotFound, terminate_instances, + self.context, ['i-2']) + db.instance_destroy(self.context, inst1['id']) + def test_update_of_instance_display_fields(self): inst = db.instance_create(self.context, {}) ec2_id = ec2utils.id_to_ec2_id(inst['id']) -- cgit From ba5715b46d82498ed30aa146294850a134022617 Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Wed, 30 Mar 2011 13:32:13 -0700 Subject: Fix for LP Bug #745152 --- nova/network/api.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'nova') diff --git a/nova/network/api.py b/nova/network/api.py index 4ee1148cb..424c3f592 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -66,6 +66,18 @@ class API(base.Base): if isinstance(fixed_ip, str) or isinstance(fixed_ip, unicode): fixed_ip = self.db.fixed_ip_get_by_address(context, fixed_ip) floating_ip = self.db.floating_ip_get_by_address(context, floating_ip) + # Check if the floating ip address is allocated + if floating_ip['project_id'] is None: + raise exception.ApiError(_("Address (%s) is not allocated") + % floating_ip['address']) + # Check if the floating ip address is allocated to the same project + if floating_ip['project_id'] != context.project_id: + LOG.warn(_("Address (%s) is not allocated to your project " + "(%s)"), floating_ip['address'], context.project_id) + raise exception.ApiError(_("Address (%s) is not " + "allocated to your project (%s)") % + (floating_ip['address'], + context.project_id)) # NOTE(vish): Perhaps we should just pass this on to compute and # let compute communicate with network. host = fixed_ip['network']['host'] -- cgit From b8173ba9fe80045665064208e66f46cca75fd3ba Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 30 Mar 2011 13:48:07 -0700 Subject: review cleanup --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 1e6c4d1db..3541e49ca 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -881,7 +881,7 @@ class CloudController(object): @staticmethod def _image_ec2_id(image_id, image_type='ami'): - """Returns image ec2_id using id and three letter type""" + """Returns image ec2_id using id and three letter type.""" template = image_type + '-%08x' return ec2utils.id_to_ec2_id(int(image_id), template=template) -- cgit From bb1c49bc324956241383add85297510842d4464c Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Wed, 30 Mar 2011 15:00:47 -0700 Subject: fixed review comment for i18n string multiple replacement strings need to use dictionary format --- nova/network/api.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/network/api.py b/nova/network/api.py index 424c3f592..47e56ebc6 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -68,16 +68,20 @@ class API(base.Base): floating_ip = self.db.floating_ip_get_by_address(context, floating_ip) # Check if the floating ip address is allocated if floating_ip['project_id'] is None: - raise exception.ApiError(_("Address (%s) is not allocated") - % floating_ip['address']) + raise exception.ApiError(_("Address (%(address)s) is not " + "allocated") % {'address': + floating_ip['address']}) # Check if the floating ip address is allocated to the same project if floating_ip['project_id'] != context.project_id: - LOG.warn(_("Address (%s) is not allocated to your project " - "(%s)"), floating_ip['address'], context.project_id) - raise exception.ApiError(_("Address (%s) is not " - "allocated to your project (%s)") % - (floating_ip['address'], - context.project_id)) + LOG.warn(_("Address (%(address)s) is not allocated to your " + "project (%(project)s)"), + {'address': floating_ip['address'], + 'project': context.project_id}) + raise exception.ApiError(_("Address (%(address)s) is not " + "allocated to your project" + "(%(project)s)") % + {'address': floating_ip['address'], + 'project': context.project_id}) # NOTE(vish): Perhaps we should just pass this on to compute and # let compute communicate with network. host = fixed_ip['network']['host'] -- cgit From 7d86a9e478c92ac9f79039c4592c6355c91b8b61 Mon Sep 17 00:00:00 2001 From: Tushar Patil Date: Wed, 30 Mar 2011 15:10:55 -0700 Subject: fixed review comment for i18n string multiple replacement strings need to use dictionary format --- nova/network/api.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/network/api.py b/nova/network/api.py index 47e56ebc6..c56e3062b 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -68,9 +68,8 @@ class API(base.Base): floating_ip = self.db.floating_ip_get_by_address(context, floating_ip) # Check if the floating ip address is allocated if floating_ip['project_id'] is None: - raise exception.ApiError(_("Address (%(address)s) is not " - "allocated") % {'address': - floating_ip['address']}) + raise exception.ApiError(_("Address (%s) is not allocated") % + floating_ip['address']) # Check if the floating ip address is allocated to the same project if floating_ip['project_id'] != context.project_id: LOG.warn(_("Address (%(address)s) is not allocated to your " -- cgit From ce5ad4acbc81c8c444d7b6a02400d6bc0ef6b233 Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Wed, 30 Mar 2011 20:33:56 -0700 Subject: Removed adminclient and referred to pypi nova_adminclient module --- nova/adminclient.py | 473 ---------------------------------------------------- 1 file changed, 473 deletions(-) delete mode 100644 nova/adminclient.py (limited to 'nova') diff --git a/nova/adminclient.py b/nova/adminclient.py deleted file mode 100644 index f570e12c2..000000000 --- a/nova/adminclient.py +++ /dev/null @@ -1,473 +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. -""" -Nova User API client library. -""" - -import base64 -import boto -import boto.exception -import httplib -import re -import string - -from boto.ec2.regioninfo import RegionInfo - - -DEFAULT_CLC_URL = 'http://127.0.0.1:8773' -DEFAULT_REGION = 'nova' - - -class UserInfo(object): - """ - Information about a Nova user, as parsed through SAX. - - **Fields Include** - - * username - * accesskey - * secretkey - * file (optional) containing zip of X509 cert & rc file - - """ - - def __init__(self, connection=None, username=None, endpoint=None): - self.connection = connection - self.username = username - self.endpoint = endpoint - - def __repr__(self): - return 'UserInfo:%s' % self.username - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - if name == 'username': - self.username = str(value) - elif name == 'file': - self.file = base64.b64decode(str(value)) - elif name == 'accesskey': - self.accesskey = str(value) - elif name == 'secretkey': - self.secretkey = str(value) - - -class UserRole(object): - """ - Information about a Nova user's role, as parsed through SAX. - - **Fields include** - - * role - - """ - - def __init__(self, connection=None): - self.connection = connection - self.role = None - - def __repr__(self): - return 'UserRole:%s' % self.role - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - if name == 'role': - self.role = value - else: - setattr(self, name, str(value)) - - -class ProjectInfo(object): - """ - Information about a Nova project, as parsed through SAX. - - **Fields include** - - * projectname - * description - * projectManagerId - * memberIds - - """ - - def __init__(self, connection=None): - self.connection = connection - self.projectname = None - self.description = None - self.projectManagerId = None - self.memberIds = [] - - def __repr__(self): - return 'ProjectInfo:%s' % self.projectname - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - if name == 'projectname': - self.projectname = value - elif name == 'description': - self.description = value - elif name == 'projectManagerId': - self.projectManagerId = value - elif name == 'memberId': - self.memberIds.append(value) - else: - setattr(self, name, str(value)) - - -class ProjectMember(object): - """ - Information about a Nova project member, as parsed through SAX. - - **Fields include** - - * memberId - - """ - - def __init__(self, connection=None): - self.connection = connection - self.memberId = None - - def __repr__(self): - return 'ProjectMember:%s' % self.memberId - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - if name == 'member': - self.memberId = value - else: - setattr(self, name, str(value)) - - -class HostInfo(object): - """ - Information about a Nova Host, as parsed through SAX. - - **Fields Include** - - * Hostname - * Compute service status - * Volume service status - * Instance count - * Volume count - """ - - def __init__(self, connection=None): - self.connection = connection - self.hostname = None - self.compute = None - self.volume = None - self.instance_count = 0 - self.volume_count = 0 - - def __repr__(self): - return 'Host:%s' % self.hostname - - # this is needed by the sax parser, so ignore the ugly name - def startElement(self, name, attrs, connection): - return None - - # this is needed by the sax parser, so ignore the ugly name - def endElement(self, name, value, connection): - fixed_name = string.lower(re.sub(r'([A-Z])', r'_\1', name)) - setattr(self, fixed_name, value) - - -class Vpn(object): - """ - Information about a Vpn, as parsed through SAX - - **Fields Include** - - * instance_id - * project_id - * public_ip - * public_port - * created_at - * internal_ip - * state - """ - - def __init__(self, connection=None): - self.connection = connection - self.instance_id = None - self.project_id = None - - def __repr__(self): - return 'Vpn:%s:%s' % (self.project_id, self.instance_id) - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - fixed_name = string.lower(re.sub(r'([A-Z])', r'_\1', name)) - setattr(self, fixed_name, value) - - -class InstanceType(object): - """ - Information about a Nova instance type, as parsed through SAX. - - **Fields include** - - * name - * vcpus - * disk_gb - * memory_mb - * flavor_id - - """ - - def __init__(self, connection=None): - self.connection = connection - self.name = None - self.vcpus = None - self.disk_gb = None - self.memory_mb = None - self.flavor_id = None - - def __repr__(self): - return 'InstanceType:%s' % self.name - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - if name == "memoryMb": - self.memory_mb = str(value) - elif name == "flavorId": - self.flavor_id = str(value) - elif name == "diskGb": - self.disk_gb = str(value) - else: - setattr(self, name, str(value)) - - -class NovaAdminClient(object): - - def __init__( - self, - clc_url=DEFAULT_CLC_URL, - region=DEFAULT_REGION, - access_key=None, - secret_key=None, - **kwargs): - parts = self.split_clc_url(clc_url) - - self.clc_url = clc_url - self.region = region - self.access = access_key - self.secret = secret_key - self.apiconn = boto.connect_ec2(aws_access_key_id=access_key, - aws_secret_access_key=secret_key, - is_secure=parts['is_secure'], - region=RegionInfo(None, - region, - parts['ip']), - port=parts['port'], - path='/services/Admin', - **kwargs) - self.apiconn.APIVersion = 'nova' - - def connection_for(self, username, project, clc_url=None, region=None, - **kwargs): - """Returns a boto ec2 connection for the given username.""" - if not clc_url: - clc_url = self.clc_url - if not region: - region = self.region - parts = self.split_clc_url(clc_url) - user = self.get_user(username) - access_key = '%s:%s' % (user.accesskey, project) - return boto.connect_ec2(aws_access_key_id=access_key, - aws_secret_access_key=user.secretkey, - is_secure=parts['is_secure'], - region=RegionInfo(None, - self.region, - parts['ip']), - port=parts['port'], - path='/services/Cloud', - **kwargs) - - def split_clc_url(self, clc_url): - """Splits a cloud controller endpoint url.""" - parts = httplib.urlsplit(clc_url) - is_secure = parts.scheme == 'https' - ip, port = parts.netloc.split(':') - return {'ip': ip, 'port': int(port), 'is_secure': is_secure} - - def get_users(self): - """Grabs the list of all users.""" - return self.apiconn.get_list('DescribeUsers', {}, [('item', UserInfo)]) - - def get_user(self, name): - """Grab a single user by name.""" - user = self.apiconn.get_object('DescribeUser', - {'Name': name}, - UserInfo) - if user.username != None: - return user - - def has_user(self, username): - """Determine if user exists.""" - return self.get_user(username) != None - - def create_user(self, username): - """Creates a new user, returning the userinfo object with - access/secret.""" - return self.apiconn.get_object('RegisterUser', {'Name': username}, - UserInfo) - - def delete_user(self, username): - """Deletes a user.""" - return self.apiconn.get_object('DeregisterUser', {'Name': username}, - UserInfo) - - def get_roles(self, project_roles=True): - """Returns a list of available roles.""" - return self.apiconn.get_list('DescribeRoles', - {'ProjectRoles': project_roles}, - [('item', UserRole)]) - - def get_user_roles(self, user, project=None): - """Returns a list of roles for the given user. - - Omitting project will return any global roles that the user has. - Specifying project will return only project specific roles. - - """ - params = {'User': user} - if project: - params['Project'] = project - return self.apiconn.get_list('DescribeUserRoles', - params, - [('item', UserRole)]) - - def add_user_role(self, user, role, project=None): - """Add a role to a user either globally or for a specific project.""" - return self.modify_user_role(user, role, project=project, - operation='add') - - def remove_user_role(self, user, role, project=None): - """Remove a role from a user either globally or for a specific - project.""" - return self.modify_user_role(user, role, project=project, - operation='remove') - - def modify_user_role(self, user, role, project=None, operation='add', - **kwargs): - """Add or remove a role for a user and project.""" - params = {'User': user, - 'Role': role, - 'Project': project, - 'Operation': operation} - return self.apiconn.get_status('ModifyUserRole', params) - - def get_projects(self, user=None): - """Returns a list of all projects.""" - if user: - params = {'User': user} - else: - params = {} - return self.apiconn.get_list('DescribeProjects', - params, - [('item', ProjectInfo)]) - - def get_project(self, name): - """Returns a single project with the specified name.""" - project = self.apiconn.get_object('DescribeProject', - {'Name': name}, - ProjectInfo) - - if project.projectname != None: - return project - - def create_project(self, projectname, manager_user, description=None, - member_users=None): - """Creates a new project.""" - params = {'Name': projectname, - 'ManagerUser': manager_user, - 'Description': description, - 'MemberUsers': member_users} - return self.apiconn.get_object('RegisterProject', params, ProjectInfo) - - def modify_project(self, projectname, manager_user=None, description=None): - """Modifies an existing project.""" - params = {'Name': projectname, - 'ManagerUser': manager_user, - 'Description': description} - return self.apiconn.get_status('ModifyProject', params) - - def delete_project(self, projectname): - """Permanently deletes the specified project.""" - return self.apiconn.get_object('DeregisterProject', - {'Name': projectname}, - ProjectInfo) - - def get_project_members(self, name): - """Returns a list of members of a project.""" - return self.apiconn.get_list('DescribeProjectMembers', - {'Name': name}, - [('item', ProjectMember)]) - - def add_project_member(self, user, project): - """Adds a user to a project.""" - return self.modify_project_member(user, project, operation='add') - - def remove_project_member(self, user, project): - """Removes a user from a project.""" - return self.modify_project_member(user, project, operation='remove') - - def modify_project_member(self, user, project, operation='add'): - """Adds or removes a user from a project.""" - params = {'User': user, - 'Project': project, - 'Operation': operation} - return self.apiconn.get_status('ModifyProjectMember', params) - - def get_zip(self, user, project): - """Returns the content of a zip file containing novarc and access - credentials.""" - params = {'Name': user, 'Project': project} - zip = self.apiconn.get_object('GenerateX509ForUser', params, UserInfo) - return zip.file - - def start_vpn(self, project): - """ - Starts the vpn for a user - """ - return self.apiconn.get_object('StartVpn', {'Project': project}, Vpn) - - def get_vpns(self): - """Return a list of vpn with project name""" - return self.apiconn.get_list('DescribeVpns', {}, [('item', Vpn)]) - - def get_hosts(self): - return self.apiconn.get_list('DescribeHosts', {}, [('item', HostInfo)]) - - def get_instance_types(self): - """Grabs the list of all users.""" - return self.apiconn.get_list('DescribeInstanceTypes', {}, - [('item', InstanceType)]) -- cgit From b5f0d2c22ce48d06dc502f7790ed48fdf007d7f6 Mon Sep 17 00:00:00 2001 From: Muneyuki Noguchi Date: Thu, 31 Mar 2011 17:39:00 +0900 Subject: Add volume.API.remove_from_compute instead of compute.API.remove_volume. --- nova/compute/api.py | 7 ------- nova/compute/manager.py | 12 +++++------- nova/volume/api.py | 7 +++++++ 3 files changed, 12 insertions(+), 14 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 63983afd8..1dbd73f8f 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -687,13 +687,6 @@ class API(base.Base): "volume_id": volume_id}}) return instance - def remove_volume(self, context, volume_id, host): - """Remove volume on specified compute host.""" - rpc.call(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "remove_volume", - "args": {'volume_id': volume_id}}) - def associate_floating_ip(self, context, instance_id, address): instance = self.get(context, instance_id) self.network_api.associate_floating_ip(context, diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 8ce8a5d86..85bcd7590 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -46,13 +46,13 @@ import functools from eventlet import greenthread -from nova import compute from nova import exception from nova import flags from nova import log as logging from nova import manager from nova import rpc from nova import utils +from nova import volume from nova.compute import power_state from nova.virt import driver @@ -1037,14 +1037,12 @@ class ComputeManager(manager.SchedulerDependentManager): 'host': host}) if dest: - # NOTE(noguchimn): We set image_service here - # not to import an image service object. - compute_api = compute.API(image_service=1) - for volume in instance_ref['volumes']: - volume_id = volume['id'] + volume_api = volume.API() + for volume_ref in instance_ref['volumes']: + volume_id = volume_ref['id'] self.db.volume_update(ctxt, volume_id, {'status': 'in-use'}) if dest: - compute_api.remove_volume(ctxt, volume_id, dest) + volume_api.remove_from_compute(ctxt, volume_id, dest) def periodic_tasks(self, context=None): """Tasks to be run at a periodic interval.""" diff --git a/nova/volume/api.py b/nova/volume/api.py index 4b4bb9dc5..09befb647 100644 --- a/nova/volume/api.py +++ b/nova/volume/api.py @@ -103,3 +103,10 @@ class API(base.Base): # TODO(vish): abstract status checking? if volume['status'] == "available": raise exception.ApiError(_("Volume is already detached")) + + def remove_from_compute(self, context, volume_id, host): + """Remove volume from specified compute host.""" + rpc.call(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "remove_volume", + "args": {'volume_id': volume_id}}) -- cgit From 7f98416fdba59c4305f88a30a457bbf6c68ef372 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 31 Mar 2011 09:20:47 -0400 Subject: Limit image metadata to the configured metadata quota for a project. --- nova/api/openstack/image_metadata.py | 12 ++++++++ nova/tests/api/openstack/test_image_metadata.py | 39 +++++++++++++++++++++++++ 2 files changed, 51 insertions(+) (limited to 'nova') diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index c9d6ac532..76abdaa7e 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -18,6 +18,7 @@ from webob import exc from nova import flags +from nova import quota from nova import utils from nova import wsgi from nova.api.openstack import faults @@ -39,6 +40,15 @@ class Controller(wsgi.Controller): metadata = image.get('properties', {}) return metadata + def _check_quota_limit(self, context, metadata): + if metadata is None: + return + num_metadata = len(metadata) + quota_metadata = quota.allowed_metadata_items(context, num_metadata) + if quota_metadata < num_metadata: + expl = ("Image metadata limit exceeded") + raise exc.HTTPBadRequest(explanation=expl) + def index(self, req, image_id): """Returns the list of metadata for a given instance""" context = req.environ['nova.context'] @@ -61,6 +71,7 @@ class Controller(wsgi.Controller): if 'metadata' in body: for key, value in body['metadata'].iteritems(): metadata[key] = value + self._check_quota_limit(context, metadata) img['properties'] = metadata self.image_service.update(context, image_id, img, None) return dict(metadata=metadata) @@ -77,6 +88,7 @@ class Controller(wsgi.Controller): img = self.image_service.show(context, image_id) metadata = self._get_metadata(context, image_id, img) metadata[id] = body[id] + self._check_quota_limit(context, metadata) img['properties'] = metadata self.image_service.update(context, image_id, img, None) diff --git a/nova/tests/api/openstack/test_image_metadata.py b/nova/tests/api/openstack/test_image_metadata.py index 9be753f84..51f64680b 100644 --- a/nova/tests/api/openstack/test_image_metadata.py +++ b/nova/tests/api/openstack/test_image_metadata.py @@ -67,6 +67,19 @@ class ImageMetaDataTest(unittest.TestCase): 'key2': 'value2' }, 'size': 5882349}, + {'status': 'active', + 'name': 'image3', + 'deleted': False, + 'container_format': None, + 'created_at': '2011-03-22T17:40:15', + 'disk_format': None, + 'updated_at': '2011-03-22T17:40:15', + 'id': '3', + 'location': 'file:///var/lib/glance/images/2', + 'is_public': True, + 'deleted_at': None, + 'properties': {}, + 'size': 5882349}, ] def setUp(self): @@ -77,6 +90,10 @@ class ImageMetaDataTest(unittest.TestCase): fakes.FakeAuthManager.auth_data = {} fakes.FakeAuthDatabase.data = {} fakes.stub_out_auth(self.stubs) + # NOTE(dprince) max out properties/metadata in image 3 for testing + img3 = self.IMAGE_FIXTURES[2] + for num in range(FLAGS.quota_metadata_items): + img3['properties']['key%i' % num] = "blah" fakes.stub_out_glance(self.stubs, self.IMAGE_FIXTURES) def tearDown(self): @@ -164,3 +181,25 @@ class ImageMetaDataTest(unittest.TestCase): req.method = 'DELETE' res = req.get_response(fakes.wsgi_app()) self.assertEqual(404, res.status_int) + + def test_too_many_metadata_items_on_create(self): + data = {"metadata": {}} + 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/meta') + req.environ['api.version'] = '1.1' + req.method = 'POST' + req.body = json_string + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) + + def test_too_many_metadata_items_on_put(self): + req = webob.Request.blank('/v1.1/images/3/meta/blah') + req.environ['api.version'] = '1.1' + req.method = 'PUT' + req.body = '{"blah": "blah"}' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) -- cgit From 45bb2fb1f74c21e9ef8b65f6c4e22965e2c94fbd Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 31 Mar 2011 13:08:49 -0500 Subject: More friendly error message --- nova/scheduler/chance.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/scheduler/chance.py b/nova/scheduler/chance.py index 9deaa2777..ae81679e1 100644 --- a/nova/scheduler/chance.py +++ b/nova/scheduler/chance.py @@ -34,5 +34,7 @@ class ChanceScheduler(driver.Scheduler): hosts = self.hosts_up(context, topic) if not hosts: - raise driver.NoValidHost(_("No hosts found")) + raise driver.NoValidHost(_("Scheduler was unable to locate a host" + " for this request. Is the compute node" + " running?")) return hosts[int(random.random() * len(hosts))] -- cgit From 62b52833cc28d203c648585feceb1de3be9eed25 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 31 Mar 2011 14:29:16 -0500 Subject: Review feedback --- nova/scheduler/chance.py | 4 ++-- nova/scheduler/simple.py | 12 +++++++++--- nova/scheduler/zone.py | 5 ++++- 3 files changed, 15 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/scheduler/chance.py b/nova/scheduler/chance.py index ae81679e1..f4461cee2 100644 --- a/nova/scheduler/chance.py +++ b/nova/scheduler/chance.py @@ -35,6 +35,6 @@ class ChanceScheduler(driver.Scheduler): hosts = self.hosts_up(context, topic) if not hosts: raise driver.NoValidHost(_("Scheduler was unable to locate a host" - " for this request. Is the compute node" - " running?")) + " for this request. Is the appropriate" + " service running?")) return hosts[int(random.random() * len(hosts))] diff --git a/nova/scheduler/simple.py b/nova/scheduler/simple.py index 0191ceb3d..dd568d2c6 100644 --- a/nova/scheduler/simple.py +++ b/nova/scheduler/simple.py @@ -72,7 +72,9 @@ class SimpleScheduler(chance.ChanceScheduler): {'host': service['host'], 'scheduled_at': now}) return service['host'] - raise driver.NoValidHost(_("No hosts found")) + raise driver.NoValidHost(_("Scheduler was unable to locate a host" + " for this request. Is the appropriate" + " service running?")) def schedule_create_volume(self, context, volume_id, *_args, **_kwargs): """Picks a host that is up and has the fewest volumes.""" @@ -107,7 +109,9 @@ class SimpleScheduler(chance.ChanceScheduler): {'host': service['host'], 'scheduled_at': now}) return service['host'] - raise driver.NoValidHost(_("No hosts found")) + raise driver.NoValidHost(_("Scheduler was unable to locate a host" + " for this request. Is the appropriate" + " service running?")) def schedule_set_network_host(self, context, *_args, **_kwargs): """Picks a host that is up and has the fewest networks.""" @@ -119,4 +123,6 @@ class SimpleScheduler(chance.ChanceScheduler): raise driver.NoValidHost(_("All hosts have too many networks")) if self.service_is_up(service): return service['host'] - raise driver.NoValidHost(_("No hosts found")) + raise driver.NoValidHost(_("Scheduler was unable to locate a host" + " for this request. Is the appropriate" + " service running?")) diff --git a/nova/scheduler/zone.py b/nova/scheduler/zone.py index 49786cd32..44d5a166f 100644 --- a/nova/scheduler/zone.py +++ b/nova/scheduler/zone.py @@ -52,5 +52,8 @@ class ZoneScheduler(driver.Scheduler): zone = _kwargs.get('availability_zone') hosts = self.hosts_up_with_zone(context, topic, zone) if not hosts: - raise driver.NoValidHost(_("No hosts found")) + raise driver.NoValidHost(_("Scheduler was unable to locate a host" + " for this request. Is the appropriate" + " service running?")) + return hosts[int(random.random() * len(hosts))] -- cgit From 82224b6681750819a4ee0d8b081823863cb0523c Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 31 Mar 2011 12:31:35 -0700 Subject: removes excessive logging on rabbitmq failure --- nova/rpc.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova') diff --git a/nova/rpc.py b/nova/rpc.py index be7cb3f37..b610cdf9b 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -134,8 +134,6 @@ class Consumer(messaging.Consumer): if not self.failed_connection: LOG.exception(_("Failed to fetch message from queue: %s" % e)) self.failed_connection = True - else: - LOG.exception(_("Unhandled exception %s" % e)) def attach_to_eventlet(self): """Only needed for unit tests!""" -- cgit From 1654afaabba498ab690c07ccc6de858783dc1742 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 31 Mar 2011 12:38:05 -0700 Subject: makes sure s3 filtering works even without metadata set properly --- nova/image/s3.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/image/s3.py b/nova/image/s3.py index 203bedc49..def49f682 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -80,9 +80,10 @@ class S3ImageService(service.BaseImageService): @classmethod def _is_visible(cls, context, image): + return (context.is_admin - or context.project_id == image['properties']['owner_id'] - or image['properties']['is_public'] == 'True') + or context.project_id == image['properties'].get('owner_id') + or str(image['properties'].get('is_public')) == 'True') @classmethod def _filter(cls, context, images): -- cgit From 032acaab1fd9a3823e203ddaf9c50857acd25ac7 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 31 Mar 2011 15:32:31 -0500 Subject: Don't include first 4 chars of instance name in adminPass --- nova/api/openstack/servers.py | 3 +-- nova/tests/api/openstack/test_servers.py | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index f7696d918..d47ea5788 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -180,8 +180,7 @@ class Controller(wsgi.Controller): builder = self._get_view_builder(req) server = builder.build(inst, is_detail=True) - password = "%s%s" % (server['server']['name'][:4], - utils.generate_password(12)) + password = utils.generate_password(16) server['server']['adminPass'] = password self.compute_api.set_admin_password(context, server['server']['id'], password) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 130b8c5d5..5fbc9837b 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -377,7 +377,6 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) server = json.loads(res.body)['server'] - self.assertEqual('serv', server['adminPass'][:4]) self.assertEqual(16, len(server['adminPass'])) self.assertEqual('server_test', server['name']) self.assertEqual(1, server['id']) @@ -486,7 +485,6 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) server = json.loads(res.body)['server'] - self.assertEqual('serv', server['adminPass'][:4]) self.assertEqual(16, len(server['adminPass'])) self.assertEqual('server_test', server['name']) self.assertEqual(1, server['id']) @@ -1426,7 +1424,7 @@ class TestServerInstanceCreation(test.TestCase): self.assertEquals(response.status_int, 200) response = json.loads(response.body) self.assertTrue('adminPass' in response['server']) - self.assertTrue(response['server']['adminPass'].startswith('fake')) + self.assertEqual(16, len(response['server']['adminPass'])) def test_create_instance_admin_pass_xml(self): request, response, dummy = \ @@ -1435,7 +1433,7 @@ class TestServerInstanceCreation(test.TestCase): dom = minidom.parseString(response.body) server = dom.childNodes[0] self.assertEquals(server.nodeName, 'server') - self.assertTrue(server.getAttribute('adminPass').startswith('fake')) + self.assertEqual(16, len(server.getAttribute('adminPass'))) class TestGetKernelRamdiskFromImage(test.TestCase): -- cgit From 5d71e187dc6eddab19ecdc0feb97e41263e09a84 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 31 Mar 2011 17:19:59 -0400 Subject: Hopefully absolved us of the suds issue? --- nova/virt/vmwareapi/vim.py | 47 ++++++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 25 deletions(-) (limited to 'nova') diff --git a/nova/virt/vmwareapi/vim.py b/nova/virt/vmwareapi/vim.py index 1c850595d..159e16a80 100644 --- a/nova/virt/vmwareapi/vim.py +++ b/nova/virt/vmwareapi/vim.py @@ -22,13 +22,9 @@ Classes for making VMware VI SOAP calls. import httplib try: - suds = True - from suds import WebFault - from suds.client import Client - from suds.plugin import MessagePlugin - from suds.sudsobject import Property + import suds except ImportError: - suds = False + suds = None from nova import flags from nova.virt.vmwareapi import error_util @@ -46,24 +42,25 @@ flags.DEFINE_string('vmwareapi_wsdl_loc', 'Refer readme-vmware to setup') -class VIMMessagePlugin(MessagePlugin): +if suds: + class VIMMessagePlugin(suds.plugin.MessagePlugin): - def addAttributeForValue(self, node): - # suds does not handle AnyType properly. - # VI SDK requires type attribute to be set when AnyType is used - if node.name == 'value': - node.set('xsi:type', 'xsd:string') + def addAttributeForValue(self, node): + # suds does not handle AnyType properly. + # VI SDK requires type attribute to be set when AnyType is used + if node.name == 'value': + node.set('xsi:type', 'xsd:string') - def marshalled(self, context): - """suds will send the specified soap envelope. - Provides the plugin with the opportunity to prune empty - nodes and fixup nodes before sending it to the server. - """ - # suds builds the entire request object based on the wsdl schema. - # VI SDK throws server errors if optional SOAP nodes are sent without - # values, e.g. as opposed to test - context.envelope.prune() - context.envelope.walk(self.addAttributeForValue) + def marshalled(self, context): + """suds will send the specified soap envelope. + Provides the plugin with the opportunity to prune empty + nodes and fixup nodes before sending it to the server. + """ + # suds builds the entire request object based on the wsdl schema. + # VI SDK throws server errors if optional SOAP nodes are sent + # without values, e.g. as opposed to test + context.envelope.prune() + context.envelope.walk(self.addAttributeForValue) class Vim: @@ -91,7 +88,7 @@ class Vim: #wsdl_url = '%s://%s/sdk/vimService.wsdl' % (self._protocol, # self._host_name) url = '%s://%s/sdk' % (self._protocol, self._host_name) - self.client = Client(wsdl_url, location=url, + self.client = suds.client.Client(wsdl_url, location=url, plugins=[VIMMessagePlugin()]) self._service_content = \ self.RetrieveServiceContent("ServiceInstance") @@ -134,7 +131,7 @@ class Vim: # check of the SOAP response except error_util.VimFaultException, excep: raise - except WebFault, excep: + except suds.WebFault, excep: doc = excep.document detail = doc.childAtPath("/Envelope/Body/Fault/detail") fault_list = [] @@ -170,7 +167,7 @@ class Vim: """Builds the request managed object.""" # Request Managed Object Builder if type(managed_object) == type(""): - mo = Property(managed_object) + mo = suds.sudsobject.Property(managed_object) mo._type = managed_object else: mo = managed_object -- cgit From 007992e57c064c90e6a09f8dac070d3b56dc72a0 Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Sat, 2 Apr 2011 00:28:32 +0900 Subject: Added updated_at field to update statement according to Jay's comment. --- nova/db/sqlalchemy/api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 08eb0b7b2..6da8dac10 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -660,7 +660,9 @@ def fixed_ip_disassociate_all_by_timeout(_context, host, time): filter(models.FixedIp.instance_id != None).\ filter_by(allocated=0).\ update({'instance_id': None, - 'leased': 0}, synchronize_session='fetch') + 'leased': 0, + 'updated_at': datetime.datetime.utcnow()}, + synchronize_session='fetch') return result -- cgit From d98c61d21f3a60e3368cc723fc6764c66b8176b4 Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Sat, 2 Apr 2011 01:05:50 +0900 Subject: Add checking if the floating_ip is allocated or not before appending to result array. --- nova/api/ec2/cloud.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 7ba8dfbea..a6bdab808 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -757,19 +757,20 @@ class CloudController(object): iterator = db.floating_ip_get_all_by_project(context, context.project_id) for floating_ip_ref in iterator: - address = floating_ip_ref['address'] - ec2_id = None - if (floating_ip_ref['fixed_ip'] - and floating_ip_ref['fixed_ip']['instance']): - instance_id = floating_ip_ref['fixed_ip']['instance']['id'] - ec2_id = ec2utils.id_to_ec2_id(instance_id) - address_rv = {'public_ip': address, - 'instance_id': ec2_id} - if context.is_admin: - details = "%s (%s)" % (address_rv['instance_id'], - floating_ip_ref['project_id']) - address_rv['instance_id'] = details - addresses.append(address_rv) + if floating_ip_ref['project_id'] is not None: + address = floating_ip_ref['address'] + ec2_id = None + if (floating_ip_ref['fixed_ip'] + and floating_ip_ref['fixed_ip']['instance']): + instance_id = floating_ip_ref['fixed_ip']['instance']['id'] + ec2_id = ec2utils.id_to_ec2_id(instance_id) + address_rv = {'public_ip': address, + 'instance_id': ec2_id} + if context.is_admin: + details = "%s (%s)" % (address_rv['instance_id'], + floating_ip_ref['project_id']) + address_rv['instance_id'] = details + addresses.append(address_rv) return {'addressesSet': addresses} def allocate_address(self, context, **kwargs): -- cgit From ec3b3d6ae97cddce490c2cbeed2f8f9241704ed1 Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Sat, 2 Apr 2011 01:44:12 +0900 Subject: Made the fix simpler. --- nova/api/ec2/cloud.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index a6bdab808..425784e8a 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -757,20 +757,21 @@ class CloudController(object): iterator = db.floating_ip_get_all_by_project(context, context.project_id) for floating_ip_ref in iterator: - if floating_ip_ref['project_id'] is not None: - address = floating_ip_ref['address'] - ec2_id = None - if (floating_ip_ref['fixed_ip'] - and floating_ip_ref['fixed_ip']['instance']): - instance_id = floating_ip_ref['fixed_ip']['instance']['id'] - ec2_id = ec2utils.id_to_ec2_id(instance_id) - address_rv = {'public_ip': address, - 'instance_id': ec2_id} - if context.is_admin: - details = "%s (%s)" % (address_rv['instance_id'], - floating_ip_ref['project_id']) - address_rv['instance_id'] = details - addresses.append(address_rv) + if floating_ip_ref['project_id'] is None: + continue + address = floating_ip_ref['address'] + ec2_id = None + if (floating_ip_ref['fixed_ip'] + and floating_ip_ref['fixed_ip']['instance']): + instance_id = floating_ip_ref['fixed_ip']['instance']['id'] + ec2_id = ec2utils.id_to_ec2_id(instance_id) + address_rv = {'public_ip': address, + 'instance_id': ec2_id} + if context.is_admin: + details = "%s (%s)" % (address_rv['instance_id'], + floating_ip_ref['project_id']) + address_rv['instance_id'] = details + addresses.append(address_rv) return {'addressesSet': addresses} def allocate_address(self, context, **kwargs): -- cgit From c1120caaa8c8ed8902b5634da56b2bd5478662e1 Mon Sep 17 00:00:00 2001 From: Muneyuki Noguchi Date: Mon, 4 Apr 2011 10:25:58 +0900 Subject: Use keyword arguments. --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index c5a71d244..c03c2ae1d 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1475,7 +1475,7 @@ class LibvirtConnection(driver.ComputeDriver): FLAGS.live_migration_bandwidth) except Exception: - recover_method(ctxt, instance_ref, None, dest) + recover_method(ctxt, instance_ref, dest=dest) raise # Waiting for completion of live_migration. -- cgit From ec30c42b3b4532743c8353c5e9fa04ae00803db3 Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Mon, 4 Apr 2011 18:31:35 +0400 Subject: network injection check fixed --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index f34ea7225..c952f6f47 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -842,7 +842,7 @@ class LibvirtConnection(driver.ComputeDriver): for (network_ref, mapping) in network_info: ifc_num += 1 - if not 'injected' in network_ref: + if not network_ref.injected: continue have_injected_networks = True -- cgit From bd64c4f6bebb50528b87bf6e3f64d7d1cba053df Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Mon, 4 Apr 2011 10:59:44 -0400 Subject: Fixes error which occurs when no name is specified for an image. --- nova/api/openstack/views/images.py | 2 +- nova/tests/api/openstack/test_images.py | 57 +++++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index 3807fa95f..d8578ebdd 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -61,7 +61,7 @@ class ViewBuilder(object): image = { "id": image_obj["id"], - "name": image_obj["name"], + "name": image_obj.get("name"), } if "instance_id" in properties: diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 57e447dce..69cc3116d 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -263,7 +263,8 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): {'id': 124, 'name': 'queued backup'}, {'id': 125, 'name': 'saving backup'}, {'id': 126, 'name': 'active backup'}, - {'id': 127, 'name': 'killed backup'}] + {'id': 127, 'name': 'killed backup'}, + {'id': 129, 'name': None}] self.assertDictListMatch(response_list, expected) @@ -339,6 +340,24 @@ 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.accept = "application/xml" + response = request.get_response(fakes.wsgi_app()) + + actual_image = minidom.parseString(response.body.replace(" ", "")) + + expected_now = self.NOW_API_FORMAT + expected_image = minidom.parseString(""" + + """ % (locals())) + + self.assertEqual(expected_image.toxml(), actual_image.toxml()) + def test_get_image_v1_1_xml(self): request = webob.Request.blank('/v1.1/images/123') request.accept = "application/xml" @@ -516,6 +535,13 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): 'updated': self.NOW_API_FORMAT, 'created': self.NOW_API_FORMAT, 'status': 'FAILED', + }, + { + 'id': 129, + 'name': None, + 'updated': self.NOW_API_FORMAT, + 'created': self.NOW_API_FORMAT, + 'status': 'ACTIVE', }] self.assertDictListMatch(expected, response_list) @@ -635,7 +661,29 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): "type": "application/xml", "href": "http://localhost/v1.1/images/127", }], - }] + }, + { + 'id': 129, + 'name': None, + 'updated': self.NOW_API_FORMAT, + 'created': self.NOW_API_FORMAT, + 'status': 'ACTIVE', + "links": [{ + "rel": "self", + "href": "http://localhost/v1.1/images/129", + }, + { + "rel": "bookmark", + "type": "application/json", + "href": "http://localhost/v1.1/images/129", + }, + { + "rel": "bookmark", + "type": "application/xml", + "href": "http://localhost/v1.1/images/129", + }], + }, + ] self.assertDictListMatch(expected, response_list) @@ -694,4 +742,9 @@ class ImageControllerWithGlanceServiceTest(test.TestCase): status='active', properties=other_backup_properties) image_id += 1 + # Image without a name + add_fixture(id=image_id, is_public=True, status='active', + properties={}) + image_id += 1 + return fixtures -- cgit From 59d46ada05f47bf477427b932a47c1cf1d91811e Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Mon, 4 Apr 2011 11:19:20 -0400 Subject: Ensure no errors for improper responses from image service. --- nova/api/openstack/views/images.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index d8578ebdd..16195b050 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -60,7 +60,7 @@ class ViewBuilder(object): self._format_status(image_obj) image = { - "id": image_obj["id"], + "id": image_obj.get("id"), "name": image_obj.get("name"), } @@ -72,9 +72,9 @@ class ViewBuilder(object): if detail: image.update({ - "created": image_obj["created_at"], - "updated": image_obj["updated_at"], - "status": image_obj["status"], + "created": image_obj.get("created_at"), + "updated": image_obj.get("updated_at"), + "status": image_obj.get("status"), }) if image["status"] == "SAVING": -- cgit From fc53de592fb903f8a7e3741fa98d03140aca9066 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Mon, 4 Apr 2011 09:45:26 -0700 Subject: corrected capitalization of openstack api status and added tests --- nova/api/openstack/views/servers.py | 24 ++++++++++++------------ nova/tests/api/openstack/test_servers.py | 2 ++ 2 files changed, 14 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 6b471a0f4..d24c025be 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -57,27 +57,27 @@ class ViewBuilder(object): def _build_detail(self, inst): """Returns a detailed model of a server.""" power_mapping = { - None: 'build', - power_state.NOSTATE: 'build', - power_state.RUNNING: 'active', - power_state.BLOCKED: 'active', - power_state.SUSPENDED: 'suspended', - power_state.PAUSED: 'paused', - power_state.SHUTDOWN: 'active', - power_state.SHUTOFF: 'active', - power_state.CRASHED: 'error', - power_state.FAILED: 'error'} + None: 'BUILD', + power_state.NOSTATE: 'BUILD', + power_state.RUNNING: 'ACTIVE', + power_state.BLOCKED: 'ACTIVE', + power_state.SUSPENDED: 'SUSPENDED', + power_state.PAUSED: 'PAUSED', + power_state.SHUTDOWN: 'ACTIVE', + power_state.SHUTOFF: 'ACTIVE', + power_state.CRASHED: 'ERROR', + power_state.FAILED: 'ERROR'} inst_dict = { 'id': int(inst['id']), 'name': inst['display_name'], 'addresses': self.addresses_builder.build(inst), - 'status': power_mapping[inst.get('state')].upper()} + 'status': power_mapping[inst.get('state')]} ctxt = nova.context.get_admin_context() compute_api = nova.compute.API() if compute_api.has_finished_migration(ctxt, inst['id']): - inst_dict['status'] = 'resize-confirm'.upper() + inst_dict['status'] = 'RESIZE-CONFIRM' # Return the metadata as a dictionary metadata = {} diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index d32e8eea8..cf55c8383 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -631,6 +631,7 @@ class ServersTest(test.TestCase): self.assertEqual(s['name'], 'server%d' % i) self.assertEqual(s['imageId'], '10') self.assertEqual(s['flavorId'], '1') + self.assertEqual(s['status'], 'BUILD') self.assertEqual(s['metadata']['seq'], i) def test_get_all_server_details_v1_1(self): @@ -644,6 +645,7 @@ class ServersTest(test.TestCase): self.assertEqual(s['name'], 'server%d' % i) self.assertEqual(s['imageRef'], 'http://localhost/v1.1/images/10') self.assertEqual(s['flavorRef'], 'http://localhost/v1.1/flavors/1') + self.assertEqual(s['status'], 'BUILD') self.assertEqual(s['metadata']['seq'], i) def test_get_all_server_details_with_host(self): -- cgit From 95fe2026e869e2da29196f8bf3e48ae2a2560e46 Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Tue, 5 Apr 2011 02:04:58 +0900 Subject: Moved 'name' property from to , corrected and fixes bug # 750482. --- nova/api/ec2/cloud.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 425784e8a..ecf144452 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -886,10 +886,7 @@ class CloudController(object): image_type = image['properties'].get('type') ec2_id = self._image_ec2_id(image.get('id'), image_type) name = image.get('name') - if name: - i['imageId'] = "%s (%s)" % (ec2_id, name) - else: - i['imageId'] = ec2_id + i['imageId'] = ec2_id kernel_id = image['properties'].get('kernel_id') if kernel_id: i['kernelId'] = self._image_ec2_id(kernel_id, 'kernel') @@ -897,11 +894,15 @@ class CloudController(object): if ramdisk_id: i['ramdiskId'] = self._image_ec2_id(ramdisk_id, 'ramdisk') i['imageOwnerId'] = image['properties'].get('owner_id') - i['imageLocation'] = image['properties'].get('image_location') + if name: + i['imageLocation'] = "%s (%s)" % (image['properties']. + get('image_location'), name) + else: + i['imageLocation'] = image['properties'].get('image_location') i['imageState'] = image['properties'].get('image_state') - i['displayName'] = image.get('name') + i['displayName'] = name i['description'] = image.get('description') - i['type'] = image_type + i['imageType'] = image_type i['isPublic'] = str(image['properties'].get('is_public', '')) == 'True' i['architecture'] = image['properties'].get('architecture') return i -- cgit From d7053efa810aa3d20ef7cd089429c6d96f451a7d Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Mon, 4 Apr 2011 21:05:38 +0400 Subject: Fixed network_info creating. --- nova/tests/test_virt.py | 6 ++++-- nova/virt/libvirt_conn.py | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 958c8e3e2..5a010d347 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -617,7 +617,8 @@ class IptablesFirewallTestCase(test.TestCase): instance_ref = db.instance_create(self.context, {'user_id': 'fake', 'project_id': 'fake', - 'mac_address': '56:12:12:12:12:12'}) + 'mac_address': '56:12:12:12:12:12', + 'instance_type': 'm1.small'}) ip = '10.11.12.13' network_ref = db.project_get_network(self.context, @@ -840,7 +841,8 @@ class NWFilterTestCase(test.TestCase): instance_ref = db.instance_create(self.context, {'user_id': 'fake', 'project_id': 'fake', - 'mac_address': '00:A0:C9:14:C8:29'}) + 'mac_address': '00:A0:C9:14:C8:29', + 'instance_type': 'm1.small'}) inst_id = instance_ref['id'] ip = '10.11.12.13' diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index f34ea7225..93a250502 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -167,6 +167,9 @@ def _get_network_info(instance): networks = db.network_get_all_by_instance(admin_context, instance['id']) + + flavor = db.instance_type_get_by_name(admin_context, + instance['instance_type']) network_info = [] def ip_dict(ip): @@ -191,7 +194,9 @@ def _get_network_info(instance): mapping = { 'label': network['label'], 'gateway': network['gateway'], + 'broadcast': network['broadcast'], 'mac': instance.mac_address, + 'rxtx_cap': flavor['rxtx_cap'], 'dns': [network['dns']], 'ips': [ip_dict(ip) for ip in network_ips]} -- cgit From 350aaa819c8f97e0bcbd9e4d0f6f0da41784b630 Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Mon, 4 Apr 2011 22:39:39 +0400 Subject: working with network_ref like with mapping --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index c952f6f47..babbc610d 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -842,7 +842,7 @@ class LibvirtConnection(driver.ComputeDriver): for (network_ref, mapping) in network_info: ifc_num += 1 - if not network_ref.injected: + if not network_ref['injected']: continue have_injected_networks = True -- cgit From 38b4cd9e68d7e1c262b08474b277573440ec3e87 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 4 Apr 2011 16:17:04 -0400 Subject: Refactor so that instances.instance_type is now instances.instance_type_id. Update the Openstack API to return the correct flavor_id. --- nova/api/ec2/cloud.py | 7 +- nova/api/openstack/servers.py | 6 +- nova/api/openstack/views/servers.py | 4 +- nova/compute/api.py | 18 +++-- nova/compute/instance_types.py | 46 ++++++------ nova/db/api.py | 5 ++ nova/db/sqlalchemy/api.py | 22 ++++++ .../014_add_instance_type_id_to_instances.py | 86 ++++++++++++++++++++++ nova/db/sqlalchemy/models.py | 11 ++- nova/tests/api/openstack/test_servers.py | 24 +++--- nova/tests/db/fakes.py | 22 ++++-- nova/tests/test_compute.py | 18 +++-- nova/tests/test_console.py | 2 +- nova/tests/test_instance_types.py | 6 +- nova/tests/test_quota.py | 17 +++-- nova/tests/test_scheduler.py | 2 +- nova/tests/test_virt.py | 2 +- nova/tests/test_volume.py | 2 +- nova/tests/test_xenapi.py | 12 +-- nova/virt/libvirt_conn.py | 22 +++--- nova/virt/xenapi/vm_utils.py | 8 +- nova/virt/xenapi/vmops.py | 8 +- 22 files changed, 255 insertions(+), 95 deletions(-) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 7ba8dfbea..cd1195502 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -722,7 +722,10 @@ class CloudController(object): instance['project_id'], instance['host']) i['productCodesSet'] = self._convert_to_set([], 'product_codes') - i['instanceType'] = instance['instance_type'] + if instance['instance_type']: + i['instanceType'] = instance['instance_type'].get('name', None) + else: + i['instanceType'] = None i['launchTime'] = instance['created_at'] i['amiLaunchIndex'] = instance['launch_index'] i['displayName'] = instance['display_name'] @@ -805,7 +808,7 @@ class CloudController(object): ramdisk = self._get_image(context, kwargs['ramdisk_id']) kwargs['ramdisk_id'] = ramdisk['id'] instances = self.compute_api.create(context, - instance_type=instance_types.get_by_type( + instance_type=instance_types.get_instance_type_by_name( kwargs.get('instance_type', None)), image_id=self._get_image(context, kwargs['image_id'])['id'], min_count=int(kwargs.get('min_count', max_count)), diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 4e2ebb2bd..327911f6f 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -160,9 +160,11 @@ class Controller(wsgi.Controller): name = name.strip() try: + inst_type = \ + instance_types.get_instance_type_by_flavor_id(flavor_id) (inst,) = self.compute_api.create( context, - instance_types.get_by_flavor_id(flavor_id), + inst_type, image_id, kernel_id=kernel_id, ramdisk_id=ramdisk_id, @@ -175,7 +177,7 @@ class Controller(wsgi.Controller): except quota.QuotaError as error: self._handle_quota_error(error) - inst['instance_type'] = flavor_id + inst['instance_type'] = inst_type inst['image_id'] = requested_image_id builder = self._get_view_builder(req) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 4e7f62eb3..2f400eef6 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -115,7 +115,7 @@ class ViewBuilderV10(ViewBuilder): def _build_flavor(self, response, inst): if 'instance_type' in dict(inst): - response['flavorId'] = inst['instance_type'] + response['flavorId'] = inst['instance_type']['flavorid'] class ViewBuilderV11(ViewBuilder): @@ -134,7 +134,7 @@ class ViewBuilderV11(ViewBuilder): def _build_flavor(self, response, inst): if "instance_type" in dict(inst): - flavor_id = inst["instance_type"] + flavor_id = inst["instance_type"]['flavorid'] flavor_ref = self.flavor_builder.generate_href(flavor_id) response["flavorRef"] = flavor_ref diff --git a/nova/compute/api.py b/nova/compute/api.py index 1dbd73f8f..363d61f29 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -110,8 +110,11 @@ class API(base.Base): """Create the number of instances requested if quota and other arguments check out ok.""" - type_data = instance_types.get_instance_type(instance_type) - num_instances = quota.allowed_instances(context, max_count, type_data) + if not instance_type: + instance_type = instance_types.get_default_instance_type() + + num_instances = quota.allowed_instances(context, max_count, + instance_type) if num_instances < min_count: pid = context.project_id LOG.warn(_("Quota exceeeded for %(pid)s," @@ -197,10 +200,10 @@ class API(base.Base): 'user_id': context.user_id, 'project_id': context.project_id, 'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), - 'instance_type': instance_type, - 'memory_mb': type_data['memory_mb'], - 'vcpus': type_data['vcpus'], - 'local_gb': type_data['local_gb'], + 'instance_type_id': instance_type['id'], + 'memory_mb': instance_type['memory_mb'], + 'vcpus': instance_type['vcpus'], + 'local_gb': instance_type['local_gb'], 'display_name': display_name, 'display_description': display_description, 'user_data': user_data or '', @@ -517,8 +520,7 @@ class API(base.Base): def resize(self, context, instance_id, flavor_id): """Resize a running instance.""" instance = self.db.instance_get(context, instance_id) - current_instance_type = self.db.instance_type_get_by_name( - context, instance['instance_type']) + current_instance_type = instance['instance_type'] new_instance_type = self.db.instance_type_get_by_flavor_id( context, flavor_id) diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index fa02a5dfa..5b1d92e29 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -101,41 +101,43 @@ def get_all_flavors(): return get_all_types(context.get_admin_context()) -def get_instance_type(name): - """Retrieves single instance type by name""" - if name is None: - return FLAGS.default_instance_type +def get_default_instance_type(): + name = FLAGS.default_instance_type try: - ctxt = context.get_admin_context() - inst_type = db.instance_type_get_by_name(ctxt, name) - return inst_type + return get_instance_type_by_name(name) except exception.DBError: raise exception.ApiError(_("Unknown instance type: %s" % name)) -def get_by_type(instance_type): - """retrieve instance type name""" - if instance_type is None: - return FLAGS.default_instance_type +def get_instance_type(id): + """Retrieves single instance type by id""" + if id is None: + return get_default_instance_type() + try: + ctxt = context.get_admin_context() + return db.instance_type_get_by_id(ctxt, id) + except exception.DBError: + raise exception.ApiError(_("Unknown instance type: %s" % name)) + +def get_instance_type_by_name(name): + """Retrieves single instance type by name""" + if name is None: + return get_default_instance_type() try: ctxt = context.get_admin_context() - inst_type = db.instance_type_get_by_name(ctxt, instance_type) - return inst_type['name'] - except exception.DBError, e: - LOG.exception(_('DB error: %s' % e)) - raise exception.ApiError(_("Unknown instance type: %s" %\ - instance_type)) + return db.instance_type_get_by_name(ctxt, name) + except exception.DBError: + raise exception.ApiError(_("Unknown instance type: %s" % name)) -def get_by_flavor_id(flavor_id): - """retrieve instance type's name by flavor_id""" +def get_instance_type_by_flavor_id(flavor_id): + """retrieve instance type by flavor_id""" if flavor_id is None: - return FLAGS.default_instance_type + return get_default_instance_type() try: ctxt = context.get_admin_context() - flavor = db.instance_type_get_by_flavor_id(ctxt, flavor_id) - return flavor['name'] + return db.instance_type_get_by_flavor_id(ctxt, flavor_id) except exception.DBError, e: LOG.exception(_('DB error: %s' % e)) raise exception.ApiError(_("Unknown flavor: %s" % flavor_id)) diff --git a/nova/db/api.py b/nova/db/api.py index fd3c63b76..63901e94d 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -1124,6 +1124,11 @@ def instance_type_get_all(context, inactive=False): return IMPL.instance_type_get_all(context, inactive) +def instance_type_get_by_id(context, id): + """Get instance type by id""" + return IMPL.instance_type_get_by_id(context, id) + + def instance_type_get_by_name(context, name): """Get instance type by name""" return IMPL.instance_type_get_by_name(context, name) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index b2a13a01b..9f600b236 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -829,6 +829,7 @@ def instance_get(context, instance_id, session=None): options(joinedload('volumes')).\ options(joinedload_all('fixed_ip.network')).\ options(joinedload('metadata')).\ + options(joinedload('instance_type')).\ filter_by(id=instance_id).\ filter_by(deleted=can_read_deleted(context)).\ first() @@ -838,6 +839,7 @@ def instance_get(context, instance_id, session=None): options(joinedload_all('security_groups.rules')).\ options(joinedload('volumes')).\ options(joinedload('metadata')).\ + options(joinedload('instance_type')).\ filter_by(project_id=context.project_id).\ filter_by(id=instance_id).\ filter_by(deleted=False).\ @@ -857,6 +859,7 @@ def instance_get_all(context): options(joinedload_all('fixed_ip.floating_ips')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ip.network')).\ + options(joinedload('instance_type')).\ filter_by(deleted=can_read_deleted(context)).\ all() @@ -868,6 +871,7 @@ def instance_get_all_by_user(context, user_id): options(joinedload_all('fixed_ip.floating_ips')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ip.network')).\ + options(joinedload('instance_type')).\ filter_by(deleted=can_read_deleted(context)).\ filter_by(user_id=user_id).\ all() @@ -880,6 +884,7 @@ def instance_get_all_by_host(context, host): options(joinedload_all('fixed_ip.floating_ips')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ip.network')).\ + options(joinedload('instance_type')).\ filter_by(host=host).\ filter_by(deleted=can_read_deleted(context)).\ all() @@ -894,6 +899,7 @@ def instance_get_all_by_project(context, project_id): options(joinedload_all('fixed_ip.floating_ips')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ip.network')).\ + options(joinedload('instance_type')).\ filter_by(project_id=project_id).\ filter_by(deleted=can_read_deleted(context)).\ all() @@ -908,6 +914,7 @@ def instance_get_all_by_reservation(context, reservation_id): options(joinedload_all('fixed_ip.floating_ips')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ip.network')).\ + options(joinedload('instance_type')).\ filter_by(reservation_id=reservation_id).\ filter_by(deleted=can_read_deleted(context)).\ all() @@ -916,6 +923,7 @@ def instance_get_all_by_reservation(context, reservation_id): options(joinedload_all('fixed_ip.floating_ips')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ip.network')).\ + options(joinedload('instance_type')).\ filter_by(project_id=context.project_id).\ filter_by(reservation_id=reservation_id).\ filter_by(deleted=False).\ @@ -928,6 +936,7 @@ def instance_get_project_vpn(context, project_id): return session.query(models.Instance).\ options(joinedload_all('fixed_ip.floating_ips')).\ options(joinedload('security_groups')).\ + options(joinedload('instance_type')).\ filter_by(project_id=project_id).\ filter_by(image_id=FLAGS.vpn_image_id).\ filter_by(deleted=can_read_deleted(context)).\ @@ -2368,6 +2377,19 @@ def instance_type_get_all(context, inactive=False): raise exception.NotFound +@require_context +def instance_type_get_by_id(context, id): + """Returns a dict describing specific instance_type""" + session = get_session() + inst_type = session.query(models.InstanceTypes).\ + filter_by(id=id).\ + first() + if not inst_type: + raise exception.NotFound(_("No instance type with id %s") % id) + else: + return dict(inst_type) + + @require_context def instance_type_get_by_name(context, name): """Returns a dict describing specific instance_type""" diff --git a/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py new file mode 100644 index 000000000..813e57e10 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py @@ -0,0 +1,86 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 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 * +from sqlalchemy.sql import text +from migrate import * + +#from nova import log as logging + + +meta = MetaData() + + +c_instance_type = Column('instance_type', + String(length=255, convert_unicode=False, + assert_unicode=None, unicode_error=None, + _warn_on_bytestring=False), + nullable=True) + +c_instance_type_id = Column('instance_type_id', + String(length=255, convert_unicode=False, + assert_unicode=None, unicode_error=None, + _warn_on_bytestring=False), + nullable=True) + +instance_types = Table('instance_types', meta, + Column('id', Integer(), primary_key=True, nullable=False), + Column('name', + String(length=255, convert_unicode=False, assert_unicode=None, + unicode_error=None, _warn_on_bytestring=False), + unique=True)) + + +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 = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + + instances.create_column(c_instance_type_id) + + recs = migrate_engine.execute(instance_types.select()) + for row in recs: + type_id = row[0] + type_name = row[1] + migrate_engine.execute(instances.update()\ + .where(instances.c.instance_type == type_name)\ + .values(instance_type_id=type_id)) + + instances.c.instance_type.drop() + #instances.c.instance_type_id.alter(nullable=False) + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + + instances = Table('instances', meta, autoload=True, + autoload_with=migrate_engine) + + instances.create_column(c_instance_type) + + recs = migrate_engine.execute(instance_types.select()) + for row in recs: + type_id = row[0] + type_name = row[1] + migrate_engine.execute(instances.update()\ + .where(instances.c.instance_type_id == type_id)\ + .values(instance_type=type_name)) + + instances.c.instance_type_id.drop() + #instances.c.instance_type.alter(nullable=False) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 3b95ac23e..9d4c6cdef 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -209,7 +209,7 @@ class Instance(BASE, NovaBase): hostname = Column(String(255)) host = Column(String(255)) # , ForeignKey('hosts.id')) - instance_type = Column(String(255)) + instance_type_id = Column(String(255)) user_data = Column(Text) @@ -258,7 +258,8 @@ class InstanceActions(BASE, NovaBase): class InstanceTypes(BASE, NovaBase): """Represent possible instance_types or flavor of VM offered""" __tablename__ = "instance_types" - id = Column(Integer, primary_key=True) + id = Column(Integer, ForeignKey('instances.instance_type_id'), + primary_key=True) name = Column(String(255), unique=True) memory_mb = Column(Integer) vcpus = Column(Integer) @@ -268,6 +269,12 @@ class InstanceTypes(BASE, NovaBase): rxtx_quota = Column(Integer, nullable=False, default=0) rxtx_cap = Column(Integer, nullable=False, default=0) + instances = relationship(Instance, + backref=backref('instance_type', uselist=False), + foreign_keys=id, + primaryjoin='and_(Instance.instance_type_id == ' + 'InstanceTypes.id)') + class Volume(BASE, NovaBase): """Represents a block storage device that can be attached to a vm.""" diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index cb5047da9..899dcf7f7 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -32,6 +32,7 @@ from nova import test import nova.api.openstack from nova.api.openstack import servers import nova.compute.api +from nova.compute import instance_types import nova.db.api from nova.db.sqlalchemy.models import Instance from nova.db.sqlalchemy.models import InstanceMetadata @@ -71,13 +72,19 @@ def instance_address(context, instance_id): return None -def stub_instance(id, user_id=1, private_address=None, public_addresses=None): +def stub_instance(id, user_id=1, private_address=None, public_addresses=None, + host=None): metadata = [] metadata.append(InstanceMetadata(key='seq', value=id)) + inst_type = instance_types.get_instance_type_by_flavor_id(1) + if public_addresses == None: public_addresses = list() + if host != None: + host = str(host) + instance = { "id": id, "admin_pass": "", @@ -95,8 +102,8 @@ def stub_instance(id, user_id=1, private_address=None, public_addresses=None): "vcpus": 0, "local_gb": 0, "hostname": "", - "host": None, - "instance_type": "1", + "host": host, + "instance_type": dict(inst_type), "user_data": "", "reservation_id": "", "mac_address": "", @@ -630,7 +637,7 @@ class ServersTest(test.TestCase): self.assertEqual(s['hostId'], '') self.assertEqual(s['name'], 'server%d' % i) self.assertEqual(s['imageId'], '10') - self.assertEqual(s['flavorId'], '1') + self.assertEqual(s['flavorId'], 1) self.assertEqual(s['metadata']['seq'], i) def test_get_all_server_details_v1_1(self): @@ -654,12 +661,8 @@ class ServersTest(test.TestCase): instances - 2 on one host and 3 on another. ''' - def stub_instance(id, user_id=1): - return Instance(id=id, state=0, image_id=10, user_id=user_id, - display_name='server%s' % id, host='host%s' % (id % 2)) - def return_servers_with_host(context, user_id=1): - return [stub_instance(i) for i in xrange(5)] + return [stub_instance(i, 1, None, None, i % 2) for i in xrange(5)] self.stubs.Set(nova.db.api, 'instance_get_all_by_user', return_servers_with_host) @@ -677,7 +680,8 @@ class ServersTest(test.TestCase): self.assertEqual(s['id'], i) self.assertEqual(s['hostId'], host_ids[i % 2]) self.assertEqual(s['name'], 'server%d' % i) - self.assertEqual(s['imageId'], 10) + self.assertEqual(s['imageId'], '10') + self.assertEqual(s['flavorId'], 1) def test_server_pause(self): FLAGS.allow_admin_api = True diff --git a/nova/tests/db/fakes.py b/nova/tests/db/fakes.py index 7ddfe377a..58d251b1e 100644 --- a/nova/tests/db/fakes.py +++ b/nova/tests/db/fakes.py @@ -28,29 +28,34 @@ def stub_out_db_instance_api(stubs, injected=True): """Stubs out the db API for creating Instances.""" INSTANCE_TYPES = { - 'm1.tiny': dict(memory_mb=512, + 'm1.tiny': dict(id=2, + memory_mb=512, vcpus=1, local_gb=0, flavorid=1, rxtx_cap=1), - 'm1.small': dict(memory_mb=2048, + 'm1.small': dict(id=5, + memory_mb=2048, vcpus=1, local_gb=20, flavorid=2, rxtx_cap=2), 'm1.medium': - dict(memory_mb=4096, + dict(id=1, + memory_mb=4096, vcpus=2, local_gb=40, flavorid=3, rxtx_cap=3), - 'm1.large': dict(memory_mb=8192, + 'm1.large': dict(id=3, + memory_mb=8192, vcpus=4, local_gb=80, flavorid=4, rxtx_cap=4), 'm1.xlarge': - dict(memory_mb=16384, + dict(id=4, + memory_mb=16384, vcpus=8, local_gb=160, flavorid=5, @@ -107,6 +112,12 @@ def stub_out_db_instance_api(stubs, injected=True): def fake_instance_type_get_by_name(context, name): return INSTANCE_TYPES[name] + def fake_instance_type_get_by_id(context, id): + for name, inst_type in INSTANCE_TYPES.iteritems(): + if str(inst_type['id']) == str(id): + return inst_type + return None + def fake_network_get_by_instance(context, instance_id): # Even instance numbers are on vlan networks if instance_id % 2 == 0: @@ -136,6 +147,7 @@ def stub_out_db_instance_api(stubs, injected=True): fake_network_get_all_by_instance) stubs.Set(db, 'instance_type_get_all', fake_instance_type_get_all) stubs.Set(db, 'instance_type_get_by_name', fake_instance_type_get_by_name) + stubs.Set(db, 'instance_type_get_by_id', fake_instance_type_get_by_id) stubs.Set(db, 'instance_get_fixed_address', fake_instance_get_fixed_address) stubs.Set(db, 'instance_get_fixed_address_v6', diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 1b0f426d2..1917dff3e 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -84,7 +84,8 @@ class ComputeTestCase(test.TestCase): inst['launch_time'] = '10' inst['user_id'] = self.user.id inst['project_id'] = self.project.id - inst['instance_type'] = 'm1.tiny' + type_id = instance_types.get_instance_type_by_name('m1.tiny')['id'] + inst['instance_type_id'] = type_id inst['mac_address'] = utils.generate_mac() inst['ami_launch_index'] = 0 inst.update(params) @@ -132,7 +133,7 @@ class ComputeTestCase(test.TestCase): cases = [dict(), dict(display_name=None)] for instance in cases: ref = self.compute_api.create(self.context, - FLAGS.default_instance_type, None, **instance) + instance_types.get_default_instance_type(), None, **instance) try: self.assertNotEqual(ref[0]['display_name'], None) finally: @@ -143,7 +144,7 @@ class ComputeTestCase(test.TestCase): group = self._create_group() ref = self.compute_api.create( self.context, - instance_type=FLAGS.default_instance_type, + instance_type=instance_types.get_default_instance_type(), image_id=None, security_group=['testgroup']) try: @@ -161,7 +162,7 @@ class ComputeTestCase(test.TestCase): ref = self.compute_api.create( self.context, - instance_type=FLAGS.default_instance_type, + instance_type=instance_types.get_default_instance_type(), image_id=None, security_group=['testgroup']) try: @@ -177,7 +178,7 @@ class ComputeTestCase(test.TestCase): ref = self.compute_api.create( self.context, - instance_type=FLAGS.default_instance_type, + instance_type=instance_types.get_default_instance_type(), image_id=None, security_group=['testgroup']) @@ -359,8 +360,9 @@ class ComputeTestCase(test.TestCase): instance_id = self._create_instance() self.compute.run_instance(self.context, instance_id) + inst_type = instance_types.get_instance_type_by_name('m1.xlarge') db.instance_update(self.context, instance_id, - {'instance_type': 'm1.xlarge'}) + {'instance_type_id': inst_type['id']}) self.assertRaises(exception.ApiError, self.compute_api.resize, context, instance_id, 1) @@ -380,8 +382,8 @@ class ComputeTestCase(test.TestCase): self.compute.terminate_instance(context, instance_id) def test_get_by_flavor_id(self): - type = instance_types.get_by_flavor_id(1) - self.assertEqual(type, 'm1.tiny') + type = instance_types.get_instance_type_by_flavor_id(1) + self.assertEqual(type['name'], 'm1.tiny') def test_resize_same_source_fails(self): """Ensure instance fails to migrate when source and destination are diff --git a/nova/tests/test_console.py b/nova/tests/test_console.py index d47c70d88..1a9a867ee 100644 --- a/nova/tests/test_console.py +++ b/nova/tests/test_console.py @@ -62,7 +62,7 @@ class ConsoleTestCase(test.TestCase): inst['launch_time'] = '10' inst['user_id'] = self.user.id inst['project_id'] = self.project.id - inst['instance_type'] = 'm1.tiny' + inst['instance_type_id'] = 1 inst['mac_address'] = utils.generate_mac() inst['ami_launch_index'] = 0 return db.instance_create(self.context, inst)['id'] diff --git a/nova/tests/test_instance_types.py b/nova/tests/test_instance_types.py index edc538879..5d6d5e1f4 100644 --- a/nova/tests/test_instance_types.py +++ b/nova/tests/test_instance_types.py @@ -40,7 +40,11 @@ class InstanceTypeTestCase(test.TestCase): max_flavorid = session.query(models.InstanceTypes).\ order_by("flavorid desc").\ first() + max_id = session.query(models.InstanceTypes).\ + order_by("id desc").\ + first() self.flavorid = max_flavorid["flavorid"] + 1 + self.id = max_id["id"] + 1 self.name = str(int(time.time())) def test_instance_type_create_then_delete(self): @@ -53,7 +57,7 @@ class InstanceTypeTestCase(test.TestCase): 'instance type was not created') instance_types.destroy(self.name) self.assertEqual(1, - instance_types.get_instance_type(self.name)["deleted"]) + instance_types.get_instance_type(self.id)["deleted"]) self.assertEqual(starting_inst_list, instance_types.get_all_types()) instance_types.purge(self.name) self.assertEqual(len(starting_inst_list), diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py index c65bc459d..39a123158 100644 --- a/nova/tests/test_quota.py +++ b/nova/tests/test_quota.py @@ -67,7 +67,7 @@ class QuotaTestCase(test.TestCase): inst['reservation_id'] = 'r-fakeres' inst['user_id'] = self.user.id inst['project_id'] = self.project.id - inst['instance_type'] = 'm1.large' + inst['instance_type_id'] = '3' # m1.large inst['vcpus'] = cores inst['mac_address'] = utils.generate_mac() return db.instance_create(self.context, inst)['id'] @@ -124,11 +124,12 @@ class QuotaTestCase(test.TestCase): for i in range(FLAGS.quota_instances): instance_id = self._create_instance() instance_ids.append(instance_id) + inst_type = instance_types.get_instance_type_by_name('m1.small') self.assertRaises(quota.QuotaError, compute.API().create, self.context, min_count=1, max_count=1, - instance_type='m1.small', + instance_type=inst_type, image_id=1) for instance_id in instance_ids: db.instance_destroy(self.context, instance_id) @@ -137,11 +138,12 @@ class QuotaTestCase(test.TestCase): instance_ids = [] instance_id = self._create_instance(cores=4) instance_ids.append(instance_id) + inst_type = instance_types.get_instance_type_by_name('m1.small') self.assertRaises(quota.QuotaError, compute.API().create, self.context, min_count=1, max_count=1, - instance_type='m1.small', + instance_type=inst_type, image_id=1) for instance_id in instance_ids: db.instance_destroy(self.context, instance_id) @@ -192,11 +194,12 @@ class QuotaTestCase(test.TestCase): metadata = {} for i in range(FLAGS.quota_metadata_items + 1): metadata['key%s' % i] = 'value%s' % i + inst_type = instance_types.get_instance_type_by_name('m1.small') self.assertRaises(quota.QuotaError, compute.API().create, self.context, min_count=1, max_count=1, - instance_type='m1.small', + instance_type=inst_type, image_id='fake', metadata=metadata) @@ -207,13 +210,15 @@ class QuotaTestCase(test.TestCase): def _create_with_injected_files(self, files): api = compute.API(image_service=self.StubImageService()) + inst_type = instance_types.get_instance_type_by_name('m1.small') api.create(self.context, min_count=1, max_count=1, - instance_type='m1.small', image_id='fake', + instance_type=inst_type, image_id='fake', injected_files=files) def test_no_injected_files(self): api = compute.API(image_service=self.StubImageService()) - api.create(self.context, instance_type='m1.small', image_id='fake') + inst_type = instance_types.get_instance_type_by_name('m1.small') + api.create(self.context, instance_type=inst_type, image_id='fake') def test_max_injected_files(self): files = [] diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py index 6df74dd61..ae56a1a16 100644 --- a/nova/tests/test_scheduler.py +++ b/nova/tests/test_scheduler.py @@ -263,7 +263,7 @@ class SimpleDriverTestCase(test.TestCase): inst['reservation_id'] = 'r-fakeres' inst['user_id'] = self.user.id inst['project_id'] = self.project.id - inst['instance_type'] = 'm1.tiny' + inst['instance_type_id'] = '1' inst['mac_address'] = utils.generate_mac() inst['vcpus'] = kwargs.get('vcpus', 1) inst['ami_launch_index'] = 0 diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 958c8e3e2..c13cbf043 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -140,7 +140,7 @@ class LibvirtConnTestCase(test.TestCase): 'vcpus': 2, 'project_id': 'fake', 'bridge': 'br101', - 'instance_type': 'm1.small'} + 'instance_type_id': '5'} # m1.small def lazy_load_library_exists(self): """check if libvirt is available.""" diff --git a/nova/tests/test_volume.py b/nova/tests/test_volume.py index d71b75f3f..e9d8289aa 100644 --- a/nova/tests/test_volume.py +++ b/nova/tests/test_volume.py @@ -106,7 +106,7 @@ class VolumeTestCase(test.TestCase): inst['launch_time'] = '10' inst['user_id'] = 'fake' inst['project_id'] = 'fake' - inst['instance_type'] = 'm1.tiny' + inst['instance_type_id'] = '2' # m1.tiny inst['mac_address'] = utils.generate_mac() inst['ami_launch_index'] = 0 instance_id = db.instance_create(self.context, inst)['id'] diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 17e3f55e9..665ec068e 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -80,7 +80,7 @@ class XenAPIVolumeTestCase(test.TestCase): 'image_id': 1, 'kernel_id': 2, 'ramdisk_id': 3, - 'instance_type': 'm1.large', + 'instance_type_id': '3', # m1.large 'mac_address': 'aa:bb:cc:dd:ee:ff', 'os_type': 'linux'} @@ -328,7 +328,7 @@ class XenAPIVMTestCase(test.TestCase): self.assertEquals(self.vm['HVM_boot_policy'], '') def _test_spawn(self, image_id, kernel_id, ramdisk_id, - instance_type="m1.large", os_type="linux", + instance_type_id="3", os_type="linux", instance_id=1, check_injection=False): stubs.stubout_loopingcall_start(self.stubs) values = {'id': instance_id, @@ -337,7 +337,7 @@ class XenAPIVMTestCase(test.TestCase): 'image_id': image_id, 'kernel_id': kernel_id, 'ramdisk_id': ramdisk_id, - 'instance_type': instance_type, + 'instance_type_id': instance_type_id, 'mac_address': 'aa:bb:cc:dd:ee:ff', 'os_type': os_type} instance = db.instance_create(self.context, values) @@ -349,7 +349,7 @@ class XenAPIVMTestCase(test.TestCase): FLAGS.xenapi_image_service = 'glance' self.assertRaises(Exception, self._test_spawn, - 1, 2, 3, "m1.xlarge") + 1, 2, 3, "4") # m1.xlarge def test_spawn_raw_objectstore(self): FLAGS.xenapi_image_service = 'objectstore' @@ -523,7 +523,7 @@ class XenAPIVMTestCase(test.TestCase): 'image_id': 1, 'kernel_id': 2, 'ramdisk_id': 3, - 'instance_type': 'm1.large', + 'instance_type_id': '3', # m1.large 'mac_address': 'aa:bb:cc:dd:ee:ff', 'os_type': 'linux'} instance = db.instance_create(self.context, values) @@ -580,7 +580,7 @@ class XenAPIMigrateInstance(test.TestCase): 'kernel_id': None, 'ramdisk_id': None, 'local_gb': 5, - 'instance_type': 'm1.large', + 'instance_type_id': '3', # m1.large 'mac_address': 'aa:bb:cc:dd:ee:ff', 'os_type': 'linux'} diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index f34ea7225..6b7fce634 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -797,7 +797,10 @@ class LibvirtConnection(driver.ComputeDriver): root_fname = '%08x' % int(disk_images['image_id']) size = FLAGS.minimum_root_size - if inst['instance_type'] == 'm1.tiny' or suffix == '.rescue': + + inst_type_id = instance['instance_type_id'] + inst_type = instance_types.get_instance_type(inst_type_id) + if inst_type['name'] == 'm1.tiny' or suffix == '.rescue': size = None root_fname += "_sm" @@ -809,14 +812,13 @@ class LibvirtConnection(driver.ComputeDriver): user=user, project=project, size=size) - type_data = instance_types.get_instance_type(inst['instance_type']) - if type_data['local_gb']: + if inst_type['local_gb']: self._cache_image(fn=self._create_local, target=basepath('disk.local'), - fname="local_%s" % type_data['local_gb'], + fname="local_%s" % inst_type['local_gb'], cow=FLAGS.use_cow_images, - local_gb=type_data['local_gb']) + local_gb=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 @@ -950,8 +952,8 @@ class LibvirtConnection(driver.ComputeDriver): nics.append(self._get_nic_for_xml(network, mapping)) # FIXME(vish): stick this in db - instance_type_name = instance['instance_type'] - instance_type = instance_types.get_instance_type(instance_type_name) + inst_type_id = instance['instance_type_id'] + inst_type = instance_types.get_instance_type(inst_type_id) if FLAGS.use_cow_images: driver_type = 'qcow2' @@ -962,10 +964,10 @@ class LibvirtConnection(driver.ComputeDriver): 'name': instance['name'], 'basepath': os.path.join(FLAGS.instances_path, instance['name']), - 'memory_kb': instance_type['memory_mb'] * 1024, - 'vcpus': instance_type['vcpus'], + 'memory_kb': inst_type['memory_mb'] * 1024, + 'vcpus': inst_type['vcpus'], 'rescue': rescue, - 'local': instance_type['local_gb'], + 'local': inst_type['local_gb'], 'driver_type': driver_type, 'nics': nics} diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index d07d60800..fd4f3705a 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -101,8 +101,8 @@ class VMHelper(HelperBase): 3. Using hardware virtualization """ - instance_type = instance_types.\ - get_instance_type(instance.instance_type) + inst_type_id = instance.instance_type_id + instance_type = instance_types.get_instance_type(inst_type_id) mem = str(long(instance_type['memory_mb']) * 1024 * 1024) vcpus = str(instance_type['vcpus']) rec = { @@ -169,8 +169,8 @@ class VMHelper(HelperBase): @classmethod def ensure_free_mem(cls, session, instance): - instance_type = instance_types.get_instance_type( - instance.instance_type) + inst_type_id = instance.instance_type_id + instance_type = instance_types.get_instance_type(inst_type_id) mem = long(instance_type['memory_mb']) * 1024 * 1024 #get free memory from host host = session.get_xenapi_host() diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index c96c35a6e..c26965c9a 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -802,8 +802,10 @@ class VMOps(object): instance['id']) networks = db.network_get_all_by_instance(admin_context, instance['id']) - flavor = db.instance_type_get_by_name(admin_context, - instance['instance_type']) + + inst_type = db.instance_type_get_by_id(admin_context, + instance['instance_type_id']) + network_info = [] for network in networks: network_IPs = [ip for ip in IPs if ip.network_id == network.id] @@ -827,7 +829,7 @@ class VMOps(object): 'gateway': network['gateway'], 'broadcast': network['broadcast'], 'mac': instance.mac_address, - 'rxtx_cap': flavor['rxtx_cap'], + 'rxtx_cap': inst_type['rxtx_cap'], 'dns': [network['dns']], 'ips': [ip_dict(ip) for ip in network_IPs]} if network['cidr_v6']: -- cgit From 5c67809e4b9a1546c48316ea52676dfeba8f1a75 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Mon, 4 Apr 2011 15:58:41 -0700 Subject: openstack api requires uppercase image format status responses --- nova/api/openstack/views/images.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index 16195b050..9dec8a355 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -34,11 +34,11 @@ class ViewBuilder(object): def _format_status(self, image): """Update the status field to standardize format.""" status_mapping = { - 'pending': 'queued', - 'decrypting': 'preparing', - 'untarring': 'saving', - 'available': 'active', - 'killed': 'failed', + 'pending': 'QUEUED', + 'decrypting': 'PREPARING', + 'untarring': 'SAVING', + 'available': 'ACTIVE', + 'killed': 'FAILED', } try: -- cgit From a6c283f00d67a5172d8271d7e48bd58484ac6b96 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Mon, 4 Apr 2011 16:00:59 -0700 Subject: openstack api metadata responses must be strings --- nova/api/openstack/views/servers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index d24c025be..baa911590 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -82,7 +82,8 @@ class ViewBuilder(object): # Return the metadata as a dictionary metadata = {} for item in inst.get('metadata', []): - metadata[item['key']] = item['value'] + # metadata values must be strings per API + metadata[item['key']] = str(item['value']) inst_dict['metadata'] = metadata inst_dict['hostId'] = '' -- cgit From 868288b9705ea765b5515fd0ac9e0858f37239d0 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Mon, 4 Apr 2011 16:07:39 -0700 Subject: correct test for numeric/string metadata value conversion --- nova/tests/api/openstack/test_servers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 313676e72..08a3f5d5a 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -630,7 +630,7 @@ class ServersTest(test.TestCase): self.assertEqual(s['imageId'], '10') self.assertEqual(s['flavorId'], '1') self.assertEqual(s['status'], 'BUILD') - self.assertEqual(s['metadata']['seq'], i) + self.assertEqual(s['metadata']['seq'], str(i)) def test_get_all_server_details_v1_1(self): req = webob.Request.blank('/v1.1/servers/detail') @@ -644,7 +644,7 @@ class ServersTest(test.TestCase): self.assertEqual(s['imageRef'], 'http://localhost/v1.1/images/10') self.assertEqual(s['flavorRef'], 'http://localhost/v1.1/flavors/1') self.assertEqual(s['status'], 'BUILD') - self.assertEqual(s['metadata']['seq'], i) + self.assertEqual(s['metadata']['seq'], str(i)) def test_get_all_server_details_with_host(self): ''' -- cgit From a18fece993c21f2ae1cbb44d8a0dea92d58d3b44 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 4 Apr 2011 22:16:53 -0400 Subject: Correct variable name. --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index d4cef8d7c..099ef647c 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -798,7 +798,7 @@ class LibvirtConnection(driver.ComputeDriver): root_fname = '%08x' % int(disk_images['image_id']) size = FLAGS.minimum_root_size - inst_type_id = instance['instance_type_id'] + inst_type_id = inst['instance_type_id'] inst_type = instance_types.get_instance_type(inst_type_id) if inst_type['name'] == 'm1.tiny' or suffix == '.rescue': size = None -- cgit From ff23dd2a3b86c816da04eddc903de0c8c3141954 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 5 Apr 2011 11:42:14 +0200 Subject: Allow CA code and state to be separated, and make sure CA code gets installed by setup.py install. --- nova/CA/.gitignore | 11 ++++++ nova/CA/geninter.sh | 39 +++++++++++++++++++ nova/CA/genrootca.sh | 29 ++++++++++++++ nova/CA/genvpn.sh | 36 +++++++++++++++++ nova/CA/newcerts/.placeholder | 0 nova/CA/openssl.cnf.tmpl | 90 +++++++++++++++++++++++++++++++++++++++++++ nova/CA/private/.placeholder | 0 nova/CA/projects/.gitignore | 1 + nova/CA/projects/.placeholder | 0 nova/CA/reqs/.gitignore | 1 + nova/CA/reqs/.placeholder | 0 nova/api/ec2/cloud.py | 8 +++- nova/crypto.py | 10 ++++- 13 files changed, 222 insertions(+), 3 deletions(-) create mode 100644 nova/CA/.gitignore create mode 100755 nova/CA/geninter.sh create mode 100755 nova/CA/genrootca.sh create mode 100755 nova/CA/genvpn.sh create mode 100644 nova/CA/newcerts/.placeholder create mode 100644 nova/CA/openssl.cnf.tmpl create mode 100644 nova/CA/private/.placeholder create mode 100644 nova/CA/projects/.gitignore create mode 100644 nova/CA/projects/.placeholder create mode 100644 nova/CA/reqs/.gitignore create mode 100644 nova/CA/reqs/.placeholder (limited to 'nova') diff --git a/nova/CA/.gitignore b/nova/CA/.gitignore new file mode 100644 index 000000000..fae0922bf --- /dev/null +++ b/nova/CA/.gitignore @@ -0,0 +1,11 @@ +index.txt +index.txt.old +index.txt.attr +index.txt.attr.old +cacert.pem +serial +serial.old +openssl.cnf +private/* +newcerts/* + diff --git a/nova/CA/geninter.sh b/nova/CA/geninter.sh new file mode 100755 index 000000000..1fbcc9e73 --- /dev/null +++ b/nova/CA/geninter.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# 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. + +# $1 is the id of the project and $2 is the subject of the cert +NAME=$1 +SUBJ=$2 +mkdir -p projects/$NAME +cd projects/$NAME +cp ../../openssl.cnf.tmpl openssl.cnf +sed -i -e s/%USERNAME%/$NAME/g openssl.cnf +mkdir certs crl newcerts private +openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes +echo "10" > serial +touch index.txt +# NOTE(vish): Disabling intermediate ca's because we don't actually need them. +# It makes more sense to have each project have its own root ca. +# openssl genrsa -out private/cakey.pem 1024 -config ./openssl.cnf -batch -nodes +# openssl req -new -sha256 -key private/cakey.pem -out ../../reqs/inter$NAME.csr -batch -subj "$SUBJ" +openssl ca -gencrl -config ./openssl.cnf -out crl.pem +if [ "`id -u`" != "`grep nova /etc/passwd | cut -d':' -f3`" ]; then + sudo chown -R nova:nogroup . +fi +# cd ../../ +# openssl ca -extensions v3_ca -days 365 -out INTER/$NAME/cacert.pem -in reqs/inter$NAME.csr -config openssl.cnf -batch diff --git a/nova/CA/genrootca.sh b/nova/CA/genrootca.sh new file mode 100755 index 000000000..8f2c3ee3f --- /dev/null +++ b/nova/CA/genrootca.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# 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. + +if [ -f "cacert.pem" ]; +then + echo "Not installing, it's already done." +else + cp openssl.cnf.tmpl openssl.cnf + sed -i -e s/%USERNAME%/ROOT/g openssl.cnf + openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes + touch index.txt + echo "10" > serial + openssl ca -gencrl -config ./openssl.cnf -out crl.pem +fi diff --git a/nova/CA/genvpn.sh b/nova/CA/genvpn.sh new file mode 100755 index 000000000..7e7db185d --- /dev/null +++ b/nova/CA/genvpn.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# 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. + +# This gets zipped and run on the cloudpipe-managed OpenVPN server +NAME=$1 +SUBJ=$2 + +mkdir -p projects/$NAME +cd projects/$NAME + +# generate a server priv key +openssl genrsa -out server.key 2048 + +# generate a server CSR +openssl req -new -key server.key -out server.csr -batch -subj "$SUBJ" + +novauid=`getent passwd nova | awk -F: '{print $3}'` +if [ ! -z "${novauid}" ] && [ "`id -u`" != "${novauid}" ]; then + sudo chown -R nova:nogroup . +fi diff --git a/nova/CA/newcerts/.placeholder b/nova/CA/newcerts/.placeholder new file mode 100644 index 000000000..e69de29bb diff --git a/nova/CA/openssl.cnf.tmpl b/nova/CA/openssl.cnf.tmpl new file mode 100644 index 000000000..dd81f1c2b --- /dev/null +++ b/nova/CA/openssl.cnf.tmpl @@ -0,0 +1,90 @@ +# 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. + +# +# OpenSSL configuration file. +# + +# Establish working directory. + +dir = . + +[ ca ] +default_ca = CA_default + +[ CA_default ] +serial = $dir/serial +database = $dir/index.txt +new_certs_dir = $dir/newcerts +certificate = $dir/cacert.pem +private_key = $dir/private/cakey.pem +unique_subject = no +default_crl_days = 365 +default_days = 365 +default_md = md5 +preserve = no +email_in_dn = no +nameopt = default_ca +certopt = default_ca +policy = policy_match + +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + + +[ req ] +default_bits = 1024 # Size of keys +default_keyfile = key.pem # name of generated keys +default_md = md5 # message digest algorithm +string_mask = nombstr # permitted characters +distinguished_name = req_distinguished_name + +[ req_distinguished_name ] +# Variable name Prompt string +#---------------------- ---------------------------------- +0.organizationName = Organization Name (company) +organizationalUnitName = Organizational Unit Name (department, division) +emailAddress = Email Address +emailAddress_max = 40 +localityName = Locality Name (city, district) +stateOrProvinceName = State or Province Name (full name) +countryName = Country Name (2 letter code) +countryName_min = 2 +countryName_max = 2 +commonName = Common Name (hostname, IP, or your name) +commonName_max = 64 + +# Default values for the above, for consistency and less typing. +# Variable name Value +#------------------------------ ------------------------------ +0.organizationName_default = NOVA %USERNAME% +localityName_default = Mountain View +stateOrProvinceName_default = California +countryName_default = US + +[ v3_ca ] +basicConstraints = CA:TRUE +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer:always + +[ v3_req ] +basicConstraints = CA:FALSE +subjectKeyIdentifier = hash diff --git a/nova/CA/private/.placeholder b/nova/CA/private/.placeholder new file mode 100644 index 000000000..e69de29bb diff --git a/nova/CA/projects/.gitignore b/nova/CA/projects/.gitignore new file mode 100644 index 000000000..72e8ffc0d --- /dev/null +++ b/nova/CA/projects/.gitignore @@ -0,0 +1 @@ +* diff --git a/nova/CA/projects/.placeholder b/nova/CA/projects/.placeholder new file mode 100644 index 000000000..e69de29bb diff --git a/nova/CA/reqs/.gitignore b/nova/CA/reqs/.gitignore new file mode 100644 index 000000000..72e8ffc0d --- /dev/null +++ b/nova/CA/reqs/.gitignore @@ -0,0 +1 @@ +* diff --git a/nova/CA/reqs/.placeholder b/nova/CA/reqs/.placeholder new file mode 100644 index 000000000..e69de29bb diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 425784e8a..f119bd75c 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -103,10 +103,16 @@ class CloudController(object): # Gen root CA, if we don't have one root_ca_path = os.path.join(FLAGS.ca_path, FLAGS.ca_file) if not os.path.exists(root_ca_path): + genrootca_sh_path = os.path.join(os.path.dirname(__file__), + os.path.pardir, + os.path.pardir, + 'CA', + 'genrootca.sh') + start = os.getcwd() os.chdir(FLAGS.ca_path) # TODO(vish): Do this with M2Crypto instead - utils.runthis(_("Generating root CA: %s"), "sh", "genrootca.sh") + utils.runthis(_("Generating root CA: %s"), "sh", genrootca_sh_path) os.chdir(start) def _get_mpi_data(self, context, project_id): diff --git a/nova/crypto.py b/nova/crypto.py index b112e5b92..2b122e560 100644 --- a/nova/crypto.py +++ b/nova/crypto.py @@ -215,9 +215,12 @@ def generate_x509_cert(user_id, project_id, bits=1024): def _ensure_project_folder(project_id): if not os.path.exists(ca_path(project_id)): + geninter_sh_path = os.path.join(os.path.dirname(__file__), + 'CA', + 'geninter.sh') start = os.getcwd() os.chdir(ca_folder()) - utils.execute('sh', 'geninter.sh', project_id, + utils.execute('sh', geninter_sh_path, project_id, _project_cert_subject(project_id)) os.chdir(start) @@ -227,13 +230,16 @@ def generate_vpn_files(project_id): csr_fn = os.path.join(project_folder, "server.csr") crt_fn = os.path.join(project_folder, "server.crt") + genvpn_sh_path = os.path.join(os.path.dirname(__file__), + 'CA', + 'geninter.sh') if os.path.exists(crt_fn): return _ensure_project_folder(project_id) start = os.getcwd() os.chdir(ca_folder()) # TODO(vish): the shell scripts could all be done in python - utils.execute('sh', 'genvpn.sh', + utils.execute('sh', genvpn_sh_path, project_id, _vpn_cert_subject(project_id)) with open(csr_fn, "r") as csrfile: csr_text = csrfile.read() -- cgit From 7702affdbf09127e3e7cdfafcb6382673914438e Mon Sep 17 00:00:00 2001 From: Masanori Itoh Date: Tue, 5 Apr 2011 18:43:11 +0900 Subject: Remove and from AllocateAddress response, and fix bug #751176. --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 425784e8a..3b333437b 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -777,7 +777,7 @@ class CloudController(object): def allocate_address(self, context, **kwargs): LOG.audit(_("Allocate address"), context=context) public_ip = self.network_api.allocate_floating_ip(context) - return {'addressSet': [{'publicIp': public_ip}]} + return {'publicIp': public_ip} def release_address(self, context, public_ip, **kwargs): LOG.audit(_("Release address %s"), public_ip, context=context) -- cgit From 2b3aea4be35f370c68ac3c24ab15d4851aa28e94 Mon Sep 17 00:00:00 2001 From: Kei Masumoto Date: Tue, 5 Apr 2011 20:10:10 +0900 Subject: fix bug 746821 --- nova/tests/test_virt.py | 3 ++- nova/virt/libvirt_conn.py | 38 +++++++++++++++++++++++++++----------- 2 files changed, 29 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 958c8e3e2..2e9eac0d5 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -479,7 +479,7 @@ class LibvirtConnTestCase(test.TestCase): fake_timer = FakeTime() - self.create_fake_libvirt_mock(nwfilterLookupByName=fake_raise) + self.create_fake_libvirt_mock() instance_ref = db.instance_create(self.context, self.test_instance) # Start test @@ -488,6 +488,7 @@ class LibvirtConnTestCase(test.TestCase): conn = libvirt_conn.LibvirtConnection(False) conn.firewall_driver.setattr('setup_basic_filtering', fake_none) conn.firewall_driver.setattr('prepare_instance_filter', fake_none) + conn.firewall_driver.setattr('instance_filter_exists', fake_none) conn.ensure_filtering_rules_for_instance(instance_ref, time=fake_timer) except exception.Error, e: diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index babbc610d..bdf577825 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1401,18 +1401,13 @@ class LibvirtConnection(driver.ComputeDriver): # wait for completion timeout_count = range(FLAGS.live_migration_retry_count) while timeout_count: - try: - filter_name = 'nova-instance-%s' % instance_ref.name - self._conn.nwfilterLookupByName(filter_name) + if self.firewall_driver.instance_filter_exists(instance_ref): break - except libvirt.libvirtError: - timeout_count.pop() - if len(timeout_count) == 0: - ec2_id = instance_ref['hostname'] - iname = instance_ref.name - msg = _('Timeout migrating for %(ec2_id)s(%(iname)s)') - raise exception.Error(msg % locals()) - time.sleep(1) + timeout_count.pop() + if len(timeout_count) == 0: + msg = _('Timeout migrating for %s. nwfilter not found.') + raise exception.Error(msg % instance_ref.name) + time.sleep(1) def live_migration(self, ctxt, instance_ref, dest, post_method, recover_method): @@ -1541,6 +1536,10 @@ class FirewallDriver(object): """ raise NotImplementedError() + def instance_filter_exists(self, instance): + """Check nova-instance-instance-xxx exists""" + raise NotImplementedError() + class NWFilterFirewall(FirewallDriver): """ @@ -1848,6 +1847,19 @@ class NWFilterFirewall(FirewallDriver): return 'nova-instance-%s' % (instance['name']) return 'nova-instance-%s-%s' % (instance['name'], nic_id) + def instance_filter_exists(self, instance): + """Check nova-instance-instance-xxx exists""" + + network_info = _get_network_info(instance) + for (network, mapping) in network_info: + nic_id = mapping['mac'].replace(':', '') + instance_filter_name = self._instance_filter_name(instance, nic_id) + try: + self._conn.nwfilterLookupByName(instance_filter_name) + except libvirt.libvirtError: + return False + return True + class IptablesFirewallDriver(FirewallDriver): def __init__(self, execute=None, **kwargs): @@ -2037,6 +2049,10 @@ class IptablesFirewallDriver(FirewallDriver): return ipv4_rules, ipv6_rules + def instance_filter_exists(self, instance): + """Check nova-instance-instance-xxx exists""" + return self.nwfilter.instance_filter_exists(instance) + def refresh_security_group_members(self, security_group): pass -- cgit From d7013c9617d0740976a78ba87b1214c2b15ee702 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 5 Apr 2011 13:16:12 +0200 Subject: Automatically create CA state dir, and make sure the CA scripts look for the templates in the right places. --- nova/CA/geninter.sh | 2 +- nova/CA/genrootca.sh | 3 ++- nova/api/ec2/cloud.py | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/CA/geninter.sh b/nova/CA/geninter.sh index 1fbcc9e73..4b7f5a55c 100755 --- a/nova/CA/geninter.sh +++ b/nova/CA/geninter.sh @@ -23,7 +23,7 @@ mkdir -p projects/$NAME cd projects/$NAME cp ../../openssl.cnf.tmpl openssl.cnf sed -i -e s/%USERNAME%/$NAME/g openssl.cnf -mkdir certs crl newcerts private +mkdir -p certs crl newcerts private openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes echo "10" > serial touch index.txt diff --git a/nova/CA/genrootca.sh b/nova/CA/genrootca.sh index 8f2c3ee3f..091cf17fc 100755 --- a/nova/CA/genrootca.sh +++ b/nova/CA/genrootca.sh @@ -20,8 +20,9 @@ if [ -f "cacert.pem" ]; then echo "Not installing, it's already done." else - cp openssl.cnf.tmpl openssl.cnf + cp "$(dirname $0)/openssl.cnf.tmpl" openssl.cnf sed -i -e s/%USERNAME%/ROOT/g openssl.cnf + mkdir -p certs crl newcerts private openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes touch index.txt echo "10" > serial diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index f119bd75c..5d6d9537a 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -110,6 +110,7 @@ class CloudController(object): 'genrootca.sh') start = os.getcwd() + os.makedirs(FLAGS.ca_path) os.chdir(FLAGS.ca_path) # TODO(vish): Do this with M2Crypto instead utils.runthis(_("Generating root CA: %s"), "sh", genrootca_sh_path) -- cgit From 4d1f60df049e11623df7d6174eda19e5e27d004e Mon Sep 17 00:00:00 2001 From: Kei Masumoto Date: Tue, 5 Apr 2011 20:20:32 +0900 Subject: fix bug lp751231 --- nova/compute/manager.py | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 08b772517..37a904b1e 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1090,6 +1090,15 @@ class ComputeManager(manager.SchedulerDependentManager): vm_state = vm_instance.state vms_not_found_in_db.remove(name) + if db_instance['state_description'] == 'migrating': + # A situation which db record exists, but no instance" + # sometimes occurs while live-migration at src compute, + # this case should be ignored. + LOG.info(_("the instance '%(name)s' is not found in hypervisor" + ", while db record is found. But not synchronize " + "since it is migrating." % locals())) + continue + if vm_state != db_state: LOG.info(_("DB/VM state mismatch. Changing state from " "'%(db_state)s' to '%(vm_state)s'") % locals()) -- cgit From ca2ce6dc4a96c7bded2c30c258b7becaee5a1ed7 Mon Sep 17 00:00:00 2001 From: Kei Masumoto Date: Tue, 5 Apr 2011 20:39:22 +0900 Subject: fix bug lp751242 --- nova/compute/api.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 996955fe3..f67aa47e8 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -372,6 +372,10 @@ class API(base.Base): instance_id) return + if (instance['state_description'] == 'migrating'): + LOG.warning(_("Instance %s is being migrated"), instance_id) + return + self.update(context, instance['id'], state_description='terminating', -- cgit From d8ab404fd74facd181eaaee7cbc78b39a82afd1c Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 5 Apr 2011 08:26:16 -0400 Subject: Add missing underscore. --- nova/api/openstack/image_metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index 76abdaa7e..c733172ae 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -46,7 +46,7 @@ class Controller(wsgi.Controller): num_metadata = len(metadata) quota_metadata = quota.allowed_metadata_items(context, num_metadata) if quota_metadata < num_metadata: - expl = ("Image metadata limit exceeded") + expl = _("Image metadata limit exceeded") raise exc.HTTPBadRequest(explanation=expl) def index(self, req, image_id): -- cgit From affb632be0c0054a7b0a4858c6e0a585cc1afd0d Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 5 Apr 2011 08:49:44 -0400 Subject: Nits. --- nova/api/openstack/server_metadata.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index 618665082..2f7f6bc82 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -44,11 +44,12 @@ class Controller(wsgi.Controller): def create(self, req, server_id): context = req.environ['nova.context'] - data = self._deserialize(req.body, req.get_content_type())['metadata'] + data = self._deserialize(req.body, req.get_content_type()) + metadata = data.get('metadata') try: self.compute_api.update_or_create_instance_metadata(context, server_id, - data) + metadata) except quota.QuotaError as error: self._handle_quota_error(error) return req.body -- cgit From 6c8c454826263b334eb674c9ab1c8d62e5fd491f Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 5 Apr 2011 14:55:19 +0200 Subject: Help paste_config_file find the api config now that we moved it. --- nova/wsgi.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/wsgi.py b/nova/wsgi.py index ba0819466..05e7d5924 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -532,6 +532,7 @@ def paste_config_file(basename): """ configfiles = [basename, + os.path.join(FLAGS.state_path, 'etc', 'nova', basename), os.path.join(FLAGS.state_path, 'etc', basename), os.path.join(FLAGS.state_path, basename), '/etc/nova/%s' % basename] -- cgit From f1f8e00dc420bbc78b8d143c56375afc721c4c7d Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Tue, 5 Apr 2011 10:17:29 -0400 Subject: Dont configure vnc if we are using lxc --- nova/virt/libvirt_conn.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index babbc610d..2be190256 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -970,7 +970,8 @@ class LibvirtConnection(driver.ComputeDriver): 'nics': nics} if FLAGS.vnc_enabled: - xml_info['vncserver_host'] = FLAGS.vncserver_host + if FLAGS.libvirt_type != 'lxc': + xml_info['vncserver_host'] = FLAGS.vncserver_host if not rescue: if instance['kernel_id']: xml_info['kernel'] = xml_info['basepath'] + "/kernel" -- cgit From e6505fd78cc28a5e91ec6fbf55e2e8de07679c7c Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Tue, 5 Apr 2011 10:56:24 -0400 Subject: Fixed the addresses and metadata collections in xml responses. Added corresponding tests. --- nova/api/openstack/servers.py | 7 +++++++ nova/tests/api/openstack/test_servers.py | 36 ++++++++++++++++++++++++++++++++ nova/wsgi.py | 18 ++++++++++++++++ 3 files changed, 61 insertions(+) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 6704a68ae..cada92813 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -55,6 +55,13 @@ class Controller(wsgi.Controller): "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"}, + }, }, } diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 313676e72..ce9821587 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -192,6 +192,26 @@ class ServersTest(test.TestCase): print res_dict['server'] self.assertEqual(res_dict['server']['links'], expected_links) + def test_get_server_by_id_with_addresses_xml(self): + private = "192.168.0.3" + public = ["1.2.3.4"] + new_return_server = return_server_with_addresses(private, public) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + req = webob.Request.blank('/v1.0/servers/1') + req.headers['Accept'] = 'application/xml' + res = req.get_response(fakes.wsgi_app()) + dom = minidom.parseString(res.body) + server = dom.childNodes[0] + self.assertEquals(server.nodeName, 'server') + self.assertEquals(server.getAttribute('id'), '1') + self.assertEquals(server.getAttribute('name'), 'server1') + (public,) = server.getElementsByTagName('public'); + (ip,) = public.getElementsByTagName('ip') + self.assertEquals(ip.getAttribute('addr'), '1.2.3.4') + (private,) = server.getElementsByTagName('private') + (ip,) = private.getElementsByTagName('ip') + self.assertEquals(ip.getAttribute('addr'), '192.168.0.3') + def test_get_server_by_id_with_addresses(self): private = "192.168.0.3" public = ["1.2.3.4"] @@ -618,6 +638,22 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 404) + def test_get_all_server_details_xml_v1_0(self): + req = webob.Request.blank('/v1.0/servers/detail') + req.headers['Accept'] = 'application/xml' + res = req.get_response(fakes.wsgi_app()) + print res.body + dom = minidom.parseString(res.body) + for i, server in enumerate(dom.getElementsByTagName('server')): + self.assertEqual(server.getAttribute('id'), str(i)) + self.assertEqual(server.getAttribute('hostId'), '') + self.assertEqual(server.getAttribute('name'), 'server%d' % i) + self.assertEqual(server.getAttribute('imageId'), '10') + self.assertEqual(server.getAttribute('status'), 'BUILD') + (meta,) = server.getElementsByTagName('meta') + self.assertEqual(meta.getAttribute('key'), 'seq') + self.assertEqual(meta.firstChild.data.strip(), str(i)) + def test_get_all_server_details_v1_0(self): req = webob.Request.blank('/v1.0/servers/detail') res = req.get_response(fakes.wsgi_app()) diff --git a/nova/wsgi.py b/nova/wsgi.py index ba0819466..a550a33b0 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -484,6 +484,14 @@ class Serializer(object): """Recursive method to convert data members to XML nodes.""" result = doc.createElement(nodename) if type(data) is list: + collections = metadata.get('list_collections', {}) + if nodename in collections: + metadata = collections[nodename] + for item in data: + node = doc.createElement(metadata['item_name']) + node.setAttribute(metadata['item_key'], str(item)) + result.appendChild(node) + return result singular = metadata.get('plurals', {}).get(nodename, None) if singular is None: if nodename.endswith('s'): @@ -494,6 +502,16 @@ class Serializer(object): node = self._to_xml_node(doc, metadata, singular, item) result.appendChild(node) elif type(data) is dict: + collections = metadata.get('dict_collections', {}) + if nodename in collections: + metadata = collections[nodename] + for k, v in data.items(): + node = doc.createElement(metadata['item_name']) + node.setAttribute(metadata['item_key'], str(k)) + text = doc.createTextNode(str(v)) + node.appendChild(text) + result.appendChild(node) + return result attrs = metadata.get('attributes', {}).get(nodename, {}) for k, v in data.items(): if k in attrs: -- cgit From 519c885a4e3622417cd78655a211a2f23033d610 Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Tue, 5 Apr 2011 19:42:09 +0400 Subject: pep8 fixed --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 93a250502..1eec55e5f 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -167,7 +167,7 @@ def _get_network_info(instance): networks = db.network_get_all_by_instance(admin_context, instance['id']) - + flavor = db.instance_type_get_by_name(admin_context, instance['instance_type']) network_info = [] -- cgit From 28568e51ab4eb84e66e7d15adc9648220684ab84 Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Tue, 5 Apr 2011 20:40:52 +0400 Subject: removed blank lines for pep8 fix --- nova/virt/libvirt_conn.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 1eec55e5f..d6f51a644 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -164,10 +164,8 @@ def _get_network_info(instance): ip_addresses = db.fixed_ip_get_all_by_instance(admin_context, instance['id']) - networks = db.network_get_all_by_instance(admin_context, instance['id']) - flavor = db.instance_type_get_by_name(admin_context, instance['instance_type']) network_info = [] -- cgit From 3bb6e627fd99a307825f88ff8882e974bcf1b365 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 5 Apr 2011 13:02:42 -0400 Subject: Remove comments. --- .../migrate_repo/versions/014_add_instance_type_id_to_instances.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py index 813e57e10..b12a0a801 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py @@ -63,7 +63,6 @@ def upgrade(migrate_engine): .values(instance_type_id=type_id)) instances.c.instance_type.drop() - #instances.c.instance_type_id.alter(nullable=False) def downgrade(migrate_engine): @@ -83,4 +82,3 @@ def downgrade(migrate_engine): .values(instance_type=type_name)) instances.c.instance_type_id.drop() - #instances.c.instance_type.alter(nullable=False) -- cgit From a7a1f4dfda3751bb1f0f5c875e0799f1905259bd Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Tue, 5 Apr 2011 15:44:41 -0400 Subject: add tests for adminPass on server create --- nova/tests/api/openstack/test_servers.py | 46 ++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 313676e72..bd0bdf1b3 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -508,6 +508,52 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) + def test_create_instance_with_admin_pass_v10(self): + self._setup_for_create_instance() + + body = { + 'server': { + 'name': 'test-server-create', + 'imageId': 3, + 'flavorId': 1, + 'adminPass': 'testpass', + }, + } + + 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()) + res = json.loads(res.body) + self.assertNotEqual(res['server']['adminPass'], + body['server']['adminPass']) + + def test_create_instance_with_admin_pass_v11(self): + self._setup_for_create_instance() + + imageRef = 'http://localhost/v1.1/images/2' + flavorRef = 'http://localhost/v1.1/flavors/3' + body = { + 'server': { + 'name': 'server_test', + 'imageRef': imageRef, + 'flavorRef': flavorRef, + 'adminPass': 'testpass', + }, + } + + 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()) + + server = json.loads(res.body)['server'] + self.assertEqual(server['adminPass'], body['server']['adminPass']) + + def test_update_no_body(self): req = webob.Request.blank('/v1.0/servers/1') req.method = 'PUT' -- cgit From 2468743697faf81851bef1cafba95337dbd57153 Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Tue, 5 Apr 2011 15:55:49 -0400 Subject: add support for specifying adminPass for JSON only in openstack api 1.1 --- nova/api/openstack/servers.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 6704a68ae..c19b53b5e 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -180,7 +180,7 @@ class Controller(wsgi.Controller): builder = self._get_view_builder(req) server = builder.build(inst, is_detail=True) - password = utils.generate_password(16) + password = self._get_admin_password_from_request_server(env['server']) server['server']['adminPass'] = password self.compute_api.set_admin_password(context, server['server']['id'], password) @@ -242,6 +242,9 @@ class Controller(wsgi.Controller): # if the original error is okay, just reraise it raise error + def _get_admin_password_from_request_server(self, server): + return utils.generate_password(16) + @scheduler_api.redirect_handler def update(self, req, id): """ Updates the server name or password """ @@ -648,6 +651,12 @@ class ControllerV11(Controller): def _limit_items(self, items, req): return common.limited_by_marker(items, req) + def _get_admin_password_from_request_server(self, server): + password = server.get('adminPass') + if password: + return password + return utils.generate_password(16) + class ServerCreateRequestXMLDeserializer(object): """ -- cgit From b59f7e2b9ed0f08ac033cd839a6996a1931ccec6 Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Tue, 5 Apr 2011 16:30:30 -0400 Subject: refactor to handle invalid adminPass --- nova/api/openstack/servers.py | 11 ++++++----- nova/tests/api/openstack/test_servers.py | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index c19b53b5e..8f5adaad5 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -120,6 +120,11 @@ class Controller(wsgi.Controller): context = req.environ['nova.context'] + password = self._get_admin_password_from_request_server(env['server']) + if not isinstance(password, basestring) or password == '': + msg = _("Invalid adminPass") + return exc.HTTPBadRequest(msg) + key_name = None key_data = None key_pairs = auth_manager.AuthManager.get_key_pairs(context) @@ -180,7 +185,6 @@ class Controller(wsgi.Controller): builder = self._get_view_builder(req) server = builder.build(inst, is_detail=True) - password = self._get_admin_password_from_request_server(env['server']) server['server']['adminPass'] = password self.compute_api.set_admin_password(context, server['server']['id'], password) @@ -652,10 +656,7 @@ class ControllerV11(Controller): return common.limited_by_marker(items, req) def _get_admin_password_from_request_server(self, server): - password = server.get('adminPass') - if password: - return password - return utils.generate_password(16) + return server.get('adminPass', utils.generate_password(16)) class ServerCreateRequestXMLDeserializer(object): diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index bd0bdf1b3..65e6fee51 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -553,6 +553,26 @@ class ServersTest(test.TestCase): server = json.loads(res.body)['server'] self.assertEqual(server['adminPass'], body['server']['adminPass']) + def test_create_instance_with_empty_admin_pass_v11(self): + self._setup_for_create_instance() + + imageRef = 'http://localhost/v1.1/images/2' + flavorRef = 'http://localhost/v1.1/flavors/3' + body = { + 'server': { + 'name': 'server_test', + 'imageRef': imageRef, + 'flavorRef': flavorRef, + 'adminPass': '', + }, + } + + 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_update_no_body(self): req = webob.Request.blank('/v1.0/servers/1') -- cgit From 7ed00acea541dc4cf3b70ecf923b649e9ee7ae11 Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Tue, 5 Apr 2011 16:46:44 -0400 Subject: move error handling down into get_password function --- nova/api/openstack/servers.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 8f5adaad5..40aca64b4 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -120,10 +120,7 @@ class Controller(wsgi.Controller): context = req.environ['nova.context'] - password = self._get_admin_password_from_request_server(env['server']) - if not isinstance(password, basestring) or password == '': - msg = _("Invalid adminPass") - return exc.HTTPBadRequest(msg) + password = self._get_server_admin_password(env['server']) key_name = None key_data = None @@ -246,7 +243,7 @@ class Controller(wsgi.Controller): # if the original error is okay, just reraise it raise error - def _get_admin_password_from_request_server(self, server): + def _get_server_admin_password(self, server): return utils.generate_password(16) @scheduler_api.redirect_handler @@ -655,8 +652,14 @@ class ControllerV11(Controller): def _limit_items(self, items, req): return common.limited_by_marker(items, req) - def _get_admin_password_from_request_server(self, server): - return server.get('adminPass', utils.generate_password(16)) + def _get_server_admin_password(self, server): + password = server.get('adminPass') + if password is None: + return utils.generate_password(16) + if not isinstance(password, basestring) or password == '': + msg = _("Invalid adminPass") + raise exc.HTTPBadRequest(msg) + return password class ServerCreateRequestXMLDeserializer(object): -- cgit From 1afcf5511d4cfa70dd17113c9253594fbd5b7aa1 Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Tue, 5 Apr 2011 16:53:59 -0400 Subject: remove extraneous empty lines --- nova/tests/api/openstack/test_servers.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 65e6fee51..368343ce6 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -547,9 +547,7 @@ class ServersTest(test.TestCase): req.method = 'POST' req.body = json.dumps(body) req.headers['content-type'] = "application/json" - res = req.get_response(fakes.wsgi_app()) - server = json.loads(res.body)['server'] self.assertEqual(server['adminPass'], body['server']['adminPass']) -- cgit From 3c487493de2a0f827780d54080797bfe70183ba6 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Tue, 5 Apr 2011 17:49:38 -0400 Subject: Removing naughty semicolon. --- nova/tests/api/openstack/test_servers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index ce9821587..a424a8105 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -205,7 +205,7 @@ class ServersTest(test.TestCase): self.assertEquals(server.nodeName, 'server') self.assertEquals(server.getAttribute('id'), '1') self.assertEquals(server.getAttribute('name'), 'server1') - (public,) = server.getElementsByTagName('public'); + (public,) = server.getElementsByTagName('public') (ip,) = public.getElementsByTagName('ip') self.assertEquals(ip.getAttribute('addr'), '1.2.3.4') (private,) = server.getElementsByTagName('private') -- cgit From 56eb682a169c20a9a8275b3b9bfd423f0d562cbc Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 5 Apr 2011 15:01:33 -0700 Subject: unite the filtering done by glance client and s3 --- nova/api/ec2/cloud.py | 2 +- nova/image/glance.py | 28 ---------------------------- nova/image/local.py | 4 +++- nova/image/s3.py | 45 +++------------------------------------------ nova/image/service.py | 27 +++++++++++++++++++++++++++ 5 files changed, 34 insertions(+), 72 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 02a30220a..b4705820e 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -927,7 +927,7 @@ class CloudController(object): 'ari': 'ramdisk', 'ami': 'machine'} i['imageType'] = display_mapping.get(image_type) - i['isPublic'] = str(image['properties'].get('is_public', '')) == 'True' + i['isPublic'] = image.get('is_public') == True i['architecture'] = image['properties'].get('architecture') return i diff --git a/nova/image/glance.py b/nova/image/glance.py index fdf468594..e583c504d 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -186,34 +186,6 @@ class GlanceImageService(service.BaseImageService): image_meta = _convert_timestamps_to_datetimes(image_meta) return image_meta - @staticmethod - def _is_image_available(context, image_meta): - """ - Images are always available if they are public or if the user is an - admin. - - Otherwise, we filter by project_id (if present) and then fall-back to - images owned by user. - """ - # FIXME(sirp): We should be filtering by user_id on the Glance side - # for security; however, we can't do that until we get authn/authz - # sorted out. Until then, filtering in Nova. - if image_meta['is_public'] or context.is_admin: - return True - - properties = image_meta['properties'] - - if context.project_id and ('project_id' in properties): - return str(properties['project_id']) == str(project_id) - - try: - user_id = properties['user_id'] - except KeyError: - return False - - return str(user_id) == str(context.user_id) - - # utility functions def _convert_timestamps_to_datetimes(image_meta): """ diff --git a/nova/image/local.py b/nova/image/local.py index 1fb6e1f13..92d3dabd4 100644 --- a/nova/image/local.py +++ b/nova/image/local.py @@ -84,7 +84,9 @@ class LocalImageService(service.BaseImageService): def show(self, context, image_id): try: with open(self._path_to(image_id)) as metadata_file: - return json.load(metadata_file) + image_meta = json.load(metadata_file) + if not self._is_image_available(context, image_meta): + raise exception.NotFound except (IOError, ValueError): raise exception.NotFound diff --git a/nova/image/s3.py b/nova/image/s3.py index def49f682..93af21022 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -58,54 +58,16 @@ class S3ImageService(service.BaseImageService): return image def delete(self, context, image_id): - # FIXME(vish): call to show is to check filter + # FIXME(vish): call to show is to check visibility self.show(context, image_id) self.service.delete(context, image_id) def update(self, context, image_id, metadata, data=None): - # FIXME(vish): call to show is to check filter + # FIXME(vish): call to show is to check visibility self.show(context, image_id) image = self.service.update(context, image_id, metadata, data) return image - def index(self, context): - images = self.service.index(context) - # FIXME(vish): index doesn't filter so we do it manually - return self._filter(context, images) - - def detail(self, context): - images = self.service.detail(context) - # FIXME(vish): detail doesn't filter so we do it manually - return self._filter(context, images) - - @classmethod - def _is_visible(cls, context, image): - - return (context.is_admin - or context.project_id == image['properties'].get('owner_id') - or str(image['properties'].get('is_public')) == 'True') - - @classmethod - def _filter(cls, context, images): - filtered = [] - for image in images: - if not cls._is_visible(context, image): - continue - filtered.append(image) - return filtered - - def show(self, context, image_id): - image = self.service.show(context, image_id) - if not self._is_visible(context, image): - raise exception.NotFound - return image - - def show_by_name(self, context, name): - image = self.service.show_by_name(context, name) - if not self._is_visible(context, image): - raise exception.NotFound - return image - @staticmethod def _conn(context): # TODO(vish): is there a better way to get creds to sign @@ -168,7 +130,7 @@ class S3ImageService(service.BaseImageService): arch = 'x86_64' properties = metadata['properties'] - properties['owner_id'] = context.project_id + properties['project_id'] = context.project_id properties['architecture'] = arch if kernel_id: @@ -177,7 +139,6 @@ class S3ImageService(service.BaseImageService): if ramdisk_id: properties['ramdisk_id'] = ec2utils.ec2_id_to_id(ramdisk_id) - properties['is_public'] = False metadata.update({'disk_format': image_format, 'container_format': image_format, 'status': 'queued', diff --git a/nova/image/service.py b/nova/image/service.py index b9897ecae..fddc72409 100644 --- a/nova/image/service.py +++ b/nova/image/service.py @@ -136,6 +136,33 @@ class BaseImageService(object): """ raise NotImplementedError + @staticmethod + def _is_image_available(context, image_meta): + """ + Images are always available if they are public or if the user is an + admin. + + Otherwise, we filter by project_id (if present) and then fall-back to + images owned by user. + """ + # FIXME(sirp): We should be filtering by user_id on the Glance side + # for security; however, we can't do that until we get authn/authz + # sorted out. Until then, filtering in Nova. + if image_meta['is_public'] or context.is_admin: + return True + + properties = image_meta['properties'] + + if context.project_id and ('project_id' in properties): + return str(properties['project_id']) == str(context.project_id) + + try: + user_id = properties['user_id'] + except KeyError: + return False + + return str(user_id) == str(context.user_id) + @classmethod def _translate_to_base(cls, metadata): """Return a metadata dictionary that is BaseImageService compliant. -- cgit From 8eff0a91bbc475d8559b39fb81e1a62beda841f3 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 5 Apr 2011 15:35:17 -0700 Subject: update and fix tests --- nova/image/local.py | 1 + nova/image/s3.py | 13 +++++++++++++ nova/tests/api/openstack/test_servers.py | 16 +++++++--------- 3 files changed, 21 insertions(+), 9 deletions(-) (limited to 'nova') diff --git a/nova/image/local.py b/nova/image/local.py index 92d3dabd4..bad4c2d85 100644 --- a/nova/image/local.py +++ b/nova/image/local.py @@ -87,6 +87,7 @@ class LocalImageService(service.BaseImageService): image_meta = json.load(metadata_file) if not self._is_image_available(context, image_meta): raise exception.NotFound + return image_meta except (IOError, ValueError): raise exception.NotFound diff --git a/nova/image/s3.py b/nova/image/s3.py index 93af21022..a7301eefd 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -46,6 +46,7 @@ flags.DEFINE_string('image_decryption_dir', '/tmp', class S3ImageService(service.BaseImageService): + """Wraps an existing image service to support s3 based register""" def __init__(self, service=None, *args, **kwargs): if service == None: service = utils.import_object(FLAGS.image_service) @@ -68,6 +69,18 @@ class S3ImageService(service.BaseImageService): image = self.service.update(context, image_id, metadata, data) return image + def index(self, context): + return self.service.index(context) + + def detail(self, context): + return self.service.detail(context) + + def show(self, context, image_id): + return self.service.show(context, image_id) + + def show_by_name(self, context, name): + return self.service.show(context, name) + @staticmethod def _conn(context): # TODO(vish): is there a better way to get creds to sign diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 313676e72..1ec01bb4c 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1525,29 +1525,27 @@ class TestGetKernelRamdiskFromImage(test.TestCase): def test_not_ami(self): """Anything other than ami should return no kernel and no ramdisk""" - image_meta = {'id': 1, 'status': 'active', - 'properties': {'disk_format': 'vhd'}} + image_meta = {'id': 1, 'status': 'active', 'container_format': 'vhd'} kernel_id, ramdisk_id = self._get_k_r(image_meta) self.assertEqual(kernel_id, None) self.assertEqual(ramdisk_id, None) def test_ami_no_kernel(self): """If an ami is missing a kernel it should raise NotFound""" - image_meta = {'id': 1, 'status': 'active', - 'properties': {'disk_format': 'ami', 'ramdisk_id': 1}} + image_meta = {'id': 1, 'status': 'active', 'container_format': 'ami', + 'properties': {'ramdisk_id': 1}} self.assertRaises(exception.NotFound, self._get_k_r, image_meta) def test_ami_no_ramdisk(self): """If an ami is missing a ramdisk it should raise NotFound""" - image_meta = {'id': 1, 'status': 'active', - 'properties': {'disk_format': 'ami', 'kernel_id': 1}} + image_meta = {'id': 1, 'status': 'active', 'container_format': 'ami', + 'properties': {'kernel_id': 1}} self.assertRaises(exception.NotFound, self._get_k_r, image_meta) def test_ami_kernel_ramdisk_present(self): """Return IDs if both kernel and ramdisk are present""" - image_meta = {'id': 1, 'status': 'active', - 'properties': {'disk_format': 'ami', 'kernel_id': 1, - 'ramdisk_id': 2}} + image_meta = {'id': 1, 'status': 'active', 'container_format': 'ami', + 'properties': {'kernel_id': 1, 'ramdisk_id': 2}} kernel_id, ramdisk_id = self._get_k_r(image_meta) self.assertEqual(kernel_id, 1) self.assertEqual(ramdisk_id, 2) -- cgit From 5f414311fb581ba612cf152f3ec9983d5f39c2e4 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 5 Apr 2011 15:45:01 -0700 Subject: fallback to status if image_state is not set --- nova/api/ec2/cloud.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index b4705820e..4e5b8bfc6 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -920,7 +920,11 @@ class CloudController(object): get('image_location'), name) else: i['imageLocation'] = image['properties'].get('image_location') - i['imageState'] = image['properties'].get('image_state') + # NOTE(vish): fallback status if image_state isn't set + state = image.get('status') + if state == 'active': + state = 'available' + i['imageState'] = image['properties'].get('image_state', state) i['displayName'] = name i['description'] = image.get('description') display_mapping = {'aki': 'kernel', -- cgit From 75082c44cdc0319c56805b5558927760636eaf4b Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 5 Apr 2011 15:49:43 -0700 Subject: pep8 --- nova/image/glance.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/image/glance.py b/nova/image/glance.py index e583c504d..f848f40d8 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -186,6 +186,7 @@ class GlanceImageService(service.BaseImageService): image_meta = _convert_timestamps_to_datetimes(image_meta) return image_meta + # utility functions def _convert_timestamps_to_datetimes(image_meta): """ -- cgit From a254fd9b63c48f64a62fd38df3a2caae81ce63c7 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Tue, 5 Apr 2011 18:29:53 -0500 Subject: typo --- nova/virt/xenapi/vmops.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index c96c35a6e..ef2ab09b9 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -814,12 +814,11 @@ class VMOps(object): "netmask": network["netmask"], "enabled": "1"} - def ip6_dict(ip6): + def ip6_dict(): return { "ip": utils.to_global_ipv6(network['cidr_v6'], instance['mac_address']), "netmask": network['netmask_v6'], - "gateway": network['gateway_v6'], "enabled": "1"} info = { @@ -831,7 +830,9 @@ class VMOps(object): 'dns': [network['dns']], 'ips': [ip_dict(ip) for ip in network_IPs]} if network['cidr_v6']: - info['ip6s'] = [ip6_dict(ip) for ip in network_IPs] + info['ip6s'] = [ip6_dict()] + if network['gateway_v6']: + info['gateway6'] = network['gateway_v6'], network_info.append((network, info)) return network_info -- cgit From 44eefb1eefda5e42a286ee1aa689c1c93e72aae4 Mon Sep 17 00:00:00 2001 From: Kei Masumoto Date: Wed, 6 Apr 2011 14:44:19 +0900 Subject: fixed based on reviwer's comment --- nova/compute/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index f67aa47e8..db04143c1 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -367,12 +367,12 @@ class API(base.Base): instance_id) raise - if (instance['state_description'] == 'terminating'): + if instance['state_description'] == 'terminating': LOG.warning(_("Instance %s is already being terminated"), instance_id) return - if (instance['state_description'] == 'migrating'): + if instance['state_description'] == 'migrating': LOG.warning(_("Instance %s is being migrated"), instance_id) return -- cgit From d99a40d0a34a0d7ec05dcaa188a58535f0e41953 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Wed, 6 Apr 2011 14:05:12 +0200 Subject: Only create ca_path directory if it does not already exist. --- nova/api/ec2/cloud.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 99520b302..58effd134 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -110,7 +110,8 @@ class CloudController(object): 'genrootca.sh') start = os.getcwd() - os.makedirs(FLAGS.ca_path) + if not os.path.exists(FLAGS.ca_path): + os.makedirs(FLAGS.ca_path) os.chdir(FLAGS.ca_path) # TODO(vish): Do this with M2Crypto instead utils.runthis(_("Generating root CA: %s"), "sh", genrootca_sh_path) -- cgit From dbeab47bbdeceab0bef896c9d7646ae346c9dd3a Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 6 Apr 2011 09:05:12 -0700 Subject: Wait for device node to be created after mounting image VDI --- nova/virt/xenapi/vm_utils.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'nova') diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index d07d60800..d29456ec6 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -49,6 +49,8 @@ LOG = logging.getLogger("nova.virt.xenapi.vm_utils") FLAGS = flags.FLAGS flags.DEFINE_string('default_os_type', 'linux', 'Default OS type') +flags.DEFINE_integer('timeout_block', 10, + 'time to wait for a block device to be created') XENAPI_POWER_STATE = { 'Halted': power_state.SHUTDOWN, @@ -896,6 +898,16 @@ def remap_vbd_dev(dev): return remapped_dev +def _wait_for_device(dev): + """Wait for device node to appear""" + for i in xrange(0, FLAGS.timeout_block): + if os.path.exists('/dev/%s' % dev): + return + time.sleep(1) + + raise StorageError(_('Timeout waiting for device %s to be created') % dev) + + def with_vdi_attached_here(session, vdi_ref, read_only, f): this_vm_ref = get_this_vm_ref(session) vbd_rec = {} @@ -924,6 +936,7 @@ def with_vdi_attached_here(session, vdi_ref, read_only, f): if dev != orig_dev: LOG.debug(_('VBD %(vbd_ref)s plugged into wrong dev, ' 'remapping to %(dev)s') % locals()) + _wait_for_device(dev) return f(dev) finally: LOG.debug(_('Destroying VBD for VDI %s ... '), vdi_ref) -- cgit From 0a8ca1bb7f123718ae48bb842b1c532b07f03890 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Wed, 6 Apr 2011 18:10:42 +0200 Subject: Create ca_folder if it does not already exist. --- nova/crypto.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nova') diff --git a/nova/crypto.py b/nova/crypto.py index 2b122e560..9b1897926 100644 --- a/nova/crypto.py +++ b/nova/crypto.py @@ -269,6 +269,8 @@ def _sign_csr(csr_text, ca_folder): LOG.debug(_("Flags path: %s"), ca_folder) start = os.getcwd() # Change working dir to CA + if not os.path.exists(ca_folder): + os.makedirs(ca_folder) os.chdir(ca_folder) utils.execute('openssl', 'ca', '-batch', '-out', outbound, '-config', './openssl.cnf', '-infiles', inbound) -- cgit From 481a77134a4e0e1d668fa488d7c5b1d7e1bc5429 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 6 Apr 2011 11:15:35 -0500 Subject: modified behavior of inject_network_info and reset_network related to a vm_ref not being passed in --- nova/virt/xenapi/vmops.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index ef2ab09b9..f02beda24 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -176,7 +176,7 @@ class VMOps(object): vdi_ref, network_info) self.create_vifs(vm_ref, network_info) - self.inject_network_info(instance, vm_ref, network_info) + self.inject_network_info(instance, network_info, vm_ref) return vm_ref def _spawn(self, instance, vm_ref): @@ -836,15 +836,31 @@ class VMOps(object): network_info.append((network, info)) return network_info - def inject_network_info(self, instance, vm_ref, network_info): + #TODO{tr3buchet) remove this shim with nova-multi-nic + def inject_network_info(self, instance, network_info=None, vm_ref=None): + """ + shim in place which makes inject_network_info work without being + passed network_info. + shim goes away after nova-multi-nic + """ + if not network_info: + network_info = self._get_network_info(instance) + self._inject_network_info(instance, network_info, vm_ref) + + def _inject_network_info(self, instance, network_info, vm_ref=None): """ Generate the network info and make calls to place it into the xenstore and the xenstore param list. + vm_ref can be passed in because it will sometimes be different than + what VMHelper.lookup(session, instance.name) will find (ex: rescue) """ logging.debug(_("injecting network info to xs for vm: |%s|"), vm_ref) - # this function raises if vm_ref is not a vm_opaque_ref - self._session.get_xenapi().VM.get_record(vm_ref) + if vm_ref: + # this function raises if vm_ref is not a vm_opaque_ref + self._session.get_xenapi().VM.get_record(vm_ref) + else: + vm_ref = VMHelper.lookup(self._session, instance.name) for (network, info) in network_info: location = 'vm-data/networking/%s' % info['mac'].replace(':', '') @@ -876,8 +892,10 @@ class VMOps(object): VMHelper.create_vif(self._session, vm_ref, network_ref, mac_address, device, rxtx_cap) - def reset_network(self, instance, vm_ref): + def reset_network(self, instance, vm_ref=None): """Creates uuid arg to pass to make_agent_call and calls it.""" + if not vm_ref: + vm_ref = VMHelper.lookup(self._session, instance.name) args = {'id': str(uuid.uuid4())} # TODO(tr3buchet): fix function call after refactor #resp = self._make_agent_call('resetnetwork', instance, '', args) -- cgit From e46d78218eec77f8502579496ee0922ce401e84a Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 6 Apr 2011 12:33:07 -0500 Subject: updated _prepare_injectables() to use info[gateway6] instead of looking inside the ip6 address dict for the gateway6 information --- nova/virt/xenapi/vm_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index d07d60800..886f1ec88 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -1130,7 +1130,7 @@ def _prepare_injectables(inst, networks_info): 'dns': dns, 'address_v6': ip_v6 and ip_v6['ip'] or '', 'netmask_v6': ip_v6 and ip_v6['netmask'] or '', - 'gateway_v6': ip_v6 and ip_v6['gateway'] or '', + 'gateway_v6': ip_v6 and info['gateway6'] or '', 'use_ipv6': FLAGS.use_ipv6} interfaces_info.append(interface_info) -- cgit From 3e120e52fcb8fca1b97b496c618c3b9e2b459598 Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Wed, 6 Apr 2011 13:42:04 -0400 Subject: add docstrings --- nova/api/openstack/servers.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 40aca64b4..3b6d14ae1 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -244,6 +244,7 @@ class Controller(wsgi.Controller): raise error def _get_server_admin_password(self, server): + """ Determine the admin password for a server on creation """ return utils.generate_password(16) @scheduler_api.redirect_handler @@ -653,6 +654,7 @@ class ControllerV11(Controller): return common.limited_by_marker(items, req) def _get_server_admin_password(self, server): + """ Determine the admin password for a server on creation """ password = server.get('adminPass') if password is None: return utils.generate_password(16) -- cgit From c18bf716f08e6b9fbdc259755cf172b5a6cf096a Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 6 Apr 2011 12:52:25 -0500 Subject: updated get_network_info in libvirt_conn to correctly insert ip6s and gateway6 into the network info, also small style fixes --- nova/virt/libvirt_conn.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 2be190256..f1fa859ed 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -169,34 +169,34 @@ def _get_network_info(instance): instance['id']) network_info = [] - def ip_dict(ip): - return { - "ip": ip.address, - "netmask": network["netmask"], - "enabled": "1"} - - def ip6_dict(ip6): - prefix = ip6.network.cidr_v6 - mac = instance.mac_address - return { - "ip": utils.to_global_ipv6(prefix, mac), - "netmask": ip6.network.netmask_v6, - "gateway": ip6.network.gateway_v6, - "enabled": "1"} - for network in networks: network_ips = [ip for ip in ip_addresses - if ip.network_id == network.id] + if ip['network_id'] == network['id']] + + def ip_dict(ip): + return { + 'ip': ip['address'], + 'netmask': network['netmask'], + 'enabled': '1'} + + def ip6_dict(): + prefix = network['cidr_v6'] + mac = instance['mac_address'] + return { + 'ip': utils.to_global_ipv6(prefix, mac), + 'netmask': network['netmask_v6'], + 'enabled': '1'} mapping = { 'label': network['label'], 'gateway': network['gateway'], - 'mac': instance.mac_address, + 'mac': instance['mac_address'], 'dns': [network['dns']], 'ips': [ip_dict(ip) for ip in network_ips]} if FLAGS.use_ipv6: - mapping['ip6s'] = [ip6_dict(ip) for ip in network_ips] + mapping['ip6s'] = [ip6_dict()] + mapping['gateway6'] = network['gateway_v6'], network_info.append((network, mapping)) return network_info -- cgit From d5c077131e00f41a38fa03fdbea46aa4351f95b5 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 6 Apr 2011 13:05:39 -0500 Subject: updated check_vm_record in test_xenapi to check the gateway6 correctly --- nova/tests/test_xenapi.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 17e3f55e9..42fe8f3fa 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -289,11 +289,11 @@ class XenAPIVMTestCase(test.TestCase): 'enabled':'1'}], 'ip6s': [{'ip': 'fe80::a8bb:ccff:fedd:eeff', 'netmask': '120', - 'enabled': '1', - 'gateway': 'fe80::a00:1'}], + 'enabled': '1'}], 'mac': 'aa:bb:cc:dd:ee:ff', 'dns': ['10.0.0.2'], - 'gateway': '10.0.0.1'}) + 'gateway': '10.0.0.1', + 'gateway6': 'fe80::a00:1'}) def check_vm_params_for_windows(self): self.assertEquals(self.vm['platform']['nx'], 'true') -- cgit From d3fec5c2c3de2d3a1ef0fd1fd809ff248b6df5a8 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 6 Apr 2011 13:31:51 -0500 Subject: syntax error --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index f1fa859ed..50b09d19b 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -196,7 +196,7 @@ def _get_network_info(instance): if FLAGS.use_ipv6: mapping['ip6s'] = [ip6_dict()] - mapping['gateway6'] = network['gateway_v6'], + mapping['gateway6'] = network['gateway_v6'] network_info.append((network, mapping)) return network_info -- cgit From cb51075ceeb17d43bd53617a3dc8a561e7608722 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 6 Apr 2011 14:31:54 -0400 Subject: Added logging statements for generic WSGI and specific OpenStack API requests. --- nova/api/openstack/auth.py | 17 ++++++++++++++++- nova/wsgi.py | 5 +++++ 2 files changed, 21 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index f3a9bdeca..42c23785a 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -55,6 +55,9 @@ class AuthMiddleware(wsgi.Middleware): user = self.get_user_by_authentication(req) accounts = self.auth.get_projects(user=user) if not user: + token = req.headers["X-Auth-Token"] + msg = _("%(user)s could not be found with token '%(token)s'") + LOG.warn(msg % locals()) return faults.Fault(webob.exc.HTTPUnauthorized()) if accounts: @@ -66,6 +69,8 @@ class AuthMiddleware(wsgi.Middleware): if not self.auth.is_admin(user) and \ not self.auth.is_project_member(user, account): + msg = _("%(user)s must be an admin or a member of %(account)s") + LOG.warn(msg % locals()) return faults.Fault(webob.exc.HTTPUnauthorized()) req.environ['nova.context'] = context.RequestContext(user, account) @@ -82,12 +87,15 @@ class AuthMiddleware(wsgi.Middleware): # honor it path_info = req.path_info if len(path_info) > 1: + msg = _("Authentication requests must be made against /") + LOG.warn(msg) return faults.Fault(webob.exc.HTTPUnauthorized()) try: username = req.headers['X-Auth-User'] key = req.headers['X-Auth-Key'] - except KeyError: + except KeyError as ex: + LOG.warn(_("Could not find %s in request.") % ex) return faults.Fault(webob.exc.HTTPUnauthorized()) token, user = self._authorize_user(username, key, req) @@ -100,6 +108,7 @@ class AuthMiddleware(wsgi.Middleware): res.headers['X-CDN-Management-Url'] = token.cdn_management_url res.content_type = 'text/plain' res.status = '204' + LOG.debug(_("Successfully authenticated '%s'") % username) return res else: return faults.Fault(webob.exc.HTTPUnauthorized()) @@ -139,6 +148,7 @@ class AuthMiddleware(wsgi.Middleware): try: user = self.auth.get_user_from_access_key(key) except exception.NotFound: + LOG.warn(_("User not found with provided API key.")) user = None if user and user.name == username: @@ -153,4 +163,9 @@ class AuthMiddleware(wsgi.Middleware): token_dict['user_id'] = user.id token = self.db.auth_token_create(ctxt, token_dict) return token, user + elif user and user.name != username: + msg = _("Provided API key is valid, but not for user " + "'%(username)s'") % locals() + LOG.warn(msg) + return None, None diff --git a/nova/wsgi.py b/nova/wsgi.py index 05e7d5924..d7a1594d9 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -43,6 +43,7 @@ from nova import utils FLAGS = flags.FLAGS +LOG = logging.getLogger('nova.wsgi') class WritableLogger(object): @@ -346,6 +347,7 @@ class Controller(object): arg_dict = req.environ['wsgiorg.routing_args'][1] action = arg_dict['action'] method = getattr(self, action) + LOG.debug("%s %s" % (req.method, req.url)) del arg_dict['controller'] del arg_dict['action'] if 'format' in arg_dict: @@ -360,6 +362,9 @@ class Controller(object): response = webob.Response() response.headers["Content-Type"] = content_type response.body = body + msg_dict = dict(url=req.url, status=response.status_int) + msg = _("%(url)s returned with HTTP %(status)d") % msg_dict + LOG.debug(msg) return response else: -- cgit From c649c8d5febab7d0dfa329bc5d78f0147383c5ee Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Wed, 6 Apr 2011 11:36:08 -0700 Subject: Use a more descriptive name for the flag to make it easier to understand the purpose --- nova/virt/xenapi/vm_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index d29456ec6..cf2adb44c 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -49,7 +49,7 @@ LOG = logging.getLogger("nova.virt.xenapi.vm_utils") FLAGS = flags.FLAGS flags.DEFINE_string('default_os_type', 'linux', 'Default OS type') -flags.DEFINE_integer('timeout_block', 10, +flags.DEFINE_integer('block_device_creation_timeout', 10, 'time to wait for a block device to be created') XENAPI_POWER_STATE = { @@ -900,7 +900,7 @@ def remap_vbd_dev(dev): def _wait_for_device(dev): """Wait for device node to appear""" - for i in xrange(0, FLAGS.timeout_block): + for i in xrange(0, FLAGS.block_device_creation_timeout): if os.path.exists('/dev/%s' % dev): return time.sleep(1) -- cgit From 560d36e7ad87ca7e8f8619e146ed4965f33dd391 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 6 Apr 2011 13:43:02 -0500 Subject: another syntax error --- nova/virt/xenapi/vmops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index f02beda24..30754b7b6 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -832,7 +832,7 @@ class VMOps(object): if network['cidr_v6']: info['ip6s'] = [ip6_dict()] if network['gateway_v6']: - info['gateway6'] = network['gateway_v6'], + info['gateway6'] = network['gateway_v6'] network_info.append((network, info)) return network_info -- cgit From 7ef28c854fa386ee1aa64aaa22c3ef026094f40a Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Wed, 6 Apr 2011 15:42:24 -0400 Subject: YADU (Yet Another Docstring Update). --- nova/compute/api.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/compute/api.py b/nova/compute/api.py index 790c205c0..af212ed32 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -135,8 +135,11 @@ class API(base.Base): key_name=None, key_data=None, security_group='default', availability_zone=None, user_data=None, metadata={}, injected_files=None): - """Create the number of instances requested if quota and - other arguments check out ok.""" + """Create number of instances requested, given quotas. + + Create the number of instances requested if quota and + other arguments check out ok. + """ type_data = instance_types.get_instance_type(instance_type) num_instances = quota.allowed_instances(context, max_count, type_data) -- cgit From cd279bec6391ef40d278f377f0039b507b14904b Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 6 Apr 2011 12:58:57 -0700 Subject: check visibility on delete and update --- nova/image/glance.py | 4 ++++ nova/image/local.py | 6 +++++- nova/image/s3.py | 4 ---- 3 files changed, 9 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/image/glance.py b/nova/image/glance.py index f848f40d8..bf49ca96c 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -151,6 +151,8 @@ class GlanceImageService(service.BaseImageService): :raises NotFound if the image does not exist. """ + # NOTE(vish): show is to check if image is available + self.show(context, image_id) try: image_meta = self.client.update_image(image_id, image_meta, data) except glance_exception.NotFound: @@ -165,6 +167,8 @@ class GlanceImageService(service.BaseImageService): :raises NotFound if the image does not exist. """ + # NOTE(vish): show is to check if image is available + self.show(context, image_id) try: result = self.client.delete_image(image_id) except glance_exception.NotFound: diff --git a/nova/image/local.py b/nova/image/local.py index bad4c2d85..8bf78b4c9 100644 --- a/nova/image/local.py +++ b/nova/image/local.py @@ -126,6 +126,8 @@ class LocalImageService(service.BaseImageService): def update(self, context, image_id, metadata, data=None): """Replace the contents of the given image with the new data.""" + # NOTE(vish): show is to check if image is available + self.show(context, image_id) metadata['id'] = image_id try: if data: @@ -143,9 +145,11 @@ class LocalImageService(service.BaseImageService): def delete(self, context, image_id): """Delete the given image. - Raises OSError if the image does not exist. + Raises NotFound if the image does not exist. """ + # NOTE(vish): show is to check if image is available + self.show(context, image_id) try: shutil.rmtree(self._path_to(image_id, None)) except (IOError, ValueError): diff --git a/nova/image/s3.py b/nova/image/s3.py index a7301eefd..554760d53 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -59,13 +59,9 @@ class S3ImageService(service.BaseImageService): return image def delete(self, context, image_id): - # FIXME(vish): call to show is to check visibility - self.show(context, image_id) self.service.delete(context, image_id) def update(self, context, image_id, metadata, data=None): - # FIXME(vish): call to show is to check visibility - self.show(context, image_id) image = self.service.update(context, image_id, metadata, data) return image -- cgit From 5534113f65cc578dcdf93432981b836bf4e3dfaf Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Wed, 6 Apr 2011 16:12:32 -0400 Subject: Added support for listing addresses of a server in the openstack api. Now you can GET * /servers/1/ips * /servers/1/ips/public * /servers/1/ips/private Supports v1.0 json and xml. Added corresponding tests. --- nova/api/openstack/__init__.py | 6 +++ nova/api/openstack/ips.py | 71 +++++++++++++++++++++++++++++ nova/api/openstack/servers.py | 9 ---- nova/api/openstack/views/addresses.py | 10 +++- nova/tests/api/openstack/test_servers.py | 78 ++++++++++++++++++++++++++++++++ 5 files changed, 163 insertions(+), 11 deletions(-) create mode 100644 nova/api/openstack/ips.py (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 7545eb0c9..5e76a06f7 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -34,6 +34,7 @@ from nova.api.openstack import consoles from nova.api.openstack import flavors from nova.api.openstack import images from nova.api.openstack import image_metadata +from nova.api.openstack import ips from nova.api.openstack import limits from nova.api.openstack import servers from nova.api.openstack import server_metadata @@ -144,6 +145,11 @@ class APIRouterV10(APIRouter): parent_resource=dict(member_name='server', collection_name='servers')) + mapper.resource("ip", "ips", controller=ips.Controller(), + collection=dict(public='GET', private='GET'), + parent_resource=dict(member_name='server', + collection_name='servers')) + class APIRouterV11(APIRouter): """Define routes specific to OpenStack API V1.1.""" diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py new file mode 100644 index 000000000..89b0936d5 --- /dev/null +++ b/nova/api/openstack/ips.py @@ -0,0 +1,71 @@ +# 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 time + +from webob import exc + +import nova +import nova.api.openstack.views.addresses +from nova.api.openstack import faults + + +class Controller(nova.wsgi.Controller): + """The servers addresses API controller for the Openstack API.""" + + _serialization_metadata = { + 'application/xml': { + 'list_collections': { + 'public': {'item_name': 'ip', 'item_key': 'addr'}, + 'private': {'item_name': 'ip', 'item_key': 'addr'}, + }, + }, + } + + def __init__(self): + self.compute_api = nova.compute.API() + self.builder = nova.api.openstack.views.addresses.ViewBuilderV10() + + def index(self, req, server_id): + try: + instance = self.compute_api.get(req.environ['nova.context'], id) + except nova.exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + return {'addresses': self.builder.build(instance)} + + def public(self, req, server_id): + try: + instance = self.compute_api.get(req.environ['nova.context'], id) + except nova.exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + return {'public': self.builder.build_public_parts(instance)} + + def private(self, req, server_id): + try: + instance = self.compute_api.get(req.environ['nova.context'], id) + except nova.exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + return {'private': self.builder.build_private_parts(instance)} + + def show(self, req, server_id, id): + return faults.Fault(exc.HTTPNotImplemented()) + + def create(self, req, server_id): + return faults.Fault(exc.HTTPNotImplemented()) + + def delete(self, req, server_id, id): + return faults.Fault(exc.HTTPNotImplemented()) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index cada92813..67e6eefd8 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -70,15 +70,6 @@ class Controller(wsgi.Controller): self._image_service = utils.import_object(FLAGS.image_service) super(Controller, self).__init__() - def ips(self, req, id): - try: - instance = self.compute_api.get(req.environ['nova.context'], id) - except exception.NotFound: - return faults.Fault(exc.HTTPNotFound()) - - builder = self._get_addresses_view_builder(req) - return builder.build(instance) - def index(self, req): """ Returns a list of server names and ids for a given user """ return self._items(req, is_detail=False) diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py index 90c77855b..2810cce39 100644 --- a/nova/api/openstack/views/addresses.py +++ b/nova/api/openstack/views/addresses.py @@ -28,10 +28,16 @@ class ViewBuilder(object): class ViewBuilderV10(ViewBuilder): def build(self, inst): - private_ips = utils.get_from_path(inst, 'fixed_ip/address') - public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address') + private_ips = self.build_private_parts(inst) + public_ips = self.build_public_parts(inst) return dict(public=public_ips, private=private_ips) + def build_public_parts(self, inst): + return utils.get_from_path(inst, 'fixed_ip/floating_ips/address') + + def build_private_parts(self, inst): + return utils.get_from_path(inst, 'fixed_ip/address') + class ViewBuilderV11(ViewBuilder): def build(self, inst): diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index a424a8105..640418cfa 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -228,6 +228,84 @@ class ServersTest(test.TestCase): self.assertEqual(len(addresses["private"]), 1) self.assertEqual(addresses["private"][0], private) + def test_get_server_addresses_V10(self): + private = '192.168.0.3' + public = ['1.2.3.4'] + new_return_server = return_server_with_addresses(private, public) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + req = webob.Request.blank('/v1.0/servers/1/ips') + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + self.assertEqual(res_dict, { + 'addresses': {'public': public, 'private': [private]}}) + + def test_get_server_addresses_xml_V10(self): + private_expected = "192.168.0.3" + public_expected = ["1.2.3.4"] + new_return_server = return_server_with_addresses(private_expected, + public_expected) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + req = webob.Request.blank('/v1.0/servers/1/ips') + req.headers['Accept'] = 'application/xml' + res = req.get_response(fakes.wsgi_app()) + dom = minidom.parseString(res.body) + (addresses,) = dom.childNodes + self.assertEquals(addresses.nodeName, 'addresses') + (public,) = addresses.getElementsByTagName('public') + (ip,) = public.getElementsByTagName('ip') + self.assertEquals(ip.getAttribute('addr'), public_expected[0]) + (private,) = addresses.getElementsByTagName('private') + (ip,) = private.getElementsByTagName('ip') + self.assertEquals(ip.getAttribute('addr'), private_expected) + + def test_get_server_addresses_public_V10(self): + private = "192.168.0.3" + public = ["1.2.3.4"] + new_return_server = return_server_with_addresses(private, public) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + req = webob.Request.blank('/v1.0/servers/1/ips/public') + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + self.assertEqual(res_dict, {'public': public}) + + def test_get_server_addresses_private_V10(self): + private = "192.168.0.3" + public = ["1.2.3.4"] + new_return_server = return_server_with_addresses(private, public) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + req = webob.Request.blank('/v1.0/servers/1/ips/private') + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + self.assertEqual(res_dict, {'private': [private]}) + + def test_get_server_addresses_public_xml_V10(self): + private = "192.168.0.3" + public = ["1.2.3.4"] + new_return_server = return_server_with_addresses(private, public) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + req = webob.Request.blank('/v1.0/servers/1/ips/public') + req.headers['Accept'] = 'application/xml' + res = req.get_response(fakes.wsgi_app()) + dom = minidom.parseString(res.body) + (public_node,) = dom.childNodes + self.assertEquals(public_node.nodeName, 'public') + (ip,) = public_node.getElementsByTagName('ip') + self.assertEquals(ip.getAttribute('addr'), public[0]) + + def test_get_server_addresses_private_xml_V10(self): + private = "192.168.0.3" + public = ["1.2.3.4"] + new_return_server = return_server_with_addresses(private, public) + self.stubs.Set(nova.db.api, 'instance_get', new_return_server) + req = webob.Request.blank('/v1.0/servers/1/ips/private') + req.headers['Accept'] = 'application/xml' + res = req.get_response(fakes.wsgi_app()) + dom = minidom.parseString(res.body) + (private_node,) = dom.childNodes + self.assertEquals(private_node.nodeName, 'private') + (ip,) = private_node.getElementsByTagName('ip') + self.assertEquals(ip.getAttribute('addr'), private) + def test_get_server_by_id_with_addresses_v11(self): private = "192.168.0.3" public = ["1.2.3.4"] -- cgit From e16571671e4baab50521870ec64e4ab954c7d165 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Wed, 6 Apr 2011 17:50:11 -0400 Subject: Controllers now inherit from nova.api.openstack.common.OpenstackController. --- nova/api/openstack/ips.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py index 89b0936d5..778e9ba1a 100644 --- a/nova/api/openstack/ips.py +++ b/nova/api/openstack/ips.py @@ -21,10 +21,11 @@ from webob import exc import nova import nova.api.openstack.views.addresses +from nova.api.openstack import common from nova.api.openstack import faults -class Controller(nova.wsgi.Controller): +class Controller(common.OpenstackController): """The servers addresses API controller for the Openstack API.""" _serialization_metadata = { -- cgit From 040428cc35aa046de4bcf744c95b7c507df94550 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 6 Apr 2011 14:53:35 -0700 Subject: Add automatic metadata ip to network host on start. Also fix race where gw is readded twice --- nova/network/linux_net.py | 13 ++++++++++++- nova/network/manager.py | 3 +++ 2 files changed, 15 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index d11d21dad..6a7051d10 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -391,6 +391,13 @@ def unbind_floating_ip(floating_ip): 'dev', FLAGS.public_interface) +def ensure_metadata_ip(interface): + """Sets up local metadata ip""" + _execute('sudo', 'ip', 'addr', 'add', '169.254.169.254/32', + 'scope', 'link', 'dev', interface, + check_exit_code=False) + + def ensure_vlan_forward(public_ip, port, private_ip): """Sets up forwarding rules for vlan""" iptables_manager.ipv4['filter'].add_rule("FORWARD", @@ -504,7 +511,11 @@ def ensure_bridge(bridge, interface, net_attrs=None): _execute(*_ip_bridge_cmd('del', params, fields[-1])) _execute(*_ip_bridge_cmd('add', params, bridge)) if gateway: - _execute('sudo', 'route', 'add', '0.0.0.0', 'gw', gateway) + # NOTE(vish): If the gateway already exists we are fine + out, err = _execute('sudo', 'route', 'add', '0.0.0.0', 'gw', + gateway, check_exit_code=False) + if err and err != "SIOCADDRT: File exists\n": + raise exception.Error("Failed to reset gateway: %s" % err) out, err = _execute('sudo', 'brctl', 'addif', bridge, interface, check_exit_code=False) diff --git a/nova/network/manager.py b/nova/network/manager.py index 86ee4fc00..a3c345b69 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -100,6 +100,8 @@ flags.DEFINE_string('network_host', socket.gethostname(), 'Network host to use for ip allocation in flat modes') flags.DEFINE_bool('fake_call', False, 'If True, skip using the queue and make local calls') +flags.DEFINE_string('metadata_interface', 'eth0', + 'interface to add the metadata ip to') class AddressAlreadyAllocated(exception.Error): @@ -128,6 +130,7 @@ class NetworkManager(manager.SchedulerDependentManager): self.driver.init_host() # Set up networking for the projects for which we're already # the designated network host. + self.driver.ensure_metadata_ip(FLAGS.metadata_interface) ctxt = context.get_admin_context() for network in self.db.host_get_networks(ctxt, self.host): self._on_set_network_host(ctxt, network['id']) -- cgit From dc8b3cb3fa124755ed3e5282b2d11a811f1db2d5 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Wed, 6 Apr 2011 15:39:44 -0700 Subject: removed comment on API compliance --- nova/api/openstack/views/servers.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index baa911590..096bf70c6 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -82,7 +82,6 @@ class ViewBuilder(object): # Return the metadata as a dictionary metadata = {} for item in inst.get('metadata', []): - # metadata values must be strings per API metadata[item['key']] = str(item['value']) inst_dict['metadata'] = metadata -- cgit From 3831008b4e8aeec08b58afb49e40428ad5ece5b1 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 6 Apr 2011 16:10:08 -0700 Subject: if we delete the old route when we move it we don't need to check for exists --- nova/network/linux_net.py | 9 ++++----- nova/network/manager.py | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 6a7051d10..a4d312f02 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -449,6 +449,7 @@ def ensure_vlan(vlan_num): return interface +@utils.synchronized('ensure_bridge', external=True) def ensure_bridge(bridge, interface, net_attrs=None): """Create a bridge unless it already exists. @@ -502,6 +503,8 @@ def ensure_bridge(bridge, interface, net_attrs=None): fields = line.split() if fields and fields[0] == "0.0.0.0" and fields[-1] == interface: gateway = fields[1] + _execute('sudo', 'route', 'del', 'default', 'gw', gateway, + 'dev', interface) out, err = _execute('sudo', 'ip', 'addr', 'show', 'dev', interface, 'scope', 'global') for line in out.split("\n"): @@ -511,11 +514,7 @@ def ensure_bridge(bridge, interface, net_attrs=None): _execute(*_ip_bridge_cmd('del', params, fields[-1])) _execute(*_ip_bridge_cmd('add', params, bridge)) if gateway: - # NOTE(vish): If the gateway already exists we are fine - out, err = _execute('sudo', 'route', 'add', '0.0.0.0', 'gw', - gateway, check_exit_code=False) - if err and err != "SIOCADDRT: File exists\n": - raise exception.Error("Failed to reset gateway: %s" % err) + _execute('sudo', 'route', 'add', 'default', 'gw', gateway) out, err = _execute('sudo', 'brctl', 'addif', bridge, interface, check_exit_code=False) diff --git a/nova/network/manager.py b/nova/network/manager.py index a3c345b69..b80560bdf 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -128,9 +128,9 @@ class NetworkManager(manager.SchedulerDependentManager): standalone service. """ self.driver.init_host() + self.driver.ensure_metadata_ip(FLAGS.metadata_interface) # Set up networking for the projects for which we're already # the designated network host. - self.driver.ensure_metadata_ip(FLAGS.metadata_interface) ctxt = context.get_admin_context() for network in self.db.host_get_networks(ctxt, self.host): self._on_set_network_host(ctxt, network['id']) -- cgit From 1ee150c449e630c6409798399eccb577c8273c70 Mon Sep 17 00:00:00 2001 From: Muneyuki Noguchi Date: Thu, 7 Apr 2011 12:35:45 +0900 Subject: Make description of volume_id more generic. --- nova/compute/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 85bcd7590..383eda0dc 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -786,7 +786,7 @@ class ComputeManager(manager.SchedulerDependentManager): """Remove volume on compute host. :param context: security context - :param volume_id: nova.db.sqlalchemy.models.Volume.id + :param volume_id: volume ID """ self.volume_manager.remove_compute_volume(context, volume_id) -- cgit From d7e8d91d6bc4954f2d256f57e34444b5bd170ab0 Mon Sep 17 00:00:00 2001 From: Kei Masumoto Date: Thu, 7 Apr 2011 13:08:14 +0900 Subject: fixed based on reviewer's comment - 'locals() should be off from _() --- nova/compute/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 37a904b1e..847c3655a 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1096,7 +1096,7 @@ class ComputeManager(manager.SchedulerDependentManager): # this case should be ignored. LOG.info(_("the instance '%(name)s' is not found in hypervisor" ", while db record is found. But not synchronize " - "since it is migrating." % locals())) + "since it is migrating.") % locals()) continue if vm_state != db_state: -- cgit From 2bc0e744162276048ddd9c1a1eeacbd647cda6f4 Mon Sep 17 00:00:00 2001 From: Kei Masumoto Date: Thu, 7 Apr 2011 13:32:19 +0900 Subject: fixed based on reviewer's comment - 1. erase unnecessary blank line, 2. adding LOG.debug --- nova/virt/libvirt_conn.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index bdf577825..eab54c53e 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1849,7 +1849,6 @@ class NWFilterFirewall(FirewallDriver): def instance_filter_exists(self, instance): """Check nova-instance-instance-xxx exists""" - network_info = _get_network_info(instance) for (network, mapping) in network_info: nic_id = mapping['mac'].replace(':', '') @@ -1857,6 +1856,9 @@ class NWFilterFirewall(FirewallDriver): try: self._conn.nwfilterLookupByName(instance_filter_name) except libvirt.libvirtError: + name = instance.name + LOG.debug(_('The nwfilter(%(instance_filter_name)s) for' + '%(name)s is not found.') % locals()) return False return True -- cgit From 7cf0deda8f7ab410005c556779353d599c8e8a63 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 7 Apr 2011 10:34:14 -0300 Subject: adds timeout to login_with_password --- nova/virt/xenapi_conn.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 99fd35c61..6dfe0b9a9 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -63,6 +63,7 @@ import xmlrpclib from eventlet import event from eventlet import tpool +from eventlet import timeout from nova import context from nova import db @@ -140,7 +141,9 @@ flags.DEFINE_bool('xenapi_remap_vbd_dev', False, flags.DEFINE_string('xenapi_remap_vbd_dev_prefix', 'sd', 'Specify prefix to remap VBD dev to ' '(ex. /dev/xvdb -> /dev/sdb)') - +flags.DEFINE_integer('xenapi_login_timeout', + 10, + 'Timeout in seconds for XenAPI login.') def get_connection(_): """Note that XenAPI doesn't have a read-only connection mode, so @@ -318,7 +321,9 @@ class XenAPISession(object): def __init__(self, url, user, pw): self.XenAPI = self.get_imported_xenapi() self._session = self._create_session(url) - self._session.login_with_password(user, pw) + exception = self.XenAPI.Failure(_("Unable to log in to XenAPI.")) + with timeout.Timeout(FLAGS.xenapi_login_timeout, exception): + self._session.login_with_password(user, pw) self.loop = None def get_imported_xenapi(self): -- cgit From 9b24c399c5689a1492b96dcd6725590c2a97c6e3 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 7 Apr 2011 10:42:29 -0300 Subject: pep8 --- nova/virt/xenapi_conn.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 6dfe0b9a9..f10aa6eb5 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -145,6 +145,7 @@ flags.DEFINE_integer('xenapi_login_timeout', 10, 'Timeout in seconds for XenAPI login.') + def get_connection(_): """Note that XenAPI doesn't have a read-only connection mode, so the read_only parameter is ignored.""" -- cgit From 9f57f78efab4a31bfe29e2edab1e86eedf4352fd Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 7 Apr 2011 11:59:40 -0300 Subject: better error message --- nova/virt/xenapi_conn.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index f10aa6eb5..0cabccf08 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -322,7 +322,8 @@ class XenAPISession(object): def __init__(self, url, user, pw): self.XenAPI = self.get_imported_xenapi() self._session = self._create_session(url) - exception = self.XenAPI.Failure(_("Unable to log in to XenAPI.")) + exception = self.XenAPI.Failure(_("Unable to log in to XenAPI " + "(is the Dom0 disk full?)")) with timeout.Timeout(FLAGS.xenapi_login_timeout, exception): self._session.login_with_password(user, pw) self.loop = None -- cgit From 8da3e6c19b97ab7cd08e69fb0df114653c0b90db Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 7 Apr 2011 10:37:40 -0700 Subject: Simplify by always adding to loopback --- nova/network/linux_net.py | 5 ++--- nova/network/manager.py | 4 +--- 2 files changed, 3 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index a4d312f02..ed6c943c7 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -391,11 +391,10 @@ def unbind_floating_ip(floating_ip): 'dev', FLAGS.public_interface) -def ensure_metadata_ip(interface): +def ensure_metadata_ip(): """Sets up local metadata ip""" _execute('sudo', 'ip', 'addr', 'add', '169.254.169.254/32', - 'scope', 'link', 'dev', interface, - check_exit_code=False) + 'scope', 'link', 'dev', 'lo', check_exit_code=False) def ensure_vlan_forward(public_ip, port, private_ip): diff --git a/nova/network/manager.py b/nova/network/manager.py index b80560bdf..0dd7f2360 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -100,8 +100,6 @@ flags.DEFINE_string('network_host', socket.gethostname(), 'Network host to use for ip allocation in flat modes') flags.DEFINE_bool('fake_call', False, 'If True, skip using the queue and make local calls') -flags.DEFINE_string('metadata_interface', 'eth0', - 'interface to add the metadata ip to') class AddressAlreadyAllocated(exception.Error): @@ -128,7 +126,7 @@ class NetworkManager(manager.SchedulerDependentManager): standalone service. """ self.driver.init_host() - self.driver.ensure_metadata_ip(FLAGS.metadata_interface) + self.driver.ensure_metadata_ip() # Set up networking for the projects for which we're already # the designated network host. ctxt = context.get_admin_context() -- cgit From 404feb59a829e24e026f793a362e54aad1aaa03f Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 7 Apr 2011 13:55:42 -0500 Subject: Renamed computeFault to cloudServersFault --- nova/api/openstack/faults.py | 2 +- nova/tests/api/openstack/test_api.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py index bc97639a0..87118ce19 100644 --- a/nova/api/openstack/faults.py +++ b/nova/api/openstack/faults.py @@ -47,7 +47,7 @@ class Fault(webob.exc.HTTPException): """Generate a WSGI response based on the exception passed to ctor.""" # Replace the body with fault details. code = self.wrapped_exc.status_int - fault_name = self._fault_names.get(code, "computeFault") + fault_name = self._fault_names.get(code, "cloudServersFault") fault_data = { fault_name: { 'code': code, diff --git a/nova/tests/api/openstack/test_api.py b/nova/tests/api/openstack/test_api.py index 5112c486f..c63431a45 100644 --- a/nova/tests/api/openstack/test_api.py +++ b/nova/tests/api/openstack/test_api.py @@ -53,13 +53,13 @@ class APITest(test.TestCase): #api.application = succeed api = self._wsgi_app(succeed) resp = Request.blank('/').get_response(api) - self.assertFalse('computeFault' in resp.body, resp.body) + self.assertFalse('cloudServersFault' in resp.body, resp.body) self.assertEqual(resp.status_int, 200, resp.body) #api.application = raise_webob_exc api = self._wsgi_app(raise_webob_exc) resp = Request.blank('/').get_response(api) - self.assertFalse('computeFault' in resp.body, resp.body) + self.assertFalse('cloudServersFault' in resp.body, resp.body) self.assertEqual(resp.status_int, 404, resp.body) #api.application = raise_api_fault @@ -71,11 +71,11 @@ class APITest(test.TestCase): #api.application = fail api = self._wsgi_app(fail) resp = Request.blank('/').get_response(api) - self.assertTrue('{"computeFault' in resp.body, resp.body) + self.assertTrue('{"cloudServersFault' in resp.body, resp.body) self.assertEqual(resp.status_int, 500, resp.body) #api.application = fail api = self._wsgi_app(fail) resp = Request.blank('/.xml').get_response(api) - self.assertTrue(' Date: Thu, 7 Apr 2011 15:09:10 -0400 Subject: Drop unneeded Fkey on InstanceTypes.id. --- nova/db/sqlalchemy/models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 9d4c6cdef..f79d0f16c 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -258,8 +258,7 @@ class InstanceActions(BASE, NovaBase): class InstanceTypes(BASE, NovaBase): """Represent possible instance_types or flavor of VM offered""" __tablename__ = "instance_types" - id = Column(Integer, ForeignKey('instances.instance_type_id'), - primary_key=True) + id = Column(Integer, primary_key=True) name = Column(String(255), unique=True) memory_mb = Column(Integer) vcpus = Column(Integer) -- cgit From 99e8335e9b07b1cbf9c28cda2dfb2496d955c72c Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 7 Apr 2011 15:43:51 -0400 Subject: Some i18n fixes to instance_types. --- nova/compute/instance_types.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'nova') diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index 5b1d92e29..b3a5ab0ac 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -59,8 +59,8 @@ def create(name, memory, vcpus, local_gb, flavorid, swap=0, rxtx_quota=rxtx_quota, rxtx_cap=rxtx_cap)) except exception.DBError, e: - LOG.exception(_('DB error: %s' % e)) - raise exception.ApiError(_("Cannot create instance type: %s" % name)) + LOG.exception(_('DB error: %s') % e) + raise exception.ApiError(_("Cannot create instance type: %s") % name) def destroy(name): @@ -72,8 +72,8 @@ def destroy(name): try: db.instance_type_destroy(context.get_admin_context(), name) except exception.NotFound: - LOG.exception(_('Instance type %s not found for deletion' % name)) - raise exception.ApiError(_("Unknown instance type: %s" % name)) + LOG.exception(_('Instance type %s not found for deletion') % name) + raise exception.ApiError(_("Unknown instance type: %s") % name) def purge(name): @@ -85,8 +85,8 @@ def purge(name): try: db.instance_type_purge(context.get_admin_context(), name) except exception.NotFound: - LOG.exception(_('Instance type %s not found for purge' % name)) - raise exception.ApiError(_("Unknown instance type: %s" % name)) + LOG.exception(_('Instance type %s not found for purge') % name) + raise exception.ApiError(_("Unknown instance type: %s") % name) def get_all_types(inactive=0): @@ -106,7 +106,7 @@ def get_default_instance_type(): try: return get_instance_type_by_name(name) except exception.DBError: - raise exception.ApiError(_("Unknown instance type: %s" % name)) + raise exception.ApiError(_("Unknown instance type: %s") % name) def get_instance_type(id): @@ -117,7 +117,7 @@ def get_instance_type(id): ctxt = context.get_admin_context() return db.instance_type_get_by_id(ctxt, id) except exception.DBError: - raise exception.ApiError(_("Unknown instance type: %s" % name)) + raise exception.ApiError(_("Unknown instance type: %s") % name) def get_instance_type_by_name(name): @@ -128,7 +128,7 @@ def get_instance_type_by_name(name): ctxt = context.get_admin_context() return db.instance_type_get_by_name(ctxt, name) except exception.DBError: - raise exception.ApiError(_("Unknown instance type: %s" % name)) + raise exception.ApiError(_("Unknown instance type: %s") % name) def get_instance_type_by_flavor_id(flavor_id): @@ -139,5 +139,5 @@ def get_instance_type_by_flavor_id(flavor_id): ctxt = context.get_admin_context() return db.instance_type_get_by_flavor_id(ctxt, flavor_id) except exception.DBError, e: - LOG.exception(_('DB error: %s' % e)) - raise exception.ApiError(_("Unknown flavor: %s" % flavor_id)) + LOG.exception(_('DB error: %s') % e) + raise exception.ApiError(_("Unknown flavor: %s") % flavor_id) -- cgit From c7fd470d7ff0df4b23664b6599e5ae5acdb21511 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 7 Apr 2011 15:52:14 -0400 Subject: Drop extra 'None' arg from dict.get call. --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index e4bcccb2b..c3124b89d 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -731,7 +731,7 @@ class CloudController(object): instance['host']) i['productCodesSet'] = self._convert_to_set([], 'product_codes') if instance['instance_type']: - i['instanceType'] = instance['instance_type'].get('name', None) + i['instanceType'] = instance['instance_type'].get('name') else: i['instanceType'] = None i['launchTime'] = instance['created_at'] -- cgit From b5310d58d418f123b2d5d2953d6b4082a70120cd Mon Sep 17 00:00:00 2001 From: Kei Masumoto Date: Fri, 8 Apr 2011 06:32:53 +0900 Subject: fix pep8 violation --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index eab54c53e..256e6e635 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1857,7 +1857,7 @@ class NWFilterFirewall(FirewallDriver): self._conn.nwfilterLookupByName(instance_filter_name) except libvirt.libvirtError: name = instance.name - LOG.debug(_('The nwfilter(%(instance_filter_name)s) for' + LOG.debug(_('The nwfilter(%(instance_filter_name)s) for' '%(name)s is not found.') % locals()) return False return True -- cgit From a1572a4f234bdeda1d25250de62b5892d8f47891 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Thu, 7 Apr 2011 15:00:35 -0700 Subject: clarified nova-manage instance_type create error output on duplicate flavorid --- nova/compute/instance_types.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index fa02a5dfa..bfa120675 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -60,7 +60,9 @@ def create(name, memory, vcpus, local_gb, flavorid, swap=0, rxtx_cap=rxtx_cap)) except exception.DBError, e: LOG.exception(_('DB error: %s' % e)) - raise exception.ApiError(_("Cannot create instance type: %s" % name)) + raise exception.ApiError( + _("Cannot create instance_type with name %s and flavorid %s"\ + % (name, flavorid))) def destroy(name): -- cgit From b54be0e29cdcd91e3d106fb587b89c39ca3a0bff Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Thu, 7 Apr 2011 15:52:27 -0700 Subject: Removed commented-out old 'delete instance on SHUTOFF' code --- nova/compute/manager.py | 6 ------ nova/virt/libvirt_conn.py | 8 +------- 2 files changed, 1 insertion(+), 13 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 72f04ecb1..d3cd2e51a 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1090,12 +1090,6 @@ class ComputeManager(manager.SchedulerDependentManager): # NOTE(justinsb): We no longer auto-remove SHUTOFF instances # It's quite hard to get them back when we do. - #if vm_state == power_state.SHUTOFF: - # # TODO(soren): This is what the compute manager does when you - # # terminate an instance. At some point I figure we'll have a - # # "terminated" state and some sort of cleanup job that runs - # # occasionally, cleaning them out. - # self.db.instance_destroy(context, db_instance['id']) # Are there VMs not in the DB? for vm_not_found_in_db in vms_not_found_in_db: diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 533ff9394..4523cdd2f 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -232,14 +232,8 @@ class LibvirtConnection(driver.ComputeDriver): {'name': instance['name'], 'state': state}) db.instance_set_state(ctxt, instance['id'], state) - # NOTE(justinsb): We no longer delete these instances, + # NOTE(justinsb): We no longer delete SHUTOFF instances, # the user may want to power them back on - #if state == power_state.SHUTOFF: - # # TODO(soren): This is what the compute manager does when you - # # terminate # an instance. At some point I figure we'll have a - # # "terminated" state and some sort of cleanup job that runs - # # occasionally, cleaning them out. - # db.instance_destroy(ctxt, instance['id']) if state != power_state.RUNNING: continue -- cgit From 52478e039b094861e7d783b7995b9cafa68e32b9 Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Thu, 7 Apr 2011 15:56:16 -0700 Subject: Fix to correct libvirt error code when the domain is not found --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 4523cdd2f..a7a8a14b1 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -974,7 +974,7 @@ class LibvirtConnection(driver.ComputeDriver): try: virt_dom = self._conn.lookupByName(instance_name) except libvirt.libvirtError as e: - if e.get_error_code() == libvirt.VIR_ERR_UNKNOWN_HOST: + if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN: raise exception.NotFound(_("Instance %s not found") % instance_name) LOG.warning(_("Error from libvirt during lookup: %s") % e) -- cgit From 0c5f70c0bcf9395fb25a231057d997b075d04fda Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Thu, 7 Apr 2011 16:00:55 -0700 Subject: Log libvirt errcode on exception --- nova/virt/libvirt_conn.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index a7a8a14b1..5f1c12ab3 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -974,10 +974,13 @@ class LibvirtConnection(driver.ComputeDriver): try: virt_dom = self._conn.lookupByName(instance_name) except libvirt.libvirtError as e: - if e.get_error_code() == libvirt.VIR_ERR_NO_DOMAIN: + errcode = e.get_error_code() + if errcode == libvirt.VIR_ERR_NO_DOMAIN: raise exception.NotFound(_("Instance %s not found") % instance_name) - LOG.warning(_("Error from libvirt during lookup: %s") % e) + LOG.warning(_("Error from libvirt during lookup. " + "Code=%(errcode)s Error=%(e)s") % + locals()) raise (state, max_mem, mem, num_cpu, cpu_time) = virt_dom.info() -- cgit From b0ad20c796bd25dad0538ab85b1a56f421e16039 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 7 Apr 2011 16:42:33 -0700 Subject: fix tests from moving access check into update and delete --- nova/image/local.py | 5 ++++- nova/tests/image/test_glance.py | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/image/local.py b/nova/image/local.py index 8bf78b4c9..d4fd62156 100644 --- a/nova/image/local.py +++ b/nova/image/local.py @@ -122,12 +122,15 @@ class LocalImageService(service.BaseImageService): image_path = self._path_to(image_id, None) if not os.path.exists(image_path): os.mkdir(image_path) - return self.update(context, image_id, metadata, data) + return self._store(context, image_id, metadata, data) def update(self, context, image_id, metadata, data=None): """Replace the contents of the given image with the new data.""" # NOTE(vish): show is to check if image is available self.show(context, image_id) + return self._store(context, image_id, metadata, data) + + def _store(self, context, image_id, metadata, data=None): metadata['id'] = image_id try: if data: diff --git a/nova/tests/image/test_glance.py b/nova/tests/image/test_glance.py index 9d0b14613..109905ded 100644 --- a/nova/tests/image/test_glance.py +++ b/nova/tests/image/test_glance.py @@ -209,17 +209,17 @@ class TestMutatorDateTimeTests(BaseGlanceTest): self.assertDateTimesEmpty(image_meta) def test_update_handles_datetimes(self): + self.client.images = {'image1': self._make_datetime_fixture()} self.client.update_response = self._make_datetime_fixture() - dummy_id = 'dummy_id' dummy_meta = {} - image_meta = self.service.update(self.context, 'dummy_id', dummy_meta) + image_meta = self.service.update(self.context, 'image1', dummy_meta) self.assertDateTimesFilled(image_meta) def test_update_handles_none_datetimes(self): + self.client.images = {'image1': self._make_datetime_fixture()} self.client.update_response = self._make_none_datetime_fixture() - dummy_id = 'dummy_id' dummy_meta = {} - image_meta = self.service.update(self.context, 'dummy_id', dummy_meta) + image_meta = self.service.update(self.context, 'image1', dummy_meta) self.assertDateTimesEmpty(image_meta) def _make_datetime_fixture(self): -- cgit From 479c95b8e855c5f07b75883a1f55b4657e886a92 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 7 Apr 2011 21:44:41 -0400 Subject: Ignore errors when deleting the default route in the ensure_bridge function. --- nova/network/linux_net.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index ed6c943c7..ec5579dee 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -503,7 +503,7 @@ def ensure_bridge(bridge, interface, net_attrs=None): if fields and fields[0] == "0.0.0.0" and fields[-1] == interface: gateway = fields[1] _execute('sudo', 'route', 'del', 'default', 'gw', gateway, - 'dev', interface) + 'dev', interface, check_exit_code=False) out, err = _execute('sudo', 'ip', 'addr', 'show', 'dev', interface, 'scope', 'global') for line in out.split("\n"): -- cgit From 7badb6c0278c8cc51fc3e870fd3810ea3706f494 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 7 Apr 2011 23:39:29 -0400 Subject: Update the describe_image_attribute and modify_image_attribute functions in the ec2 API so they use the top level 'is_public' attribute of image objects. This brings these functions in line with the base image service. Added missing EC2 unit tests on describing and modifying image attributes. --- nova/api/ec2/cloud.py | 4 ++-- nova/tests/test_cloud.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 4ed8a9ecf..651ec47f9 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -984,7 +984,7 @@ class CloudController(object): except exception.NotFound: raise exception.NotFound(_('Image %s not found') % image_id) result = {'imageId': image_id, 'launchPermission': []} - if image['properties']['is_public']: + if image['is_public']: result['launchPermission'].append({'group': 'all'}) return result @@ -1009,7 +1009,7 @@ class CloudController(object): internal_id = image['id'] del(image['id']) - image['properties']['is_public'] = (operation_type == 'add') + image['is_public'] = (operation_type == 'add') return self.image_service.update(context, internal_id, image) def update_image(self, context, image_id, **kwargs): diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 5cb969979..5f76a9005 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -247,6 +247,37 @@ class CloudTestCase(test.TestCase): self.assertRaises(NotFound, describe_images, self.context, ['ami-fake']) + def test_describe_image_attribute(self): + describe_image_attribute = self.cloud.describe_image_attribute + + def fake_show(meh, context, id): + return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, + 'type': 'machine'}, 'is_public': True} + + self.stubs.Set(local.LocalImageService, 'show', fake_show) + self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show) + result = describe_image_attribute(self.context, 'ami-00000001', + 'launchPermission') + self.assertEqual([{'group': 'all'}], result['launchPermission']) + + def test_modify_image_attribute(self): + modify_image_attribute = self.cloud.modify_image_attribute + + def fake_show(meh, context, id): + return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, + 'type': 'machine'}, 'is_public': False} + + def fake_update(meh, context, image_id, metadata, data=None): + return metadata + + self.stubs.Set(local.LocalImageService, 'show', fake_show) + self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show) + self.stubs.Set(local.LocalImageService, 'update', fake_update) + result = modify_image_attribute(self.context, 'ami-00000001', + 'launchPermission', 'add', + user_group=['all']) + self.assertEqual(True, result['is_public']) + def test_console_output(self): instance_type = FLAGS.default_instance_type max_count = 1 -- cgit From decdaa30acb15e088eb6a0ca3ebc8ea6f377cbfe Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Fri, 8 Apr 2011 12:22:09 -0400 Subject: Set default stateOrProvice to 'supplied' in openssl.cnf.tmpl. This resolves a stateOrProvince printable string UTF8 mismatch on RHEL 6 and Fedora 14 (using openssl-1.0.0-4.el6.x86_64 or openssl-1.0.0d-1.fc14.x86_64). Fixes x509 certificate generation on Fedora 14 and Redhat 6. --- nova/CA/openssl.cnf.tmpl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/CA/openssl.cnf.tmpl b/nova/CA/openssl.cnf.tmpl index dd81f1c2b..b80fadf40 100644 --- a/nova/CA/openssl.cnf.tmpl +++ b/nova/CA/openssl.cnf.tmpl @@ -41,9 +41,13 @@ nameopt = default_ca certopt = default_ca policy = policy_match +# NOTE(dprince): stateOrProvinceName must be 'supplied' or 'optional' to +# work around a stateOrProvince printable string UTF8 mismatch on +# RHEL 6 and Fedora 14 (using openssl-1.0.0-4.el6.x86_64 or +# openssl-1.0.0d-1.fc14.x86_64) [ policy_match ] countryName = match -stateOrProvinceName = match +stateOrProvinceName = supplied organizationName = optional organizationalUnitName = optional commonName = supplied -- cgit From 0cf2a52218fbb801a35e5dd73e146c6c37e218e2 Mon Sep 17 00:00:00 2001 From: Kei Masumoto Date: Sat, 9 Apr 2011 02:39:18 +0900 Subject: fixed LOG level and log message phrase --- nova/compute/manager.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 847c3655a..c7d86e27b 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1094,9 +1094,8 @@ class ComputeManager(manager.SchedulerDependentManager): # A situation which db record exists, but no instance" # sometimes occurs while live-migration at src compute, # this case should be ignored. - LOG.info(_("the instance '%(name)s' is not found in hypervisor" - ", while db record is found. But not synchronize " - "since it is migrating.") % locals()) + LOG.debug(_("Ignoring %(name)s, as it's currently being " + "migrated.") % locals()) continue if vm_state != db_state: -- cgit From 5deb4796bc26d98eeea94065c5098f7ce30ac2af Mon Sep 17 00:00:00 2001 From: Johannes Erdfelt Date: Fri, 8 Apr 2011 11:21:36 -0700 Subject: Short circuit non-existant device during unit tests. It won't ever be created because of the stubs used during the unit tests --- nova/virt/xenapi/vm_utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 73a1e2a3a..d2045a557 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -936,7 +936,11 @@ def with_vdi_attached_here(session, vdi_ref, read_only, f): if dev != orig_dev: LOG.debug(_('VBD %(vbd_ref)s plugged into wrong dev, ' 'remapping to %(dev)s') % locals()) - _wait_for_device(dev) + if dev != 'autodetect': + # NOTE(johannes): Unit tests will end up with a device called + # 'autodetect' which obviously won't exist. It's not ideal, + # but the alternatives were much messier + _wait_for_device(dev) return f(dev) finally: LOG.debug(_('Destroying VBD for VDI %s ... '), vdi_ref) -- cgit From 5ea0991db9526969f100f3361661731aaf4d24d5 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Fri, 8 Apr 2011 15:22:15 -0400 Subject: Fixes bug which hangs nova-compute when terminating an instance when using libvirt backend. --- nova/virt/libvirt_conn.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index b949e6c92..51a0a3380 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -325,12 +325,13 @@ class LibvirtConnection(driver.ComputeDriver): state = self.get_info(instance['name'])['state'] db.instance_set_state(context.get_admin_context(), instance['id'], state) - if state == power_state.SHUTDOWN: + if state == power_state.SHUTOFF: break - except Exception: + except Exception as ex: + LOG.debug(ex) db.instance_set_state(context.get_admin_context(), instance['id'], - power_state.SHUTDOWN) + power_state.SHUTOFF) break self.firewall_driver.unfilter_instance(instance) -- cgit From a1c40feb0cd592829b63df1cf19109bc322f81a7 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Fri, 8 Apr 2011 15:54:17 -0400 Subject: Added error message to exception logging. --- nova/virt/libvirt_conn.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 51a0a3380..bfa9ff688 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -328,7 +328,9 @@ class LibvirtConnection(driver.ComputeDriver): if state == power_state.SHUTOFF: break except Exception as ex: - LOG.debug(ex) + msg = _("Error encountered when destroying instance '%(id)s': " + "%(ex)s") % locals().update({"id": instance["id"]}) + LOG.debug(msg) db.instance_set_state(context.get_admin_context(), instance['id'], power_state.SHUTOFF) -- cgit From 2800f931e134e7d6b316bb4d7f7118162c301ca9 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Fri, 8 Apr 2011 14:53:54 -0700 Subject: zero out volumes on delete using dd --- nova/volume/driver.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'nova') diff --git a/nova/volume/driver.py b/nova/volume/driver.py index 850893914..906a9654b 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -112,6 +112,11 @@ class VolumeDriver(object): # If the volume isn't present, then don't attempt to delete return True + # zero out old volumes to prevent data leaking between users + # TODO(ja): reclaiming space should be done lazy and low priority + self._try_execute('sudo', 'dd', 'if=/dev/zero', + 'of=%s' % self.local_path(volume), + 'bs=1M') self._try_execute('sudo', 'lvremove', '-f', "%s/%s" % (FLAGS.volume_group, volume['name'])) -- cgit From df4c269a338a9b983488ce4d5b86c829a92d471b Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Fri, 8 Apr 2011 15:17:52 -0700 Subject: dd needs a count to succeed, and remove unused/non-working special case for size 0 --- nova/volume/driver.py | 11 +++-------- nova/volume/san.py | 10 ++-------- 2 files changed, 5 insertions(+), 16 deletions(-) (limited to 'nova') diff --git a/nova/volume/driver.py b/nova/volume/driver.py index 906a9654b..43792b791 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -93,10 +93,7 @@ class VolumeDriver(object): def create_volume(self, volume): """Creates a logical volume. Can optionally return a Dictionary of changes to the volume object to be persisted.""" - if int(volume['size']) == 0: - sizestr = '100M' - else: - sizestr = '%sG' % volume['size'] + sizestr = '%sG' % volume['size'] self._try_execute('sudo', 'lvcreate', '-L', sizestr, '-n', volume['name'], FLAGS.volume_group) @@ -116,6 +113,7 @@ class VolumeDriver(object): # TODO(ja): reclaiming space should be done lazy and low priority self._try_execute('sudo', 'dd', 'if=/dev/zero', 'of=%s' % self.local_path(volume), + 'count=%d' % volume['size'] * 1024, 'bs=1M') self._try_execute('sudo', 'lvremove', '-f', "%s/%s" % (FLAGS.volume_group, @@ -601,10 +599,7 @@ class SheepdogDriver(VolumeDriver): def create_volume(self, volume): """Creates a sheepdog volume""" - if int(volume['size']) == 0: - sizestr = '100M' - else: - sizestr = '%sG' % volume['size'] + sizestr = '%sG' % volume['size'] self._try_execute('qemu-img', 'create', "sheepdog:%s" % volume['name'], sizestr) diff --git a/nova/volume/san.py b/nova/volume/san.py index 9532c8116..dd332de1a 100644 --- a/nova/volume/san.py +++ b/nova/volume/san.py @@ -219,10 +219,7 @@ class SolarisISCSIDriver(SanISCSIDriver): def create_volume(self, volume): """Creates a volume.""" - if int(volume['size']) == 0: - sizestr = '100M' - else: - sizestr = '%sG' % volume['size'] + sizestr = '%sG' % volume['size'] zfs_poolname = self._build_zfs_poolname(volume) @@ -489,10 +486,7 @@ class HpSanISCSIDriver(SanISCSIDriver): #TODO(justinsb): Should we default to inheriting thinProvision? cliq_args['thinProvision'] = '1' if FLAGS.san_thin_provision else '0' cliq_args['volumeName'] = volume['name'] - if int(volume['size']) == 0: - cliq_args['size'] = '100MB' - else: - cliq_args['size'] = '%sGB' % volume['size'] + cliq_args['size'] = '%sGB' % volume['size'] self._cliq_run_xml("createVolume", cliq_args) -- cgit From 7f04c6f5165ea96f22ec17bdbe7a3f2a7595edb1 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Fri, 8 Apr 2011 15:23:17 -0700 Subject: typo - need to get nova-volumes working on this machine :-/ --- nova/volume/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/volume/driver.py b/nova/volume/driver.py index 43792b791..f9c268191 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -113,7 +113,7 @@ class VolumeDriver(object): # TODO(ja): reclaiming space should be done lazy and low priority self._try_execute('sudo', 'dd', 'if=/dev/zero', 'of=%s' % self.local_path(volume), - 'count=%d' % volume['size'] * 1024, + 'count=%d' % (volume['size'] * 1024), 'bs=1M') self._try_execute('sudo', 'lvremove', '-f', "%s/%s" % (FLAGS.volume_group, -- cgit From 5aab609b24140622b87db970243641ec382b214e Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Fri, 8 Apr 2011 15:24:09 -0700 Subject: pylintage --- nova/volume/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/volume/driver.py b/nova/volume/driver.py index f9c268191..0c4086039 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -111,7 +111,7 @@ class VolumeDriver(object): # zero out old volumes to prevent data leaking between users # TODO(ja): reclaiming space should be done lazy and low priority - self._try_execute('sudo', 'dd', 'if=/dev/zero', + self._try_execute('sudo', 'dd', 'if=/dev/zero', 'of=%s' % self.local_path(volume), 'count=%d' % (volume['size'] * 1024), 'bs=1M') -- cgit From 79ebe165f255037b0d5eaad7afe81b51cf85ed63 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Sat, 9 Apr 2011 11:08:47 -0400 Subject: Fixed log message gaffe. --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index bfa9ff688..9c665ab15 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -329,7 +329,7 @@ class LibvirtConnection(driver.ComputeDriver): break except Exception as ex: msg = _("Error encountered when destroying instance '%(id)s': " - "%(ex)s") % locals().update({"id": instance["id"]}) + "%(ex)s") % {"id": instance["id"], "ex": ex} LOG.debug(msg) db.instance_set_state(context.get_admin_context(), instance['id'], -- cgit From a572b49e376cd6da4265c2807eaed8f0a2daf954 Mon Sep 17 00:00:00 2001 From: Justin SB Date: Sat, 9 Apr 2011 11:57:14 -0700 Subject: Remove the XML definition when we destroy a machine --- nova/virt/libvirt_conn.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index b949e6c92..1ad6f8b32 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -309,10 +309,18 @@ class LibvirtConnection(driver.ComputeDriver): return infos def destroy(self, instance, cleanup=True): + name = instance['name'] try: - virt_dom = self._conn.lookupByName(instance['name']) + virt_dom = self._conn.lookupByName(name) virt_dom.destroy() - except Exception as _err: + # NOTE(justinsb): We remove the domain definition. We probably + # would do better to keep it if cleanup=False (e.g. volumes?) + # (e.g. #2 - not losing machines on failure) + virt_dom.undefine() + except Exception as e: + # TODO(justinsb): We really should check the error is 'not found' + LOG.warn(_("Ignoring error destroying domain %(name)s: %(e)s") % + locals()) pass # If the instance is already terminated, we're still happy -- cgit From be386ee614777212da2a14ebd8211f4b3d90ce66 Mon Sep 17 00:00:00 2001 From: Justin SB Date: Sat, 9 Apr 2011 12:33:24 -0700 Subject: Split logic on shutdown and undefine, so that even if the machine is already shutdown we will be able to proceed --- nova/virt/libvirt_conn.py | 53 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 13 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 1ad6f8b32..47eb17abb 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -309,20 +309,47 @@ class LibvirtConnection(driver.ComputeDriver): return infos def destroy(self, instance, cleanup=True): - name = instance['name'] + instance_name = instance['name'] + + # TODO(justinsb): Refactor all lookupByName calls for error-handling try: - virt_dom = self._conn.lookupByName(name) - virt_dom.destroy() - # NOTE(justinsb): We remove the domain definition. We probably - # would do better to keep it if cleanup=False (e.g. volumes?) - # (e.g. #2 - not losing machines on failure) - virt_dom.undefine() - except Exception as e: - # TODO(justinsb): We really should check the error is 'not found' - LOG.warn(_("Ignoring error destroying domain %(name)s: %(e)s") % - locals()) - pass - # If the instance is already terminated, we're still happy + virt_dom = self._conn.lookupByName(instance_name) + except libvirt.libvirtError as e: + errcode = e.get_error_code() + if errcode == libvirt.VIR_ERR_NO_DOMAIN: + virt_dom = None + else: + LOG.warning(_("Error from libvirt during lookup of " + "%(instance_name)s. Code=%(errcode)s " + "Error=%(e)s") % + locals()) + raise + + # If the instance is already terminated, we're still happy + # Otherwise, destroy it + if virt_dom is not None: + try: + virt_dom.destroy() + except libvirt.libvirtError as e: + errcode = e.get_error_code() + LOG.warning(_("Error from libvirt during destroy of " + "%(instance_name)s. Code=%(errcode)s " + "Error=%(e)s") % + locals()) + raise + + try: + # NOTE(justinsb): We remove the domain definition. We probably + # would do better to keep it if cleanup=False (e.g. volumes?) + # (e.g. #2 - not losing machines on failure) + virt_dom.undefine() + except libvirt.libvirtError as e: + errcode = e.get_error_code() + LOG.warning(_("Error from libvirt during undefine of " + "%(instance_name)s. Code=%(errcode)s " + "Error=%(e)s") % + locals()) + raise # We'll save this for when we do shutdown, # instead of destroy - but destroy returns immediately -- cgit From c6923ec603288e1d46fdb80e874c8e71361442f5 Mon Sep 17 00:00:00 2001 From: Justin SB Date: Sat, 9 Apr 2011 12:41:30 -0700 Subject: Handle the case when the machine is already SHUTOFF --- nova/virt/libvirt_conn.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 47eb17abb..7771aad7a 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -331,12 +331,22 @@ class LibvirtConnection(driver.ComputeDriver): try: virt_dom.destroy() except libvirt.libvirtError as e: + is_okay = False errcode = e.get_error_code() - LOG.warning(_("Error from libvirt during destroy of " - "%(instance_name)s. Code=%(errcode)s " - "Error=%(e)s") % - locals()) - raise + if errcode == libvirt.VIR_ERR_OPERATION_INVALID: + # If the instance if already shut off, we get this: + # Code=55 Error=Requested operation is not valid: + # domain is not running + (state, _, _, _, _) = virt_dom.info() + if state == power_state.SHUTOFF: + is_okay = True + + if not is_okay: + LOG.warning(_("Error from libvirt during destroy of " + "%(instance_name)s. Code=%(errcode)s " + "Error=%(e)s") % + locals()) + raise try: # NOTE(justinsb): We remove the domain definition. We probably -- cgit From f59f8e8fcbde6f0d8d4c19b00bfc5f4141287772 Mon Sep 17 00:00:00 2001 From: Justin SB Date: Sat, 9 Apr 2011 12:57:32 -0700 Subject: Ooops - redefining the _ variable seems like a _really_ bad idea --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 7771aad7a..22b0e1103 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -337,7 +337,7 @@ class LibvirtConnection(driver.ComputeDriver): # If the instance if already shut off, we get this: # Code=55 Error=Requested operation is not valid: # domain is not running - (state, _, _, _, _) = virt_dom.info() + (state, _max_mem, _mem, _cpus, _t) = virt_dom.info() if state == power_state.SHUTOFF: is_okay = True -- cgit From 5752838917237e7b86a64117f46c71d1c2a356f3 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Mon, 11 Apr 2011 16:18:18 +0200 Subject: Replace instance ref from compute.api.get_all with one from instance_get. This should ensure it gets fully populated with all the relevant attributes. --- nova/api/ec2/cloud.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 651ec47f9..83c894bea 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -142,6 +142,11 @@ class CloudController(object): instance_ref = self.compute_api.get_all(ctxt, fixed_ip=address) if instance_ref is None: return None + + # This ensures that all attributes of the instance + # are populated. + instance_ref = db.instance_get(ctxt, instance_ref['id']) + mpi = self._get_mpi_data(ctxt, instance_ref['project_id']) if instance_ref['key_name']: keys = {'0': {'_name': instance_ref['key_name'], -- cgit From 21fd04c34487b97f7d1ed199773cf80e9ab60839 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Mon, 11 Apr 2011 15:56:36 +0000 Subject: fix reference to genvpn to point to the right shell script --- nova/crypto.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/crypto.py b/nova/crypto.py index 9b1897926..605be2a32 100644 --- a/nova/crypto.py +++ b/nova/crypto.py @@ -232,7 +232,7 @@ def generate_vpn_files(project_id): genvpn_sh_path = os.path.join(os.path.dirname(__file__), 'CA', - 'geninter.sh') + 'genvpn.sh') if os.path.exists(crt_fn): return _ensure_project_folder(project_id) -- cgit From 6ac2b25d77ea71be0f9232b5502a75f255a6b2ec Mon Sep 17 00:00:00 2001 From: termie Date: Mon, 11 Apr 2011 11:34:19 -0500 Subject: docstring cleanup, direct api, part of compute --- nova/api/__init__.py | 2 - nova/api/direct.py | 90 +++++++++++++++++++++++++++++++- nova/compute/api.py | 115 ++++++++++++++++++++++------------------- nova/compute/instance_types.py | 35 ++++++------- 4 files changed, 165 insertions(+), 77 deletions(-) (limited to 'nova') diff --git a/nova/api/__init__.py b/nova/api/__init__.py index 0fedbbfad..747015af5 100644 --- a/nova/api/__init__.py +++ b/nova/api/__init__.py @@ -15,5 +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. - -"""No-op __init__ for directory full of api goodies.""" diff --git a/nova/api/direct.py b/nova/api/direct.py index f487df7c7..8ceae299c 100644 --- a/nova/api/direct.py +++ b/nova/api/direct.py @@ -44,14 +44,33 @@ from nova import utils from nova import wsgi +# Global storage for registering modules. ROUTES = {} def register_service(path, handle): + """Register a service handle at a given path. + + Services registered in this way will be made available to any instances of + nova.api.direct.Router. + + :param path: `routes` path, can be a basic string like "/path" + :param handle: an object whose methods will be made available via the api + + """ ROUTES[path] = handle class Router(wsgi.Router): + """A simple WSGI router configured via `register_service`. + + This is a quick way to attach multiple services to a given endpoint. + It will automatically load the routes registered in the `ROUTES` global. + + TODO(termie): provide a paste-deploy version of this. + + """ + def __init__(self, mapper=None): if mapper is None: mapper = routes.Mapper() @@ -66,6 +85,24 @@ class Router(wsgi.Router): class DelegatedAuthMiddleware(wsgi.Middleware): + """A simple and naive authentication middleware. + + Designed mostly to provide basic support for alternative authentication + schemes, this middleware only desires the identity of the user and will + generate the appropriate nova.context.RequestContext for the rest of the + application. This allows any middleware above it in the stack to + authenticate however it would like while only needing to conform to a + minimal interface. + + Expects two headers to determine identity: + - X-OpenStack-User + - X-OpenStack-Project + + This middleware is tied to identity management and will need to be kept + in sync with any changes to the way identity is dealt with internally. + + """ + def process_request(self, request): os_user = request.headers['X-OpenStack-User'] os_project = request.headers['X-OpenStack-Project'] @@ -74,6 +111,20 @@ class DelegatedAuthMiddleware(wsgi.Middleware): class JsonParamsMiddleware(wsgi.Middleware): + """Middleware to allow method arguments to be passed as serialized JSON. + + Accepting arguments as JSON is useful for accepting data that may be more + complex than simple primitives. + + In this case we accept it as urlencoded data under the key 'json' as in + json= but this could be extended to accept raw JSON + in the POST body. + + Filters out the parameters `self`, `context` and anything beginning with + an underscore. + + """ + def process_request(self, request): if 'json' not in request.params: return @@ -92,6 +143,13 @@ class JsonParamsMiddleware(wsgi.Middleware): class PostParamsMiddleware(wsgi.Middleware): + """Middleware to allow method arguments to be passed as POST parameters. + + Filters out the parameters `self`, `context` and anything beginning with + an underscore. + + """ + def process_request(self, request): params_parsed = request.params params = {} @@ -106,12 +164,21 @@ class PostParamsMiddleware(wsgi.Middleware): class Reflection(object): - """Reflection methods to list available methods.""" + """Reflection methods to list available methods. + + This is an object that expects to be registered via register_service. + These methods allow the endpoint to be self-describing. They introspect + the exposed methods and provide call signatures and documentation for + them allowing quick experimentation. + + """ + def __init__(self): self._methods = {} self._controllers = {} def _gather_methods(self): + """Introspect available methods and generate documentation for them.""" methods = {} controllers = {} for route, handler in ROUTES.iteritems(): @@ -185,6 +252,16 @@ class Reflection(object): class ServiceWrapper(wsgi.Controller): + """Wrapper to dynamically povide a WSGI controller for arbitrary objects. + + With lightweight introspection allows public methods on the object to + be accesed via simple WSGI routing and parameters and serializes the + return values. + + Automatically used be nova.api.direct.Router to wrap registered instances. + + """ + def __init__(self, service_handle): self.service_handle = service_handle @@ -260,7 +337,16 @@ class Limited(object): class Proxy(object): - """Pretend a Direct API endpoint is an object.""" + """Pretend a Direct API endpoint is an object. + + This is mostly useful in testing at the moment though it should be easily + extendable to provide a basic API library functionality. + + In testing we use this to stub out internal objects to verify that results + from the API are serializable. + + """ + def __init__(self, app, prefix=None): self.app = app self.prefix = prefix diff --git a/nova/compute/api.py b/nova/compute/api.py index 041e0e74a..e6146231c 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -16,9 +16,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Handles all requests relating to instances (guest vms). -""" +"""Handles all requests relating to instances (guest vms).""" import datetime import re @@ -86,10 +84,10 @@ class API(base.Base): {"method": "get_network_topic", "args": {'fake': 1}}) def _check_injected_file_quota(self, context, injected_files): - """ - Enforce quota limits on injected files + """Enforce quota limits on injected files. + + Raises a QuotaError if any limit is exceeded. - Raises a QuotaError if any limit is exceeded """ if injected_files is None: return @@ -111,8 +109,11 @@ class API(base.Base): key_name=None, key_data=None, security_group='default', availability_zone=None, user_data=None, metadata=[], injected_files=None): - """Create the number of instances requested if quota and - other arguments check out ok.""" + """Create the number and type of instances requested. + + Verifies that quota and other arguments are valid. + + """ if not instance_type: instance_type = instance_types.get_default_instance_type() @@ -262,8 +263,7 @@ class API(base.Base): return [dict(x.iteritems()) for x in instances] def has_finished_migration(self, context, instance_id): - """Retrieves whether or not a finished migration exists for - an instance""" + """Returns true if an instance has a finished migration.""" try: db.migration_get_by_instance_and_status(context, instance_id, 'finished') @@ -272,8 +272,10 @@ class API(base.Base): return False def ensure_default_security_group(self, context): - """ Create security group for the security context if it - does not already exist + """Ensure that a context has a security group. + + Creates a security group for the security context if it does not + already exist. :param context: the security context @@ -289,7 +291,7 @@ class API(base.Base): db.security_group_create(context, values) def trigger_security_group_rules_refresh(self, context, security_group_id): - """Called when a rule is added to or removed from a security_group""" + """Called when a rule is added to or removed from a security_group.""" security_group = self.db.security_group_get(context, security_group_id) @@ -305,11 +307,12 @@ class API(base.Base): "args": {"security_group_id": security_group.id}}) def trigger_security_group_members_refresh(self, context, group_id): - """Called when a security group gains a new or loses a member + """Called when a security group gains a new or loses a member. Sends an update request to each compute node for whom this is - relevant.""" + relevant. + """ # First, we get the security group rules that reference this group as # the grantee.. security_group_rules = \ @@ -354,7 +357,7 @@ class API(base.Base): as data fields of the instance to be updated - :retval None + :returns: None """ rv = self.db.instance_update(context, instance_id, kwargs) @@ -362,6 +365,7 @@ class API(base.Base): @scheduler_api.reroute_compute("delete") def delete(self, context, instance_id): + """Terminate an instance.""" LOG.debug(_("Going to try to terminate %s"), instance_id) try: instance = self.get(context, instance_id) @@ -393,22 +397,28 @@ class API(base.Base): self.db.instance_destroy(context, instance_id) def get(self, context, instance_id): - """Get a single instance with the given ID.""" + """Get a single instance with the given instance_id.""" rv = self.db.instance_get(context, instance_id) return dict(rv.iteritems()) @scheduler_api.reroute_compute("get") def routing_get(self, context, instance_id): - """Use this method instead of get() if this is the only - operation you intend to to. It will route to novaclient.get - if the instance is not found.""" + """A version of get with special routing characteristics. + + Use this method instead of get() if this is the only operation you + intend to to. It will route to novaclient.get if the instance is not + found. + + """ return self.get(context, instance_id) def get_all(self, context, project_id=None, reservation_id=None, fixed_ip=None): - """Get all instances, possibly filtered by one of the - given parameters. If there is no filter and the context is - an admin, it will retreive all instances in the system. + """Get all instances filtered by one of the given parameters. + + If there is no filter and the context is an admin, it will retreive + all instances in the system. + """ if reservation_id is not None: return self.db.instance_get_all_by_reservation( @@ -437,7 +447,8 @@ class API(base.Base): :param params: Optional dictionary of arguments to be passed to the compute worker - :retval None + :returns: None + """ if not params: params = {} @@ -456,7 +467,7 @@ class API(base.Base): :param params: Optional dictionary of arguments to be passed to the compute worker - :retval: Result returned by compute worker + :returns: Result returned by compute worker """ if not params: params = {} @@ -469,13 +480,14 @@ class API(base.Base): return rpc.call(context, queue, kwargs) def _cast_scheduler_message(self, context, args): - """Generic handler for RPC calls to the scheduler""" + """Generic handler for RPC calls to the scheduler.""" rpc.cast(context, FLAGS.scheduler_topic, args) def snapshot(self, context, instance_id, name): """Snapshot the given instance. - :retval: A dict containing image metadata + :returns: A dict containing image metadata + """ properties = {'instance_id': str(instance_id), 'user_id': str(context.user_id)} @@ -492,7 +504,7 @@ class API(base.Base): self._cast_compute_message('reboot_instance', context, instance_id) def revert_resize(self, context, instance_id): - """Reverts a resize, deleting the 'new' instance in the process""" + """Reverts a resize, deleting the 'new' instance in the process.""" context = context.elevated() migration_ref = self.db.migration_get_by_instance_and_status(context, instance_id, 'finished') @@ -507,8 +519,7 @@ class API(base.Base): {'status': 'reverted'}) def confirm_resize(self, context, instance_id): - """Confirms a migration/resize, deleting the 'old' instance in the - process.""" + """Confirms a migration/resize and deletes the 'old' instance.""" context = context.elevated() migration_ref = self.db.migration_get_by_instance_and_status(context, instance_id, 'finished') @@ -568,10 +579,9 @@ class API(base.Base): @scheduler_api.reroute_compute("diagnostics") def get_diagnostics(self, context, instance_id): """Retrieve diagnostics for the given instance.""" - return self._call_compute_message( - "get_diagnostics", - context, - instance_id) + return self._call_compute_message("get_diagnostics", + context, + instance_id) def get_actions(self, context, instance_id): """Retrieve actions for the given instance.""" @@ -579,12 +589,12 @@ class API(base.Base): @scheduler_api.reroute_compute("suspend") def suspend(self, context, instance_id): - """suspend the instance with instance_id""" + """Suspend the given instance.""" self._cast_compute_message('suspend_instance', context, instance_id) @scheduler_api.reroute_compute("resume") def resume(self, context, instance_id): - """resume the instance with instance_id""" + """Resume the given instance.""" self._cast_compute_message('resume_instance', context, instance_id) @scheduler_api.reroute_compute("rescue") @@ -599,15 +609,15 @@ class API(base.Base): def set_admin_password(self, context, instance_id, password=None): """Set the root/admin password for the given instance.""" - self._cast_compute_message('set_admin_password', context, instance_id, - password) + self._cast_compute_message( + 'set_admin_password', context, instance_id, password) def inject_file(self, context, instance_id): """Write a file to the given instance.""" self._cast_compute_message('inject_file', context, instance_id) def get_ajax_console(self, context, instance_id): - """Get a url to an AJAX Console""" + """Get a url to an AJAX Console.""" output = self._call_compute_message('get_ajax_console', context, instance_id) @@ -616,7 +626,7 @@ class API(base.Base): 'args': {'token': output['token'], 'host': output['host'], 'port': output['port']}}) return {'url': '%s/?token=%s' % (FLAGS.ajax_console_proxy_url, - output['token'])} + output['token'])} def get_vnc_console(self, context, instance_id): """Get a url to a VNC Console.""" @@ -638,39 +648,34 @@ class API(base.Base): 'portignore')} def get_console_output(self, context, instance_id): - """Get console output for an an instance""" + """Get console output for an an instance.""" return self._call_compute_message('get_console_output', context, instance_id) def lock(self, context, instance_id): - """lock the instance with instance_id""" + """Lock the given instance.""" self._cast_compute_message('lock_instance', context, instance_id) def unlock(self, context, instance_id): - """unlock the instance with instance_id""" + """Unlock the given instance.""" self._cast_compute_message('unlock_instance', context, instance_id) def get_lock(self, context, instance_id): - """return the boolean state of (instance with instance_id)'s lock""" + """Return the boolean state of given instance's lock.""" instance = self.get(context, instance_id) return instance['locked'] def reset_network(self, context, instance_id): - """ - Reset networking on the instance. - - """ + """Reset networking on the instance.""" self._cast_compute_message('reset_network', context, instance_id) def inject_network_info(self, context, instance_id): - """ - Inject network info for the instance. - - """ + """Inject network info for the instance.""" self._cast_compute_message('inject_network_info', context, instance_id) def attach_volume(self, context, instance_id, volume_id, device): + """Attach an existing volume to an existing instance.""" if not re.match("^/dev/[a-z]d[a-z]+$", device): raise exception.ApiError(_("Invalid device specified: %s. " "Example device: /dev/vdb") % device) @@ -685,6 +690,7 @@ class API(base.Base): "mountpoint": device}}) def detach_volume(self, context, volume_id): + """Detach a volume from an instance.""" instance = self.db.volume_get_instance(context.elevated(), volume_id) if not instance: raise exception.ApiError(_("Volume isn't attached to anything!")) @@ -698,6 +704,7 @@ class API(base.Base): return instance def associate_floating_ip(self, context, instance_id, address): + """Associate a floating ip with an instance.""" instance = self.get(context, instance_id) self.network_api.associate_floating_ip(context, floating_ip=address, @@ -709,11 +716,11 @@ class API(base.Base): return dict(rv.iteritems()) def delete_instance_metadata(self, context, instance_id, key): - """Delete the given metadata item""" + """Delete the given metadata item from an instance.""" self.db.instance_metadata_delete(context, instance_id, key) def update_or_create_instance_metadata(self, context, instance_id, metadata): - """Updates or creates instance metadata""" + """Updates or creates instance metadata.""" self.db.instance_metadata_update_or_create(context, instance_id, metadata) diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index b3a5ab0ac..f893f8478 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -18,9 +18,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" -The built-in instance properties. -""" +"""Built-in instance properties.""" from nova import context from nova import db @@ -34,9 +32,7 @@ LOG = logging.getLogger('nova.instance_types') def create(name, memory, vcpus, local_gb, flavorid, swap=0, rxtx_quota=0, rxtx_cap=0): - """Creates instance types / flavors - arguments: name memory vcpus local_gb flavorid swap rxtx_quota rxtx_cap - """ + """Creates instance types.""" for option in [memory, vcpus, local_gb, flavorid]: try: int(option) @@ -64,8 +60,7 @@ def create(name, memory, vcpus, local_gb, flavorid, swap=0, def destroy(name): - """Marks instance types / flavors as deleted - arguments: name""" + """Marks instance types as deleted.""" if name == None: raise exception.InvalidInputException(_("No instance type specified")) else: @@ -77,8 +72,7 @@ def destroy(name): def purge(name): - """Removes instance types / flavors from database - arguments: name""" + """Removes instance types from database.""" if name == None: raise exception.InvalidInputException(_("No instance type specified")) else: @@ -90,18 +84,19 @@ def purge(name): def get_all_types(inactive=0): - """Retrieves non-deleted instance_types. - Pass true as argument if you want deleted instance types returned also.""" + """Get all non-deleted instance_types. + + Pass true as argument if you want deleted instance types returned also. + + """ return db.instance_type_get_all(context.get_admin_context(), inactive) -def get_all_flavors(): - """retrieves non-deleted flavors. alias for instance_types.get_all_types(). - Pass true as argument if you want deleted instance types returned also.""" - return get_all_types(context.get_admin_context()) +get_all_flavors = get_all_types def get_default_instance_type(): + """Get the default instance type.""" name = FLAGS.default_instance_type try: return get_instance_type_by_name(name) @@ -110,7 +105,7 @@ def get_default_instance_type(): def get_instance_type(id): - """Retrieves single instance type by id""" + """Retrieves single instance type by id.""" if id is None: return get_default_instance_type() try: @@ -121,7 +116,7 @@ def get_instance_type(id): def get_instance_type_by_name(name): - """Retrieves single instance type by name""" + """Retrieves single instance type by name.""" if name is None: return get_default_instance_type() try: @@ -131,8 +126,10 @@ def get_instance_type_by_name(name): raise exception.ApiError(_("Unknown instance type: %s") % name) +# TODO(termie): flavor-specific code should probably be in the API that uses +# flavors. def get_instance_type_by_flavor_id(flavor_id): - """retrieve instance type by flavor_id""" + """Retrieve instance type by flavor_id.""" if flavor_id is None: return get_default_instance_type() try: -- cgit From b342b1b63a860b9f4abdc28224ab7a6a0f3b00dd Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Mon, 11 Apr 2011 12:15:22 -0500 Subject: Remove unused self.interfaces_xml --- nova/virt/libvirt_conn.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 6ec15fbb8..4b6cfa6a4 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -211,7 +211,6 @@ class LibvirtConnection(driver.ComputeDriver): self.libvirt_uri = self.get_uri() self.libvirt_xml = open(FLAGS.libvirt_xml_template).read() - self.interfaces_xml = open(FLAGS.injected_network_template).read() self.cpuinfo_xml = open(FLAGS.cpuinfo_xml_template).read() self._wrapped_conn = None self.read_only = read_only -- cgit From 9ce66a4a09094d2b0403deea77416149aa789f3c Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Mon, 11 Apr 2011 22:35:09 +0400 Subject: Floating ips auto assignment --- nova/compute/manager.py | 21 +++++++++++++++++++++ nova/db/api.py | 3 +++ nova/db/sqlalchemy/api.py | 12 ++++++++++++ 3 files changed, 36 insertions(+) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 68b163355..86273b6b4 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -73,6 +73,8 @@ flags.DEFINE_integer('live_migration_retry_count', 30, flags.DEFINE_integer("rescue_timeout", 0, "Automatically unrescue an instance after N seconds." " Set to 0 to disable.") +flags.DEFINE_bool('auto_assign_floating_ip',False, 'Autoassigning floating' + ' ip to VM') LOG = logging.getLogger('nova.compute.manager') @@ -224,6 +226,16 @@ class ComputeManager(manager.SchedulerDependentManager): self.network_manager.setup_compute_network(context, instance_id) + if FLAGS.auto_assign_floating_ip: + public_ip = rpc.call(context, + FLAGS.network_topic, + {"method": "allocate_floating_ip", + "args": {"project_id": context.project_id}}) + self.network_manager.associate_floating_ip(context, + instance_id=instance_id, + address=public_ip) + + # TODO(vish) check to make sure the availability zone matches self.db.instance_set_state(context, instance_id, @@ -271,6 +283,15 @@ class ComputeManager(manager.SchedulerDependentManager): network_topic, {"method": "disassociate_floating_ip", "args": {"floating_address": address}}) + + if FLAGS.auto_assign_floating_ip: + LOG.debug(_("Deallocating floating ip %s"), + floating_ip['address'], context=context) + rpc.cast(context, + FLAGS.network_topic, + {"method": "deallocate_floating_ip", + "args": {"floating_address": + floating_ip['address']}}) address = fixed_ip['address'] if address: diff --git a/nova/db/api.py b/nova/db/api.py index 63901e94d..859acc146 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -290,6 +290,9 @@ def floating_ip_update(context, address, values): """Update a floating ip by address or raise if it doesn't exist.""" return IMPL.floating_ip_update(context, address, values) +def floating_ip_set_auto_assigned(context, address): + """Set auto_assigned flag to floating ip""" + return IMPL.floating_ip_set_auto_assigned(context, address) #################### diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index e675022e9..28126a517 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -489,6 +489,7 @@ def floating_ip_deallocate(context, address): address, session=session) floating_ip_ref['project_id'] = None + floating_ip_ref['auto_assigned'] = False floating_ip_ref.save(session=session) @@ -522,6 +523,17 @@ def floating_ip_disassociate(context, address): return fixed_ip_address +@require_context +def floating_ip_set_auto_assigned(context, address): + session = get_session() + with session.begin(): + floating_ip_ref = floating_ip_get_by_address(context, + address, + session=session) + floating_ip_ref.auto_assigned = True + floating_ip_ref.save(session=session) + + @require_admin_context def floating_ip_get_all(context): session = get_session() -- cgit From 4a2c973fe5c7cf68ff7f45a4927dc6d2e0a3986b Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Tue, 12 Apr 2011 02:44:44 +0400 Subject: migaration and pep8 fixes --- nova/api/openstack/contrib/volumes.py | 3 +- nova/compute/manager.py | 4 +-- nova/db/api.py | 2 ++ .../015_add_auto_assign_to_floating_ips.py | 38 ++++++++++++++++++++++ 4 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py (limited to 'nova') diff --git a/nova/api/openstack/contrib/volumes.py b/nova/api/openstack/contrib/volumes.py index 6efacce52..18de2ec71 100644 --- a/nova/api/openstack/contrib/volumes.py +++ b/nova/api/openstack/contrib/volumes.py @@ -322,8 +322,7 @@ class Volumes(extensions.ExtensionDescriptor): # Does this matter? res = extensions.ResourceExtension('volumes', VolumeController(), - collection_actions={'detail': 'GET'} - ) + collection_actions={'detail': 'GET'}) resources.append(res) res = extensions.ResourceExtension('volume_attachments', diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 86273b6b4..af3551708 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -73,7 +73,7 @@ flags.DEFINE_integer('live_migration_retry_count', 30, flags.DEFINE_integer("rescue_timeout", 0, "Automatically unrescue an instance after N seconds." " Set to 0 to disable.") -flags.DEFINE_bool('auto_assign_floating_ip',False, 'Autoassigning floating' +flags.DEFINE_bool('auto_assign_floating_ip', False, 'Autoassigning floating' ' ip to VM') LOG = logging.getLogger('nova.compute.manager') @@ -235,7 +235,6 @@ class ComputeManager(manager.SchedulerDependentManager): instance_id=instance_id, address=public_ip) - # TODO(vish) check to make sure the availability zone matches self.db.instance_set_state(context, instance_id, @@ -283,7 +282,6 @@ class ComputeManager(manager.SchedulerDependentManager): network_topic, {"method": "disassociate_floating_ip", "args": {"floating_address": address}}) - if FLAGS.auto_assign_floating_ip: LOG.debug(_("Deallocating floating ip %s"), floating_ip['address'], context=context) diff --git a/nova/db/api.py b/nova/db/api.py index 859acc146..6b465c021 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -290,12 +290,14 @@ def floating_ip_update(context, address, values): """Update a floating ip by address or raise if it doesn't exist.""" return IMPL.floating_ip_update(context, address, values) + def floating_ip_set_auto_assigned(context, address): """Set auto_assigned flag to floating ip""" return IMPL.floating_ip_set_auto_assigned(context, address) #################### + def migration_update(context, id, values): """Update a migration instance""" return IMPL.migration_update(context, id, values) diff --git a/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py new file mode 100644 index 000000000..b7e480e67 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py @@ -0,0 +1,38 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# Copyright 2011 Grid Dynamics +# +# 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 * +from sqlalchemy.sql import text +from migrate import * + + +meta = MetaData() + +c_auto_assigned = Column('auto_assigned',Boolean, default=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 + + floating_ips = Table('floating_ips', meta, autoload=True, + autoload_with=migrate_engine) + + floating_ips.create_column(c_auto_assigned) + + \ No newline at end of file -- cgit From fa4aeb9af8d00ecff6620646c142e5ff68e1cd5e Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Tue, 12 Apr 2011 03:07:22 +0400 Subject: pep8 fixes --- .../migrate_repo/versions/015_add_auto_assign_to_floating_ips.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py index b7e480e67..6829b5d6e 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py @@ -22,7 +22,7 @@ from migrate import * meta = MetaData() -c_auto_assigned = Column('auto_assigned',Boolean, default=False) +c_auto_assigned = Column('auto_assigned', Boolean, default=False) def upgrade(migrate_engine): @@ -33,6 +33,4 @@ def upgrade(migrate_engine): floating_ips = Table('floating_ips', meta, autoload=True, autoload_with=migrate_engine) - floating_ips.create_column(c_auto_assigned) - - \ No newline at end of file + floating_ips.create_column(c_auto_assigned) \ No newline at end of file -- cgit From 87d758b18836085d73c8b4230cd4812e0aa876aa Mon Sep 17 00:00:00 2001 From: Yoshiaki Tamura Date: Tue, 12 Apr 2011 11:37:51 +0900 Subject: Fix RBDDriver in volume manager. discover_volume was raising exception. Modified local_path as well. --- nova/volume/driver.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/volume/driver.py b/nova/volume/driver.py index 850893914..85ff17708 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -557,7 +557,7 @@ class RBDDriver(VolumeDriver): """Returns the path of the rbd volume.""" # This is the same as the remote path # since qemu accesses it directly. - return self.discover_volume(volume) + return "rbd:%s/%s" % (FLAGS.rbd_pool, volume['name']) def ensure_export(self, context, volume): """Synchronously recreates an export for a logical volume.""" @@ -571,10 +571,8 @@ class RBDDriver(VolumeDriver): """Removes an export for a logical volume""" pass - def discover_volume(self, volume): + def discover_volume(self, context, volume): """Discover volume on a remote host""" - # NOTE(justinsb): This is messed up... discover_volume takes 3 args - # but then that would break local_path return "rbd:%s/%s" % (FLAGS.rbd_pool, volume['name']) def undiscover_volume(self, volume): -- cgit From 07c1f30225fb27cbc8e7cfeebc6a73ec67a7f2e5 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 12 Apr 2011 09:41:42 +0200 Subject: pep8 --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 83c894bea..5e81878c4 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -144,7 +144,7 @@ class CloudController(object): return None # This ensures that all attributes of the instance - # are populated. + # are populated. instance_ref = db.instance_get(ctxt, instance_ref['id']) mpi = self._get_mpi_data(ctxt, instance_ref['project_id']) -- cgit From 70c7558b54b693872af09772ae310d893b334dff Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 12 Apr 2011 11:21:29 -0500 Subject: Only warn about rouge instances that compute should know about. --- nova/compute/manager.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 68b163355..39d7af9c1 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -1111,6 +1111,9 @@ class ComputeManager(manager.SchedulerDependentManager): # Are there VMs not in the DB? for vm_not_found_in_db in vms_not_found_in_db: name = vm_not_found_in_db - # TODO(justinsb): What to do here? Adopt it? Shut it down? - LOG.warning(_("Found VM not in DB: '%(name)s'. Ignoring") - % locals()) + + # We only care about instances that compute *should* know about + if name.startswith("instance-"): + # TODO(justinsb): What to do here? Adopt it? Shut it down? + LOG.warning(_("Found VM not in DB: '%(name)s'. Ignoring") + % locals()) -- cgit From baa129773c41f143237db992d90e1c681b3d33f8 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 12 Apr 2011 13:47:45 -0400 Subject: dots. --- nova/api/openstack/server_metadata.py | 2 +- nova/compute/api.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index 01ca79eec..fd64ee4fb 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -88,7 +88,7 @@ class Controller(common.OpenstackController): self.compute_api.delete_instance_metadata(context, server_id, id) def _handle_quota_error(self, error): - """Reraise quota errors as api-specific http exceptions""" + """Reraise quota errors as api-specific http exceptions.""" if error.code == "MetadataLimitExceeded": raise exc.HTTPBadRequest(explanation=error.message) raise error diff --git a/nova/compute/api.py b/nova/compute/api.py index f237f994c..0c7e8f84e 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -105,7 +105,7 @@ class API(base.Base): raise quota.QuotaError(code="OnsetFileContentLimitExceeded") def _check_metadata_properties_quota(self, context, metadata={}): - """Enforce quota limits on metadata properties""" + """Enforce quota limits on metadata properties.""" num_metadata = len(metadata) quota_metadata = quota.allowed_metadata_items(context, num_metadata) if quota_metadata < num_metadata: -- cgit From 76bb9f42c6cc39218824332e396dca4a5e6ec351 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 12 Apr 2011 18:43:49 +0000 Subject: fix show_by_name in s3.py and give a helpful error message if image lookup fails --- nova/api/ec2/cloud.py | 5 ++++- nova/image/s3.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 5e81878c4..10b1d0ac5 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -908,7 +908,10 @@ class CloudController(object): internal_id = ec2utils.ec2_id_to_id(ec2_id) return self.image_service.show(context, internal_id) except exception.NotFound: - return self.image_service.show_by_name(context, ec2_id) + try: + return self.image_service.show_by_name(context, ec2_id) + except exception.NotFound: + raise exception.NotFound(_('Image %s not found') % ec2_id) def _format_image(self, image): """Convert from format defined by BaseImageService to S3 format.""" diff --git a/nova/image/s3.py b/nova/image/s3.py index 554760d53..b1034d151 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -75,7 +75,7 @@ class S3ImageService(service.BaseImageService): return self.service.show(context, image_id) def show_by_name(self, context, name): - return self.service.show(context, name) + return self.service.show_by_name(context, name) @staticmethod def _conn(context): -- cgit From acfa9d4e3ae2185a0d6d9afdddf3e8a2e7f6f398 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 12 Apr 2011 14:43:07 -0700 Subject: Make VMWare Connection inherit from ComputeDriver --- nova/virt/vmwareapi_conn.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py index 20c1b2b45..1c6d2572d 100644 --- a/nova/virt/vmwareapi_conn.py +++ b/nova/virt/vmwareapi_conn.py @@ -42,6 +42,7 @@ from nova import exception from nova import flags from nova import log as logging from nova import utils +from nova.virt import driver from nova.virt.vmwareapi import error_util from nova.virt.vmwareapi import vim from nova.virt.vmwareapi import vim_util @@ -104,11 +105,12 @@ def get_connection(_): api_retry_count) -class VMWareESXConnection(object): +class VMWareESXConnection(driver.ComputeDriver): """The ESX host connection object.""" def __init__(self, host_ip, host_username, host_password, api_retry_count, scheme="https"): + super(VMWareESXConnection, self).__init__() session = VMWareAPISession(host_ip, host_username, host_password, api_retry_count, scheme=scheme) self._vmops = VMWareVMOps(session) -- cgit From 822ec6fe3075bed4479c8e48a984bd4c9622ffe1 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Tue, 12 Apr 2011 16:46:18 -0500 Subject: move from try_execute to _execute --- nova/volume/driver.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/volume/driver.py b/nova/volume/driver.py index 0c4086039..6d86f193f 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -111,10 +111,10 @@ class VolumeDriver(object): # zero out old volumes to prevent data leaking between users # TODO(ja): reclaiming space should be done lazy and low priority - self._try_execute('sudo', 'dd', 'if=/dev/zero', - 'of=%s' % self.local_path(volume), - 'count=%d' % (volume['size'] * 1024), - 'bs=1M') + self._execute('sudo', 'dd', 'if=/dev/zero', + 'of=%s' % self.local_path(volume), + 'count=%d' % (volume['size'] * 1024), + 'bs=1M') self._try_execute('sudo', 'lvremove', '-f', "%s/%s" % (FLAGS.volume_group, volume['name'])) -- cgit From b64af9a52d9093c01d9e5df52e7ced877f6ad9a3 Mon Sep 17 00:00:00 2001 From: Thierry Carrez Date: Wed, 13 Apr 2011 10:33:56 +0200 Subject: Final versioning --- nova/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/version.py b/nova/version.py index c3ecc2245..75d4d69c9 100644 --- a/nova/version.py +++ b/nova/version.py @@ -24,7 +24,7 @@ except ImportError: NOVA_VERSION = ['2011', '2'] YEAR, COUNT = NOVA_VERSION -FINAL = False # This becomes true at Release Candidate time +FINAL = True # This becomes true at Release Candidate time def canonical_version_string(): -- cgit From 899e6607086f6df9442f588aae4f3c37367e696d Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Wed, 13 Apr 2011 11:31:28 -0500 Subject: re-add broken code --- nova/volume/driver.py | 10 ++++++++-- nova/volume/san.py | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/volume/driver.py b/nova/volume/driver.py index 6d86f193f..15b6b550b 100644 --- a/nova/volume/driver.py +++ b/nova/volume/driver.py @@ -93,7 +93,10 @@ class VolumeDriver(object): def create_volume(self, volume): """Creates a logical volume. Can optionally return a Dictionary of changes to the volume object to be persisted.""" - sizestr = '%sG' % volume['size'] + if int(volume['size']) == 0: + sizestr = '100M' + else: + sizestr = '%sG' % volume['size'] self._try_execute('sudo', 'lvcreate', '-L', sizestr, '-n', volume['name'], FLAGS.volume_group) @@ -599,7 +602,10 @@ class SheepdogDriver(VolumeDriver): def create_volume(self, volume): """Creates a sheepdog volume""" - sizestr = '%sG' % volume['size'] + if int(volume['size']) == 0: + sizestr = '100M' + else: + sizestr = '%sG' % volume['size'] self._try_execute('qemu-img', 'create', "sheepdog:%s" % volume['name'], sizestr) diff --git a/nova/volume/san.py b/nova/volume/san.py index dd332de1a..9532c8116 100644 --- a/nova/volume/san.py +++ b/nova/volume/san.py @@ -219,7 +219,10 @@ class SolarisISCSIDriver(SanISCSIDriver): def create_volume(self, volume): """Creates a volume.""" - sizestr = '%sG' % volume['size'] + if int(volume['size']) == 0: + sizestr = '100M' + else: + sizestr = '%sG' % volume['size'] zfs_poolname = self._build_zfs_poolname(volume) @@ -486,7 +489,10 @@ class HpSanISCSIDriver(SanISCSIDriver): #TODO(justinsb): Should we default to inheriting thinProvision? cliq_args['thinProvision'] = '1' if FLAGS.san_thin_provision else '0' cliq_args['volumeName'] = volume['name'] - cliq_args['size'] = '%sGB' % volume['size'] + if int(volume['size']) == 0: + cliq_args['size'] = '100MB' + else: + cliq_args['size'] = '%sGB' % volume['size'] self._cliq_run_xml("createVolume", cliq_args) -- cgit From eda350a605b5711b8373849f389e3fe472670ca0 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Wed, 13 Apr 2011 13:35:32 -0500 Subject: Don't hammer on the DB --- nova/virt/libvirt_conn.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 6ec15fbb8..94410003e 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -372,6 +372,9 @@ class LibvirtConnection(driver.ComputeDriver): instance['id'], state) if state == power_state.SHUTOFF: break + + # Let's not hammer on the DB + time.sleep(1) except Exception as ex: msg = _("Error encountered when destroying instance '%(id)s': " "%(ex)s") % {"id": instance["id"], "ex": ex} -- cgit From 3d72f59530b1c974dca498fbca44e5720547fc61 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Wed, 13 Apr 2011 11:51:03 -0700 Subject: fixed error message i18n-ization. added test. --- nova/compute/instance_types.py | 8 ++++---- nova/tests/test_instance_types.py | 10 ++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index 70b43540f..158cf1e9d 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -59,10 +59,10 @@ def create(name, memory, vcpus, local_gb, flavorid, swap=0, rxtx_quota=rxtx_quota, rxtx_cap=rxtx_cap)) except exception.DBError, e: - LOG.exception(_('DB error: %s' % e)) - raise exception.ApiError( - _("Cannot create instance_type with name %s and flavorid %s"\ - % (name, flavorid))) + LOG.exception(_('DB error: %s') % e) + raise exception.ApiError(_("Cannot create instance_type with\ + name %(name)s and flavorid %(flavorid)s") % + locals()) def destroy(name): diff --git a/nova/tests/test_instance_types.py b/nova/tests/test_instance_types.py index 5d6d5e1f4..ec3bc5bbf 100644 --- a/nova/tests/test_instance_types.py +++ b/nova/tests/test_instance_types.py @@ -88,3 +88,13 @@ class InstanceTypeTestCase(test.TestCase): """Ensures that instance type creation fails with invalid args""" self.assertRaises(exception.ApiError, instance_types.destroy, "sfsfsdfdfs") + + def test_repeated_inst_types_should_raise_api_error(self): + """Ensures that instance duplicates raises ApiError""" + new_name = self.name + "dup" + instance_types.create(new_name, 256, 1, 120, self.flavorid + 1) + instance_types.destroy(new_name) + self.assertRaises( + exception.ApiError, + instance_types.create, new_name, 256, 1, 120, self.flavorid) + \ No newline at end of file -- cgit From 6a20cba0ea3c1e9945897ec27646d74d597492d7 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Wed, 13 Apr 2011 12:01:59 -0700 Subject: pep8 --- nova/tests/test_instance_types.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/test_instance_types.py b/nova/tests/test_instance_types.py index ec3bc5bbf..dd7d0737e 100644 --- a/nova/tests/test_instance_types.py +++ b/nova/tests/test_instance_types.py @@ -97,4 +97,3 @@ class InstanceTypeTestCase(test.TestCase): self.assertRaises( exception.ApiError, instance_types.create, new_name, 256, 1, 120, self.flavorid) - \ No newline at end of file -- cgit From db755b38609f5c94b70f88057d0b2f0f4964744e Mon Sep 17 00:00:00 2001 From: "jaypipes@gmail.com" <> Date: Wed, 13 Apr 2011 18:32:43 -0400 Subject: Rework GlanceImageService._translate_base() to not call BaseImageService._translate_base() otherwise the wrong class attributes are used in properties construction... --- nova/image/glance.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/image/glance.py b/nova/image/glance.py index bf49ca96c..1a80bb2af 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -186,7 +186,8 @@ class GlanceImageService(service.BaseImageService): """Overriding the base translation to handle conversion to datetime objects """ - image_meta = service.BaseImageService._translate_to_base(image_meta) + image_meta = service.BaseImageService._propertify_metadata( + image_meta, cls.SERVICE_IMAGE_ATTRS) image_meta = _convert_timestamps_to_datetimes(image_meta) return image_meta -- cgit From ba69d58d21a6164626835e5dd7f45f75dfca07bd Mon Sep 17 00:00:00 2001 From: Yoshiaki Tamura Date: Thu, 14 Apr 2011 21:38:55 +0900 Subject: Fix parameter mismatch calling to_xml() from spawn() in libvirt_conn.py Insert 'False' between instance and network_info. --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 94410003e..72896bb20 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -606,7 +606,7 @@ class LibvirtConnection(driver.ComputeDriver): # for xenapi(tr3buchet) @exception.wrap_exception def spawn(self, instance, network_info=None): - xml = self.to_xml(instance, network_info) + xml = self.to_xml(instance, False, network_info) db.instance_set_state(context.get_admin_context(), instance['id'], power_state.NOSTATE, -- cgit From 1b460de2f881d3cda0fd58bacedc3886020e4ca7 Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Thu, 14 Apr 2011 17:12:54 +0400 Subject: bugfix --- nova/compute/manager.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index af3551708..63d374326 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -226,15 +226,6 @@ class ComputeManager(manager.SchedulerDependentManager): self.network_manager.setup_compute_network(context, instance_id) - if FLAGS.auto_assign_floating_ip: - public_ip = rpc.call(context, - FLAGS.network_topic, - {"method": "allocate_floating_ip", - "args": {"project_id": context.project_id}}) - self.network_manager.associate_floating_ip(context, - instance_id=instance_id, - address=public_ip) - # TODO(vish) check to make sure the availability zone matches self.db.instance_set_state(context, instance_id, @@ -255,6 +246,16 @@ class ComputeManager(manager.SchedulerDependentManager): instance_id, power_state.SHUTDOWN) + if not FLAGS.stub_network: + if FLAGS.auto_assign_floating_ip: + public_ip = rpc.call(context, + FLAGS.network_topic, + {"method": "allocate_floating_ip", + "args": {"project_id": context.project_id}}) + self.network_manager.associate_floating_ip(context, + floating_address=public_ip, + fixed_address=address) + self._update_state(context, instance_id) @exception.wrap_exception -- cgit From 76e643dc0b6b8b6e2ad499034f4d4491380e91ba Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Thu, 14 Apr 2011 21:23:40 +0400 Subject: bugfix --- nova/compute/manager.py | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 63d374326..94fee36a5 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -252,9 +252,34 @@ class ComputeManager(manager.SchedulerDependentManager): FLAGS.network_topic, {"method": "allocate_floating_ip", "args": {"project_id": context.project_id}}) - self.network_manager.associate_floating_ip(context, - floating_address=public_ip, - fixed_address=address) + + fixed_ip = self.db.fixed_ip_get_by_address(context, address) + floating_ip = self.db.floating_ip_get_by_address(context, + public_ip) + # Check if the floating ip address is allocated + if floating_ip['project_id'] is None: + raise exception.Error(_("Address (%s) is not allocated") % + floating_ip['address']) + # Check if the floating ip address is allocated + # to the same project + if floating_ip['project_id'] != context.project_id: + LOG.warn(_("Address (%(address)s) is not allocated to your" + " project (%(project)s)"), + {'address': floating_ip['address'], + 'project': context.project_id}) + raise exception.Error(_("Address (%(address)s) is not " + "allocated to your project" + "(%(project)s)") % + {'address': floating_ip['address'], + 'project': context.project_id}) + + host = fixed_ip['network']['host'] + rpc.cast(context, + self.db.queue_get_for(context, + FLAGS.network_topic, host), + {"method": "associate_floating_ip", + "args": {"floating_address": floating_ip['address'], + "fixed_address": fixed_ip['address']}}) self._update_state(context, instance_id) -- cgit From e152c5d06a2ba4004b6d2a3c6517c43069d3713f Mon Sep 17 00:00:00 2001 From: Thierry Carrez Date: Fri, 15 Apr 2011 11:39:08 +0200 Subject: Diablo versioning --- nova/version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/version.py b/nova/version.py index 75d4d69c9..c43d12cf8 100644 --- a/nova/version.py +++ b/nova/version.py @@ -21,10 +21,10 @@ except ImportError: 'revision_id': 'LOCALREVISION', 'revno': 0} -NOVA_VERSION = ['2011', '2'] +NOVA_VERSION = ['2011', '3'] YEAR, COUNT = NOVA_VERSION -FINAL = True # This becomes true at Release Candidate time +FINAL = False # This becomes true at Release Candidate time def canonical_version_string(): -- cgit From b571bf6bb329e3bb085987554461c411ef56b330 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Fri, 15 Apr 2011 15:01:17 -0400 Subject: Explicitly tell a user that they need to authenticate against a version root. --- nova/api/openstack/auth.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index 42c23785a..311e6bde9 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -87,9 +87,10 @@ class AuthMiddleware(wsgi.Middleware): # honor it path_info = req.path_info if len(path_info) > 1: - msg = _("Authentication requests must be made against /") + msg = _("Authentication requests must be made against a version " + "root (e.g. /v1.0 or /v1.1).") LOG.warn(msg) - return faults.Fault(webob.exc.HTTPUnauthorized()) + return faults.Fault(webob.exc.HTTPUnauthorized(explanation=msg)) try: username = req.headers['X-Auth-User'] -- cgit From 7080cbe4d5d3e963dac21a51cb7e9819ec03a27b Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Fri, 15 Apr 2011 15:36:52 -0400 Subject: Added period to docstring for metadata test. --- nova/tests/integrated/test_servers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/integrated/test_servers.py b/nova/tests/integrated/test_servers.py index 8175659a3..e89d0100a 100644 --- a/nova/tests/integrated/test_servers.py +++ b/nova/tests/integrated/test_servers.py @@ -135,7 +135,7 @@ class ServersTest(integrated_helpers._IntegratedTestBase): self.assertFalse(found_server) def test_create_server_with_metadata(self): - """Creates a server with metadata""" + """Creates a server with metadata.""" # Build the server data gradually, checking errors along the way server = self._build_minimal_create_server_request() -- cgit From 25d95c9f9ba0000773902186a5838fbe57a25a8c Mon Sep 17 00:00:00 2001 From: termie Date: Sat, 16 Apr 2011 20:23:06 -0700 Subject: change libvirt snapshot to new style execute --- nova/virt/libvirt_conn.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index ccfce39e4..7f25a8503 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -58,7 +58,6 @@ from nova import db from nova import exception from nova import flags from nova import log as logging -#from nova import test from nova import utils from nova import vnc from nova.auth import manager @@ -499,12 +498,17 @@ class LibvirtConnection(driver.ComputeDriver): # Export the snapshot to a raw image temp_dir = tempfile.mkdtemp() out_path = os.path.join(temp_dir, snapshot_name) - qemu_img_cmd = '%s convert -f qcow2 -O raw -s %s %s %s' % ( - FLAGS.qemu_img, - snapshot_name, - disk_path, - out_path) - utils.execute(qemu_img_cmd) + qemu_img_cmd = (FLAGS.qemu_img, + 'convert', + '-f', + 'qcow2', + '-O', + 'raw', + '-s', + snapshot_name, + disk_path, + out_path) + utils.execute(*qemu_img_cmd) # Upload that image to the image service with open(out_path) as image_file: -- cgit From dbb0ff6b7720d4715d26b470f0ee39f27b1e187c Mon Sep 17 00:00:00 2001 From: termie Date: Sat, 16 Apr 2011 20:23:06 -0700 Subject: move name into main metadata instead of properties --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 7f25a8503..d62cb5224 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -470,8 +470,8 @@ class LibvirtConnection(driver.ComputeDriver): metadata = {'disk_format': base['disk_format'], 'container_format': base['container_format'], 'is_public': False, + 'name': '%s.%s' % (base['name'], image_id), 'properties': {'architecture': base['architecture'], - 'name': '%s.%s' % (base['name'], image_id), 'kernel_id': instance['kernel_id'], 'image_location': 'snapshot', 'image_state': 'available', -- cgit From c49c372ec3e94331eb8a16a0af7c9c9c5e46bba0 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Mon, 18 Apr 2011 17:06:18 +0400 Subject: Fix logging in openstack api --- nova/api/openstack/common.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 234f921ab..2ace9b3de 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -25,7 +25,7 @@ from nova import log as logging from nova import wsgi -LOG = logging.getLogger('common') +LOG = logging.getLogger('nova.api.openstack.common') FLAGS = flags.FLAGS @@ -116,8 +116,14 @@ def get_image_id_from_image_hash(image_service, context, image_hash): items = image_service.index(context) for image in items: image_id = image['id'] - if abs(hash(image_id)) == int(image_hash): - return image_id + try: + if abs(hash(image_id)) == int(image_hash): + return image_id + except ValueError: + msg = _("Requested image_id has wrong format: %s," + "should have numerical format" % image_id) + LOG.error(msg) + raise msg raise exception.NotFound(image_hash) -- cgit From a582afce13286160411a65d4b1b91e69f67ab430 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Mon, 18 Apr 2011 17:31:29 +0400 Subject: Fix logging in openstack api --- nova/api/openstack/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 2ace9b3de..f3f7f6d9b 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -121,7 +121,7 @@ def get_image_id_from_image_hash(image_service, context, image_hash): return image_id except ValueError: msg = _("Requested image_id has wrong format: %s," - "should have numerical format" % image_id) + "should have numerical format") % image_id LOG.error(msg) raise msg raise exception.NotFound(image_hash) -- cgit From d1a7cf94f368e0c115bd7680512c582163f5e49e Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Mon, 18 Apr 2011 17:32:48 +0400 Subject: Fix logging in openstack api --- nova/api/openstack/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index f3f7f6d9b..0b6dc944a 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -123,7 +123,7 @@ def get_image_id_from_image_hash(image_service, context, image_hash): msg = _("Requested image_id has wrong format: %s," "should have numerical format") % image_id LOG.error(msg) - raise msg + raise Exception(msg) raise exception.NotFound(image_hash) -- cgit From fe2d43472548f7c32a621ab4f245e078d0f46f0b Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Mon, 18 Apr 2011 18:13:56 +0400 Subject: add fault as response --- nova/api/openstack/servers.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 43e0c7963..4fb0f078b 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -127,8 +127,13 @@ class Controller(common.OpenstackController): key_data = key_pair['public_key'] requested_image_id = self._image_id_from_req_data(env) - image_id = common.get_image_id_from_image_hash(self._image_service, - context, requested_image_id) + try: + image_id = common.get_image_id_from_image_hash(self._image_service, + context, requested_image_id) + except: + msg = _("Can not find requested image") + return faults.Fault(exc.HTTPBadRequest(msg)) + kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image( req, image_id) -- cgit From c8ca373cfc71cf62d79ff90957961e9b0aa2ed36 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Mon, 18 Apr 2011 19:36:19 +0400 Subject: pep8 fix --- nova/api/openstack/servers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 4fb0f078b..d3f51df0d 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -133,7 +133,7 @@ class Controller(common.OpenstackController): except: msg = _("Can not find requested image") return faults.Fault(exc.HTTPBadRequest(msg)) - + kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image( req, image_id) -- cgit From 9a0d079cfe28d6d8d4e909f68541efda5ad3a3c5 Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Mon, 18 Apr 2011 21:06:29 +0400 Subject: not performing floating ip operation with auto allocated ips --- nova/compute/manager.py | 3 ++- nova/db/sqlalchemy/api.py | 2 ++ nova/db/sqlalchemy/models.py | 1 + nova/network/api.py | 6 ++++++ 4 files changed, 11 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 94fee36a5..829d59170 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -308,7 +308,8 @@ class ComputeManager(manager.SchedulerDependentManager): network_topic, {"method": "disassociate_floating_ip", "args": {"floating_address": address}}) - if FLAGS.auto_assign_floating_ip: + if FLAGS.auto_assign_floating_ip \ + and floating_ip.get('auto_assigned'): LOG.debug(_("Deallocating floating ip %s"), floating_ip['address'], context=context) rpc.cast(context, diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 28126a517..6b2caf46c 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -461,6 +461,7 @@ def floating_ip_count_by_project(context, project_id): session = get_session() return session.query(models.FloatingIp).\ filter_by(project_id=project_id).\ + filter_by(auto_assigned=False).\ filter_by(deleted=False).\ count() @@ -560,6 +561,7 @@ def floating_ip_get_all_by_project(context, project_id): return session.query(models.FloatingIp).\ options(joinedload_all('fixed_ip.instance')).\ filter_by(project_id=project_id).\ + filter_by(auto_assigned=False).\ filter_by(deleted=False).\ all() diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index f79d0f16c..36a084a1d 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -592,6 +592,7 @@ class FloatingIp(BASE, NovaBase): 'FloatingIp.deleted == False)') project_id = Column(String(255)) host = Column(String(255)) # , ForeignKey('hosts.id')) + auto_assigned = Column(Boolean, default=False, nullable=False) class ConsolePool(BASE, NovaBase): diff --git a/nova/network/api.py b/nova/network/api.py index c56e3062b..c5f76a14e 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -53,6 +53,8 @@ class API(base.Base): def release_floating_ip(self, context, address): floating_ip = self.db.floating_ip_get_by_address(context, address) + if floating_ip.get('auto_assigned'): + return # NOTE(vish): We don't know which network host should get the ip # when we deallocate, so just send it to any one. This # will probably need to move into a network supervisor @@ -66,6 +68,8 @@ class API(base.Base): if isinstance(fixed_ip, str) or isinstance(fixed_ip, unicode): fixed_ip = self.db.fixed_ip_get_by_address(context, fixed_ip) floating_ip = self.db.floating_ip_get_by_address(context, floating_ip) + if floating_ip.get('auto_assigned'): + return # Check if the floating ip address is allocated if floating_ip['project_id'] is None: raise exception.ApiError(_("Address (%s) is not allocated") % @@ -92,6 +96,8 @@ class API(base.Base): def disassociate_floating_ip(self, context, address): floating_ip = self.db.floating_ip_get_by_address(context, address) + if floating_ip.get('auto_assigned'): + return if not floating_ip.get('fixed_ip'): raise exception.ApiError('Address is not associated.') # NOTE(vish): Get the topic from the host name of the network of -- cgit From 841d25c1c9ab840ed39261a3bb234b981d9c337a Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Mon, 18 Apr 2011 22:02:12 +0400 Subject: pep8 fixed --- .../migrate_repo/versions/015_add_auto_assign_to_floating_ips.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py index 6829b5d6e..f3767b29f 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py @@ -33,4 +33,4 @@ def upgrade(migrate_engine): floating_ips = Table('floating_ips', meta, autoload=True, autoload_with=migrate_engine) - floating_ips.create_column(c_auto_assigned) \ No newline at end of file + floating_ips.create_column(c_auto_assigned) -- cgit From eb20dd53832577f94f5f251bd97e866435f6aeb9 Mon Sep 17 00:00:00 2001 From: Jason Koelker Date: Mon, 18 Apr 2011 15:40:16 -0500 Subject: Change '== None' to 'is None' --- nova/api/ec2/cloud.py | 4 ++-- nova/auth/manager.py | 8 ++++---- nova/compute/api.py | 2 +- nova/compute/instance_types.py | 4 ++-- nova/compute/monitor.py | 2 +- nova/image/local.py | 2 +- nova/image/s3.py | 2 +- nova/log.py | 2 +- nova/network/xenapi_net.py | 2 +- nova/tests/api/openstack/test_servers.py | 2 +- nova/tests/test_scheduler.py | 4 ++-- nova/virt/libvirt_conn.py | 4 ++-- 12 files changed, 19 insertions(+), 19 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 10b1d0ac5..bd4c9dcd4 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -442,7 +442,7 @@ class CloudController(object): group_name) criteria = self._revoke_rule_args_to_dict(context, **kwargs) - if criteria == None: + if criteria is None: raise exception.ApiError(_("Not enough parameters to build a " "valid rule.")) @@ -664,7 +664,7 @@ class CloudController(object): 'volumeId': ec2utils.id_to_ec2_id(volume_id, 'vol-%08x')} def _convert_to_set(self, lst, label): - if lst == None or lst == []: + if lst is None or lst == []: return None if not isinstance(lst, list): lst = [lst] diff --git a/nova/auth/manager.py b/nova/auth/manager.py index 486845399..8479c95a4 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -268,7 +268,7 @@ class AuthManager(object): LOG.debug(_('Looking up user: %r'), access_key) user = self.get_user_from_access_key(access_key) LOG.debug('user: %r', user) - if user == None: + if user is None: LOG.audit(_("Failed authorization for access key %s"), access_key) raise exception.NotFound(_('No user found for access key %s') % access_key) @@ -280,7 +280,7 @@ class AuthManager(object): project_id = user.name project = self.get_project(project_id) - if project == None: + if project is None: pjid = project_id uname = user.name LOG.audit(_("failed authorization: no project named %(pjid)s" @@ -646,9 +646,9 @@ class AuthManager(object): @rtype: User @return: The new user. """ - if access == None: + if access is None: access = str(uuid.uuid4()) - if secret == None: + if secret is None: secret = str(uuid.uuid4()) with self.driver() as drv: user_dict = drv.create_user(name, access, secret, admin) diff --git a/nova/compute/api.py b/nova/compute/api.py index e6146231c..48f8b7b0e 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -239,7 +239,7 @@ class API(base.Base): # Set sane defaults if not specified updates = dict(hostname=self.hostname_factory(instance_id)) if (not hasattr(instance, 'display_name') or - instance.display_name == None): + instance.display_name is None): updates['display_name'] = "Server %s" % instance_id instance = self.update(context, instance_id, **updates) diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index f893f8478..98b4425c8 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -61,7 +61,7 @@ def create(name, memory, vcpus, local_gb, flavorid, swap=0, def destroy(name): """Marks instance types as deleted.""" - if name == None: + if name is None: raise exception.InvalidInputException(_("No instance type specified")) else: try: @@ -73,7 +73,7 @@ def destroy(name): def purge(name): """Removes instance types from database.""" - if name == None: + if name is None: raise exception.InvalidInputException(_("No instance type specified")) else: try: diff --git a/nova/compute/monitor.py b/nova/compute/monitor.py index 04e08a235..afe5ddb1e 100644 --- a/nova/compute/monitor.py +++ b/nova/compute/monitor.py @@ -313,7 +313,7 @@ class Instance(object): LOG.debug('CPU: %d', self.cputime) # Skip calculation on first pass. Need delta to get a meaningful value. - if cputime_last_updated == None: + if cputime_last_updated is None: return None # Calculate the number of seconds between samples. diff --git a/nova/image/local.py b/nova/image/local.py index d4fd62156..fa5e93346 100644 --- a/nova/image/local.py +++ b/nova/image/local.py @@ -101,7 +101,7 @@ class LocalImageService(service.BaseImageService): if name == cantidate.get('name'): image = cantidate break - if image == None: + if image is None: raise exception.NotFound return image diff --git a/nova/image/s3.py b/nova/image/s3.py index b1034d151..2a02d4674 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -48,7 +48,7 @@ flags.DEFINE_string('image_decryption_dir', '/tmp', class S3ImageService(service.BaseImageService): """Wraps an existing image service to support s3 based register""" def __init__(self, service=None, *args, **kwargs): - if service == None: + if service is None: service = utils.import_object(FLAGS.image_service) self.service = service self.service.__init__(*args, **kwargs) diff --git a/nova/log.py b/nova/log.py index d194ab8f0..ea94be194 100644 --- a/nova/log.py +++ b/nova/log.py @@ -106,7 +106,7 @@ logging.addLevelName(AUDIT, 'AUDIT') def _dictify_context(context): - if context == None: + if context is None: return None if not isinstance(context, dict) \ and getattr(context, 'to_dict', None): diff --git a/nova/network/xenapi_net.py b/nova/network/xenapi_net.py index 9a99602d9..8c22a7d4b 100644 --- a/nova/network/xenapi_net.py +++ b/nova/network/xenapi_net.py @@ -47,7 +47,7 @@ def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None): network_ref = network_utils.NetworkHelper.find_network_with_name_label( session, bridge) - if network_ref == None: + if network_ref is None: # If bridge does not exists # 1 - create network description = "network for nova bridge %s" % bridge diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 34513734b..9d3352a1c 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -79,7 +79,7 @@ def stub_instance(id, user_id=1, private_address=None, public_addresses=None, inst_type = instance_types.get_instance_type_by_flavor_id(1) - if public_addresses == None: + if public_addresses is None: public_addresses = list() if host != None: diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py index ae56a1a16..51d987288 100644 --- a/nova/tests/test_scheduler.py +++ b/nova/tests/test_scheduler.py @@ -737,7 +737,7 @@ class SimpleDriverTestCase(test.TestCase): ret = self.scheduler.driver._live_migration_src_check(self.context, i_ref) - self.assertTrue(ret == None) + self.assertTrue(ret is None) db.instance_destroy(self.context, instance_id) db.service_destroy(self.context, s_ref['id']) @@ -805,7 +805,7 @@ class SimpleDriverTestCase(test.TestCase): ret = self.scheduler.driver._live_migration_dest_check(self.context, i_ref, 'somewhere') - self.assertTrue(ret == None) + self.assertTrue(ret is None) db.instance_destroy(self.context, instance_id) db.service_destroy(self.context, s_ref['id']) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index ccfce39e4..a405b43fe 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1116,7 +1116,7 @@ class LibvirtConnection(driver.ComputeDriver): if child.name == 'target': devdst = child.prop('dev') - if devdst == None: + if devdst is None: continue disks.append(devdst) @@ -1158,7 +1158,7 @@ class LibvirtConnection(driver.ComputeDriver): if child.name == 'target': devdst = child.prop('dev') - if devdst == None: + if devdst is None: continue interfaces.append(devdst) -- cgit From 65ca5ba31f2c5ecea05290390ad66e65543aa83d Mon Sep 17 00:00:00 2001 From: Jason Koelker Date: Mon, 18 Apr 2011 15:49:06 -0500 Subject: pep8 fixes --- nova/api/openstack/contrib/volumes.py | 3 +-- nova/image/fake.py | 3 +-- nova/tests/api/openstack/test_image_metadata.py | 6 ++---- nova/tests/api/openstack/test_server_metadata.py | 3 +-- nova/tests/api/openstack/test_versions.py | 6 ++---- nova/virt/vmwareapi/vim.py | 1 + 6 files changed, 8 insertions(+), 14 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/contrib/volumes.py b/nova/api/openstack/contrib/volumes.py index 6efacce52..18de2ec71 100644 --- a/nova/api/openstack/contrib/volumes.py +++ b/nova/api/openstack/contrib/volumes.py @@ -322,8 +322,7 @@ class Volumes(extensions.ExtensionDescriptor): # Does this matter? res = extensions.ResourceExtension('volumes', VolumeController(), - collection_actions={'detail': 'GET'} - ) + collection_actions={'detail': 'GET'}) resources.append(res) res = extensions.ResourceExtension('volume_attachments', diff --git a/nova/image/fake.py b/nova/image/fake.py index d1c62757f..e02b4127e 100644 --- a/nova/image/fake.py +++ b/nova/image/fake.py @@ -47,8 +47,7 @@ class FakeImageService(service.BaseImageService): 'container_format': 'ami', 'disk_format': 'raw', 'properties': {'kernel_id': FLAGS.null_kernel, - 'ramdisk_id': FLAGS.null_kernel} - } + 'ramdisk_id': FLAGS.null_kernel}} self.create(None, image) super(FakeImageService, self).__init__() diff --git a/nova/tests/api/openstack/test_image_metadata.py b/nova/tests/api/openstack/test_image_metadata.py index 543c59629..56be0f1cc 100644 --- a/nova/tests/api/openstack/test_image_metadata.py +++ b/nova/tests/api/openstack/test_image_metadata.py @@ -46,8 +46,7 @@ class ImageMetaDataTest(unittest.TestCase): 'deleted_at': None, 'properties': { 'key1': 'value1', - 'key2': 'value2' - }, + 'key2': 'value2'}, 'size': 5882349}, {'status': 'active', 'name': 'image2', @@ -62,8 +61,7 @@ class ImageMetaDataTest(unittest.TestCase): 'deleted_at': None, 'properties': { 'key1': 'value1', - 'key2': 'value2' - }, + 'key2': 'value2'}, 'size': 5882349}, {'status': 'active', 'name': 'image3', diff --git a/nova/tests/api/openstack/test_server_metadata.py b/nova/tests/api/openstack/test_server_metadata.py index c8d456472..680ff3e2c 100644 --- a/nova/tests/api/openstack/test_server_metadata.py +++ b/nova/tests/api/openstack/test_server_metadata.py @@ -48,8 +48,7 @@ def stub_server_metadata(): "key2": "value2", "key3": "value3", "key4": "value4", - "key5": "value5" - } + "key5": "value5"} return metadata diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 2640a4ddb..fd8d50904 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -47,8 +47,7 @@ class VersionsTest(test.TestCase): { "rel": "self", "href": "http://localhost/v1.1", - } - ], + }], }, { "id": "v1.0", @@ -57,8 +56,7 @@ class VersionsTest(test.TestCase): { "rel": "self", "href": "http://localhost/v1.0", - } - ], + }], }, ] self.assertEqual(versions, expected) diff --git a/nova/virt/vmwareapi/vim.py b/nova/virt/vmwareapi/vim.py index 159e16a80..0cbdba363 100644 --- a/nova/virt/vmwareapi/vim.py +++ b/nova/virt/vmwareapi/vim.py @@ -43,6 +43,7 @@ flags.DEFINE_string('vmwareapi_wsdl_loc', if suds: + class VIMMessagePlugin(suds.plugin.MessagePlugin): def addAttributeForValue(self, node): -- cgit From f59f792c83c7f18e48903165df8d3dd78f45dd4c Mon Sep 17 00:00:00 2001 From: Jason Koelker Date: Mon, 18 Apr 2011 15:53:09 -0500 Subject: use 'is not None' instead of '!= None' --- nova/api/ec2/apirequest.py | 2 +- nova/auth/dbdriver.py | 2 +- nova/compute/monitor.py | 2 +- nova/db/sqlalchemy/api.py | 2 +- nova/tests/api/openstack/test_servers.py | 2 +- nova/utils.py | 2 +- nova/virt/libvirt_conn.py | 12 ++++++------ 7 files changed, 12 insertions(+), 12 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/apirequest.py b/nova/api/ec2/apirequest.py index d7ad08d2f..6672e60bb 100644 --- a/nova/api/ec2/apirequest.py +++ b/nova/api/ec2/apirequest.py @@ -196,7 +196,7 @@ class APIRequest(object): elif isinstance(data, datetime.datetime): data_el.appendChild( xml.createTextNode(_database_to_isoformat(data))) - elif data != None: + elif data is not None: data_el.appendChild(xml.createTextNode(str(data))) return data_el diff --git a/nova/auth/dbdriver.py b/nova/auth/dbdriver.py index d1e3f2ed5..b2c580d83 100644 --- a/nova/auth/dbdriver.py +++ b/nova/auth/dbdriver.py @@ -115,7 +115,7 @@ class DbDriver(object): # on to create the project. This way we won't have to destroy # the project again because a user turns out to be invalid. members = set([manager]) - if member_uids != None: + if member_uids is not None: for member_uid in member_uids: member = db.user_get(context.get_admin_context(), member_uid) if not member: diff --git a/nova/compute/monitor.py b/nova/compute/monitor.py index 04e08a235..0b1a4c13f 100644 --- a/nova/compute/monitor.py +++ b/nova/compute/monitor.py @@ -260,7 +260,7 @@ class Instance(object): try: data = self.fetch_cpu_stats() - if data != None: + if data is not None: LOG.debug('CPU: %s', data) update_rrd(self, 'cpu', data) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index e675022e9..646675a45 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1835,7 +1835,7 @@ def security_group_get_by_instance(context, instance_id): def security_group_exists(context, project_id, group_name): try: group = security_group_get_by_name(context, project_id, group_name) - return group != None + return group is not None except exception.NotFound: return False diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 34513734b..d7525fca0 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -82,7 +82,7 @@ def stub_instance(id, user_id=1, private_address=None, public_addresses=None, if public_addresses == None: public_addresses = list() - if host != None: + if host is not None: host = str(host) instance = { diff --git a/nova/utils.py b/nova/utils.py index 3f6f9fc8a..76cba1a08 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -157,7 +157,7 @@ def execute(*cmd, **kwargs): stderr=subprocess.PIPE, env=env) result = None - if process_input != None: + if process_input is not None: result = obj.communicate(process_input) else: result = obj.communicate() diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 6ec15fbb8..bf5d0c00a 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -437,9 +437,9 @@ class LibvirtConnection(driver.ComputeDriver): if child.prop('dev') == device: return str(node) finally: - if ctx != None: + if ctx is not None: ctx.xpathFreeContext() - if doc != None: + if doc is not None: doc.freeDoc() @exception.wrap_exception @@ -1119,9 +1119,9 @@ class LibvirtConnection(driver.ComputeDriver): disks.append(devdst) finally: - if ctx != None: + if ctx is not None: ctx.xpathFreeContext() - if doc != None: + if doc is not None: doc.freeDoc() return disks @@ -1161,9 +1161,9 @@ class LibvirtConnection(driver.ComputeDriver): interfaces.append(devdst) finally: - if ctx != None: + if ctx is not None: ctx.xpathFreeContext() - if doc != None: + if doc is not None: doc.freeDoc() return interfaces -- cgit From d9628e8ba927074b6e80433de80d745b34acaa28 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Mon, 18 Apr 2011 17:00:39 -0500 Subject: First round of pylint cleanup. --- nova/compute/manager.py | 5 ----- nova/virt/xenapi/fake.py | 2 +- nova/virt/xenapi/vm_utils.py | 9 ++------- nova/virt/xenapi/vmops.py | 10 ++++------ 4 files changed, 7 insertions(+), 19 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 39d7af9c1..67d1eab5d 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -434,7 +434,6 @@ class ComputeManager(manager.SchedulerDependentManager): """Destroys the source instance""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) - migration_ref = self.db.migration_get(context, migration_id) self.driver.destroy(instance_ref) @exception.wrap_exception @@ -525,8 +524,6 @@ class ComputeManager(manager.SchedulerDependentManager): self.db.migration_update(context, migration_id, {'status': 'post-migrating', }) - service = self.db.service_get_by_host_and_topic(context, - migration_ref['dest_compute'], FLAGS.compute_topic) topic = self.db.queue_get_for(context, FLAGS.compute_topic, migration_ref['dest_compute']) rpc.cast(context, topic, @@ -652,7 +649,6 @@ class ComputeManager(manager.SchedulerDependentManager): """ context = context.elevated() - instance_ref = self.db.instance_get(context, instance_id) LOG.debug(_('instance %s: locking'), instance_id, context=context) self.db.instance_update(context, instance_id, {'locked': True}) @@ -664,7 +660,6 @@ class ComputeManager(manager.SchedulerDependentManager): """ context = context.elevated() - instance_ref = self.db.instance_get(context, instance_id) LOG.debug(_('instance %s: unlocking'), instance_id, context=context) self.db.instance_update(context, instance_id, {'locked': False}) diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index 4434dbf0b..e36ef3288 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -294,7 +294,7 @@ class Failure(Exception): def __str__(self): try: return str(self.details) - except Exception, exc: + except Exception: return "XenAPI Fake Failure: %s" % str(self.details) def _details_map(self): diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index d2045a557..1927500ad 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -28,10 +28,7 @@ import urllib import uuid from xml.dom import minidom -from eventlet import event import glance.client -from nova import context -from nova import db from nova import exception from nova import flags from nova import log as logging @@ -306,7 +303,6 @@ class VMHelper(HelperBase): % locals()) vm_vdi_ref, vm_vdi_rec = cls.get_vdi_for_vm_safely(session, vm_ref) - vm_vdi_uuid = vm_vdi_rec["uuid"] sr_ref = vm_vdi_rec["SR"] original_parent_uuid = get_vhd_parent_uuid(session, vm_vdi_ref) @@ -755,14 +751,14 @@ class VMHelper(HelperBase): session.call_xenapi('SR.scan', sr_ref) -def get_rrd(host, uuid): +def get_rrd(host, vm_uuid): """Return the VM RRD XML as a string""" try: xml = urllib.urlopen("http://%s:%s@%s/vm_rrd?uuid=%s" % ( FLAGS.xenapi_connection_username, FLAGS.xenapi_connection_password, host, - uuid)) + vm_uuid)) return xml.read() except IOError: return None @@ -1020,7 +1016,6 @@ def _stream_disk(dev, image_type, virtual_size, image_file): def _write_partition(virtual_size, dev): dest = '/dev/%s' % dev - mbr_last = MBR_SIZE_SECTORS - 1 primary_first = MBR_SIZE_SECTORS primary_last = MBR_SIZE_SECTORS + (virtual_size / SECTOR_SIZE) - 1 diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 7c7aa8e98..8b6a35f74 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -387,7 +387,6 @@ class VMOps(object): def link_disks(self, instance, base_copy_uuid, cow_uuid): """Links the base copy VHD to the COW via the XAPI plugin.""" - vm_ref = VMHelper.lookup(self._session, instance.name) new_base_copy_uuid = str(uuid.uuid4()) new_cow_uuid = str(uuid.uuid4()) params = {'instance_id': instance.id, @@ -760,7 +759,6 @@ class VMOps(object): instance))) for vm in rescue_vms: - rescue_name = vm["name"] rescue_vm_ref = vm["vm_ref"] self._destroy_rescue_instance(rescue_vm_ref) @@ -798,7 +796,7 @@ class VMOps(object): def _get_network_info(self, instance): """Creates network info list for instance.""" admin_context = context.get_admin_context() - IPs = db.fixed_ip_get_all_by_instance(admin_context, + ips = db.fixed_ip_get_all_by_instance(admin_context, instance['id']) networks = db.network_get_all_by_instance(admin_context, instance['id']) @@ -808,7 +806,7 @@ class VMOps(object): network_info = [] for network in networks: - network_IPs = [ip for ip in IPs if ip.network_id == network.id] + network_ips = [ip for ip in ips if ip.network_id == network.id] def ip_dict(ip): return { @@ -830,7 +828,7 @@ class VMOps(object): 'mac': instance.mac_address, 'rxtx_cap': inst_type['rxtx_cap'], 'dns': [network['dns']], - 'ips': [ip_dict(ip) for ip in network_IPs]} + 'ips': [ip_dict(ip) for ip in network_ips]} if network['cidr_v6']: info['ip6s'] = [ip6_dict()] if network['gateway_v6']: @@ -923,7 +921,7 @@ class VMOps(object): try: ret = self._make_xenstore_call('read_record', vm, path, {'ignore_missing_path': 'True'}) - except self.XenAPI.Failure, e: + except self.XenAPI.Failure: return None ret = json.loads(ret) if ret == "None": -- cgit From 50bd39e0413c2231ebdf9f4c9fb7e58d27624250 Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Tue, 19 Apr 2011 16:57:17 +0400 Subject: refractoring --- nova/compute/manager.py | 50 ++++++++++--------------------------------------- nova/network/api.py | 15 +++++++++------ 2 files changed, 19 insertions(+), 46 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index db97ae690..313b9e0e1 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -54,6 +54,7 @@ from nova import rpc from nova import utils from nova.compute import power_state from nova.virt import driver +from nova.network import api as network_api FLAGS = flags.FLAGS flags.DEFINE_string('instances_path', '$state_path/instances', @@ -134,6 +135,7 @@ class ComputeManager(manager.SchedulerDependentManager): self.network_manager = utils.import_object(FLAGS.network_manager) self.volume_manager = utils.import_object(FLAGS.volume_manager) + self.network_api = network_api.API() super(ComputeManager, self).__init__(service_name="compute", *args, **kwargs) @@ -248,39 +250,15 @@ class ComputeManager(manager.SchedulerDependentManager): if not FLAGS.stub_network: if FLAGS.auto_assign_floating_ip: - public_ip = rpc.call(context, - FLAGS.network_topic, - {"method": "allocate_floating_ip", - "args": {"project_id": context.project_id}}) + public_ip = self.network_api.allocate_floating_ip(context) fixed_ip = self.db.fixed_ip_get_by_address(context, address) floating_ip = self.db.floating_ip_get_by_address(context, public_ip) - # Check if the floating ip address is allocated - if floating_ip['project_id'] is None: - raise exception.Error(_("Address (%s) is not allocated") % - floating_ip['address']) - # Check if the floating ip address is allocated - # to the same project - if floating_ip['project_id'] != context.project_id: - LOG.warn(_("Address (%(address)s) is not allocated to your" - " project (%(project)s)"), - {'address': floating_ip['address'], - 'project': context.project_id}) - raise exception.Error(_("Address (%(address)s) is not " - "allocated to your project" - "(%(project)s)") % - {'address': floating_ip['address'], - 'project': context.project_id}) - - host = fixed_ip['network']['host'] - rpc.cast(context, - self.db.queue_get_for(context, - FLAGS.network_topic, host), - {"method": "associate_floating_ip", - "args": {"floating_address": floating_ip['address'], - "fixed_address": fixed_ip['address']}}) + self.network_api.associate_floating_ip(context, floating_ip, + fixed_ip, + affect_auto_assigned=True) self._update_state(context, instance_id) @exception.wrap_exception @@ -301,22 +279,14 @@ class ComputeManager(manager.SchedulerDependentManager): # NOTE(vish): Right now we don't really care if the ip is # disassociated. We may need to worry about # checking this later. - network_topic = self.db.queue_get_for(context, - FLAGS.network_topic, - floating_ip['host']) - rpc.cast(context, - network_topic, - {"method": "disassociate_floating_ip", - "args": {"floating_address": address}}) + self.network_api.disassociate_floating_ip(context, address, + affect_auto_assigned=True) if FLAGS.auto_assign_floating_ip \ and floating_ip.get('auto_assigned'): LOG.debug(_("Deallocating floating ip %s"), floating_ip['address'], context=context) - rpc.cast(context, - FLAGS.network_topic, - {"method": "deallocate_floating_ip", - "args": {"floating_address": - floating_ip['address']}}) + self.network_api.release_floating_ip(context, address, + affect_auto_assigned=True) address = fixed_ip['address'] if address: diff --git a/nova/network/api.py b/nova/network/api.py index c5f76a14e..61db646ae 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -51,9 +51,10 @@ class API(base.Base): {"method": "allocate_floating_ip", "args": {"project_id": context.project_id}}) - def release_floating_ip(self, context, address): + def release_floating_ip(self, context, address, + affect_auto_assigned = False): floating_ip = self.db.floating_ip_get_by_address(context, address) - if floating_ip.get('auto_assigned'): + if not affect_auto_assigned and floating_ip.get('auto_assigned'): return # NOTE(vish): We don't know which network host should get the ip # when we deallocate, so just send it to any one. This @@ -64,11 +65,12 @@ class API(base.Base): {"method": "deallocate_floating_ip", "args": {"floating_address": floating_ip['address']}}) - def associate_floating_ip(self, context, floating_ip, fixed_ip): + def associate_floating_ip(self, context, floating_ip, fixed_ip, + affect_auto_assigned = False): if isinstance(fixed_ip, str) or isinstance(fixed_ip, unicode): fixed_ip = self.db.fixed_ip_get_by_address(context, fixed_ip) floating_ip = self.db.floating_ip_get_by_address(context, floating_ip) - if floating_ip.get('auto_assigned'): + if not affect_auto_assigned and floating_ip.get('auto_assigned'): return # Check if the floating ip address is allocated if floating_ip['project_id'] is None: @@ -94,9 +96,10 @@ class API(base.Base): "args": {"floating_address": floating_ip['address'], "fixed_address": fixed_ip['address']}}) - def disassociate_floating_ip(self, context, address): + def disassociate_floating_ip(self, context, address, + affect_auto_assigned = False): floating_ip = self.db.floating_ip_get_by_address(context, address) - if floating_ip.get('auto_assigned'): + if not affect_auto_assigned and floating_ip.get('auto_assigned'): return if not floating_ip.get('fixed_ip'): raise exception.ApiError('Address is not associated.') -- cgit From 9812ae8d3c113475f8ef5d609874317d0b330425 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 19 Apr 2011 11:05:37 -0400 Subject: Removed extra calls in exception handling and standardized the way LoopingCalls are done. --- nova/virt/libvirt_conn.py | 186 ++++++++++++++++++++++++---------------------- 1 file changed, 98 insertions(+), 88 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index d212be3c9..4e96b4e97 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -154,8 +154,8 @@ def _get_net_and_prefixlen(cidr): def _get_ip_version(cidr): - net = IPy.IP(cidr) - return int(net.version()) + net = IPy.IP(cidr) + return int(net.version()) def _get_network_info(instance): @@ -359,28 +359,24 @@ class LibvirtConnection(driver.ComputeDriver): locals()) raise - # We'll save this for when we do shutdown, - # instead of destroy - but destroy returns immediately - timer = utils.LoopingCall(f=None) + def _wait_for_destroy(): + """Called at an interval until the VM is running again.""" + instance_name = insatnce['name'] - while True: try: - state = self.get_info(instance['name'])['state'] - db.instance_set_state(context.get_admin_context(), - instance['id'], state) - if state == power_state.SHUTOFF: - break - - # Let's not hammer on the DB - time.sleep(1) - except Exception as ex: - msg = _("Error encountered when destroying instance '%(id)s': " - "%(ex)s") % {"id": instance["id"], "ex": ex} - LOG.debug(msg) - db.instance_set_state(context.get_admin_context(), - instance['id'], - power_state.SHUTOFF) - break + state = self.get_info(instance_name)['state'] + except exception.NotFound: + msg = _("During destroy, %s disappeared.") % instance_name + LOG.error(msg) + raise utils.LoopingCallDone + + if state == power_state.SHUTOFF: + msg = _("Instance %s destroyed successfully.") % instance_name + LOG.debug(instance_name) + raise utils.LoopingCallDone + + timer = utils.LoopingCall(_wait_for_destroy) + timer.start(interval=0.5, now=True) self.firewall_driver.unfilter_instance(instance) @@ -522,6 +518,12 @@ class LibvirtConnection(driver.ComputeDriver): @exception.wrap_exception def reboot(self, instance): + """Reboot a virtual machine, given an instance reference. + + This method actually destroys and re-creates the domain to ensure the + reboot happens, as the guest OS cannot ignore this action. + + """ self.destroy(instance, False) xml = self.to_xml(instance) self.firewall_driver.setup_basic_filtering(instance) @@ -529,24 +531,23 @@ class LibvirtConnection(driver.ComputeDriver): self._create_new_domain(xml) self.firewall_driver.apply_instance_filter(instance) - timer = utils.LoopingCall(f=None) - def _wait_for_reboot(): + """Called at an interval until the VM is running again.""" + instance_name = insatnce['name'] + try: - state = self.get_info(instance['name'])['state'] - db.instance_set_state(context.get_admin_context(), - instance['id'], state) - if state == power_state.RUNNING: - LOG.debug(_('instance %s: rebooted'), instance['name']) - timer.stop() - except Exception, exn: - LOG.exception(_('_wait_for_reboot failed: %s'), exn) - db.instance_set_state(context.get_admin_context(), - instance['id'], - power_state.SHUTDOWN) - timer.stop() + state = self.get_info(instance_name)['state'] + except exception.NotFound: + msg = _("During reboot, %s disappeared.") % instance_name + LOG.error(msg) + raise utils.LoopingCallDone + + if state == power_state.RUNNING: + msg = _("Instance %s rebooted successfully.") % instance_name + LOG.debug(instance_name) + raise utils.LoopingCallDone - timer.f = _wait_for_reboot + timer = utils.LoopingCall(_wait_for_reboot) return timer.start(interval=0.5, now=True) @exception.wrap_exception @@ -566,7 +567,15 @@ class LibvirtConnection(driver.ComputeDriver): raise exception.ApiError("resume not supported for libvirt") @exception.wrap_exception - def rescue(self, instance, callback=None): + def rescue(self, instance): + """Loads a VM using rescue images. + + A rescue is normally performed when something goes wrong with the + primary images and data needs to be corrected/recovered. Rescuing + should not edit or over-ride the original image, only allow for + data recovery. + + """ self.destroy(instance, False) xml = self.to_xml(instance, rescue=True) @@ -576,29 +585,33 @@ class LibvirtConnection(driver.ComputeDriver): self._create_image(instance, xml, '.rescue', rescue_images) self._create_new_domain(xml) - timer = utils.LoopingCall(f=None) - def _wait_for_rescue(): + """Called at an interval until the VM is running again.""" + instance_name = instance['name'] + try: - state = self.get_info(instance['name'])['state'] - db.instance_set_state(None, instance['id'], state) - if state == power_state.RUNNING: - LOG.debug(_('instance %s: rescued'), instance['name']) - timer.stop() - except Exception, exn: - LOG.exception(_('_wait_for_rescue failed: %s'), exn) - db.instance_set_state(None, - instance['id'], - power_state.SHUTDOWN) - timer.stop() + state = self.get_info(instance_name)['state'] + except exception.NotFound: + msg = _("During reboot, %s disappeared.") % instance_name + LOG.error(msg) + raise utils.LoopingCallDone + + if state == power_state.RUNNING: + msg = _("Instance %s rescued successfully.") % instance_name + LOG.debug(instance_name) + raise utils.LoopingCallDone - timer.f = _wait_for_rescue + timer = utils.LoopingCall(_wait_for_rescue) return timer.start(interval=0.5, now=True) @exception.wrap_exception - def unrescue(self, instance, callback=None): - # NOTE(vish): Because reboot destroys and recreates an instance using - # the normal xml file, we can just call reboot here + def unrescue(self, instance): + """Reboot the VM which is being rescued back into primary images. + + Because reboot destroys and re-creates instances, unresue should + simply call reboot. + + """ self.reboot(instance) @exception.wrap_exception @@ -610,10 +623,6 @@ class LibvirtConnection(driver.ComputeDriver): @exception.wrap_exception def spawn(self, instance, network_info=None): xml = self.to_xml(instance, False, network_info) - db.instance_set_state(context.get_admin_context(), - instance['id'], - power_state.NOSTATE, - 'launching') self.firewall_driver.setup_basic_filtering(instance, network_info) self.firewall_driver.prepare_instance_filter(instance, network_info) self._create_image(instance, xml, network_info) @@ -626,25 +635,23 @@ class LibvirtConnection(driver.ComputeDriver): instance['name']) domain.setAutostart(1) - timer = utils.LoopingCall(f=None) - def _wait_for_boot(): + """Called at an interval until the VM is running.""" + instance_name = insatnce['name'] + try: - state = self.get_info(instance['name'])['state'] - db.instance_set_state(context.get_admin_context(), - instance['id'], state) - if state == power_state.RUNNING: - LOG.debug(_('instance %s: booted'), instance['name']) - timer.stop() - except: - LOG.exception(_('instance %s: failed to boot'), - instance['name']) - db.instance_set_state(context.get_admin_context(), - instance['id'], - power_state.SHUTDOWN) - timer.stop() + state = self.get_info(instance_name)['state'] + except exception.NotFound: + msg = _("During reboot, %s disappeared.") % instance_name + LOG.error(msg) + raise utils.LoopingCallDone - timer.f = _wait_for_boot + if state == power_state.RUNNING: + msg = _("Instance %s spawned successfully.") % instance_name + LOG.debug(instance_name) + raise utils.LoopingCallDone + + timer = utils.LoopingCall(_wait_for_boot) return timer.start(interval=0.5, now=True) def _flush_xen_console(self, virsh_output): @@ -1045,21 +1052,24 @@ class LibvirtConnection(driver.ComputeDriver): return xml def get_info(self, instance_name): - # NOTE(justinsb): When libvirt isn't running / can't connect, we get: - # libvir: Remote error : unable to connect to - # '/var/run/libvirt/libvirt-sock', libvirtd may need to be started: - # No such file or directory + """Retrieve information from libvirt for a specific instance name. + + If a libvirt error is encountered during lookup, we might raise a + NotFound exception or Error exception depending on how severe the + libvirt error is. + + """ try: virt_dom = self._conn.lookupByName(instance_name) - except libvirt.libvirtError as e: - errcode = e.get_error_code() - if errcode == libvirt.VIR_ERR_NO_DOMAIN: - raise exception.NotFound(_("Instance %s not found") - % instance_name) - LOG.warning(_("Error from libvirt during lookup. " - "Code=%(errcode)s Error=%(e)s") % - locals()) - raise + except libvirt.libvirtError as ex: + error_code = ex.get_error_code() + if error_code == libvirt.VIR_ERR_NO_DOMAIN: + msg = _("Instance %s not found") % instance_name + raise exception.NotFound(msg) + + msg = _("Error from libvirt while looking up %(instance_name)s: " + "[Error Code %(error_code)s] %(ex)s") % locals() + raise exception.Error(msg) (state, max_mem, mem, num_cpu, cpu_time) = virt_dom.info() return {'state': state, -- cgit From 8e98888323d4308640ab5061cdae5ccd4e3ebabf Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 19 Apr 2011 11:09:07 -0400 Subject: Pretty critical spelling error. --- nova/virt/libvirt_conn.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 4e96b4e97..fad8dd52a 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -361,7 +361,7 @@ class LibvirtConnection(driver.ComputeDriver): def _wait_for_destroy(): """Called at an interval until the VM is running again.""" - instance_name = insatnce['name'] + instance_name = instance['name'] try: state = self.get_info(instance_name)['state'] @@ -533,7 +533,7 @@ class LibvirtConnection(driver.ComputeDriver): def _wait_for_reboot(): """Called at an interval until the VM is running again.""" - instance_name = insatnce['name'] + instance_name = instance['name'] try: state = self.get_info(instance_name)['state'] @@ -637,7 +637,7 @@ class LibvirtConnection(driver.ComputeDriver): def _wait_for_boot(): """Called at an interval until the VM is running.""" - instance_name = insatnce['name'] + instance_name = instance['name'] try: state = self.get_info(instance_name)['state'] -- cgit From 3e31785d86c59dbda62e3a3ba3a1e23452e52562 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 19 Apr 2011 11:16:46 -0400 Subject: Tweak to destroy loop logic. --- nova/virt/libvirt_conn.py | 5 ----- 1 file changed, 5 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index fad8dd52a..53137395e 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -366,11 +366,6 @@ class LibvirtConnection(driver.ComputeDriver): try: state = self.get_info(instance_name)['state'] except exception.NotFound: - msg = _("During destroy, %s disappeared.") % instance_name - LOG.error(msg) - raise utils.LoopingCallDone - - if state == power_state.SHUTOFF: msg = _("Instance %s destroyed successfully.") % instance_name LOG.debug(instance_name) raise utils.LoopingCallDone -- cgit From ad2d97972d63f50500ec8215c7f8f04d87468060 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 19 Apr 2011 11:29:26 -0400 Subject: Fixed info messages. --- nova/virt/libvirt_conn.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 53137395e..13378bbd2 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -367,7 +367,7 @@ class LibvirtConnection(driver.ComputeDriver): state = self.get_info(instance_name)['state'] except exception.NotFound: msg = _("Instance %s destroyed successfully.") % instance_name - LOG.debug(instance_name) + LOG.info(msg) raise utils.LoopingCallDone timer = utils.LoopingCall(_wait_for_destroy) @@ -539,7 +539,7 @@ class LibvirtConnection(driver.ComputeDriver): if state == power_state.RUNNING: msg = _("Instance %s rebooted successfully.") % instance_name - LOG.debug(instance_name) + LOG.info(msg) raise utils.LoopingCallDone timer = utils.LoopingCall(_wait_for_reboot) @@ -593,7 +593,7 @@ class LibvirtConnection(driver.ComputeDriver): if state == power_state.RUNNING: msg = _("Instance %s rescued successfully.") % instance_name - LOG.debug(instance_name) + LOG.info(msg) raise utils.LoopingCallDone timer = utils.LoopingCall(_wait_for_rescue) @@ -643,7 +643,7 @@ class LibvirtConnection(driver.ComputeDriver): if state == power_state.RUNNING: msg = _("Instance %s spawned successfully.") % instance_name - LOG.debug(instance_name) + LOG.info(msg) raise utils.LoopingCallDone timer = utils.LoopingCall(_wait_for_boot) -- cgit From 25e1e2d64ad43638ad4231e6e6edd84d96e14bdb Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 19 Apr 2011 11:33:51 -0400 Subject: Merged trunk and fixed small comment. --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 13378bbd2..2582b9730 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -360,7 +360,7 @@ class LibvirtConnection(driver.ComputeDriver): raise def _wait_for_destroy(): - """Called at an interval until the VM is running again.""" + """Called at an interval until the VM is gone.""" instance_name = instance['name'] try: -- cgit From 745351d1e2a98a98de0a5f955385a92c01110684 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 19 Apr 2011 09:19:52 -0700 Subject: Fixes cloudpipe to get the proper ip address. * Changes FLAGS.vpn_image_id to integer * Converts to str when comparing because instance['image_id'] is a str * Removes unused method from db * Converts integer_id to ami when launching * Adds docs for setting up interface in cloudpipe image --- nova/api/ec2/admin.py | 2 +- nova/api/ec2/cloud.py | 4 ++-- nova/cloudpipe/pipelib.py | 2 ++ nova/compute/manager.py | 2 +- nova/db/api.py | 5 ----- nova/db/sqlalchemy/api.py | 9 +-------- nova/flags.py | 2 +- nova/virt/libvirt_conn.py | 2 +- 8 files changed, 9 insertions(+), 19 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index 6a5609d4a..ea94d9c1f 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -266,7 +266,7 @@ class AdminController(object): def _vpn_for(self, context, project_id): """Get the VPN instance for a project ID.""" for instance in db.instance_get_all_by_project(context, project_id): - if (instance['image_id'] == FLAGS.vpn_image_id + if (instance['image_id'] == str(FLAGS.vpn_image_id) and not instance['state_description'] in ['shutting_down', 'shutdown']): return instance diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 10b1d0ac5..d9c1af072 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -703,7 +703,7 @@ class CloudController(object): instances = self.compute_api.get_all(context, **kwargs) for instance in instances: if not context.is_admin: - if instance['image_id'] == FLAGS.vpn_image_id: + if instance['image_id'] == str(FLAGS.vpn_image_id): continue i = {} instance_id = instance['id'] @@ -898,7 +898,7 @@ class CloudController(object): return image_type @staticmethod - def _image_ec2_id(image_id, image_type='ami'): + def image_ec2_id(image_id, image_type='ami'): """Returns image ec2_id using id and three letter type.""" template = image_type + '-%08x' return ec2utils.id_to_ec2_id(int(image_id), template=template) diff --git a/nova/cloudpipe/pipelib.py b/nova/cloudpipe/pipelib.py index dc6f55af2..2c8912422 100644 --- a/nova/cloudpipe/pipelib.py +++ b/nova/cloudpipe/pipelib.py @@ -37,6 +37,7 @@ 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 +from nova.api FLAGS = flags.FLAGS @@ -101,6 +102,7 @@ class CloudPipe(object): key_name = self.setup_key_pair(ctxt) group_name = self.setup_security_group(ctxt) + ec2_id = self.controller.image_ec2_id(FLAGS.vpn_image_id) reservation = self.controller.run_instances(ctxt, user_data=self.get_encoded_zip(project_id), max_count=1, diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 39d7af9c1..05b168e13 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -209,7 +209,7 @@ class ComputeManager(manager.SchedulerDependentManager): power_state.NOSTATE, 'networking') - is_vpn = instance_ref['image_id'] == FLAGS.vpn_image_id + is_vpn = instance_ref['image_id'] == str(FLAGS.vpn_image_id) # NOTE(vish): This could be a cast because we don't do anything # with the address currently, but I'm leaving it as # a call to ensure that network setup completes. We diff --git a/nova/db/api.py b/nova/db/api.py index 63901e94d..030d2f434 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -455,11 +455,6 @@ def instance_get_project_vpn(context, project_id): return IMPL.instance_get_project_vpn(context, project_id) -def instance_is_vpn(context, instance_id): - """True if instance is a vpn.""" - return IMPL.instance_is_vpn(context, instance_id) - - def instance_set_state(context, instance_id, state, description=None): """Set the state of an instance.""" return IMPL.instance_set_state(context, instance_id, state, description) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index e675022e9..e9450e392 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -940,7 +940,7 @@ def instance_get_project_vpn(context, project_id): options(joinedload('security_groups')).\ options(joinedload('instance_type')).\ filter_by(project_id=project_id).\ - filter_by(image_id=FLAGS.vpn_image_id).\ + filter_by(image_id=str(FLAGS.vpn_image_id)).\ filter_by(deleted=can_read_deleted(context)).\ first() @@ -979,13 +979,6 @@ def instance_get_floating_address(context, instance_id): return instance_ref.fixed_ip.floating_ips[0]['address'] -@require_admin_context -def instance_is_vpn(context, instance_id): - # TODO(vish): Move this into image code somewhere - instance_ref = instance_get(context, instance_id) - return instance_ref['image_id'] == FLAGS.vpn_image_id - - @require_admin_context def instance_set_state(context, instance_id, state, description=None): # TODO(devcamcar): Move this out of models and into driver diff --git a/nova/flags.py b/nova/flags.py index f011ab383..760bcc37d 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -316,7 +316,7 @@ DEFINE_string('null_kernel', 'nokernel', 'kernel image that indicates not to use a kernel,' ' but to use a raw disk image instead') -DEFINE_string('vpn_image_id', 'ami-cloudpipe', 'AMI for cloudpipe vpn server') +DEFINE_integer('vpn_image_id', 0, 'integer id for cloudpipe vpn server') DEFINE_string('vpn_key_suffix', '-vpn', 'Suffix to add to project name for vpn key and secgroups') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 5da091920..9c8d64446 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1837,7 +1837,7 @@ class NWFilterFirewall(FirewallDriver): """ if not network_info: network_info = _get_network_info(instance) - if instance['image_id'] == FLAGS.vpn_image_id: + if instance['image_id'] == str(FLAGS.vpn_image_id): base_filter = 'nova-vpn' else: base_filter = 'nova-base' -- cgit From 7554ab7da290565ee457b2d42730a2bff2fd7861 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 19 Apr 2011 09:31:18 -0700 Subject: remove typo --- nova/cloudpipe/pipelib.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova') diff --git a/nova/cloudpipe/pipelib.py b/nova/cloudpipe/pipelib.py index 2c8912422..f4cb53da0 100644 --- a/nova/cloudpipe/pipelib.py +++ b/nova/cloudpipe/pipelib.py @@ -37,7 +37,6 @@ 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 -from nova.api FLAGS = flags.FLAGS -- cgit From 2d649fa8928e9682064613f2c984f53f492efbec Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 19 Apr 2011 09:32:33 -0700 Subject: actually use the ec2_id --- nova/cloudpipe/pipelib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/cloudpipe/pipelib.py b/nova/cloudpipe/pipelib.py index f4cb53da0..7844d31e1 100644 --- a/nova/cloudpipe/pipelib.py +++ b/nova/cloudpipe/pipelib.py @@ -107,7 +107,7 @@ class CloudPipe(object): max_count=1, min_count=1, instance_type='m1.tiny', - image_id=FLAGS.vpn_image_id, + image_id=ec2_id, key_name=key_name, security_group=[group_name]) -- cgit From 3e3f8e1f09d0615e66cc1be0b656d0d8e1d69671 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 19 Apr 2011 12:36:07 -0400 Subject: Abstracted lookupByName calls to _lookup_by_name for centralized error handling. --- nova/virt/libvirt_conn.py | 53 +++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 27 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 2582b9730..c1f62c391 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -309,19 +309,10 @@ class LibvirtConnection(driver.ComputeDriver): def destroy(self, instance, cleanup=True): instance_name = instance['name'] - # TODO(justinsb): Refactor all lookupByName calls for error-handling try: - virt_dom = self._conn.lookupByName(instance_name) - except libvirt.libvirtError as e: - errcode = e.get_error_code() - if errcode == libvirt.VIR_ERR_NO_DOMAIN: - virt_dom = None - else: - LOG.warning(_("Error from libvirt during lookup of " - "%(instance_name)s. Code=%(errcode)s " - "Error=%(e)s") % - locals()) - raise + virt_dom = self._lookup_by_name(instance_name) + except exception.NotFound: + virt_dom = None # If the instance is already terminated, we're still happy # Otherwise, destroy it @@ -392,7 +383,7 @@ class LibvirtConnection(driver.ComputeDriver): @exception.wrap_exception def attach_volume(self, instance_name, device_path, mountpoint): - virt_dom = self._conn.lookupByName(instance_name) + virt_dom = self._lookup_by_name(instance_name) mount_device = mountpoint.rpartition("/")[2] if device_path.startswith('/dev/'): xml = """ @@ -436,7 +427,7 @@ class LibvirtConnection(driver.ComputeDriver): @exception.wrap_exception def detach_volume(self, instance_name, mountpoint): - virt_dom = self._conn.lookupByName(instance_name) + virt_dom = self._lookup_by_name(instance_name) mount_device = mountpoint.rpartition("/")[2] xml = self._get_disk_xml(virt_dom.XMLDesc(0), mount_device) if not xml: @@ -453,7 +444,7 @@ class LibvirtConnection(driver.ComputeDriver): """ image_service = utils.import_object(FLAGS.image_service) - virt_dom = self._conn.lookupByName(instance['name']) + virt_dom = self._lookup_by_name(instance['name']) elevated = context.get_admin_context() base = image_service.show(elevated, instance['image_id']) @@ -712,7 +703,7 @@ class LibvirtConnection(driver.ComputeDriver): raise Exception(_('Unable to find an open port')) def get_pty_for_instance(instance_name): - virt_dom = self._conn.lookupByName(instance_name) + virt_dom = self._lookup_by_name(instance_name) xml = virt_dom.XMLDesc(0) dom = minidom.parseString(xml) @@ -737,7 +728,7 @@ class LibvirtConnection(driver.ComputeDriver): @exception.wrap_exception def get_vnc_console(self, instance): def get_vnc_port_for_instance(instance_name): - virt_dom = self._conn.lookupByName(instance_name) + virt_dom = self._lookup_by_name(instance_name) xml = virt_dom.XMLDesc(0) # TODO: use etree instead of minidom dom = minidom.parseString(xml) @@ -1046,16 +1037,15 @@ class LibvirtConnection(driver.ComputeDriver): instance['name']) return xml - def get_info(self, instance_name): - """Retrieve information from libvirt for a specific instance name. + def _lookup_by_name(self, instance_name): + """Retrieve libvirt domain object given an instance name. - If a libvirt error is encountered during lookup, we might raise a - NotFound exception or Error exception depending on how severe the - libvirt error is. + All libvirt error handling should be handled in this method and + relevant nova exceptions should be raised in response. """ try: - virt_dom = self._conn.lookupByName(instance_name) + return self._conn.lookupByName(instance_name) except libvirt.libvirtError as ex: error_code = ex.get_error_code() if error_code == libvirt.VIR_ERR_NO_DOMAIN: @@ -1066,6 +1056,15 @@ class LibvirtConnection(driver.ComputeDriver): "[Error Code %(error_code)s] %(ex)s") % locals() raise exception.Error(msg) + def get_info(self, instance_name): + """Retrieve information from libvirt for a specific instance name. + + If a libvirt error is encountered during lookup, we might raise a + NotFound exception or Error exception depending on how severe the + libvirt error is. + + """ + virt_dom = self._lookup_by_name(instance_name) (state, max_mem, mem, num_cpu, cpu_time) = virt_dom.info() return {'state': state, 'max_mem': max_mem, @@ -1102,7 +1101,7 @@ class LibvirtConnection(driver.ComputeDriver): Returns a list of all block devices for this domain. """ - domain = self._conn.lookupByName(instance_name) + domain = self._lookup_by_name(instance_name) # TODO(devcamcar): Replace libxml2 with etree. xml = domain.XMLDesc(0) doc = None @@ -1144,7 +1143,7 @@ class LibvirtConnection(driver.ComputeDriver): Returns a list of all network interfaces for this instance. """ - domain = self._conn.lookupByName(instance_name) + domain = self._lookup_by_name(instance_name) # TODO(devcamcar): Replace libxml2 with etree. xml = domain.XMLDesc(0) doc = None @@ -1359,7 +1358,7 @@ class LibvirtConnection(driver.ComputeDriver): Note that this function takes an instance name, not an Instance, so that it can be called by monitor. """ - domain = self._conn.lookupByName(instance_name) + domain = self._lookup_by_name(instance_name) return domain.blockStats(disk) def interface_stats(self, instance_name, interface): @@ -1367,7 +1366,7 @@ class LibvirtConnection(driver.ComputeDriver): Note that this function takes an instance name, not an Instance, so that it can be called by monitor. """ - domain = self._conn.lookupByName(instance_name) + domain = self._lookup_by_name(instance_name) return domain.interfaceStats(interface) def get_console_pool_info(self, console_type): -- cgit From c3a45962a322086e4d7339f980bcf61ee8bd3167 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 19 Apr 2011 09:38:01 -0700 Subject: rename all versions of image_ec2_id --- nova/api/ec2/cloud.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index d9c1af072..c48ec9004 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -159,7 +159,7 @@ class CloudController(object): floating_ip = db.instance_get_floating_address(ctxt, instance_ref['id']) ec2_id = ec2utils.id_to_ec2_id(instance_ref['id']) - image_ec2_id = self._image_ec2_id(instance_ref['image_id'], 'ami') + image_ec2_id = self.image_ec2_id(instance_ref['image_id']) data = { 'user-data': base64.b64decode(instance_ref['user_data']), 'meta-data': { @@ -188,8 +188,8 @@ class CloudController(object): for image_type in ['kernel', 'ramdisk']: if '%s_id' % image_type in instance_ref: - ec2_id = self._image_ec2_id(instance_ref['%s_id' % image_type], - self._image_type(image_type)) + ec2_id = self.image_ec2_id(instance_ref['%s_id' % image_type], + self._image_type(image_type)) data['meta-data']['%s-id' % image_type] = ec2_id if False: # TODO(vish): store ancestor ids @@ -709,7 +709,7 @@ class CloudController(object): instance_id = instance['id'] ec2_id = ec2utils.id_to_ec2_id(instance_id) i['instanceId'] = ec2_id - i['imageId'] = self._image_ec2_id(instance['image_id']) + i['imageId'] = self.image_ec2_id(instance['image_id']) i['instanceState'] = { 'code': instance['state'], 'name': instance['state_description']} @@ -917,15 +917,15 @@ class CloudController(object): """Convert from format defined by BaseImageService to S3 format.""" i = {} image_type = self._image_type(image.get('container_format')) - ec2_id = self._image_ec2_id(image.get('id'), image_type) + ec2_id = self.image_ec2_id(image.get('id'), image_type) name = image.get('name') i['imageId'] = ec2_id kernel_id = image['properties'].get('kernel_id') if kernel_id: - i['kernelId'] = self._image_ec2_id(kernel_id, 'aki') + i['kernelId'] = self.image_ec2_id(kernel_id, 'aki') ramdisk_id = image['properties'].get('ramdisk_id') if ramdisk_id: - i['ramdiskId'] = self._image_ec2_id(ramdisk_id, 'ari') + i['ramdiskId'] = self.image_ec2_id(ramdisk_id, 'ari') i['imageOwnerId'] = image['properties'].get('owner_id') if name: i['imageLocation'] = "%s (%s)" % (image['properties']. @@ -976,8 +976,8 @@ class CloudController(object): metadata = {'properties': {'image_location': image_location}} image = self.image_service.create(context, metadata) image_type = self._image_type(image.get('container_format')) - image_id = self._image_ec2_id(image['id'], - image_type) + image_id = self.image_ec2_id(image['id'], + image_type) msg = _("Registered image %(image_location)s with" " id %(image_id)s") % locals() LOG.audit(msg, context=context) -- cgit From 4442d8f7017868f64eacc6d8ad94620263b9a9c9 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 19 Apr 2011 10:20:56 -0700 Subject: make geninter.sh use the right tmpl file --- nova/CA/geninter.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/CA/geninter.sh b/nova/CA/geninter.sh index 4b7f5a55c..9b3ea3b76 100755 --- a/nova/CA/geninter.sh +++ b/nova/CA/geninter.sh @@ -21,7 +21,7 @@ NAME=$1 SUBJ=$2 mkdir -p projects/$NAME cd projects/$NAME -cp ../../openssl.cnf.tmpl openssl.cnf +cp "$(dirname $0)/openssl.cnf.tmpl" openssl.cnf sed -i -e s/%USERNAME%/$NAME/g openssl.cnf mkdir -p certs crl newcerts private openssl req -new -x509 -extensions v3_ca -keyout private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf -batch -nodes -- cgit From ccf9b2ccb41b1e7f946f2b2c21e6f8fbc9bd04e8 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Tue, 19 Apr 2011 21:25:53 +0400 Subject: fix logging in reboot OpenStack API --- nova/api/openstack/servers.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index f221229f0..e9f570213 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -40,7 +40,7 @@ import nova.api.openstack from nova.scheduler import api as scheduler_api -LOG = logging.getLogger('server') +LOG = logging.getLogger('nova.api.openstack.servers') FLAGS = flags.FLAGS @@ -331,6 +331,7 @@ class Controller(common.OpenstackController): return exc.HTTPAccepted() def _action_rebuild(self, input_dict, req, id): + LOG.debug(_("Rebuild server action is not implemented")) return faults.Fault(exc.HTTPNotImplemented()) def _action_resize(self, input_dict, req, id): @@ -346,18 +347,20 @@ class Controller(common.OpenstackController): except Exception, e: LOG.exception(_("Error in resize %s"), e) return faults.Fault(exc.HTTPBadRequest()) - return faults.Fault(exc.HTTPAccepted()) + return exc.HTTPAccepted() def _action_reboot(self, input_dict, req, id): - try: + if 'reboot' in input_dict and 'type' in input_dict['reboot']: reboot_type = input_dict['reboot']['type'] - except Exception: - raise faults.Fault(exc.HTTPNotImplemented()) + else: + LOG.exception(_("Missing argument 'type' for reboot")) + return faults.Fault(exc.HTTPUnprocessableEntity()) try: # TODO(gundlach): pass reboot_type, support soft reboot in # virt driver self.compute_api.reboot(req.environ['nova.context'], id) - except: + except Exception, e: + LOG.exception(_("Error in reboot %s"), e) return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() -- cgit From 0465f9249c0bcca27ad04bf8326bada2449e96c9 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 19 Apr 2011 13:29:16 -0500 Subject: Review feedback. --- nova/compute/manager.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 67d1eab5d..c795d72ad 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -524,6 +524,9 @@ class ComputeManager(manager.SchedulerDependentManager): self.db.migration_update(context, migration_id, {'status': 'post-migrating', }) + # Make sure the service exists before sending a message. + _service = self.db.service_get_by_host_and_topic(context, + migration_ref['dest_compute'], FLAGS.compute_topic) topic = self.db.queue_get_for(context, FLAGS.compute_topic, migration_ref['dest_compute']) rpc.cast(context, topic, -- cgit From a474310be8ed4d7a9840412779567abef71406f1 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 19 Apr 2011 17:24:01 -0400 Subject: Create a dictionary of instance_types before executing SQL updates in the instance_type_id migration (014). This should resolve a "cannot commit transaction - SQL statements in progress" error with some versions of sqlite. --- .../migrate_repo/versions/014_add_instance_type_id_to_instances.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py b/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py index b12a0a801..334d1f255 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/014_add_instance_type_id_to_instances.py @@ -54,10 +54,12 @@ def upgrade(migrate_engine): instances.create_column(c_instance_type_id) + type_names = {} recs = migrate_engine.execute(instance_types.select()) for row in recs: - type_id = row[0] - type_name = row[1] + type_names[row[0]] = row[1] + + for type_id, type_name in type_names.iteritems(): migrate_engine.execute(instances.update()\ .where(instances.c.instance_type == type_name)\ .values(instance_type_id=type_id)) -- cgit From 63f5aa5484aa9d61f2ed79caae1c665230a56f35 Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Tue, 19 Apr 2011 15:25:39 -0700 Subject: revamped spacing per Rick Harris suggestion. Added exact error to nova-manage output. --- nova/compute/instance_types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index a92e5d3bc..6accf82bb 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -56,8 +56,8 @@ def create(name, memory, vcpus, local_gb, flavorid, swap=0, rxtx_cap=rxtx_cap)) except exception.DBError, e: LOG.exception(_('DB error: %s') % e) - raise exception.ApiError(_("Cannot create instance_type with\ - name %(name)s and flavorid %(flavorid)s") % + raise exception.ApiError(_("Cannot create instance_type with " + "name %(name)s and flavorid %(flavorid)s") % locals()) -- cgit From d992fbbde7c8e5274d80e2fce9c840e7209c78c6 Mon Sep 17 00:00:00 2001 From: Jimmy Bergman Date: Wed, 20 Apr 2011 14:06:23 +0200 Subject: Change response format of CreateVolume to match EC2 --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index bd4c9dcd4..2bbe4c368 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -613,7 +613,7 @@ class CloudController(object): # TODO(vish): Instance should be None at db layer instead of # trying to lazy load, but for now we turn it into # a dict to avoid an error. - return {'volumeSet': [self._format_volume(context, dict(volume))]} + return self._format_volume(context, dict(volume)) def delete_volume(self, context, volume_id, **kwargs): volume_id = ec2utils.ec2_id_to_id(volume_id) -- cgit From 45178fd6da58ff37617e35b5cddaf416ae5cee65 Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Wed, 20 Apr 2011 17:44:25 +0400 Subject: fix: mark floating ip as auto assigned --- nova/compute/manager.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 313b9e0e1..c1dc06557 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -252,6 +252,7 @@ class ComputeManager(manager.SchedulerDependentManager): if FLAGS.auto_assign_floating_ip: public_ip = self.network_api.allocate_floating_ip(context) + self.db.floating_ip_set_auto_assigned(context, public_ip) fixed_ip = self.db.fixed_ip_get_by_address(context, address) floating_ip = self.db.floating_ip_get_by_address(context, public_ip) -- cgit From 803d246c35256e0578837226b1a91003e451ab6f Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Wed, 20 Apr 2011 18:35:07 +0400 Subject: instance type get approach changed. tests fixed --- nova/tests/test_virt.py | 4 ++-- nova/virt/libvirt_conn.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 5c8705a1c..0a0c7a958 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -619,7 +619,7 @@ class IptablesFirewallTestCase(test.TestCase): {'user_id': 'fake', 'project_id': 'fake', 'mac_address': '56:12:12:12:12:12', - 'instance_type': 'm1.small'}) + 'instance_type_id': 1}) ip = '10.11.12.13' network_ref = db.project_get_network(self.context, @@ -843,7 +843,7 @@ class NWFilterTestCase(test.TestCase): {'user_id': 'fake', 'project_id': 'fake', 'mac_address': '00:A0:C9:14:C8:29', - 'instance_type': 'm1.small'}) + 'instance_type_id': 1}) inst_id = instance_ref['id'] ip = '10.11.12.13' diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index c12b6e91e..d5a88ebed 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -167,8 +167,8 @@ def _get_network_info(instance): instance['id']) networks = db.network_get_all_by_instance(admin_context, instance['id']) - flavor = db.instance_type_get_by_name(admin_context, - instance['instance_type']) + flavor = db.instance_type_get_by_id(admin_context, + instance['instance_type_id']) network_info = [] for network in networks: -- cgit From 48936f6b8f063cf71fa42b4586d8ba524ed39cc4 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Wed, 20 Apr 2011 20:37:51 +0400 Subject: fix Request.get_content_type --- nova/api/openstack/servers.py | 67 +++++++++++------------------ nova/tests/api/openstack/test_extensions.py | 1 + nova/tests/api/openstack/test_servers.py | 4 ++ nova/tests/api/test_wsgi.py | 7 +++ nova/wsgi.py | 20 ++++++--- 5 files changed, 52 insertions(+), 47 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 22a9c632c..e0681e597 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -294,61 +294,47 @@ class Controller(common.OpenstackController): 'revertResize': self._action_revert_resize, 'rebuild': self._action_rebuild, } - input_dict = self._deserialize(req.body, req.get_content_type()) for key in actions.keys(): if key in input_dict: - return actions[key](input_dict, req, id) + try: + context = req.environ['nova.context'] + return actions[key](context, input_dict, id) + except Exception, e: + LOG.exception(_("Error in action %(key)s: %(e)s") % + locals()) + return faults.Fault(exc.HTTPBadRequest()) return faults.Fault(exc.HTTPNotImplemented()) - def _action_change_password(self, input_dict, req, id): + def _action_change_password(self, context, input_dict, id): return exc.HTTPNotImplemented() - def _action_confirm_resize(self, input_dict, req, id): - try: - self.compute_api.confirm_resize(req.environ['nova.context'], id) - except Exception, e: - LOG.exception(_("Error in confirm-resize %s"), e) - return faults.Fault(exc.HTTPBadRequest()) + def _action_confirm_resize(self, context, input_dict, id): + self.compute_api.confirm_resize(context, id) return exc.HTTPNoContent() - def _action_revert_resize(self, input_dict, req, id): - try: - self.compute_api.revert_resize(req.environ['nova.context'], id) - except Exception, e: - LOG.exception(_("Error in revert-resize %s"), e) - return faults.Fault(exc.HTTPBadRequest()) + def _action_revert_resize(self, context, input_dict, id): + self.compute_api.revert_resize(context, id) return exc.HTTPAccepted() - def _action_rebuild(self, input_dict, req, id): + def _action_rebuild(self, context, input_dict, id): return faults.Fault(exc.HTTPNotImplemented()) - def _action_resize(self, input_dict, req, id): + def _action_resize(self, context, input_dict, id): """ Resizes a given instance to the flavor size requested """ - try: - if 'resize' in input_dict and 'flavorId' in input_dict['resize']: - flavor_id = input_dict['resize']['flavorId'] - self.compute_api.resize(req.environ['nova.context'], id, - flavor_id) - else: - LOG.exception(_("Missing arguments for resize")) - return faults.Fault(exc.HTTPUnprocessableEntity()) - except Exception, e: - LOG.exception(_("Error in resize %s"), e) - return faults.Fault(exc.HTTPBadRequest()) + if 'resize' in input_dict and 'flavorId' in input_dict['resize']: + flavor_id = input_dict['resize']['flavorId'] + self.compute_api.resize(context, id, flavor_id) + else: + LOG.exception(_("Missing arguments for resize")) + return faults.Fault(exc.HTTPUnprocessableEntity()) return faults.Fault(exc.HTTPAccepted()) - def _action_reboot(self, input_dict, req, id): - try: - reboot_type = input_dict['reboot']['type'] - except Exception: - raise faults.Fault(exc.HTTPNotImplemented()) - try: - # TODO(gundlach): pass reboot_type, support soft reboot in - # virt driver - self.compute_api.reboot(req.environ['nova.context'], id) - except: - return faults.Fault(exc.HTTPUnprocessableEntity()) + def _action_reboot(self, context, input_dict, id): + reboot_type = input_dict['reboot']['type'] + # TODO(gundlach): pass reboot_type, support soft reboot in + # virt driver + self.compute_api.reboot(context, id) return exc.HTTPAccepted() @scheduler_api.redirect_handler @@ -632,8 +618,7 @@ class ControllerV11(Controller): def _get_addresses_view_builder(self, req): return nova.api.openstack.views.addresses.ViewBuilderV11(req) - def _action_change_password(self, input_dict, req, id): - context = req.environ['nova.context'] + def _action_change_password(self, context, input_dict, id): if (not 'changePassword' in input_dict or not 'adminPass' in input_dict['changePassword']): msg = _("No adminPass was specified") diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 481d34ed1..6453e96c9 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -158,6 +158,7 @@ class ActionExtensionTest(unittest.TestCase): request.method = 'POST' request.content_type = 'application/json' request.body = json.dumps(body) + request.environ = {'nova.context': 'context'} response = request.get_response(ext_midware) return response diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 556046e9d..a92da52b1 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -952,6 +952,7 @@ class ServersTest(test.TestCase): req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) + req.environ = {"nova.context": "context"} res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 501) @@ -973,6 +974,7 @@ class ServersTest(test.TestCase): req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) + req.environ = {"nova.context": "context"} res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 202) self.assertEqual(mock_method.instance_id, '1') @@ -993,6 +995,7 @@ class ServersTest(test.TestCase): req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) + req.environ = {"nova.context": "context"} res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) @@ -1002,6 +1005,7 @@ class ServersTest(test.TestCase): req.method = 'POST' req.content_type = 'application/json' req.body = json.dumps(body) + req.environ = {"nova.context": "context"} res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 400) diff --git a/nova/tests/api/test_wsgi.py b/nova/tests/api/test_wsgi.py index 1ecdd1cfb..ed96aac5e 100644 --- a/nova/tests/api/test_wsgi.py +++ b/nova/tests/api/test_wsgi.py @@ -136,6 +136,13 @@ class RequestTest(test.TestCase): request.body = "asdf
" self.assertRaises(webob.exc.HTTPBadRequest, request.get_content_type) + def test_request_content_type_with_charset(self): + request = wsgi.Request.blank('/tests/123') + request.headers["Content-Type"] = "application/json; charset=UTF-8" + request.body = "" + result = request.get_content_type() + self.assertEqual(result, "application/json") + def test_content_type_from_accept_xml(self): request = wsgi.Request.blank('/tests/123') request.headers["Accept"] = "application/xml" diff --git a/nova/wsgi.py b/nova/wsgi.py index de2e0749f..617830c22 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -28,6 +28,7 @@ from xml.dom import minidom import eventlet import eventlet.wsgi eventlet.patcher.monkey_patch(all=False, socket=True, time=True) +import re import routes import routes.middleware import webob @@ -105,12 +106,19 @@ class Request(webob.Request): return bm or "application/json" def get_content_type(self): - try: - ct = self.headers["Content-Type"] - assert ct in ("application/xml", "application/json") - return ct - except Exception: - raise webob.exc.HTTPBadRequest("Invalid content type") + allowed_types = ("application/xml", "application/json") + if not "Content-Type" in self.headers: + msg = _("Missing Content-Type") + LOG.debug(msg) + raise webob.exc.HTTPBadRequest(msg) + content_type = self.headers["Content-Type"] + match = re.search("([\w/]+)", content_type) + if match: + type = match.group(0) + if type in allowed_types: + return type + LOG.debug(_("Wrong Content-Type: %s") % content_type) + raise webob.exc.HTTPBadRequest("Invalid content type") class Application(object): -- cgit From 155635faf20d4a1996639baf5d2c10b05734c3df Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Wed, 20 Apr 2011 21:50:03 +0400 Subject: replaced regex to webob.Request.content_type --- nova/wsgi.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/wsgi.py b/nova/wsgi.py index 617830c22..9e0f80565 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -28,7 +28,6 @@ from xml.dom import minidom import eventlet import eventlet.wsgi eventlet.patcher.monkey_patch(all=False, socket=True, time=True) -import re import routes import routes.middleware import webob @@ -111,13 +110,10 @@ class Request(webob.Request): msg = _("Missing Content-Type") LOG.debug(msg) raise webob.exc.HTTPBadRequest(msg) - content_type = self.headers["Content-Type"] - match = re.search("([\w/]+)", content_type) - if match: - type = match.group(0) - if type in allowed_types: - return type - LOG.debug(_("Wrong Content-Type: %s") % content_type) + type = self.content_type + if type in allowed_types: + return type + LOG.debug(_("Wrong Content-Type: %s") % type) raise webob.exc.HTTPBadRequest("Invalid content type") -- cgit From fe23f71687e09248feb7542ea97001a496697742 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Wed, 20 Apr 2011 22:01:14 +0400 Subject: remove ambiguity in test --- nova/tests/api/test_wsgi.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova') diff --git a/nova/tests/api/test_wsgi.py b/nova/tests/api/test_wsgi.py index ed96aac5e..5820ecdc2 100644 --- a/nova/tests/api/test_wsgi.py +++ b/nova/tests/api/test_wsgi.py @@ -139,7 +139,6 @@ class RequestTest(test.TestCase): def test_request_content_type_with_charset(self): request = wsgi.Request.blank('/tests/123') request.headers["Content-Type"] = "application/json; charset=UTF-8" - request.body = "" result = request.get_content_type() self.assertEqual(result, "application/json") -- cgit From f4cfb9f0e654f26664345a041a62fd93613ef83b Mon Sep 17 00:00:00 2001 From: termie Date: Wed, 20 Apr 2011 11:52:17 -0700 Subject: docstring cleanup compute manager --- nova/compute/manager.py | 236 +++++++++++++++++++++--------------------------- 1 file changed, 105 insertions(+), 131 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index c795d72ad..8103ed61e 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -17,8 +17,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Handles all processes relating to instances (guest vms). +"""Handles all processes relating to instances (guest vms). The :py:class:`ComputeManager` class is a :py:class:`nova.manager.Manager` that handles RPC calls relating to creating instances. It is responsible for @@ -33,6 +32,7 @@ terminating it. by :func:`nova.utils.import_object` :volume_manager: Name of class that handles persistent storage, loaded by :func:`nova.utils.import_object` + """ import datetime @@ -55,6 +55,7 @@ from nova import utils from nova.compute import power_state from nova.virt import driver + FLAGS = flags.FLAGS flags.DEFINE_string('instances_path', '$state_path/instances', 'where instances are stored on disk') @@ -74,19 +75,14 @@ flags.DEFINE_integer("rescue_timeout", 0, "Automatically unrescue an instance after N seconds." " Set to 0 to disable.") + LOG = logging.getLogger('nova.compute.manager') def checks_instance_lock(function): - """ - decorator used for preventing action against locked instances - unless, of course, you happen to be admin - - """ - + """Decorator to prevent action against locked instances for non-admins.""" @functools.wraps(function) def decorated_function(self, context, instance_id, *args, **kwargs): - LOG.info(_("check_instance_lock: decorating: |%s|"), function, context=context) LOG.info(_("check_instance_lock: arguments: |%(self)s| |%(context)s|" @@ -112,7 +108,6 @@ def checks_instance_lock(function): class ComputeManager(manager.SchedulerDependentManager): - """Manages the running instances from creation to destruction.""" def __init__(self, compute_driver=None, *args, **kwargs): @@ -136,9 +131,7 @@ class ComputeManager(manager.SchedulerDependentManager): *args, **kwargs) def init_host(self): - """Do any initialization that needs to be run if this is a - standalone service. - """ + """Initialization for a standalone compute service.""" self.driver.init_host(host=self.host) def _update_state(self, context, instance_id): @@ -153,16 +146,18 @@ class ComputeManager(manager.SchedulerDependentManager): self.db.instance_set_state(context, instance_id, state) def get_console_topic(self, context, **kwargs): - """Retrieves the console host for a project on this host - Currently this is just set in the flags for each compute - host.""" + """Retrieves the console host for a project on this host. + + Currently this is just set in the flags for each compute host. + + """ #TODO(mdragon): perhaps make this variable by console_type? return self.db.queue_get_for(context, FLAGS.console_topic, FLAGS.console_host) def get_network_topic(self, context, **kwargs): - """Retrieves the network host for a project on this host""" + """Retrieves the network host for a project on this host.""" # TODO(vish): This method should be memoized. This will make # the call to get_network_host cheaper, so that # it can pas messages instead of checking the db @@ -179,15 +174,23 @@ class ComputeManager(manager.SchedulerDependentManager): return self.driver.get_console_pool_info(console_type) @exception.wrap_exception - def refresh_security_group_rules(self, context, - security_group_id, **kwargs): - """This call passes straight through to the virtualization driver.""" + def refresh_security_group_rules(self, context, security_group_id, + **kwargs): + """Tell the virtualization driver to refresh security group rules. + + Passes straight through to the virtualization driver. + + """ return self.driver.refresh_security_group_rules(security_group_id) @exception.wrap_exception def refresh_security_group_members(self, context, security_group_id, **kwargs): - """This call passes straight through to the virtualization driver.""" + """Tell the virtualization driver to refresh security group members. + + Passes straight through to the virtualization driver. + + """ return self.driver.refresh_security_group_members(security_group_id) @exception.wrap_exception @@ -249,7 +252,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock def terminate_instance(self, context, instance_id): - """Terminate an instance on this machine.""" + """Terminate an instance on this host.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) LOG.audit(_("Terminating instance %s"), instance_id, context=context) @@ -297,7 +300,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock def reboot_instance(self, context, instance_id): - """Reboot an instance on this server.""" + """Reboot an instance on this host.""" context = context.elevated() self._update_state(context, instance_id) instance_ref = self.db.instance_get(context, instance_id) @@ -321,7 +324,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception def snapshot_instance(self, context, instance_id, image_id): - """Snapshot an instance on this server.""" + """Snapshot an instance on this host.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) @@ -344,7 +347,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock 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/admin password for an instance on this host.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) instance_id = instance_ref['id'] @@ -365,7 +368,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock def inject_file(self, context, instance_id, path, file_contents): - """Write a file to the specified path on an instance on this server""" + """Write a file to the specified path in an instance on this host.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) instance_id = instance_ref['id'] @@ -383,44 +386,34 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock def rescue_instance(self, context, instance_id): - """Rescue an instance on this server.""" + """Rescue an instance on this host.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) LOG.audit(_('instance %s: rescuing'), instance_id, context=context) - self.db.instance_set_state( - context, - instance_id, - power_state.NOSTATE, - 'rescuing') + self.db.instance_set_state(context, + instance_id, + power_state.NOSTATE, + 'rescuing') self.network_manager.setup_compute_network(context, instance_id) - self.driver.rescue( - instance_ref, - lambda result: self._update_state_callback( - self, - context, - instance_id, - result)) + _update_state = lambda result: self._update_state_callback( + self, context, instance_id, result)) + self.driver.rescue(instance_ref, _update_state) self._update_state(context, instance_id) @exception.wrap_exception @checks_instance_lock def unrescue_instance(self, context, instance_id): - """Rescue an instance on this server.""" + """Rescue an instance on this host.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) LOG.audit(_('instance %s: unrescuing'), instance_id, context=context) - self.db.instance_set_state( - context, - instance_id, - power_state.NOSTATE, - 'unrescuing') - self.driver.unrescue( - instance_ref, - lambda result: self._update_state_callback( - self, - context, - instance_id, - result)) + self.db.instance_set_state(context, + instance_id, + power_state.NOSTATE, + 'unrescuing') + _update_state = lambda result: self._update_state_callback( + self, context, instance_id, result)) + self.driver.unrescue(instance_ref, _update_state) self._update_state(context, instance_id) @staticmethod @@ -431,7 +424,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock def confirm_resize(self, context, instance_id, migration_id): - """Destroys the source instance""" + """Destroys the source instance.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) self.driver.destroy(instance_ref) @@ -439,9 +432,12 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock def revert_resize(self, context, instance_id, migration_id): - """Destroys the new instance on the destination machine, - reverts the model changes, and powers on the old - instance on the source machine""" + """Destroys the new instance on the destination machine. + + Reverts the model changes, and powers on the old instance on the + source machine. + + """ instance_ref = self.db.instance_get(context, instance_id) migration_ref = self.db.migration_get(context, migration_id) @@ -458,9 +454,12 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock def finish_revert_resize(self, context, instance_id, migration_id): - """Finishes the second half of reverting a resize, powering back on - the source instance and reverting the resized attributes in the - database""" + """Finishes the second half of reverting a resize. + + Power back on the source instance and revert the resized attributes + in the database. + + """ instance_ref = self.db.instance_get(context, instance_id) migration_ref = self.db.migration_get(context, migration_id) instance_type = self.db.instance_type_get_by_flavor_id(context, @@ -480,8 +479,11 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock def prep_resize(self, context, instance_id, flavor_id): - """Initiates the process of moving a running instance to another - host, possibly changing the RAM and disk size in the process""" + """Initiates the process of moving a running instance to another host. + + Possibly changes the RAM and disk size in the process. + + """ context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) if instance_ref['host'] == FLAGS.host: @@ -513,35 +515,38 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock def resize_instance(self, context, instance_id, migration_id): - """Starts the migration of a running instance to another host""" + """Starts the migration of a running instance to another host.""" migration_ref = self.db.migration_get(context, migration_id) instance_ref = self.db.instance_get(context, instance_id) - self.db.migration_update(context, migration_id, - {'status': 'migrating', }) - - disk_info = self.driver.migrate_disk_and_power_off(instance_ref, - migration_ref['dest_host']) - self.db.migration_update(context, migration_id, - {'status': 'post-migrating', }) - - # Make sure the service exists before sending a message. - _service = self.db.service_get_by_host_and_topic(context, - migration_ref['dest_compute'], FLAGS.compute_topic) - topic = self.db.queue_get_for(context, FLAGS.compute_topic, - migration_ref['dest_compute']) - rpc.cast(context, topic, - {'method': 'finish_resize', - 'args': { - 'migration_id': migration_id, - 'instance_id': instance_id, - 'disk_info': disk_info, }, - }) + self.db.migration_update(context, + migration_id, + {'status': 'migrating'}) + + disk_info = self.driver.migrate_disk_and_power_off( + instance_ref, migration_ref['dest_host']) + self.db.migration_update(context, + migration_id, + {'status': 'post-migrating'}) + + service = self.db.service_get_by_host_and_topic( + context, migration_ref['dest_compute'], FLAGS.compute_topic) + topic = self.db.queue_get_for(context, + FLAGS.compute_topic, + migration_ref['dest_compute']) + rpc.cast(context, topic, {'method': 'finish_resize', + 'args': {'migration_id': migration_id, + 'instance_id': instance_id, + 'disk_info': disk_info}}) @exception.wrap_exception @checks_instance_lock def finish_resize(self, context, instance_id, migration_id, disk_info): - """Completes the migration process by setting up the newly transferred - disk and turning on the instance on its new host machine""" + """Completes the migration process. + + Sets up the newly transferred disk and turns on the instance at its + new host machine. + + """ migration_ref = self.db.migration_get(context, migration_id) instance_ref = self.db.instance_get(context, migration_ref['instance_id']) @@ -566,7 +571,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock def pause_instance(self, context, instance_id): - """Pause an instance on this server.""" + """Pause an instance on this host.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) LOG.audit(_('instance %s: pausing'), instance_id, context=context) @@ -583,7 +588,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock def unpause_instance(self, context, instance_id): - """Unpause a paused instance on this server.""" + """Unpause a paused instance on this host.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) LOG.audit(_('instance %s: unpausing'), instance_id, context=context) @@ -599,7 +604,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception def get_diagnostics(self, context, instance_id): - """Retrieve diagnostics for an instance on this server.""" + """Retrieve diagnostics for an instance on this host.""" instance_ref = self.db.instance_get(context, instance_id) if instance_ref["state"] == power_state.RUNNING: @@ -610,10 +615,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock def suspend_instance(self, context, instance_id): - """ - suspend the instance with instance_id - - """ + """Suspend the given instance.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) LOG.audit(_('instance %s: suspending'), instance_id, context=context) @@ -629,10 +631,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception @checks_instance_lock def resume_instance(self, context, instance_id): - """ - resume the suspended instance with instance_id - - """ + """Resume the given suspended instance.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) LOG.audit(_('instance %s: resuming'), instance_id, context=context) @@ -647,10 +646,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception def lock_instance(self, context, instance_id): - """ - lock the instance with instance_id - - """ + """Lock the given instance.""" context = context.elevated() LOG.debug(_('instance %s: locking'), instance_id, context=context) @@ -658,10 +654,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception def unlock_instance(self, context, instance_id): - """ - unlock the instance with instance_id - - """ + """Unlock the given instance.""" context = context.elevated() LOG.debug(_('instance %s: unlocking'), instance_id, context=context) @@ -669,10 +662,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception def get_lock(self, context, instance_id): - """ - return the boolean state of (instance with instance_id)'s lock - - """ + """Return the boolean state of the given instance's lock.""" context = context.elevated() LOG.debug(_('instance %s: getting locked state'), instance_id, context=context) @@ -681,10 +671,7 @@ class ComputeManager(manager.SchedulerDependentManager): @checks_instance_lock def reset_network(self, context, instance_id): - """ - Reset networking on the instance. - - """ + """Reset networking on the given instance.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) LOG.debug(_('instance %s: reset network'), instance_id, @@ -693,10 +680,7 @@ class ComputeManager(manager.SchedulerDependentManager): @checks_instance_lock def inject_network_info(self, context, instance_id): - """ - Inject network info for the instance. - - """ + """Inject network info for the given instance.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) LOG.debug(_('instance %s: inject network info'), instance_id, @@ -705,7 +689,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception def get_console_output(self, context, instance_id): - """Send the console output for an instance.""" + """Send the console output for the given instance.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) LOG.audit(_("Get console output for instance %s"), instance_id, @@ -714,20 +698,18 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception def get_ajax_console(self, context, instance_id): - """Return connection information for an ajax console""" + """Return connection information for an ajax console.""" context = context.elevated() LOG.debug(_("instance %s: getting ajax console"), instance_id) instance_ref = self.db.instance_get(context, instance_id) - return self.driver.get_ajax_console(instance_ref) @exception.wrap_exception def get_vnc_console(self, context, instance_id): - """Return connection information for an vnc console.""" + """Return connection information for a vnc console.""" context = context.elevated() LOG.debug(_("instance %s: getting vnc console"), instance_id) instance_ref = self.db.instance_get(context, instance_id) - return self.driver.get_vnc_console(instance_ref) @checks_instance_lock @@ -781,7 +763,7 @@ class ComputeManager(manager.SchedulerDependentManager): @exception.wrap_exception def compare_cpu(self, context, cpu_info): - """Checks the host cpu is compatible to a cpu given by xml. + """Checks that the host cpu is compatible with a cpu given by xml. :param context: security context :param cpu_info: json string obtained from virConnect.getCapabilities @@ -802,7 +784,6 @@ class ComputeManager(manager.SchedulerDependentManager): :returns: tmpfile name(basename) """ - dirpath = FLAGS.instances_path fd, tmp_file = tempfile.mkstemp(dir=dirpath) LOG.debug(_("Creating tmpfile %s to notify to other " @@ -819,7 +800,6 @@ class ComputeManager(manager.SchedulerDependentManager): :param filename: confirm existence of FLAGS.instances_path/thisfile """ - tmp_file = os.path.join(FLAGS.instances_path, filename) if not os.path.exists(tmp_file): raise exception.NotFound(_('%s not found') % tmp_file) @@ -832,7 +812,6 @@ class ComputeManager(manager.SchedulerDependentManager): :param filename: remove existence of FLAGS.instances_path/thisfile """ - tmp_file = os.path.join(FLAGS.instances_path, filename) os.remove(tmp_file) @@ -844,7 +823,6 @@ class ComputeManager(manager.SchedulerDependentManager): :returns: See driver.update_available_resource() """ - return self.driver.update_available_resource(context, self.host) def pre_live_migration(self, context, instance_id, time=None): @@ -854,7 +832,6 @@ class ComputeManager(manager.SchedulerDependentManager): :param instance_id: nova.db.sqlalchemy.models.Instance.Id """ - if not time: time = greenthread @@ -913,7 +890,6 @@ class ComputeManager(manager.SchedulerDependentManager): :param dest: destination host """ - # Get instance for error handling. instance_ref = self.db.instance_get(context, instance_id) i_name = instance_ref.name @@ -1009,12 +985,10 @@ class ComputeManager(manager.SchedulerDependentManager): :param ctxt: security context :param instance_id: nova.db.sqlalchemy.models.Instance.Id - :param host: - DB column value is updated by this hostname. - if none, the host instance currently running is selected. + :param host: DB column value is updated by this hostname. + If none, the host instance currently running is selected. """ - if not host: host = instance_ref['host'] -- cgit From b26f3166554cf5de29fcedee7463bc523786cf72 Mon Sep 17 00:00:00 2001 From: termie Date: Wed, 20 Apr 2011 11:52:19 -0700 Subject: fix typo --- nova/compute/manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 8103ed61e..fac00e45e 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -396,7 +396,7 @@ class ComputeManager(manager.SchedulerDependentManager): 'rescuing') self.network_manager.setup_compute_network(context, instance_id) _update_state = lambda result: self._update_state_callback( - self, context, instance_id, result)) + self, context, instance_id, result) self.driver.rescue(instance_ref, _update_state) self._update_state(context, instance_id) @@ -412,7 +412,7 @@ class ComputeManager(manager.SchedulerDependentManager): power_state.NOSTATE, 'unrescuing') _update_state = lambda result: self._update_state_callback( - self, context, instance_id, result)) + self, context, instance_id, result) self.driver.unrescue(instance_ref, _update_state) self._update_state(context, instance_id) -- cgit From 98b8a800b16a1e6699b1d4c7ce0e4ab61319be6e Mon Sep 17 00:00:00 2001 From: termie Date: Wed, 20 Apr 2011 12:00:21 -0700 Subject: docstring cleanup, nova/db dir --- nova/db/api.py | 61 +++++++++++++++++++++++++++------------------------- nova/db/base.py | 8 +++---- nova/db/migration.py | 2 ++ 3 files changed, 38 insertions(+), 33 deletions(-) (limited to 'nova') diff --git a/nova/db/api.py b/nova/db/api.py index 63901e94d..1b33d8932 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -15,8 +15,8 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -""" -Defines interface for DB access. + +"""Defines interface for DB access. The underlying driver is loaded as a :class:`LazyPluggable`. @@ -30,6 +30,7 @@ The underlying driver is loaded as a :class:`LazyPluggable`. :enable_new_services: when adding a new service to the database, is it in the pool of available hardware (Default: True) + """ from nova import exception @@ -86,7 +87,7 @@ def service_get(context, service_id): def service_get_by_host_and_topic(context, host, topic): - """Get a service by host it's on and topic it listens to""" + """Get a service by host it's on and topic it listens to.""" return IMPL.service_get_by_host_and_topic(context, host, topic) @@ -113,7 +114,7 @@ def service_get_all_compute_by_host(context, host): def service_get_all_compute_sorted(context): """Get all compute services sorted by instance count. - Returns a list of (Service, instance_count) tuples. + :returns: a list of (Service, instance_count) tuples. """ return IMPL.service_get_all_compute_sorted(context) @@ -122,7 +123,7 @@ def service_get_all_compute_sorted(context): def service_get_all_network_sorted(context): """Get all network services sorted by network count. - Returns a list of (Service, network_count) tuples. + :returns: a list of (Service, network_count) tuples. """ return IMPL.service_get_all_network_sorted(context) @@ -131,7 +132,7 @@ def service_get_all_network_sorted(context): def service_get_all_volume_sorted(context): """Get all volume services sorted by volume count. - Returns a list of (Service, volume_count) tuples. + :returns: a list of (Service, volume_count) tuples. """ return IMPL.service_get_all_volume_sorted(context) @@ -241,7 +242,7 @@ def floating_ip_count_by_project(context, project_id): def floating_ip_deallocate(context, address): - """Deallocate an floating ip by address""" + """Deallocate an floating ip by address.""" return IMPL.floating_ip_deallocate(context, address) @@ -253,7 +254,7 @@ def floating_ip_destroy(context, address): def floating_ip_disassociate(context, address): """Disassociate an floating ip from a fixed ip by address. - Returns the address of the existing fixed ip. + :returns: the address of the existing fixed ip. """ return IMPL.floating_ip_disassociate(context, address) @@ -294,22 +295,22 @@ def floating_ip_update(context, address, values): #################### def migration_update(context, id, values): - """Update a migration instance""" + """Update a migration instance.""" return IMPL.migration_update(context, id, values) def migration_create(context, values): - """Create a migration record""" + """Create a migration record.""" return IMPL.migration_create(context, values) def migration_get(context, migration_id): - """Finds a migration by the id""" + """Finds a migration by the id.""" return IMPL.migration_get(context, migration_id) def migration_get_by_instance_and_status(context, instance_id, status): - """Finds a migration by the instance id its migrating""" + """Finds a migration by the instance id its migrating.""" return IMPL.migration_get_by_instance_and_status(context, instance_id, status) @@ -579,7 +580,9 @@ def network_create_safe(context, values): def network_delete_safe(context, network_id): """Delete network with key network_id. + This method assumes that the network is not associated with any project + """ return IMPL.network_delete_safe(context, network_id) @@ -674,7 +677,6 @@ def project_get_network(context, project_id, associate=True): network if one is not found, otherwise it returns None. """ - return IMPL.project_get_network(context, project_id, associate) @@ -722,7 +724,9 @@ def iscsi_target_create_safe(context, values): The device is not returned. If the create violates the unique constraints because the iscsi_target and host already exist, - no exception is raised.""" + no exception is raised. + + """ return IMPL.iscsi_target_create_safe(context, values) @@ -1050,10 +1054,7 @@ def project_delete(context, project_id): def host_get_networks(context, host): - """Return all networks for which the given host is the designated - network host. - - """ + """All networks for which the given host is the network host.""" return IMPL.host_get_networks(context, host) @@ -1115,38 +1116,40 @@ def console_get(context, console_id, instance_id=None): def instance_type_create(context, values): - """Create a new instance type""" + """Create a new instance type.""" return IMPL.instance_type_create(context, values) def instance_type_get_all(context, inactive=False): - """Get all instance types""" + """Get all instance types.""" return IMPL.instance_type_get_all(context, inactive) def instance_type_get_by_id(context, id): - """Get instance type by id""" + """Get instance type by id.""" return IMPL.instance_type_get_by_id(context, id) def instance_type_get_by_name(context, name): - """Get instance type by name""" + """Get instance type by name.""" return IMPL.instance_type_get_by_name(context, name) def instance_type_get_by_flavor_id(context, id): - """Get instance type by name""" + """Get instance type by name.""" return IMPL.instance_type_get_by_flavor_id(context, id) def instance_type_destroy(context, name): - """Delete a instance type""" + """Delete a instance type.""" return IMPL.instance_type_destroy(context, name) def instance_type_purge(context, name): - """Purges (removes) an instance type from DB - Use instance_type_destroy for most cases + """Purges (removes) an instance type from DB. + + Use instance_type_destroy for most cases + """ return IMPL.instance_type_purge(context, name) @@ -1183,15 +1186,15 @@ def zone_get_all(context): def instance_metadata_get(context, instance_id): - """Get all metadata for an instance""" + """Get all metadata for an instance.""" return IMPL.instance_metadata_get(context, instance_id) def instance_metadata_delete(context, instance_id, key): - """Delete the given metadata item""" + """Delete the given metadata item.""" IMPL.instance_metadata_delete(context, instance_id, key) def instance_metadata_update_or_create(context, instance_id, metadata): - """Create or update instance metadata""" + """Create or update instance metadata.""" IMPL.instance_metadata_update_or_create(context, instance_id, metadata) diff --git a/nova/db/base.py b/nova/db/base.py index a0f2180c6..a0d055d5b 100644 --- a/nova/db/base.py +++ b/nova/db/base.py @@ -16,20 +16,20 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Base class for classes that need modular database access. -""" +"""Base class for classes that need modular database access.""" from nova import utils from nova import flags + FLAGS = flags.FLAGS flags.DEFINE_string('db_driver', 'nova.db.api', 'driver to use for database access') class Base(object): - """DB driver is injected in the init method""" + """DB driver is injected in the init method.""" + def __init__(self, db_driver=None): if not db_driver: db_driver = FLAGS.db_driver diff --git a/nova/db/migration.py b/nova/db/migration.py index e54b90cd8..ccd06cffe 100644 --- a/nova/db/migration.py +++ b/nova/db/migration.py @@ -15,11 +15,13 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. + """Database setup and migration commands.""" from nova import flags from nova import utils + FLAGS = flags.FLAGS flags.DECLARE('db_backend', 'nova.db.api') -- cgit From 04fd29085c6ed5fe72378b061b1d7659f110c924 Mon Sep 17 00:00:00 2001 From: termie Date: Wed, 20 Apr 2011 12:06:10 -0700 Subject: docstring cleanup, console --- nova/console/api.py | 23 ++++++-------- nova/console/fake.py | 22 ++++++-------- nova/console/manager.py | 17 ++++++----- nova/console/vmrc.py | 48 ++++++++++++++--------------- nova/console/vmrc_manager.py | 72 ++++++++++++++++++++------------------------ nova/console/xvp.py | 48 +++++++++++++++-------------- 6 files changed, 108 insertions(+), 122 deletions(-) (limited to 'nova') diff --git a/nova/console/api.py b/nova/console/api.py index 3850d2c44..137ddcaac 100644 --- a/nova/console/api.py +++ b/nova/console/api.py @@ -15,23 +15,19 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Handles ConsoleProxy API requests -""" +"""Handles ConsoleProxy API requests.""" from nova import exception -from nova.db import base - - from nova import flags from nova import rpc +from nova.db import base FLAGS = flags.FLAGS class API(base.Base): - """API for spining up or down console proxy connections""" + """API for spinning up or down console proxy connections.""" def __init__(self, **kwargs): super(API, self).__init__(**kwargs) @@ -51,8 +47,8 @@ class API(base.Base): self.db.queue_get_for(context, FLAGS.console_topic, pool['host']), - {"method": "remove_console", - "args": {"console_id": console['id']}}) + {'method': 'remove_console', + 'args': {'console_id': console['id']}}) def create_console(self, context, instance_id): instance = self.db.instance_get(context, instance_id) @@ -63,13 +59,12 @@ class API(base.Base): # here. rpc.cast(context, self._get_console_topic(context, instance['host']), - {"method": "add_console", - "args": {"instance_id": instance_id}}) + {'method': 'add_console', + 'args': {'instance_id': instance_id}}) def _get_console_topic(self, context, instance_host): topic = self.db.queue_get_for(context, FLAGS.compute_topic, instance_host) - return rpc.call(context, - topic, - {"method": "get_console_topic", "args": {'fake': 1}}) + return rpc.call(context, topic, {'method': 'get_console_topic', + 'args': {'fake': 1}}) diff --git a/nova/console/fake.py b/nova/console/fake.py index 7a90d5221..e2eb886f8 100644 --- a/nova/console/fake.py +++ b/nova/console/fake.py @@ -15,9 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Fake ConsoleProxy driver for tests. -""" +"""Fake ConsoleProxy driver for tests.""" from nova import exception @@ -27,32 +25,32 @@ class FakeConsoleProxy(object): @property def console_type(self): - return "fake" + return 'fake' def setup_console(self, context, console): - """Sets up actual proxies""" + """Sets up actual proxies.""" pass def teardown_console(self, context, console): - """Tears down actual proxies""" + """Tears down actual proxies.""" pass def init_host(self): - """Start up any config'ed consoles on start""" + """Start up any config'ed consoles on start.""" pass def generate_password(self, length=8): - """Returns random console password""" - return "fakepass" + """Returns random console password.""" + return 'fakepass' def get_port(self, context): - """get available port for consoles that need one""" + """Get available port for consoles that need one.""" return 5999 def fix_pool_password(self, password): - """Trim password to length, and any other massaging""" + """Trim password to length, and any other massaging.""" return password def fix_console_password(self, password): - """Trim password to length, and any other massaging""" + """Trim password to length, and any other massaging.""" return password diff --git a/nova/console/manager.py b/nova/console/manager.py index bfa571ea9..e0db21666 100644 --- a/nova/console/manager.py +++ b/nova/console/manager.py @@ -15,9 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Console Proxy Service -""" +"""Console Proxy Service.""" import functools import socket @@ -29,6 +27,7 @@ from nova import manager from nova import rpc from nova import utils + FLAGS = flags.FLAGS flags.DEFINE_string('console_driver', 'nova.console.xvp.XVPConsoleProxy', @@ -41,9 +40,11 @@ flags.DEFINE_string('console_public_hostname', class ConsoleProxyManager(manager.Manager): + """Sets up and tears down any console proxy connections. + + Needed for accessing instance consoles securely. - """ Sets up and tears down any proxy connections needed for accessing - instance consoles securely""" + """ def __init__(self, console_driver=None, *args, **kwargs): if not console_driver: @@ -67,7 +68,7 @@ class ConsoleProxyManager(manager.Manager): pool['id'], instance_id) except exception.NotFound: - logging.debug(_("Adding console")) + logging.debug(_('Adding console')) if not password: password = utils.generate_password(8) if not port: @@ -115,8 +116,8 @@ class ConsoleProxyManager(manager.Manager): self.db.queue_get_for(context, FLAGS.compute_topic, instance_host), - {"method": "get_console_pool_info", - "args": {"console_type": console_type}}) + {'method': 'get_console_pool_info', + 'args': {'console_type': console_type}}) pool_info['password'] = self.driver.fix_pool_password( pool_info['password']) pool_info['host'] = self.host diff --git a/nova/console/vmrc.py b/nova/console/vmrc.py index 521da289f..127d31121 100644 --- a/nova/console/vmrc.py +++ b/nova/console/vmrc.py @@ -15,9 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" -VMRC console drivers. -""" +"""VMRC console drivers.""" import base64 import json @@ -27,6 +25,8 @@ from nova import flags from nova import log as logging from nova.virt.vmwareapi import vim_util + +FLAGS = flags.FLAGS flags.DEFINE_integer('console_vmrc_port', 443, "port for VMware VMRC connections") @@ -34,8 +34,6 @@ flags.DEFINE_integer('console_vmrc_error_retries', 10, "number of retries for retrieving VMRC information") -FLAGS = flags.FLAGS - class VMRCConsole(object): """VMRC console driver with ESX credentials.""" @@ -69,34 +67,34 @@ class VMRCConsole(object): return password def generate_password(self, vim_session, pool, instance_name): - """ - Returns VMRC Connection credentials. + """Returns VMRC Connection credentials. Return string is of the form ':@'. + """ username, password = pool['username'], pool['password'] - vms = vim_session._call_method(vim_util, "get_objects", - "VirtualMachine", ["name", "config.files.vmPathName"]) + vms = vim_session._call_method(vim_util, 'get_objects', + 'VirtualMachine', ['name', 'config.files.vmPathName']) vm_ds_path_name = None vm_ref = None for vm in vms: vm_name = None ds_path_name = None for prop in vm.propSet: - if prop.name == "name": + if prop.name == 'name': vm_name = prop.val - elif prop.name == "config.files.vmPathName": + elif prop.name == 'config.files.vmPathName': ds_path_name = prop.val if vm_name == instance_name: vm_ref = vm.obj vm_ds_path_name = ds_path_name break if vm_ref is None: - raise exception.NotFound(_("instance - %s not present") % + raise exception.NotFound(_('instance - %s not present') % instance_name) - json_data = json.dumps({"vm_id": vm_ds_path_name, - "username": username, - "password": password}) + json_data = json.dumps({'vm_id': vm_ds_path_name, + 'username': username, + 'password': password}) return base64.b64encode(json_data) def is_otp(self): @@ -115,28 +113,28 @@ class VMRCSessionConsole(VMRCConsole): return 'vmrc+session' def generate_password(self, vim_session, pool, instance_name): - """ - Returns a VMRC Session. + """Returns a VMRC Session. Return string is of the form ':'. + """ - vms = vim_session._call_method(vim_util, "get_objects", - "VirtualMachine", ["name"]) - vm_ref = None + vms = vim_session._call_method(vim_util, 'get_objects', + 'VirtualMachine', ['name']) + vm_ref = NoneV for vm in vms: if vm.propSet[0].val == instance_name: vm_ref = vm.obj if vm_ref is None: - raise exception.NotFound(_("instance - %s not present") % + raise exception.NotFound(_('instance - %s not present') % instance_name) virtual_machine_ticket = \ vim_session._call_method( vim_session._get_vim(), - "AcquireCloneTicket", + 'AcquireCloneTicket', vim_session._get_vim().get_service_content().sessionManager) - json_data = json.dumps({"vm_id": str(vm_ref.value), - "username": virtual_machine_ticket, - "password": virtual_machine_ticket}) + json_data = json.dumps({'vm_id': str(vm_ref.value), + 'username': virtual_machine_ticket, + 'password': virtual_machine_ticket}) return base64.b64encode(json_data) def is_otp(self): diff --git a/nova/console/vmrc_manager.py b/nova/console/vmrc_manager.py index 09beac7a0..3c6b77782 100644 --- a/nova/console/vmrc_manager.py +++ b/nova/console/vmrc_manager.py @@ -15,9 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" -VMRC Console Manager. -""" +"""VMRC Console Manager.""" from nova import exception from nova import flags @@ -25,24 +23,21 @@ from nova import log as logging from nova import manager from nova import rpc from nova import utils -from nova.virt.vmwareapi_conn import VMWareAPISession +from nova.virt import vmwareapi_conn + LOG = logging.getLogger("nova.console.vmrc_manager") + FLAGS = flags.FLAGS -flags.DEFINE_string('console_public_hostname', - '', +flags.DEFINE_string('console_public_hostname', '', 'Publicly visible name for this console host') -flags.DEFINE_string('console_driver', - 'nova.console.vmrc.VMRCConsole', +flags.DEFINE_string('console_driver', 'nova.console.vmrc.VMRCConsole', 'Driver to use for the console') class ConsoleVMRCManager(manager.Manager): - - """ - Manager to handle VMRC connections needed for accessing instance consoles. - """ + """Manager to handle VMRC connections for accessing instance consoles.""" def __init__(self, console_driver=None, *args, **kwargs): self.driver = utils.import_object(FLAGS.console_driver) @@ -56,7 +51,7 @@ class ConsoleVMRCManager(manager.Manager): """Get VIM session for the pool specified.""" vim_session = None if pool['id'] not in self.sessions.keys(): - vim_session = VMWareAPISession(pool['address'], + vim_session = vmwareapi_conn.VMWareAPISession(pool['address'], pool['username'], pool['password'], FLAGS.console_vmrc_error_retries) @@ -65,7 +60,7 @@ class ConsoleVMRCManager(manager.Manager): def _generate_console(self, context, pool, name, instance_id, instance): """Sets up console for the instance.""" - LOG.debug(_("Adding console")) + LOG.debug(_('Adding console')) password = self.driver.generate_password( self._get_vim_session(pool), @@ -84,9 +79,10 @@ class ConsoleVMRCManager(manager.Manager): @exception.wrap_exception def add_console(self, context, instance_id, password=None, port=None, **kwargs): - """ - Adds a console for the instance. If it is one time password, then we - generate new console credentials. + """Adds a console for the instance. + + If it is one time password, then we generate new console credentials. + """ instance = self.db.instance_get(context, instance_id) host = instance['host'] @@ -97,19 +93,17 @@ class ConsoleVMRCManager(manager.Manager): pool['id'], instance_id) if self.driver.is_otp(): - console = self._generate_console( - context, - pool, - name, - instance_id, - instance) + console = self._generate_console(context, + pool, + name, + instance_id, + instance) except exception.NotFound: - console = self._generate_console( - context, - pool, - name, - instance_id, - instance) + console = self._generate_console(context, + pool, + name, + instance_id, + instance) return console['id'] @exception.wrap_exception @@ -118,13 +112,11 @@ class ConsoleVMRCManager(manager.Manager): try: console = self.db.console_get(context, console_id) except exception.NotFound: - LOG.debug(_("Tried to remove non-existent console " - "%(console_id)s.") % - {'console_id': console_id}) + LOG.debug(_('Tried to remove non-existent console ' + '%(console_id)s.') % {'console_id': console_id}) return - LOG.debug(_("Removing console " - "%(console_id)s.") % - {'console_id': console_id}) + LOG.debug(_('Removing console ' + '%(console_id)s.') % {'console_id': console_id}) self.db.console_delete(context, console_id) self.driver.teardown_console(context, console) @@ -139,11 +131,11 @@ class ConsoleVMRCManager(manager.Manager): console_type) except exception.NotFound: pool_info = rpc.call(context, - self.db.queue_get_for(context, - FLAGS.compute_topic, - instance_host), - {"method": "get_console_pool_info", - "args": {"console_type": console_type}}) + self.db.queue_get_for(context, + FLAGS.compute_topic, + instance_host), + {'method': 'get_console_pool_info', + 'args': {'console_type': console_type}}) pool_info['password'] = self.driver.fix_pool_password( pool_info['password']) pool_info['host'] = self.host diff --git a/nova/console/xvp.py b/nova/console/xvp.py index 0cedfbb13..3cd287183 100644 --- a/nova/console/xvp.py +++ b/nova/console/xvp.py @@ -15,16 +15,14 @@ # License for the specific language governing permissions and limitations # under the License. -""" -XVP (Xenserver VNC Proxy) driver. -""" +"""XVP (Xenserver VNC Proxy) driver.""" import fcntl import os import signal import subprocess -from Cheetah.Template import Template +from Cheetah import Template from nova import context from nova import db @@ -33,6 +31,8 @@ from nova import flags from nova import log as logging from nova import utils + +FLAGS = flags.FLAGS flags.DEFINE_string('console_xvp_conf_template', utils.abspath('console/xvp.conf.template'), 'XVP conf template') @@ -47,12 +47,11 @@ flags.DEFINE_string('console_xvp_log', 'XVP log file') flags.DEFINE_integer('console_xvp_multiplex_port', 5900, - "port for XVP to multiplex VNC connections on") -FLAGS = flags.FLAGS + 'port for XVP to multiplex VNC connections on') class XVPConsoleProxy(object): - """Sets up XVP config, and manages xvp daemon""" + """Sets up XVP config, and manages XVP daemon.""" def __init__(self): self.xvpconf_template = open(FLAGS.console_xvp_conf_template).read() @@ -61,50 +60,51 @@ class XVPConsoleProxy(object): @property def console_type(self): - return "vnc+xvp" + return 'vnc+xvp' def get_port(self, context): - """get available port for consoles that need one""" + """Get available port for consoles that need one.""" #TODO(mdragon): implement port selection for non multiplex ports, # we are not using that, but someone else may want # it. return FLAGS.console_xvp_multiplex_port def setup_console(self, context, console): - """Sets up actual proxies""" + """Sets up actual proxies.""" self._rebuild_xvp_conf(context.elevated()) def teardown_console(self, context, console): - """Tears down actual proxies""" + """Tears down actual proxies.""" self._rebuild_xvp_conf(context.elevated()) def init_host(self): - """Start up any config'ed consoles on start""" + """Start up any config'ed consoles on start.""" ctxt = context.get_admin_context() self._rebuild_xvp_conf(ctxt) def fix_pool_password(self, password): - """Trim password to length, and encode""" + """Trim password to length, and encode.""" return self._xvp_encrypt(password, is_pool_password=True) def fix_console_password(self, password): - """Trim password to length, and encode""" + """Trim password to length, and encode.""" return self._xvp_encrypt(password) def _rebuild_xvp_conf(self, context): - logging.debug(_("Rebuilding xvp conf")) + logging.debug(_('Rebuilding xvp conf')) pools = [pool for pool in db.console_pool_get_all_by_host_type(context, self.host, self.console_type) if pool['consoles']] if not pools: - logging.debug("No console pools!") + logging.debug('No console pools!') self._xvp_stop() return conf_data = {'multiplex_port': FLAGS.console_xvp_multiplex_port, 'pools': pools, 'pass_encode': self.fix_console_password} - config = str(Template(self.xvpconf_template, searchList=[conf_data])) + config = str(Template.Template(self.xvpconf_template, + searchList=[conf_data])) self._write_conf(config) self._xvp_restart() @@ -114,7 +114,7 @@ class XVPConsoleProxy(object): cfile.write(config) def _xvp_stop(self): - logging.debug(_("Stopping xvp")) + logging.debug(_('Stopping xvp')) pid = self._xvp_pid() if not pid: return @@ -127,19 +127,19 @@ class XVPConsoleProxy(object): def _xvp_start(self): if self._xvp_check_running(): return - logging.debug(_("Starting xvp")) + logging.debug(_('Starting xvp')) try: utils.execute('xvp', '-p', FLAGS.console_xvp_pid, '-c', FLAGS.console_xvp_conf, '-l', FLAGS.console_xvp_log) except exception.ProcessExecutionError, err: - logging.error(_("Error starting xvp: %s") % err) + logging.error(_('Error starting xvp: %s') % err) def _xvp_restart(self): - logging.debug(_("Restarting xvp")) + logging.debug(_('Restarting xvp')) if not self._xvp_check_running(): - logging.debug(_("xvp not running...")) + logging.debug(_('xvp not running...')) self._xvp_start() else: pid = self._xvp_pid() @@ -178,7 +178,9 @@ class XVPConsoleProxy(object): Note that xvp's obfuscation should not be considered 'real' encryption. It simply DES encrypts the passwords with static keys plainly viewable - in the xvp source code.""" + in the xvp source code. + + """ maxlen = 8 flag = '-e' if is_pool_password: -- cgit From 740547fcb1aa02c31a362d1be2d4a27b3799e36a Mon Sep 17 00:00:00 2001 From: termie Date: Wed, 20 Apr 2011 12:06:10 -0700 Subject: fixed indentation --- nova/console/vmrc_manager.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/console/vmrc_manager.py b/nova/console/vmrc_manager.py index 3c6b77782..acecc1075 100644 --- a/nova/console/vmrc_manager.py +++ b/nova/console/vmrc_manager.py @@ -51,10 +51,11 @@ class ConsoleVMRCManager(manager.Manager): """Get VIM session for the pool specified.""" vim_session = None if pool['id'] not in self.sessions.keys(): - vim_session = vmwareapi_conn.VMWareAPISession(pool['address'], - pool['username'], - pool['password'], - FLAGS.console_vmrc_error_retries) + vim_session = vmwareapi_conn.VMWareAPISession( + pool['address'], + pool['username'], + pool['password'], + FLAGS.console_vmrc_error_retries) self.sessions[pool['id']] = vim_session return self.sessions[pool['id']] -- cgit From f69600e1844898bd48dc8f615c6684044d9aebe0 Mon Sep 17 00:00:00 2001 From: termie Date: Wed, 20 Apr 2011 12:08:22 -0700 Subject: docstring cleanup, nova dir --- nova/context.py | 10 +++- nova/crypto.py | 61 +++++++++++---------- nova/exception.py | 25 +++++---- nova/fakememcache.py | 4 +- nova/flags.py | 16 ++++-- nova/log.py | 49 ++++++++--------- nova/manager.py | 34 ++++++++---- nova/quota.py | 23 ++++---- nova/rpc.py | 152 +++++++++++++++++++++++++++++---------------------- nova/service.py | 83 +++++++++++++--------------- nova/test.py | 50 +++++++++-------- nova/utils.py | 147 ++++++++++++++++++++++++------------------------- nova/version.py | 6 +- nova/wsgi.py | 144 ++++++++++++++++++++++++------------------------ 14 files changed, 422 insertions(+), 382 deletions(-) (limited to 'nova') diff --git a/nova/context.py b/nova/context.py index 0256bf448..c113f7ea7 100644 --- a/nova/context.py +++ b/nova/context.py @@ -16,9 +16,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" -RequestContext: context for requests that persist through all of nova. -""" +"""RequestContext: context for requests that persist through all of nova.""" import datetime import random @@ -28,6 +26,12 @@ from nova import utils class RequestContext(object): + """Security context and request information. + + Represents the user taking a given action within the system. + + """ + def __init__(self, user, project, is_admin=None, read_deleted=False, remote_address=None, timestamp=None, request_id=None): if hasattr(user, 'id'): diff --git a/nova/crypto.py b/nova/crypto.py index 605be2a32..14b9cbef6 100644 --- a/nova/crypto.py +++ b/nova/crypto.py @@ -15,10 +15,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. -""" -Wrappers around standard crypto data elements. + +"""Wrappers around standard crypto data elements. Includes root and intermediate CAs, SSH key_pairs and x509 certificates. + """ import base64 @@ -43,6 +44,8 @@ from nova import log as logging LOG = logging.getLogger("nova.crypto") + + FLAGS = flags.FLAGS flags.DEFINE_string('ca_file', 'cacert.pem', _('Filename of root CA')) flags.DEFINE_string('key_file', @@ -90,13 +93,13 @@ def key_path(project_id=None): def fetch_ca(project_id=None, chain=True): if not FLAGS.use_project_ca: project_id = None - buffer = "" + buffer = '' if project_id: - with open(ca_path(project_id), "r") as cafile: + with open(ca_path(project_id), 'r') as cafile: buffer += cafile.read() if not chain: return buffer - with open(ca_path(None), "r") as cafile: + with open(ca_path(None), 'r') as cafile: buffer += cafile.read() return buffer @@ -143,7 +146,7 @@ def ssl_pub_to_ssh_pub(ssl_public_key, name='root', suffix='nova'): def revoke_cert(project_id, file_name): - """Revoke a cert by file name""" + """Revoke a cert by file name.""" start = os.getcwd() os.chdir(ca_folder(project_id)) # NOTE(vish): potential race condition here @@ -155,14 +158,14 @@ def revoke_cert(project_id, file_name): def revoke_certs_by_user(user_id): - """Revoke all user certs""" + """Revoke all user certs.""" admin = context.get_admin_context() for cert in db.certificate_get_all_by_user(admin, user_id): revoke_cert(cert['project_id'], cert['file_name']) def revoke_certs_by_project(project_id): - """Revoke all project certs""" + """Revoke all project certs.""" # NOTE(vish): This is somewhat useless because we can just shut down # the vpn. admin = context.get_admin_context() @@ -171,29 +174,29 @@ def revoke_certs_by_project(project_id): def revoke_certs_by_user_and_project(user_id, project_id): - """Revoke certs for user in project""" + """Revoke certs for user in project.""" admin = context.get_admin_context() for cert in db.certificate_get_all_by_user(admin, user_id, project_id): revoke_cert(cert['project_id'], cert['file_name']) def _project_cert_subject(project_id): - """Helper to generate user cert subject""" + """Helper to generate user cert subject.""" return FLAGS.project_cert_subject % (project_id, utils.isotime()) def _vpn_cert_subject(project_id): - """Helper to generate user cert subject""" + """Helper to generate user cert subject.""" return FLAGS.vpn_cert_subject % (project_id, utils.isotime()) def _user_cert_subject(user_id, project_id): - """Helper to generate user cert subject""" + """Helper to generate user cert subject.""" return FLAGS.user_cert_subject % (project_id, user_id, utils.isotime()) def generate_x509_cert(user_id, project_id, bits=1024): - """Generate and sign a cert for user in project""" + """Generate and sign a cert for user in project.""" subject = _user_cert_subject(user_id, project_id) tmpdir = tempfile.mkdtemp() keyfile = os.path.abspath(os.path.join(tmpdir, 'temp.key')) @@ -205,7 +208,7 @@ def generate_x509_cert(user_id, project_id, bits=1024): csr = open(csrfile).read() shutil.rmtree(tmpdir) (serial, signed_csr) = sign_csr(csr, project_id) - fname = os.path.join(ca_folder(project_id), "newcerts/%s.pem" % serial) + fname = os.path.join(ca_folder(project_id), 'newcerts/%s.pem' % serial) cert = {'user_id': user_id, 'project_id': project_id, 'file_name': fname} @@ -227,8 +230,8 @@ def _ensure_project_folder(project_id): def generate_vpn_files(project_id): project_folder = ca_folder(project_id) - csr_fn = os.path.join(project_folder, "server.csr") - crt_fn = os.path.join(project_folder, "server.crt") + csr_fn = os.path.join(project_folder, 'server.csr') + crt_fn = os.path.join(project_folder, 'server.crt') genvpn_sh_path = os.path.join(os.path.dirname(__file__), 'CA', @@ -241,10 +244,10 @@ def generate_vpn_files(project_id): # TODO(vish): the shell scripts could all be done in python utils.execute('sh', genvpn_sh_path, project_id, _vpn_cert_subject(project_id)) - with open(csr_fn, "r") as csrfile: + with open(csr_fn, 'r') as csrfile: csr_text = csrfile.read() (serial, signed_csr) = sign_csr(csr_text, project_id) - with open(crt_fn, "w") as crtfile: + with open(crt_fn, 'w') as crtfile: crtfile.write(signed_csr) os.chdir(start) @@ -261,12 +264,12 @@ def sign_csr(csr_text, project_id=None): def _sign_csr(csr_text, ca_folder): tmpfolder = tempfile.mkdtemp() - inbound = os.path.join(tmpfolder, "inbound.csr") - outbound = os.path.join(tmpfolder, "outbound.csr") - csrfile = open(inbound, "w") + inbound = os.path.join(tmpfolder, 'inbound.csr') + outbound = os.path.join(tmpfolder, 'outbound.csr') + csrfile = open(inbound, 'w') csrfile.write(csr_text) csrfile.close() - LOG.debug(_("Flags path: %s"), ca_folder) + LOG.debug(_('Flags path: %s'), ca_folder) start = os.getcwd() # Change working dir to CA if not os.path.exists(ca_folder): @@ -276,13 +279,13 @@ def _sign_csr(csr_text, ca_folder): './openssl.cnf', '-infiles', inbound) out, _err = utils.execute('openssl', 'x509', '-in', outbound, '-serial', '-noout') - serial = string.strip(out.rpartition("=")[2]) + serial = string.strip(out.rpartition('=')[2]) os.chdir(start) - with open(outbound, "r") as crtfile: + with open(outbound, 'r') as crtfile: return (serial, crtfile.read()) -def mkreq(bits, subject="foo", ca=0): +def mkreq(bits, subject='foo', ca=0): pk = M2Crypto.EVP.PKey() req = M2Crypto.X509.Request() rsa = M2Crypto.RSA.gen_key(bits, 65537, callback=lambda: None) @@ -314,7 +317,7 @@ def mkcacert(subject='nova', years=1): cert.set_not_before(now) cert.set_not_after(nowPlusYear) issuer = M2Crypto.X509.X509_Name() - issuer.C = "US" + issuer.C = 'US' issuer.CN = subject cert.set_issuer(issuer) cert.set_pubkey(pkey) @@ -352,13 +355,15 @@ def mkcacert(subject='nova', years=1): # http://code.google.com/p/boto def compute_md5(fp): - """ + """Compute an md5 hash. + :type fp: file :param fp: File pointer to the file to MD5 hash. The file pointer will be reset to the beginning of the file before the method returns. :rtype: tuple - :return: the hex digest version of the MD5 hash + :returns: the hex digest version of the MD5 hash + """ m = hashlib.md5() fp.seek(0) diff --git a/nova/exception.py b/nova/exception.py index 4e2bbdbaf..3123b2f1f 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -16,31 +16,34 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Nova base exception handling, including decorator for re-raising -Nova-type exceptions. SHOULD include dedicated exception logging. +"""Nova base exception handling. + +Includes decorator for re-raising Nova-type exceptions. + +SHOULD include dedicated exception logging. + """ from nova import log as logging + + LOG = logging.getLogger('nova.exception') class ProcessExecutionError(IOError): - def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None, description=None): if description is None: - description = _("Unexpected error while running command.") + description = _('Unexpected error while running command.') if exit_code is None: exit_code = '-' - message = _("%(description)s\nCommand: %(cmd)s\n" - "Exit code: %(exit_code)s\nStdout: %(stdout)r\n" - "Stderr: %(stderr)r") % locals() + message = _('%(description)s\nCommand: %(cmd)s\n' + 'Exit code: %(exit_code)s\nStdout: %(stdout)r\n' + 'Stderr: %(stderr)r') % locals() IOError.__init__(self, message) class Error(Exception): - def __init__(self, message=None): super(Error, self).__init__(message) @@ -97,7 +100,7 @@ class TimeoutException(Error): class DBError(Error): - """Wraps an implementation specific exception""" + """Wraps an implementation specific exception.""" def __init__(self, inner_exception): self.inner_exception = inner_exception super(DBError, self).__init__(str(inner_exception)) @@ -108,7 +111,7 @@ def wrap_db_error(f): try: return f(*args, **kwargs) except Exception, e: - LOG.exception(_('DB exception wrapped')) + LOG.exception(_('DB exception wrapped.')) raise DBError(e) return _wrap _wrap.func_name = f.func_name diff --git a/nova/fakememcache.py b/nova/fakememcache.py index 67f46dbdc..e4f238aa9 100644 --- a/nova/fakememcache.py +++ b/nova/fakememcache.py @@ -18,14 +18,14 @@ """Super simple fake memcache client.""" -import utils +from nova import utils class Client(object): """Replicates a tiny subset of memcached client interface.""" def __init__(self, *args, **kwargs): - """Ignores the passed in args""" + """Ignores the passed in args.""" self.cache = {} def get(self, key): diff --git a/nova/flags.py b/nova/flags.py index f011ab383..d1b93f0a8 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -16,9 +16,13 @@ # License for the specific language governing permissions and limitations # under the License. -""" +"""Command-line flag library. + +Wraps gflags. + Package-level global flags are defined here, the rest are defined where they're used. + """ import getopt @@ -145,10 +149,12 @@ class FlagValues(gflags.FlagValues): class StrWrapper(object): - """Wrapper around FlagValues objects + """Wrapper around FlagValues objects. Wraps FlagValues objects for string.Template so that we're - sure to return strings.""" + sure to return strings. + + """ def __init__(self, context_objs): self.context_objs = context_objs @@ -169,6 +175,7 @@ def _GetCallingModule(): We generally use this function to get the name of the module calling a DEFINE_foo... function. + """ # Walk down the stack to find the first globals dict that's not ours. for depth in range(1, sys.getrecursionlimit()): @@ -192,6 +199,7 @@ def __GetModuleName(globals_dict): Returns: A string (the name of the module) or None (if the module could not be identified. + """ for name, module in sys.modules.iteritems(): if getattr(module, '__dict__', None) is globals_dict: @@ -326,7 +334,7 @@ DEFINE_integer('auth_token_ttl', 3600, 'Seconds for auth tokens to linger') DEFINE_string('state_path', os.path.join(os.path.dirname(__file__), '../'), "Top-level directory for maintaining nova's state") DEFINE_string('lock_path', os.path.join(os.path.dirname(__file__), '../'), - "Directory for lock files") + 'Directory for lock files') DEFINE_string('logdir', None, 'output to a per-service log file in named ' 'directory') diff --git a/nova/log.py b/nova/log.py index ea94be194..096279f7c 100644 --- a/nova/log.py +++ b/nova/log.py @@ -16,16 +16,15 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Nova logging handler. +"""Nova logging handler. This module adds to logging functionality by adding the option to specify a context object when calling the various log methods. If the context object is not specified, default formatting is used. It also allows setting of formatting information through flags. -""" +""" import cStringIO import inspect @@ -41,34 +40,28 @@ from nova import version FLAGS = flags.FLAGS - flags.DEFINE_string('logging_context_format_string', '%(asctime)s %(levelname)s %(name)s ' '[%(request_id)s %(user)s ' '%(project)s] %(message)s', 'format string to use for log messages with context') - flags.DEFINE_string('logging_default_format_string', '%(asctime)s %(levelname)s %(name)s [-] ' '%(message)s', 'format string to use for log messages without context') - flags.DEFINE_string('logging_debug_format_suffix', 'from (pid=%(process)d) %(funcName)s' ' %(pathname)s:%(lineno)d', 'data to append to log format when level is DEBUG') - flags.DEFINE_string('logging_exception_prefix', '(%(name)s): TRACE: ', 'prefix each line of exception output with this format') - flags.DEFINE_list('default_log_levels', ['amqplib=WARN', 'sqlalchemy=WARN', 'boto=WARN', 'eventlet.wsgi.server=WARN'], 'list of logger=LEVEL pairs') - flags.DEFINE_bool('use_syslog', False, 'output to syslog') flags.DEFINE_string('logfile', None, 'output to named file') @@ -83,6 +76,8 @@ WARN = logging.WARN INFO = logging.INFO DEBUG = logging.DEBUG NOTSET = logging.NOTSET + + # methods getLogger = logging.getLogger debug = logging.debug @@ -93,6 +88,8 @@ error = logging.error exception = logging.exception critical = logging.critical log = logging.log + + # handlers StreamHandler = logging.StreamHandler WatchedFileHandler = logging.handlers.WatchedFileHandler @@ -127,17 +124,18 @@ def _get_log_file_path(binary=None): class NovaLogger(logging.Logger): - """ - NovaLogger manages request context and formatting. + """NovaLogger manages request context and formatting. This becomes the class that is instanciated by logging.getLogger. + """ + def __init__(self, name, level=NOTSET): logging.Logger.__init__(self, name, level) self.setup_from_flags() def setup_from_flags(self): - """Setup logger from flags""" + """Setup logger from flags.""" level = NOTSET for pair in FLAGS.default_log_levels: logger, _sep, level_name = pair.partition('=') @@ -148,7 +146,7 @@ class NovaLogger(logging.Logger): self.setLevel(level) def _log(self, level, msg, args, exc_info=None, extra=None, context=None): - """Extract context from any log call""" + """Extract context from any log call.""" if not extra: extra = {} if context: @@ -157,17 +155,17 @@ class NovaLogger(logging.Logger): return logging.Logger._log(self, level, msg, args, exc_info, extra) def addHandler(self, handler): - """Each handler gets our custom formatter""" + """Each handler gets our custom formatter.""" handler.setFormatter(_formatter) return logging.Logger.addHandler(self, handler) def audit(self, msg, *args, **kwargs): - """Shortcut for our AUDIT level""" + """Shortcut for our AUDIT level.""" if self.isEnabledFor(AUDIT): self._log(AUDIT, msg, args, **kwargs) def exception(self, msg, *args, **kwargs): - """Logging.exception doesn't handle kwargs, so breaks context""" + """Logging.exception doesn't handle kwargs, so breaks context.""" if not kwargs.get('exc_info'): kwargs['exc_info'] = 1 self.error(msg, *args, **kwargs) @@ -181,14 +179,13 @@ class NovaLogger(logging.Logger): for k in env.keys(): if not isinstance(env[k], str): env.pop(k) - message = "Environment: %s" % json.dumps(env) + message = 'Environment: %s' % json.dumps(env) kwargs.pop('exc_info') self.error(message, **kwargs) class NovaFormatter(logging.Formatter): - """ - A nova.context.RequestContext aware formatter configured through flags. + """A nova.context.RequestContext aware formatter configured through flags. The flags used to set format strings are: logging_context_foramt_string and logging_default_format_string. You can also specify @@ -197,10 +194,11 @@ class NovaFormatter(logging.Formatter): For information about what variables are available for the formatter see: http://docs.python.org/library/logging.html#formatter + """ def format(self, record): - """Uses contextstring if request_id is set, otherwise default""" + """Uses contextstring if request_id is set, otherwise default.""" if record.__dict__.get('request_id', None): self._fmt = FLAGS.logging_context_format_string else: @@ -214,20 +212,21 @@ class NovaFormatter(logging.Formatter): return logging.Formatter.format(self, record) def formatException(self, exc_info, record=None): - """Format exception output with FLAGS.logging_exception_prefix""" + """Format exception output with FLAGS.logging_exception_prefix.""" if not record: return logging.Formatter.formatException(self, exc_info) stringbuffer = cStringIO.StringIO() traceback.print_exception(exc_info[0], exc_info[1], exc_info[2], None, stringbuffer) - lines = stringbuffer.getvalue().split("\n") + lines = stringbuffer.getvalue().split('\n') stringbuffer.close() formatted_lines = [] for line in lines: pl = FLAGS.logging_exception_prefix % record.__dict__ - fl = "%s%s" % (pl, line) + fl = '%s%s' % (pl, line) formatted_lines.append(fl) - return "\n".join(formatted_lines) + return '\n'.join(formatted_lines) + _formatter = NovaFormatter() @@ -241,7 +240,7 @@ class NovaRootLogger(NovaLogger): NovaLogger.__init__(self, name, level) def setup_from_flags(self): - """Setup logger from flags""" + """Setup logger from flags.""" global _filelog if FLAGS.use_syslog: self.syslog = SysLogHandler(address='/dev/log') diff --git a/nova/manager.py b/nova/manager.py index 804a50479..34338ac04 100644 --- a/nova/manager.py +++ b/nova/manager.py @@ -16,7 +16,8 @@ # License for the specific language governing permissions and limitations # under the License. -""" +"""Base Manager class. + Managers are responsible for a certain aspect of the sytem. It is a logical grouping of code relating to a portion of the system. In general other components should be using the manager to make changes to the components that @@ -49,16 +50,19 @@ Managers will often provide methods for initial setup of a host or periodic tasksto a wrapping service. This module provides Manager, a base class for managers. + """ -from nova import utils from nova import flags from nova import log as logging +from nova import utils from nova.db import base from nova.scheduler import api + FLAGS = flags.FLAGS + LOG = logging.getLogger('nova.manager') @@ -70,23 +74,29 @@ class Manager(base.Base): super(Manager, self).__init__(db_driver) def periodic_tasks(self, context=None): - """Tasks to be run at a periodic interval""" + """Tasks to be run at a periodic interval.""" pass def init_host(self): - """Do any initialization that needs to be run if this is a standalone - service. Child classes should override this method.""" + """Handle initialization if this is a standalone service. + + Child classes should override this method. + + """ pass class SchedulerDependentManager(Manager): """Periodically send capability updates to the Scheduler services. - Services that need to update the Scheduler of their capabilities - should derive from this class. Otherwise they can derive from - manager.Manager directly. Updates are only sent after - update_service_capabilities is called with non-None values.""" - def __init__(self, host=None, db_driver=None, service_name="undefined"): + Services that need to update the Scheduler of their capabilities + should derive from this class. Otherwise they can derive from + manager.Manager directly. Updates are only sent after + update_service_capabilities is called with non-None values. + + """ + + def __init__(self, host=None, db_driver=None, service_name='undefined'): self.last_capabilities = None self.service_name = service_name super(SchedulerDependentManager, self).__init__(host, db_driver) @@ -96,9 +106,9 @@ class SchedulerDependentManager(Manager): self.last_capabilities = capabilities def periodic_tasks(self, context=None): - """Pass data back to the scheduler at a periodic interval""" + """Pass data back to the scheduler at a periodic interval.""" if self.last_capabilities: - LOG.debug(_("Notifying Schedulers of capabilities ...")) + LOG.debug(_('Notifying Schedulers of capabilities ...')) api.update_service_capabilities(context, self.service_name, self.host, self.last_capabilities) diff --git a/nova/quota.py b/nova/quota.py index 2b24c0b5b..d8b5d9a93 100644 --- a/nova/quota.py +++ b/nova/quota.py @@ -15,16 +15,15 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -""" -Quotas for instances, volumes, and floating ips -""" + +"""Quotas for instances, volumes, and floating ips.""" from nova import db from nova import exception from nova import flags -FLAGS = flags.FLAGS +FLAGS = flags.FLAGS flags.DEFINE_integer('quota_instances', 10, 'number of instances allowed per project') flags.DEFINE_integer('quota_cores', 20, @@ -64,7 +63,7 @@ def get_quota(context, project_id): def allowed_instances(context, num_instances, instance_type): - """Check quota and return min(num_instances, allowed_instances)""" + """Check quota and return min(num_instances, allowed_instances).""" project_id = context.project_id context = context.elevated() used_instances, used_cores = db.instance_data_get_for_project(context, @@ -79,7 +78,7 @@ def allowed_instances(context, num_instances, instance_type): def allowed_volumes(context, num_volumes, size): - """Check quota and return min(num_volumes, allowed_volumes)""" + """Check quota and return min(num_volumes, allowed_volumes).""" project_id = context.project_id context = context.elevated() used_volumes, used_gigabytes = db.volume_data_get_for_project(context, @@ -95,7 +94,7 @@ def allowed_volumes(context, num_volumes, size): def allowed_floating_ips(context, num_floating_ips): - """Check quota and return min(num_floating_ips, allowed_floating_ips)""" + """Check quota and return min(num_floating_ips, allowed_floating_ips).""" project_id = context.project_id context = context.elevated() used_floating_ips = db.floating_ip_count_by_project(context, project_id) @@ -105,7 +104,7 @@ def allowed_floating_ips(context, num_floating_ips): def allowed_metadata_items(context, num_metadata_items): - """Check quota; return min(num_metadata_items,allowed_metadata_items)""" + """Check quota; return min(num_metadata_items,allowed_metadata_items).""" project_id = context.project_id context = context.elevated() quota = get_quota(context, project_id) @@ -114,20 +113,20 @@ def allowed_metadata_items(context, num_metadata_items): def allowed_injected_files(context): - """Return the number of injected files allowed""" + """Return the number of injected files allowed.""" return FLAGS.quota_max_injected_files def allowed_injected_file_content_bytes(context): - """Return the number of bytes allowed per injected file content""" + """Return the number of bytes allowed per injected file content.""" return FLAGS.quota_max_injected_file_content_bytes def allowed_injected_file_path_bytes(context): - """Return the number of bytes allowed in an injected file path""" + """Return the number of bytes allowed in an injected file path.""" return FLAGS.quota_max_injected_file_path_bytes class QuotaError(exception.ApiError): - """Quota Exceeeded""" + """Quota Exceeeded.""" pass diff --git a/nova/rpc.py b/nova/rpc.py index b610cdf9b..2116f22c3 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -16,9 +16,12 @@ # License for the specific language governing permissions and limitations # under the License. -""" -AMQP-based RPC. Queues have consumers and publishers. +"""AMQP-based RPC. + +Queues have consumers and publishers. + No fan-out support yet. + """ import json @@ -40,17 +43,19 @@ from nova import log as logging from nova import utils -FLAGS = flags.FLAGS LOG = logging.getLogger('nova.rpc') + +FLAGS = flags.FLAGS flags.DEFINE_integer('rpc_thread_pool_size', 1024, 'Size of RPC thread pool') class Connection(carrot_connection.BrokerConnection): - """Connection instance object""" + """Connection instance object.""" + @classmethod def instance(cls, new=True): - """Returns the instance""" + """Returns the instance.""" if new or not hasattr(cls, '_instance'): params = dict(hostname=FLAGS.rabbit_host, port=FLAGS.rabbit_port, @@ -71,9 +76,11 @@ class Connection(carrot_connection.BrokerConnection): @classmethod def recreate(cls): - """Recreates the connection instance + """Recreates the connection instance. + + This is necessary to recover from some network errors/disconnects. - This is necessary to recover from some network errors/disconnects""" + """ try: del cls._instance except AttributeError, e: @@ -84,10 +91,12 @@ class Connection(carrot_connection.BrokerConnection): class Consumer(messaging.Consumer): - """Consumer base class + """Consumer base class. + + Contains methods for connecting the fetch method to async loops. - Contains methods for connecting the fetch method to async loops """ + def __init__(self, *args, **kwargs): for i in xrange(FLAGS.rabbit_max_retries): if i > 0: @@ -100,19 +109,18 @@ class Consumer(messaging.Consumer): fl_host = FLAGS.rabbit_host fl_port = FLAGS.rabbit_port fl_intv = FLAGS.rabbit_retry_interval - LOG.error(_("AMQP server on %(fl_host)s:%(fl_port)d is" - " unreachable: %(e)s. Trying again in %(fl_intv)d" - " seconds.") - % locals()) + LOG.error(_('AMQP server on %(fl_host)s:%(fl_port)d is' + ' unreachable: %(e)s. Trying again in %(fl_intv)d' + ' seconds.') % locals()) self.failed_connection = True if self.failed_connection: - LOG.error(_("Unable to connect to AMQP server " - "after %d tries. Shutting down."), + LOG.error(_('Unable to connect to AMQP server ' + 'after %d tries. Shutting down.'), FLAGS.rabbit_max_retries) sys.exit(1) def fetch(self, no_ack=None, auto_ack=None, enable_callbacks=False): - """Wraps the parent fetch with some logic for failed connections""" + """Wraps the parent fetch with some logic for failed connection.""" # TODO(vish): the logic for failed connections and logging should be # refactored into some sort of connection manager object try: @@ -125,14 +133,14 @@ class Consumer(messaging.Consumer): self.declare() super(Consumer, self).fetch(no_ack, auto_ack, enable_callbacks) if self.failed_connection: - LOG.error(_("Reconnected to queue")) + LOG.error(_('Reconnected to queue')) self.failed_connection = False # NOTE(vish): This is catching all errors because we really don't # want exceptions to be logged 10 times a second if some # persistent failure occurs. except Exception, e: # pylint: disable=W0703 if not self.failed_connection: - LOG.exception(_("Failed to fetch message from queue: %s" % e)) + LOG.exception(_('Failed to fetch message from queue: %s' % e)) self.failed_connection = True def attach_to_eventlet(self): @@ -143,8 +151,9 @@ class Consumer(messaging.Consumer): class AdapterConsumer(Consumer): - """Calls methods on a proxy object based on method and args""" - def __init__(self, connection=None, topic="broadcast", proxy=None): + """Calls methods on a proxy object based on method and args.""" + + def __init__(self, connection=None, topic='broadcast', proxy=None): LOG.debug(_('Initing the Adapter Consumer for %s') % topic) self.proxy = proxy self.pool = greenpool.GreenPool(FLAGS.rpc_thread_pool_size) @@ -156,13 +165,14 @@ class AdapterConsumer(Consumer): @exception.wrap_exception def _receive(self, message_data, message): - """Magically looks for a method on the proxy object and calls it + """Magically looks for a method on the proxy object and calls it. Message data should be a dictionary with two keys: method: string representing the method to call args: dictionary of arg: value Example: {'method': 'echo', 'args': {'value': 42}} + """ LOG.debug(_('received %s') % message_data) msg_id = message_data.pop('_msg_id', None) @@ -189,22 +199,23 @@ class AdapterConsumer(Consumer): if msg_id: msg_reply(msg_id, rval, None) except Exception as e: - logging.exception("Exception during message handling") + logging.exception('Exception during message handling') if msg_id: msg_reply(msg_id, None, sys.exc_info()) return class Publisher(messaging.Publisher): - """Publisher base class""" + """Publisher base class.""" pass class TopicAdapterConsumer(AdapterConsumer): - """Consumes messages on a specific topic""" - exchange_type = "topic" + """Consumes messages on a specific topic.""" + + exchange_type = 'topic' - def __init__(self, connection=None, topic="broadcast", proxy=None): + def __init__(self, connection=None, topic='broadcast', proxy=None): self.queue = topic self.routing_key = topic self.exchange = FLAGS.control_exchange @@ -214,27 +225,29 @@ class TopicAdapterConsumer(AdapterConsumer): class FanoutAdapterConsumer(AdapterConsumer): - """Consumes messages from a fanout exchange""" - exchange_type = "fanout" + """Consumes messages from a fanout exchange.""" - def __init__(self, connection=None, topic="broadcast", proxy=None): - self.exchange = "%s_fanout" % topic + exchange_type = 'fanout' + + def __init__(self, connection=None, topic='broadcast', proxy=None): + self.exchange = '%s_fanout' % topic self.routing_key = topic unique = uuid.uuid4().hex - self.queue = "%s_fanout_%s" % (topic, unique) + self.queue = '%s_fanout_%s' % (topic, unique) self.durable = False - LOG.info(_("Created '%(exchange)s' fanout exchange " - "with '%(key)s' routing key"), - dict(exchange=self.exchange, key=self.routing_key)) + LOG.info(_('Created "%(exchange)s" fanout exchange ' + 'with "%(key)s" routing key'), + dict(exchange=self.exchange, key=self.routing_key)) super(FanoutAdapterConsumer, self).__init__(connection=connection, topic=topic, proxy=proxy) class TopicPublisher(Publisher): - """Publishes messages on a specific topic""" - exchange_type = "topic" + """Publishes messages on a specific topic.""" + + exchange_type = 'topic' - def __init__(self, connection=None, topic="broadcast"): + def __init__(self, connection=None, topic='broadcast'): self.routing_key = topic self.exchange = FLAGS.control_exchange self.durable = False @@ -243,20 +256,22 @@ class TopicPublisher(Publisher): class FanoutPublisher(Publisher): """Publishes messages to a fanout exchange.""" - exchange_type = "fanout" + + exchange_type = 'fanout' def __init__(self, topic, connection=None): - self.exchange = "%s_fanout" % topic - self.queue = "%s_fanout" % topic + self.exchange = '%s_fanout' % topic + self.queue = '%s_fanout' % topic self.durable = False - LOG.info(_("Creating '%(exchange)s' fanout exchange"), - dict(exchange=self.exchange)) + LOG.info(_('Creating "%(exchange)s" fanout exchange'), + dict(exchange=self.exchange)) super(FanoutPublisher, self).__init__(connection=connection) class DirectConsumer(Consumer): - """Consumes messages directly on a channel specified by msg_id""" - exchange_type = "direct" + """Consumes messages directly on a channel specified by msg_id.""" + + exchange_type = 'direct' def __init__(self, connection=None, msg_id=None): self.queue = msg_id @@ -268,8 +283,9 @@ class DirectConsumer(Consumer): class DirectPublisher(Publisher): - """Publishes messages directly on a channel specified by msg_id""" - exchange_type = "direct" + """Publishes messages directly on a channel specified by msg_id.""" + + exchange_type = 'direct' def __init__(self, connection=None, msg_id=None): self.routing_key = msg_id @@ -279,9 +295,9 @@ class DirectPublisher(Publisher): def msg_reply(msg_id, reply=None, failure=None): - """Sends a reply or an error on the channel signified by msg_id + """Sends a reply or an error on the channel signified by msg_id. - failure should be a sys.exc_info() tuple. + Failure should be a sys.exc_info() tuple. """ if failure: @@ -303,17 +319,20 @@ def msg_reply(msg_id, reply=None, failure=None): class RemoteError(exception.Error): - """Signifies that a remote class has raised an exception + """Signifies that a remote class has raised an exception. Containes a string representation of the type of the original exception, the value of the original exception, and the traceback. These are sent to the parent as a joined string so printing the exception - contains all of the relevent info.""" + contains all of the relevent info. + + """ + def __init__(self, exc_type, value, traceback): self.exc_type = exc_type self.value = value self.traceback = traceback - super(RemoteError, self).__init__("%s %s\n%s" % (exc_type, + super(RemoteError, self).__init__('%s %s\n%s' % (exc_type, value, traceback)) @@ -339,6 +358,7 @@ def _pack_context(msg, context): context out into a bunch of separate keys. If we want to support more arguments in rabbit messages, we may want to do the same for args at some point. + """ context = dict([('_context_%s' % key, value) for (key, value) in context.to_dict().iteritems()]) @@ -346,11 +366,11 @@ def _pack_context(msg, context): def call(context, topic, msg): - """Sends a message on a topic and wait for a response""" - LOG.debug(_("Making asynchronous call on %s ..."), topic) + """Sends a message on a topic and wait for a response.""" + LOG.debug(_('Making asynchronous call on %s ...'), topic) msg_id = uuid.uuid4().hex msg.update({'_msg_id': msg_id}) - LOG.debug(_("MSG_ID is %s") % (msg_id)) + LOG.debug(_('MSG_ID is %s') % (msg_id)) _pack_context(msg, context) class WaitMessage(object): @@ -387,8 +407,8 @@ def call(context, topic, msg): def cast(context, topic, msg): - """Sends a message on a topic without waiting for a response""" - LOG.debug(_("Making asynchronous cast on %s..."), topic) + """Sends a message on a topic without waiting for a response.""" + LOG.debug(_('Making asynchronous cast on %s...'), topic) _pack_context(msg, context) conn = Connection.instance() publisher = TopicPublisher(connection=conn, topic=topic) @@ -397,8 +417,8 @@ def cast(context, topic, msg): def fanout_cast(context, topic, msg): - """Sends a message on a fanout exchange without waiting for a response""" - LOG.debug(_("Making asynchronous fanout cast...")) + """Sends a message on a fanout exchange without waiting for a response.""" + LOG.debug(_('Making asynchronous fanout cast...')) _pack_context(msg, context) conn = Connection.instance() publisher = FanoutPublisher(topic, connection=conn) @@ -407,14 +427,14 @@ def fanout_cast(context, topic, msg): def generic_response(message_data, message): - """Logs a result and exits""" + """Logs a result and exits.""" LOG.debug(_('response %s'), message_data) message.ack() sys.exit(0) def send_message(topic, message, wait=True): - """Sends a message for testing""" + """Sends a message for testing.""" msg_id = uuid.uuid4().hex message.update({'_msg_id': msg_id}) LOG.debug(_('topic is %s'), topic) @@ -425,14 +445,14 @@ def send_message(topic, message, wait=True): queue=msg_id, exchange=msg_id, auto_delete=True, - exchange_type="direct", + exchange_type='direct', routing_key=msg_id) consumer.register_callback(generic_response) publisher = messaging.Publisher(connection=Connection.instance(), exchange=FLAGS.control_exchange, durable=False, - exchange_type="topic", + exchange_type='topic', routing_key=topic) publisher.send(message) publisher.close() @@ -441,8 +461,8 @@ def send_message(topic, message, wait=True): consumer.wait() -if __name__ == "__main__": - # NOTE(vish): you can send messages from the command line using - # topic and a json sting representing a dictionary - # for the method +if __name__ == '__main__': + # You can send messages from the command line using + # topic and a json string representing a dictionary + # for the method send_message(sys.argv[1], json.loads(sys.argv[2])) diff --git a/nova/service.py b/nova/service.py index 47c0b96c0..2532b9df2 100644 --- a/nova/service.py +++ b/nova/service.py @@ -17,9 +17,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Generic Node baseclass for all workers that run on hosts -""" +"""Generic Node baseclass for all workers that run on hosts.""" import inspect import os @@ -30,13 +28,11 @@ from eventlet import event from eventlet import greenthread from eventlet import greenpool -from sqlalchemy.exc import OperationalError - from nova import context from nova import db from nova import exception -from nova import log as logging from nova import flags +from nova import log as logging from nova import rpc from nova import utils from nova import version @@ -79,7 +75,7 @@ class Service(object): def start(self): vcs_string = version.version_string_with_vcs() - logging.audit(_("Starting %(topic)s node (version %(vcs_string)s)"), + logging.audit(_('Starting %(topic)s node (version %(vcs_string)s)'), {'topic': self.topic, 'vcs_string': vcs_string}) self.manager.init_host() self.model_disconnected = False @@ -140,29 +136,24 @@ class Service(object): return getattr(manager, key) @classmethod - def create(cls, - host=None, - binary=None, - topic=None, - manager=None, - report_interval=None, - periodic_interval=None): + def create(cls, host=None, binary=None, topic=None, manager=None, + report_interval=None, periodic_interval=None): """Instantiates class and passes back application object. - Args: - host, defaults to FLAGS.host - binary, defaults to basename of executable - topic, defaults to bin_name - "nova-" part - manager, defaults to FLAGS._manager - report_interval, defaults to FLAGS.report_interval - periodic_interval, defaults to FLAGS.periodic_interval + :param host: defaults to FLAGS.host + :param binary: defaults to basename of executable + :param topic: defaults to bin_name - 'nova-' part + :param manager: defaults to FLAGS._manager + :param report_interval: defaults to FLAGS.report_interval + :param periodic_interval: defaults to FLAGS.periodic_interval + """ if not host: host = FLAGS.host if not binary: binary = os.path.basename(inspect.stack()[-1][1]) if not topic: - topic = binary.rpartition("nova-")[2] + topic = binary.rpartition('nova-')[2] if not manager: manager = FLAGS.get('%s_manager' % topic, None) if not report_interval: @@ -175,12 +166,12 @@ class Service(object): return service_obj def kill(self): - """Destroy the service object in the datastore""" + """Destroy the service object in the datastore.""" self.stop() try: db.service_destroy(context.get_admin_context(), self.service_id) except exception.NotFound: - logging.warn(_("Service killed that has no database entry")) + logging.warn(_('Service killed that has no database entry')) def stop(self): for x in self.timers: @@ -198,7 +189,7 @@ class Service(object): pass def periodic_tasks(self): - """Tasks to be run at a periodic interval""" + """Tasks to be run at a periodic interval.""" self.manager.periodic_tasks(context.get_admin_context()) def report_state(self): @@ -208,8 +199,8 @@ class Service(object): try: service_ref = db.service_get(ctxt, self.service_id) except exception.NotFound: - logging.debug(_("The service database object disappeared, " - "Recreating it.")) + logging.debug(_('The service database object disappeared, ' + 'Recreating it.')) self._create_service_ref(ctxt) service_ref = db.service_get(ctxt, self.service_id) @@ -218,23 +209,24 @@ class Service(object): {'report_count': service_ref['report_count'] + 1}) # TODO(termie): make this pattern be more elegant. - if getattr(self, "model_disconnected", False): + if getattr(self, 'model_disconnected', False): self.model_disconnected = False - logging.error(_("Recovered model server connection!")) + logging.error(_('Recovered model server connection!')) # TODO(vish): this should probably only catch connection errors except Exception: # pylint: disable=W0702 - if not getattr(self, "model_disconnected", False): + if not getattr(self, 'model_disconnected', False): self.model_disconnected = True - logging.exception(_("model server went away")) + logging.exception(_('model server went away')) class WsgiService(object): """Base class for WSGI based services. For each api you define, you must also define these flags: - :_listen: The address on which to listen - :_listen_port: The port on which to listen + :_listen: The address on which to listen + :_listen_port: The port on which to listen + """ def __init__(self, conf, apis): @@ -250,13 +242,14 @@ class WsgiService(object): class ApiService(WsgiService): - """Class for our nova-api service""" + """Class for our nova-api service.""" + @classmethod def create(cls, conf=None): if not conf: conf = wsgi.paste_config_file(FLAGS.api_paste_config) if not conf: - message = (_("No paste configuration found for: %s"), + message = (_('No paste configuration found for: %s'), FLAGS.api_paste_config) raise exception.Error(message) api_endpoints = ['ec2', 'osapi'] @@ -280,11 +273,11 @@ def serve(*services): FLAGS.ParseNewFlags() name = '_'.join(x.binary for x in services) - logging.debug(_("Serving %s"), name) - logging.debug(_("Full set of FLAGS:")) + 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()) + logging.debug('%(flag)s : %(flag_get)s' % locals()) for x in services: x.start() @@ -315,20 +308,20 @@ def serve_wsgi(cls, conf=None): def _run_wsgi(paste_config_file, apis): - logging.debug(_("Using paste.deploy config at: %s"), paste_config_file) + logging.debug(_('Using paste.deploy config at: %s'), paste_config_file) apps = [] for api in apis: config = wsgi.load_paste_configuration(paste_config_file, api) if config is None: - logging.debug(_("No paste configuration for app: %s"), api) + logging.debug(_('No paste configuration for app: %s'), api) continue - logging.debug(_("App Config: %(api)s\n%(config)r") % locals()) - logging.info(_("Running %s API"), api) + logging.debug(_('App Config: %(api)s\n%(config)r') % locals()) + logging.info(_('Running %s API'), api) app = wsgi.load_paste_app(paste_config_file, api) - apps.append((app, getattr(FLAGS, "%s_listen_port" % api), - getattr(FLAGS, "%s_listen" % api))) + apps.append((app, getattr(FLAGS, '%s_listen_port' % api), + getattr(FLAGS, '%s_listen' % api))) if len(apps) == 0: - logging.error(_("No known API applications configured in %s."), + logging.error(_('No known API applications configured in %s.'), paste_config_file) return diff --git a/nova/test.py b/nova/test.py index 3b608520a..4deb2a175 100644 --- a/nova/test.py +++ b/nova/test.py @@ -16,12 +16,12 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Base classes for our unit tests. -Allows overriding of flags for use of fakes, -and some black magic for inline callbacks. -""" +"""Base classes for our unit tests. +Allows overriding of flags for use of fakes, and some black magic for +inline callbacks. + +""" import datetime import functools @@ -52,9 +52,9 @@ flags.DEFINE_bool('fake_tests', True, def skip_if_fake(func): - """Decorator that skips a test if running in fake mode""" + """Decorator that skips a test if running in fake mode.""" def _skipper(*args, **kw): - """Wrapped skipper function""" + """Wrapped skipper function.""" if FLAGS.fake_tests: raise unittest.SkipTest('Test cannot be run in fake mode') else: @@ -63,9 +63,10 @@ def skip_if_fake(func): class TestCase(unittest.TestCase): - """Test case base class for all unit tests""" + """Test case base class for all unit tests.""" + def setUp(self): - """Run before each test method to initialize test environment""" + """Run before each test method to initialize test environment.""" super(TestCase, self).setUp() # NOTE(vish): We need a better method for creating fixtures for tests # now that we have some required db setup for the system @@ -86,8 +87,7 @@ class TestCase(unittest.TestCase): self._original_flags = FLAGS.FlagValuesDict() def tearDown(self): - """Runs after each test method to finalize/tear down test - environment.""" + """Runs after each test method to tear down test environment.""" try: self.mox.UnsetStubs() self.stubs.UnsetAll() @@ -121,7 +121,7 @@ class TestCase(unittest.TestCase): pass def flags(self, **kw): - """Override flag variables for a test""" + """Override flag variables for a test.""" for k, v in kw.iteritems(): if k in self.flag_overrides: self.reset_flags() @@ -131,7 +131,11 @@ class TestCase(unittest.TestCase): setattr(FLAGS, k, v) def reset_flags(self): - """Resets all flag variables for the test. Runs after each test""" + """Resets all flag variables for the test. + + Runs after each test. + + """ FLAGS.Reset() for k, v in self._original_flags.iteritems(): setattr(FLAGS, k, v) @@ -158,7 +162,6 @@ class TestCase(unittest.TestCase): def _monkey_patch_wsgi(self): """Allow us to kill servers spawned by wsgi.Server.""" - # TODO(termie): change these patterns to use functools self.original_start = wsgi.Server.start @functools.wraps(self.original_start) @@ -189,12 +192,13 @@ class TestCase(unittest.TestCase): If you don't care (or don't know) a given value, you can specify the string DONTCARE as the value. This will cause that dict-item to be skipped. + """ def raise_assertion(msg): d1str = str(d1) d2str = str(d2) - base_msg = ("Dictionaries do not match. %(msg)s d1: %(d1str)s " - "d2: %(d2str)s" % locals()) + base_msg = ('Dictionaries do not match. %(msg)s d1: %(d1str)s ' + 'd2: %(d2str)s' % locals()) raise AssertionError(base_msg) d1keys = set(d1.keys()) @@ -202,8 +206,8 @@ class TestCase(unittest.TestCase): if d1keys != d2keys: d1only = d1keys - d2keys d2only = d2keys - d1keys - raise_assertion("Keys in d1 and not d2: %(d1only)s. " - "Keys in d2 and not d1: %(d2only)s" % locals()) + raise_assertion('Keys in d1 and not d2: %(d1only)s. ' + 'Keys in d2 and not d1: %(d2only)s' % locals()) for key in d1keys: d1value = d1[key] @@ -217,19 +221,19 @@ class TestCase(unittest.TestCase): "d2['%(key)s']=%(d2value)s" % locals()) def assertDictListMatch(self, L1, L2): - """Assert a list of dicts are equivalent""" + """Assert a list of dicts are equivalent.""" def raise_assertion(msg): L1str = str(L1) L2str = str(L2) - base_msg = ("List of dictionaries do not match: %(msg)s " - "L1: %(L1str)s L2: %(L2str)s" % locals()) + base_msg = ('List of dictionaries do not match: %(msg)s ' + 'L1: %(L1str)s L2: %(L2str)s' % locals()) raise AssertionError(base_msg) L1count = len(L1) L2count = len(L2) if L1count != L2count: - raise_assertion("Length mismatch: len(L1)=%(L1count)d != " - "len(L2)=%(L2count)d" % locals()) + raise_assertion('Length mismatch: len(L1)=%(L1count)d != ' + 'len(L2)=%(L2count)d' % locals()) for d1, d2 in zip(L1, L2): self.assertDictMatch(d1, d2) diff --git a/nova/utils.py b/nova/utils.py index 76cba1a08..59f694196 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -17,9 +17,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" -System-level utilities and helper functions. -""" +"""Utilities and helper functions.""" import base64 import datetime @@ -43,9 +41,8 @@ from eventlet import event from eventlet import greenthread from eventlet import semaphore from eventlet.green import subprocess -None + from nova import exception -from nova.exception import ProcessExecutionError from nova import flags from nova import log as logging @@ -56,7 +53,7 @@ FLAGS = flags.FLAGS def import_class(import_str): - """Returns a class from a string including module and class""" + """Returns a class from a string including module and class.""" mod_str, _sep, class_str = import_str.rpartition('.') try: __import__(mod_str) @@ -67,7 +64,7 @@ def import_class(import_str): def import_object(import_str): - """Returns an object including a module or module and class""" + """Returns an object including a module or module and class.""" try: __import__(import_str) return sys.modules[import_str] @@ -99,11 +96,12 @@ def vpn_ping(address, port, timeout=0.05, session_id=None): cli_id = 64 bit identifier ? = unknown, probably flags/padding bit 9 was 1 and the rest were 0 in testing + """ if session_id is None: session_id = random.randint(0, 0xffffffffffffffff) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - data = struct.pack("!BQxxxxxx", 0x38, session_id) + data = struct.pack('!BQxxxxxx', 0x38, session_id) sock.sendto(data, (address, port)) sock.settimeout(timeout) try: @@ -112,7 +110,7 @@ def vpn_ping(address, port, timeout=0.05, session_id=None): return False finally: sock.close() - fmt = "!BQxxxxxQxxxx" + fmt = '!BQxxxxxQxxxx' if len(received) != struct.calcsize(fmt): print struct.calcsize(fmt) return False @@ -122,15 +120,8 @@ def vpn_ping(address, port, timeout=0.05, session_id=None): def fetchfile(url, target): - LOG.debug(_("Fetching %s") % url) -# c = pycurl.Curl() -# fp = open(target, "wb") -# c.setopt(c.URL, url) -# c.setopt(c.WRITEDATA, fp) -# c.perform() -# c.close() -# fp.close() - execute("curl", "--fail", url, "-o", target) + LOG.debug(_('Fetching %s') % url) + execute('curl', '--fail', url, '-o', target) def execute(*cmd, **kwargs): @@ -147,7 +138,7 @@ def execute(*cmd, **kwargs): while attempts > 0: attempts -= 1 try: - LOG.debug(_("Running cmd (subprocess): %s"), ' '.join(cmd)) + LOG.debug(_('Running cmd (subprocess): %s'), ' '.join(cmd)) env = os.environ.copy() if addl_env: env.update(addl_env) @@ -163,20 +154,21 @@ def execute(*cmd, **kwargs): result = obj.communicate() obj.stdin.close() if obj.returncode: - LOG.debug(_("Result was %s") % obj.returncode) + LOG.debug(_('Result was %s') % obj.returncode) if type(check_exit_code) == types.IntType \ and obj.returncode != check_exit_code: (stdout, stderr) = result - raise ProcessExecutionError(exit_code=obj.returncode, - stdout=stdout, - stderr=stderr, - cmd=' '.join(cmd)) + raise exception.ProcessExecutionError( + exit_code=obj.returncode, + stdout=stdout, + stderr=stderr, + cmd=' '.join(cmd)) return result - except ProcessExecutionError: + except exception.ProcessExecutionError: if not attempts: raise else: - LOG.debug(_("%r failed. Retrying."), cmd) + LOG.debug(_('%r failed. Retrying.'), cmd) if delay_on_retry: greenthread.sleep(random.randint(20, 200) / 100.0) finally: @@ -188,13 +180,13 @@ def execute(*cmd, **kwargs): def ssh_execute(ssh, cmd, process_input=None, addl_env=None, check_exit_code=True): - LOG.debug(_("Running cmd (SSH): %s"), ' '.join(cmd)) + LOG.debug(_('Running cmd (SSH): %s'), ' '.join(cmd)) if addl_env: - raise exception.Error("Environment not supported over SSH") + raise exception.Error('Environment not supported over SSH') if process_input: # This is (probably) fixable if we need it... - raise exception.Error("process_input not supported over SSH") + raise exception.Error('process_input not supported over SSH') stdin_stream, stdout_stream, stderr_stream = ssh.exec_command(cmd) channel = stdout_stream.channel @@ -212,7 +204,7 @@ def ssh_execute(ssh, cmd, process_input=None, # exit_status == -1 if no exit code was returned if exit_status != -1: - LOG.debug(_("Result was %s") % exit_status) + LOG.debug(_('Result was %s') % exit_status) if check_exit_code and exit_status != 0: raise exception.ProcessExecutionError(exit_code=exit_status, stdout=stdout, @@ -251,7 +243,7 @@ def debug(arg): def runthis(prompt, *cmd, **kwargs): - LOG.debug(_("Running %s"), (" ".join(cmd))) + LOG.debug(_('Running %s'), (' '.join(cmd))) rv, err = execute(*cmd, **kwargs) @@ -266,48 +258,49 @@ def generate_mac(): random.randint(0x00, 0x7f), random.randint(0x00, 0xff), random.randint(0x00, 0xff)] - return ':'.join(map(lambda x: "%02x" % x, mac)) + return ':'.join(map(lambda x: '%02x' % x, mac)) # Default symbols to use for passwords. Avoids visually confusing characters. # ~6 bits per symbol -DEFAULT_PASSWORD_SYMBOLS = ("23456789" # Removed: 0,1 - "ABCDEFGHJKLMNPQRSTUVWXYZ" # Removed: I, O - "abcdefghijkmnopqrstuvwxyz") # Removed: l +DEFAULT_PASSWORD_SYMBOLS = ('23456789' # Removed: 0,1 + 'ABCDEFGHJKLMNPQRSTUVWXYZ' # Removed: I, O + 'abcdefghijkmnopqrstuvwxyz') # Removed: l # ~5 bits per symbol -EASIER_PASSWORD_SYMBOLS = ("23456789" # Removed: 0, 1 - "ABCDEFGHJKLMNPQRSTUVWXYZ") # Removed: I, O +EASIER_PASSWORD_SYMBOLS = ('23456789' # Removed: 0, 1 + 'ABCDEFGHJKLMNPQRSTUVWXYZ') # Removed: I, O def generate_password(length=20, symbols=DEFAULT_PASSWORD_SYMBOLS): """Generate a random password from the supplied symbols. Believed to be reasonably secure (with a reasonable password length!) + """ r = random.SystemRandom() - return "".join([r.choice(symbols) for _i in xrange(length)]) + return ''.join([r.choice(symbols) for _i in xrange(length)]) def last_octet(address): - return int(address.split(".")[-1]) + return int(address.split('.')[-1]) def get_my_linklocal(interface): try: - if_str = execute("ip", "-f", "inet6", "-o", "addr", "show", interface) - condition = "\s+inet6\s+([0-9a-f:]+)/\d+\s+scope\s+link" + if_str = execute('ip', '-f', 'inet6', '-o', 'addr', 'show', interface) + condition = '\s+inet6\s+([0-9a-f:]+)/\d+\s+scope\s+link' links = [re.search(condition, x) for x in if_str[0].split('\n')] address = [w.group(1) for w in links if w is not None] if address[0] is not None: return address[0] else: - raise exception.Error(_("Link Local address is not found.:%s") + raise exception.Error(_('Link Local address is not found.:%s') % if_str) except Exception as ex: raise exception.Error(_("Couldn't get Link Local IP of %(interface)s" - " :%(ex)s") % locals()) + " :%(ex)s") % locals()) def to_global_ipv6(prefix, mac): @@ -319,15 +312,15 @@ def to_global_ipv6(prefix, mac): return (mac64_addr ^ netaddr.IPAddress('::0200:0:0:0') | maskIP).\ format() except TypeError: - raise TypeError(_("Bad mac for to_global_ipv6: %s") % mac) + raise TypeError(_('Bad mac for to_global_ipv6: %s') % mac) def to_mac(ipv6_address): address = netaddr.IPAddress(ipv6_address) - mask1 = netaddr.IPAddress("::ffff:ffff:ffff:ffff") - mask2 = netaddr.IPAddress("::0200:0:0:0") + mask1 = netaddr.IPAddress('::ffff:ffff:ffff:ffff') + mask2 = netaddr.IPAddress('::0200:0:0:0') mac64 = netaddr.EUI(int(address & mask1 ^ mask2)).words - return ":".join(["%02x" % i for i in mac64[0:3] + mac64[5:8]]) + return ':'.join(['%02x' % i for i in mac64[0:3] + mac64[5:8]]) def utcnow(): @@ -341,7 +334,7 @@ utcnow.override_time = None def is_older_than(before, seconds): - """Return True if before is older than seconds""" + """Return True if before is older than seconds.""" return utcnow() - before > datetime.timedelta(seconds=seconds) @@ -379,7 +372,7 @@ def isotime(at=None): def parse_isotime(timestr): - """Turn an iso formatted time back into a datetime""" + """Turn an iso formatted time back into a datetime.""" return datetime.datetime.strptime(timestr, TIME_FORMAT) @@ -433,16 +426,19 @@ class LazyPluggable(object): class LoopingCallDone(Exception): - """The poll-function passed to LoopingCall can raise this exception to + """Exception to break out and stop a LoopingCall. + + The poll-function passed to LoopingCall can raise this exception to break out of the loop normally. This is somewhat analogous to StopIteration. An optional return-value can be included as the argument to the exception; this return-value will be returned by LoopingCall.wait() + """ def __init__(self, retvalue=True): - """:param retvalue: Value that LoopingCall.wait() should return""" + """:param retvalue: Value that LoopingCall.wait() should return.""" self.retvalue = retvalue @@ -493,7 +489,7 @@ def xhtml_escape(value): http://github.com/facebook/tornado/blob/master/tornado/escape.py """ - return saxutils.escape(value, {'"': """}) + return saxutils.escape(value, {'"': '"'}) def utf8(value): @@ -504,7 +500,7 @@ def utf8(value): """ if isinstance(value, unicode): - return value.encode("utf-8") + return value.encode('utf-8') assert isinstance(value, str) return value @@ -554,7 +550,7 @@ class _NoopContextManager(object): def synchronized(name, external=False): - """Synchronization decorator + """Synchronization decorator. Decorating a method like so: @synchronized('mylock') @@ -578,6 +574,7 @@ def synchronized(name, external=False): multiple processes. This means that if two different workers both run a a method decorated with @synchronized('mylock', external=True), only one of them will execute at a time. + """ def wrap(f): @@ -590,13 +587,13 @@ def synchronized(name, external=False): _semaphores[name] = semaphore.Semaphore() sem = _semaphores[name] LOG.debug(_('Attempting to grab semaphore "%(lock)s" for method ' - '"%(method)s"...' % {"lock": name, - "method": f.__name__})) + '"%(method)s"...' % {'lock': name, + 'method': f.__name__})) with sem: if external: LOG.debug(_('Attempting to grab file lock "%(lock)s" for ' 'method "%(method)s"...' % - {"lock": name, "method": f.__name__})) + {'lock': name, 'method': f.__name__})) lock_file_path = os.path.join(FLAGS.lock_path, 'nova-%s.lock' % name) lock = lockfile.FileLock(lock_file_path) @@ -617,21 +614,23 @@ def synchronized(name, external=False): def get_from_path(items, path): - """ Returns a list of items matching the specified path. Takes an - XPath-like expression e.g. prop1/prop2/prop3, and for each item in items, - looks up items[prop1][prop2][prop3]. Like XPath, if any of the + """Returns a list of items matching the specified path. + + Takes an XPath-like expression e.g. prop1/prop2/prop3, and for each item + in items, looks up items[prop1][prop2][prop3]. Like XPath, if any of the intermediate results are lists it will treat each list item individually. A 'None' in items or any child expressions will be ignored, this function will not throw because of None (anywhere) in items. The returned list - will contain no None values.""" + will contain no None values. + """ if path is None: - raise exception.Error("Invalid mini_xpath") + raise exception.Error('Invalid mini_xpath') - (first_token, sep, remainder) = path.partition("/") + (first_token, sep, remainder) = path.partition('/') - if first_token == "": - raise exception.Error("Invalid mini_xpath") + if first_token == '': + raise exception.Error('Invalid mini_xpath') results = [] @@ -645,7 +644,7 @@ def get_from_path(items, path): for item in items: if item is None: continue - get_method = getattr(item, "get", None) + get_method = getattr(item, 'get', None) if get_method is None: continue child = get_method(first_token) @@ -666,7 +665,7 @@ def get_from_path(items, path): def flatten_dict(dict_, flattened=None): - """Recursively flatten a nested dictionary""" + """Recursively flatten a nested dictionary.""" flattened = flattened or {} for key, value in dict_.iteritems(): if hasattr(value, 'iteritems'): @@ -677,9 +676,7 @@ def flatten_dict(dict_, flattened=None): def partition_dict(dict_, keys): - """Return two dicts, one containing only `keys` the other containing - everything but `keys` - """ + """Return two dicts, one with `keys` the other with everything else.""" intersection = {} difference = {} for key, value in dict_.iteritems(): @@ -691,9 +688,7 @@ def partition_dict(dict_, keys): def map_dict_keys(dict_, key_map): - """Return a dictionary in which the dictionaries keys are mapped to - new keys. - """ + """Return a dict in which the dictionaries keys are mapped to new keys.""" mapped = {} for key, value in dict_.iteritems(): mapped_key = key_map[key] if key in key_map else key @@ -702,15 +697,15 @@ def map_dict_keys(dict_, key_map): def subset_dict(dict_, keys): - """Return a dict that only contains a subset of keys""" + """Return a dict that only contains a subset of keys.""" subset = partition_dict(dict_, keys)[0] return subset def check_isinstance(obj, cls): - """Checks that obj is of type cls, and lets PyLint infer types""" + """Checks that obj is of type cls, and lets PyLint infer types.""" if isinstance(obj, cls): return obj - raise Exception(_("Expected object of type: %s") % (str(cls))) + raise Exception(_('Expected object of type: %s') % (str(cls))) # TODO(justinsb): Can we make this better?? return cls() # Ugly PyLint hack diff --git a/nova/version.py b/nova/version.py index c43d12cf8..1f8d08e8c 100644 --- a/nova/version.py +++ b/nova/version.py @@ -21,9 +21,9 @@ except ImportError: 'revision_id': 'LOCALREVISION', 'revno': 0} + NOVA_VERSION = ['2011', '3'] YEAR, COUNT = NOVA_VERSION - FINAL = False # This becomes true at Release Candidate time @@ -39,8 +39,8 @@ def version_string(): def vcs_version_string(): - return "%s:%s" % (version_info['branch_nick'], version_info['revision_id']) + return '%s:%s' % (version_info['branch_nick'], version_info['revision_id']) def version_string_with_vcs(): - return "%s-%s" % (canonical_version_string(), vcs_version_string()) + return '%s-%s' % (canonical_version_string(), vcs_version_string()) diff --git a/nova/wsgi.py b/nova/wsgi.py index de2e0749f..119fcbe4c 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -17,9 +17,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Utility methods for working with WSGI servers -""" +"""Utility methods for working with WSGI servers.""" import os import sys @@ -33,7 +31,6 @@ import routes.middleware import webob import webob.dec import webob.exc - from paste import deploy from nova import exception @@ -66,7 +63,7 @@ class Server(object): def start(self, application, port, host='0.0.0.0', backlog=128): """Run a WSGI server with the given application.""" arg0 = sys.argv[0] - logging.audit(_("Starting %(arg0)s on %(host)s:%(port)s") % locals()) + logging.audit(_('Starting %(arg0)s on %(host)s:%(port)s') % locals()) socket = eventlet.listen((host, port), backlog=backlog) self.pool.spawn_n(self._run, application, socket) @@ -87,30 +84,31 @@ class Server(object): class Request(webob.Request): def best_match_content_type(self): - """ - Determine the most acceptable content-type based on the - query extension then the Accept header + """Determine the most acceptable content-type. + + Based on the query extension then the Accept header. + """ - parts = self.path.rsplit(".", 1) + parts = self.path.rsplit('.', 1) if len(parts) > 1: format = parts[1] - if format in ["json", "xml"]: - return "application/{0}".format(parts[1]) + if format in ['json', 'xml']: + return 'application/{0}'.format(parts[1]) - ctypes = ["application/json", "application/xml"] + ctypes = ['application/json', 'application/xml'] bm = self.accept.best_match(ctypes) - return bm or "application/json" + return bm or 'application/json' def get_content_type(self): try: - ct = self.headers["Content-Type"] - assert ct in ("application/xml", "application/json") + ct = self.headers['Content-Type'] + assert ct in ('application/xml', 'application/json') return ct except Exception: - raise webob.exc.HTTPBadRequest("Invalid content type") + raise webob.exc.HTTPBadRequest('Invalid content type') class Application(object): @@ -118,7 +116,7 @@ class Application(object): @classmethod def factory(cls, global_config, **local_config): - """Used for paste app factories in paste.deploy config fles. + """Used for paste app factories in paste.deploy config files. Any local configuration (that is, values under the [app:APPNAME] section of the paste config) will be passed into the `__init__` method @@ -173,8 +171,9 @@ class Application(object): See the end of http://pythonpaste.org/webob/modules/dec.html for more info. + """ - raise NotImplementedError(_("You must implement __call__")) + raise NotImplementedError(_('You must implement __call__')) class Middleware(Application): @@ -184,11 +183,12 @@ class Middleware(Application): initialized that will be called next. By default the middleware will simply call its wrapped app, or you can override __call__ to customize its behavior. + """ @classmethod def factory(cls, global_config, **local_config): - """Used for paste app factories in paste.deploy config fles. + """Used for paste app factories in paste.deploy config files. Any local configuration (that is, values under the [filter:APPNAME] section of the paste config) will be passed into the `__init__` method @@ -240,20 +240,24 @@ class Middleware(Application): class Debug(Middleware): - """Helper class that can be inserted into any WSGI application chain - to get information about the request and response.""" + """Helper class for debugging a WSGI application. + + Can be inserted into any WSGI application chain to get information + about the request and response. + + """ @webob.dec.wsgify(RequestClass=Request) def __call__(self, req): - print ("*" * 40) + " REQUEST ENVIRON" + print ('*' * 40) + ' REQUEST ENVIRON' for key, value in req.environ.items(): - print key, "=", value + print key, '=', value print resp = req.get_response(self.application) - print ("*" * 40) + " RESPONSE HEADERS" + print ('*' * 40) + ' RESPONSE HEADERS' for (key, value) in resp.headers.iteritems(): - print key, "=", value + print key, '=', value print resp.app_iter = self.print_generator(resp.app_iter) @@ -262,11 +266,8 @@ class Debug(Middleware): @staticmethod def print_generator(app_iter): - """ - Iterator that prints the contents of a wrapper string iterator - when iterated. - """ - print ("*" * 40) + " BODY" + """Iterator that prints the contents of a wrapper string.""" + print ('*' * 40) + ' BODY' for part in app_iter: sys.stdout.write(part) sys.stdout.flush() @@ -275,13 +276,10 @@ class Debug(Middleware): class Router(object): - """ - WSGI middleware that maps incoming requests to WSGI apps. - """ + """WSGI middleware that maps incoming requests to WSGI apps.""" def __init__(self, mapper): - """ - Create a router for the given routes.Mapper. + """Create a router for the given routes.Mapper. Each route in `mapper` must specify a 'controller', which is a WSGI app to call. You'll probably want to specify an 'action' as @@ -293,15 +291,16 @@ class Router(object): sc = ServerController() # Explicit mapping of one route to a controller+action - mapper.connect(None, "/svrlist", controller=sc, action="list") + mapper.connect(None, '/svrlist', controller=sc, action='list') # Actions are all implicitly defined - mapper.resource("server", "servers", controller=sc) + mapper.resource('server', 'servers', controller=sc) # Pointing to an arbitrary WSGI app. You can specify the # {path_info:.*} parameter so the target app can be handed just that # section of the URL. - mapper.connect(None, "/v1.0/{path_info:.*}", controller=BlogApp()) + mapper.connect(None, '/v1.0/{path_info:.*}', controller=BlogApp()) + """ self.map = mapper self._router = routes.middleware.RoutesMiddleware(self._dispatch, @@ -309,19 +308,22 @@ class Router(object): @webob.dec.wsgify(RequestClass=Request) def __call__(self, req): - """ - Route the incoming request to a controller based on self.map. + """Route the incoming request to a controller based on self.map. + If no match, return a 404. + """ return self._router @staticmethod @webob.dec.wsgify(RequestClass=Request) def _dispatch(req): - """ + """Dispatch the request to the appropriate controller. + Called by self._router after matching the incoming request to a route and putting the information into req.environ. Either returns 404 or the routed WSGI app's response. + """ match = req.environ['wsgiorg.routing_args'][1] if not match: @@ -331,19 +333,19 @@ class Router(object): class Controller(object): - """ + """WSGI app that dispatched to methods. + WSGI app that reads routing information supplied by RoutesMiddleware and calls the requested action method upon itself. All action methods must, in addition to their normal parameters, accept a 'req' argument which is the incoming wsgi.Request. They raise a webob.exc exception, or return a dict which will be serialized by requested content type. + """ @webob.dec.wsgify(RequestClass=Request) def __call__(self, req): - """ - Call the method specified in req.environ by RoutesMiddleware. - """ + """Call the method specified in req.environ by RoutesMiddleware.""" arg_dict = req.environ['wsgiorg.routing_args'][1] action = arg_dict['action'] method = getattr(self, action) @@ -361,7 +363,7 @@ class Controller(object): body = self._serialize(result, content_type, default_xmlns) response = webob.Response() - response.headers["Content-Type"] = content_type + response.headers['Content-Type'] = content_type response.body = body msg_dict = dict(url=req.url, status=response.status_int) msg = _("%(url)s returned with HTTP %(status)d") % msg_dict @@ -371,12 +373,13 @@ class Controller(object): return result def _serialize(self, data, content_type, default_xmlns): - """ - Serialize the given dict to the provided content_type. + """Serialize the given dict to the provided content_type. + Uses self._serialization_metadata if it exists, which is a dict mapping MIME types to information needed to serialize to that type. + """ - _metadata = getattr(type(self), "_serialization_metadata", {}) + _metadata = getattr(type(self), '_serialization_metadata', {}) serializer = Serializer(_metadata, default_xmlns) try: @@ -385,12 +388,13 @@ class Controller(object): raise webob.exc.HTTPNotAcceptable() def _deserialize(self, data, content_type): - """ - Deserialize the request body to the specefied content type. + """Deserialize the request body to the specefied content type. + Uses self._serialization_metadata if it exists, which is a dict mapping MIME types to information needed to serialize to that type. + """ - _metadata = getattr(type(self), "_serialization_metadata", {}) + _metadata = getattr(type(self), '_serialization_metadata', {}) serializer = Serializer(_metadata) return serializer.deserialize(data, content_type) @@ -400,23 +404,22 @@ class Controller(object): class Serializer(object): - """ - Serializes and deserializes dictionaries to certain MIME types. - """ + """Serializes and deserializes dictionaries to certain MIME types.""" def __init__(self, metadata=None, default_xmlns=None): - """ - Create a serializer based on the given WSGI environment. + """Create a serializer based on the given WSGI environment. + 'metadata' is an optional dict mapping MIME types to information needed to serialize a dictionary to that type. + """ self.metadata = metadata or {} self.default_xmlns = default_xmlns def _get_serialize_handler(self, content_type): handlers = { - "application/json": self._to_json, - "application/xml": self._to_xml, + 'application/json': self._to_json, + 'application/xml': self._to_xml, } try: @@ -425,29 +428,27 @@ class Serializer(object): raise exception.InvalidContentType() def serialize(self, data, content_type): - """ - Serialize a dictionary into a string of the specified content type. - """ + """Serialize a dictionary into the specified content type.""" return self._get_serialize_handler(content_type)(data) def deserialize(self, datastring, content_type): - """ - Deserialize a string to a dictionary. + """Deserialize a string to a dictionary. The string must be in the format of a supported MIME type. + """ return self.get_deserialize_handler(content_type)(datastring) def get_deserialize_handler(self, content_type): handlers = { - "application/json": self._from_json, - "application/xml": self._from_xml, + 'application/json': self._from_json, + 'application/xml': self._from_xml, } try: return handlers[content_type] except Exception: - raise exception.InvalidContentType(_("Invalid content type %s" + raise exception.InvalidContentType(_('Invalid content type %s' % content_type)) def _from_json(self, datastring): @@ -460,11 +461,11 @@ class Serializer(object): return {node.nodeName: self._from_xml_node(node, plurals)} def _from_xml_node(self, node, listnames): - """ - Convert a minidom node to a simple Python type. + """Convert a minidom node to a simple Python type. listnames is a collection of names of XML nodes whose subnodes should be considered list items. + """ if len(node.childNodes) == 1 and node.childNodes[0].nodeType == 3: return node.childNodes[0].nodeValue @@ -571,7 +572,6 @@ def paste_config_file(basename): * /etc/nova, which may not be diffrerent from state_path on your distro """ - configfiles = [basename, os.path.join(FLAGS.state_path, 'etc', 'nova', basename), os.path.join(FLAGS.state_path, 'etc', basename), @@ -587,7 +587,7 @@ def load_paste_configuration(filename, appname): filename = os.path.abspath(filename) config = None try: - config = deploy.appconfig("config:%s" % filename, name=appname) + config = deploy.appconfig('config:%s' % filename, name=appname) except LookupError: pass return config @@ -598,7 +598,7 @@ def load_paste_app(filename, appname): filename = os.path.abspath(filename) app = None try: - app = deploy.loadapp("config:%s" % filename, name=appname) + app = deploy.loadapp('config:%s' % filename, name=appname) except LookupError: pass return app -- cgit From 6eacc130af49ced7a1e5ce511c7096dd7563b4b2 Mon Sep 17 00:00:00 2001 From: termie Date: Wed, 20 Apr 2011 12:08:24 -0700 Subject: cleanups per code review --- nova/utils.py | 4 ++-- nova/wsgi.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/utils.py b/nova/utils.py index 59f694196..b783f6c14 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -182,11 +182,11 @@ def ssh_execute(ssh, cmd, process_input=None, addl_env=None, check_exit_code=True): LOG.debug(_('Running cmd (SSH): %s'), ' '.join(cmd)) if addl_env: - raise exception.Error('Environment not supported over SSH') + raise exception.Error(_('Environment not supported over SSH')) if process_input: # This is (probably) fixable if we need it... - raise exception.Error('process_input not supported over SSH') + raise exception.Error(_('process_input not supported over SSH')) stdin_stream, stdout_stream, stderr_stream = ssh.exec_command(cmd) channel = stdout_stream.channel diff --git a/nova/wsgi.py b/nova/wsgi.py index 119fcbe4c..418087641 100644 --- a/nova/wsgi.py +++ b/nova/wsgi.py @@ -89,7 +89,6 @@ class Request(webob.Request): Based on the query extension then the Accept header. """ - parts = self.path.rsplit('.', 1) if len(parts) > 1: -- cgit From 8bf11973b0de6a57f18ac48452e3f8b36adac565 Mon Sep 17 00:00:00 2001 From: termie Date: Wed, 20 Apr 2011 12:26:15 -0700 Subject: docstring cleanup, nova/image dir --- nova/image/fake.py | 9 ++++--- nova/image/glance.py | 70 +++++++++++++++++++++------------------------------ nova/image/local.py | 8 +++--- nova/image/s3.py | 43 +++++++++++++++---------------- nova/image/service.py | 63 +++++++++++++++++++++++----------------------- 5 files changed, 92 insertions(+), 101 deletions(-) (limited to 'nova') diff --git a/nova/image/fake.py b/nova/image/fake.py index e02b4127e..3bc2a8287 100644 --- a/nova/image/fake.py +++ b/nova/image/fake.py @@ -14,6 +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. + """Implementation of an fake image service""" import copy @@ -69,14 +70,14 @@ class FakeImageService(service.BaseImageService): image = self.images.get(image_id) if image: return copy.deepcopy(image) - LOG.warn("Unable to find image id %s. Have images: %s", + LOG.warn('Unable to find image id %s. Have images: %s', image_id, self.images) raise exception.NotFound def create(self, context, data): """Store the image data and return the new image id. - :raises Duplicate if the image already exist. + :raises: Duplicate if the image already exist. """ image_id = int(data['id']) @@ -88,7 +89,7 @@ class FakeImageService(service.BaseImageService): def update(self, context, image_id, data): """Replace the contents of the given image with the new data. - :raises NotFound if the image does not exist. + :raises: NotFound if the image does not exist. """ image_id = int(image_id) @@ -99,7 +100,7 @@ class FakeImageService(service.BaseImageService): def delete(self, context, image_id): """Delete the given image. - :raises NotFound if the image does not exist. + :raises: NotFound if the image does not exist. """ image_id = int(image_id) diff --git a/nova/image/glance.py b/nova/image/glance.py index 1a80bb2af..81661b3b0 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -14,6 +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. + """Implementation of an image service that uses Glance as the backend""" from __future__ import absolute_import @@ -31,16 +32,18 @@ from nova.image import service LOG = logging.getLogger('nova.image.glance') + FLAGS = flags.FLAGS + GlanceClient = utils.import_class('glance.client.Client') class GlanceImageService(service.BaseImageService): """Provides storage and retrieval of disk image objects within Glance.""" - GLANCE_ONLY_ATTRS = ["size", "location", "disk_format", - "container_format"] + GLANCE_ONLY_ATTRS = ['size', 'location', 'disk_format', + 'container_format'] # NOTE(sirp): Overriding to use _translate_to_service provided by # BaseImageService @@ -56,9 +59,7 @@ class GlanceImageService(service.BaseImageService): self.client = client def index(self, context): - """ - Calls out to Glance for a list of images available - """ + """Calls out to Glance for a list of images available.""" # NOTE(sirp): We need to use `get_images_detailed` and not # `get_images` here because we need `is_public` and `properties` # included so we can filter by user @@ -71,9 +72,7 @@ class GlanceImageService(service.BaseImageService): return filtered def detail(self, context): - """ - Calls out to Glance for a list of detailed image information - """ + """Calls out to Glance for a list of detailed image information.""" filtered = [] image_metas = self.client.get_images_detailed() for image_meta in image_metas: @@ -83,9 +82,7 @@ class GlanceImageService(service.BaseImageService): return filtered def show(self, context, image_id): - """ - Returns a dict containing image data for the given opaque image id. - """ + """Returns a dict with image data for the given opaque image id.""" try: image_meta = self.client.get_image_meta(image_id) except glance_exception.NotFound: @@ -98,9 +95,7 @@ class GlanceImageService(service.BaseImageService): return base_image_meta def show_by_name(self, context, name): - """ - Returns a dict containing image data for the given name. - """ + """Returns a dict containing image data for the given name.""" # TODO(vish): replace this with more efficient call when glance # supports it. image_metas = self.detail(context) @@ -110,9 +105,7 @@ class GlanceImageService(service.BaseImageService): raise exception.NotFound def get(self, context, image_id, data): - """ - Calls out to Glance for metadata and data and writes data. - """ + """Calls out to Glance for metadata and data and writes data.""" try: image_meta, image_chunks = self.client.get_image(image_id) except glance_exception.NotFound: @@ -125,16 +118,16 @@ class GlanceImageService(service.BaseImageService): return base_image_meta def create(self, context, image_meta, data=None): - """ - Store the image data and return the new image id. + """Store the image data and return the new image id. + + :raises: AlreadyExists if the image already exist. - :raises AlreadyExists if the image already exist. """ # Translate Base -> Service - LOG.debug(_("Creating image in Glance. Metadata passed in %s"), + LOG.debug(_('Creating image in Glance. Metadata passed in %s'), image_meta) sent_service_image_meta = self._translate_to_service(image_meta) - LOG.debug(_("Metadata after formatting for Glance %s"), + LOG.debug(_('Metadata after formatting for Glance %s'), sent_service_image_meta) recv_service_image_meta = self.client.add_image( @@ -142,14 +135,15 @@ class GlanceImageService(service.BaseImageService): # Translate Service -> Base base_image_meta = self._translate_to_base(recv_service_image_meta) - LOG.debug(_("Metadata returned from Glance formatted for Base %s"), + LOG.debug(_('Metadata returned from Glance formatted for Base %s'), base_image_meta) return base_image_meta def update(self, context, image_id, image_meta, data=None): """Replace the contents of the given image with the new data. - :raises NotFound if the image does not exist. + :raises: NotFound if the image does not exist. + """ # NOTE(vish): show is to check if image is available self.show(context, image_id) @@ -162,10 +156,10 @@ class GlanceImageService(service.BaseImageService): return base_image_meta def delete(self, context, image_id): - """ - Delete the given image. + """Delete the given image. + + :raises: NotFound if the image does not exist. - :raises NotFound if the image does not exist. """ # NOTE(vish): show is to check if image is available self.show(context, image_id) @@ -176,16 +170,12 @@ class GlanceImageService(service.BaseImageService): return result def delete_all(self): - """ - Clears out all images - """ + """Clears out all images.""" pass @classmethod def _translate_to_base(cls, image_meta): - """Overriding the base translation to handle conversion to datetime - objects - """ + """Override translation to handle conversion to datetime objects.""" image_meta = service.BaseImageService._propertify_metadata( image_meta, cls.SERVICE_IMAGE_ATTRS) image_meta = _convert_timestamps_to_datetimes(image_meta) @@ -194,9 +184,7 @@ class GlanceImageService(service.BaseImageService): # utility functions def _convert_timestamps_to_datetimes(image_meta): - """ - Returns image with known timestamp fields converted to datetime objects - """ + """Returns image with timestamp fields converted to datetime objects.""" for attr in ['created_at', 'updated_at', 'deleted_at']: if image_meta.get(attr): image_meta[attr] = _parse_glance_iso8601_timestamp( @@ -205,10 +193,8 @@ def _convert_timestamps_to_datetimes(image_meta): def _parse_glance_iso8601_timestamp(timestamp): - """ - Parse a subset of iso8601 timestamps into datetime objects - """ - iso_formats = ["%Y-%m-%dT%H:%M:%S.%f", "%Y-%m-%dT%H:%M:%S"] + """Parse a subset of iso8601 timestamps into datetime objects.""" + iso_formats = ['%Y-%m-%dT%H:%M:%S.%f', '%Y-%m-%dT%H:%M:%S'] for iso_format in iso_formats: try: @@ -216,5 +202,5 @@ def _parse_glance_iso8601_timestamp(timestamp): except ValueError: pass - raise ValueError(_("%(timestamp)s does not follow any of the " - "signatures: %(ISO_FORMATS)s") % locals()) + raise ValueError(_('%(timestamp)s does not follow any of the ' + 'signatures: %(ISO_FORMATS)s') % locals()) diff --git a/nova/image/local.py b/nova/image/local.py index fa5e93346..d59c3970f 100644 --- a/nova/image/local.py +++ b/nova/image/local.py @@ -23,14 +23,15 @@ import shutil from nova import exception from nova import flags from nova import log as logging -from nova.image import service from nova import utils +from nova.image import service FLAGS = flags.FLAGS flags.DEFINE_string('images_path', '$state_path/images', 'path to decrypted images') + LOG = logging.getLogger('nova.image.local') @@ -57,7 +58,7 @@ class LocalImageService(service.BaseImageService): unhexed_image_id = int(image_dir, 16) except ValueError: LOG.error( - _("%s is not in correct directory naming format"\ + _('%s is not in correct directory naming format'\ % image_dir)) else: images.append(unhexed_image_id) @@ -148,7 +149,8 @@ class LocalImageService(service.BaseImageService): def delete(self, context, image_id): """Delete the given image. - Raises NotFound if the image does not exist. + + :raises: NotFound if the image does not exist. """ # NOTE(vish): show is to check if image is available diff --git a/nova/image/s3.py b/nova/image/s3.py index 2a02d4674..4a4ee44d8 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -16,19 +16,16 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Proxy AMI-related calls from the cloud controller, to the running -objectstore service. -""" +"""Proxy AMI-related calls from cloud controller to objectstore service.""" import binascii -import eventlet import os import shutil import tarfile import tempfile from xml.etree import ElementTree +import eventlet import boto.s3.connection from nova import crypto @@ -46,7 +43,7 @@ flags.DEFINE_string('image_decryption_dir', '/tmp', class S3ImageService(service.BaseImageService): - """Wraps an existing image service to support s3 based register""" + """Wraps an existing image service to support s3 based register.""" def __init__(self, service=None, *args, **kwargs): if service is None: service = utils.import_object(FLAGS.image_service) @@ -54,7 +51,11 @@ class S3ImageService(service.BaseImageService): self.service.__init__(*args, **kwargs) def create(self, context, metadata, data=None): - """metadata['properties'] should contain image_location""" + """Create an image. + + metadata['properties'] should contain image_location. + + """ image = self._s3_create(context, metadata) return image @@ -100,12 +101,12 @@ class S3ImageService(service.BaseImageService): return local_filename def _s3_create(self, context, metadata): - """Gets a manifext from s3 and makes an image""" + """Gets a manifext from s3 and makes an image.""" image_path = tempfile.mkdtemp(dir=FLAGS.image_decryption_dir) image_location = metadata['properties']['image_location'] - bucket_name = image_location.split("/")[0] + bucket_name = image_location.split('/')[0] manifest_path = image_location[len(bucket_name) + 1:] bucket = self._conn(context).get_bucket(bucket_name) key = bucket.get_key(manifest_path) @@ -116,7 +117,7 @@ class S3ImageService(service.BaseImageService): image_type = 'machine' try: - kernel_id = manifest.find("machine_configuration/kernel_id").text + kernel_id = manifest.find('machine_configuration/kernel_id').text if kernel_id == 'true': image_format = 'aki' image_type = 'kernel' @@ -125,7 +126,7 @@ class S3ImageService(service.BaseImageService): kernel_id = None try: - ramdisk_id = manifest.find("machine_configuration/ramdisk_id").text + ramdisk_id = manifest.find('machine_configuration/ramdisk_id').text if ramdisk_id == 'true': image_format = 'ari' image_type = 'ramdisk' @@ -134,7 +135,7 @@ class S3ImageService(service.BaseImageService): ramdisk_id = None try: - arch = manifest.find("machine_configuration/architecture").text + arch = manifest.find('machine_configuration/architecture').text except Exception: arch = 'x86_64' @@ -160,7 +161,7 @@ class S3ImageService(service.BaseImageService): def delayed_create(): """This handles the fetching and decrypting of the part files.""" parts = [] - for fn_element in manifest.find("image").getiterator("filename"): + for fn_element in manifest.find('image').getiterator('filename'): part = self._download_file(bucket, fn_element.text, image_path) parts.append(part) @@ -174,9 +175,9 @@ class S3ImageService(service.BaseImageService): metadata['properties']['image_state'] = 'decrypting' self.service.update(context, image_id, metadata) - hex_key = manifest.find("image/ec2_encrypted_key").text + hex_key = manifest.find('image/ec2_encrypted_key').text encrypted_key = binascii.a2b_hex(hex_key) - hex_iv = manifest.find("image/ec2_encrypted_iv").text + hex_iv = manifest.find('image/ec2_encrypted_iv').text encrypted_iv = binascii.a2b_hex(hex_iv) # FIXME(vish): grab key from common service so this can run on @@ -214,7 +215,7 @@ class S3ImageService(service.BaseImageService): process_input=encrypted_key, check_exit_code=False) if err: - raise exception.Error(_("Failed to decrypt private key: %s") + raise exception.Error(_('Failed to decrypt private key: %s') % err) iv, err = utils.execute('openssl', 'rsautl', @@ -223,8 +224,8 @@ class S3ImageService(service.BaseImageService): process_input=encrypted_iv, check_exit_code=False) if err: - raise exception.Error(_("Failed to decrypt initialization " - "vector: %s") % err) + raise exception.Error(_('Failed to decrypt initialization ' + 'vector: %s') % err) _out, err = utils.execute('openssl', 'enc', '-d', '-aes-128-cbc', @@ -234,14 +235,14 @@ class S3ImageService(service.BaseImageService): '-out', '%s' % (decrypted_filename,), check_exit_code=False) if err: - raise exception.Error(_("Failed to decrypt image file " - "%(image_file)s: %(err)s") % + raise exception.Error(_('Failed to decrypt image file ' + '%(image_file)s: %(err)s') % {'image_file': encrypted_filename, 'err': err}) @staticmethod def _untarzip_image(path, filename): - tar_file = tarfile.open(filename, "r|gz") + tar_file = tarfile.open(filename, 'r|gz') tar_file.extractall(path) image_file = tar_file.getnames()[0] tar_file.close() diff --git a/nova/image/service.py b/nova/image/service.py index fddc72409..3e2357df8 100644 --- a/nova/image/service.py +++ b/nova/image/service.py @@ -20,7 +20,7 @@ from nova import utils class BaseImageService(object): - """Base class for providing image search and retrieval services + """Base class for providing image search and retrieval services. ImageService exposes two concepts of metadata: @@ -35,7 +35,9 @@ class BaseImageService(object): This means that ImageServices will return BASE_IMAGE_ATTRS as keys in the metadata dict, all other attributes will be returned as keys in the nested 'properties' dict. + """ + BASE_IMAGE_ATTRS = ['id', 'name', 'created_at', 'updated_at', 'deleted_at', 'deleted', 'status', 'is_public'] @@ -45,23 +47,18 @@ class BaseImageService(object): SERVICE_IMAGE_ATTRS = [] def index(self, context): - """ - Returns a sequence of mappings of id and name information about - images. + """List images. - :rtype: array - :retval: a sequence of mappings with the following signature + :returnsl: a sequence of mappings with the following signature {'id': opaque id of image, 'name': name of image} """ raise NotImplementedError def detail(self, context): - """ - Returns a sequence of mappings of detailed information about images. + """Detailed information about an images. - :rtype: array - :retval: a sequence of mappings with the following signature + :returns: a sequence of mappings with the following signature {'id': opaque id of image, 'name': name of image, 'created_at': creation datetime object, @@ -77,15 +74,14 @@ class BaseImageService(object): NotImplementedError, in which case Nova will emulate this method with repeated calls to show() for each image received from the index() method. + """ raise NotImplementedError def show(self, context, image_id): - """ - Returns a dict containing image metadata for the given opaque image id. - - :retval a mapping with the following signature: + """Detailed information about an image. + :returns: a mapping with the following signature: {'id': opaque id of image, 'name': name of image, 'created_at': creation datetime object, @@ -96,31 +92,32 @@ class BaseImageService(object): 'is_public': boolean indicating if image is public }, ... - :raises NotFound if the image does not exist + :raises: NotFound if the image does not exist + """ raise NotImplementedError def get(self, context, data): - """ - Returns a dict containing image metadata and writes image data to data. + """Get an image. :param data: a file-like object to hold binary image data - + :returns: a dict containing image metadata, writes image data to data. :raises NotFound if the image does not exist + """ raise NotImplementedError def create(self, context, metadata, data=None): - """ - Store the image metadata and data and return the new image metadata. + """Store the image metadata and data. - :raises AlreadyExists if the image already exist. + :returns: the new image metadata. + :raises: AlreadyExists if the image already exist. """ raise NotImplementedError def update(self, context, image_id, metadata, data=None): - """Update the given image metadata and data and return the metadata + """Update the given image metadata and data and return the metadata. :raises NotFound if the image does not exist. @@ -128,8 +125,7 @@ class BaseImageService(object): raise NotImplementedError def delete(self, context, image_id): - """ - Delete the given image. + """Delete the given image. :raises NotFound if the image does not exist. @@ -138,12 +134,14 @@ class BaseImageService(object): @staticmethod def _is_image_available(context, image_meta): - """ + """Check image availability. + Images are always available if they are public or if the user is an admin. Otherwise, we filter by project_id (if present) and then fall-back to images owned by user. + """ # FIXME(sirp): We should be filtering by user_id on the Glance side # for security; however, we can't do that until we get authn/authz @@ -169,29 +167,32 @@ class BaseImageService(object): This is used by subclasses to expose only a metadata dictionary that is the same across ImageService implementations. + """ return cls._propertify_metadata(metadata, cls.BASE_IMAGE_ATTRS) @classmethod def _translate_to_service(cls, metadata): - """Return a metadata dictionary that is usable by the ImageService - subclass. + """Return a metadata dict that is usable by the ImageService subclass. As an example, Glance has additional attributes (like 'location'); the BaseImageService considers these properties, but we need to translate these back to first-class attrs for sending to Glance. This method handles this by allowing you to specify the attributes an ImageService considers first-class. + """ if not cls.SERVICE_IMAGE_ATTRS: - raise NotImplementedError(_("Cannot use this without specifying " - "SERVICE_IMAGE_ATTRS for subclass")) + raise NotImplementedError(_('Cannot use this without specifying ' + 'SERVICE_IMAGE_ATTRS for subclass')) return cls._propertify_metadata(metadata, cls.SERVICE_IMAGE_ATTRS) @staticmethod def _propertify_metadata(metadata, keys): - """Return a dict with any unrecognized keys placed in the nested - 'properties' dict. + """Move unknown keys to a nested 'properties' dict. + + :returns: a new dict with the keys moved. + """ flattened = utils.flatten_dict(metadata) attributes, properties = utils.partition_dict(flattened, keys) -- cgit From 42b139f740c08cce04d898c4ce7c85030733927f Mon Sep 17 00:00:00 2001 From: termie Date: Wed, 20 Apr 2011 12:26:17 -0700 Subject: fixes per review --- nova/image/s3.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/image/s3.py b/nova/image/s3.py index 4a4ee44d8..c38c58d95 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -25,8 +25,8 @@ import tarfile import tempfile from xml.etree import ElementTree -import eventlet import boto.s3.connection +import eventlet from nova import crypto from nova import exception @@ -44,6 +44,7 @@ flags.DEFINE_string('image_decryption_dir', '/tmp', class S3ImageService(service.BaseImageService): """Wraps an existing image service to support s3 based register.""" + def __init__(self, service=None, *args, **kwargs): if service is None: service = utils.import_object(FLAGS.image_service) -- cgit From ce8fd6b5e3ef0c757c76bbe9db37c696a1d2c11c Mon Sep 17 00:00:00 2001 From: termie Date: Wed, 20 Apr 2011 12:26:17 -0700 Subject: more changes per review --- nova/image/local.py | 5 ++--- nova/image/service.py | 10 +++++----- 2 files changed, 7 insertions(+), 8 deletions(-) (limited to 'nova') diff --git a/nova/image/local.py b/nova/image/local.py index d59c3970f..50f00bee1 100644 --- a/nova/image/local.py +++ b/nova/image/local.py @@ -57,9 +57,8 @@ class LocalImageService(service.BaseImageService): try: unhexed_image_id = int(image_dir, 16) except ValueError: - LOG.error( - _('%s is not in correct directory naming format'\ - % image_dir)) + LOG.error(_('%s is not in correct directory naming format') + % image_dir) else: images.append(unhexed_image_id) return images diff --git a/nova/image/service.py b/nova/image/service.py index 3e2357df8..ab6749049 100644 --- a/nova/image/service.py +++ b/nova/image/service.py @@ -49,8 +49,8 @@ class BaseImageService(object): def index(self, context): """List images. - :returnsl: a sequence of mappings with the following signature - {'id': opaque id of image, 'name': name of image} + :returns: a sequence of mappings with the following signature + {'id': opaque id of image, 'name': name of image} """ raise NotImplementedError @@ -102,7 +102,7 @@ class BaseImageService(object): :param data: a file-like object to hold binary image data :returns: a dict containing image metadata, writes image data to data. - :raises NotFound if the image does not exist + :raises: NotFound if the image does not exist """ raise NotImplementedError @@ -119,7 +119,7 @@ class BaseImageService(object): def update(self, context, image_id, metadata, data=None): """Update the given image metadata and data and return the metadata. - :raises NotFound if the image does not exist. + :raises: NotFound if the image does not exist. """ raise NotImplementedError @@ -127,7 +127,7 @@ class BaseImageService(object): def delete(self, context, image_id): """Delete the given image. - :raises NotFound if the image does not exist. + :raises: NotFound if the image does not exist. """ raise NotImplementedError -- cgit From 7d8698ad551b756a9dfc7058e6d836de65a64945 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 20 Apr 2011 16:21:37 -0700 Subject: in doesn't work properly on instance_ref --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index bd4c9dcd4..4785d812a 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -187,7 +187,7 @@ class CloudController(object): 'mpi': mpi}} for image_type in ['kernel', 'ramdisk']: - if '%s_id' % image_type in instance_ref: + if instance_ref.get('%s_id' % image_type): ec2_id = self._image_ec2_id(instance_ref['%s_id' % image_type], self._image_type(image_type)) data['meta-data']['%s-id' % image_type] = ec2_id -- cgit From 783cea4dc4497176b57b7a718a29bde102fb92bc Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Thu, 21 Apr 2011 04:31:17 +0400 Subject: style fix --- nova/virt/libvirt_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index d5a88ebed..a42433fed 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -168,7 +168,7 @@ def _get_network_info(instance): networks = db.network_get_all_by_instance(admin_context, instance['id']) flavor = db.instance_type_get_by_id(admin_context, - instance['instance_type_id']) + instance['instance_type_id']) network_info = [] for network in networks: -- cgit From 2217872ff5e8e5b53af0b38064a3cdbc2c783ebb Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Thu, 21 Apr 2011 05:22:09 +0400 Subject: pep8 cleaning --- nova/compute/manager.py | 47 +++++++++++----------- .../015_add_auto_assign_to_floating_ips.py | 1 + nova/network/api.py | 6 +-- 3 files changed, 28 insertions(+), 26 deletions(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index c1dc06557..13a082b44 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -37,8 +37,6 @@ terminating it. import datetime import os -import random -import string import socket import sys import tempfile @@ -54,7 +52,7 @@ from nova import rpc from nova import utils from nova.compute import power_state from nova.virt import driver -from nova.network import api as network_api +from nova import network FLAGS = flags.FLAGS flags.DEFINE_string('instances_path', '$state_path/instances', @@ -74,8 +72,8 @@ flags.DEFINE_integer('live_migration_retry_count', 30, flags.DEFINE_integer("rescue_timeout", 0, "Automatically unrescue an instance after N seconds." " Set to 0 to disable.") -flags.DEFINE_bool('auto_assign_floating_ip', False, 'Autoassigning floating' - ' ip to VM') +flags.DEFINE_bool('auto_assign_floating_ip', False, + 'Autoassigning floating ip to VM') LOG = logging.getLogger('nova.compute.manager') @@ -135,7 +133,7 @@ class ComputeManager(manager.SchedulerDependentManager): self.network_manager = utils.import_object(FLAGS.network_manager) self.volume_manager = utils.import_object(FLAGS.volume_manager) - self.network_api = network_api.API() + self.network_api = network.API() super(ComputeManager, self).__init__(service_name="compute", *args, **kwargs) @@ -248,18 +246,18 @@ class ComputeManager(manager.SchedulerDependentManager): instance_id, power_state.SHUTDOWN) - if not FLAGS.stub_network: - if FLAGS.auto_assign_floating_ip: - public_ip = self.network_api.allocate_floating_ip(context) + if not FLAGS.stub_network and FLAGS.auto_assign_floating_ip: + public_ip = self.network_api.allocate_floating_ip(context) - self.db.floating_ip_set_auto_assigned(context, public_ip) - fixed_ip = self.db.fixed_ip_get_by_address(context, address) - floating_ip = self.db.floating_ip_get_by_address(context, - public_ip) + self.db.floating_ip_set_auto_assigned(context, public_ip) + fixed_ip = self.db.fixed_ip_get_by_address(context, address) + floating_ip = self.db.floating_ip_get_by_address(context, + public_ip) - self.network_api.associate_floating_ip(context, floating_ip, - fixed_ip, - affect_auto_assigned=True) + self.network_api.associate_floating_ip(context, + floating_ip, + fixed_ip, + affect_auto_assigned=True) self._update_state(context, instance_id) @exception.wrap_exception @@ -280,14 +278,17 @@ class ComputeManager(manager.SchedulerDependentManager): # NOTE(vish): Right now we don't really care if the ip is # disassociated. We may need to worry about # checking this later. - self.network_api.disassociate_floating_ip(context, address, - affect_auto_assigned=True) - if FLAGS.auto_assign_floating_ip \ - and floating_ip.get('auto_assigned'): + self.network_api.disassociate_floating_ip(context, + address, + True) + if (FLAGS.auto_assign_floating_ip + and floating_ip.get('auto_assigned')): LOG.debug(_("Deallocating floating ip %s"), - floating_ip['address'], context=context) - self.network_api.release_floating_ip(context, address, - affect_auto_assigned=True) + floating_ip['address'], + context=context) + self.network_api.release_floating_ip(context, + address, + True) address = fixed_ip['address'] if address: diff --git a/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py index f3767b29f..64d6df77f 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py @@ -22,6 +22,7 @@ from migrate import * meta = MetaData() + c_auto_assigned = Column('auto_assigned', Boolean, default=False) diff --git a/nova/network/api.py b/nova/network/api.py index 61db646ae..1d8193b28 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -52,7 +52,7 @@ class API(base.Base): "args": {"project_id": context.project_id}}) def release_floating_ip(self, context, address, - affect_auto_assigned = False): + affect_auto_assigned=False): floating_ip = self.db.floating_ip_get_by_address(context, address) if not affect_auto_assigned and floating_ip.get('auto_assigned'): return @@ -66,7 +66,7 @@ class API(base.Base): "args": {"floating_address": floating_ip['address']}}) def associate_floating_ip(self, context, floating_ip, fixed_ip, - affect_auto_assigned = False): + affect_auto_assigned=False): if isinstance(fixed_ip, str) or isinstance(fixed_ip, unicode): fixed_ip = self.db.fixed_ip_get_by_address(context, fixed_ip) floating_ip = self.db.floating_ip_get_by_address(context, floating_ip) @@ -97,7 +97,7 @@ class API(base.Base): "fixed_address": fixed_ip['address']}}) def disassociate_floating_ip(self, context, address, - affect_auto_assigned = False): + affect_auto_assigned=False): floating_ip = self.db.floating_ip_get_by_address(context, address) if not affect_auto_assigned and floating_ip.get('auto_assigned'): return -- cgit From e1f37b81e805c087947c87a9bc341dd60e7e481c Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Thu, 21 Apr 2011 15:49:47 +0400 Subject: style fixing --- nova/compute/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 13a082b44..864b3a816 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -48,11 +48,11 @@ from nova import exception from nova import flags from nova import log as logging from nova import manager +from nova import network from nova import rpc from nova import utils from nova.compute import power_state from nova.virt import driver -from nova import network FLAGS = flags.FLAGS flags.DEFINE_string('instances_path', '$state_path/instances', -- cgit From ea11033935192ee26ea6d0d0dad47a0a624b17a0 Mon Sep 17 00:00:00 2001 From: Jimmy Bergman Date: Thu, 21 Apr 2011 15:23:36 +0200 Subject: Add privateIpAddress and ipAddress to EC2 API DescribeInstances response. --- nova/api/ec2/cloud.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nova') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 4785d812a..4aa973290 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -726,7 +726,9 @@ class CloudController(object): instance['mac_address']) i['privateDnsName'] = fixed_addr + i['privateIpAddress'] = fixed_addr i['publicDnsName'] = floating_addr + i['ipAddress'] = floating_addr or fixed_addr i['dnsName'] = i['publicDnsName'] or i['privateDnsName'] i['keyName'] = instance['key_name'] -- cgit From 2d82195d59240ea53d4726879d2a28a5872e58f7 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 21 Apr 2011 07:39:49 -0700 Subject: use vpn filter in basic filtering so cloudpipe works with iptables driver --- nova/virt/libvirt_conn.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) (limited to 'nova') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 9c8d64446..3dcb8ae42 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1734,11 +1734,16 @@ class NWFilterFirewall(FirewallDriver): logging.info('ensuring static filters') self._ensure_static_filters() + if instance['image_id'] == str(FLAGS.vpn_image_id): + base_filter = 'nova-vpn' + else: + base_filter = 'nova-base' + for (network, mapping) in network_info: nic_id = mapping['mac'].replace(':', '') instance_filter_name = self._instance_filter_name(instance, nic_id) self._define_filter(self._filter_container(instance_filter_name, - ['nova-base'])) + [base_filter])) def _ensure_static_filters(self): if self.static_filters_configured: @@ -1749,11 +1754,12 @@ class NWFilterFirewall(FirewallDriver): 'no-ip-spoofing', 'no-arp-spoofing', 'allow-dhcp-server'])) + self._define_filter(self._filter_container('nova-vpn', + ['allow-dhcp-server'])) self._define_filter(self.nova_base_ipv4_filter) self._define_filter(self.nova_base_ipv6_filter) self._define_filter(self.nova_dhcp_filter) self._define_filter(self.nova_ra_filter) - self._define_filter(self.nova_vpn_filter) if FLAGS.allow_project_net_traffic: self._define_filter(self.nova_project_filter) if FLAGS.use_ipv6: @@ -1767,14 +1773,6 @@ class NWFilterFirewall(FirewallDriver): ''.join(["" % (f,) for f in filters])) return xml - nova_vpn_filter = ''' - 2086015e-cf03-11df-8c5d-080027c27973 - - - - - ''' - def nova_base_ipv4_filter(self): retval = "" for protocol in ['tcp', 'udp', 'icmp']: -- cgit From 5904cba617038600f3d8e7f65c71485abb163927 Mon Sep 17 00:00:00 2001 From: Eldar Nugaev Date: Thu, 21 Apr 2011 22:23:40 +0400 Subject: style cleaning --- .../migrate_repo/versions/015_add_auto_assign_to_floating_ips.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py index 64d6df77f..29b26b3dd 100644 --- a/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py +++ b/nova/db/sqlalchemy/migrate_repo/versions/015_add_auto_assign_to_floating_ips.py @@ -31,7 +31,9 @@ def upgrade(migrate_engine): # bind migrate_engine to your metadata meta.bind = migrate_engine - floating_ips = Table('floating_ips', meta, autoload=True, - autoload_with=migrate_engine) + floating_ips = Table('floating_ips', + meta, + autoload=True, + autoload_with=migrate_engine) floating_ips.create_column(c_auto_assigned) -- cgit From f710ad1e3fff16de696f608986f24bdc8ffc3f6b Mon Sep 17 00:00:00 2001 From: Ken Pepple Date: Fri, 22 Apr 2011 00:22:23 -0700 Subject: pep8 --- nova/compute/instance_types.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/compute/instance_types.py b/nova/compute/instance_types.py index 4e250bb83..7e7198b96 100644 --- a/nova/compute/instance_types.py +++ b/nova/compute/instance_types.py @@ -57,8 +57,8 @@ def create(name, memory, vcpus, local_gb, flavorid, swap=0, except exception.DBError, e: LOG.exception(_('DB error: %s') % e) raise exception.ApiError(_("Cannot create instance_type with " - "name %(name)s and flavorid %(flavorid)s") % - locals()) + "name %(name)s and flavorid %(flavorid)s") + % locals()) def destroy(name): -- cgit