summaryrefslogtreecommitdiffstats
path: root/nova/virt
diff options
context:
space:
mode:
authorsateesh <sateesh.chodapuneedi@citrix.com>2011-02-27 08:07:24 +0530
committersateesh <sateesh.chodapuneedi@citrix.com>2011-02-27 08:07:24 +0530
commit1e5008368a84ed9cdc672b69f18cb3eeddf57e6f (patch)
treea2c30cb374f3604e1fcbc87ad2fd7f7ab93e0ec9 /nova/virt
parentc0bcd792fcf96e79ddbaa19952e9ab397db91503 (diff)
parentedf5da85648659b1a7ad105248d69ef9f8c977e4 (diff)
Rebased to Nova revision 749.
Diffstat (limited to 'nova/virt')
-rw-r--r--nova/virt/disk.py8
-rw-r--r--nova/virt/fake.py19
-rw-r--r--nova/virt/xenapi/vm_utils.py27
-rw-r--r--nova/virt/xenapi/vmops.py143
-rw-r--r--nova/virt/xenapi_conn.py14
5 files changed, 188 insertions, 23 deletions
diff --git a/nova/virt/disk.py b/nova/virt/disk.py
index c5565abfa..2bded07a4 100644
--- a/nova/virt/disk.py
+++ b/nova/virt/disk.py
@@ -38,6 +38,10 @@ 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_integer('timeout_nbd', 10,
+ 'time to wait for a NBD device coming up')
+flags.DEFINE_integer('max_nbd_devices', 16,
+ 'maximum number of possible nbd devices')
def extend(image, size):
@@ -117,7 +121,7 @@ def _link_device(image, nbd):
utils.execute('sudo qemu-nbd -c %s %s' % (device, image))
# NOTE(vish): this forks into another process, so give it a chance
# to set up before continuuing
- for i in xrange(10):
+ for i in xrange(FLAGS.timeout_nbd):
if os.path.exists("/sys/block/%s/pid" % os.path.basename(device)):
return device
time.sleep(1)
@@ -139,7 +143,7 @@ def _unlink_device(device, nbd):
utils.execute('sudo losetup --detach %s' % device)
-_DEVICES = ['/dev/nbd%s' % i for i in xrange(16)]
+_DEVICES = ['/dev/nbd%s' % i for i in xrange(FLAGS.max_nbd_devices)]
def _allocate_device():
diff --git a/nova/virt/fake.py b/nova/virt/fake.py
index 161445b86..4346dffc1 100644
--- a/nova/virt/fake.py
+++ b/nova/virt/fake.py
@@ -152,6 +152,21 @@ class FakeConnection(object):
"""
pass
+ def inject_file(self, instance, b64_path, b64_contents):
+ """
+ Writes a file on the specified instance.
+
+ The first parameter is an instance of nova.compute.service.Instance,
+ and so the instance is being specified as instance.name. The second
+ parameter is the base64-encoded path to which the file is to be
+ written on the instance; the third is the contents of the file, also
+ base64-encoded.
+
+ The work will be done asynchronously. This function returns a
+ task that allows the caller to detect when it is complete.
+ """
+ pass
+
def rescue(self, instance):
"""
Rescue the specified instance.
@@ -304,7 +319,9 @@ class FakeConnection(object):
return 'FAKE CONSOLE OUTPUT'
def get_ajax_console(self, instance):
- return 'http://fakeajaxconsole.com/?token=FAKETOKEN'
+ return {'token': 'FAKETOKEN',
+ 'host': 'fakeajaxconsole.com',
+ 'port': 6969}
def get_console_pool_info(self, console_type):
return {'address': '127.0.0.1',
diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py
index 80cc3035d..564a25057 100644
--- a/nova/virt/xenapi/vm_utils.py
+++ b/nova/virt/xenapi/vm_utils.py
@@ -400,19 +400,7 @@ class VMHelper(HelperBase):
@classmethod
def _lookup_image_glance(cls, session, vdi_ref):
LOG.debug(_("Looking up vdi %s for PV kernel"), vdi_ref)
-
- def is_vdi_pv(dev):
- LOG.debug(_("Running pygrub against %s"), dev)
- output = os.popen('pygrub -qn /dev/%s' % dev)
- for line in output.readlines():
- #try to find kernel string
- m = re.search('(?<=kernel:)/.*(?:>)', line)
- if m and m.group(0).find('xen') != -1:
- LOG.debug(_("Found Xen kernel %s") % m.group(0))
- return True
- LOG.debug(_("No Xen kernel found. Booting HVM."))
- return False
- return with_vdi_attached_here(session, vdi_ref, True, is_vdi_pv)
+ return with_vdi_attached_here(session, vdi_ref, True, _is_vdi_pv)
@classmethod
def lookup(cls, session, i):
@@ -714,6 +702,19 @@ def get_this_vm_ref(session):
return session.get_xenapi().VM.get_by_uuid(get_this_vm_uuid())
+def _is_vdi_pv(dev):
+ LOG.debug(_("Running pygrub against %s"), dev)
+ output = os.popen('pygrub -qn /dev/%s' % dev)
+ for line in output.readlines():
+ #try to find kernel string
+ m = re.search('(?<=kernel:)/.*(?:>)', line)
+ if m and m.group(0).find('xen') != -1:
+ LOG.debug(_("Found Xen kernel %s") % m.group(0))
+ return True
+ LOG.debug(_("No Xen kernel found. Booting HVM."))
+ return False
+
+
def _stream_disk(dev, type, virtual_size, image_file):
offset = 0
if type == ImageType.DISK:
diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py
index 98f8ab46e..2aa0dde70 100644
--- a/nova/virt/xenapi/vmops.py
+++ b/nova/virt/xenapi/vmops.py
@@ -66,6 +66,7 @@ class VMOps(object):
if vm is not None:
raise exception.Duplicate(_('Attempted to create'
' non-unique name %s') % instance.name)
+
#ensure enough free memory is available
if not VMHelper.ensure_free_mem(self._session, instance):
name = instance['name']
@@ -75,13 +76,10 @@ class VMOps(object):
instance['id'],
power_state.SHUTDOWN)
return
- bridge = db.network_get_by_instance(context.get_admin_context(),
- instance['id'])['bridge']
- network_ref = \
- NetworkHelper.find_network_with_bridge(self._session, bridge)
user = AuthManager().get_user(instance.user_id)
project = AuthManager().get_project(instance.project_id)
+
#if kernel is not present we must download a raw disk
if instance.kernel_id:
disk_image_type = ImageType.DISK
@@ -107,16 +105,34 @@ 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)
+ # inject_network_info and create vifs
+ networks = self.inject_network_info(instance)
+ self.create_vifs(instance, networks)
+
LOG.debug(_('Starting VM %s...'), vm_ref)
self._session.call_xenapi('VM.start', vm_ref, False, False)
instance_name = instance.name
LOG.info(_('Spawning VM %(instance_name)s created %(vm_ref)s.')
% locals())
+ def _inject_onset_files():
+ onset_files = instance.onset_files
+ if onset_files:
+ # Check if this is a JSON-encoded string and convert if needed.
+ if isinstance(onset_files, basestring):
+ try:
+ onset_files = json.loads(onset_files)
+ except ValueError:
+ LOG.exception(_("Invalid value for onset_files: '%s'")
+ % onset_files)
+ onset_files = []
+ # Inject any files, if specified
+ for path, contents in instance.onset_files:
+ LOG.debug(_("Injecting file path: '%s'") % path)
+ self.inject_file(instance, path, contents)
# NOTE(armando): Do we really need to do this in virt?
+ # NOTE(tr3buchet): not sure but wherever we do it, we need to call
+ # reset_network afterwards
timer = utils.LoopingCall(f=None)
def _wait_for_boot():
@@ -127,6 +143,8 @@ class VMOps(object):
if state == power_state.RUNNING:
LOG.debug(_('Instance %s: booted'), instance['name'])
timer.stop()
+ _inject_onset_files()
+ return True
except Exception, exc:
LOG.warn(exc)
LOG.exception(_('instance %s: failed to boot'),
@@ -135,8 +153,13 @@ class VMOps(object):
instance['id'],
power_state.SHUTDOWN)
timer.stop()
+ return False
timer.f = _wait_for_boot
+
+ # call to reset network to configure network from xenstore
+ self.reset_network(instance)
+
return timer.start(interval=0.5, now=True)
def _get_vm_opaque_ref(self, instance_or_vm):
@@ -264,6 +287,32 @@ class VMOps(object):
raise RuntimeError(resp_dict['message'])
return resp_dict['message']
+ def inject_file(self, instance, b64_path, b64_contents):
+ """Write a file to the VM instance. The path to which it is to be
+ written and the contents of the file need to be supplied; both should
+ be base64-encoded to prevent errors with non-ASCII characters being
+ transmitted. If the agent does not support file injection, or the user
+ has disabled it, a NotImplementedError will be raised.
+ """
+ # Files/paths *should* be base64-encoded at this point, but
+ # double-check to make sure.
+ b64_path = utils.ensure_b64_encoding(b64_path)
+ b64_contents = utils.ensure_b64_encoding(b64_contents)
+
+ # Need to uniquely identify this request.
+ transaction_id = str(uuid.uuid4())
+ args = {'id': transaction_id, 'b64_path': b64_path,
+ 'b64_contents': b64_contents}
+ # If the agent doesn't support file injection, a NotImplementedError
+ # will be raised with the appropriate message.
+ resp = self._make_agent_call('inject_file', instance, '', args)
+ resp_dict = json.loads(resp)
+ if resp_dict['returncode'] != '0':
+ # There was some other sort of error; the message will contain
+ # a description of the error.
+ raise RuntimeError(resp_dict['message'])
+ return resp_dict['message']
+
def _shutdown(self, instance, vm):
"""Shutdown an instance """
state = self.get_info(instance['name'])['state']
@@ -398,6 +447,81 @@ class VMOps(object):
# TODO: implement this!
return 'http://fakeajaxconsole/fake_url'
+ def inject_network_info(self, instance):
+ """
+ Generate the network info and make calls to place it into the
+ xenstore and the xenstore param list
+
+ """
+ # TODO(tr3buchet) - remove comment in multi-nic
+ # I've decided to go ahead and consider multiple IPs and networks
+ # at this stage even though they aren't implemented because these will
+ # be needed for multi-nic and there was no sense writing it for single
+ # network/single IP and then having to turn around and re-write it
+ vm_opaque_ref = self._get_vm_opaque_ref(instance.id)
+ logging.debug(_("injecting network info to xenstore for vm: |%s|"),
+ vm_opaque_ref)
+ admin_context = context.get_admin_context()
+ IPs = db.fixed_ip_get_all_by_instance(admin_context, instance['id'])
+ networks = db.network_get_all_by_instance(admin_context,
+ instance['id'])
+ for network in networks:
+ network_IPs = [ip for ip in IPs if ip.network_id == network.id]
+
+ def ip_dict(ip):
+ return {'netmask': network['netmask'],
+ 'enabled': '1',
+ 'ip': ip.address}
+
+ mac_id = instance.mac_address.replace(':', '')
+ location = 'vm-data/networking/%s' % mac_id
+ mapping = {'label': network['label'],
+ 'gateway': network['gateway'],
+ 'mac': instance.mac_address,
+ 'dns': [network['dns']],
+ 'ips': [ip_dict(ip) for ip in network_IPs]}
+ self.write_to_param_xenstore(vm_opaque_ref, {location: mapping})
+ try:
+ self.write_to_xenstore(vm_opaque_ref, location,
+ mapping['location'])
+ except KeyError:
+ # catch KeyError for domid if instance isn't running
+ pass
+
+ return networks
+
+ def create_vifs(self, instance, networks=None):
+ """
+ Creates vifs for an instance
+
+ """
+ vm_opaque_ref = self._get_vm_opaque_ref(instance.id)
+ logging.debug(_("creating vif(s) for vm: |%s|"), vm_opaque_ref)
+ if networks is None:
+ networks = db.network_get_all_by_instance(admin_context,
+ instance['id'])
+ # TODO(tr3buchet) - remove comment in multi-nic
+ # this bit here about creating the vifs will be updated
+ # in multi-nic to handle multiple IPs on the same network
+ # and multiple networks
+ # for now it works as there is only one of each
+ for network in networks:
+ bridge = network['bridge']
+ network_ref = \
+ NetworkHelper.find_network_with_bridge(self._session, bridge)
+
+ if network_ref:
+ VMHelper.create_vif(self._session, vm_opaque_ref,
+ network_ref, instance.mac_address)
+
+ def reset_network(self, instance):
+ """
+ Creates uuid arg to pass to make_agent_call and calls it.
+
+ """
+ args = {'id': str(uuid.uuid4())}
+ resp = self._make_agent_call('resetnetwork', instance, '', args)
+
def list_from_xenstore(self, vm, path):
"""Runs the xenstore-ls command to get a listing of all records
from 'path' downward. Returns a dict with the sub-paths as keys,
@@ -467,6 +591,11 @@ class VMOps(object):
if 'TIMEOUT:' in err_msg:
LOG.error(_('TIMEOUT: The call to %(method)s timed out. '
'VM id=%(instance_id)s; args=%(strargs)s') % locals())
+ elif 'NOT IMPLEMENTED:' in err_msg:
+ LOG.error(_('NOT IMPLEMENTED: The call to %(method)s is not'
+ ' supported by the agent. VM id=%(instance_id)s;'
+ ' args=%(strargs)s') % locals())
+ raise NotImplementedError(err_msg)
else:
LOG.error(_('The call to %(method)s returned an error: %(e)s. '
'VM id=%(instance_id)s; args=%(strargs)s') % locals())
diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py
index a0b0499b8..fc56a4bae 100644
--- a/nova/virt/xenapi_conn.py
+++ b/nova/virt/xenapi_conn.py
@@ -168,6 +168,12 @@ class XenAPIConnection(object):
"""Set the root/admin password on the VM instance"""
self._vmops.set_admin_password(instance, new_pass)
+ def inject_file(self, instance, b64_path, b64_contents):
+ """Create a file on the VM instance. The file path and contents
+ should be base64-encoded.
+ """
+ self._vmops.inject_file(instance, b64_path, b64_contents)
+
def destroy(self, instance):
"""Destroy VM instance"""
self._vmops.destroy(instance)
@@ -188,6 +194,14 @@ class XenAPIConnection(object):
"""resume the specified instance"""
self._vmops.resume(instance, callback)
+ def reset_network(self, instance):
+ """reset networking for specified instance"""
+ self._vmops.reset_network(instance)
+
+ def inject_network_info(self, instance):
+ """inject network info for specified instance"""
+ self._vmops.inject_network_info(instance)
+
def get_info(self, instance_id):
"""Return data about VM instance"""
return self._vmops.get_info(instance_id)