summaryrefslogtreecommitdiffstats
path: root/nova/virt
diff options
context:
space:
mode:
authorChuck Short <zulcss@ubuntu.com>2011-03-25 16:06:38 -0400
committerChuck Short <zulcss@ubuntu.com>2011-03-25 16:06:38 -0400
commit12581e79bd914fdb2adb2f5a8e1e4db21bdbcd2d (patch)
tree40cdf45298e04ab80c2cba6066b16899c4033ee9 /nova/virt
parentc8fb0c5a16852afc98349edf89bb31afac166749 (diff)
parented12a2cd2beef77d1c7e9d16771e766aa068530d (diff)
merged trunk
Diffstat (limited to 'nova/virt')
-rw-r--r--nova/virt/disk.py26
-rw-r--r--nova/virt/libvirt_conn.py4
-rw-r--r--nova/virt/xenapi/fake.py37
-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
6 files changed, 211 insertions, 31 deletions
diff --git a/nova/virt/disk.py b/nova/virt/disk.py
index b9a8fb7e7..fd45e2c51 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)
@@ -196,7 +197,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.
@@ -211,7 +223,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 bfb0a75f1..c0e7384a3 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 ba12d4d3a..18d558058 100644
--- a/nova/virt/xenapi/fake.py
+++ b/nova/virt/xenapi/fake.py
@@ -162,6 +162,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,
@@ -286,6 +292,25 @@ class SessionBase(object):
rec['currently_attached'] = False
rec['device'] = ''
+ def VM_get_xenstore_data(self, _1, vm_ref):
+ return _db_content['VM'][vm_ref].get('xenstore_data', '')
+
+ def VM_remove_from_xenstore_data(self, _1, vm_ref, key):
+ db_ref = _db_content['VM'][vm_ref]
+ if not 'xenstore_data' in db_ref:
+ return
+ db_ref['xenstore_data'][key] = None
+
+ def 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:
+ db_ref['xenstore_data'] = {}
+ db_ref['xenstore_data'][key] = value
+
def host_compute_free_memory(self, _1, ref):
#Always return 12GB available
return 12 * 1024 * 1024 * 1024
@@ -376,7 +401,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)
@@ -399,10 +423,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(
@@ -476,7 +501,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 419b9ad90..0235e2dc4 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')