summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVishvananda Ishaya <vishvananda@yahoo.com>2010-08-16 12:08:31 -0700
committerVishvananda Ishaya <vishvananda@yahoo.com>2010-08-16 12:08:31 -0700
commitb80a555549d4380b29d1aa0c8ac267111544cc4e (patch)
tree43bc01a158254bf1503185a5d4235ee68e2eff7b
parentfa70aefb00e487102564b92f6d32047dd8998054 (diff)
parentadcde1a3d8d9fb50e45b794ad90217c03a0aac23 (diff)
merged trunk and fixed conflicts
-rw-r--r--doc/source/getting.started.rst1
-rw-r--r--nova/endpoint/cloud.py15
-rw-r--r--nova/tests/cloud_unittest.py2
-rw-r--r--nova/tests/virt_unittest.py69
-rw-r--r--nova/virt/connection.py9
-rw-r--r--nova/virt/fake.py131
-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.template25
-rw-r--r--nova/virt/libvirt_conn.py60
-rw-r--r--plugins/xenapi/README4
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