From fe22186eb989b0302e1cb26a5b92cd77ce47bb9b Mon Sep 17 00:00:00 2001 From: Andy Southgate Date: Fri, 14 Jan 2011 16:13:50 +0000 Subject: OS-55: Inject network settings in linux images --- nova/virt/conn_common.py | 50 ++++++++++++++++++++++ nova/virt/disk.py | 19 +++++--- nova/virt/libvirt_conn.py | 24 ++--------- nova/virt/xenapi/vm_utils.py | 36 ++++++++++++++++ nova/virt/xenapi/vmops.py | 7 +++ .../xenapi/etc/xapi.d/plugins/pluginlib_nova.py | 1 + 6 files changed, 110 insertions(+), 27 deletions(-) create mode 100644 nova/virt/conn_common.py diff --git a/nova/virt/conn_common.py b/nova/virt/conn_common.py new file mode 100644 index 000000000..bd9ed7794 --- /dev/null +++ b/nova/virt/conn_common.py @@ -0,0 +1,50 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 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. + +from nova import context +from nova import db +from nova import exception +from nova import flags +from nova import log as logging +from nova import utils + +LOG = logging.getLogger('nova.virt.conn_common') +FLAGS = flags.FLAGS + +flags.DEFINE_string('injected_network_template', + utils.abspath('virt/interfaces.template'), + 'Template file for injected network') + +def get_injectables(inst): + key = str(inst['key_data']) + net = None + network_ref = db.network_get_by_instance(context.get_admin_context(), + inst['id']) + if network_ref['injected']: + admin_context = context.get_admin_context() + address = db.instance_get_fixed_address(admin_context, inst['id']) + ra_server = network_ref['ra_server'] + if not ra_server: + ra_server = "fd00::" + with open(FLAGS.injected_network_template) as f: + net = f.read() % {'address': address, + 'netmask': network_ref['netmask'], + 'gateway': network_ref['gateway'], + 'broadcast': network_ref['broadcast'], + 'dns': network_ref['dns'], + 'ra_server': ra_server} + + return key, net diff --git a/nova/virt/disk.py b/nova/virt/disk.py index c5565abfa..88b05f0c0 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -92,11 +92,7 @@ def inject_data(image, key=None, net=None, partition=None, nbd=False): % err) try: - if key: - # inject key file - _inject_key_into_fs(key, tmpdir) - if net: - _inject_net_into_fs(net, tmpdir) + inject_data_into_fs(tmpdir, key, net, execute) finally: # unmount device utils.execute('sudo umount %s' % mapped_device) @@ -158,8 +154,17 @@ def _allocate_device(): def _free_device(device): _DEVICES.append(device) +def inject_data_into_fs(fs, key, net, execute): + """Injects data into a filesystem already mounted by the caller. + Virt connections can call this directly if they mount their fs + in a different way to inject_data + """ + if key: + _inject_key_into_fs(key, fs, execute=execute) + if net: + _inject_net_into_fs(net, fs, execute=execute) -def _inject_key_into_fs(key, fs): +def _inject_key_into_fs(key, fs, execute=None): """Add the given public ssh key to root's authorized_keys. key is an ssh key string. @@ -173,7 +178,7 @@ def _inject_key_into_fs(key, fs): utils.execute('sudo tee -a %s' % keyfile, '\n' + key.strip() + '\n') -def _inject_net_into_fs(net, fs): +def _inject_net_into_fs(net, fs, execute=None): """Inject /etc/network/interfaces into the filesystem rooted at fs. net is the contents of /etc/network/interfaces. diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 4e0fd106f..45d8754ab 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -62,6 +62,7 @@ from nova.compute import instance_types from nova.compute import power_state from nova.virt import disk from nova.virt import images +from nova.virt import conn_common libvirt = None libxml2 = None @@ -74,9 +75,7 @@ FLAGS = flags.FLAGS flags.DEFINE_string('rescue_image_id', 'ami-rescue', 'Rescue ami image') flags.DEFINE_string('rescue_kernel_id', 'aki-rescue', 'Rescue aki image') flags.DEFINE_string('rescue_ramdisk_id', 'ari-rescue', 'Rescue ari image') -flags.DEFINE_string('injected_network_template', - utils.abspath('virt/interfaces.template'), - 'Template file for injected network') + flags.DEFINE_string('libvirt_xml_template', utils.abspath('virt/libvirt.xml.template'), 'Libvirt XML Template') @@ -622,23 +621,8 @@ class LibvirtConnection(object): if not inst['kernel_id']: target_partition = "1" - key = str(inst['key_data']) - net = None - network_ref = db.network_get_by_instance(context.get_admin_context(), - inst['id']) - if network_ref['injected']: - admin_context = context.get_admin_context() - address = db.instance_get_fixed_address(admin_context, inst['id']) - ra_server = network_ref['ra_server'] - if not ra_server: - ra_server = "fd00::" - with open(FLAGS.injected_network_template) as f: - net = f.read() % {'address': address, - 'netmask': network_ref['netmask'], - 'gateway': network_ref['gateway'], - 'broadcast': network_ref['broadcast'], - 'dns': network_ref['dns'], - 'ra_server': ra_server} + key, net = conn_common.get_injectables(inst) + if key or net: inst_name = inst['name'] img_id = inst.image_id diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 4bbd522c1..70f81b3b6 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -22,6 +22,7 @@ their attributes like VDIs, VIFs, as well as their lookup functions. import os import pickle import re +import tempfile import time import urllib from xml.dom import minidom @@ -33,10 +34,12 @@ from nova import flags from nova import log as logging from nova import utils from nova.auth.manager import AuthManager +from nova.compute import disk from nova.compute import instance_types from nova.compute import power_state from nova.virt import images from nova.virt.xenapi import HelperBase +from nova.virt import conn_common from nova.virt.xenapi.volume_utils import StorageError @@ -439,6 +442,39 @@ class VMHelper(HelperBase): else: return None + @classmethod + def preconfigure_instance(cls, session, instance, vdi_ref): + """Makes alterations to the image before launching as part of spawn. + May also set xenstore values to modify the image behaviour after + VM start.""" + + # As mounting the image VDI is expensive, we only want do do it once, + # if at all, so determine whether it's required first, and then do + # everything + mount_required = False + key, net = conn_common.get_injectables(instance) + if key is not None or net is not None: + mount_required = True + + if mount_required: + def _mounted_processing(device): + devPath = '/dev/'+device+'1' # Note: Partition 1 hardcoded + tmpdir = tempfile.mkdtemp() + try: + out, err = utils.execute('sudo mount %s %s' % (devPath, tmpdir)) + if err: + raise exception.Error(_('Failed to mount filesystem: %s') % err) + try: + disk.inject_data_into_fs(tmpdir, key, net, utils.execute) + finally: + utils.execute('sudo umount %s' % devPath) + finally: + # remove temporary directory + os.rmdir(tmpdir) + + # FIXME: Check self._session is the type of session this fn wants + with_vdi_attached_here(session, vdi_ref, False, _mounted_processing) + @classmethod def compile_info(cls, record): """Fill record with VM status information""" diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index e84ce20c4..efab9c9ce 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -79,6 +79,9 @@ class VMOps(object): disk_image_type = ImageType.DISK else: disk_image_type = ImageType.DISK_RAW + # TODO: Coalesce fetch_image, lookup_image and + # manipulate_root_image so requires a single VDI mount/umount + # sequence vdi_uuid = VMHelper.fetch_image(self._session, instance.id, instance.image_id, user, project, disk_image_type) vdi_ref = self._session.call_xenapi('VDI.get_by_uuid', vdi_uuid) @@ -102,6 +105,10 @@ class VMOps(object): if network_ref: VMHelper.create_vif(self._session, vm_ref, network_ref, instance.mac_address) + + # Alter the image before VM start for, e.g. network injection + VMHelper.preconfigure_instance(self._session, instance, vdi_ref) + LOG.debug(_('Starting VM %s...'), vm_ref) self._session.call_xenapi('VM.start', vm_ref, False, False) instance_name = instance.name diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py index f51f5fce4..dbbce5c51 100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py @@ -28,6 +28,7 @@ import re import time import XenAPI +import XenAPI ##### Logging setup -- cgit From 88be6540d2a796e313f2d8ef4ccc6e66ba1a3ed1 Mon Sep 17 00:00:00 2001 From: Andy Southgate Date: Thu, 20 Jan 2011 16:49:54 +0000 Subject: OS-55: Only modify Linux image with no or injection-incapable guest agent OS-55: Support network configuration via xenstore for Windows images --- nova/virt/xenapi/vm_utils.py | 105 +++++++++++++++++++++++++++++++++++++++---- nova/virt/xenapi/vmops.py | 3 ++ 2 files changed, 99 insertions(+), 9 deletions(-) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 70f81b3b6..79d529ce2 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -29,6 +29,8 @@ 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 @@ -458,22 +460,107 @@ class VMHelper(HelperBase): if mount_required: def _mounted_processing(device): - devPath = '/dev/'+device+'1' # Note: Partition 1 hardcoded + devPath = '/dev/'+device+'1' # NB: Partition 1 hardcoded tmpdir = tempfile.mkdtemp() try: - out, err = utils.execute('sudo mount %s %s' % (devPath, tmpdir)) - if err: - raise exception.Error(_('Failed to mount filesystem: %s') % err) + # Mount only Linux filesystems, as we mustn't disturb NTFS images try: - disk.inject_data_into_fs(tmpdir, key, net, utils.execute) - finally: - utils.execute('sudo umount %s' % devPath) + out, err = utils.execute('sudo mount -t ext2,ext3 "%s" "%s"' % (devPath, tmpdir)) + except exception.ProcessExecutionError as e: + err = str(e) + if err: + LOG.info('Failed to mount filesystem (expected for non-linux instances): %s' % err) + else: + try: + # This try block ensures that the umount occurs + + xe_update_networking_filename = os.path.join(tmpdir, 'usr', 'sbin', 'xe-update-networking') + if os.path.isfile(xe_update_networking_filename): + # The presence of the xe-update-networking file indicates that this guest + # agent can reconfigure the netwokr from xenstore data, so manipulation + # of files in /etc is not required + LOG.info('XenServer tools installed in this image are capable of network injection. ' + 'Networking files will not be manipulated') + else: + xe_daemon_filename = os.path.join(tmpdir, 'usr', 'sbin', 'xe-daemon') + if os.path.isfile(xe_daemon_filename): + LOG.info('XenServer tools are present in this image but ' + 'are not capable of network injection') + else: + LOG.info('XenServer tools are not installed in this image') + LOG.info('Manipulating interface files directly') + disk.inject_data_into_fs(tmpdir, key, net, utils.execute) + finally: + utils.execute('sudo umount "%s"' % devPath) finally: # remove temporary directory os.rmdir(tmpdir) - - # FIXME: Check self._session is the type of session this fn wants + with_vdi_attached_here(session, vdi_ref, False, _mounted_processing) + + + @classmethod + def preconfigure_xenstore(cls, session, instance, vm_ref): + XENSTORE_TYPES = { + 'BroadcastAddress' : 'multi_sz', + 'DefaultGateway' : 'multi_sz', + 'EnableDhcp' : 'dword', + 'IPAddress' : 'multi_sz', + 'NameServer' : 'string', + 'SubnetMask' : 'multi_sz' + } + + # Network setup + network_ref = db.network_get_by_instance(context.get_admin_context(), + instance['id']) + if network_ref['injected']: + admin_context = context.get_admin_context() + address = db.instance_get_fixed_address(admin_context, instance['id']) + + xenstore_data = { + # NB: Setting broadcast address is not supported by + # Windows or the Windows guest agent, and will be ignored + # on that platform + 'BroadcastAddress': network_ref['broadcast'], + 'EnableDhcp': '0', + 'IPAddress': address, + 'SubnetMask': network_ref['netmask'], + 'DefaultGateway': network_ref['gateway'], + 'NameServer': network_ref['dns'] + } + + device_to_configure = 0 # Configure network device 0 in the VM + + vif_refs = session.call_xenapi('VM.get_VIFs', vm_ref) + mac_addr = None + + for vif_ref in vif_refs: + device = session.call_xenapi('VIF.get_device', vif_ref) + if str(device) == str(device_to_configure): + mac_addr = session.call_xenapi('VIF.get_MAC', vif_ref) + break + + if mac_addr is None: + raise exception.NotFound('Networking device %s not found in VM') + + # MAC address must be upper case in the xenstore key, + # with colons replaced by underscores + underscore_mac_addr = mac_addr.replace(':', '_') + xenstore_prefix='vm-data/vif/'+underscore_mac_addr.upper()+'/tcpip/' + + for xenstore_key, xenstore_value in xenstore_data.iteritems(): + # NB: The xenstore_key part of the instance_key isn't used but must + # be unique. We set it to xenstore_key as a convenient unique name. + # The xenstore_key value takes effect in the /name element. + instance_key = xenstore_prefix + xenstore_key + type = XENSTORE_TYPES[xenstore_key] + + session.call_xenapi('VM.add_to_xenstore_data', vm_ref, instance_key+'/name', xenstore_key) + session.call_xenapi('VM.add_to_xenstore_data', vm_ref, instance_key+'/type', type) + if type == 'multi_sz': + session.call_xenapi('VM.add_to_xenstore_data', vm_ref, instance_key+'/data/0', xenstore_value) + else: + session.call_xenapi('VM.add_to_xenstore_data', vm_ref, instance_key+'/data', xenstore_value) @classmethod def compile_info(cls, record): diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index efab9c9ce..ae477f11e 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -109,6 +109,9 @@ class VMOps(object): # Alter the image before VM start for, e.g. network injection VMHelper.preconfigure_instance(self._session, instance, vdi_ref) + # Configure the VM's xenstore data before start for, e.g. network configuration + VMHelper.preconfigure_xenstore(self._session, instance, vm_ref) + LOG.debug(_('Starting VM %s...'), vm_ref) self._session.call_xenapi('VM.start', vm_ref, False, False) instance_name = instance.name -- cgit From 8f531ef7c0782feba46f83ec2e45d113753c4052 Mon Sep 17 00:00:00 2001 From: Andy Southgate Date: Thu, 20 Jan 2011 19:51:23 +0000 Subject: OS-55: pylint fixes --- nova/virt/xenapi/vm_utils.py | 96 ++++++++++++++++++++++++++++---------------- nova/virt/xenapi/vmops.py | 3 +- 2 files changed, 63 insertions(+), 36 deletions(-) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 79d529ce2..eb699d715 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -447,8 +447,7 @@ class VMHelper(HelperBase): @classmethod def preconfigure_instance(cls, session, instance, vdi_ref): """Makes alterations to the image before launching as part of spawn. - May also set xenstore values to modify the image behaviour after - VM start.""" + """ # As mounting the image VDI is expensive, we only want do do it once, # if at all, so determine whether it's required first, and then do @@ -460,38 +459,53 @@ class VMHelper(HelperBase): if mount_required: def _mounted_processing(device): - devPath = '/dev/'+device+'1' # NB: Partition 1 hardcoded + """Callback whioch runds with the image VDI attached""" + + dev_path = '/dev/'+device+'1' # NB: Partition 1 hardcoded tmpdir = tempfile.mkdtemp() try: - # Mount only Linux filesystems, as we mustn't disturb NTFS images + # Mount only Linux filesystems, to avoid disturbing + # NTFS images try: - out, err = utils.execute('sudo mount -t ext2,ext3 "%s" "%s"' % (devPath, tmpdir)) + _, err = utils.execute( + 'sudo mount -t ext2,ext3 "%s" "%s"' % + (dev_path, tmpdir)) except exception.ProcessExecutionError as e: err = str(e) if err: - LOG.info('Failed to mount filesystem (expected for non-linux instances): %s' % err) + LOG.info(_('Failed to mount filesystem (expected for ' + 'non-linux instances): %s') % err) else: try: # This try block ensures that the umount occurs - xe_update_networking_filename = os.path.join(tmpdir, 'usr', 'sbin', 'xe-update-networking') + xe_update_networking_filename = os.path.join(tmpdir, + 'usr', 'sbin', 'xe-update-networking') if os.path.isfile(xe_update_networking_filename): - # The presence of the xe-update-networking file indicates that this guest - # agent can reconfigure the netwokr from xenstore data, so manipulation - # of files in /etc is not required - LOG.info('XenServer tools installed in this image are capable of network injection. ' - 'Networking files will not be manipulated') + # The presence of the xe-update-networking + # file indicates that this guest agent can + # reconfigure the network from xenstore data, + # so manipulation of files in /etc is not + # required + LOG.info(_('XenServer tools installed in this ' + 'image are capable of network injection. ' + 'Networking files will not be manipulated')) else: - xe_daemon_filename = os.path.join(tmpdir, 'usr', 'sbin', 'xe-daemon') + xe_daemon_filename = os.path.join(tmpdir, 'usr', + 'sbin', 'xe-daemon') if os.path.isfile(xe_daemon_filename): - LOG.info('XenServer tools are present in this image but ' - 'are not capable of network injection') + LOG.info(_('XenServer tools are present in ' + 'this image but are not capable of ' + 'network injection')) else: - LOG.info('XenServer tools are not installed in this image') - LOG.info('Manipulating interface files directly') - disk.inject_data_into_fs(tmpdir, key, net, utils.execute) + LOG.info(_('XenServer tools are not ' + 'installed in this image')) + LOG.info(_('Manipulating interface files ' + 'directly')) + disk.inject_data_into_fs(tmpdir, key, net, + utils.execute) finally: - utils.execute('sudo umount "%s"' % devPath) + utils.execute('sudo umount "%s"' % dev_path) finally: # remove temporary directory os.rmdir(tmpdir) @@ -501,7 +515,11 @@ class VMHelper(HelperBase): @classmethod def preconfigure_xenstore(cls, session, instance, vm_ref): - XENSTORE_TYPES = { + """Sets xenstore values to modify the image behaviour after + VM start. + """ + + xenstore_types = { 'BroadcastAddress' : 'multi_sz', 'DefaultGateway' : 'multi_sz', 'EnableDhcp' : 'dword', @@ -511,11 +529,12 @@ class VMHelper(HelperBase): } # Network setup - network_ref = db.network_get_by_instance(context.get_admin_context(), - instance['id']) + network_ref = db.network_get_by_instance( + context.get_admin_context(), instance['id']) if network_ref['injected']: admin_context = context.get_admin_context() - address = db.instance_get_fixed_address(admin_context, instance['id']) + address = db.instance_get_fixed_address(admin_context, + instance['id']) xenstore_data = { # NB: Setting broadcast address is not supported by @@ -541,26 +560,33 @@ class VMHelper(HelperBase): break if mac_addr is None: - raise exception.NotFound('Networking device %s not found in VM') + raise exception.NotFound(_('Networking device %s not found ' + 'in VM')) # MAC address must be upper case in the xenstore key, # with colons replaced by underscores underscore_mac_addr = mac_addr.replace(':', '_') - xenstore_prefix='vm-data/vif/'+underscore_mac_addr.upper()+'/tcpip/' + xenstore_prefix = ('vm-data/vif/' + + underscore_mac_addr.upper() + '/tcpip/') for xenstore_key, xenstore_value in xenstore_data.iteritems(): - # NB: The xenstore_key part of the instance_key isn't used but must - # be unique. We set it to xenstore_key as a convenient unique name. - # The xenstore_key value takes effect in the /name element. + # NB: The xenstore_key part of the instance_key isn't used but + # must be unique. We set it to xenstore_key as a convenient + # unique name...The xenstore_key value takes effect in the + # /name element. instance_key = xenstore_prefix + xenstore_key - type = XENSTORE_TYPES[xenstore_key] - - session.call_xenapi('VM.add_to_xenstore_data', vm_ref, instance_key+'/name', xenstore_key) - session.call_xenapi('VM.add_to_xenstore_data', vm_ref, instance_key+'/type', type) - if type == 'multi_sz': - session.call_xenapi('VM.add_to_xenstore_data', vm_ref, instance_key+'/data/0', xenstore_value) + key_type = xenstore_types[xenstore_key] + + session.call_xenapi('VM.add_to_xenstore_data', vm_ref, + instance_key+'/name', xenstore_key) + session.call_xenapi('VM.add_to_xenstore_data', vm_ref, + instance_key+'/type', key_type) + if key_type == 'multi_sz': + session.call_xenapi('VM.add_to_xenstore_data', vm_ref, + instance_key+'/data/0', xenstore_value) else: - session.call_xenapi('VM.add_to_xenstore_data', vm_ref, instance_key+'/data', xenstore_value) + session.call_xenapi('VM.add_to_xenstore_data', vm_ref, + instance_key+'/data', xenstore_value) @classmethod def compile_info(cls, record): diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index ae477f11e..df282807a 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -109,7 +109,8 @@ class VMOps(object): # Alter the image before VM start for, e.g. network injection VMHelper.preconfigure_instance(self._session, instance, vdi_ref) - # Configure the VM's xenstore data before start for, e.g. network configuration + # Configure the VM's xenstore data before start for, + # e.g. network configuration VMHelper.preconfigure_xenstore(self._session, instance, vm_ref) LOG.debug(_('Starting VM %s...'), vm_ref) -- cgit From 1dbdb180cb93a812f8336bbfc49bf67a5203d1eb Mon Sep 17 00:00:00 2001 From: Andy Southgate Date: Fri, 21 Jan 2011 12:00:45 +0000 Subject: OS-55: Fix current unit tests --- nova/tests/db/fakes.py | 2 ++ nova/virt/xenapi/fake.py | 4 ++++ nova/virt/xenapi/vm_utils.py | 4 ++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/nova/tests/db/fakes.py b/nova/tests/db/fakes.py index 05bdd172e..72e093f99 100644 --- a/nova/tests/db/fakes.py +++ b/nova/tests/db/fakes.py @@ -58,6 +58,7 @@ def stub_out_db_instance_api(stubs): 'project_id': values['project_id'], 'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), 'instance_type': values['instance_type'], + 'key_data': None, 'memory_mb': type_data['memory_mb'], 'mac_address': values['mac_address'], 'vcpus': type_data['vcpus'], @@ -68,6 +69,7 @@ def stub_out_db_instance_api(stubs): def fake_network_get_by_instance(context, instance_id): fields = { 'bridge': 'xenbr0', + 'injected': False } return FakeModel(fields) diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index e8352771c..836252730 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -161,6 +161,10 @@ def after_VBD_create(vbd_ref, vbd_rec): vm_name_label = _db_content['VM'][vm_ref]['name_label'] vbd_rec['vm_name_label'] = vm_name_label +def after_VM_create(vm_ref, vm_rec): + """Create read-only fields in the VM record.""" + if 'is_control_domain' not in vm_rec: + vm_rec['is_control_domain'] = False def create_pbd(config, host_ref, sr_ref, attached): return _create_object('PBD', { diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index eb699d715..9b78a178a 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -459,7 +459,7 @@ class VMHelper(HelperBase): if mount_required: def _mounted_processing(device): - """Callback whioch runds with the image VDI attached""" + """Callback which runs with the image VDI attached""" dev_path = '/dev/'+device+'1' # NB: Partition 1 hardcoded tmpdir = tempfile.mkdtemp() @@ -467,7 +467,7 @@ class VMHelper(HelperBase): # Mount only Linux filesystems, to avoid disturbing # NTFS images try: - _, err = utils.execute( + out, err = utils.execute( 'sudo mount -t ext2,ext3 "%s" "%s"' % (dev_path, tmpdir)) except exception.ProcessExecutionError as e: -- cgit From 9039c2cc59904a72fc71255a3a31ec2b17018963 Mon Sep 17 00:00:00 2001 From: Andy Southgate Date: Fri, 21 Jan 2011 16:06:32 +0000 Subject: OS-55: Added unit test for network injection via xenstore --- nova/tests/db/fakes.py | 54 ++++++++++++++++++++++++++++++---------------- nova/tests/test_xenapi.py | 46 +++++++++++++++++++++++++++++++++------ nova/tests/xenapi/stubs.py | 15 +++++++++++++ nova/virt/xenapi/fake.py | 12 +++++++++-- 4 files changed, 100 insertions(+), 27 deletions(-) diff --git a/nova/tests/db/fakes.py b/nova/tests/db/fakes.py index 72e093f99..e92efbedd 100644 --- a/nova/tests/db/fakes.py +++ b/nova/tests/db/fakes.py @@ -23,23 +23,22 @@ from nova import db from nova import utils from nova.compute import instance_types +class FakeModel(object): + """ Stubs out for model """ + def __init__(self, values): + self.values = values -def stub_out_db_instance_api(stubs): - """ Stubs out the db API for creating Instances """ + def __getattr__(self, name): + return self.values[name] - class FakeModel(object): - """ Stubs out for model """ - def __init__(self, values): - self.values = values + def __getitem__(self, key): + if key in self.values: + return self.values[key] + else: + raise NotImplementedError() - def __getattr__(self, name): - return self.values[name] - - def __getitem__(self, key): - if key in self.values: - return self.values[key] - else: - raise NotImplementedError() +def stub_out_db_instance_api(stubs): + """ Stubs out the db API for creating Instances """ def fake_instance_create(values): """ Stubs out the db.instance_create method """ @@ -66,12 +65,29 @@ def stub_out_db_instance_api(stubs): } return FakeModel(base_options) - def fake_network_get_by_instance(context, instance_id): - fields = { + stubs.Set(db, 'instance_create', fake_instance_create) + +def stub_out_db_network_api(stubs, injected = False): + """Stubs out the db API for retrieving networks""" + network_fields = { 'bridge': 'xenbr0', - 'injected': False + 'injected': injected } - return FakeModel(fields) - stubs.Set(db, 'instance_create', fake_instance_create) + if injected: + network_fields.update({ + 'netmask': '255.255.255.0', + 'gateway': '10.0.0.1', + 'broadcast': '10.0.0.255', + 'dns': '10.0.0.2' + }) + + def fake_network_get_by_instance(context, instance_id): + return FakeModel(network_fields) + + def fake_instance_get_fixed_address(context, instance_id): + return '10.0.0.3' + stubs.Set(db, 'network_get_by_instance', fake_network_get_by_instance) + stubs.Set(db, 'instance_get_fixed_address', fake_instance_get_fixed_address) + diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 9f5b266f3..2a32845d3 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -50,6 +50,7 @@ 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 = {'name': 1, 'id': 1, @@ -158,6 +159,7 @@ class XenAPIVMTestCase(test.TestCase): xenapi_fake.reset() xenapi_fake.create_local_srs() db_fakes.stub_out_db_instance_api(self.stubs) + db_fakes.stub_out_db_network_api(self.stubs) xenapi_fake.create_network('fake', FLAGS.flat_network_bridge) stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) stubs.stubout_get_this_vm_uuid(self.stubs) @@ -211,7 +213,7 @@ class XenAPIVMTestCase(test.TestCase): check() - def check_vm_record(self, conn): + def check_vm_record(self, conn, check_injection = False): instances = conn.list_instances() self.assertEquals(instances, [1]) @@ -219,10 +221,10 @@ class XenAPIVMTestCase(test.TestCase): vm_info = conn.get_info(1) # Get XenAPI record for VM - vms = [rec for ref, rec + vms = [(ref, rec) for ref, rec in xenapi_fake.get_all_records('VM').iteritems() if not rec['is_control_domain']] - vm = vms[0] + vm_ref, vm = vms[0] # Check that m1.large above turned into the right thing. instance_type = instance_types.INSTANCE_TYPES['m1.large'] @@ -242,8 +244,35 @@ class XenAPIVMTestCase(test.TestCase): # Check that the VM is running according to XenAPI. self.assertEquals(vm['power_state'], 'Running') - - def _test_spawn(self, image_id, kernel_id, ramdisk_id): + + if check_injection: + xenstore_data = xenapi_fake.VM_get_xenstore_data(vm_ref) + key_prefix = 'vm-data/vif/22_33_2A_B3_CC_DD/tcpip/' + tcpip_data = dict([(k.replace(key_prefix, ''), v) + for k, v in xenstore_data.iteritems() if k.startswith(key_prefix) ]) + + self.assertEquals(tcpip_data, { + 'BroadcastAddress/data/0': '10.0.0.255', + 'BroadcastAddress/name': 'BroadcastAddress', + 'BroadcastAddress/type': 'multi_sz', + 'DefaultGateway/data/0': '10.0.0.1', + 'DefaultGateway/name': 'DefaultGateway', + 'DefaultGateway/type': 'multi_sz', + 'EnableDhcp/data': '0', + 'EnableDhcp/name': 'EnableDhcp', + 'EnableDhcp/type': 'dword', + 'IPAddress/data/0': '10.0.0.3', + 'IPAddress/name': 'IPAddress', + 'IPAddress/type': 'multi_sz', + 'NameServer/data': '10.0.0.2', + 'NameServer/name': 'NameServer', + 'NameServer/type': 'string', + 'SubnetMask/data/0': '255.255.255.0', + 'SubnetMask/name': 'SubnetMask', + 'SubnetMask/type': 'multi_sz' + }) + + def _test_spawn(self, image_id, kernel_id, ramdisk_id, check_injection = False): stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) values = {'name': 1, 'id': 1, @@ -258,7 +287,7 @@ class XenAPIVMTestCase(test.TestCase): conn = xenapi_conn.get_connection(False) instance = db.instance_create(values) conn.spawn(instance) - self.check_vm_record(conn) + self.check_vm_record(conn, check_injection = check_injection) def test_spawn_raw_objectstore(self): FLAGS.xenapi_image_service = 'objectstore' @@ -276,6 +305,11 @@ class XenAPIVMTestCase(test.TestCase): FLAGS.xenapi_image_service = 'glance' self._test_spawn(1, 2, 3) + def test_spawn_netinject(self): + FLAGS.xenapi_image_service = 'glance' + db_fakes.stub_out_db_network_api(self.stubs, injected = True) + self._test_spawn(1, 2, 3, check_injection = True) + def tearDown(self): super(XenAPIVMTestCase, self).tearDown() self.manager.delete_project(self.project) diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index 624995ada..3cfc8e5f9 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -171,6 +171,21 @@ class FakeSessionForVMTests(fake.SessionBase): def VM_destroy(self, session_ref, vm_ref): fake.destroy_vm(vm_ref) + def VM_get_VIFs(self, session_ref, vm_ref): + return (['0', '1', '2']) + + def VIF_get_device(self, session_ref, vif_ref): + return ('1', '0', '2')[int(vif_ref)] + + def VIF_get_MAC(self, session_ref, vif_ref): + return ( + '11:22:2a:b3:CC:dd', + '22:33:2a:b3:CC:dd', + '44:44:2a:b3:CC:dd' + )[int(vif_ref)] + + def VM_add_to_xenstore_data(self, session_ref, vm_ref, key, value): + fake.VM_add_to_xenstore_data(vm_ref, key, value) class FakeSessionForVolumeTests(fake.SessionBase): """ Stubs out a XenAPISession for Volume tests """ diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index 836252730..f9ae8766a 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -148,7 +148,15 @@ def create_vbd(vm_ref, vdi_ref): after_VBD_create(vbd_ref, vbd_rec) return vbd_ref - +def VM_get_xenstore_data(vm_ref): + return _db_content['VM'][vm_ref].get('xenstore_data', '') + +def VM_add_to_xenstore_data(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 after_VBD_create(vbd_ref, vbd_rec): """Create read-only fields and backref from VM to VBD when VBD is created.""" @@ -401,7 +409,7 @@ class SessionBase(object): field in _db_content[cls][ref]): return _db_content[cls][ref][field] - LOG.debuug(_('Raising NotImplemented')) + LOG.debug(_('Raising NotImplemented')) raise NotImplementedError( _('xenapi.fake does not have an implementation for %s or it has ' 'been called with the wrong number of arguments') % name) -- cgit From 48990b109eb39f0dd4ea7bf86be79f6e03c3ad74 Mon Sep 17 00:00:00 2001 From: Andy Southgate Date: Fri, 21 Jan 2011 16:23:30 +0000 Subject: OS55: pylint fixes --- nova/tests/test_xenapi.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 2a32845d3..c94aeefd1 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -249,7 +249,8 @@ class XenAPIVMTestCase(test.TestCase): xenstore_data = xenapi_fake.VM_get_xenstore_data(vm_ref) key_prefix = 'vm-data/vif/22_33_2A_B3_CC_DD/tcpip/' tcpip_data = dict([(k.replace(key_prefix, ''), v) - for k, v in xenstore_data.iteritems() if k.startswith(key_prefix) ]) + for k, v in xenstore_data.iteritems() + if k.startswith(key_prefix) ]) self.assertEquals(tcpip_data, { 'BroadcastAddress/data/0': '10.0.0.255', @@ -272,7 +273,8 @@ class XenAPIVMTestCase(test.TestCase): 'SubnetMask/type': 'multi_sz' }) - def _test_spawn(self, image_id, kernel_id, ramdisk_id, check_injection = False): + def _test_spawn(self, image_id, kernel_id, ramdisk_id, + check_injection = False): stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) values = {'name': 1, 'id': 1, -- cgit From c97618e1eaff4091f01381073a298d0f67050126 Mon Sep 17 00:00:00 2001 From: Andy Southgate Date: Mon, 24 Jan 2011 18:28:50 +0000 Subject: OS-55: Post-merge fixes --- nova/tests/db/fakes.py | 3 ++- nova/virt/xenapi/vm_utils.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/nova/tests/db/fakes.py b/nova/tests/db/fakes.py index e92efbedd..4a0d1cc3c 100644 --- a/nova/tests/db/fakes.py +++ b/nova/tests/db/fakes.py @@ -79,7 +79,8 @@ def stub_out_db_network_api(stubs, injected = False): 'netmask': '255.255.255.0', 'gateway': '10.0.0.1', 'broadcast': '10.0.0.255', - 'dns': '10.0.0.2' + 'dns': '10.0.0.2', + 'ra_server': None }) def fake_network_get_by_instance(context, instance_id): diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 9b78a178a..68946a2bb 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -36,9 +36,9 @@ from nova import flags from nova import log as logging from nova import utils from nova.auth.manager import AuthManager -from nova.compute import disk from nova.compute import instance_types from nova.compute import power_state +from nova.virt import disk from nova.virt import images from nova.virt.xenapi import HelperBase from nova.virt import conn_common -- cgit From cd346a2cda13833f976b9e838d67cf17c52f327e Mon Sep 17 00:00:00 2001 From: Andy Southgate Date: Mon, 24 Jan 2011 19:04:25 +0000 Subject: OS-55: PEP8 fixes --- nova/tests/db/fakes.py | 17 +++++----- nova/tests/test_xenapi.py | 19 +++++------ nova/tests/xenapi/stubs.py | 10 +++--- nova/virt/conn_common.py | 3 +- nova/virt/disk.py | 2 ++ nova/virt/xenapi/fake.py | 9 +++-- nova/virt/xenapi/vm_utils.py | 80 ++++++++++++++++++++++---------------------- nova/virt/xenapi/vmops.py | 6 ++-- 8 files changed, 77 insertions(+), 69 deletions(-) diff --git a/nova/tests/db/fakes.py b/nova/tests/db/fakes.py index 4a0d1cc3c..c47fba5f3 100644 --- a/nova/tests/db/fakes.py +++ b/nova/tests/db/fakes.py @@ -23,6 +23,7 @@ from nova import db from nova import utils from nova.compute import instance_types + class FakeModel(object): """ Stubs out for model """ def __init__(self, values): @@ -37,6 +38,7 @@ class FakeModel(object): else: raise NotImplementedError() + def stub_out_db_instance_api(stubs): """ Stubs out the db API for creating Instances """ @@ -67,12 +69,12 @@ def stub_out_db_instance_api(stubs): stubs.Set(db, 'instance_create', fake_instance_create) -def stub_out_db_network_api(stubs, injected = False): + +def stub_out_db_network_api(stubs, injected=False): """Stubs out the db API for retrieving networks""" network_fields = { - 'bridge': 'xenbr0', - 'injected': injected - } + 'bridge': 'xenbr0', + 'injected': injected} if injected: network_fields.update({ @@ -80,8 +82,7 @@ def stub_out_db_network_api(stubs, injected = False): 'gateway': '10.0.0.1', 'broadcast': '10.0.0.255', 'dns': '10.0.0.2', - 'ra_server': None - }) + 'ra_server': None}) def fake_network_get_by_instance(context, instance_id): return FakeModel(network_fields) @@ -90,5 +91,5 @@ def stub_out_db_network_api(stubs, injected = False): return '10.0.0.3' stubs.Set(db, 'network_get_by_instance', fake_network_get_by_instance) - stubs.Set(db, 'instance_get_fixed_address', fake_instance_get_fixed_address) - + stubs.Set(db, 'instance_get_fixed_address', + fake_instance_get_fixed_address) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index c94aeefd1..f7f7102a8 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -213,7 +213,7 @@ class XenAPIVMTestCase(test.TestCase): check() - def check_vm_record(self, conn, check_injection = False): + def check_vm_record(self, conn, check_injection=False): instances = conn.list_instances() self.assertEquals(instances, [1]) @@ -244,13 +244,13 @@ class XenAPIVMTestCase(test.TestCase): # Check that the VM is running according to XenAPI. self.assertEquals(vm['power_state'], 'Running') - + if check_injection: xenstore_data = xenapi_fake.VM_get_xenstore_data(vm_ref) key_prefix = 'vm-data/vif/22_33_2A_B3_CC_DD/tcpip/' tcpip_data = dict([(k.replace(key_prefix, ''), v) for k, v in xenstore_data.iteritems() - if k.startswith(key_prefix) ]) + if k.startswith(key_prefix)]) self.assertEquals(tcpip_data, { 'BroadcastAddress/data/0': '10.0.0.255', @@ -270,11 +270,10 @@ class XenAPIVMTestCase(test.TestCase): 'NameServer/type': 'string', 'SubnetMask/data/0': '255.255.255.0', 'SubnetMask/name': 'SubnetMask', - 'SubnetMask/type': 'multi_sz' - }) + 'SubnetMask/type': 'multi_sz'}) def _test_spawn(self, image_id, kernel_id, ramdisk_id, - check_injection = False): + check_injection=False): stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) values = {'name': 1, 'id': 1, @@ -289,7 +288,7 @@ class XenAPIVMTestCase(test.TestCase): conn = xenapi_conn.get_connection(False) instance = db.instance_create(values) conn.spawn(instance) - self.check_vm_record(conn, check_injection = check_injection) + self.check_vm_record(conn, check_injection=check_injection) def test_spawn_raw_objectstore(self): FLAGS.xenapi_image_service = 'objectstore' @@ -309,9 +308,9 @@ class XenAPIVMTestCase(test.TestCase): def test_spawn_netinject(self): FLAGS.xenapi_image_service = 'glance' - db_fakes.stub_out_db_network_api(self.stubs, injected = True) - self._test_spawn(1, 2, 3, check_injection = True) - + db_fakes.stub_out_db_network_api(self.stubs, injected=True) + self._test_spawn(1, 2, 3, check_injection=True) + def tearDown(self): super(XenAPIVMTestCase, self).tearDown() self.manager.delete_project(self.project) diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index 3cfc8e5f9..027855592 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -173,20 +173,20 @@ class FakeSessionForVMTests(fake.SessionBase): def VM_get_VIFs(self, session_ref, vm_ref): return (['0', '1', '2']) - + def VIF_get_device(self, session_ref, vif_ref): return ('1', '0', '2')[int(vif_ref)] - + def VIF_get_MAC(self, session_ref, vif_ref): return ( '11:22:2a:b3:CC:dd', '22:33:2a:b3:CC:dd', - '44:44:2a:b3:CC:dd' - )[int(vif_ref)] - + '44:44:2a:b3:CC:dd')[int(vif_ref)] + def VM_add_to_xenstore_data(self, session_ref, vm_ref, key, value): fake.VM_add_to_xenstore_data(vm_ref, key, value) + class FakeSessionForVolumeTests(fake.SessionBase): """ Stubs out a XenAPISession for Volume tests """ def __init__(self, uri): diff --git a/nova/virt/conn_common.py b/nova/virt/conn_common.py index bd9ed7794..5550b50c1 100644 --- a/nova/virt/conn_common.py +++ b/nova/virt/conn_common.py @@ -27,7 +27,8 @@ FLAGS = flags.FLAGS flags.DEFINE_string('injected_network_template', utils.abspath('virt/interfaces.template'), 'Template file for injected network') - + + def get_injectables(inst): key = str(inst['key_data']) net = None diff --git a/nova/virt/disk.py b/nova/virt/disk.py index 88b05f0c0..21bb53369 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -154,6 +154,7 @@ def _allocate_device(): def _free_device(device): _DEVICES.append(device) + def inject_data_into_fs(fs, key, net, execute): """Injects data into a filesystem already mounted by the caller. Virt connections can call this directly if they mount their fs @@ -164,6 +165,7 @@ def inject_data_into_fs(fs, key, net, execute): if net: _inject_net_into_fs(net, fs, execute=execute) + def _inject_key_into_fs(key, fs, execute=None): """Add the given public ssh key to root's authorized_keys. diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index f9ae8766a..561e47911 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -148,15 +148,18 @@ def create_vbd(vm_ref, vdi_ref): after_VBD_create(vbd_ref, vbd_rec) return vbd_ref + def VM_get_xenstore_data(vm_ref): return _db_content['VM'][vm_ref].get('xenstore_data', '') - + + def VM_add_to_xenstore_data(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 after_VBD_create(vbd_ref, vbd_rec): """Create read-only fields and backref from VM to VBD when VBD is created.""" @@ -169,11 +172,13 @@ def after_VBD_create(vbd_ref, vbd_rec): vm_name_label = _db_content['VM'][vm_ref]['name_label'] vbd_rec['vm_name_label'] = vm_name_label + def after_VM_create(vm_ref, vm_rec): """Create read-only fields in the VM record.""" if 'is_control_domain' not in vm_rec: vm_rec['is_control_domain'] = False + def create_pbd(config, host_ref, sr_ref, attached): return _create_object('PBD', { 'device-config': config, diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 68946a2bb..fa60c44c3 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -448,7 +448,7 @@ class VMHelper(HelperBase): def preconfigure_instance(cls, session, instance, vdi_ref): """Makes alterations to the image before launching as part of spawn. """ - + # As mounting the image VDI is expensive, we only want do do it once, # if at all, so determine whether it's required first, and then do # everything @@ -456,12 +456,13 @@ class VMHelper(HelperBase): key, net = conn_common.get_injectables(instance) if key is not None or net is not None: mount_required = True - + if mount_required: + def _mounted_processing(device): """Callback which runs with the image VDI attached""" - - dev_path = '/dev/'+device+'1' # NB: Partition 1 hardcoded + + dev_path = '/dev/' + device + '1' # NB: Partition 1 hardcoded tmpdir = tempfile.mkdtemp() try: # Mount only Linux filesystems, to avoid disturbing @@ -478,9 +479,9 @@ class VMHelper(HelperBase): else: try: # This try block ensures that the umount occurs - - xe_update_networking_filename = os.path.join(tmpdir, - 'usr', 'sbin', 'xe-update-networking') + + xe_update_networking_filename = os.path.join( + tmpdir, 'usr', 'sbin', 'xe-update-networking') if os.path.isfile(xe_update_networking_filename): # The presence of the xe-update-networking # file indicates that this guest agent can @@ -489,14 +490,15 @@ class VMHelper(HelperBase): # required LOG.info(_('XenServer tools installed in this ' 'image are capable of network injection. ' - 'Networking files will not be manipulated')) + 'Networking files will not be' + 'manipulated')) else: - xe_daemon_filename = os.path.join(tmpdir, 'usr', - 'sbin', 'xe-daemon') + xe_daemon_filename = os.path.join(tmpdir, + 'usr', 'sbin', 'xe-daemon') if os.path.isfile(xe_daemon_filename): - LOG.info(_('XenServer tools are present in ' - 'this image but are not capable of ' - 'network injection')) + LOG.info(_('XenServer tools are present ' + 'in this image but are not capable ' + 'of network injection')) else: LOG.info(_('XenServer tools are not ' 'installed in this image')) @@ -510,24 +512,23 @@ class VMHelper(HelperBase): # remove temporary directory os.rmdir(tmpdir) - with_vdi_attached_here(session, vdi_ref, False, _mounted_processing) - - + with_vdi_attached_here(session, vdi_ref, False, + _mounted_processing) + @classmethod def preconfigure_xenstore(cls, session, instance, vm_ref): """Sets xenstore values to modify the image behaviour after VM start. """ - + xenstore_types = { - 'BroadcastAddress' : 'multi_sz', - 'DefaultGateway' : 'multi_sz', - 'EnableDhcp' : 'dword', - 'IPAddress' : 'multi_sz', - 'NameServer' : 'string', - 'SubnetMask' : 'multi_sz' - } - + 'BroadcastAddress': 'multi_sz', + 'DefaultGateway': 'multi_sz', + 'EnableDhcp': 'dword', + 'IPAddress': 'multi_sz', + 'NameServer': 'string', + 'SubnetMask': 'multi_sz'} + # Network setup network_ref = db.network_get_by_instance( context.get_admin_context(), instance['id']) @@ -535,8 +536,8 @@ class VMHelper(HelperBase): admin_context = context.get_admin_context() address = db.instance_get_fixed_address(admin_context, instance['id']) - - xenstore_data = { + + xenstore_data = { # NB: Setting broadcast address is not supported by # Windows or the Windows guest agent, and will be ignored # on that platform @@ -545,30 +546,29 @@ class VMHelper(HelperBase): 'IPAddress': address, 'SubnetMask': network_ref['netmask'], 'DefaultGateway': network_ref['gateway'], - 'NameServer': network_ref['dns'] - } - - device_to_configure = 0 # Configure network device 0 in the VM - + 'NameServer': network_ref['dns']} + + device_to_configure = 0 # Configure network device 0 in the VM + vif_refs = session.call_xenapi('VM.get_VIFs', vm_ref) mac_addr = None - + for vif_ref in vif_refs: device = session.call_xenapi('VIF.get_device', vif_ref) if str(device) == str(device_to_configure): mac_addr = session.call_xenapi('VIF.get_MAC', vif_ref) break - + if mac_addr is None: raise exception.NotFound(_('Networking device %s not found ' 'in VM')) - + # MAC address must be upper case in the xenstore key, # with colons replaced by underscores underscore_mac_addr = mac_addr.replace(':', '_') xenstore_prefix = ('vm-data/vif/' + underscore_mac_addr.upper() + '/tcpip/') - + for xenstore_key, xenstore_value in xenstore_data.iteritems(): # NB: The xenstore_key part of the instance_key isn't used but # must be unique. We set it to xenstore_key as a convenient @@ -578,15 +578,15 @@ class VMHelper(HelperBase): key_type = xenstore_types[xenstore_key] session.call_xenapi('VM.add_to_xenstore_data', vm_ref, - instance_key+'/name', xenstore_key) + instance_key + '/name', xenstore_key) session.call_xenapi('VM.add_to_xenstore_data', vm_ref, - instance_key+'/type', key_type) + instance_key + '/type', key_type) if key_type == 'multi_sz': session.call_xenapi('VM.add_to_xenstore_data', vm_ref, - instance_key+'/data/0', xenstore_value) + instance_key + '/data/0', xenstore_value) else: session.call_xenapi('VM.add_to_xenstore_data', vm_ref, - instance_key+'/data', xenstore_value) + instance_key + '/data', xenstore_value) @classmethod def compile_info(cls, record): diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index df282807a..9eea19c42 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -105,14 +105,14 @@ class VMOps(object): if network_ref: VMHelper.create_vif(self._session, vm_ref, network_ref, instance.mac_address) - + # Alter the image before VM start for, e.g. network injection VMHelper.preconfigure_instance(self._session, instance, vdi_ref) - + # Configure the VM's xenstore data before start for, # e.g. network configuration VMHelper.preconfigure_xenstore(self._session, instance, vm_ref) - + LOG.debug(_('Starting VM %s...'), vm_ref) self._session.call_xenapi('VM.start', vm_ref, False, False) instance_name = instance.name -- cgit From 0f868b00dbb2de469dde3519f2370e59937c4fc6 Mon Sep 17 00:00:00 2001 From: Andy Southgate Date: Wed, 26 Jan 2011 19:58:45 +0000 Subject: OS-55: Added a test case for XenAPI file-based network injection OS-55: Stubbed out utils.execute for all XenAPI VM tests, including command simulation where necessary --- nova/tests/fake_utils.py | 98 +++++++++++++++++++++++++++++++++++++++++++++++ nova/tests/test_xenapi.py | 83 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 nova/tests/fake_utils.py diff --git a/nova/tests/fake_utils.py b/nova/tests/fake_utils.py new file mode 100644 index 000000000..f51d31e0c --- /dev/null +++ b/nova/tests/fake_utils.py @@ -0,0 +1,98 @@ +# 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): + """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, process_input=None, addl_env=None, check_exit_code=True): + """This function stubs out execute, optionally executing + a preconfigued function to return expected data + """ + global _fake_execute_repliers + + LOG.debug(_("Faking execution of cmd (subprocess): %s"), cmd) + _fake_execute_log.append(cmd) + + reply_handler = fake_execute_default_reply_handler + + for fake_replier in _fake_execute_repliers: + if re.match(fake_replier[0], cmd): + 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, addl_env, + 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 f7f7102a8..8af47da63 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -18,11 +18,14 @@ Test suite for XenAPI """ +import os +import re import stubout from nova import db from nova import context from nova import flags +from nova import log as logging from nova import test from nova import utils from nova.auth import manager @@ -35,6 +38,9 @@ from nova.virt.xenapi.vmops import SimpleDH 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.tests import fake_utils + +LOG = logging.getLogger('nova.tests.test_xenapi') FLAGS = flags.FLAGS @@ -166,6 +172,7 @@ class XenAPIVMTestCase(test.TestCase): stubs.stubout_stream_disk(self.stubs) glance_stubs.stubout_glance_client(self.stubs, glance_stubs.FakeGlance) + fake_utils.stub_out_utils_execute(self.stubs) self.conn = xenapi_conn.get_connection(False) def test_list_instances_0(self): @@ -306,11 +313,85 @@ class XenAPIVMTestCase(test.TestCase): FLAGS.xenapi_image_service = 'glance' self._test_spawn(1, 2, 3) - def test_spawn_netinject(self): + def test_spawn_netinject_file(self): + FLAGS.xenapi_image_service = 'glance' + db_fakes.stub_out_db_network_api(self.stubs, injected=True) + + self._tee_executed = False + + def _tee_handler(cmd, input, *ignore_args): + self.assertNotEqual(input, None) + + config = [line.strip() for line in input.split("\n")] + + # Find the start of eth0 configuration and check it + index = config.index('auto eth0') + self.assertEquals(config[index + 1:index + 8], [ + 'iface eth0 inet static', + 'address 10.0.0.3', + 'netmask 255.255.255.0', + 'broadcast 10.0.0.255', + 'gateway 10.0.0.1', + 'dns-nameservers 10.0.0.2', + '']) + + self._tee_executed = True + + return '', '' + + fake_utils.fake_execute_set_repliers([ + # Capture the sudo tee .../etc/network/interfaces command + (r'(sudo\s+)?tee.*interfaces', _tee_handler), + ]) + self._test_spawn(1, 2, 3, check_injection=True) + self.assertTrue(self._tee_executed) + + def test_spawn_netinject_xenstore(self): FLAGS.xenapi_image_service = 'glance' db_fakes.stub_out_db_network_api(self.stubs, injected=True) + + self._tee_executed = False + + def _mount_handler(cmd, *ignore_args): + # When mounting, create real files under the mountpoint to simulate + # files in the mounted filesystem + + # RegExp extracts the path of the mountpoint + match = re.match(r'(sudo\s+)?mount[^"]*"[^"]*"\s+"([^"]*)"', cmd) + self._tmpdir = match.group(2) + LOG.debug(_('Creating files in %s to simulate guest agent' % + self._tmpdir)) + os.makedirs(os.path.join(self._tmpdir, 'usr', 'sbin')) + # Touch the file using open + open(os.path.join(self._tmpdir, 'usr', 'sbin', + 'xe-update-networking'), 'w').close() + return '', '' + + def _umount_handler(cmd, *ignore_args): + # Umount would normall make files in the m,ounted filesystem + # disappear, so do that here + LOG.debug(_('Removing simulated guest agent files in %s' % + self._tmpdir)) + os.remove(os.path.join(self._tmpdir, 'usr', 'sbin', + 'xe-update-networking')) + os.rmdir(os.path.join(self._tmpdir, 'usr', 'sbin')) + os.rmdir(os.path.join(self._tmpdir, 'usr')) + return '', '' + + def _tee_handler(cmd, input, *ignore_args): + self._tee_executed = True + return '', '' + + fake_utils.fake_execute_set_repliers([ + (r'(sudo\s+)?mount', _mount_handler), + (r'(sudo\s+)?umount', _umount_handler), + (r'(sudo\s+)?tee.*interfaces', _tee_handler)]) self._test_spawn(1, 2, 3, check_injection=True) + # tee must not run in this case, where an injection-capable + # guest agent is detected + self.assertFalse(self._tee_executed) + def tearDown(self): super(XenAPIVMTestCase, self).tearDown() self.manager.delete_project(self.project) -- cgit From 46456155d42dd8a668b370fa84972c388094e1d8 Mon Sep 17 00:00:00 2001 From: Andy Southgate Date: Fri, 28 Jan 2011 18:40:19 +0000 Subject: OS-55: Fix typo for libvirt_conn operation --- nova/virt/disk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/virt/disk.py b/nova/virt/disk.py index 21bb53369..98121df2a 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -92,7 +92,7 @@ def inject_data(image, key=None, net=None, partition=None, nbd=False): % err) try: - inject_data_into_fs(tmpdir, key, net, execute) + inject_data_into_fs(tmpdir, key, net, utils.execute) finally: # unmount device utils.execute('sudo umount %s' % mapped_device) -- cgit From 2dfcfccd74821851c965ee2912fd315e25e7f838 Mon Sep 17 00:00:00 2001 From: Andy Southgate Date: Tue, 15 Feb 2011 12:15:49 +0000 Subject: OS-55: Moved conn_common code into disk.py --- nova/virt/conn_common.py | 51 -------------------------------------------- nova/virt/disk.py | 27 +++++++++++++++++++++++ nova/virt/libvirt_conn.py | 3 +-- nova/virt/xenapi/vm_utils.py | 3 +-- 4 files changed, 29 insertions(+), 55 deletions(-) delete mode 100644 nova/virt/conn_common.py diff --git a/nova/virt/conn_common.py b/nova/virt/conn_common.py deleted file mode 100644 index 5550b50c1..000000000 --- a/nova/virt/conn_common.py +++ /dev/null @@ -1,51 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2010 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. - -from nova import context -from nova import db -from nova import exception -from nova import flags -from nova import log as logging -from nova import utils - -LOG = logging.getLogger('nova.virt.conn_common') -FLAGS = flags.FLAGS - -flags.DEFINE_string('injected_network_template', - utils.abspath('virt/interfaces.template'), - 'Template file for injected network') - - -def get_injectables(inst): - key = str(inst['key_data']) - net = None - network_ref = db.network_get_by_instance(context.get_admin_context(), - inst['id']) - if network_ref['injected']: - admin_context = context.get_admin_context() - address = db.instance_get_fixed_address(admin_context, inst['id']) - ra_server = network_ref['ra_server'] - if not ra_server: - ra_server = "fd00::" - with open(FLAGS.injected_network_template) as f: - net = f.read() % {'address': address, - 'netmask': network_ref['netmask'], - 'gateway': network_ref['gateway'], - 'broadcast': network_ref['broadcast'], - 'dns': network_ref['dns'], - 'ra_server': ra_server} - - return key, net diff --git a/nova/virt/disk.py b/nova/virt/disk.py index 98121df2a..cee1ffbce 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -26,6 +26,8 @@ import os import tempfile import time +from nova import context +from nova import db from nova import exception from nova import flags from nova import log as logging @@ -38,6 +40,9 @@ flags.DEFINE_integer('minimum_root_size', 1024 * 1024 * 1024 * 10, 'minimum size in bytes of root partition') flags.DEFINE_integer('block_size', 1024 * 1024 * 256, 'block_size to use for dd') +flags.DEFINE_string('injected_network_template', + utils.abspath('virt/interfaces.template'), + 'Template file for injected network') def extend(image, size): @@ -155,6 +160,28 @@ def _free_device(device): _DEVICES.append(device) +def get_injectables(inst): + key = str(inst['key_data']) + net = None + network_ref = db.network_get_by_instance(context.get_admin_context(), + inst['id']) + if network_ref['injected']: + admin_context = context.get_admin_context() + address = db.instance_get_fixed_address(admin_context, inst['id']) + ra_server = network_ref['ra_server'] + if not ra_server: + ra_server = "fd00::" + with open(FLAGS.injected_network_template) as f: + net = f.read() % {'address': address, + 'netmask': network_ref['netmask'], + 'gateway': network_ref['gateway'], + 'broadcast': network_ref['broadcast'], + 'dns': network_ref['dns'], + 'ra_server': ra_server} + + return key, net + + def inject_data_into_fs(fs, key, net, execute): """Injects data into a filesystem already mounted by the caller. Virt connections can call this directly if they mount their fs diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 45d8754ab..806f35a81 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -62,7 +62,6 @@ from nova.compute import instance_types from nova.compute import power_state from nova.virt import disk from nova.virt import images -from nova.virt import conn_common libvirt = None libxml2 = None @@ -621,7 +620,7 @@ class LibvirtConnection(object): if not inst['kernel_id']: target_partition = "1" - key, net = conn_common.get_injectables(inst) + key, net = disk.get_injectables(inst) 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 fa60c44c3..603cef1f6 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -41,7 +41,6 @@ from nova.compute import power_state from nova.virt import disk from nova.virt import images from nova.virt.xenapi import HelperBase -from nova.virt import conn_common from nova.virt.xenapi.volume_utils import StorageError @@ -453,7 +452,7 @@ class VMHelper(HelperBase): # if at all, so determine whether it's required first, and then do # everything mount_required = False - key, net = conn_common.get_injectables(instance) + key, net = disk.get_injectables(instance) if key is not None or net is not None: mount_required = True -- cgit From 552875913e263d0e44be4613f0a07d3b53067e96 Mon Sep 17 00:00:00 2001 From: Andy Southgate Date: Wed, 16 Feb 2011 19:32:45 +0000 Subject: Fixed merge error --- nova/virt/xenapi/vm_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index c5347498d..4b9883d21 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -597,6 +597,7 @@ class VMHelper(HelperBase): session.call_xenapi('VM.add_to_xenstore_data', vm_ref, instance_key + '/data', xenstore_value) + @classmethod def lookup_kernel_ramdisk(cls, session, vm): vm_rec = session.get_xenapi().VM.get_record(vm) if 'PV_kernel' in vm_rec and 'PV_ramdisk' in vm_rec: -- cgit From 75190533d887a8824f234aa5e6185328f6f9b4f8 Mon Sep 17 00:00:00 2001 From: Ewan Mellor Date: Tue, 22 Feb 2011 19:54:42 +0000 Subject: Remove duplicate import gained across a merge. --- plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py index dbbce5c51..108459a27 100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py @@ -28,8 +28,6 @@ import re import time import XenAPI -import XenAPI - ##### Logging setup def configure_logging(name): -- cgit From 9049ad18f0ad40361d936c2066bded3436082275 Mon Sep 17 00:00:00 2001 From: Ewan Mellor Date: Tue, 22 Feb 2011 20:12:30 +0000 Subject: Put the whitespace back *sigh* --- plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py index 108459a27..f51f5fce4 100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py @@ -28,6 +28,7 @@ import re import time import XenAPI + ##### Logging setup def configure_logging(name): -- cgit From 8d1f22255ee968e0693ac0932677a4830a45e57e Mon Sep 17 00:00:00 2001 From: Ewan Mellor Date: Tue, 22 Feb 2011 20:28:31 +0000 Subject: Added Andy Southgate to the Authors file. --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index 494e614a0..f638c3c09 100644 --- a/Authors +++ b/Authors @@ -1,4 +1,5 @@ Andy Smith +Andy Southgate Anne Gentle Anthony Young Antony Messerli -- cgit From d1a6cb96c1c72894cbba24e6806da5c81fb915df Mon Sep 17 00:00:00 2001 From: Ewan Mellor Date: Tue, 22 Feb 2011 20:39:33 +0000 Subject: Removed block of code that resurrected itself in the last merge. --- nova/virt/xenapi/vmops.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 1b2712d15..c9cc8e698 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -108,10 +108,6 @@ class VMOps(object): instance, kernel, ramdisk, pv_kernel) VMHelper.create_vbd(self._session, vm_ref, vdi_ref, 0, True) - if network_ref: - VMHelper.create_vif(self._session, vm_ref, - network_ref, instance.mac_address) - # Alter the image before VM start for, e.g. network injection VMHelper.preconfigure_instance(self._session, instance, vdi_ref) -- cgit From 72940957611bcba7dc41bfe9232743369d9a151f Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Mon, 28 Feb 2011 16:04:26 +0000 Subject: Fixed default value for xenapi_agent_path flag --- nova/virt/xenapi_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 546a10d09..4fc69961d 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -106,7 +106,7 @@ flags.DEFINE_integer('xenapi_inject_image', ' data into the disk image should be made.' ' Used only if connection_type=xenapi.') flags.DEFINE_integer('xenapi_agent_path', - True, + '/usr/sbin/xe-update-networking' 'Specifies the path in which the xenapi guest agent' ' should be located. If the agent is present,' ' network configuration if not injected into the image' -- cgit From 026c83551fa2e07f7f20d6b163f7da93e331b084 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Mon, 28 Feb 2011 16:59:00 +0000 Subject: Fixed obvious errors with flags. Note: tests still fail. --- nova/virt/xenapi_conn.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 4fc69961d..b9e87c2ce 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -100,13 +100,13 @@ flags.DEFINE_integer('xenapi_vhd_coalesce_max_attempts', 5, 'Max number of times to poll for VHD to coalesce.' ' Used only if connection_type=xenapi.') -flags.DEFINE_integer('xenapi_inject_image', +flags.DEFINE_bool('xenapi_inject_image', True, 'Specifies whether an attempt to inject network/key' ' data into the disk image should be made.' ' Used only if connection_type=xenapi.') -flags.DEFINE_integer('xenapi_agent_path', - '/usr/sbin/xe-update-networking' +flags.DEFINE_string('xenapi_agent_path', + '/usr/sbin/xe-update-networking', 'Specifies the path in which the xenapi guest agent' ' should be located. If the agent is present,' ' network configuration if not injected into the image' -- cgit From 4e4711ccfc7ce3c3df704a8635dccd506d6e7f01 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Tue, 1 Mar 2011 00:28:59 +0000 Subject: Units tests fixed partially. Still need to address checking data injected into xenstore need to convert string into dict or similar. Also todo PEP8 fixes --- nova/tests/db/fakes.py | 41 +++++++++++++++++++++++++++++++---------- nova/tests/test_xenapi.py | 42 ++++++++++++++++++------------------------ nova/tests/xenapi/stubs.py | 3 +++ nova/virt/xenapi/fake.py | 9 +++++++++ nova/virt/xenapi/vm_utils.py | 3 ++- nova/virt/xenapi/vmops.py | 7 +++++++ nova/virt/xenapi_conn.py | 2 +- 7 files changed, 71 insertions(+), 36 deletions(-) diff --git a/nova/tests/db/fakes.py b/nova/tests/db/fakes.py index c47fba5f3..b939f99a0 100644 --- a/nova/tests/db/fakes.py +++ b/nova/tests/db/fakes.py @@ -36,6 +36,8 @@ class FakeModel(object): if key in self.values: return self.values[key] else: + print "Key:%s" %key + print "Values:%s" %self.values raise NotImplementedError() @@ -70,26 +72,45 @@ def stub_out_db_instance_api(stubs): stubs.Set(db, 'instance_create', fake_instance_create) -def stub_out_db_network_api(stubs, injected=False): +def stub_out_db_network_api(stubs, injected=True): """Stubs out the db API for retrieving networks""" + network_fields = { + 'id': 'test', 'bridge': 'xenbr0', + 'label': 'test_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} - - if injected: - network_fields.update({ - 'netmask': '255.255.255.0', - 'gateway': '10.0.0.1', - 'broadcast': '10.0.0.255', - 'dns': '10.0.0.2', - 'ra_server': None}) + + fixed_ip_fields = { + 'address':'10.0.0.3', + 'network_id':'test'} def fake_network_get_by_instance(context, instance_id): return FakeModel(network_fields) + def fake_network_get_all_by_instance(context, instance_id): + l = [] + l.append(FakeModel(network_fields)) + return l + def fake_instance_get_fixed_address(context, instance_id): - return '10.0.0.3' + return FakeModel(fixed_ip_fields).address + + def fake_fixed_ip_get_all_by_instance(context, instance_id): + l = [] + l.append(FakeModel(fixed_ip_fields)) + return l stubs.Set(db, 'network_get_by_instance', fake_network_get_by_instance) stubs.Set(db, 'instance_get_fixed_address', fake_instance_get_fixed_address) + stubs.Set(db, 'network_get_all_by_instance', + fake_network_get_all_by_instance) + stubs.Set(db, 'fixed_ip_get_all_by_instance', + fake_fixed_ip_get_all_by_instance) + \ No newline at end of file diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index ce89a53c8..10a1b6c11 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -261,30 +261,21 @@ class XenAPIVMTestCase(test.TestCase): if check_injection: xenstore_data = xenapi_fake.VM_get_xenstore_data(vm_ref) - key_prefix = 'vm-data/vif/22_33_2A_B3_CC_DD/tcpip/' - tcpip_data = dict([(k.replace(key_prefix, ''), v) - for k, v in xenstore_data.iteritems() - if k.startswith(key_prefix)]) - - self.assertEquals(tcpip_data, { - 'BroadcastAddress/data/0': '10.0.0.255', - 'BroadcastAddress/name': 'BroadcastAddress', - 'BroadcastAddress/type': 'multi_sz', - 'DefaultGateway/data/0': '10.0.0.1', - 'DefaultGateway/name': 'DefaultGateway', - 'DefaultGateway/type': 'multi_sz', - 'EnableDhcp/data': '0', - 'EnableDhcp/name': 'EnableDhcp', - 'EnableDhcp/type': 'dword', - 'IPAddress/data/0': '10.0.0.3', - 'IPAddress/name': 'IPAddress', - 'IPAddress/type': 'multi_sz', - 'NameServer/data': '10.0.0.2', - 'NameServer/name': 'NameServer', - 'NameServer/type': 'string', - 'SubnetMask/data/0': '255.255.255.0', - 'SubnetMask/name': 'SubnetMask', - 'SubnetMask/type': 'multi_sz'}) + key = 'vm-data/networking/aabbccddeeff' + LOG.debug("Xenstore data: %s",xenstore_data) + xenstore_value=xenstore_data[key] + #tcpip_data = dict([(k, v) + # for k, v in xenstore_value.iteritems() + # if k.startswith(key_prefix)]) + #LOG.debug("tcpip data: %s",tcpip_data) + #self.assertEquals(tcpip_data['label'],'test_network') + #self.assertEquals(tcpip_data, { + # 'label': 'test_network', + # 'broadcast': '10.0.0.255', + # 'ips': [{'ip': '10.0.0.3', 'netmask':'255.255.255.0', 'enabled':'1'}], + # 'mac': 'aa:bb:cc:dd:ee:ff', + # 'dns': ['10.0.0.2'], + # 'gateway': '10.0.0.1'}) def _test_spawn(self, image_id, kernel_id, ramdisk_id, instance_type="m1.large", check_injection=False): @@ -340,6 +331,9 @@ class XenAPIVMTestCase(test.TestCase): # Find the start of eth0 configuration and check it index = config.index('auto eth0') + LOG.debug("CONFIG") + LOG.debug(config) + self.assertEquals(config[index + 1:index + 8], [ 'iface eth0 inet static', 'address 10.0.0.3', diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index 6981b4f7a..551d326a4 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -191,6 +191,9 @@ class FakeSessionForVMTests(fake.SessionBase): def VM_add_to_xenstore_data(self, session_ref, vm_ref, key, value): fake.VM_add_to_xenstore_data(vm_ref, key, value) + + def VM_remove_from_xenstore_data(self, session_ref, vm_ref, key): + fake.VM_remove_from_xenstore_data(vm_ref, key) class FakeSessionForVolumeTests(fake.SessionBase): diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index 08e2a03f7..664cfbd79 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -153,7 +153,14 @@ def VM_get_xenstore_data(vm_ref): return _db_content['VM'][vm_ref].get('xenstore_data', '') +def VM_remove_from_xenstore_data(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(vm_ref, key, value): + LOG.debug("ADDING TO XENSTORE DATA %s %s",key,value) db_ref = _db_content['VM'][vm_ref] if not 'xenstore_data' in db_ref: db_ref['xenstore_data'] = {} @@ -503,7 +510,9 @@ class SessionBase(object): def _get_by_field(self, recs, k, v, return_singleton): result = [] + LOG.debug("_get_by_field!!!! - %d", return_singleton) for ref, rec in recs.iteritems(): + LOG.debug("k:%s,rec[k]:%s,v:%s",k,rec.get(k),v) if rec.get(k) == v: result.append(ref) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 0434f745d..a01bab8de 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -409,8 +409,10 @@ class VMHelper(HelperBase): @classmethod def lookup(cls, session, i): """Look the instance i up, and returns it if available""" + LOG.debug("Entering lookup for instance:%s",str(i)) vms = session.get_xenapi().VM.get_by_name_label(i) n = len(vms) + LOG.debug("n:%d",n) if n == 0: return None elif n > 1: @@ -480,7 +482,6 @@ class VMHelper(HelperBase): else: try: # This try block ensures that the umount occurs - xe_guest_agent_filename = os.path.join( tmpdir, FLAGS.xenapi_agent_path) if os.path.isfile(xe_guest_agent_filename): diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index cc84b7032..c137d4931 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -117,6 +117,7 @@ class VMOps(object): VMHelper.preconfigure_instance(self._session, instance, vdi_ref) # inject_network_info and create vifs + LOG.debug("About to run inject_network_info") networks = self.inject_network_info(instance) self.create_vifs(instance, networks) @@ -186,6 +187,7 @@ class VMOps(object): # Must be the instance name instance_name = instance_or_vm except (AttributeError, KeyError): + # # Note the the KeyError will only happen with fakes.py # Not a string; must be an ID or a vm instance if isinstance(instance_or_vm, (int, long)): @@ -201,8 +203,12 @@ class VMOps(object): instance_name = instance_or_vm else: instance_name = instance_or_vm.name + #fake xenapi does not use OpaqueRef as a prefix + #when running tests we will always end up here vm = VMHelper.lookup(self._session, instance_name) if vm is None: + if FLAGS.xenapi_connection_url == 'test_url': + return instance_or_vm raise exception.NotFound( _('Instance not present %s') % instance_name) return vm @@ -496,6 +502,7 @@ class VMOps(object): 'ips': [ip_dict(ip) for ip in network_IPs]} self.write_to_param_xenstore(vm_opaque_ref, {location: mapping}) try: + logging.debug("About to run write_to_xenstore") self.write_to_xenstore(vm_opaque_ref, location, mapping['location']) except KeyError: diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index b9e87c2ce..9f8b6af02 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -106,7 +106,7 @@ flags.DEFINE_bool('xenapi_inject_image', ' data into the disk image should be made.' ' Used only if connection_type=xenapi.') flags.DEFINE_string('xenapi_agent_path', - '/usr/sbin/xe-update-networking', + 'usr/sbin/xe-update-networking', 'Specifies the path in which the xenapi guest agent' ' should be located. If the agent is present,' ' network configuration if not injected into the image' -- cgit From 8806858918f396cfca41a28c191dc9e8d2809a0e Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Tue, 1 Mar 2011 01:10:38 +0000 Subject: Fixed xenapi tests Gave up on clever things with map stored as string in xenstore. Used ast.liteeral_eval instead. Changed instance ID and names in tests from in to string (1 => '1') This simplified VMOps._get_vm_opaqueref Changed VMOps._get_vm_opaqueref as references returned by fake xenapi do not start with "OpaqueRef:" prefix. Fixed PEP8 Errors --- nova/tests/db/fakes.py | 13 +++++-------- nova/tests/test_xenapi.py | 46 ++++++++++++++++++++------------------------ nova/tests/xenapi/stubs.py | 2 +- nova/virt/xenapi/fake.py | 6 ++---- nova/virt/xenapi/vm_utils.py | 8 -------- nova/virt/xenapi/vmops.py | 15 +++------------ 6 files changed, 32 insertions(+), 58 deletions(-) diff --git a/nova/tests/db/fakes.py b/nova/tests/db/fakes.py index b939f99a0..7aa72e4a3 100644 --- a/nova/tests/db/fakes.py +++ b/nova/tests/db/fakes.py @@ -36,8 +36,6 @@ class FakeModel(object): if key in self.values: return self.values[key] else: - print "Key:%s" %key - print "Values:%s" %self.values raise NotImplementedError() @@ -74,7 +72,7 @@ def stub_out_db_instance_api(stubs): def stub_out_db_network_api(stubs, injected=True): """Stubs out the db API for retrieving networks""" - + network_fields = { 'id': 'test', 'bridge': 'xenbr0', @@ -83,12 +81,12 @@ def stub_out_db_network_api(stubs, injected=True): 'gateway': '10.0.0.1', 'broadcast': '10.0.0.255', 'dns': '10.0.0.2', - 'ra_server': None, + 'ra_server': None, 'injected': injected} - + fixed_ip_fields = { - 'address':'10.0.0.3', - 'network_id':'test'} + 'address': '10.0.0.3', + 'network_id': 'test'} def fake_network_get_by_instance(context, instance_id): return FakeModel(network_fields) @@ -113,4 +111,3 @@ def stub_out_db_network_api(stubs, injected=True): fake_network_get_all_by_instance) stubs.Set(db, 'fixed_ip_get_all_by_instance', fake_fixed_ip_get_all_by_instance) - \ No newline at end of file diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 10a1b6c11..a3d3371a1 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -21,6 +21,7 @@ Test suite for XenAPI import os import re import stubout +import ast from nova import db from nova import context @@ -204,7 +205,7 @@ class XenAPIVMTestCase(test.TestCase): if not vm_rec["is_control_domain"]: vm_labels.append(vm_rec["name_label"]) - self.assertEquals(vm_labels, [1]) + self.assertEquals(vm_labels, ['1']) def ensure_vbd_was_torn_down(): vbd_labels = [] @@ -212,7 +213,7 @@ class XenAPIVMTestCase(test.TestCase): vbd_rec = xenapi_fake.get_record('VBD', vbd_ref) vbd_labels.append(vbd_rec["vm_name_label"]) - self.assertEquals(vbd_labels, [1]) + self.assertEquals(vbd_labels, ['1']) def ensure_vdi_was_torn_down(): for vdi_ref in xenapi_fake.get_all('VDI'): @@ -229,10 +230,10 @@ class XenAPIVMTestCase(test.TestCase): def check_vm_record(self, conn, check_injection=False): instances = conn.list_instances() - self.assertEquals(instances, [1]) + self.assertEquals(instances, ['1']) # Get Nova record for VM - vm_info = conn.get_info(1) + vm_info = conn.get_info('1') # Get XenAPI record for VM vms = [(ref, rec) for ref, rec @@ -262,27 +263,24 @@ class XenAPIVMTestCase(test.TestCase): if check_injection: xenstore_data = xenapi_fake.VM_get_xenstore_data(vm_ref) key = 'vm-data/networking/aabbccddeeff' - LOG.debug("Xenstore data: %s",xenstore_data) - xenstore_value=xenstore_data[key] - #tcpip_data = dict([(k, v) - # for k, v in xenstore_value.iteritems() - # if k.startswith(key_prefix)]) - #LOG.debug("tcpip data: %s",tcpip_data) - #self.assertEquals(tcpip_data['label'],'test_network') - #self.assertEquals(tcpip_data, { - # 'label': 'test_network', - # 'broadcast': '10.0.0.255', - # 'ips': [{'ip': '10.0.0.3', 'netmask':'255.255.255.0', 'enabled':'1'}], - # 'mac': 'aa:bb:cc:dd:ee:ff', - # 'dns': ['10.0.0.2'], - # 'gateway': '10.0.0.1'}) + xenstore_value = xenstore_data[key] + tcpip_data = ast.literal_eval(xenstore_value) + self.assertEquals(tcpip_data, { + 'label': 'test_network', + 'broadcast': '10.0.0.255', + 'ips': [{'ip': '10.0.0.3', + 'netmask':'255.255.255.0', + 'enabled':'1'}], + 'mac': 'aa:bb:cc:dd:ee:ff', + 'dns': ['10.0.0.2'], + 'gateway': '10.0.0.1'}) def _test_spawn(self, image_id, kernel_id, ramdisk_id, instance_type="m1.large", check_injection=False): stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) - values = {'name': 1, - 'id': 1, + values = {'name': "1", + 'id': "1", 'project_id': self.project.id, 'user_id': self.user.id, 'image_id': image_id, @@ -331,9 +329,7 @@ class XenAPIVMTestCase(test.TestCase): # Find the start of eth0 configuration and check it index = config.index('auto eth0') - LOG.debug("CONFIG") - LOG.debug(config) - + self.assertEquals(config[index + 1:index + 8], [ 'iface eth0 inet static', 'address 10.0.0.3', @@ -409,8 +405,8 @@ class XenAPIVMTestCase(test.TestCase): def _create_instance(self): """Creates and spawns a test instance""" values = { - 'name': 1, - 'id': 1, + 'name': '1', + 'id': '1', 'project_id': self.project.id, 'user_id': self.user.id, 'image_id': 1, diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index 551d326a4..2392a97b3 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -191,7 +191,7 @@ class FakeSessionForVMTests(fake.SessionBase): def VM_add_to_xenstore_data(self, session_ref, vm_ref, key, value): fake.VM_add_to_xenstore_data(vm_ref, key, value) - + def VM_remove_from_xenstore_data(self, session_ref, vm_ref, key): fake.VM_remove_from_xenstore_data(vm_ref, key) diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index 664cfbd79..7aa82211d 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -156,11 +156,11 @@ def VM_get_xenstore_data(vm_ref): def VM_remove_from_xenstore_data(vm_ref, key): db_ref = _db_content['VM'][vm_ref] if not 'xenstore_data' in db_ref: - return + return db_ref['xenstore_data'][key] = None + def VM_add_to_xenstore_data(vm_ref, key, value): - LOG.debug("ADDING TO XENSTORE DATA %s %s",key,value) db_ref = _db_content['VM'][vm_ref] if not 'xenstore_data' in db_ref: db_ref['xenstore_data'] = {} @@ -510,9 +510,7 @@ class SessionBase(object): def _get_by_field(self, recs, k, v, return_singleton): result = [] - LOG.debug("_get_by_field!!!! - %d", return_singleton) for ref, rec in recs.iteritems(): - LOG.debug("k:%s,rec[k]:%s,v:%s",k,rec.get(k),v) if rec.get(k) == v: result.append(ref) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index a01bab8de..510261b15 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -409,10 +409,8 @@ class VMHelper(HelperBase): @classmethod def lookup(cls, session, i): """Look the instance i up, and returns it if available""" - LOG.debug("Entering lookup for instance:%s",str(i)) vms = session.get_xenapi().VM.get_by_name_label(i) n = len(vms) - LOG.debug("n:%d",n) if n == 0: return None elif n > 1: @@ -451,20 +449,17 @@ class VMHelper(HelperBase): # As mounting the image VDI is expensive, we only want do do it once, # if at all, so determine whether it's required first, and then do # everything - LOG.debug("Running preconfigure_instance") mount_required = False key, net = disk.get_injectables(instance) if key is not None or net is not None: mount_required = True - LOG.debug("Mount_required:%s", str(mount_required)) if mount_required: def _mounted_processing(device): """Callback which runs with the image VDI attached""" dev_path = '/dev/' + device + '1' # NB: Partition 1 hardcoded - LOG.debug("Device path:%s", dev_path) tmpdir = tempfile.mkdtemp() try: # Mount only Linux filesystems, to avoid disturbing @@ -473,7 +468,6 @@ class VMHelper(HelperBase): out, err = utils.execute( 'sudo mount -t ext2,ext3 "%s" "%s"' % (dev_path, tmpdir)) - LOG.debug("filesystem mounted") except exception.ProcessExecutionError as e: err = str(e) if err: @@ -506,7 +500,6 @@ class VMHelper(HelperBase): 'installed in this image')) LOG.info(_('Manipulating interface files ' 'directly')) - LOG.debug("Going to inject data in filesystem") disk.inject_data_into_fs(tmpdir, key, net, utils.execute) finally: @@ -748,7 +741,6 @@ def vbd_unplug_with_retry(session, vbd): # FIXME(sirp): We can use LoopingCall here w/o blocking sleep() while True: try: - LOG.debug("About to unplug VBD") session.get_xenapi().VBD.unplug(vbd) LOG.debug(_('VBD.unplug successful first time.')) return diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index c137d4931..a2f3a8f09 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -112,12 +112,10 @@ class VMOps(object): # Alter the image before VM start for, e.g. network injection #TODO(salvatore-orlando): do this only if flag is true - LOG.debug("About to run preconfigure_instance") if FLAGS.xenapi_inject_image: VMHelper.preconfigure_instance(self._session, instance, vdi_ref) # inject_network_info and create vifs - LOG.debug("About to run inject_network_info") networks = self.inject_network_info(instance) self.create_vifs(instance, networks) @@ -192,19 +190,12 @@ class VMOps(object): # Not a string; must be an ID or a vm instance if isinstance(instance_or_vm, (int, long)): ctx = context.get_admin_context() - try: - instance_obj = db.instance_get(ctx, instance_or_vm) - instance_name = instance_obj.name - except exception.NotFound: - # The unit tests screw this up, as they use an integer for - # the vm name. I'd fix that up, but that's a matter for - # another bug report. So for now, just try with the passed - # value - instance_name = instance_or_vm + instance_obj = db.instance_get(ctx, instance_or_vm) + instance_name = instance_obj.name else: instance_name = instance_or_vm.name #fake xenapi does not use OpaqueRef as a prefix - #when running tests we will always end up here + #when running tests we will always end up here vm = VMHelper.lookup(self._session, instance_name) if vm is None: if FLAGS.xenapi_connection_url == 'test_url': -- cgit From e0f1490e481e5b3e0e28b25049cc69eb905b74d6 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Wed, 2 Mar 2011 09:50:44 +0000 Subject: Removed excess TODO comments and debug line --- nova/virt/xenapi/vmops.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index a2f3a8f09..ec620f918 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -111,7 +111,6 @@ class VMOps(object): VMHelper.create_vbd(self._session, vm_ref, vdi_ref, 0, True) # Alter the image before VM start for, e.g. network injection - #TODO(salvatore-orlando): do this only if flag is true if FLAGS.xenapi_inject_image: VMHelper.preconfigure_instance(self._session, instance, vdi_ref) @@ -493,7 +492,6 @@ class VMOps(object): 'ips': [ip_dict(ip) for ip in network_IPs]} self.write_to_param_xenstore(vm_opaque_ref, {location: mapping}) try: - logging.debug("About to run write_to_xenstore") self.write_to_xenstore(vm_opaque_ref, location, mapping['location']) except KeyError: -- cgit From 97563d650a08e7f2d1aa1f08237219291d821e39 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Wed, 2 Mar 2011 10:43:45 +0000 Subject: Changed _get_vm_opaqueref removing test-specific code paths. --- nova/virt/xenapi/vmops.py | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index ec620f918..450a06315 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -51,6 +51,7 @@ class VMOps(object): def __init__(self, session): self.XenAPI = session.get_imported_xenapi() self._session = session + self.known_vm_refs = [] VMHelper.XenAPI = self.XenAPI def list_instances(self): @@ -176,29 +177,16 @@ class VMOps(object): a vm name or a vm instance, and want a vm instance in return. """ vm = None - try: - if instance_or_vm.startswith("OpaqueRef:"): - # Got passed an opaque ref; return it + if instance_or_vm in self.known_vm_refs: return instance_or_vm - else: - # Must be the instance name - instance_name = instance_or_vm - except (AttributeError, KeyError): - # - # Note the the KeyError will only happen with fakes.py - # Not a string; must be an ID or a vm instance - if isinstance(instance_or_vm, (int, long)): - ctx = context.get_admin_context() - instance_obj = db.instance_get(ctx, instance_or_vm) - instance_name = instance_obj.name - else: - instance_name = instance_or_vm.name - #fake xenapi does not use OpaqueRef as a prefix - #when running tests we will always end up here + instance_name = instance_or_vm + #if instance_or_vm is not a string; + #must be an ID or a vm instance + if not isinstance(instance_or_vm, str): + instance_name = instance_or_vm.name vm = VMHelper.lookup(self._session, instance_name) + self.known_vm_refs.append(vm) if vm is None: - if FLAGS.xenapi_connection_url == 'test_url': - return instance_or_vm raise exception.NotFound( _('Instance not present %s') % instance_name) return vm @@ -482,7 +470,7 @@ class VMOps(object): mac_id = instance.mac_address.replace(':', '') location = 'vm-data/networking/%s' % mac_id - #TODO(salvatore-orlando): key for broadcast address + #salvatore-orlando: key for broadcast address #provisionally set to 'broadcast' mapping = {'label': network['label'], 'gateway': network['gateway'], -- cgit From e7626da8ade4a3d29d441fed1c21c50cbc9928de Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Tue, 8 Mar 2011 23:33:17 +0000 Subject: using get_uuid in place of get_record in _get_vm_opaqueref changed SessionBase._getter in fake xenapi in order to return HANDLE_INVALID failure when reference is not in DB (was NotImplementedException) --- nova/virt/xenapi/fake.py | 11 ++++++----- nova/virt/xenapi/vmops.py | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index 09e381e2c..eecfa20b1 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -400,7 +400,6 @@ class SessionBase(object): def _getter(self, name, params): self._check_session(params) (cls, func) = name.split('.') - if func == 'get_all': self._check_arg_count(params, 1) return get_all(cls) @@ -423,10 +422,12 @@ class SessionBase(object): if len(params) == 2: field = func[len('get_'):] ref = params[1] - - if (ref in _db_content[cls] and - field in _db_content[cls][ref]): - return _db_content[cls][ref][field] + if (ref in _db_content[cls]): + if (field in _db_content[cls][ref]): + return _db_content[cls][ref][field] + else: + LOG.debug(_('Raising Failure')) + raise Failure(['HANDLE_INVALID', cls, ref]) LOG.debug(_('Raising NotImplemented')) raise NotImplementedError( diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index ddd5b0e16..273a7ba38 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -198,7 +198,7 @@ class VMOps(object): obj = None try: # check for opaque ref - obj = self._session.get_xenapi().VM.get_record(instance_or_vm) + obj = self._session.get_xenapi().VM.get_uuid(instance_or_vm) return instance_or_vm except self.XenAPI.Failure: # wasn't an opaque ref, can be an instance name -- cgit From 4f2d48eeffc79333afae829ea31f7eae0549b40a Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Sun, 13 Mar 2011 23:35:30 +0000 Subject: Removed excess LOG.debug line --- nova/tests/test_xenapi.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 85d804ebd..def37b377 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -268,7 +268,6 @@ class XenAPIVMTestCase(test.TestCase): mem_kib = long(instance_type['memory_mb']) << 10 mem_bytes = str(mem_kib << 10) vcpus = instance_type['vcpus'] - LOG.debug("VM:%s", self.vm) self.assertEquals(self.vm_info['max_mem'], mem_kib) self.assertEquals(self.vm_info['mem'], mem_kib) self.assertEquals(self.vm['memory_static_max'], mem_bytes) -- cgit From 77a48cdd8a22cc84ed67a6b3d1c3793dd93e44a8 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 16 Mar 2011 16:15:56 -0400 Subject: expanding osapi flavors tests; rewriting flavors resource with view builders; adding 1.1 specific links to flavors resources --- nova/api/openstack/flavors.py | 48 +++++++------- nova/api/openstack/views/flavors.py | 45 ++++++++++++- nova/db/sqlalchemy/api.py | 2 +- nova/tests/api/openstack/test_flavors.py | 107 +++++++++++++++++++++++++++++-- 4 files changed, 167 insertions(+), 35 deletions(-) diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index c99b945fb..bc61e8d1a 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -15,16 +15,12 @@ # License for the specific language governing permissions and limitations # under the License. -from webob import exc +import webob from nova import db -from nova import context -from nova.api.openstack import faults -from nova.api.openstack import common -from nova.compute import instance_types -from nova.api.openstack.views import flavors as flavors_views +from nova import exception from nova import wsgi -import nova.api.openstack +from nova.api.openstack.views import flavors as flavors_views class Controller(wsgi.Controller): @@ -37,29 +33,31 @@ class Controller(wsgi.Controller): def index(self, req): """Return all flavors in brief.""" - return dict(flavors=[dict(id=flavor['id'], name=flavor['name']) - for flavor in self.detail(req)['flavors']]) + items = self._get_flavors(req, False) + return dict(flavors=items) def detail(self, req): """Return all flavors in detail.""" - items = [self.show(req, id)['flavor'] for id in self._all_ids(req)] + items = self._get_flavors(req, True) return dict(flavors=items) + def _get_flavors(self, req, is_detail): + """Helper function that returns a list of flavor dicts.""" + ctxt = req.environ['nova.context'] + flavors = db.api.instance_type_get_all(ctxt) + builder = flavors_views.get_view_builder(req) + items = [builder.build(flavor, is_detail=is_detail) \ + for flavor in flavors.values()] + return items + def show(self, req, id): """Return data about the given flavor id.""" - ctxt = req.environ['nova.context'] - flavor = db.api.instance_type_get_by_flavor_id(ctxt, id) - values = { - "id": flavor["flavorid"], - "name": flavor["name"], - "ram": flavor["memory_mb"], - "disk": flavor["local_gb"], - } + try: + ctxt = req.environ['nova.context'] + flavor = db.api.instance_type_get_by_flavor_id(ctxt, id) + except exception.NotFound: + return webob.exc.HTTPNotFound() + + builder = flavors_views.get_view_builder(req) + values = builder.build(flavor, is_detail=True) return dict(flavor=values) - - def _all_ids(self, req): - """Return the list of all flavorids.""" - ctxt = req.environ['nova.context'] - inst_types = db.api.instance_type_get_all(ctxt) - flavor_ids = [inst_types[i]['flavorid'] for i in inst_types.keys()] - return sorted(flavor_ids) diff --git a/nova/api/openstack/views/flavors.py b/nova/api/openstack/views/flavors.py index dd2e75a7a..19ac8f114 100644 --- a/nova/api/openstack/views/flavors.py +++ b/nova/api/openstack/views/flavors.py @@ -35,14 +35,55 @@ class ViewBuilder(object): def __init__(self): pass - def build(self, flavor_obj): - raise NotImplementedError() + def build(self, flavor_obj, is_detail=False): + if is_detail: + flavor = self._build_detail(flavor_obj) + else: + flavor = self._build_simple(flavor_obj) + + full_flavor = self._build_extra(flavor) + + return full_flavor + + def _build_simple(self, flavor_obj): + return { + "id": flavor_obj["flavorid"], + "name": flavor_obj["name"], + } + + def _build_detail(self, flavor_obj): + simple = self._build_simple(flavor_obj) + + detail = { + "ram": flavor_obj["memory_mb"], + "disk": flavor_obj["local_gb"], + } + + detail.update(simple) + + return detail + + def _build_extra(self, flavor_obj): + return flavor_obj class ViewBuilder_1_1(ViewBuilder): def __init__(self, base_url): self.base_url = base_url + def _build_extra(self, flavor_obj): + flavor_obj["links"] = self._build_links(flavor_obj) + return flavor_obj + + def _build_links(self, flavor_obj): + links = [ + { + "rel": "self", + "href": self.generate_href(flavor_obj["id"]), + }, + ] + return links + def generate_href(self, flavor_id): return "%s/flavors/%s" % (self.base_url, flavor_id) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 56998ce05..6789ac22a 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -2356,7 +2356,7 @@ def instance_type_get_by_flavor_id(context, id): filter_by(flavorid=int(id)).\ first() if not inst_type: - raise exception.NotFound(_("No flavor with name %s") % id) + raise exception.NotFound(_("No flavor with flavorid %s") % id) else: return dict(inst_type) diff --git a/nova/tests/api/openstack/test_flavors.py b/nova/tests/api/openstack/test_flavors.py index 4f504808c..197e907c4 100644 --- a/nova/tests/api/openstack/test_flavors.py +++ b/nova/tests/api/openstack/test_flavors.py @@ -19,11 +19,10 @@ import json import stubout import webob -from nova import test -import nova.api +import nova.db.api from nova import context -from nova.api.openstack import flavors -from nova import db +from nova import exception +from nova import test from nova.tests.api.openstack import fakes @@ -47,6 +46,9 @@ def return_instance_types(context, num=2): instance_types[name] = stub_flavor(i, name) return instance_types +def return_instance_type_not_found(context, flavorid): + raise exception.NotFound() + class FlavorsTest(test.TestCase): def setUp(self): @@ -67,7 +69,7 @@ class FlavorsTest(test.TestCase): self.stubs.UnsetAll() super(FlavorsTest, self).tearDown() - def test_get_flavor_list(self): + def test_get_flavor_list_v1_0(self): req = webob.Request.blank('/v1.0/flavors') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) @@ -84,7 +86,7 @@ class FlavorsTest(test.TestCase): ] self.assertEqual(flavors, expected) - def test_get_flavor_list_detail(self): + def test_get_flavor_list_detail_v1_0(self): req = webob.Request.blank('/v1.0/flavors/detail') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) @@ -105,7 +107,7 @@ class FlavorsTest(test.TestCase): ] self.assertEqual(flavors, expected) - def test_get_flavor_by_id(self): + def test_get_flavor_by_id_v1_0(self): req = webob.Request.blank('/v1.0/flavors/12') res = req.get_response(fakes.wsgi_app()) self.assertEqual(res.status_int, 200) @@ -117,3 +119,94 @@ class FlavorsTest(test.TestCase): "disk": "10", } self.assertEqual(flavor, expected) + + def test_get_flavor_by_invalid_id(self): + self.stubs.Set(nova.db.api, "instance_type_get_by_flavor_id", + return_instance_type_not_found) + req = webob.Request.blank('/v1.0/flavors/asdf') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 404) + + def test_get_flavor_by_id_v1_1(self): + req = webob.Request.blank('/v1.1/flavors/12') + req.environ['api.version'] = '1.1' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + flavor = json.loads(res.body)["flavor"] + expected = { + "id": "12", + "name": "flavor 12", + "ram": "256", + "disk": "10", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/flavors/12", + }, + ], + } + self.assertEqual(flavor, expected) + + def test_get_flavor_list_v1_1(self): + req = webob.Request.blank('/v1.1/flavors') + req.environ['api.version'] = '1.1' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + flavor = json.loads(res.body)["flavors"] + expected = [ + { + "id": "1", + "name": "flavor 1", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/flavors/1", + }, + ], + }, + { + "id": "2", + "name": "flavor 2", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/flavors/2", + }, + ], + }, + ] + self.assertEqual(flavor, expected) + + def test_get_flavor_list_detail_v1_1(self): + req = webob.Request.blank('/v1.1/flavors/detail') + req.environ['api.version'] = '1.1' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + flavor = json.loads(res.body)["flavors"] + expected = [ + { + "id": "1", + "name": "flavor 1", + "ram": "256", + "disk": "10", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/flavors/1", + }, + ], + }, + { + "id": "2", + "name": "flavor 2", + "ram": "256", + "disk": "10", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1/flavors/2", + }, + ], + }, + ] + self.assertEqual(flavor, expected) -- cgit From bb606c7ba42fc567f2e9989e0f560783743e5ddd Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 16 Mar 2011 16:58:38 -0400 Subject: adding bookmarks links to 1.1 flavor entities --- nova/api/openstack/views/flavors.py | 13 ++++++++- nova/tests/api/openstack/test_flavors.py | 50 ++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/nova/api/openstack/views/flavors.py b/nova/api/openstack/views/flavors.py index 19ac8f114..be7e68763 100644 --- a/nova/api/openstack/views/flavors.py +++ b/nova/api/openstack/views/flavors.py @@ -76,10 +76,21 @@ class ViewBuilder_1_1(ViewBuilder): return flavor_obj def _build_links(self, flavor_obj): + href = self.generate_href(flavor_obj["id"]) links = [ { "rel": "self", - "href": self.generate_href(flavor_obj["id"]), + "href": href, + }, + { + "rel": "bookmark", + "type": "application/json", + "href": href, + }, + { + "rel": "bookmark", + "type": "application/xml", + "href": href, }, ] return links diff --git a/nova/tests/api/openstack/test_flavors.py b/nova/tests/api/openstack/test_flavors.py index 197e907c4..8dfcfe293 100644 --- a/nova/tests/api/openstack/test_flavors.py +++ b/nova/tests/api/openstack/test_flavors.py @@ -143,6 +143,16 @@ class FlavorsTest(test.TestCase): "rel": "self", "href": "http://localhost/v1.1/flavors/12", }, + { + "rel": "bookmark", + "type": "application/json", + "href": "http://localhost/v1.1/flavors/12", + }, + { + "rel": "bookmark", + "type": "application/xml", + "href": "http://localhost/v1.1/flavors/12", + }, ], } self.assertEqual(flavor, expected) @@ -162,6 +172,16 @@ class FlavorsTest(test.TestCase): "rel": "self", "href": "http://localhost/v1.1/flavors/1", }, + { + "rel": "bookmark", + "type": "application/json", + "href": "http://localhost/v1.1/flavors/1", + }, + { + "rel": "bookmark", + "type": "application/xml", + "href": "http://localhost/v1.1/flavors/1", + }, ], }, { @@ -172,6 +192,16 @@ class FlavorsTest(test.TestCase): "rel": "self", "href": "http://localhost/v1.1/flavors/2", }, + { + "rel": "bookmark", + "type": "application/json", + "href": "http://localhost/v1.1/flavors/2", + }, + { + "rel": "bookmark", + "type": "application/xml", + "href": "http://localhost/v1.1/flavors/2", + }, ], }, ] @@ -194,6 +224,16 @@ class FlavorsTest(test.TestCase): "rel": "self", "href": "http://localhost/v1.1/flavors/1", }, + { + "rel": "bookmark", + "type": "application/json", + "href": "http://localhost/v1.1/flavors/1", + }, + { + "rel": "bookmark", + "type": "application/xml", + "href": "http://localhost/v1.1/flavors/1", + }, ], }, { @@ -206,6 +246,16 @@ class FlavorsTest(test.TestCase): "rel": "self", "href": "http://localhost/v1.1/flavors/2", }, + { + "rel": "bookmark", + "type": "application/json", + "href": "http://localhost/v1.1/flavors/2", + }, + { + "rel": "bookmark", + "type": "application/xml", + "href": "http://localhost/v1.1/flavors/2", + }, ], }, ] -- cgit From 05ccc91bdb3ad47ffecee29d21835ded17f65816 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 16 Mar 2011 17:24:32 -0400 Subject: pep8 --- nova/api/openstack/views/flavors.py | 4 ++-- nova/tests/api/openstack/test_flavors.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/nova/api/openstack/views/flavors.py b/nova/api/openstack/views/flavors.py index be7e68763..7d75c0aa2 100644 --- a/nova/api/openstack/views/flavors.py +++ b/nova/api/openstack/views/flavors.py @@ -61,8 +61,8 @@ class ViewBuilder(object): detail.update(simple) - return detail - + return detail + def _build_extra(self, flavor_obj): return flavor_obj diff --git a/nova/tests/api/openstack/test_flavors.py b/nova/tests/api/openstack/test_flavors.py index 8dfcfe293..954d72adf 100644 --- a/nova/tests/api/openstack/test_flavors.py +++ b/nova/tests/api/openstack/test_flavors.py @@ -46,6 +46,7 @@ def return_instance_types(context, num=2): instance_types[name] = stub_flavor(i, name) return instance_types + def return_instance_type_not_found(context, flavorid): raise exception.NotFound() @@ -205,7 +206,7 @@ class FlavorsTest(test.TestCase): ], }, ] - self.assertEqual(flavor, expected) + self.assertEqual(flavor, expected) def test_get_flavor_list_detail_v1_1(self): req = webob.Request.blank('/v1.1/flavors/detail') -- cgit From c1f7df80d22b718bc96332c2f52354627d11700d Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 17 Mar 2011 12:31:16 -0400 Subject: adding comments; removing returns from build_extra; removing unnecessary backslash --- nova/api/openstack/flavors.py | 2 +- nova/api/openstack/views/flavors.py | 26 ++++++++++++++++++++------ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index bc61e8d1a..6eba0f9b8 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -46,7 +46,7 @@ class Controller(wsgi.Controller): ctxt = req.environ['nova.context'] flavors = db.api.instance_type_get_all(ctxt) builder = flavors_views.get_view_builder(req) - items = [builder.build(flavor, is_detail=is_detail) \ + items = [builder.build(flavor, is_detail=is_detail) for flavor in flavors.values()] return items diff --git a/nova/api/openstack/views/flavors.py b/nova/api/openstack/views/flavors.py index 7d75c0aa2..92003a19f 100644 --- a/nova/api/openstack/views/flavors.py +++ b/nova/api/openstack/views/flavors.py @@ -32,26 +32,27 @@ def get_view_builder(req): class ViewBuilder(object): - def __init__(self): - pass def build(self, flavor_obj, is_detail=False): + """Generic method used to generate a flavor entity.""" if is_detail: flavor = self._build_detail(flavor_obj) else: flavor = self._build_simple(flavor_obj) - full_flavor = self._build_extra(flavor) + self._build_extra(flavor) - return full_flavor + return flavor def _build_simple(self, flavor_obj): + """Build a minimal representation of a flavor.""" return { "id": flavor_obj["flavorid"], "name": flavor_obj["name"], } def _build_detail(self, flavor_obj): + """Build a more complete representation of a flavor.""" simple = self._build_simple(flavor_obj) detail = { @@ -64,19 +65,26 @@ class ViewBuilder(object): return detail def _build_extra(self, flavor_obj): - return flavor_obj + """Hook for version-specific changes to newly created flavor object.""" + pass class ViewBuilder_1_1(ViewBuilder): + """Openstack API v1.1 flavors view builder.""" + def __init__(self, base_url): + """ + :param base_url: url of the root wsgi application + """ self.base_url = base_url def _build_extra(self, flavor_obj): flavor_obj["links"] = self._build_links(flavor_obj) - return flavor_obj def _build_links(self, flavor_obj): + """Generate a container of links that refer to the provided flavor.""" href = self.generate_href(flavor_obj["id"]) + links = [ { "rel": "self", @@ -93,11 +101,17 @@ class ViewBuilder_1_1(ViewBuilder): "href": href, }, ] + return links def generate_href(self, flavor_id): + """Create an url that refers to a specific flavor id.""" return "%s/flavors/%s" % (self.base_url, flavor_id) class ViewBuilder_1_0(ViewBuilder): + """ + Openstack API v1.0 flavors view builder. Currently, there + are no 1.0-specific attributes of a flavor. + """ pass -- cgit From 99a3899291ef14149bee0581ad7615e07dfc55c1 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 17 Mar 2011 14:07:25 -0400 Subject: adding serialization_metadata to encode links on flavors --- nova/api/openstack/flavors.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index 6eba0f9b8..f0936332c 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -29,7 +29,11 @@ class Controller(wsgi.Controller): _serialization_metadata = { 'application/xml': { "attributes": { - "flavor": ["id", "name", "ram", "disk"]}}} + "flavor": ["id", "name", "ram", "disk"], + "link": ["rel","type","href"], + } + } + } def index(self, req): """Return all flavors in brief.""" -- cgit From 5a141466db962e184ced0a57efb6bfe94ff5a246 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 17 Mar 2011 14:09:44 -0400 Subject: pep8 --- nova/api/openstack/flavors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index f0936332c..e9ee9d012 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -30,7 +30,7 @@ class Controller(wsgi.Controller): 'application/xml': { "attributes": { "flavor": ["id", "name", "ram", "disk"], - "link": ["rel","type","href"], + "link": ["rel", "type", "href"], } } } -- cgit From 35bd58bd9dc6441f5620b262d1f65b852f56c67c Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 17 Mar 2011 15:43:56 -0400 Subject: moving Versions app out of __init__.py into its own module; adding openstack versions tests; adding links to version entities --- etc/api-paste.ini | 2 +- nova/api/openstack/__init__.py | 18 --------- nova/api/openstack/versions.py | 55 ++++++++++++++++++++++++++++ nova/api/openstack/views/versions.py | 57 +++++++++++++++++++++++++++++ nova/tests/api/openstack/fakes.py | 3 +- nova/tests/api/openstack/test_versions.py | 61 +++++++++++++++++++++++++++++++ 6 files changed, 176 insertions(+), 20 deletions(-) create mode 100644 nova/api/openstack/versions.py create mode 100644 nova/api/openstack/views/versions.py create mode 100644 nova/tests/api/openstack/test_versions.py diff --git a/etc/api-paste.ini b/etc/api-paste.ini index a4483d3f8..04ab09753 100644 --- a/etc/api-paste.ini +++ b/etc/api-paste.ini @@ -89,4 +89,4 @@ paste.app_factory = nova.api.openstack:APIRouter.factory pipeline = faultwrap osversionapp [app:osversionapp] -paste.app_factory = nova.api.openstack:Versions.factory +paste.app_factory = nova.api.openstack.versions:Versions.factory diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 0244bc93c..eb0402962 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -121,21 +121,3 @@ class APIRouter(wsgi.Router): controller=shared_ip_groups.Controller()) super(APIRouter, self).__init__(mapper) - - -class Versions(wsgi.Application): - @webob.dec.wsgify(RequestClass=wsgi.Request) - def __call__(self, req): - """Respond to a request for all OpenStack API versions.""" - response = { - "versions": [ - dict(status="DEPRECATED", id="v1.0"), - dict(status="CURRENT", id="v1.1"), - ], - } - metadata = { - "application/xml": { - "attributes": dict(version=["status", "id"])}} - - content_type = req.best_match_content_type() - return wsgi.Serializer(metadata).serialize(response, content_type) diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py new file mode 100644 index 000000000..f0f2c484c --- /dev/null +++ b/nova/api/openstack/versions.py @@ -0,0 +1,55 @@ +# 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. + +import webob.dec +import webob.exc + +from nova import wsgi +import nova.api.openstack.views.versions + + +class Versions(wsgi.Application): + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + """Respond to a request for all OpenStack API versions.""" + version_objs = [ + { + "id": "v1.1", + "status": "CURRENT", + }, + { + "id": "v1.0", + "status": "DEPRECATED", + }, + ] + + builder = nova.api.openstack.views.versions.get_view_builder(req) + versions = [builder.build(version) for version in version_objs] + response = dict(versions=versions) + + metadata = { + "application/xml": { + "attributes": { + "version": ["status", "id"], + "link": ["rel", "href"], + } + } + } + + content_type = req.best_match_content_type() + return wsgi.Serializer(metadata).serialize(response, content_type) diff --git a/nova/api/openstack/views/versions.py b/nova/api/openstack/views/versions.py new file mode 100644 index 000000000..555d58d5c --- /dev/null +++ b/nova/api/openstack/views/versions.py @@ -0,0 +1,57 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010-2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +def get_view_builder(req): + base_url = req.application_url + return ViewBuilder(base_url) + + +class ViewBuilder(object): + + def __init__(self, base_url): + """ + :param base_url: url of the root wsgi application + """ + self.base_url = base_url + + def build(self, version_data): + """Generic method used to generate a version entity.""" + version = { + "id": version_data["id"], + "status": version_data["status"], + "links": self._build_links(version_data), + } + + return version + + def _build_links(self, version_data): + """Generate a container of links that refer to the provided version.""" + href = self.generate_href(version_data["id"]) + + links = [ + { + "rel": "self", + "href": href, + }, + ] + + return links + + def generate_href(self, version_number): + """Create an url that refers to a specific version_number.""" + return "%s/%s" % (self.base_url, version_number) diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index a08fe385a..3b7e558b6 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -35,6 +35,7 @@ import nova.api.openstack.auth from nova.api import openstack from nova.api.openstack import auth from nova.api.openstack import ratelimiting +from nova.api.openstack import versions from nova.auth.manager import User, Project from nova.image import glance from nova.image import local @@ -80,7 +81,7 @@ def wsgi_app(inner_application=None): ratelimiting.RateLimitingMiddleware(inner_application))) mapper['/v1.0'] = api mapper['/v1.1'] = api - mapper['/'] = openstack.FaultWrapper(openstack.Versions()) + mapper['/'] = openstack.FaultWrapper(versions.Versions()) return mapper diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py new file mode 100644 index 000000000..330d74dde --- /dev/null +++ b/nova/tests/api/openstack/test_versions.py @@ -0,0 +1,61 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 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. + +import json +import webob + +from nova import context +from nova import test +from nova.tests.api.openstack import fakes + + +class VersionsTest(test.TestCase): + def setUp(self): + super(VersionsTest, self).setUp() + self.context = context.get_admin_context() + + def tearDown(self): + super(VersionsTest, self).tearDown() + + def test_get_version_list(self): + req = webob.Request.blank('/') + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 200) + versions = json.loads(res.body)["versions"] + expected = [ + { + "id": "v1.1", + "status": "CURRENT", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.1", + } + ], + }, + { + "id": "v1.0", + "status": "DEPRECATED", + "links": [ + { + "rel": "self", + "href": "http://localhost/v1.0", + } + ], + }, + ] + self.assertEqual(versions, expected) -- cgit From 47bec2abb39f76d5b3ea634dbb7012d55d7f99ce Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 17 Mar 2011 23:07:40 -0400 Subject: adding servers container to openstack api v1.1 servers entities --- nova/api/openstack/servers.py | 11 ++++-- nova/api/openstack/views/servers.py | 56 +++++++++++++++++++++++++----- nova/tests/api/openstack/test_servers.py | 58 ++++++++++++++++++++++++++++++-- 3 files changed, 111 insertions(+), 14 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index e3141934b..de35aca8d 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -47,11 +47,15 @@ class Controller(wsgi.Controller): """ The Server API controller for the OpenStack API """ _serialization_metadata = { - 'application/xml': { + "application/xml": { "attributes": { "server": ["id", "imageId", "name", "flavorId", "hostId", "status", "progress", "adminPass", "flavorRef", - "imageRef"]}}} + "imageRef"], + "link": ["rel", "type", "href"], + }, + }, + } def __init__(self): self.compute_api = compute.API() @@ -513,6 +517,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'] @@ -546,7 +551,7 @@ class ControllerV11(Controller): base_url) addresses_builder = nova.api.openstack.views.addresses.ViewBuilderV11() return nova.api.openstack.views.servers.ViewBuilderV11( - addresses_builder, flavor_builder, image_builder) + addresses_builder, flavor_builder, image_builder, base_url) def _get_addresses_view_builder(self, req): return nova.api.openstack.views.addresses.ViewBuilderV11(req) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 8d47ac757..477f3caa0 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -39,12 +39,16 @@ class ViewBuilder(object): Rackspace-like attributes for return """ if is_detail: - return self._build_detail(inst) + server = self._build_detail(inst) else: - return self._build_simple(inst) + server = self._build_simple(inst) + + self._build_extra(server, inst) + + return dict(server=server) def _build_simple(self, inst): - return dict(server=dict(id=inst['id'], name=inst['display_name'])) + return dict(id=inst['id'], name=inst['display_name']) def _build_detail(self, inst): power_mapping = { @@ -79,7 +83,7 @@ class ViewBuilder(object): self._build_image(inst_dict, inst) self._build_flavor(inst_dict, inst) - return dict(server=inst_dict) + return inst_dict def _build_image(self, response, inst): raise NotImplementedError() @@ -87,6 +91,9 @@ class ViewBuilder(object): def _build_flavor(self, response, inst): raise NotImplementedError() + def _build_extra(self, response, inst): + pass + class ViewBuilderV10(ViewBuilder): def _build_image(self, response, inst): @@ -99,19 +106,50 @@ class ViewBuilderV10(ViewBuilder): class ViewBuilderV11(ViewBuilder): - def __init__(self, addresses_builder, flavor_builder, image_builder): + def __init__(self, addresses_builder, flavor_builder, image_builder, + base_url): ViewBuilder.__init__(self, addresses_builder) self.flavor_builder = flavor_builder self.image_builder = image_builder + self.base_url = base_url def _build_image(self, response, inst): - if inst.get('image_id') == None: + image_id = inst.get("image_id", None) + if image_id == None: return - image_id = inst["image_id"] response["imageRef"] = self.image_builder.generate_href(image_id) def _build_flavor(self, response, inst): - if inst.get('instance_type') == None: + flavor_id = inst.get("instance_type", None) + if flavor_id == None: return - flavor_id = inst["instance_type"] response["flavorRef"] = self.flavor_builder.generate_href(flavor_id) + + def _build_extra(self, response, inst): + self._build_links(response, inst) + + def _build_links(self, response, inst): + href = self.generate_href(inst["id"]) + + links = [ + { + "rel": "self", + "href": href, + }, + { + "rel": "bookmark", + "type": "application/json", + "href": href, + }, + { + "rel": "bookmark", + "type": "application/xml", + "href": href, + }, + ] + + response["links"] = links + + def generate_href(self, server_id): + """Create an url that refers to a specific server id.""" + return "%s/servers/%s" % (self.base_url, server_id) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 6e78db9da..17689d405 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -164,6 +164,32 @@ class ServersTest(test.TestCase): self.assertEqual(res_dict['server']['id'], 1) self.assertEqual(res_dict['server']['name'], 'server1') + def test_get_server_by_id_v11(self): + req = webob.Request.blank('/v1.1/servers/1') + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + self.assertEqual(res_dict['server']['id'], 1) + self.assertEqual(res_dict['server']['name'], 'server1') + + expected_links = [ + { + "rel": "self", + "href": "http://localhost/v1.1/servers/1", + }, + { + "rel": "bookmark", + "type": "application/json", + "href": "http://localhost/v1.1/servers/1", + }, + { + "rel": "bookmark", + "type": "application/xml", + "href": "http://localhost/v1.1/servers/1", + }, + ] + + self.assertEqual(res_dict['server']['links'], expected_links) + def test_get_server_by_id_with_addresses(self): private = "192.168.0.3" public = ["1.2.3.4"] @@ -186,7 +212,6 @@ class ServersTest(test.TestCase): 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.1/servers/1') - req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) self.assertEqual(res_dict['server']['id'], 1) @@ -211,6 +236,36 @@ class ServersTest(test.TestCase): self.assertEqual(s.get('imageId', None), None) i += 1 + def test_get_server_list_v11(self): + req = webob.Request.blank('/v1.1/servers') + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + + for i,s in enumerate(res_dict['servers']): + self.assertEqual(s['id'], i) + self.assertEqual(s['name'], 'server%d' % i) + self.assertEqual(s.get('imageId', None), None) + + expected_links = [ + { + "rel": "self", + "href": "http://localhost/v1.1/servers/%d" % (i,), + }, + { + "rel": "bookmark", + "type": "application/json", + "href": "http://localhost/v1.1/servers/%d" % (i,), + }, + { + "rel": "bookmark", + "type": "application/xml", + "href": "http://localhost/v1.1/servers/%d" % (i,), + }, + ] + + self.assertEqual(s['links'], expected_links) + + def test_get_servers_with_limit(self): req = webob.Request.blank('/v1.0/servers?limit=3') res = req.get_response(fakes.wsgi_app()) @@ -419,7 +474,6 @@ class ServersTest(test.TestCase): def test_get_all_server_details_v1_1(self): req = webob.Request.blank('/v1.1/servers/detail') - req.environ['api.version'] = '1.1' res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) -- cgit From 0845d7081bc912e7eefa2d98e8c53033d872ef5e Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 17 Mar 2011 23:58:23 -0400 Subject: pep8 --- nova/api/openstack/views/servers.py | 2 +- nova/tests/api/openstack/test_servers.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 477f3caa0..5f4c0f740 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -44,7 +44,7 @@ class ViewBuilder(object): server = self._build_simple(inst) self._build_extra(server, inst) - + return dict(server=server) def _build_simple(self, inst): diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 17689d405..9be68eb8d 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -241,7 +241,7 @@ class ServersTest(test.TestCase): res = req.get_response(fakes.wsgi_app()) res_dict = json.loads(res.body) - for i,s in enumerate(res_dict['servers']): + for i, s in enumerate(res_dict['servers']): self.assertEqual(s['id'], i) self.assertEqual(s['name'], 'server%d' % i) self.assertEqual(s.get('imageId', None), None) @@ -265,7 +265,6 @@ class ServersTest(test.TestCase): self.assertEqual(s['links'], expected_links) - def test_get_servers_with_limit(self): req = webob.Request.blank('/v1.0/servers?limit=3') res = req.get_response(fakes.wsgi_app()) -- cgit From 1abcdbea89e69013c193d2eb0b4b7a0bc2e2fa58 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Fri, 18 Mar 2011 02:14:36 -0400 Subject: Implement metadata resource for Openstack API v1.1. Includes: -GET /servers/id/meta -POST /servers/id/meta -GET /servers/id/meta/key -PUT /servers/id/meta/key -DELETE /servers/id/meta/key --- nova/api/openstack/__init__.py | 5 + nova/api/openstack/server_metadata.py | 75 ++++++++++++ nova/compute/api.py | 15 +++ nova/db/api.py | 18 +++ nova/db/sqlalchemy/api.py | 60 +++++++++ nova/tests/api/openstack/test_server_metadata.py | 150 +++++++++++++++++++++++ 6 files changed, 323 insertions(+) create mode 100644 nova/api/openstack/server_metadata.py create mode 100644 nova/tests/api/openstack/test_server_metadata.py diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 0b50d17d0..4beb84dcd 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 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 zones @@ -138,6 +139,10 @@ class APIRouterV11(APIRouter): controller=servers.ControllerV11(), collection={'detail': 'GET'}, member=self.server_members) + mapper.resource("server_meta", "meta", + controller=server_metadata.Controller(), + parent_resource=dict(member_name='server', + collection_name='servers')) class Versions(wsgi.Application): diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py new file mode 100644 index 000000000..1408f59a6 --- /dev/null +++ b/nova/api/openstack/server_metadata.py @@ -0,0 +1,75 @@ +# 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. + +from webob import exc + +from nova import compute +from nova import wsgi +from nova.api.openstack import faults + + +class Controller(wsgi.Controller): + """ The server metadata API controller for the Openstack API """ + + def __init__(self): + self.compute_api = compute.API() + super(Controller, self).__init__() + + def _get_metadata(self, context, server_id): + metadata = self.compute_api.get_instance_metadata(context, server_id) + meta_dict = {} + for key, value in metadata.iteritems(): + meta_dict[key] = value + return dict(metadata=meta_dict) + + def index(self, req, server_id): + """ Returns the list of metadata for a given instance """ + context = req.environ['nova.context'] + return self._get_metadata(context, server_id) + + 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']) + return req.body + + def update(self, req, server_id, id): + context = req.environ['nova.context'] + body = self._deserialize(req.body, req.get_content_type()) + if not id in body: + expl = _('Request body and URI mismatch') + raise exc.HTTPBadRequest(explanation=expl) + self.compute_api.update_or_create_instance_metadata(context, + server_id, + body) + return req.body + + def show(self, req, server_id, id): + """ Return a single metadata item """ + context = req.environ['nova.context'] + data = self._get_metadata(context, server_id) + if id in data['metadata']: + return {id: data['metadata'][id]} + else: + return faults.Fault(exc.HTTPNotFound()) + + def delete(self, req, server_id, id): + """ Deletes an existing metadata """ + context = req.environ['nova.context'] + self.compute_api.delete_instance_metadata(context, server_id, id) diff --git a/nova/compute/api.py b/nova/compute/api.py index 32577af82..e70817212 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -612,3 +612,18 @@ class API(base.Base): instance = self.get(context, instance_id) self.network_api.associate_floating_ip(context, address, instance['fixed_ip']) + + def get_instance_metadata(self, context, instance_id): + """Get all metadata associated with an instance.""" + rv = self.db.get_instance_metadata(context, instance_id) + return dict(rv.iteritems()) + + def delete_instance_metadata(self, context, instance_id, key): + """Delete the given metadata item""" + self.db.delete_instance_metadata(context, instance_id, key) + + def update_or_create_instance_metadata(self, context, instance_id, + metadata): + """Updates or creates instance metadata""" + self.db.update_or_create_instance_metadata(context, instance_id, + metadata) diff --git a/nova/db/api.py b/nova/db/api.py index 3cb0e5811..5721fe8d6 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -1171,3 +1171,21 @@ def zone_get(context, zone_id): def zone_get_all(context): """Get all child Zones.""" return IMPL.zone_get_all(context) + + +#################### + + +def get_instance_metadata(context, instance_id): + """Get all metadata for an instance""" + return IMPL.get_instance_metadata(context, instance_id) + + +def delete_instance_metadata(context, instance_id, key): + """Delete the given metadata item""" + IMPL.delete_instance_metadata(context, instance_id, key) + + +def update_or_create_instance_metadata(context, instance_id, metadata): + """Creates or updates instance metadata""" + IMPL.update_or_create_instance_metadata(context, instance_id, metadata) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 9d9b86c1d..8f656de0e 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -2457,3 +2457,63 @@ def zone_get(context, zone_id): def zone_get_all(context): session = get_session() return session.query(models.Zone).all() + + +#################### + +@require_context +def get_instance_metadata(context, instance_id): + session = get_session() + + meta_results = session.query(models.InstanceMetadata).\ + filter_by(instance_id=instance_id).\ + filter_by(deleted=False).\ + all() + + meta_dict = {} + for i in meta_results: + meta_dict[i['key']] = i['value'] + return meta_dict + + +@require_context +def delete_instance_metadata(context, instance_id, key): + session = get_session() + session.query(models.InstanceMetadata).\ + filter_by(instance_id=instance_id).\ + filter_by(key=key).\ + update({'deleted': 1, + 'deleted_at': datetime.datetime.utcnow(), + 'updated_at': literal_column('updated_at')}) + + +@require_context +def get_instance_metadata_item(context, instance_id, key): + session = get_session() + + meta_result = session.query(models.InstanceMetadata).\ + filter_by(instance_id=instance_id).\ + filter_by(key=key).\ + filter_by(deleted=False).\ + first() + + if not meta_result: + raise exception.NotFound(_('Invalid metadata key for instance %s') % + instance_id) + return meta_result + + +@require_context +def update_or_create_instance_metadata(context, instance_id, metadata): + session = get_session() + meta_ref = None + for key, value in metadata.iteritems(): + try: + meta_ref = get_instance_metadata_item(context, instance_id, key, + session) + except: + meta_ref = models.InstanceMetadata() + meta_ref.update({"key": key, "value": value, + "instance_id": instance_id}) + meta_ref.save(session=session) + return metadata diff --git a/nova/tests/api/openstack/test_server_metadata.py b/nova/tests/api/openstack/test_server_metadata.py new file mode 100644 index 000000000..b280ae94c --- /dev/null +++ b/nova/tests/api/openstack/test_server_metadata.py @@ -0,0 +1,150 @@ +# 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 +import stubout +import unittest +import webob + + +from nova.api import openstack +from nova.tests.api.openstack import fakes +import nova.wsgi + + +def return_create_instance_metadata(context, server_id, metadata): + return stub_server_metadata() + + +def return_server_metadata(context, server_id): + return stub_server_metadata() + + +def return_empty_server_metadata(context, server_id): + return {} + + +def delete_server_metadata(context, server_id, key): + pass + + +def stub_server_metadata(): + metadata = { + "key1": "value1", + "key2": "value2", + "key3": "value3", + "key4": "value4", + "key5": "value5" + } + return metadata + + +class ServerMetaDataTest(unittest.TestCase): + + def setUp(self): + super(ServerMetaDataTest, self).setUp() + self.stubs = stubout.StubOutForTesting() + fakes.FakeAuthManager.auth_data = {} + fakes.FakeAuthDatabase.data = {} + fakes.stub_out_auth(self.stubs) + fakes.stub_out_key_pair_funcs(self.stubs) + #self.allow_admin = FLAGS.allow_admin_api + + def test_index(self): + self.stubs.Set(nova.db.api, 'get_instance_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()) + res_dict = json.loads(res.body) + self.assertEqual(200, res.status_int) + self.assertEqual('value1', res_dict['metadata']['key1']) + + def test_index_no_data(self): + self.stubs.Set(nova.db.api, 'get_instance_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()) + res_dict = json.loads(res.body) + self.assertEqual(200, res.status_int) + self.assertEqual(0, len(res_dict['metadata'])) + + def test_show(self): + self.stubs.Set(nova.db.api, 'get_instance_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()) + res_dict = json.loads(res.body) + self.assertEqual(200, res.status_int) + self.assertEqual('value5', res_dict['key5']) + + def test_show_meta_not_found(self): + self.stubs.Set(nova.db.api, 'get_instance_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()) + res_dict = json.loads(res.body) + self.assertEqual(404, res.status_int) + + def test_delete(self): + self.stubs.Set(nova.db.api, 'delete_instance_metadata', + delete_server_metadata) + req = webob.Request.blank('/v1.1/servers/1/meta/key5') + req.environ['api.version'] = '1.1' + req.method = 'DELETE' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(200, res.status_int) + + def test_create(self): + self.stubs.Set(nova.db.api, 'update_or_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' + req.body = '{"metadata": {"key1": "value1"}}' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + self.assertEqual(200, res.status_int) + self.assertEqual('value1', res_dict['metadata']['key1']) + + def test_update_item(self): + self.stubs.Set(nova.db.api, 'update_or_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' + req.body = '{"key1": "value1"}' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(200, res.status_int) + res_dict = json.loads(res.body) + self.assertEqual('value1', res_dict['key1']) + + def test_update_item_body_uri_mismatch(self): + self.stubs.Set(nova.db.api, 'update_or_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' + req.body = '{"key1": "value1"}' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) -- cgit From 745ade6a7759915eefe39eedc9be7e526df32547 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Fri, 18 Mar 2011 10:36:57 +0000 Subject: Fixed issue arisen from recent feature update (utils.execute) --- nova/virt/xenapi/vm_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index eaf5bb391..287a293f0 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -699,8 +699,8 @@ class VMHelper(HelperBase): try: out, err = utils.execute('sudo', 'mount', '-t', 'ext2,ext3', - '"%s"' % dev_path, - '"%s"' % tmpdir) + '%s' % dev_path, + '%s' % tmpdir) except exception.ProcessExecutionError as e: err = str(e) if err: -- cgit From fe1f675dda0aa024a05f6f7a5e8f695932d46ccc Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 21 Mar 2011 14:25:36 -0400 Subject: Added Gabe to Authors file. He helped code this up too. --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index 9aad104a7..c3b808103 100644 --- a/Authors +++ b/Authors @@ -21,6 +21,7 @@ Eldar Nugaev Eric Day Eric Windisch Ewan Mellor +Gabe Westmaas Hisaharu Ishii Hisaki Ohara Ilya Alekseyev -- cgit From 3ae9a489667ed6f4b03a19d5e14bec8e1d4eb20d Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 22 Mar 2011 09:52:59 -0400 Subject: Add call to unset all stubs. --- nova/tests/api/openstack/test_server_metadata.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nova/tests/api/openstack/test_server_metadata.py b/nova/tests/api/openstack/test_server_metadata.py index b280ae94c..ed60d4b36 100644 --- a/nova/tests/api/openstack/test_server_metadata.py +++ b/nova/tests/api/openstack/test_server_metadata.py @@ -62,7 +62,10 @@ class ServerMetaDataTest(unittest.TestCase): fakes.FakeAuthDatabase.data = {} fakes.stub_out_auth(self.stubs) fakes.stub_out_key_pair_funcs(self.stubs) - #self.allow_admin = FLAGS.allow_admin_api + + def tearDown(self): + self.stubs.UnsetAll() + super(ServerMetaDataTest, self).tearDown() def test_index(self): self.stubs.Set(nova.db.api, 'get_instance_metadata', -- cgit From 7aa027b2005ff24f7308e1ec23eddb44bf352628 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 22 Mar 2011 10:01:18 -0400 Subject: Add unit test and code updates to ensure that a PUT requests to create/update server metadata only contain a single key. --- nova/api/openstack/server_metadata.py | 3 +++ nova/tests/api/openstack/test_server_metadata.py | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/nova/api/openstack/server_metadata.py b/nova/api/openstack/server_metadata.py index 1408f59a6..45bbac99d 100644 --- a/nova/api/openstack/server_metadata.py +++ b/nova/api/openstack/server_metadata.py @@ -55,6 +55,9 @@ class Controller(wsgi.Controller): if not id in body: expl = _('Request body and URI mismatch') raise exc.HTTPBadRequest(explanation=expl) + 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) diff --git a/nova/tests/api/openstack/test_server_metadata.py b/nova/tests/api/openstack/test_server_metadata.py index ed60d4b36..97cb57ebd 100644 --- a/nova/tests/api/openstack/test_server_metadata.py +++ b/nova/tests/api/openstack/test_server_metadata.py @@ -141,6 +141,17 @@ class ServerMetaDataTest(unittest.TestCase): res_dict = json.loads(res.body) self.assertEqual('value1', res_dict['key1']) + def test_update_item_too_many_keys(self): + self.stubs.Set(nova.db.api, 'update_or_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' + req.body = '{"key1": "value1", "key2": "value2"}' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) + def test_update_item_body_uri_mismatch(self): self.stubs.Set(nova.db.api, 'update_or_create_instance_metadata', return_create_instance_metadata) -- cgit From 1abd4e6d592fb41d86fa32c3f77fd0e0a43ca5d3 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 22 Mar 2011 10:23:33 -0400 Subject: making Controller._get_flavors is_detail a keyword argument --- nova/api/openstack/flavors.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index e9ee9d012..f7b5b722f 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -37,15 +37,15 @@ class Controller(wsgi.Controller): def index(self, req): """Return all flavors in brief.""" - items = self._get_flavors(req, False) + items = self._get_flavors(req, is_detail=False) return dict(flavors=items) def detail(self, req): """Return all flavors in detail.""" - items = self._get_flavors(req, True) + items = self._get_flavors(req, is_detail=True) return dict(flavors=items) - def _get_flavors(self, req, is_detail): + def _get_flavors(self, req, is_detail=True): """Helper function that returns a list of flavor dicts.""" ctxt = req.environ['nova.context'] flavors = db.api.instance_type_get_all(ctxt) -- cgit From 94ef3c04a56427af5b4f3d0405c21d780ac8ff07 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 22 Mar 2011 10:48:37 -0400 Subject: When updating or creating set 'delete = 0'. (thus reactivating a deleted row) Filter by 'deleted' on delete. --- nova/db/sqlalchemy/api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 9699f6b5c..22c7a22b5 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -2487,6 +2487,7 @@ def delete_instance_metadata(context, instance_id, key): session.query(models.InstanceMetadata).\ filter_by(instance_id=instance_id).\ filter_by(key=key).\ + filter_by(deleted=False).\ update({'deleted': 1, 'deleted_at': datetime.datetime.utcnow(), 'updated_at': literal_column('updated_at')}) @@ -2519,6 +2520,7 @@ def update_or_create_instance_metadata(context, instance_id, metadata): except: meta_ref = models.InstanceMetadata() meta_ref.update({"key": key, "value": value, - "instance_id": instance_id}) + "instance_id": instance_id, + "deleted": 0}) meta_ref.save(session=session) return metadata -- cgit From b82d548d0357f73ff446f5bf24e27fbefd98e4b3 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 22 Mar 2011 10:58:31 -0400 Subject: adding view builder tests --- nova/api/openstack/views/versions.py | 4 +++- nova/tests/api/openstack/test_versions.py | 36 +++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/nova/api/openstack/views/versions.py b/nova/api/openstack/views/versions.py index 555d58d5c..d0145c94a 100644 --- a/nova/api/openstack/views/versions.py +++ b/nova/api/openstack/views/versions.py @@ -15,6 +15,8 @@ # License for the specific language governing permissions and limitations # under the License. +import os + def get_view_builder(req): base_url = req.application_url @@ -54,4 +56,4 @@ class ViewBuilder(object): def generate_href(self, version_number): """Create an url that refers to a specific version_number.""" - return "%s/%s" % (self.base_url, version_number) + return os.path.join(self.base_url, version_number) diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index 330d74dde..f730fc8e4 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -21,6 +21,7 @@ import webob from nova import context from nova import test from nova.tests.api.openstack import fakes +from nova.api.openstack import views class VersionsTest(test.TestCase): @@ -59,3 +60,38 @@ class VersionsTest(test.TestCase): }, ] self.assertEqual(versions, expected) + + def test_view_builder(self): + base_url = "http://example.org/" + + version_data = { + "id": "3.2.1", + "status": "CURRENT", + } + + expected = { + "id": "3.2.1", + "status": "CURRENT", + "links": [ + { + "rel": "self", + "href": "http://example.org/3.2.1", + }, + ], + } + + builder = views.versions.ViewBuilder(base_url) + output = builder.build(version_data) + + self.assertEqual(output, expected) + + def test_generate_href(self): + base_url = "http://example.org/app/" + version_number = "v1.4.6" + + expected = "http://example.org/app/v1.4.6" + + builder = views.versions.ViewBuilder(base_url) + actual = builder.generate_href(version_number) + + self.assertEqual(actual, expected) -- cgit From f4dee61638db068c03edd7fe0ab3488ac4670d89 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 22 Mar 2011 11:56:07 -0400 Subject: pep8 fix. --- nova/api/openstack/servers.py | 1 + 1 file changed, 1 insertion(+) 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'] -- cgit From 186fab6781265b2dc92cb6049c11b390cb38b969 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 22 Mar 2011 14:09:23 -0400 Subject: fixing copyright --- nova/api/openstack/versions.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nova/api/openstack/versions.py b/nova/api/openstack/versions.py index 4fa04ebb3..33f1dd628 100644 --- a/nova/api/openstack/versions.py +++ b/nova/api/openstack/versions.py @@ -1,7 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2011 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. +# Copyright 2011 OpenStack LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may -- cgit From aa4183064e15033ce2cc35773e86809b5f8224fd Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 22 Mar 2011 14:34:46 -0400 Subject: one more copyright fix --- nova/tests/api/openstack/test_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/tests/api/openstack/test_versions.py b/nova/tests/api/openstack/test_versions.py index f730fc8e4..ebb59a9a6 100644 --- a/nova/tests/api/openstack/test_versions.py +++ b/nova/tests/api/openstack/test_versions.py @@ -1,6 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright 2010 OpenStack LLC. +# Copyright 2010-2011 OpenStack LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may -- cgit From 1f90c7c6555e042cda1371a22c9891713a3f6430 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Wed, 23 Mar 2011 13:01:59 -0400 Subject: Implement v1.1 image metadata. --- nova/api/openstack/__init__.py | 6 +- nova/api/openstack/image_metadata.py | 95 ++++++++++++++ nova/tests/api/openstack/test_image_metadata.py | 166 ++++++++++++++++++++++++ 3 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 nova/api/openstack/image_metadata.py create mode 100644 nova/tests/api/openstack/test_image_metadata.py diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 21d354f1c..c12aa7e89 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -33,6 +33,7 @@ from nova.api.openstack import backup_schedules 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 limits from nova.api.openstack import servers from nova.api.openstack import shared_ip_groups @@ -150,7 +151,10 @@ class APIRouterV11(APIRouter): controller=servers.ControllerV11(), collection={'detail': 'GET'}, member=self.server_members) - + mapper.resource("image_meta", "meta", + controller=image_metadata.Controller(), + parent_resource=dict(member_name='image', + collection_name='images')) class Versions(wsgi.Application): @webob.dec.wsgify(RequestClass=wsgi.Request) diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py new file mode 100644 index 000000000..5ca349af1 --- /dev/null +++ b/nova/api/openstack/image_metadata.py @@ -0,0 +1,95 @@ +# 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. + +from webob import exc + +from nova import flags +from nova import utils +from nova import wsgi +from nova.api.openstack import faults + + +FLAGS = flags.FLAGS + + +class Controller(wsgi.Controller): + """ The image metadata API controller for the Openstack API """ + + def __init__(self): + self.image_service = utils.import_object(FLAGS.image_service) + super(Controller, self).__init__() + + def _get_metadata(self, context, image_id, image=None): + if not image: + image = self.image_service.show(context, image_id) + metadata = {} + if 'properties' in image: + metadata = image['properties'] + return metadata + + def index(self, req, image_id): + """ Returns the list of metadata for a given instance """ + context = req.environ['nova.context'] + metadata = self._get_metadata(context, image_id) + return dict(metadata=metadata) + + def show(self, req, image_id, id): + context = req.environ['nova.context'] + metadata = self._get_metadata(context, image_id) + if id in metadata: + return {id: metadata[id]} + else: + return faults.Fault(exc.HTTPNotFound()) + + def create(self, req, image_id): + context = req.environ['nova.context'] + body = self._deserialize(req.body, req.get_content_type()) + img = self.image_service.show(context, image_id) + metadata = self._get_metadata(context, image_id, img) + if 'metadata' in body: + for key, value in body['metadata'].iteritems(): + metadata[key] = value + img['properties'] = metadata + self.image_service.update(context, image_id, img, None) + return dict(metadata=metadata) + + def update(self, req, image_id, id): + context = req.environ['nova.context'] + body = self._deserialize(req.body, req.get_content_type()) + if not id in body: + expl = _('Request body and URI mismatch') + raise exc.HTTPBadRequest(explanation=expl) + if len(body) > 1: + expl = _('Request body contains too many items') + raise exc.HTTPBadRequest(explanation=expl) + img = self.image_service.show(context, image_id) + metadata = self._get_metadata(context, image_id, img) + metadata[id] = body[id] + img['properties'] = metadata + self.image_service.update(context, image_id, img, None) + + return req.body + + def delete(self, req, image_id, id): + context = req.environ['nova.context'] + img = self.image_service.show(context, image_id) + metadata = self._get_metadata(context, image_id) + if not id in metadata: + return faults.Fault(exc.HTTPNotFound()) + metadata.pop(id) + 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 new file mode 100644 index 000000000..81280bd97 --- /dev/null +++ b/nova/tests/api/openstack/test_image_metadata.py @@ -0,0 +1,166 @@ +# 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 +import stubout +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 + + +class ImageMetaDataTest(unittest.TestCase): + + IMAGE_FIXTURES = [ + {'status': 'active', + 'name': 'image1', + 'deleted': False, + 'container_format': None, + 'created_at': '2011-03-22T17: 40: 15.492626', + 'disk_format': None, + 'updated_at': '2011-03-22T17: 40: 15.591556', + 'id': '1', + 'location': 'file: ///var/lib/glance/images/1', + 'is_public': True, + 'deleted_at': None, + 'properties': { + 'type': 'ramdisk', + 'key1': 'value1', + 'key2': 'value2' + }, + 'size': 5882349}, + {'status': 'active', + 'name': 'image2', + 'deleted': False, + 'container_format': None, + 'created_at': '2011-03-22T17: 40: 15.492626', + 'disk_format': None, + 'updated_at': '2011-03-22T17: 40: 15.591556', + 'id': '2', + 'location': 'file: ///var/lib/glance/images/2', + 'is_public': True, + 'deleted_at': None, + 'properties': { + 'type': 'ramdisk', + 'key1': 'value1', + 'key2': 'value2' + }, + 'size': 5882349} + ] + + def setUp(self): + super(ImageMetaDataTest, self).setUp() + self.stubs = stubout.StubOutForTesting() + self.orig_image_service = FLAGS.image_service + FLAGS.image_service = 'nova.image.glance.GlanceImageService' + fakes.FakeAuthManager.auth_data = {} + fakes.FakeAuthDatabase.data = {} + fakes.stub_out_auth(self.stubs) + fakes.stub_out_glance(self.stubs, self.IMAGE_FIXTURES) + + def tearDown(self): + self.stubs.UnsetAll() + FLAGS.image_service = self.orig_image_service + super(ImageMetaDataTest, self).tearDown() + + def test_index(self): + req = webob.Request.blank('/v1.1/images/1/meta') + req.environ['api.version'] = '1.1' + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + self.assertEqual(200, res.status_int) + self.assertEqual('value1', res_dict['metadata']['key1']) + + def test_show(self): + req = webob.Request.blank('/v1.1/images/1/meta/key1') + req.environ['api.version'] = '1.1' + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + self.assertEqual(200, res.status_int) + self.assertEqual('value1', res_dict['key1']) + + def test_show_not_found(self): + req = webob.Request.blank('/v1.1/images/1/meta/key9') + req.environ['api.version'] = '1.1' + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + self.assertEqual(404, res.status_int) + + def test_create(self): + req = webob.Request.blank('/v1.1/images/2/meta') + req.environ['api.version'] = '1.1' + req.method = 'POST' + req.body = '{"metadata": {"key9": "value9"}}' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + res_dict = json.loads(res.body) + self.assertEqual(200, res.status_int) + self.assertEqual('value9', res_dict['metadata']['key9']) + # other items should not be modified + self.assertEqual('value1', res_dict['metadata']['key1']) + self.assertEqual('value2', res_dict['metadata']['key2']) + self.assertEqual(1, len(res_dict)) + + def test_update_item(self): + req = webob.Request.blank('/v1.1/images/1/meta/key1') + req.environ['api.version'] = '1.1' + req.method = 'PUT' + req.body = '{"key1": "zz"}' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(200, res.status_int) + res_dict = json.loads(res.body) + self.assertEqual('zz', res_dict['key1']) + + def test_update_item_too_many_keys(self): + req = webob.Request.blank('/v1.1/images/1/meta/key1') + req.environ['api.version'] = '1.1' + req.method = 'PUT' + req.body = '{"key1": "value1", "key2": "value2"}' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) + + def test_update_item_body_uri_mismatch(self): + req = webob.Request.blank('/v1.1/images/1/meta/bad') + req.environ['api.version'] = '1.1' + req.method = 'PUT' + req.body = '{"key1": "value1"}' + req.headers["content-type"] = "application/json" + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(400, res.status_int) + + def test_delete(self): + req = webob.Request.blank('/v1.1/images/2/meta/key1') + req.environ['api.version'] = '1.1' + req.method = 'DELETE' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(200, res.status_int) + + def test_delete_not_found(self): + req = webob.Request.blank('/v1.1/images/2/meta/blah') + req.environ['api.version'] = '1.1' + req.method = 'DELETE' + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(404, res.status_int) -- cgit From b49ac333df4de61ca632666cca85f6e9baf788b0 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Wed, 23 Mar 2011 13:04:44 -0400 Subject: pep8 fix. --- nova/api/openstack/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index c12aa7e89..efb10eb1b 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -156,6 +156,7 @@ class APIRouterV11(APIRouter): parent_resource=dict(member_name='image', collection_name='images')) + class Versions(wsgi.Application): @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): -- cgit From db6aaa666dc1deaeead7f32fd22a4f6b2d40ed25 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 23 Mar 2011 15:17:34 -0400 Subject: fixing some dictionary get calls --- nova/api/openstack/views/servers.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 5f4c0f740..6b5dc7c16 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -71,7 +71,7 @@ class ViewBuilder(object): # Return the metadata as a dictionary metadata = {} - if 'metadata' in inst: + if 'metadata' in dict(inst): for item in inst['metadata']: metadata[item['key']] = item['value'] inst_dict['metadata'] = metadata @@ -97,11 +97,11 @@ class ViewBuilder(object): class ViewBuilderV10(ViewBuilder): def _build_image(self, response, inst): - if inst.get('image_id') != None: + if 'image_id' in dict(inst): response['imageId'] = inst['image_id'] def _build_flavor(self, response, inst): - if inst.get('instance_type') != None: + if 'instance_type' in dict(inst): response['flavorId'] = inst['instance_type'] @@ -114,16 +114,15 @@ class ViewBuilderV11(ViewBuilder): self.base_url = base_url def _build_image(self, response, inst): - image_id = inst.get("image_id", None) - if image_id == None: - return - response["imageRef"] = self.image_builder.generate_href(image_id) + if "image_id" in dict(inst): + image_id = inst.get("image_id") + response["imageRef"] = self.image_builder.generate_href(image_id) def _build_flavor(self, response, inst): - flavor_id = inst.get("instance_type", None) - if flavor_id == None: - return - response["flavorRef"] = self.flavor_builder.generate_href(flavor_id) + if "instance_type" in dict(inst): + flavor_id = inst["instance_type"] + flavor_ref = self.flavor_builder.generate_href(flavor_id) + response["flavorRef"] = flavor_ref def _build_extra(self, response, inst): self._build_links(response, inst) -- cgit From 52bd70d500e7e82acea55c8d23c3fd1d66555cc0 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Thu, 24 Mar 2011 02:01:46 +0000 Subject: Addressing Rick Clark's comments. --- nova/tests/db/fakes.py | 4 +- nova/tests/fake_utils.py | 16 +++--- nova/tests/test_xenapi.py | 4 +- nova/tests/xenapi/stubs.py | 4 +- nova/virt/disk.py | 9 ++-- nova/virt/xenapi/fake.py | 1 - nova/virt/xenapi/vm_utils.py | 125 +++++++++++++++++++++++-------------------- nova/virt/xenapi/vmops.py | 1 - nova/virt/xenapi_conn.py | 20 +++---- 9 files changed, 95 insertions(+), 89 deletions(-) diff --git a/nova/tests/db/fakes.py b/nova/tests/db/fakes.py index 62c7cd794..c46b75aa2 100644 --- a/nova/tests/db/fakes.py +++ b/nova/tests/db/fakes.py @@ -99,9 +99,7 @@ def stub_out_db_instance_api(stubs, injected=True): return FakeModel(network_fields) def fake_network_get_all_by_instance(context, instance_id): - l = [] - l.append(FakeModel(network_fields)) - return l + return [FakeModel(network_fields)] def fake_instance_get_fixed_address(context, instance_id): return FakeModel(fixed_ip_fields).address diff --git a/nova/tests/fake_utils.py b/nova/tests/fake_utils.py index 8982f50be..823c775cb 100644 --- a/nova/tests/fake_utils.py +++ b/nova/tests/fake_utils.py @@ -33,7 +33,6 @@ _fake_execute_log = [] def fake_execute_get_log(): - global _fake_execute_log return _fake_execute_log @@ -55,7 +54,7 @@ def fake_execute_default_reply_handler(*ignore_args, **ignore_kwargs): return '', '' -def fake_execute(*cmd, **kwargs): +def fake_execute(*cmd_parts, **kwargs): """This function stubs out execute, optionally executing a preconfigued function to return expected data """ @@ -64,8 +63,7 @@ def fake_execute(*cmd, **kwargs): 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) + cmd_str = ' '.join(str(part) for part in cmd_parts) LOG.debug(_("Faking execution of cmd (subprocess): %s"), cmd_str) _fake_execute_log.append(cmd_str) @@ -78,13 +76,13 @@ def fake_execute(*cmd, **kwargs): LOG.debug(_('Faked command matched %s') % fake_replier[0]) break - if isinstance(reply_handler, types.StringTypes): + if isinstance(reply_handler, basestring): # 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, + reply = reply_handler(cmd_parts, process_input=process_input, addl_env=addl_env, check_exit_code=check_exit_code) @@ -92,8 +90,10 @@ def fake_execute(*cmd, **kwargs): 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]}) + stdout = reply[0] + stderr = reply[1] + LOG.debug(_("Reply to faked command is stdout='%(stdout)s' " + "stderr='%(stderr)s'") % locals()) # Replicate the sleep call in the real function greenthread.sleep(0) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index b22163e9b..d31fa27ac 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -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, @@ -333,7 +332,8 @@ 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", check_injection=False): + instance_type="m1.large", os_type="linux", + check_injection=False): stubs.stubout_loopingcall_start(self.stubs) values = {'id': 1, 'project_id': self.project.id, diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index d278934c6..0f559b7f9 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -139,9 +139,9 @@ def stubout_is_vdi_pv(stubs): def stubout_loopingcall_start(stubs): - def f_1(self, interval, now=True): + def fake_start(self, interval, now=True): self.f(*self.args, **self.kw) - stubs.Set(utils.LoopingCall, 'start', f_1) + stubs.Set(utils.LoopingCall, 'start', fake_start) class FakeSessionForVMTests(fake.SessionBase): diff --git a/nova/virt/disk.py b/nova/virt/disk.py index eae667c01..ee6d3e36a 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -166,12 +166,13 @@ def _free_device(device): def get_injectables(inst, template=None, template_data=None): - #load cheetah.template if necessary + # Note(salvatore-orlando): + # it the caller does not provide template object and data + # we will import the Cheetah template module and load the + # data from the file specified by injected_network_template flag if not template: - t = __import__('Cheetah.Template', globals(), locals(), ['Template'], - -1) + from Cheetah import Template as t template = t.Template - #load template file if necessary if not template_data: template_data = open(FLAGS.injected_network_template).read() diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index 60db86ecd..18d558058 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -427,7 +427,6 @@ class SessionBase(object): if (field in _db_content[cls][ref]): return _db_content[cls][ref][field] else: - LOG.debug(_('Raising Failure')) raise Failure(['HANDLE_INVALID', cls, ref]) LOG.debug(_('Raising NotImplemented')) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 7b6f2b5da..d429dae3c 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -683,65 +683,12 @@ class VMHelper(HelperBase): # everything mount_required = False key, net = disk.get_injectables(instance) - if key is not None or net is not None: - mount_required = True - - if mount_required: - - def _mounted_processing(device): - """Callback which runs with the image VDI attached""" + mount_required = key or net + if not mount_required: + return - dev_path = '/dev/' + device + '1' # NB: Partition 1 hardcoded - tmpdir = tempfile.mkdtemp() - try: - # Mount only Linux filesystems, to avoid disturbing - # NTFS images - try: - out, err = utils.execute('sudo', 'mount', - '-t', 'ext2,ext3', - dev_path, tmpdir) - except exception.ProcessExecutionError as e: - err = str(e) - if err: - LOG.info(_('Failed to mount filesystem (expected for ' - 'non-linux instances): %s') % err) - else: - try: - # This try block ensures that the umount occurs - xe_guest_agent_filename = os.path.join( - tmpdir, FLAGS.xenapi_agent_path) - if os.path.isfile(xe_guest_agent_filename): - # The presence of the guest agent - # file indicates that this instance can - # reconfigure the network from xenstore data, - # so manipulation of files in /etc is not - # required - LOG.info(_('XenServer tools installed in this ' - 'image are capable of network injection. ' - 'Networking files will not be' - 'manipulated')) - else: - xe_daemon_filename = os.path.join(tmpdir, - 'usr', 'sbin', 'xe-daemon') - if os.path.isfile(xe_daemon_filename): - LOG.info(_('XenServer tools are present ' - 'in this image but are not capable ' - 'of network injection')) - else: - LOG.info(_('XenServer tools are not ' - 'installed in this image')) - LOG.info(_('Manipulating interface files ' - 'directly')) - disk.inject_data_into_fs(tmpdir, key, net, - utils.execute) - finally: - utils.execute('sudo', 'umount', dev_path) - finally: - # remove temporary directory - os.rmdir(tmpdir) - - with_vdi_attached_here(session, vdi_ref, False, - _mounted_processing) + with_vdi_attached_here(session, vdi_ref, False, + lambda dev: _mounted_processing(dev, key, net)) @classmethod def lookup_kernel_ramdisk(cls, session, vm): @@ -1077,3 +1024,65 @@ def _write_partition(virtual_size, dev): def get_name_label_for_image(image): # TODO(sirp): This should eventually be the URI for the Glance image return _('Glance image %s') % image + + +def _mount_filesystem(dev_path, dir): + """mounts the device specified by dev_path in dir""" + try: + out, err = utils.execute('sudo', 'mount', + '-t', 'ext2,ext3', + dev_path, dir) + except exception.ProcessExecutionError as e: + err = str(e) + return err + + +def _find_guest_agent(base_dir, agent_rel_path): + agent_path = os.path.join(base_dir, agent_rel_path) + if os.path.isfile(agent_path): + # The presence of the guest agent + # file indicates that this instance can + # reconfigure the network from xenstore data, + # so manipulation of files in /etc is not + # required + LOG.info(_('XenServer tools installed in this ' + 'image are capable of network injection. ' + 'Networking files will not be' + 'manipulated')) + return True + xe_daemon_filename = os.path.join(base_dir, + 'usr', 'sbin', 'xe-daemon') + if os.path.isfile(xe_daemon_filename): + LOG.info(_('XenServer tools are present ' + 'in this image but are not capable ' + 'of network injection')) + else: + LOG.info(_('XenServer tools are not ' + 'installed in this image')) + return False + + +def _mounted_processing(device, key, net): + """Callback which runs with the image VDI attached""" + + dev_path = '/dev/' + device + '1' # NB: Partition 1 hardcoded + tmpdir = tempfile.mkdtemp() + try: + # Mount only Linux filesystems, to avoid disturbing NTFS images + err = _mount_filesystem(dev_path, tmpdir) + if not err: + try: + # This try block ensures that the umount occurs + if not _find_guest_agent(tmpdir, FLAGS.xenapi_agent_path): + LOG.info(_('Manipulating interface files ' + 'directly')) + disk.inject_data_into_fs(tmpdir, key, net, + utils.execute) + finally: + utils.execute('sudo', 'umount', dev_path) + else: + LOG.info(_('Failed to mount filesystem (expected for ' + 'non-linux instances): %s') % err) + finally: + # remove temporary directory + os.rmdir(tmpdir) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 59c5f3c13..e3810e218 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -169,7 +169,6 @@ class VMOps(object): def _wait_for_boot(): try: - LOG.debug("ENTERING WAIT FOR BOOT!") state = self.get_info(instance_name)['state'] db.instance_set_state(context.get_admin_context(), instance['id'], state) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 4f31f1071..7c3d3544f 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -107,17 +107,17 @@ flags.DEFINE_integer('xenapi_vhd_coalesce_max_attempts', 'Max number of times to poll for VHD to coalesce.' ' Used only if connection_type=xenapi.') flags.DEFINE_bool('xenapi_inject_image', - True, - 'Specifies whether an attempt to inject network/key' - ' data into the disk image should be made.' - ' Used only if connection_type=xenapi.') + True, + 'Specifies whether an attempt to inject network/key' + ' data into the disk image should be made.' + ' Used only if connection_type=xenapi.') flags.DEFINE_string('xenapi_agent_path', - 'usr/sbin/xe-update-networking', - 'Specifies the path in which the xenapi guest agent' - ' should be located. If the agent is present,' - ' network configuration if not injected into the image' - ' Used only if connection_type=xenapi.' - ' and xenapi_inject_image=True') + 'usr/sbin/xe-update-networking', + 'Specifies the path in which the xenapi guest agent' + ' should be located. If the agent is present,' + ' network configuration if not injected into the image' + ' Used only if connection_type=xenapi.' + ' and xenapi_inject_image=True') flags.DEFINE_string('xenapi_sr_base_path', '/var/run/sr-mount', 'Base path to the storage repository') -- cgit From d8052812cb5b9d3d3578c0e6651e56b4313d5f85 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Thu, 24 Mar 2011 10:32:22 +0000 Subject: Sorted out a problem occurred with units tests for VM migration --- nova/tests/test_xenapi.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index d31fa27ac..fd8ed39d8 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -553,6 +553,7 @@ class XenAPIMigrateInstance(test.TestCase): 'image_id': 1, 'kernel_id': None, 'ramdisk_id': None, + 'local_gb': 5, 'instance_type': 'm1.large', 'mac_address': 'aa:bb:cc:dd:ee:ff', 'os_type': 'linux'} -- cgit From 3b8f1f54136a67ba4c306e47b25b686328ec23b5 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 24 Mar 2011 10:15:50 -0400 Subject: making servers.generate_href more robust --- nova/api/openstack/views/servers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 08b53177b..4e7f62eb3 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -16,6 +16,7 @@ # under the License. import hashlib +import os from nova.compute import power_state import nova.compute @@ -164,4 +165,4 @@ class ViewBuilderV11(ViewBuilder): def generate_href(self, server_id): """Create an url that refers to a specific server id.""" - return "%s/servers/%s" % (self.base_url, server_id) + return os.path.join(self.base_url, "servers", str(server_id)) -- cgit From 96e8ef1049848563b60e457ab88adfb37b2dc473 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 24 Mar 2011 10:25:36 -0400 Subject: Couple of pep8 fixes. --- nova/tests/api/openstack/test_image_metadata.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nova/tests/api/openstack/test_image_metadata.py b/nova/tests/api/openstack/test_image_metadata.py index 81280bd97..46f7e5490 100644 --- a/nova/tests/api/openstack/test_image_metadata.py +++ b/nova/tests/api/openstack/test_image_metadata.py @@ -41,7 +41,7 @@ class ImageMetaDataTest(unittest.TestCase): 'disk_format': None, 'updated_at': '2011-03-22T17: 40: 15.591556', 'id': '1', - 'location': 'file: ///var/lib/glance/images/1', + 'location': 'file:///var/lib/glance/images/1', 'is_public': True, 'deleted_at': None, 'properties': { @@ -58,7 +58,7 @@ class ImageMetaDataTest(unittest.TestCase): 'disk_format': None, 'updated_at': '2011-03-22T17: 40: 15.591556', 'id': '2', - 'location': 'file: ///var/lib/glance/images/2', + 'location': 'file:///var/lib/glance/images/2', 'is_public': True, 'deleted_at': None, 'properties': { @@ -66,7 +66,7 @@ class ImageMetaDataTest(unittest.TestCase): 'key1': 'value1', 'key2': 'value2' }, - 'size': 5882349} + 'size': 5882349}, ] def setUp(self): -- cgit From f5b2167c3a18097a0de0c5b26a63baad7c1904a1 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 24 Mar 2011 12:16:06 -0400 Subject: removing old Versions application and correcting fakes to use new controller --- nova/api/openstack/__init__.py | 18 ------------------ nova/tests/api/openstack/fakes.py | 2 +- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 143b1d2b2..a9dbde6ce 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -148,21 +148,3 @@ class APIRouterV11(APIRouter): controller=servers.ControllerV11(), collection={'detail': 'GET'}, member=self.server_members) - - -class Versions(wsgi.Application): - @webob.dec.wsgify(RequestClass=wsgi.Request) - def __call__(self, req): - """Respond to a request for all OpenStack API versions.""" - response = { - "versions": [ - dict(status="DEPRECATED", id="v1.0"), - dict(status="CURRENT", id="v1.1"), - ], - } - metadata = { - "application/xml": { - "attributes": dict(version=["status", "id"])}} - - content_type = req.best_match_content_type() - return wsgi.Serializer(metadata).serialize(response, content_type) diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index 39e6db165..285f89efc 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -86,7 +86,7 @@ def wsgi_app(inner_app10=None, inner_app11=None): limits.RateLimitingMiddleware(inner_app11))) mapper['/v1.0'] = api10 mapper['/v1.1'] = api11 - mapper['/'] = openstack.FaultWrapper(openstack.Versions()) + mapper['/'] = openstack.FaultWrapper(versions.Versions()) return mapper -- cgit From 7baaace446c441fdd699018912ef7604265000ce Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Thu, 24 Mar 2011 16:36:37 +0000 Subject: Stubbing out utils.execute for migrate tests --- nova/tests/test_xenapi.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index fd8ed39d8..2ee385cff 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -558,6 +558,7 @@ class XenAPIMigrateInstance(test.TestCase): 'mac_address': 'aa:bb:cc:dd:ee:ff', 'os_type': 'linux'} + fake_utils.stub_out_utils_execute(self.stubs) stubs.stub_out_migration_methods(self.stubs) stubs.stubout_get_this_vm_uuid(self.stubs) glance_stubs.stubout_glance_client(self.stubs, -- cgit From fbb8291263ae49521bbe02aa7f75c000c7f2db8d Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Thu, 24 Mar 2011 12:46:39 -0400 Subject: adding versioned controllers --- nova/api/openstack/__init__.py | 11 ++++++++--- nova/api/openstack/flavors.py | 17 ++++++++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 143b1d2b2..f47422359 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -117,9 +117,6 @@ class APIRouter(wsgi.Router): 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", collection={'detail': 'GET'}, controller=shared_ip_groups.Controller()) @@ -138,6 +135,10 @@ class APIRouterV10(APIRouter): collection={'detail': 'GET'}, member=self.server_members) + mapper.resource("flavor", "flavors", + controller=flavors.ControllerV10(), + collection={'detail': 'GET'}) + class APIRouterV11(APIRouter): """Define routes specific to OpenStack API V1.1.""" @@ -149,6 +150,10 @@ class APIRouterV11(APIRouter): collection={'detail': 'GET'}, member=self.server_members) + mapper.resource("flavor", "flavors", + controller=flavors.ControllerV11(), + collection={'detail': 'GET'}) + class Versions(wsgi.Application): @webob.dec.wsgify(RequestClass=wsgi.Request) diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index f7b5b722f..5b99b5a6f 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -20,7 +20,7 @@ import webob from nova import db from nova import exception from nova import wsgi -from nova.api.openstack.views import flavors as flavors_views +from nova.api.openstack import views class Controller(wsgi.Controller): @@ -49,7 +49,7 @@ class Controller(wsgi.Controller): """Helper function that returns a list of flavor dicts.""" ctxt = req.environ['nova.context'] flavors = db.api.instance_type_get_all(ctxt) - builder = flavors_views.get_view_builder(req) + builder = self._get_view_builder(req) items = [builder.build(flavor, is_detail=is_detail) for flavor in flavors.values()] return items @@ -62,6 +62,17 @@ class Controller(wsgi.Controller): except exception.NotFound: return webob.exc.HTTPNotFound() - builder = flavors_views.get_view_builder(req) + builder = self._get_view_builder(req) values = builder.build(flavor, is_detail=True) return dict(flavor=values) + + +class ControllerV10(Controller): + def _get_view_builder(self, req): + return views.flavors.ViewBuilder() + + +class ControllerV11(Controller): + def _get_view_builder(self, req): + base_url = req.application_url + return views.flavors.ViewBuilderV11(base_url) -- cgit From d91102e1ce73b5b2e1f5fbcc380814f1673cefa3 Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Thu, 24 Mar 2011 12:46:41 -0400 Subject: get image metadata tests working after the datetime interface change in image services --- nova/tests/api/openstack/fakes.py | 8 ++++++-- nova/tests/api/openstack/test_image_metadata.py | 8 ++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index 56143114d..7002c3a74 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -168,15 +168,19 @@ def stub_out_glance(stubs, initial_fixtures=None): id = ''.join(random.choice(string.letters) for _ in range(20)) image_meta['id'] = id self.fixtures.append(image_meta) - return image_meta + return copy.deepcopy(image_meta) def fake_update_image(self, image_id, image_meta, data=None): + for attr in ('created_at', 'updated_at', 'deleted_at', 'deleted'): + if attr in image_meta: + del image_meta[attr] + f = self._find_image(image_id) if not f: raise glance_exc.NotFound f.update(image_meta) - return f + return copy.deepcopy(f) def fake_delete_image(self, image_id): f = self._find_image(image_id) diff --git a/nova/tests/api/openstack/test_image_metadata.py b/nova/tests/api/openstack/test_image_metadata.py index 46f7e5490..33ef1a0a3 100644 --- a/nova/tests/api/openstack/test_image_metadata.py +++ b/nova/tests/api/openstack/test_image_metadata.py @@ -37,9 +37,9 @@ class ImageMetaDataTest(unittest.TestCase): 'name': 'image1', 'deleted': False, 'container_format': None, - 'created_at': '2011-03-22T17: 40: 15.492626', + 'created_at': '2011-03-22T17:40:15.492626', 'disk_format': None, - 'updated_at': '2011-03-22T17: 40: 15.591556', + 'updated_at': '2011-03-22T17:40:15.591556', 'id': '1', 'location': 'file:///var/lib/glance/images/1', 'is_public': True, @@ -54,9 +54,9 @@ class ImageMetaDataTest(unittest.TestCase): 'name': 'image2', 'deleted': False, 'container_format': None, - 'created_at': '2011-03-22T17: 40: 15.492626', + 'created_at': '2011-03-22T17:40:15.492626', 'disk_format': None, - 'updated_at': '2011-03-22T17: 40: 15.591556', + 'updated_at': '2011-03-22T17:40:15.591556', 'id': '2', 'location': 'file:///var/lib/glance/images/2', 'is_public': True, -- cgit From a69f6ef093805d74832a9dd531e55dd614dfa71c Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 24 Mar 2011 12:57:49 -0400 Subject: Docstring fixes. --- nova/api/openstack/image_metadata.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index 5ca349af1..e09952967 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -27,7 +27,7 @@ FLAGS = flags.FLAGS class Controller(wsgi.Controller): - """ The image metadata API controller for the Openstack API """ + """The image metadata API controller for the Openstack API""" def __init__(self): self.image_service = utils.import_object(FLAGS.image_service) @@ -42,7 +42,7 @@ class Controller(wsgi.Controller): return metadata def index(self, req, image_id): - """ Returns the list of metadata for a given instance """ + """Returns the list of metadata for a given instance""" context = req.environ['nova.context'] metadata = self._get_metadata(context, image_id) return dict(metadata=metadata) -- cgit From 6254069cdf0262e128bfa877f0c56e5aeba2b4c2 Mon Sep 17 00:00:00 2001 From: Mark Washenberger Date: Thu, 24 Mar 2011 13:52:20 -0400 Subject: change names for consistency with existing db api --- nova/compute/api.py | 6 +++--- nova/db/api.py | 14 +++++++------- nova/db/sqlalchemy/api.py | 10 +++++----- nova/tests/api/openstack/test_server_metadata.py | 18 +++++++++--------- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 1fbaa399d..825b50e48 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -651,15 +651,15 @@ class API(base.Base): def get_instance_metadata(self, context, instance_id): """Get all metadata associated with an instance.""" - rv = self.db.get_instance_metadata(context, instance_id) + rv = self.db.instance_metadata_get(context, instance_id) return dict(rv.iteritems()) def delete_instance_metadata(self, context, instance_id, key): """Delete the given metadata item""" - self.db.delete_instance_metadata(context, instance_id, key) + 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""" - self.db.update_or_create_instance_metadata(context, instance_id, + self.db.instance_metadata_update_or_create(context, instance_id, metadata) diff --git a/nova/db/api.py b/nova/db/api.py index 1856e6bcb..300723c62 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -1176,16 +1176,16 @@ def zone_get_all(context): #################### -def get_instance_metadata(context, instance_id): +def instance_metadata_get(context, instance_id): """Get all metadata for an instance""" - return IMPL.get_instance_metadata(context, instance_id) + return IMPL.instance_metadata_get(context, instance_id) -def delete_instance_metadata(context, instance_id, key): +def instance_metadata_delete(context, instance_id, key): """Delete the given metadata item""" - IMPL.delete_instance_metadata(context, instance_id, key) + IMPL.instance_metadata_delete(context, instance_id, key) -def update_or_create_instance_metadata(context, instance_id, metadata): - """Creates or updates instance metadata""" - IMPL.update_or_create_instance_metadata(context, instance_id, metadata) +def instance_metadata_update_or_create(context, instance_id, metadata): + """Create or update instance metadata""" + IMPL.instance_metadata_update_or_create(context, instance_id, metadata) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 5fe18b65c..57c05623b 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -2467,7 +2467,7 @@ def zone_get_all(context): #################### @require_context -def get_instance_metadata(context, instance_id): +def instance_metadata_get(context, instance_id): session = get_session() meta_results = session.query(models.InstanceMetadata).\ @@ -2482,7 +2482,7 @@ def get_instance_metadata(context, instance_id): @require_context -def delete_instance_metadata(context, instance_id, key): +def instance_metadata_delete(context, instance_id, key): session = get_session() session.query(models.InstanceMetadata).\ filter_by(instance_id=instance_id).\ @@ -2494,7 +2494,7 @@ def delete_instance_metadata(context, instance_id, key): @require_context -def get_instance_metadata_item(context, instance_id, key): +def instance_metadata_get_item(context, instance_id, key): session = get_session() meta_result = session.query(models.InstanceMetadata).\ @@ -2510,12 +2510,12 @@ def get_instance_metadata_item(context, instance_id, key): @require_context -def update_or_create_instance_metadata(context, instance_id, metadata): +def instance_metadata_update_or_create(context, instance_id, metadata): session = get_session() meta_ref = None for key, value in metadata.iteritems(): try: - meta_ref = get_instance_metadata_item(context, instance_id, key, + meta_ref = instance_metadata_get_item(context, instance_id, key, session) except: meta_ref = models.InstanceMetadata() diff --git a/nova/tests/api/openstack/test_server_metadata.py b/nova/tests/api/openstack/test_server_metadata.py index 97cb57ebd..c8d456472 100644 --- a/nova/tests/api/openstack/test_server_metadata.py +++ b/nova/tests/api/openstack/test_server_metadata.py @@ -68,7 +68,7 @@ class ServerMetaDataTest(unittest.TestCase): super(ServerMetaDataTest, self).tearDown() def test_index(self): - self.stubs.Set(nova.db.api, 'get_instance_metadata', + self.stubs.Set(nova.db.api, 'instance_metadata_get', return_server_metadata) req = webob.Request.blank('/v1.1/servers/1/meta') req.environ['api.version'] = '1.1' @@ -78,7 +78,7 @@ class ServerMetaDataTest(unittest.TestCase): self.assertEqual('value1', res_dict['metadata']['key1']) def test_index_no_data(self): - self.stubs.Set(nova.db.api, 'get_instance_metadata', + self.stubs.Set(nova.db.api, 'instance_metadata_get', return_empty_server_metadata) req = webob.Request.blank('/v1.1/servers/1/meta') req.environ['api.version'] = '1.1' @@ -88,7 +88,7 @@ class ServerMetaDataTest(unittest.TestCase): self.assertEqual(0, len(res_dict['metadata'])) def test_show(self): - self.stubs.Set(nova.db.api, 'get_instance_metadata', + self.stubs.Set(nova.db.api, 'instance_metadata_get', return_server_metadata) req = webob.Request.blank('/v1.1/servers/1/meta/key5') req.environ['api.version'] = '1.1' @@ -98,7 +98,7 @@ class ServerMetaDataTest(unittest.TestCase): self.assertEqual('value5', res_dict['key5']) def test_show_meta_not_found(self): - self.stubs.Set(nova.db.api, 'get_instance_metadata', + self.stubs.Set(nova.db.api, 'instance_metadata_get', return_empty_server_metadata) req = webob.Request.blank('/v1.1/servers/1/meta/key6') req.environ['api.version'] = '1.1' @@ -107,7 +107,7 @@ class ServerMetaDataTest(unittest.TestCase): self.assertEqual(404, res.status_int) def test_delete(self): - self.stubs.Set(nova.db.api, 'delete_instance_metadata', + self.stubs.Set(nova.db.api, 'instance_metadata_delete', delete_server_metadata) req = webob.Request.blank('/v1.1/servers/1/meta/key5') req.environ['api.version'] = '1.1' @@ -116,7 +116,7 @@ class ServerMetaDataTest(unittest.TestCase): self.assertEqual(200, res.status_int) def test_create(self): - self.stubs.Set(nova.db.api, 'update_or_create_instance_metadata', + self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/meta') req.environ['api.version'] = '1.1' @@ -129,7 +129,7 @@ class ServerMetaDataTest(unittest.TestCase): self.assertEqual('value1', res_dict['metadata']['key1']) def test_update_item(self): - self.stubs.Set(nova.db.api, 'update_or_create_instance_metadata', + self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/meta/key1') req.environ['api.version'] = '1.1' @@ -142,7 +142,7 @@ class ServerMetaDataTest(unittest.TestCase): self.assertEqual('value1', res_dict['key1']) def test_update_item_too_many_keys(self): - self.stubs.Set(nova.db.api, 'update_or_create_instance_metadata', + self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/meta/key1') req.environ['api.version'] = '1.1' @@ -153,7 +153,7 @@ class ServerMetaDataTest(unittest.TestCase): self.assertEqual(400, res.status_int) def test_update_item_body_uri_mismatch(self): - self.stubs.Set(nova.db.api, 'update_or_create_instance_metadata', + self.stubs.Set(nova.db.api, 'instance_metadata_update_or_create', return_create_instance_metadata) req = webob.Request.blank('/v1.1/servers/1/meta/bad') req.environ['api.version'] = '1.1' -- cgit From 08d40029973d9ca97477393531296502a407debe Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Thu, 24 Mar 2011 22:39:39 +0000 Subject: Addressing Trey's comments. Removed disk_get_injectables, using _get_network_info's return value. --- nova/virt/disk.py | 37 --------------------------------- nova/virt/libvirt_conn.py | 31 +++++++++++++++++++++++++++- nova/virt/xenapi/vm_utils.py | 49 +++++++++++++++++++++++++++++++++++++++++--- nova/virt/xenapi/vmops.py | 10 +++++---- nova/virt/xenapi_conn.py | 2 +- 5 files changed, 83 insertions(+), 46 deletions(-) diff --git a/nova/virt/disk.py b/nova/virt/disk.py index ee6d3e36a..25e4f54a9 100644 --- a/nova/virt/disk.py +++ b/nova/virt/disk.py @@ -165,43 +165,6 @@ def _free_device(device): _DEVICES.append(device) -def get_injectables(inst, template=None, template_data=None): - # Note(salvatore-orlando): - # it the caller does not provide template object and data - # we will import the Cheetah template module and load the - # data from the file specified by injected_network_template flag - if not template: - from Cheetah import Template as t - template = t.Template - if not template_data: - template_data = open(FLAGS.injected_network_template).read() - - key = str(inst['key_data']) - net = None - network_ref = db.network_get_by_instance(context.get_admin_context(), - inst['id']) - if network_ref['injected']: - admin_context = context.get_admin_context() - address = db.instance_get_fixed_address(admin_context, inst['id']) - address_v6 = None - if FLAGS.use_ipv6: - address_v6 = db.instance_get_fixed_address_v6(admin_context, - inst['id']) - interfaces_info = {'address': address, - 'netmask': network_ref['netmask'], - 'gateway': network_ref['gateway'], - 'broadcast': network_ref['broadcast'], - 'dns': network_ref['dns'], - 'address_v6': address_v6, - 'gateway_v6': network_ref['gateway_v6'], - 'netmask_v6': network_ref['netmask_v6'], - 'use_ipv6': FLAGS.use_ipv6} - net = str(template(template_data, - searchList=[interfaces_info])) - - return key, net - - def inject_data_into_fs(fs, key, net, execute): """Injects data into a filesystem already mounted by the caller. Virt connections can call this directly if they mount their fs diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 4b855f5de..f6a51fc62 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -681,7 +681,36 @@ class LibvirtConnection(driver.ComputeDriver): if not inst['kernel_id']: target_partition = "1" - key, net = disk.get_injectables(inst, Template, self.interfaces_xml) + key = str(inst['key_data']) + net = None + + nets = [] + ifc_template = open(FLAGS.injected_network_template).read() + ifc_num = -1 + admin_context = context.get_admin_context() + for (network_ref, mapping) in network_info: + ifc_num += 1 + + if not 'injected' in network_ref: + continue + + address = mapping['ips'][0]['ip'] + address_v6 = None + if FLAGS.use_ipv6: + address_v6 = mapping['ip6s'][0]['ip'] + net_info = {'name': 'eth%d' % ifc_num, + 'address': address, + 'netmask': network_ref['netmask'], + 'gateway': network_ref['gateway'], + 'broadcast': network_ref['broadcast'], + 'dns': network_ref['dns'], + 'address_v6': address_v6, + 'gateway_v6': network_ref['gateway_v6'], + 'netmask_v6': network_ref['netmask_v6'], + 'use_ipv6': FLAGS.use_ipv6} + nets.append(net_info) + + net = str(Template(ifc_template, searchList=[{'interfaces': nets}])) 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 d429dae3c..5f66c4237 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -674,7 +674,7 @@ class VMHelper(HelperBase): return None @classmethod - def preconfigure_instance(cls, session, instance, vdi_ref): + def preconfigure_instance(cls, session, instance, vdi_ref, network_info): """Makes alterations to the image before launching as part of spawn. """ @@ -682,11 +682,11 @@ class VMHelper(HelperBase): # if at all, so determine whether it's required first, and then do # everything mount_required = False - key, net = disk.get_injectables(instance) + key, net = _prepare_injectables(instance, network_info) mount_required = key or net if not mount_required: return - + with_vdi_attached_here(session, vdi_ref, False, lambda dev: _mounted_processing(dev, key, net)) @@ -1038,6 +1038,10 @@ def _mount_filesystem(dev_path, dir): def _find_guest_agent(base_dir, agent_rel_path): + """ + tries to locate a guest agent at the path + specificed by agent_rel_path + """ agent_path = os.path.join(base_dir, agent_rel_path) if os.path.isfile(agent_path): # The presence of the guest agent @@ -1086,3 +1090,42 @@ def _mounted_processing(device, key, net): finally: # remove temporary directory os.rmdir(tmpdir) + + +def _prepare_injectables(inst, networks_info): + """ + prepares the ssh key and the network configuration file to be + injected into the disk image + """ + #do the import here - Cheetah.Template will be loaded + #only if injection is performed + from Cheetah import Template as t + template = t.Template + template_data = open(FLAGS.injected_network_template).read() + + key = str(inst['key_data']) + net = None + #fetch info only for the 1st network + if len(networks_info) > 0: + network_info = networks_info[0][1] + if network_info: + #remap data in network_info onto keys for template + ip_v4 = ip_v6 = None + if len(network_info['ips']) > 0: + ip_v4 = network_info['ips'][0] + if len(network_info['ip6s']) > 0: + ip_v6 = network_info['ip6s'][0] + if len(network_info['dns']) > 0: + dns = network_info['dns'][0] + interfaces_info = {'address': ip_v4 and ip_v4['ip'] or '', + 'netmask': ip_v4 and ip_v4['netmask'] or '', + 'gateway': network_info['gateway'], + 'broadcast': network_info['broadcast'], + '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 '', + 'use_ipv6': FLAGS.use_ipv6} + net = str(template(template_data, + searchList=[interfaces_info])) + return key, net \ No newline at end of file diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 8e5302766..b33b5902c 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -161,14 +161,16 @@ class VMOps(object): VMHelper.create_vbd(session=self._session, vm_ref=vm_ref, vdi_ref=vdi_ref, userdevice=0, bootable=True) - # Alter the image before VM start for, e.g. network injection - if FLAGS.xenapi_inject_image: - VMHelper.preconfigure_instance(self._session, instance, vdi_ref) - # TODO(tr3buchet) - check to make sure we have network info, otherwise # create it now. This goes away once nova-multi-nic hits. if network_info is None: network_info = self._get_network_info(instance) + + # Alter the image before VM start for, e.g. network injection + if FLAGS.xenapi_inject_image: + VMHelper.preconfigure_instance(self._session, instance, + vdi_ref, network_info) + self.create_vifs(vm_ref, network_info) self.inject_network_info(instance, vm_ref, network_info) return vm_ref diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 0c234cb52..99fd35c61 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -116,7 +116,7 @@ flags.DEFINE_string('xenapi_agent_path', 'usr/sbin/xe-update-networking', 'Specifies the path in which the xenapi guest agent' ' should be located. If the agent is present,' - ' network configuration if not injected into the image' + ' network configuration is not injected into the image' ' Used only if connection_type=xenapi.' ' and xenapi_inject_image=True') -- cgit From 73df3e0cd54dc3b5409fba7b38ada6ee07bd911d Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Fri, 25 Mar 2011 01:23:33 +0000 Subject: minor pep8 fix in db/fakes.py --- nova/tests/db/fakes.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nova/tests/db/fakes.py b/nova/tests/db/fakes.py index c46b75aa2..21a5481bd 100644 --- a/nova/tests/db/fakes.py +++ b/nova/tests/db/fakes.py @@ -108,9 +108,7 @@ def stub_out_db_instance_api(stubs, injected=True): return FakeModel(fixed_ip_fields).address def fake_fixed_ip_get_all_by_instance(context, instance_id): - l = [] - l.append(FakeModel(fixed_ip_fields)) - return l + return [FakeModel(fixed_ip_fields)] stubs.Set(db, 'network_get_by_instance', fake_network_get_by_instance) stubs.Set(db, 'instance_type_get_all', fake_instance_type_get_all) -- cgit From ccf4727ca16d7a67c6a35950ab378ab4615dbdad Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Fri, 25 Mar 2011 04:41:47 +0000 Subject: disk_format is now an ImageService property --- nova/api/openstack/servers.py | 34 +++++++++++-------- nova/tests/api/openstack/test_servers.py | 57 ++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 13 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 144d14536..ac9e29f07 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -502,33 +502,41 @@ class Controller(wsgi.Controller): return dict(actions=actions) def _get_kernel_ramdisk_from_image(self, req, image_id): - """Retrevies kernel and ramdisk IDs from Glance - - Only 'machine' (ami) type use kernel and ramdisk outside of the - image. + """Fetch an image from the ImageService, then if present, return the + associated kernel and ramdisk image IDs. """ - # FIXME(sirp): Since we're retrieving the kernel_id from an - # image_property, this means only Glance is supported. - # The BaseImageService needs to expose a consistent way of accessing - # kernel_id and ramdisk_id - image = self._image_service.show(req.environ['nova.context'], image_id) + context = req.environ['nova.context'] + image_meta = self._image_service.show(context, image_id) + # NOTE(sirp): extracted to a separate method to aid unit-testing, the + # new method doesn't need a request obj or an ImageService stub + kernel_id, ramdisk_id = self._do_get_kernel_ramdisk_from_image( + image_meta) + return kernel_id, ramdisk_id - if image['status'] != 'active': + @staticmethod + def _do_get_kernel_ramdisk_from_image(image_meta): + """Given an ImageService image_meta, return kernel and ramdisk image + ids if present. + + This is only valid for `ami` style images. + """ + image_id = image_meta['id'] + if image_meta['status'] != 'active': raise exception.Invalid( _("Cannot build from image %(image_id)s, status not active") % locals()) - if image['disk_format'] != 'ami': + if image_meta['properties']['disk_format'] != 'ami': return None, None try: - kernel_id = image['properties']['kernel_id'] + kernel_id = image_meta['properties']['kernel_id'] except KeyError: raise exception.NotFound( _("Kernel not found for image %(image_id)s") % locals()) try: - ramdisk_id = image['properties']['ramdisk_id'] + ramdisk_id = image_meta['properties']['ramdisk_id'] except KeyError: raise exception.NotFound( _("Ramdisk not found for image %(image_id)s") % locals()) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index c48cc5179..3c117f10c 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -26,6 +26,7 @@ import webob from nova import context from nova import db +from nova import exception from nova import flags from nova import test import nova.api.openstack @@ -1260,3 +1261,59 @@ class TestServerInstanceCreation(test.TestCase): server = dom.childNodes[0] self.assertEquals(server.nodeName, 'server') self.assertTrue(server.getAttribute('adminPass').startswith('fake')) + + +class TestGetKernelRamdiskFromImage(test.TestCase): + """ + If we're building from an AMI-style image, we need to be able to fetch the + kernel and ramdisk associated with the machine image. This information is + stored with the image metadata and return via the ImageService. + + These tests ensure that we parse the metadata return the ImageService + correctly and that we handle failure modes appropriately. + """ + + def test_status_not_active(self): + """We should only allow fetching of kernel and ramdisk information if + we have a 'fully-formed' image, aka 'active' + """ + image_meta = {'id': 1, 'status': 'queued'} + self.assertRaises(exception.Invalid, self._get_k_r, image_meta) + + 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'}} + 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}} + 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}} + 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}} + kernel_id, ramdisk_id = self._get_k_r(image_meta) + self.assertEqual(kernel_id, 1) + self.assertEqual(ramdisk_id, 2) + + @staticmethod + def _get_k_r(image_meta): + """Rebinding function to a shorter name for convenience""" + kernel_id, ramdisk_id = \ + servers.Controller._do_get_kernel_ramdisk_from_image(image_meta) + return kernel_id, ramdisk_id + + -- cgit From b6745341ec06cb0a0de7963f6b4606f4ad6f8c89 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Thu, 24 Mar 2011 23:49:14 -0500 Subject: pep8 fix --- nova/tests/api/openstack/test_servers.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 3c117f10c..3dac7a1b1 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -1315,5 +1315,3 @@ class TestGetKernelRamdiskFromImage(test.TestCase): kernel_id, ramdisk_id = \ servers.Controller._do_get_kernel_ramdisk_from_image(image_meta) return kernel_id, ramdisk_id - - -- cgit From f533c441c0062d05c7c361208016e56ca8f5e9df Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Fri, 25 Mar 2011 09:28:36 -0400 Subject: Fix unit tests w/ latest trunk merge. --- nova/tests/api/openstack/test_image_metadata.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nova/tests/api/openstack/test_image_metadata.py b/nova/tests/api/openstack/test_image_metadata.py index 33ef1a0a3..9be753f84 100644 --- a/nova/tests/api/openstack/test_image_metadata.py +++ b/nova/tests/api/openstack/test_image_metadata.py @@ -37,9 +37,9 @@ class ImageMetaDataTest(unittest.TestCase): 'name': 'image1', 'deleted': False, 'container_format': None, - 'created_at': '2011-03-22T17:40:15.492626', + 'created_at': '2011-03-22T17:40:15', 'disk_format': None, - 'updated_at': '2011-03-22T17:40:15.591556', + 'updated_at': '2011-03-22T17:40:15', 'id': '1', 'location': 'file:///var/lib/glance/images/1', 'is_public': True, @@ -54,9 +54,9 @@ class ImageMetaDataTest(unittest.TestCase): 'name': 'image2', 'deleted': False, 'container_format': None, - 'created_at': '2011-03-22T17:40:15.492626', + 'created_at': '2011-03-22T17:40:15', 'disk_format': None, - 'updated_at': '2011-03-22T17:40:15.591556', + 'updated_at': '2011-03-22T17:40:15', 'id': '2', 'location': 'file:///var/lib/glance/images/2', 'is_public': True, -- cgit From 51e8841b7cd818e5a3e0fa6bf023561b0160717d Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Fri, 25 Mar 2011 10:07:42 -0400 Subject: Use metadata = image.get('properties', {}). --- nova/api/openstack/image_metadata.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nova/api/openstack/image_metadata.py b/nova/api/openstack/image_metadata.py index e09952967..c9d6ac532 100644 --- a/nova/api/openstack/image_metadata.py +++ b/nova/api/openstack/image_metadata.py @@ -36,9 +36,7 @@ class Controller(wsgi.Controller): def _get_metadata(self, context, image_id, image=None): if not image: image = self.image_service.show(context, image_id) - metadata = {} - if 'properties' in image: - metadata = image['properties'] + metadata = image.get('properties', {}) return metadata def index(self, req, image_id): -- cgit