diff options
| author | Justin Santa Barbara <justin@fathomdb.com> | 2010-08-18 22:14:24 +0100 |
|---|---|---|
| committer | Justin Santa Barbara <justin@fathomdb.com> | 2010-08-18 22:14:24 +0100 |
| commit | d8f8d121a00173cb3f5fb5e496cc010dc179cf19 (patch) | |
| tree | 79ae8953421d065a13309ae5052a0faaee94b485 /nova/virt | |
| parent | 993563b6cc9db9f24480678cf8b2d0750aee7a92 (diff) | |
| parent | 2af3bad97be40c135fb73f2e595e7fda86f17900 (diff) | |
Merged with trunk
Diffstat (limited to 'nova/virt')
| -rw-r--r-- | nova/virt/connection.py | 9 | ||||
| -rw-r--r-- | nova/virt/fake.py | 144 | ||||
| -rw-r--r-- | nova/virt/images.py | 20 | ||||
| -rw-r--r-- | nova/virt/interfaces.template | 18 | ||||
| -rw-r--r-- | nova/virt/libvirt.qemu.xml.template | 30 | ||||
| -rw-r--r-- | nova/virt/libvirt.uml.xml.template | 25 | ||||
| -rw-r--r-- | nova/virt/libvirt_conn.py | 84 | ||||
| -rw-r--r-- | nova/virt/xenapi.py | 169 |
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 |
