summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSalvatore Orlando <salvatore.orlando@eu.citrix.com>2011-03-25 23:43:21 +0000
committerSalvatore Orlando <salvatore.orlando@eu.citrix.com>2011-03-25 23:43:21 +0000
commit82d6fb2144a5411e96dc71a482d33be4990f7cab (patch)
treef1911f3b119d7221150f6bbdf41e29673b78c7ac
parent129e020b3a0712404de28386b19a652d7db8fae8 (diff)
parent9a1a2c174984ef873c80bf7aea307b393552f3a9 (diff)
downloadnova-82d6fb2144a5411e96dc71a482d33be4990f7cab.tar.gz
nova-82d6fb2144a5411e96dc71a482d33be4990f7cab.tar.xz
nova-82d6fb2144a5411e96dc71a482d33be4990f7cab.zip
merge trunk
addressing Trey's comments
-rw-r--r--Authors1
-rw-r--r--nova/tests/db/fakes.py31
-rw-r--r--nova/tests/fake_utils.py16
-rw-r--r--nova/tests/fake_utils.py.moved106
-rw-r--r--nova/tests/test_xenapi.py116
-rw-r--r--nova/tests/xenapi/stubs.py4
-rw-r--r--nova/virt/disk.py26
-rw-r--r--nova/virt/libvirt_conn.py4
-rw-r--r--nova/virt/xenapi/fake.py22
-rw-r--r--nova/virt/xenapi/vm_utils.py133
-rw-r--r--nova/virt/xenapi/vmops.py28
-rw-r--r--nova/virt/xenapi_conn.py14
12 files changed, 448 insertions, 53 deletions
diff --git a/Authors b/Authors
index 972b65bfc..09759ddcb 100644
--- a/Authors
+++ b/Authors
@@ -1,4 +1,5 @@
Andy Smith <code@term.ie>
+Andy Southgate <andy.southgate@citrix.com>
Anne Gentle <anne@openstack.org>
Anthony Young <sleepsonthefloor@gmail.com>
Antony Messerli <ant@openstack.org>
diff --git a/nova/tests/db/fakes.py b/nova/tests/db/fakes.py
index e94c7bfee..05a47d4c9 100644
--- a/nova/tests/db/fakes.py
+++ b/nova/tests/db/fakes.py
@@ -23,8 +23,6 @@ from nova import db
from nova import test
from nova import utils
-from nova import log as LOG
-
def stub_out_db_instance_api(stubs, injected=True):
""" Stubs out the db API for creating Instances """
@@ -87,6 +85,11 @@ def stub_out_db_instance_api(stubs, injected=True):
'vlan': 111,
'injected': False}
+ fixed_ip_fields = {
+ 'address': '10.0.0.3',
+ 'address_v6': 'fe80::a00:3',
+ 'network_id': 'fake_flat'}
+
class FakeModel(object):
""" Stubs out for model """
def __init__(self, values):
@@ -113,18 +116,34 @@ def stub_out_db_instance_api(stubs, injected=True):
return FakeModel(vlan_network_fields)
else:
return FakeModel(flat_network_fields)
+ return FakeModel(network_fields)
def fake_network_get_all_by_instance(context, instance_id):
- l = []
#even instance numbers are on vlan networks
if instance_id % 2 == 0:
- l.append(FakeModel(vlan_network_fields))
+ return [FakeModel(vlan_network_fields)]
else:
- l.append(FakeModel(flat_network_fields))
- return l
+ return [FakeModel(flat_network_fields)]
+
+ def fake_instance_get_fixed_address(context, instance_id):
+ return FakeModel(fixed_ip_fields).address
+
+ def fake_instance_get_fixed_address_v6(context, instance_id):
+ return FakeModel(fixed_ip_fields).address
+
+ def fake_fixed_ip_get_all_by_instance(context, instance_id):
+ return [FakeModel(fixed_ip_fields)]
stubs.Set(db, 'network_get_by_instance', fake_network_get_by_instance)
stubs.Set(db, 'network_get_all_by_instance',
fake_network_get_all_by_instance)
stubs.Set(db, 'instance_type_get_all', fake_instance_type_get_all)
stubs.Set(db, 'instance_type_get_by_name', fake_instance_type_get_by_name)
+ stubs.Set(db, 'instance_get_fixed_address',
+ fake_instance_get_fixed_address)
+ stubs.Set(db, 'instance_get_fixed_address_v6',
+ fake_instance_get_fixed_address_v6)
+ 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)
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/fake_utils.py.moved b/nova/tests/fake_utils.py.moved
new file mode 100644
index 000000000..8982f50be
--- /dev/null
+++ b/nova/tests/fake_utils.py.moved
@@ -0,0 +1,106 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2011 Citrix Systems, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""This modules stubs out functions in nova.utils
+"""
+
+import re
+import types
+
+from eventlet import greenthread
+
+from nova import exception
+from nova import log as logging
+from nova import utils
+
+LOG = logging.getLogger('nova.tests.fake_utils')
+
+_fake_execute_repliers = []
+_fake_execute_log = []
+
+
+def fake_execute_get_log():
+ global _fake_execute_log
+ return _fake_execute_log
+
+
+def fake_execute_clear_log():
+ global _fake_execute_log
+ _fake_execute_log = []
+
+
+def fake_execute_set_repliers(repliers):
+ """Allows the client to configure replies to commands"""
+ global _fake_execute_repliers
+ _fake_execute_repliers = repliers
+
+
+def fake_execute_default_reply_handler(*ignore_args, **ignore_kwargs):
+ """A reply handler for commands that haven't been added to the reply
+ list. Returns empty strings for stdout and stderr
+ """
+ return '', ''
+
+
+def fake_execute(*cmd, **kwargs):
+ """This function stubs out execute, optionally executing
+ a preconfigued function to return expected data
+ """
+ global _fake_execute_repliers
+
+ process_input = kwargs.get('process_input', None)
+ addl_env = kwargs.get('addl_env', None)
+ check_exit_code = kwargs.get('check_exit_code', 0)
+ cmd_map = map(str, cmd)
+ cmd_str = ' '.join(cmd_map)
+
+ LOG.debug(_("Faking execution of cmd (subprocess): %s"), cmd_str)
+ _fake_execute_log.append(cmd_str)
+
+ reply_handler = fake_execute_default_reply_handler
+
+ for fake_replier in _fake_execute_repliers:
+ if re.match(fake_replier[0], cmd_str):
+ reply_handler = fake_replier[1]
+ LOG.debug(_('Faked command matched %s') % fake_replier[0])
+ break
+
+ if isinstance(reply_handler, types.StringTypes):
+ # If the reply handler is a string, return it as stdout
+ reply = reply_handler, ''
+ else:
+ try:
+ # Alternative is a function, so call it
+ reply = reply_handler(cmd,
+ process_input=process_input,
+ addl_env=addl_env,
+ check_exit_code=check_exit_code)
+ except exception.ProcessExecutionError as e:
+ LOG.debug(_('Faked command raised an exception %s' % str(e)))
+ raise
+
+ LOG.debug(_("Reply to faked command is stdout='%(0)s' stderr='%(1)s'") %
+ {'0': reply[0], '1': reply[1]})
+
+ # Replicate the sleep call in the real function
+ greenthread.sleep(0)
+ return reply
+
+
+def stub_out_utils_execute(stubs):
+ fake_execute_set_repliers([])
+ fake_execute_clear_log()
+ stubs.Set(utils, 'execute', fake_execute)
diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py
index 2196257bd..f91f37d4b 100644
--- a/nova/tests/test_xenapi.py
+++ b/nova/tests/test_xenapi.py
@@ -19,11 +19,15 @@ Test suite for XenAPI
"""
import functools
+import os
+import re
import stubout
+import ast
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
@@ -40,7 +44,7 @@ from nova.tests.xenapi import stubs
from nova.tests.glance import stubs as glance_stubs
from nova.tests import fake_utils
-from nova import log as LOG
+LOG = logging.getLogger('nova.tests.test_xenapi')
FLAGS = flags.FLAGS
@@ -95,7 +99,7 @@ class XenAPIVolumeTestCase(test.TestCase):
vol['availability_zone'] = FLAGS.storage_availability_zone
vol['status'] = "creating"
vol['attach_status'] = "detached"
- return db.volume_create(context.get_admin_context(), vol)
+ return db.volume_create(self.context, vol)
def test_create_iscsi_storage(self):
""" This shows how to test helper classes' methods """
@@ -251,7 +255,6 @@ class XenAPIVMTestCase(test.TestCase):
# Get Nova record for VM
vm_info = conn.get_info(instance_id)
-
# Get XenAPI record for VM
vms = [rec for ref, rec
in xenapi_fake.get_all_records('VM').iteritems()
@@ -260,7 +263,7 @@ class XenAPIVMTestCase(test.TestCase):
self.vm_info = vm_info
self.vm = vm
- def check_vm_record(self, conn):
+ def check_vm_record(self, conn, check_injection=False):
# Check that m1.large above turned into the right thing.
instance_type = db.instance_type_get_by_name(conn, 'm1.large')
mem_kib = long(instance_type['memory_mb']) << 10
@@ -280,6 +283,25 @@ class XenAPIVMTestCase(test.TestCase):
# Check that the VM is running according to XenAPI.
self.assertEquals(self.vm['power_state'], 'Running')
+ if check_injection:
+ xenstore_data = self.vm['xenstore_data']
+ key = 'vm-data/networking/aabbccddeeff'
+ xenstore_value = xenstore_data[key]
+ tcpip_data = ast.literal_eval(xenstore_value)
+ self.assertEquals(tcpip_data, {
+ 'label': 'fake_flat_network',
+ 'broadcast': '10.0.0.255',
+ 'ips': [{'ip': '10.0.0.3',
+ 'netmask':'255.255.255.0',
+ 'enabled':'1'}],
+ 'ip6s': [{'ip': 'fe80::a8bb:ccff:fedd:eeff',
+ 'netmask': '120',
+ 'enabled': '1',
+ 'gateway': 'fe80::a00:1'}],
+ 'mac': 'aa:bb:cc:dd:ee:ff',
+ 'dns': ['10.0.0.2'],
+ 'gateway': '10.0.0.1'})
+
def check_vm_params_for_windows(self):
self.assertEquals(self.vm['platform']['nx'], 'true')
self.assertEquals(self.vm['HVM_boot_params'], {'order': 'dc'})
@@ -313,7 +335,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", instance_id=1):
+ instance_type="m1.large", os_type="linux",
+ instance_id=1, check_injection=False):
stubs.stubout_loopingcall_start(self.stubs)
values = {'id': instance_id,
'project_id': self.project.id,
@@ -327,7 +350,7 @@ class XenAPIVMTestCase(test.TestCase):
instance = db.instance_create(self.context, values)
self.conn.spawn(instance)
self.create_vm_record(self.conn, os_type, instance_id)
- self.check_vm_record(self.conn)
+ self.check_vm_record(self.conn, check_injection)
def test_spawn_not_enough_memory(self):
FLAGS.xenapi_image_service = 'glance'
@@ -368,6 +391,85 @@ class XenAPIVMTestCase(test.TestCase):
glance_stubs.FakeGlance.IMAGE_RAMDISK)
self.check_vm_params_for_linux_with_external_kernel()
+ def test_spawn_netinject_file(self):
+ FLAGS.xenapi_image_service = 'glance'
+ db_fakes.stub_out_db_instance_api(self.stubs, injected=True)
+
+ self._tee_executed = False
+
+ def _tee_handler(cmd, **kwargs):
+ input = kwargs.get('process_input', None)
+ 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),
+ ])
+ FLAGS.xenapi_image_service = 'glance'
+ self._test_spawn(glance_stubs.FakeGlance.IMAGE_MACHINE,
+ glance_stubs.FakeGlance.IMAGE_KERNEL,
+ glance_stubs.FakeGlance.IMAGE_RAMDISK,
+ check_injection=True)
+ self.assertTrue(self._tee_executed)
+
+ def test_spawn_netinject_xenstore(self):
+ FLAGS.xenapi_image_service = 'glance'
+ db_fakes.stub_out_db_instance_api(self.stubs, injected=True)
+
+ self._tee_executed = False
+
+ def _mount_handler(cmd, *ignore_args, **ignore_kwargs):
+ # When mounting, create real files under the mountpoint to simulate
+ # files in the mounted filesystem
+
+ # mount point will be the last item of the command list
+ self._tmpdir = cmd[len(cmd) - 1]
+ 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, **ignore_kwargs):
+ # 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, *ignore_args, **ignore_kwargs):
+ 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 test_spawn_vlanmanager(self):
self.flags(xenapi_image_service='glance',
network_manager='nova.network.manager.VlanManager',
@@ -399,6 +501,7 @@ class XenAPIVMTestCase(test.TestCase):
str(4 * 1024))
def test_rescue(self):
+ self.flags(xenapi_inject_image=False)
instance = self._create_instance()
conn = xenapi_conn.get_connection(False)
conn.rescue(instance, None)
@@ -492,6 +595,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,
diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py
index 7a2a2c3e2..205f6c902 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 9abe44cc3..25e4f54a9 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')
flags.DEFINE_integer('timeout_nbd', 10,
'time to wait for a NBD device coming up')
flags.DEFINE_integer('max_nbd_devices', 16,
@@ -97,11 +102,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, utils.execute)
finally:
# unmount device
utils.execute('sudo', 'umount', mapped_device)
@@ -164,7 +165,18 @@ def _free_device(device):
_DEVICES.append(device)
-def _inject_key_into_fs(key, fs):
+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, execute=None):
"""Add the given public ssh key to root's authorized_keys.
key is an ssh key string.
@@ -179,7 +191,7 @@ def _inject_key_into_fs(key, fs):
process_input='\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 2cecb010d..5962507d7 100644
--- a/nova/virt/libvirt_conn.py
+++ b/nova/virt/libvirt_conn.py
@@ -76,9 +76,7 @@ flags.DECLARE('live_migration_retry_count', 'nova.compute.manager')
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')
diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py
index c1d7f5ae7..6d17a642e 100644
--- a/nova/virt/xenapi/fake.py
+++ b/nova/virt/xenapi/fake.py
@@ -168,6 +168,12 @@ def after_VBD_create(vbd_ref, vbd_rec):
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,
@@ -340,6 +346,10 @@ class SessionBase(object):
return
db_ref['xenstore_data'][key] = None
+ def network_get_all_records_where(self, _1, _2):
+ # TODO (salvatore-orlando):filter table on _2
+ return _db_content['network']
+
def VM_add_to_xenstore_data(self, _1, vm_ref, key, value):
db_ref = _db_content['VM'][vm_ref]
if not 'xenstore_data' in db_ref:
@@ -439,7 +449,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)
@@ -462,10 +471,11 @@ 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:
+ raise Failure(['HANDLE_INVALID', cls, ref])
LOG.debug(_('Raising NotImplemented'))
raise NotImplementedError(
@@ -543,7 +553,7 @@ class SessionBase(object):
def _check_session(self, params):
if (self._session is None or
self._session not in _db_content['session']):
- raise Failure(['HANDLE_INVALID', 'session', self._session])
+ raise Failure(['HANDLE_INVALID', 'session', self._session])
if len(params) == 0 or params[0] != self._session:
LOG.debug(_('Raising NotImplemented'))
raise NotImplementedError('Call to XenAPI without using .xenapi')
diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py
index c30e4b2d1..2288ea8a5 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
import uuid
@@ -29,6 +30,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
@@ -36,6 +39,7 @@ from nova import utils
from nova.auth.manager import AuthManager
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.xenapi.volume_utils import StorageError
@@ -670,6 +674,23 @@ class VMHelper(HelperBase):
return None
@classmethod
+ def preconfigure_instance(cls, session, instance, vdi_ref, network_info):
+ """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
+ mount_required = False
+ 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))
+
+ @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:
@@ -927,6 +948,7 @@ def vbd_unplug_with_retry(session, vbd_ref):
e.details[0] == 'DEVICE_DETACH_REJECTED'):
LOG.debug(_('VBD.unplug rejected: retrying...'))
time.sleep(1)
+ LOG.debug(_('Not sleeping anymore!'))
elif (len(e.details) > 0 and
e.details[0] == 'DEVICE_ALREADY_DETACHED'):
LOG.debug(_('VBD.unplug successful eventually.'))
@@ -1002,3 +1024,114 @@ 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):
+ """
+ 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
+ # 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)
+
+
+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
+ if networks_info:
+ ifc_num = -1
+ interfaces_info = []
+ for (network_ref, info) in networks_info:
+ ifc_num += 1
+ if not network_ref['injected']:
+ continue
+
+ ip_v4 = ip_v6 = None
+ if 'ips' in info and len(info['ips']) > 0:
+ ip_v4 = info['ips'][0]
+ if 'ip6s' in info and len(info['ip6s']) > 0:
+ ip_v6 = info['ip6s'][0]
+ if len(info['dns']) > 0:
+ dns = info['dns'][0]
+ interface_info = {'name': 'eth%d' % ifc_num,
+ 'address': ip_v4 and ip_v4['ip'] or '',
+ 'netmask': ip_v4 and ip_v4['netmask'] or '',
+ 'gateway': info['gateway'],
+ 'broadcast': 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}
+ interfaces_info.append(interface_info)
+ net = str(template(template_data,
+ searchList=[{'interfaces': interfaces_info,
+ 'use_ipv6': FLAGS.use_ipv6}]))
+ return key, net
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index 2e1f3ee91..52e1f9eba 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -33,6 +33,7 @@ from nova import context
from nova import log as logging
from nova import exception
from nova import utils
+from nova import flags
from nova.auth.manager import AuthManager
from nova.compute import power_state
@@ -43,6 +44,7 @@ from nova.virt.xenapi.vm_utils import ImageType
XenAPI = None
LOG = logging.getLogger("nova.virt.xenapi.vmops")
+FLAGS = flags.FLAGS
class VMOps(object):
@@ -53,7 +55,6 @@ class VMOps(object):
self.XenAPI = session.get_imported_xenapi()
self._session = session
self.poll_rescue_last_ran = None
-
VMHelper.XenAPI = self.XenAPI
def list_instances(self):
@@ -168,6 +169,12 @@ class VMOps(object):
# 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
@@ -237,26 +244,17 @@ 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, must be an instance name
+ # wasn't an opaque ref, can be an instance name
instance_name = instance_or_vm
# if instance_or_vm is an int/long it must be instance id
elif 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
-
- # otherwise instance_or_vm is an instance object
+ instance_obj = db.instance_get(ctx, instance_or_vm)
+ instance_name = instance_obj.name
else:
instance_name = instance_or_vm.name
vm_ref = VMHelper.lookup(self._session, instance_name)
@@ -692,7 +690,6 @@ class VMOps(object):
vm_ref = VMHelper.lookup(self._session, instance.name)
self._shutdown(instance, vm_ref)
self._acquire_bootlock(vm_ref)
-
instance._rescue = True
self.spawn_rescue(instance)
rescue_vm_ref = VMHelper.lookup(self._session, instance.name)
@@ -816,6 +813,7 @@ class VMOps(object):
info = {
'label': network['label'],
'gateway': network['gateway'],
+ 'broadcast': network['broadcast'],
'mac': instance.mac_address,
'rxtx_cap': flavor['rxtx_cap'],
'dns': [network['dns']],
diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py
index f20fb29d8..99fd35c61 100644
--- a/nova/virt/xenapi_conn.py
+++ b/nova/virt/xenapi_conn.py
@@ -107,8 +107,22 @@ 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_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_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 is 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')
+
flags.DEFINE_string('target_host',
None,
'iSCSI Target Host')