diff options
| author | Nachi Ueno <nati.ueno@gmail.com> | 2011-01-12 18:46:01 +0900 |
|---|---|---|
| committer | Nachi Ueno <nati.ueno@gmail.com> | 2011-01-12 18:46:01 +0900 |
| commit | 0a33f1ed87ffb2ad3ff6c41848e2bb254a62e3da (patch) | |
| tree | bd42398ad08cecdcf72c54c7982d57a8867f6402 /nova | |
| parent | 04cd3241f442f1c6a9fd030ab47b4d15e79ec032 (diff) | |
| parent | 76fdd667f2efe7e2dc710fe0254437d176efb45c (diff) | |
| download | nova-0a33f1ed87ffb2ad3ff6c41848e2bb254a62e3da.tar.gz nova-0a33f1ed87ffb2ad3ff6c41848e2bb254a62e3da.tar.xz nova-0a33f1ed87ffb2ad3ff6c41848e2bb254a62e3da.zip | |
Merged with 549
Diffstat (limited to 'nova')
| -rw-r--r-- | nova/api/ec2/cloud.py | 5 | ||||
| -rw-r--r-- | nova/api/openstack/servers.py | 9 | ||||
| -rw-r--r-- | nova/compute/api.py | 21 | ||||
| -rw-r--r-- | nova/compute/manager.py | 8 | ||||
| -rw-r--r-- | nova/flags.py | 9 | ||||
| -rw-r--r-- | nova/tests/test_cloud.py | 13 | ||||
| -rw-r--r-- | nova/tests/test_compute.py | 10 | ||||
| -rw-r--r-- | nova/utils.py | 5 | ||||
| -rw-r--r-- | nova/virt/fake.py | 3 | ||||
| -rw-r--r-- | nova/virt/libvirt.xml.template | 13 | ||||
| -rw-r--r-- | nova/virt/libvirt_conn.py | 45 | ||||
| -rw-r--r-- | nova/virt/xenapi/vmops.py | 5 | ||||
| -rw-r--r-- | nova/virt/xenapi_conn.py | 4 |
13 files changed, 148 insertions, 2 deletions
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 8e13d0284..72d7bcc95 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -504,6 +504,11 @@ class CloudController(object): "Timestamp": now, "output": base64.b64encode(output)} + def get_ajax_console(self, context, instance_id, **kwargs): + ec2_id = instance_id[0] + internal_id = ec2_id_to_id(ec2_id) + return self.compute_api.get_ajax_console(context, internal_id) + def describe_volumes(self, context, volume_id=None, **kwargs): volumes = self.volume_api.get_all(context) # NOTE(vish): volume_id is an optional list of volume ids to filter by. diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index c8a9947f3..29af82533 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -283,6 +283,15 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + def get_ajax_console(self, req, id): + """ Returns a url to an instance's ajaxterm console. """ + try: + self.compute_api.get_ajax_console(req.environ['nova.context'], + int(id)) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + return exc.HTTPAccepted() + def diagnostics(self, req, id): """Permit Admins to retrieve server diagnostics.""" ctxt = req.environ["nova.context"] diff --git a/nova/compute/api.py b/nova/compute/api.py index fbe3c6e75..56402c11b 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -414,7 +414,26 @@ class API(base.Base): rpc.cast(context, self.db.queue_get_for(context, FLAGS.compute_topic, host), {"method": "unrescue_instance", - "args": {"instance_id": instance_id}}) + "args": {"instance_id": instance['id']}}) + + def get_ajax_console(self, context, instance_id): + """Get a url to an AJAX Console""" + + instance = self.get(context, instance_id) + + output = rpc.call(context, + '%s.%s' % (FLAGS.compute_topic, + instance['host']), + {'method': 'get_ajax_console', + 'args': {'instance_id': instance['id']}}) + + rpc.cast(context, '%s' % FLAGS.ajax_console_proxy_topic, + {'method': 'authorize_ajax_console', + 'args': {'token': output['token'], 'host': output['host'], + 'port': output['port']}}) + + return {'url': '%s?token=%s' % (FLAGS.ajax_console_proxy_url, + output['token'])} def lock(self, context, instance_id): """ diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 018e0bbbe..6b2fc4adb 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -471,6 +471,14 @@ class ComputeManager(manager.Manager): return self.driver.get_console_output(instance_ref) @exception.wrap_exception + def get_ajax_console(self, context, instance_id): + """Return connection information for an ajax console""" + context = context.elevated() + logging.debug(_("instance %s: getting ajax console"), instance_id) + instance_ref = self.db.instance_get(context, instance_id) + + return self.driver.get_ajax_console(instance_ref) + @checks_instance_lock def attach_volume(self, context, instance_id, volume_id, mountpoint): """Attach a volume to an instance.""" diff --git a/nova/flags.py b/nova/flags.py index 25362f883..76ab2f788 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -234,7 +234,14 @@ DEFINE_string('scheduler_topic', 'scheduler', 'the topic scheduler nodes listen on') DEFINE_string('volume_topic', 'volume', 'the topic volume nodes listen on') DEFINE_string('network_topic', 'network', 'the topic network nodes listen on') - +DEFINE_string('ajax_console_proxy_topic', 'ajax_proxy', + 'the topic ajax proxy nodes listen on') +DEFINE_string('ajax_console_proxy_url', + 'http://127.0.0.1:8000', + 'location of ajax console proxy, \ + in the form "http://127.0.0.1:8000"') +DEFINE_string('ajax_console_proxy_port', + 8000, 'port that ajax_console_proxy binds') DEFINE_bool('verbose', False, 'show debug output') DEFINE_boolean('fake_rabbit', False, 'use a fake rabbit') DEFINE_bool('fake_network', False, diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index b8a15c7b2..8e43eec00 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -167,6 +167,19 @@ class CloudTestCase(test.TestCase): greenthread.sleep(0.3) rv = self.cloud.terminate_instances(self.context, [instance_id]) + def test_ajax_console(self): + kwargs = {'image_id': image_id} + rv = yield self.cloud.run_instances(self.context, **kwargs) + instance_id = rv['instancesSet'][0]['instanceId'] + output = yield self.cloud.get_console_output(context=self.context, + instance_id=[instance_id]) + self.assertEquals(b64decode(output['output']), + 'http://fakeajaxconsole.com/?token=FAKETOKEN') + # TODO(soren): We need this until we can stop polling in the rpc code + # for unit tests. + greenthread.sleep(0.3) + rv = yield self.cloud.terminate_instances(self.context, [instance_id]) + def test_key_generation(self): result = self._create_key('test') private_key = result['private_key'] diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 1d407c5a3..52660ee74 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -169,6 +169,16 @@ class ComputeTestCase(test.TestCase): self.assert_(console) self.compute.terminate_instance(self.context, instance_id) + def test_ajax_console(self): + """Make sure we can get console output from instance""" + instance_id = self._create_instance() + self.compute.run_instance(self.context, instance_id) + + console = self.compute.get_ajax_console(self.context, + instance_id) + self.assert_(console) + self.compute.terminate_instance(self.context, instance_id) + def test_run_instance_existing(self): """Ensure failure when running an instance that already exists""" instance_id = self._create_instance() diff --git a/nova/utils.py b/nova/utils.py index a13fa214c..49344699a 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -155,6 +155,11 @@ def abspath(s): return os.path.join(os.path.dirname(__file__), s) +def novadir(): + import nova + return os.path.abspath(nova.__file__).split('nova/__init__.pyc')[0] + + def default_flagfile(filename='nova.conf'): for arg in sys.argv: if arg.find('flagfile') != -1: diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 849261f07..9186d885e 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -289,6 +289,9 @@ class FakeConnection(object): def get_console_output(self, instance): return 'FAKE CONSOLE OUTPUT' + def get_ajax_console(self, instance): + return 'http://fakeajaxconsole.com/?token=FAKETOKEN' + def get_console_pool_info(self, console_type): return {'address': '127.0.0.1', 'username': 'fakeuser', diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template index 52c95ee57..26b24b162 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -72,9 +72,22 @@ #end if </filterref> </interface> + + <!-- The order is significant here. File must be defined first --> <serial type="file"> <source path='${basepath}/console.log'/> <target port='1'/> </serial> + + <console type='pty' tty='/dev/pts/2'> + <source path='/dev/pts/2'/> + <target port='0'/> + </console> + + <serial type='pty'> + <source path='/dev/pts/2'/> + <target port='0'/> + </serial> + </devices> </domain> diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index e5f61f9aa..ec17e4db0 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -38,6 +38,11 @@ Supports KVM, QEMU, UML, and XEN. import os import shutil +import random +import subprocess +import uuid +from xml.dom import minidom + from eventlet import greenthread from eventlet import event @@ -86,6 +91,9 @@ flags.DEFINE_string('libvirt_uri', flags.DEFINE_bool('allow_project_net_traffic', True, 'Whether to allow in project network traffic') +flags.DEFINE_string('ajaxterm_portrange', + '10000-12000', + 'Range of ports that ajaxterm should randomly try to bind') flags.DEFINE_string('firewall_driver', 'nova.virt.libvirt_conn.IptablesFirewallDriver', 'Firewall driver (defaults to iptables)') @@ -442,6 +450,43 @@ class LibvirtConnection(object): return self._dump_file(fpath) + @exception.wrap_exception + def get_ajax_console(self, instance): + def get_open_port(): + start_port, end_port = FLAGS.ajaxterm_portrange.split("-") + for i in xrange(0, 100): # don't loop forever + port = random.randint(int(start_port), int(end_port)) + # netcat will exit with 0 only if the port is in use, + # so a nonzero return value implies it is unused + cmd = 'netcat 0.0.0.0 %s -w 1 </dev/null || echo free' % (port) + stdout, stderr = utils.execute(cmd) + if stdout.strip() == 'free': + return port + raise Exception(_('Unable to find an open port')) + + def get_pty_for_instance(instance_name): + virt_dom = self._conn.lookupByName(instance_name) + xml = virt_dom.XMLDesc(0) + dom = minidom.parseString(xml) + + for serial in dom.getElementsByTagName('serial'): + if serial.getAttribute('type') == 'pty': + source = serial.getElementsByTagName('source')[0] + return source.getAttribute('path') + + port = get_open_port() + token = str(uuid.uuid4()) + host = instance['host'] + + ajaxterm_cmd = 'sudo socat - %s' \ + % get_pty_for_instance(instance['name']) + + cmd = '%s/tools/ajaxterm/ajaxterm.py --command "%s" -t %s -p %s' \ + % (utils.novadir(), ajaxterm_cmd, token, port) + + subprocess.Popen(cmd, shell=True) + return {'token': token, 'host': host, 'port': port} + def _create_image(self, inst, libvirt_xml, prefix='', disk_images=None): # syntactic nicety basepath = lambda fname = '', prefix = prefix: os.path.join( diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index e20930fe8..7e3585991 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -284,6 +284,11 @@ class VMOps(object): # TODO: implement this to fix pylint! return 'FAKE CONSOLE OUTPUT of instance' + def get_ajax_console(self, instance): + """Return link to instance's ajax console""" + # TODO: implement this! + return 'http://fakeajaxconsole/fake_url' + def list_from_xenstore(self, vm, path): """Runs the xenstore-ls command to get a listing of all records from 'path' downward. Returns a dict with the sub-paths as keys, diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 11473ddf8..45d0738a5 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -181,6 +181,10 @@ class XenAPIConnection(object): """Return snapshot of console""" return self._vmops.get_console_output(instance) + def get_ajax_console(self, instance): + """Return link to instance's ajax console""" + return self._vmops.get_ajax_console(instance) + def attach_volume(self, instance_name, device_path, mountpoint): """Attach volume storage to VM instance""" return self._volumeops.attach_volume(instance_name, |
