summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorNachi Ueno <nati.ueno@gmail.com>2011-01-12 18:46:01 +0900
committerNachi Ueno <nati.ueno@gmail.com>2011-01-12 18:46:01 +0900
commit0a33f1ed87ffb2ad3ff6c41848e2bb254a62e3da (patch)
treebd42398ad08cecdcf72c54c7982d57a8867f6402 /nova
parent04cd3241f442f1c6a9fd030ab47b4d15e79ec032 (diff)
parent76fdd667f2efe7e2dc710fe0254437d176efb45c (diff)
downloadnova-0a33f1ed87ffb2ad3ff6c41848e2bb254a62e3da.tar.gz
nova-0a33f1ed87ffb2ad3ff6c41848e2bb254a62e3da.tar.xz
nova-0a33f1ed87ffb2ad3ff6c41848e2bb254a62e3da.zip
Merged with 549
Diffstat (limited to 'nova')
-rw-r--r--nova/api/ec2/cloud.py5
-rw-r--r--nova/api/openstack/servers.py9
-rw-r--r--nova/compute/api.py21
-rw-r--r--nova/compute/manager.py8
-rw-r--r--nova/flags.py9
-rw-r--r--nova/tests/test_cloud.py13
-rw-r--r--nova/tests/test_compute.py10
-rw-r--r--nova/utils.py5
-rw-r--r--nova/virt/fake.py3
-rw-r--r--nova/virt/libvirt.xml.template13
-rw-r--r--nova/virt/libvirt_conn.py45
-rw-r--r--nova/virt/xenapi/vmops.py5
-rw-r--r--nova/virt/xenapi_conn.py4
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,