diff options
| author | Vishvananda Ishaya <vishvananda@yahoo.com> | 2010-08-16 12:08:31 -0700 |
|---|---|---|
| committer | Vishvananda Ishaya <vishvananda@yahoo.com> | 2010-08-16 12:08:31 -0700 |
| commit | b80a555549d4380b29d1aa0c8ac267111544cc4e (patch) | |
| tree | 43bc01a158254bf1503185a5d4235ee68e2eff7b | |
| parent | fa70aefb00e487102564b92f6d32047dd8998054 (diff) | |
| parent | adcde1a3d8d9fb50e45b794ad90217c03a0aac23 (diff) | |
merged trunk and fixed conflicts
| -rw-r--r-- | doc/source/getting.started.rst | 1 | ||||
| -rw-r--r-- | nova/endpoint/cloud.py | 15 | ||||
| -rw-r--r-- | nova/tests/cloud_unittest.py | 2 | ||||
| -rw-r--r-- | nova/tests/virt_unittest.py | 69 | ||||
| -rw-r--r-- | nova/virt/connection.py | 9 | ||||
| -rw-r--r-- | nova/virt/fake.py | 131 | ||||
| -rw-r--r-- | nova/virt/interfaces.template (renamed from nova/compute/interfaces.template) | 0 | ||||
| -rw-r--r-- | nova/virt/libvirt.qemu.xml.template (renamed from nova/compute/libvirt.xml.template) | 0 | ||||
| -rw-r--r-- | nova/virt/libvirt.uml.xml.template | 25 | ||||
| -rw-r--r-- | nova/virt/libvirt_conn.py | 60 | ||||
| -rw-r--r-- | plugins/xenapi/README | 4 |
11 files changed, 293 insertions, 23 deletions
diff --git a/doc/source/getting.started.rst b/doc/source/getting.started.rst index 3eadd0882..f683bb256 100644 --- a/doc/source/getting.started.rst +++ b/doc/source/getting.started.rst @@ -40,6 +40,7 @@ Python libraries we don't vendor * M2Crypto: python library interface for openssl * curl +* XenAPI: Needed only for Xen Cloud Platform or XenServer support. Available from http://wiki.xensource.com/xenwiki/XCP_SDK or http://community.citrix.com/cdn/xs/sdks. Vendored python libaries (don't require any installation) diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index b68c13456..1c1134938 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -399,7 +399,15 @@ class CloudController(object): @rbac.allow('all') def describe_instances(self, context, **kwargs): - return defer.succeed(self._format_instances(context)) + return defer.succeed(self._format_describe_instances(context)) + + def _format_describe_instances(self, context): + return { 'reservationSet': self._format_instances(context) } + + def _format_run_instances(self, context, reservation_id): + i = self._format_instances(context, reservation_id) + assert len(i) == 1 + return i[0] def _format_instances(self, context, reservation_id = None): reservations = {} @@ -445,8 +453,7 @@ class CloudController(object): reservations[res_id] = r reservations[res_id]['instances_set'].append(i) - instance_response = {'reservationSet': list(reservations.values())} - return instance_response + return list(reservations.values()) @rbac.allow('all') def describe_addresses(self, context, **kwargs): @@ -597,7 +604,7 @@ class CloudController(object): logging.debug("Casting to node for %s's instance with IP of %s" % (context.user.name, inst.fixed_ip)) # defer.returnValue(self._format_instances(context, reservation_id)) - return self._format_instances(context, reservation_id) + return self._format_run_instances(context, reservation_id) @rbac.allow('projectmanager', 'sysadmin') @defer.inlineCallbacks diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py index 40837405c..3501771cc 100644 --- a/nova/tests/cloud_unittest.py +++ b/nova/tests/cloud_unittest.py @@ -132,7 +132,7 @@ class CloudTestCase(test.BaseTestCase): 'state': 0x01, 'user_data': '' } - rv = self.cloud._format_instances(self.context) + rv = self.cloud._format_describe_instances(self.context) self.assert_(len(rv['reservationSet']) == 0) # simulate launch of 5 instances diff --git a/nova/tests/virt_unittest.py b/nova/tests/virt_unittest.py new file mode 100644 index 000000000..2aab16809 --- /dev/null +++ b/nova/tests/virt_unittest.py @@ -0,0 +1,69 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2010 OpenStack LLC +# +# 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. + +from nova import flags +from nova import test +from nova.virt import libvirt_conn + +FLAGS = flags.FLAGS + + +class LibvirtConnTestCase(test.TrialTestCase): + def test_get_uri_and_template(self): + class MockDataModel(object): + def __init__(self): + self.datamodel = { 'name' : 'i-cafebabe', + 'memory_kb' : '1024000', + 'basepath' : '/some/path', + 'bridge_name' : 'br100', + 'mac_address' : '02:12:34:46:56:67', + 'vcpus' : 2 } + + type_uri_map = { 'qemu' : ('qemu:///system', + [lambda s: '<domain type=\'qemu\'>' in s, + lambda s: 'type>hvm</type' in s, + lambda s: 'emulator>/usr/bin/kvm' not in s]), + 'kvm' : ('qemu:///system', + [lambda s: '<domain type=\'kvm\'>' in s, + lambda s: 'type>hvm</type' in s, + lambda s: 'emulator>/usr/bin/qemu<' not in s]), + 'uml' : ('uml:///system', + [lambda s: '<domain type=\'uml\'>' in s, + lambda s: 'type>uml</type' in s]), + } + + for (libvirt_type,(expected_uri, checks)) in type_uri_map.iteritems(): + FLAGS.libvirt_type = libvirt_type + conn = libvirt_conn.LibvirtConnection(True) + + uri, template = conn.get_uri_and_template() + self.assertEquals(uri, expected_uri) + + for i, check in enumerate(checks): + xml = conn.toXml(MockDataModel()) + self.assertTrue(check(xml), '%s failed check %d' % (xml, i)) + + # Deliberately not just assigning this string to FLAGS.libvirt_uri and + # checking against that later on. This way we make sure the + # implementation doesn't fiddle around with the FLAGS. + testuri = 'something completely different' + FLAGS.libvirt_uri = testuri + for (libvirt_type,(expected_uri, checks)) in type_uri_map.iteritems(): + FLAGS.libvirt_type = libvirt_type + conn = libvirt_conn.LibvirtConnection(True) + uri, template = conn.get_uri_and_template() + self.assertEquals(uri, testuri) + 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 90ea9d053..d7416d250 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -19,6 +19,7 @@ """ 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 @@ -32,6 +33,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 +75,59 @@ 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. + + Once this function successfully completes, the instance should be + running (power_state.RUNNING). + + If this function 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.id] = fake_instance fake_instance._state = power_state.RUNNING def reboot(self, instance): + """ + 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. + """ pass - + def destroy(self, instance): - del self.instances[instance.id] + """ + 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. + """ + del self.instances[instance.name] 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 +136,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/compute/interfaces.template b/nova/virt/interfaces.template index 11df301f6..11df301f6 100644 --- a/nova/compute/interfaces.template +++ b/nova/virt/interfaces.template diff --git a/nova/compute/libvirt.xml.template b/nova/virt/libvirt.qemu.xml.template index 17bd79b7c..17bd79b7c 100644 --- a/nova/compute/libvirt.xml.template +++ b/nova/virt/libvirt.qemu.xml.template 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 ef285b86e..c00421ab8 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -44,15 +44,20 @@ 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 @@ -65,16 +70,42 @@ def get_connection(read_only): libxml2 = __import__('libxml2') return LibvirtConnection(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): @@ -231,8 +262,6 @@ class LibvirtConnection(object): def to_xml(self, instance): # TODO(termie): cache? logging.debug("Starting the toXML method") - with open(FLAGS.libvirt_xml_template) as f: - libvirt_xml = f.read() network = instance.project.network # FIXME(vish): stick this in db instance_type = instance_types.INSTANCE_TYPES[instance.instance_type] @@ -243,8 +272,7 @@ class LibvirtConnection(object): 'vcpus': instance_type['vcpus'], 'bridge_name': network.bridge_name, 'mac_address': instance.mac_address} - # TODO(joshua): Make this xml express the attached disks as well - libvirt_xml = libvirt_xml % xml_info + libvirt_xml = self.libvirt_xml % xml_info logging.debug("Finished the toXML method") return libvirt_xml @@ -261,7 +289,7 @@ class LibvirtConnection(object): def get_disks(self, instance_name): """ - Note that this function takes an instance ID, not an Instance, so + Note that this function takes an instance name, not an Instance, so that it can be called by monitor. Returns a list of all block devices for this domain. @@ -304,7 +332,7 @@ class LibvirtConnection(object): def get_interfaces(self, instance_name): """ - Note that this function takes an instance ID, not an Instance, so + Note that this function takes an instance name, not an Instance, so that it can be called by monitor. Returns a list of all network interfaces for this instance. @@ -347,7 +375,7 @@ class LibvirtConnection(object): def block_stats(self, instance_name, disk): """ - Note that this function takes an instance ID, not an Instance, so + Note that this function takes an instance name, not an Instance, so that it can be called by monitor. """ domain = self._conn.lookupByName(instance_name) @@ -356,7 +384,7 @@ class LibvirtConnection(object): def interface_stats(self, instance_name, interface): """ - Note that this function takes an instance ID, not an Instance, so + Note that this function takes an instance name, not an Instance, so that it can be called by monitor. """ domain = self._conn.lookupByName(instance_name) diff --git a/plugins/xenapi/README b/plugins/xenapi/README index 1fc67aa7a..fbd471035 100644 --- a/plugins/xenapi/README +++ b/plugins/xenapi/README @@ -1,2 +1,6 @@ This directory contains files that are required for the XenAPI support. They should be installed in the XenServer / Xen Cloud Platform domain 0. + +Also, you need to + +chmod u+x /etc/xapi.d/plugins/objectstore |
