From 75a1815aa2724d64d1f487996265ba9136017029 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Mon, 20 Sep 2010 11:33:35 +0200 Subject: Add Xen template and use it by default if libvirt_type=xen. --- nova/virt/libvirt.xen.xml.template | 30 ++++++++++++++++++++++++++++++ nova/virt/libvirt_conn.py | 8 +++++++- 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 nova/virt/libvirt.xen.xml.template diff --git a/nova/virt/libvirt.xen.xml.template b/nova/virt/libvirt.xen.xml.template new file mode 100644 index 000000000..9677902c6 --- /dev/null +++ b/nova/virt/libvirt.xen.xml.template @@ -0,0 +1,30 @@ + + %(name)s + + linux + %(basepath)s/kernel + %(basepath)s/ramdisk + /dev/xvda1 + ro + + + + + %(memory_kb)s + %(vcpus)s + + + + + + + + + + + + + + + + diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index d868e083c..09f178c2a 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -47,6 +47,9 @@ FLAGS = flags.FLAGS flags.DEFINE_string('libvirt_xml_template', utils.abspath('virt/libvirt.qemu.xml.template'), 'Libvirt XML Template for QEmu/KVM') +flags.DEFINE_string('libvirt_xen_xml_template', + utils.abspath('virt/libvirt.xen.xml.template'), + 'Libvirt XML Template for Xen') flags.DEFINE_string('libvirt_uml_xml_template', utils.abspath('virt/libvirt.uml.xml.template'), 'Libvirt XML Template for user-mode-linux') @@ -55,7 +58,7 @@ flags.DEFINE_string('injected_network_template', 'Template file for injected network') flags.DEFINE_string('libvirt_type', 'kvm', - 'Libvirt domain type (valid options are: kvm, qemu, uml)') + 'Libvirt domain type (valid options are: kvm, qemu, uml, xen)') flags.DEFINE_string('libvirt_uri', '', 'Override the default libvirt URI (which is dependent' @@ -104,6 +107,9 @@ class LibvirtConnection(object): if FLAGS.libvirt_type == 'uml': uri = FLAGS.libvirt_uri or 'uml:///system' template_file = FLAGS.libvirt_uml_xml_template + elif FLAGS.libvirt_type == 'xen': + uri = FLAGS.libvirt_uri or 'xen:///' + template_file = FLAGS.libvirt_xen_xml_template else: uri = FLAGS.libvirt_uri or 'qemu:///system' template_file = FLAGS.libvirt_xml_template -- cgit From a3d003d7ec92f3ae23a667954a790c71efdbfdbe Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Mon, 20 Sep 2010 11:46:18 +0200 Subject: Move the code that extracts the console output into the virt drivers. Move the code that formats it up into the API layer. Add support for Xen console. --- nova/compute/manager.py | 18 +----------------- nova/endpoint/cloud.py | 15 ++++++++++----- nova/virt/fake.py | 2 ++ nova/virt/libvirt_conn.py | 40 ++++++++++++++++++++++++++++++++++++++++ nova/virt/xenapi.py | 3 +++ 5 files changed, 56 insertions(+), 22 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index ae7099812..9f5674be2 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -144,26 +144,10 @@ class ComputeManager(manager.Manager): @exception.wrap_exception def get_console_output(self, context, instance_id): """Send the console output for an instance.""" - # TODO(vish): Move this into the driver layer - logging.debug("instance %s: getting console output", instance_id) instance_ref = self.db.instance_get(context, instance_id) - if FLAGS.connection_type == 'libvirt': - fname = os.path.abspath(os.path.join(FLAGS.instances_path, - instance_ref['str_id'], - 'console.log')) - with open(fname, 'r') as f: - output = f.read() - else: - output = 'FAKE CONSOLE OUTPUT' - - # TODO(termie): this stuff belongs in the API layer, no need to - # munge the data we send to ourselves - output = {"InstanceId": instance_id, - "Timestamp": "2", - "output": base64.b64encode(output)} - return output + return self.driver.get_console_output(instance_ref) @defer.inlineCallbacks @exception.wrap_exception diff --git a/nova/endpoint/cloud.py b/nova/endpoint/cloud.py index 622b4e2a4..a55c00a0d 100644 --- a/nova/endpoint/cloud.py +++ b/nova/endpoint/cloud.py @@ -233,11 +233,16 @@ class CloudController(object): def get_console_output(self, context, instance_id, **kwargs): # instance_id is passed in as a list of instances instance_ref = db.instance_get_by_str(context, instance_id[0]) - return rpc.call('%s.%s' % (FLAGS.compute_topic, - instance_ref['host']), - {"method": "get_console_output", - "args": {"context": None, - "instance_id": instance_ref['id']}}) + d = rpc.call('%s.%s' % (FLAGS.compute_topic, + instance_ref['host']), + { "method" : "get_console_output", + "args" : { "context": None, + "instance_id": instance_ref['id']}}) + + d.addCallback(lambda output: { "InstanceId": instance_id, + "Timestamp": "2", + "output": base64.b64encode(output)}) + return d @rbac.allow('projectmanager', 'sysadmin') def describe_volumes(self, context, **kwargs): diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 4ae6afcc4..dc6112f20 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -223,6 +223,8 @@ class FakeConnection(object): """ return [0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L] + def get_console_output(self, instance): + return 'FAKE CONSOLE OUTPUT' class FakeInstance(object): def __init__(self): diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 09f178c2a..addd9c997 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -248,6 +248,46 @@ class LibvirtConnection(object): timer.start(interval=0.5, now=True) yield local_d + def _flush_xen_console(self, virsh_output): + logging.info('virsh said: %r' % (virsh_output,)) + virsh_output = virsh_output[0].strip() + + if virsh_output.startswith('/dev/'): + logging.info('cool, it\'s a device') + d = process.simple_execute("sudo dd if=%s iflag=nonblock" % virsh_output, check_exit_code=False) + d.addCallback(lambda r:r[0]) + return d + else: + return '' + + def _append_to_file(self, data, fpath): + logging.info('data: %r, fpath: %r' % (data, fpath)) + fp = open(fpath, 'a+') + fp.write(data) + return fpath + + def _dump_file(self, fpath): + fp = open(fpath, 'r+') + contents = fp.read() + logging.info('Contents: %r' % (contents,)) + return contents + + @exception.wrap_exception + def get_console_output(self, instance): + console_log = os.path.join(FLAGS.instances_path, instance['name'], 'console.log') + logging.info('console_log: %s' % console_log) + logging.info('FLAGS.libvirt_type: %s' % FLAGS.libvirt_type) + if FLAGS.libvirt_type == 'xen': + # Xen is spethial + d = process.simple_execute("virsh ttyconsole %s" % instance['name']) + d.addCallback(self._flush_xen_console) + d.addCallback(self._append_to_file, console_log) + else: + d = defer.succeed(console_log) + d.addCallback(self._dump_file) + return d + + @defer.inlineCallbacks def _create_image(self, inst, libvirt_xml): # syntactic nicety diff --git a/nova/virt/xenapi.py b/nova/virt/xenapi.py index 1c6de4403..5fdd2b9fc 100644 --- a/nova/virt/xenapi.py +++ b/nova/virt/xenapi.py @@ -299,6 +299,9 @@ class XenAPIConnection(object): 'num_cpu': rec['VCPUs_max'], 'cpu_time': 0} + def get_console_output(self, instance): + return 'FAKE CONSOLE OUTPUT' + @utils.deferredToThread def _lookup(self, i): return self._lookup_blocking(i) -- cgit From 1158e1817b7d39e9655b219ede865f301153e713 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 5 Oct 2010 11:17:03 +0200 Subject: Un-twistedify get_console_ouptut. --- nova/api/ec2/cloud.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 1f01731ae..e7147ec05 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -247,16 +247,16 @@ class CloudController(object): def get_console_output(self, context, instance_id, **kwargs): # instance_id is passed in as a list of instances instance_ref = db.instance_get_by_ec2_id(context, instance_id[0]) - d = rpc.call('%s.%s' % (FLAGS.compute_topic, + output = rpc.call('%s.%s' % (FLAGS.compute_topic, instance_ref['host']), { "method" : "get_console_output", "args" : { "context": None, "instance_id": instance_ref['id']}}) - d.addCallback(lambda output: { "InstanceId": instance_id, - "Timestamp": "2", - "output": base64.b64encode(output)}) - return d + now = datetime.datetime.utcnow() + return { "InstanceId" : instance_id, + "Timestamp" : now, + "output" : base64.b64encode(output) } def describe_volumes(self, context, **kwargs): if context.user.is_admin(): -- cgit From 4b9be67025c3aff4b7f5f9b31a74eb14924885cb Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 5 Oct 2010 15:20:23 +0200 Subject: Add a connect_to_eventlet method. --- nova/rpc.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/nova/rpc.py b/nova/rpc.py index fe52ad35f..782c76765 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -28,6 +28,7 @@ import uuid from carrot import connection as carrot_connection from carrot import messaging +from eventlet import greenthread from twisted.internet import defer from twisted.internet import task @@ -107,6 +108,13 @@ class Consumer(messaging.Consumer): logging.exception("Failed to fetch message from queue") self.failed_connection = True + def attach_to_eventlet(self): + def fetch_repeatedly(): + while True: + self.fetch(enable_callbacks=True) + greenthread.sleep(0.1) + greenthread.spawn(fetch_repeatedly) + def attach_to_twisted(self): """Attach a callback to twisted that fires 10 times a second""" loop = task.LoopingCall(self.fetch, enable_callbacks=True) -- cgit From 394c5ae180ed25f7e617f6c43f9a88f003e5d2ea Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 5 Oct 2010 15:21:08 +0200 Subject: Make rpc calls work in unit tests by adding extra declare_consumer and consume methods on the FakeRabbit backend. --- nova/fakerabbit.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/nova/fakerabbit.py b/nova/fakerabbit.py index 068025249..6679f8dab 100644 --- a/nova/fakerabbit.py +++ b/nova/fakerabbit.py @@ -38,6 +38,7 @@ class Exchange(object): def publish(self, message, routing_key=None): logging.debug('(%s) publish (key: %s) %s', self.name, routing_key, message) + routing_key = routing_key.split('.')[0] if routing_key in self._routes: for f in self._routes[routing_key]: logging.debug('Publishing to route %s', f) @@ -94,6 +95,20 @@ class Backend(object): self._exchanges[exchange].bind(self._queues[queue].push, routing_key) + def declare_consumer(self, queue, callback, *args, **kwargs): + print 'declare_consumer', queue, callback + self.current_queue = queue + self.current_callback = callback + + def consume(self, *args, **kwargs): + from eventlet import greenthread + while True: + item = self.get(self.current_queue) + if item: + self.current_callback(item) + raise StopIteration() + greenthread.sleep(0) + def get(self, queue, no_ack=False): if not queue in self._queues or not self._queues[queue].size(): return None @@ -102,6 +117,7 @@ class Backend(object): message = Message(backend=self, body=message_data, content_type=content_type, content_encoding=content_encoding) + message.result = True logging.debug('Getting from %s: %s', queue, message) return message -- cgit From 10bbf9f638b5c8c9182984cc7e22f732b194476f Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 5 Oct 2010 15:21:31 +0200 Subject: Stub out ec2.images.list() for unit tests. --- nova/api/ec2/images.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nova/api/ec2/images.py b/nova/api/ec2/images.py index cb54cdda2..f0a43dad6 100644 --- a/nova/api/ec2/images.py +++ b/nova/api/ec2/images.py @@ -69,6 +69,9 @@ def list(context, filter_list=[]): optionally filtered by a list of image_id """ + if FLAGS.connection_type == 'fake': + return [{ 'imageId' : 'bar'}] + # FIXME: send along the list of only_images to check for response = conn(context).make_request( method='GET', -- cgit From 83430481760250e633275182d5cf4eb826f65ea2 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 5 Oct 2010 15:21:56 +0200 Subject: Make (some) cloud unit tests run without a full-blown set up. --- nova/tests/cloud_unittest.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py index ae7dea1db..f67eff7c2 100644 --- a/nova/tests/cloud_unittest.py +++ b/nova/tests/cloud_unittest.py @@ -16,6 +16,7 @@ # License for the specific language governing permissions and limitations # under the License. +from base64 import b64decode import json import logging from M2Crypto import BIO @@ -63,11 +64,16 @@ class CloudTestCase(test.TrialTestCase): self.cloud = cloud.CloudController() # set up a service - self.compute = utils.import_class(FLAGS.compute_manager) + self.compute = utils.import_class(FLAGS.compute_manager)() self.compute_consumer = rpc.AdapterConsumer(connection=self.conn, topic=FLAGS.compute_topic, proxy=self.compute) - self.compute_consumer.attach_to_twisted() + self.compute_consumer.attach_to_eventlet() + self.network = utils.import_class(FLAGS.network_manager)() + self.network_consumer = rpc.AdapterConsumer(connection=self.conn, + topic=FLAGS.network_topic, + proxy=self.network) + self.network_consumer.attach_to_eventlet() self.manager = manager.AuthManager() self.user = self.manager.create_user('admin', 'admin', 'admin', True) @@ -85,15 +91,17 @@ class CloudTestCase(test.TrialTestCase): return cloud._gen_key(self.context, self.context.user.id, name) def test_console_output(self): - if FLAGS.connection_type == 'fake': - logging.debug("Can't test instances without a real virtual env.") - return - instance_id = 'foo' - inst = yield self.compute.run_instance(instance_id) + image_id = FLAGS.default_image + instance_type = FLAGS.default_instance_type + max_count = 1 + kwargs = {'image_id': image_id, + 'instance_type': instance_type, + 'max_count': max_count } + rv = yield self.cloud.run_instances(self.context, **kwargs) + instance_id = rv['instancesSet'][0]['instanceId'] output = yield self.cloud.get_console_output(self.context, [instance_id]) - logging.debug(output) - self.assert_(output) - rv = yield self.compute.terminate_instance(instance_id) + self.assertEquals(b64decode(output['output']), 'FAKE CONSOLE OUTPUT') + rv = yield self.cloud.terminate_instances(self.context, [instance_id]) def test_key_generation(self): -- cgit From 5a5da05a966dcdd3113a074468b37e12d406b350 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 5 Oct 2010 15:25:50 +0200 Subject: Remove debugging code, and move import to the top. --- nova/fakerabbit.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nova/fakerabbit.py b/nova/fakerabbit.py index 6679f8dab..df5e61e6e 100644 --- a/nova/fakerabbit.py +++ b/nova/fakerabbit.py @@ -22,6 +22,7 @@ import logging import Queue as queue from carrot.backends import base +from eventlet import greenthread class Message(base.BaseMessage): @@ -96,12 +97,10 @@ class Backend(object): routing_key) def declare_consumer(self, queue, callback, *args, **kwargs): - print 'declare_consumer', queue, callback self.current_queue = queue self.current_callback = callback def consume(self, *args, **kwargs): - from eventlet import greenthread while True: item = self.get(self.current_queue) if item: -- cgit