summaryrefslogtreecommitdiffstats
path: root/nova/virt
diff options
context:
space:
mode:
authorJustin Santa Barbara <justin@fathomdb.com>2010-08-18 22:14:24 +0100
committerJustin Santa Barbara <justin@fathomdb.com>2010-08-18 22:14:24 +0100
commitd8f8d121a00173cb3f5fb5e496cc010dc179cf19 (patch)
tree79ae8953421d065a13309ae5052a0faaee94b485 /nova/virt
parent993563b6cc9db9f24480678cf8b2d0750aee7a92 (diff)
parent2af3bad97be40c135fb73f2e595e7fda86f17900 (diff)
Merged with trunk
Diffstat (limited to 'nova/virt')
-rw-r--r--nova/virt/connection.py9
-rw-r--r--nova/virt/fake.py144
-rw-r--r--nova/virt/images.py20
-rw-r--r--nova/virt/interfaces.template18
-rw-r--r--nova/virt/libvirt.qemu.xml.template30
-rw-r--r--nova/virt/libvirt.uml.xml.template25
-rw-r--r--nova/virt/libvirt_conn.py84
-rw-r--r--nova/virt/xenapi.py169
8 files changed, 428 insertions, 71 deletions
diff --git a/nova/virt/connection.py b/nova/virt/connection.py
index 004adb19d..90bc7fa0a 100644
--- a/nova/virt/connection.py
+++ b/nova/virt/connection.py
@@ -27,6 +27,15 @@ FLAGS = flags.FLAGS
def get_connection(read_only=False):
+ """Returns an object representing the connection to a virtualization
+ platform. This could be nova.virt.fake.FakeConnection in test mode,
+ a connection to KVM or QEMU via libvirt, or a connection to XenServer
+ or Xen Cloud Platform via XenAPI.
+
+ Any object returned here must conform to the interface documented by
+ FakeConnection.
+ """
+
# TODO(termie): maybe lazy load after initial check for permissions
# TODO(termie): check whether we can be disconnected
t = FLAGS.connection_type
diff --git a/nova/virt/fake.py b/nova/virt/fake.py
index d9ae5ac96..155833f3f 100644
--- a/nova/virt/fake.py
+++ b/nova/virt/fake.py
@@ -19,10 +19,13 @@
"""
A fake (in-memory) hypervisor+api. Allows nova testing w/o a hypervisor.
+This module also documents the semantics of real hypervisor connections.
"""
import logging
+from twisted.internet import defer
+
from nova.compute import power_state
@@ -32,6 +35,38 @@ def get_connection(_):
class FakeConnection(object):
+ """
+ The interface to this class talks in terms of 'instances' (Amazon EC2 and
+ internal Nova terminology), by which we mean 'running virtual machine'
+ (XenAPI terminology) or domain (Xen or libvirt terminology).
+
+ An instance has an ID, which is the identifier chosen by Nova to represent
+ the instance further up the stack. This is unfortunately also called a
+ 'name' elsewhere. As far as this layer is concerned, 'instance ID' and
+ 'instance name' are synonyms.
+
+ Note that the instance ID or name is not human-readable or
+ customer-controlled -- it's an internal ID chosen by Nova. At the
+ nova.virt layer, instances do not have human-readable names at all -- such
+ things are only known higher up the stack.
+
+ Most virtualization platforms will also have their own identity schemes,
+ to uniquely identify a VM or domain. These IDs must stay internal to the
+ platform-specific layer, and never escape the connection interface. The
+ platform-specific layer is responsible for keeping track of which instance
+ ID maps to which platform-specific ID, and vice versa.
+
+ In contrast, the list_disks and list_interfaces calls may return
+ platform-specific IDs. These identify a specific virtual disk or specific
+ virtual network interface, and these IDs are opaque to the rest of Nova.
+
+ Some methods here take an instance of nova.compute.service.Instance. This
+ is the datastructure used by nova.compute to store details regarding an
+ instance, and pass them into this layer. This layer is responsible for
+ translating that generic datastructure into terms that are specific to the
+ virtualization platform.
+ """
+
def __init__(self):
self.instances = {}
@@ -42,20 +77,70 @@ class FakeConnection(object):
return cls._instance
def list_instances(self):
+ """
+ Return the names of all the instances known to the virtualization
+ layer, as a list.
+ """
return self.instances.keys()
def spawn(self, instance):
+ """
+ Create a new instance/VM/domain on the virtualization platform.
+
+ The given parameter is an instance of nova.compute.service.Instance.
+ This function should use the data there to guide the creation of
+ the new instance.
+
+ The work will be done asynchronously. This function returns a
+ Deferred that allows the caller to detect when it is complete.
+
+ Once this successfully completes, the instance should be
+ running (power_state.RUNNING).
+
+ If this fails, any partial instance should be completely
+ cleaned up, and the virtualization platform should be in the state
+ that it was before this call began.
+ """
+
fake_instance = FakeInstance()
self.instances[instance.name] = fake_instance
fake_instance._state = power_state.RUNNING
+ return defer.succeed(None)
def reboot(self, instance):
- pass
-
+ """
+ Reboot the specified instance.
+
+ The given parameter is an instance of nova.compute.service.Instance,
+ and so the instance is being specified as instance.name.
+
+ The work will be done asynchronously. This function returns a
+ Deferred that allows the caller to detect when it is complete.
+ """
+ return defer.succeed(None)
+
def destroy(self, instance):
+ """
+ Destroy (shutdown and delete) the specified instance.
+
+ The given parameter is an instance of nova.compute.service.Instance,
+ and so the instance is being specified as instance.name.
+
+ The work will be done asynchronously. This function returns a
+ Deferred that allows the caller to detect when it is complete.
+ """
del self.instances[instance.name]
+ return defer.succeed(None)
def get_info(self, instance_id):
+ """
+ Get a block of information about the given instance. This is returned
+ as a dictionary containing 'state': The power_state of the instance,
+ 'max_mem': The maximum memory for the instance, in KiB, 'mem': The
+ current memory the instance has, in KiB, 'num_cpu': The current number
+ of virtual CPUs the instance has, 'cpu_time': The total CPU time used
+ by the instance, in nanoseconds.
+ """
i = self.instances[instance_id]
return {'state': i._state,
'max_mem': 0,
@@ -64,15 +149,70 @@ class FakeConnection(object):
'cpu_time': 0}
def list_disks(self, instance_id):
+ """
+ Return the IDs of all the virtual disks attached to the specified
+ instance, as a list. These IDs are opaque to the caller (they are
+ only useful for giving back to this layer as a parameter to
+ disk_stats). These IDs only need to be unique for a given instance.
+
+ Note that this function takes an instance ID, not a
+ compute.service.Instance, so that it can be called by compute.monitor.
+ """
return ['A_DISK']
def list_interfaces(self, instance_id):
+ """
+ Return the IDs of all the virtual network interfaces attached to the
+ specified instance, as a list. These IDs are opaque to the caller
+ (they are only useful for giving back to this layer as a parameter to
+ interface_stats). These IDs only need to be unique for a given
+ instance.
+
+ Note that this function takes an instance ID, not a
+ compute.service.Instance, so that it can be called by compute.monitor.
+ """
return ['A_VIF']
def block_stats(self, instance_id, disk_id):
+ """
+ Return performance counters associated with the given disk_id on the
+ given instance_id. These are returned as [rd_req, rd_bytes, wr_req,
+ wr_bytes, errs], where rd indicates read, wr indicates write, req is
+ the total number of I/O requests made, bytes is the total number of
+ bytes transferred, and errs is the number of requests held up due to a
+ full pipeline.
+
+ All counters are long integers.
+
+ This method is optional. On some platforms (e.g. XenAPI) performance
+ statistics can be retrieved directly in aggregate form, without Nova
+ having to do the aggregation. On those platforms, this method is
+ unused.
+
+ Note that this function takes an instance ID, not a
+ compute.service.Instance, so that it can be called by compute.monitor.
+ """
return [0L, 0L, 0L, 0L, null]
def interface_stats(self, instance_id, iface_id):
+ """
+ Return performance counters associated with the given iface_id on the
+ given instance_id. These are returned as [rx_bytes, rx_packets,
+ rx_errs, rx_drop, tx_bytes, tx_packets, tx_errs, tx_drop], where rx
+ indicates receive, tx indicates transmit, bytes and packets indicate
+ the total number of bytes or packets transferred, and errs and dropped
+ is the total number of packets failed / dropped.
+
+ All counters are long integers.
+
+ This method is optional. On some platforms (e.g. XenAPI) performance
+ statistics can be retrieved directly in aggregate form, without Nova
+ having to do the aggregation. On those platforms, this method is
+ unused.
+
+ Note that this function takes an instance ID, not a
+ compute.service.Instance, so that it can be called by compute.monitor.
+ """
return [0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L]
diff --git a/nova/virt/images.py b/nova/virt/images.py
index 5096bac0f..a60bcc4c1 100644
--- a/nova/virt/images.py
+++ b/nova/virt/images.py
@@ -23,14 +23,15 @@ Handling of VM disk images.
import os.path
import time
+import urlparse
from nova import flags
from nova import process
-from nova.auth import signer
from nova.auth import manager
+from nova.auth import signer
-FLAGS = flags.FLAGS
+FLAGS = flags.FLAGS
flags.DEFINE_bool('use_s3', True,
'whether to get images from s3 or use local copy')
@@ -42,8 +43,9 @@ def fetch(image, path, user, project):
f = _fetch_local_image
return f(image, path, user, project)
+
def _fetch_s3_image(image, path, user, project):
- url = _image_url('%s/image' % image)
+ url = image_url(image)
# This should probably move somewhere else, like e.g. a download_as
# method on User objects and at the same time get rewritten to use
@@ -51,11 +53,11 @@ def _fetch_s3_image(image, path, user, project):
headers = {}
headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
- uri = '/' + url.partition('/')[2]
+ (_, _, url_path, _, _, _) = urlparse.urlparse(url)
access = manager.AuthManager().get_access_key(user, project)
signature = signer.Signer(user.secret.encode()).s3_authorization(headers,
'GET',
- uri)
+ url_path)
headers['Authorization'] = 'AWS %s:%s' % (access, signature)
cmd = ['/usr/bin/curl', '--fail', '--silent', url]
@@ -65,12 +67,16 @@ def _fetch_s3_image(image, path, user, project):
cmd += ['-o', path]
return process.SharedPool().execute(executable=cmd[0], args=cmd[1:])
+
def _fetch_local_image(image, path, user, project):
source = _image_path('%s/image' % image)
return process.simple_execute('cp %s %s' % (source, path))
+
def _image_path(path):
return os.path.join(FLAGS.images_path, path)
-def _image_url(path):
- return "%s:%s/_images/%s" % (FLAGS.s3_host, FLAGS.s3_port, path)
+
+def image_url(image):
+ return "http://%s:%s/_images/%s/image" % (FLAGS.s3_host, FLAGS.s3_port,
+ image)
diff --git a/nova/virt/interfaces.template b/nova/virt/interfaces.template
new file mode 100644
index 000000000..11df301f6
--- /dev/null
+++ b/nova/virt/interfaces.template
@@ -0,0 +1,18 @@
+# This file describes the network interfaces available on your system
+# and how to activate them. For more information, see interfaces(5).
+
+# The loopback network interface
+auto lo
+iface lo inet loopback
+
+# The primary network interface
+auto eth0
+iface eth0 inet static
+ address %(address)s
+ netmask %(netmask)s
+ network %(network)s
+ broadcast %(broadcast)s
+ gateway %(gateway)s
+ dns-nameservers %(dns)s
+
+
diff --git a/nova/virt/libvirt.qemu.xml.template b/nova/virt/libvirt.qemu.xml.template
new file mode 100644
index 000000000..307f9d03a
--- /dev/null
+++ b/nova/virt/libvirt.qemu.xml.template
@@ -0,0 +1,30 @@
+<domain type='%(type)s'>
+ <name>%(name)s</name>
+ <os>
+ <type>hvm</type>
+ <kernel>%(basepath)s/kernel</kernel>
+ <initrd>%(basepath)s/ramdisk</initrd>
+ <cmdline>root=/dev/vda1 console=ttyS0</cmdline>
+ </os>
+ <features>
+ <acpi/>
+ </features>
+ <memory>%(memory_kb)s</memory>
+ <vcpu>%(vcpus)s</vcpu>
+ <devices>
+ <disk type='file'>
+ <source file='%(basepath)s/disk'/>
+ <target dev='vda' bus='virtio'/>
+ </disk>
+ <interface type='bridge'>
+ <source bridge='%(bridge_name)s'/>
+ <mac address='%(mac_address)s'/>
+ <!-- <model type='virtio'/> CANT RUN virtio network right now -->
+ </interface>
+ <serial type="file">
+ <source path='%(basepath)s/console.log'/>
+ <target port='1'/>
+ </serial>
+ </devices>
+ <nova>%(nova)s</nova>
+</domain>
diff --git a/nova/virt/libvirt.uml.xml.template b/nova/virt/libvirt.uml.xml.template
new file mode 100644
index 000000000..6f4290f98
--- /dev/null
+++ b/nova/virt/libvirt.uml.xml.template
@@ -0,0 +1,25 @@
+<domain type='%(type)s'>
+ <name>%(name)s</name>
+ <memory>%(memory_kb)s</memory>
+ <os>
+ <type>%(type)s</type>
+ <kernel>/usr/bin/linux</kernel>
+ <root>/dev/ubda1</root>
+ </os>
+ <devices>
+ <disk type='file'>
+ <source file='%(basepath)s/disk'/>
+ <target dev='ubd0' bus='uml'/>
+ </disk>
+ <interface type='bridge'>
+ <source bridge='%(bridge_name)s'/>
+ <mac address='%(mac_address)s'/>
+ </interface>
+ <console type="pty" />
+ <serial type="file">
+ <source path='%(basepath)s/console.log'/>
+ <target port='1'/>
+ </serial>
+ </devices>
+ <nova>%(nova)s</nova>
+</domain>
diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py
index 92ca7ef13..524646ee5 100644
--- a/nova/virt/libvirt_conn.py
+++ b/nova/virt/libvirt_conn.py
@@ -42,17 +42,25 @@ from nova.virt import images
libvirt = None
libxml2 = None
+
FLAGS = flags.FLAGS
flags.DEFINE_string('libvirt_xml_template',
- utils.abspath('compute/libvirt.xml.template'),
- 'Libvirt XML Template')
+ utils.abspath('virt/libvirt.qemu.xml.template'),
+ 'Libvirt XML Template for QEmu/KVM')
+flags.DEFINE_string('libvirt_uml_xml_template',
+ utils.abspath('virt/libvirt.uml.xml.template'),
+ 'Libvirt XML Template for user-mode-linux')
flags.DEFINE_string('injected_network_template',
- utils.abspath('compute/interfaces.template'),
+ utils.abspath('virt/interfaces.template'),
'Template file for injected network')
-
flags.DEFINE_string('libvirt_type',
'kvm',
- 'Libvirt domain type (kvm, qemu, etc)')
+ 'Libvirt domain type (valid options are: kvm, qemu, uml)')
+flags.DEFINE_string('libvirt_uri',
+ '',
+ 'Override the default libvirt URI (which is dependent'
+ ' on libvirt_type)')
+
def get_connection(read_only):
# These are loaded late so that there's no need to install these
@@ -68,20 +76,41 @@ def get_connection(read_only):
class LibvirtConnection(object):
def __init__(self, read_only):
+ self.libvirt_uri, template_file = self.get_uri_and_template()
+
+ self.libvirt_xml = open(template_file).read()
+ self._wrapped_conn = None
+ self.read_only = read_only
+
+ @property
+ def _conn(self):
+ if not self._wrapped_conn:
+ self._wrapped_conn = self._connect(self.libvirt_uri, self.read_only)
+ return self._wrapped_conn
+
+ def get_uri_and_template(self):
+ if FLAGS.libvirt_type == 'uml':
+ uri = FLAGS.libvirt_uri or 'uml:///system'
+ template_file = FLAGS.libvirt_uml_xml_template
+ else:
+ uri = FLAGS.libvirt_uri or 'qemu:///system'
+ template_file = FLAGS.libvirt_xml_template
+ return uri, template_file
+
+ def _connect(self, uri, read_only):
auth = [[libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_NOECHOPROMPT],
'root',
None]
+
if read_only:
- self._conn = libvirt.openReadOnly('qemu:///system')
+ return libvirt.openReadOnly(uri)
else:
- self._conn = libvirt.openAuth('qemu:///system', auth, 0)
-
+ return libvirt.openAuth(uri, auth, 0)
def list_instances(self):
return [self._conn.lookupByID(x).name()
for x in self._conn.listDomainsID()]
-
def destroy(self, instance):
try:
virt_dom = self._conn.lookupByName(instance.name)
@@ -110,12 +139,11 @@ class LibvirtConnection(object):
timer.start(interval=0.5, now=True)
return d
-
def _cleanup(self, instance):
target = os.path.abspath(instance.datamodel['basepath'])
logging.info("Deleting instance files at %s", target)
- shutil.rmtree(target)
-
+ if os.path.exists(target):
+ shutil.rmtree(target)
@defer.inlineCallbacks
@exception.wrap_exception
@@ -142,7 +170,6 @@ class LibvirtConnection(object):
timer.start(interval=0.5, now=True)
yield d
-
@defer.inlineCallbacks
@exception.wrap_exception
def spawn(self, instance):
@@ -173,7 +200,6 @@ class LibvirtConnection(object):
timer.start(interval=0.5, now=True)
yield local_d
-
@defer.inlineCallbacks
def _create_image(self, instance, libvirt_xml):
# syntactic nicety
@@ -228,27 +254,23 @@ class LibvirtConnection(object):
yield disk.partition(
basepath('disk-raw'), basepath('disk'), bytes, execute=execute)
-
def basepath(self, instance, path=''):
return os.path.abspath(os.path.join(instance.datamodel['basepath'], path))
-
def toXml(self, instance):
# TODO(termie): cache?
logging.debug("Starting the toXML method")
- libvirt_xml = open(FLAGS.libvirt_xml_template).read()
xml_info = instance.datamodel.copy()
# TODO(joshua): Make this xml express the attached disks as well
# TODO(termie): lazy lazy hack because xml is annoying
xml_info['nova'] = json.dumps(instance.datamodel.copy())
xml_info['type'] = FLAGS.libvirt_type
- libvirt_xml = libvirt_xml % xml_info
+ libvirt_xml = self.libvirt_xml % xml_info
logging.debug("Finished the toXML method")
return libvirt_xml
-
def get_info(self, instance_id):
virt_dom = self._conn.lookupByName(instance_id)
(state, max_mem, mem, num_cpu, cpu_time) = virt_dom.info()
@@ -258,14 +280,7 @@ class LibvirtConnection(object):
'num_cpu': num_cpu,
'cpu_time': cpu_time}
-
def get_disks(self, instance_id):
- """
- Note that this function takes an instance ID, not an Instance, so
- that it can be called by monitor.
-
- Returns a list of all block devices for this domain.
- """
domain = self._conn.lookupByName(instance_id)
# TODO(devcamcar): Replace libxml2 with etree.
xml = domain.XMLDesc(0)
@@ -301,14 +316,7 @@ class LibvirtConnection(object):
return disks
-
def get_interfaces(self, instance_id):
- """
- Note that this function takes an instance ID, not an Instance, so
- that it can be called by monitor.
-
- Returns a list of all network interfaces for this instance.
- """
domain = self._conn.lookupByName(instance_id)
# TODO(devcamcar): Replace libxml2 with etree.
xml = domain.XMLDesc(0)
@@ -344,20 +352,10 @@ class LibvirtConnection(object):
return interfaces
-
def block_stats(self, instance_id, disk):
- """
- Note that this function takes an instance ID, not an Instance, so
- that it can be called by monitor.
- """
domain = self._conn.lookupByName(instance_id)
return domain.blockStats(disk)
-
def interface_stats(self, instance_id, interface):
- """
- Note that this function takes an instance ID, not an Instance, so
- that it can be called by monitor.
- """
domain = self._conn.lookupByName(instance_id)
return domain.interfaceStats(interface)
diff --git a/nova/virt/xenapi.py b/nova/virt/xenapi.py
index dc372e3e3..2f5994983 100644
--- a/nova/virt/xenapi.py
+++ b/nova/virt/xenapi.py
@@ -19,6 +19,7 @@ A connection to XenServer or Xen Cloud Platform.
"""
import logging
+import xmlrpclib
from twisted.internet import defer
from twisted.internet import task
@@ -26,20 +27,35 @@ from twisted.internet import task
from nova import exception
from nova import flags
from nova import process
+from nova.auth.manager import AuthManager
from nova.compute import power_state
+from nova.virt import images
XenAPI = None
+
FLAGS = flags.FLAGS
flags.DEFINE_string('xenapi_connection_url',
None,
- 'URL for connection to XenServer/Xen Cloud Platform. Required if connection_type=xenapi.')
+ 'URL for connection to XenServer/Xen Cloud Platform.'
+ ' Required if connection_type=xenapi.')
flags.DEFINE_string('xenapi_connection_username',
'root',
- 'Username for connection to XenServer/Xen Cloud Platform. Used only if connection_type=xenapi.')
+ 'Username for connection to XenServer/Xen Cloud Platform.'
+ ' Used only if connection_type=xenapi.')
flags.DEFINE_string('xenapi_connection_password',
None,
- 'Password for connection to XenServer/Xen Cloud Platform. Used only if connection_type=xenapi.')
+ 'Password for connection to XenServer/Xen Cloud Platform.'
+ ' Used only if connection_type=xenapi.')
+
+
+XENAPI_POWER_STATE = {
+ 'Halted' : power_state.SHUTDOWN,
+ 'Running' : power_state.RUNNING,
+ 'Paused' : power_state.PAUSED,
+ 'Suspended': power_state.SHUTDOWN, # FIXME
+ 'Crashed' : power_state.CRASHED
+}
def get_connection(_):
@@ -59,7 +75,6 @@ def get_connection(_):
class XenAPIConnection(object):
-
def __init__(self, url, user, pw):
self._conn = XenAPI.Session(url)
self._conn.login_with_password(user, pw)
@@ -71,10 +86,40 @@ class XenAPIConnection(object):
@defer.inlineCallbacks
@exception.wrap_exception
def spawn(self, instance):
- vm = self.lookup(instance.name)
+ vm = yield self.lookup(instance.name)
if vm is not None:
raise Exception('Attempted to create non-unique name %s' %
instance.name)
+
+ if 'bridge_name' in instance.datamodel:
+ network_ref = \
+ yield self._find_network_with_bridge(
+ instance.datamodel['bridge_name'])
+ else:
+ network_ref = None
+
+ if 'mac_address' in instance.datamodel:
+ mac_address = instance.datamodel['mac_address']
+ else:
+ mac_address = ''
+
+ user = AuthManager().get_user(instance.datamodel['user_id'])
+ project = AuthManager().get_project(instance.datamodel['project_id'])
+ vdi_uuid = yield self.fetch_image(
+ instance.datamodel['image_id'], user, project, True)
+ kernel = yield self.fetch_image(
+ instance.datamodel['kernel_id'], user, project, False)
+ ramdisk = yield self.fetch_image(
+ instance.datamodel['ramdisk_id'], user, project, False)
+ vdi_ref = yield self._conn.xenapi.VDI.get_by_uuid(vdi_uuid)
+
+ vm_ref = yield self.create_vm(instance, kernel, ramdisk)
+ yield self.create_vbd(vm_ref, vdi_ref, 0, True)
+ if network_ref:
+ yield self._create_vif(vm_ref, network_ref, mac_address)
+ yield self._conn.xenapi.VM.start(vm_ref, False, False)
+
+ def create_vm(self, instance, kernel, ramdisk):
mem = str(long(instance.datamodel['memory_kb']) * 1024)
vcpus = str(instance.datamodel['vcpus'])
rec = {
@@ -92,9 +137,9 @@ class XenAPIConnection(object):
'actions_after_reboot': 'restart',
'actions_after_crash': 'destroy',
'PV_bootloader': '',
- 'PV_kernel': instance.datamodel['kernel_id'],
- 'PV_ramdisk': instance.datamodel['ramdisk_id'],
- 'PV_args': '',
+ 'PV_kernel': kernel,
+ 'PV_ramdisk': ramdisk,
+ 'PV_args': 'root=/dev/xvda1',
'PV_bootloader_args': '',
'PV_legacy_args': '',
'HVM_boot_policy': '',
@@ -106,9 +151,74 @@ class XenAPIConnection(object):
'user_version': '0',
'other_config': {},
}
- vm = yield self._conn.xenapi.VM.create(rec)
- #yield self._conn.xenapi.VM.start(vm, False, False)
+ logging.debug('Created VM %s...', instance.name)
+ vm_ref = self._conn.xenapi.VM.create(rec)
+ logging.debug('Created VM %s as %s.', instance.name, vm_ref)
+ return vm_ref
+
+ def create_vbd(self, vm_ref, vdi_ref, userdevice, bootable):
+ vbd_rec = {}
+ vbd_rec['VM'] = vm_ref
+ vbd_rec['VDI'] = vdi_ref
+ vbd_rec['userdevice'] = str(userdevice)
+ vbd_rec['bootable'] = bootable
+ vbd_rec['mode'] = 'RW'
+ vbd_rec['type'] = 'disk'
+ vbd_rec['unpluggable'] = True
+ vbd_rec['empty'] = False
+ vbd_rec['other_config'] = {}
+ vbd_rec['qos_algorithm_type'] = ''
+ vbd_rec['qos_algorithm_params'] = {}
+ vbd_rec['qos_supported_algorithms'] = []
+ logging.debug('Creating VBD for VM %s, VDI %s ... ', vm_ref, vdi_ref)
+ vbd_ref = self._conn.xenapi.VBD.create(vbd_rec)
+ logging.debug('Created VBD %s for VM %s, VDI %s.', vbd_ref, vm_ref,
+ vdi_ref)
+ return vbd_ref
+
+ def _create_vif(self, vm_ref, network_ref, mac_address):
+ vif_rec = {}
+ vif_rec['device'] = '0'
+ vif_rec['network']= network_ref
+ vif_rec['VM'] = vm_ref
+ vif_rec['MAC'] = mac_address
+ vif_rec['MTU'] = '1500'
+ vif_rec['other_config'] = {}
+ vif_rec['qos_algorithm_type'] = ''
+ vif_rec['qos_algorithm_params'] = {}
+ logging.debug('Creating VIF for VM %s, network %s ... ', vm_ref,
+ network_ref)
+ vif_ref = self._conn.xenapi.VIF.create(vif_rec)
+ logging.debug('Created VIF %s for VM %s, network %s.', vif_ref,
+ vm_ref, network_ref)
+ return vif_ref
+
+ def _find_network_with_bridge(self, bridge):
+ expr = 'field "bridge" = "%s"' % bridge
+ networks = self._conn.xenapi.network.get_all_records_where(expr)
+ if len(networks) == 1:
+ return networks.keys()[0]
+ elif len(networks) > 1:
+ raise Exception('Found non-unique network for bridge %s' % bridge)
+ else:
+ raise Exception('Found no network for bridge %s' % bridge)
+
+ def fetch_image(self, image, user, project, use_sr):
+ """use_sr: True to put the image as a VDI in an SR, False to place
+ it on dom0's filesystem. The former is for VM disks, the latter for
+ its kernel and ramdisk (if external kernels are being used)."""
+ url = images.image_url(image)
+ access = AuthManager().get_access_key(user, project)
+ logging.debug("Asking xapi to fetch %s as %s" % (url, access))
+ fn = use_sr and 'get_vdi' or 'get_kernel'
+ args = {}
+ args['src_url'] = url
+ args['username'] = access
+ args['password'] = user.secret
+ if use_sr:
+ args['add_partition'] = 'true'
+ return self._call_plugin('objectstore', fn, args)
def reboot(self, instance):
vm = self.lookup(instance.name)
@@ -125,9 +235,9 @@ class XenAPIConnection(object):
def get_info(self, instance_id):
vm = self.lookup(instance_id)
if vm is None:
- raise Exception('instance not present %s' % instance.name)
+ raise Exception('instance not present %s' % instance_id)
rec = self._conn.xenapi.VM.get_record(vm)
- return {'state': power_state_from_xenapi[rec['power_state']],
+ return {'state': XENAPI_POWER_STATE[rec['power_state']],
'max_mem': long(rec['memory_static_max']) >> 10,
'mem': long(rec['memory_dynamic_max']) >> 10,
'num_cpu': rec['VCPUs_max'],
@@ -143,10 +253,31 @@ class XenAPIConnection(object):
else:
return vms[0]
- power_state_from_xenapi = {
- 'Halted' : power_state.RUNNING, #FIXME
- 'Running' : power_state.RUNNING,
- 'Paused' : power_state.PAUSED,
- 'Suspended': power_state.SHUTDOWN, # FIXME
- 'Crashed' : power_state.CRASHED
- }
+ def _call_plugin(self, plugin, fn, args):
+ return _unwrap_plugin_exceptions(
+ self._conn.xenapi.host.call_plugin,
+ self._get_xenapi_host(), plugin, fn, args)
+
+ def _get_xenapi_host(self):
+ return self._conn.xenapi.session.get_this_host(self._conn.handle)
+
+
+def _unwrap_plugin_exceptions(func, *args, **kwargs):
+ try:
+ return func(*args, **kwargs)
+ except XenAPI.Failure, exn:
+ logging.debug("Got exception: %s", exn)
+ if (len(exn.details) == 4 and
+ exn.details[0] == 'XENAPI_PLUGIN_EXCEPTION' and
+ exn.details[2] == 'Failure'):
+ params = None
+ try:
+ params = eval(exn.details[3])
+ except:
+ raise exn
+ raise XenAPI.Failure(params)
+ else:
+ raise
+ except xmlrpclib.ProtocolError, exn:
+ logging.debug("Got exception: %s", exn)
+ raise