Merge lp:~mdragon/nova/xs-console into lp:~hudson-openstack/nova/trunk

Proposed by Monsyne Dragon
Status: Merged
Approved by: Soren Hansen
Approved revision: 509
Merged at revision: 543
Proposed branch: lp:~mdragon/nova/xs-console
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 1213 lines (+990/-3)
21 files modified
Authors (+1/-0)
bin/nova-console (+44/-0)
nova/api/openstack/__init__.py (+6/-0)
nova/api/openstack/consoles.py (+96/-0)
nova/compute/manager.py (+17/-0)
nova/console/__init__.py (+13/-0)
nova/console/api.py (+75/-0)
nova/console/fake.py (+58/-0)
nova/console/manager.py (+127/-0)
nova/console/xvp.conf.template (+16/-0)
nova/console/xvp.py (+194/-0)
nova/db/api.py (+54/-0)
nova/db/sqlalchemy/api.py (+108/-0)
nova/db/sqlalchemy/models.py (+26/-1)
nova/flags.py (+4/-0)
nova/tests/test_console.py (+129/-0)
nova/tests/test_virt.py (+1/-1)
nova/virt/fake.py (+5/-0)
nova/virt/hyperv.py (+1/-1)
nova/virt/libvirt_conn.py (+8/-0)
nova/virt/xenapi_conn.py (+7/-0)
To merge this branch: bzr merge lp:~mdragon/nova/xs-console
Reviewer Review Type Date Requested Status
Trey Morris (community) Approve
Matt Dietz (community) Approve
Vish Ishaya (community) Needs Fixing
Sandy Walsh (community) Approve
Cory Wright (community) Approve
Review via email: mp+45324@code.launchpad.net

Description of the change

Implementation of xs-console blueprint (adds support for console proxies like xvp)

If you spin up the nova-console service, you should be able to see the xvp.conf being edited, and the xvp daemon started/stopped if you exercise the openstack console api (consoles sub-resource on servers)

To post a comment you must log in.
lp:~mdragon/nova/xs-console updated
505. By Monsyne Dragon

pep8 fix

Revision history for this message
Cory Wright (corywright) wrote :

lgtm. Maybe update this at some point with some docstrings for the ConsoleAPI methods. otherwise everything looks good.

review: Approve
Revision history for this message
Sandy Walsh (sandy-walsh) wrote :

This is a just a source code review. I haven't tried to run the branch tests yet:

Overall, awesome work! Very impressive first commit!

nova/console/driver.py -> ConsoleProxy? Perhaps nova/console/proxy.py (since the class is truly a proxy and not a driver)

574 ... should there be debug code in here? Can't this be stubbed in the tests via stubout?

I think ConsolePool should be ConsoleProxyPool. It wasn't clear to me what a "console" was (since I always just viewed a console as an endpoint). A ConsoleProxy, however, makes more sense that it's a running entity.

I must be missing something, but I don't see where the ConsoleProxyManager is used? Other than via the tests. Yet, this is the only way to get a ConsoleProxy instantiated, afaik. Do you have any examples of the curl or python-cloudserver calls to add/remove a console?

I assume the proxies run on the console application server?

I have not reviewed the breadth of the tests yet. I'm not marking it Approved or Needs Fixing until you've had a chance to give feedback.

Revision history for this message
Monsyne Dragon (mdragon) wrote :

nova/console/driver.py ConsoleProxy class is the base class for the drivers (such as XVPConsoleProxy)

(I think it got that name because I was wavering between console and consoleproxy as the name for the service)

re: diff line 574: this is code used for unittests to eliminate the rpc call. THis is that same as how compute.manager uses the stub_network flag for tests.

Really, the 'console' is an entry in the config for the running xvp console proxy daemon.
the 'ConsolePool' represents a group of consoles that all live in the save (in this case xenserver) host.

the xvp daemon is the actual proxy (for multiple consoles)

ConsoleProxyManager is the main manager for the nova-console worker.
Ultimately the calls to add/remove consoles are made through the openstack api. (console is a sub-resource of server)

Revision history for this message
Sandy Walsh (sandy-walsh) wrote :

Change based on conversation with vishy: I assume the proxies run on the api servers?

Revision history for this message
Monsyne Dragon (mdragon) wrote :

> Change based on conversation with vishy: I assume the proxies run on the api
> servers?

No, the proxy runs on a console proxy server

Revision history for this message
Sandy Walsh (sandy-walsh) wrote :

Based on discussion with Dragon in IRC (thx again D):

1. The xcp daemon runs on the nova-console server and it assumed to have two NIC's: 1 public and 1 private. Clients obviously come in on the public one.

2. I think the flags for testing can be removed and stubout used. Dragon will have a look, but this works currently.

3. The Service fires up the Manager. API doesn't need to specify which manager.

4. The common pattern in Nova is:
 * Managers are Factories of Drivers.
 * Drivers are abstract Facades on something.
So calling driver.py proxy.py might add more confusion.

Approved code wise. Haven't had it running yet.

review: Approve
Revision history for this message
Vish Ishaya (vishvananda) wrote :

This should be a quick change, but the standard way we're doing apis in the other components is to name the class API and then import it in __init__.py. Your way is fine too, I just think that we should be consistent across components.

review: Needs Fixing
lp:~mdragon/nova/xs-console updated
506. By Monsyne Dragon

change API classname to match the way other API's are done.

Revision history for this message
Monsyne Dragon (mdragon) wrote :

> This should be a quick change, but the standard way we're doing apis in the
> other components is to name the class API and then import it in __init__.py.
> Your way is fine too, I just think that we should be consistent across
> components.

Ok, makes sense. I have pushed this change to the branch.

lp:~mdragon/nova/xs-console updated
507. By Monsyne Dragon

re-merged in trunk to correct conflict

Revision history for this message
Matt Dietz (cerberus) wrote :

I'm not sure I follow the utility of having the driver.ConsoleProxy class. It looks to be an abstract class of sorts, except in every subclassing you re-implement every method. I would understand the utility if it seemed a lot of the functionality could be passed down, but as is it seems to be an unnecessary contract. Am I missing something?

Otherwise seems ok to me.

review: Needs Fixing
lp:~mdragon/nova/xs-console updated
508. By Monsyne Dragon

remove uneeded superclass

509. By Monsyne Dragon

whups, fix accidental change to nova-combined

Revision history for this message
Monsyne Dragon (mdragon) wrote :

> I'm not sure I follow the utility of having the driver.ConsoleProxy class. It
> looks to be an abstract class of sorts, except in every subclassing you re-
> implement every method. I would understand the utility if it seemed a lot of
> the functionality could be passed down, but as is it seems to be an
> unnecessary contract. Am I missing something?

Nope. I was thinking there would be more common code, but not so. I have removed the driver.ConsoleProxy class. (leave that to future refactorings if need be, once we actually have more than 1 real console driver class.)

Revision history for this message
Matt Dietz (cerberus) wrote :

lgtm.

review: Approve
Revision history for this message
Trey Morris (tr3buchet) wrote :

long read! Looks good to me, except for a few places you used '\' for line breaks where you didn't have to from lines 833 to 908. However if it "needs fixing" is up to you.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Authors'
2--- Authors 2011-01-09 19:13:19 +0000
3+++ Authors 2011-01-10 21:04:17 +0000
4@@ -26,6 +26,7 @@
5 Ken Pepple <ken.pepple@gmail.com>
6 Matt Dietz <matt.dietz@rackspace.com>
7 Michael Gundlach <michael.gundlach@rackspace.com>
8+Monsyne Dragon <mdragon@rackspace.com>
9 Monty Taylor <mordred@inaugust.com>
10 Paul Voccio <paul@openstack.org>
11 Rick Clark <rick@openstack.org>
12
13=== added file 'bin/nova-console'
14--- bin/nova-console 1970-01-01 00:00:00 +0000
15+++ bin/nova-console 2011-01-10 21:04:17 +0000
16@@ -0,0 +1,44 @@
17+#!/usr/bin/env python
18+# vim: tabstop=4 shiftwidth=4 softtabstop=4
19+
20+# Copyright (c) 2010 Openstack, LLC.
21+# All Rights Reserved.
22+#
23+# Licensed under the Apache License, Version 2.0 (the "License"); you may
24+# not use this file except in compliance with the License. You may obtain
25+# a copy of the License at
26+#
27+# http://www.apache.org/licenses/LICENSE-2.0
28+#
29+# Unless required by applicable law or agreed to in writing, software
30+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
31+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
32+# License for the specific language governing permissions and limitations
33+# under the License.
34+
35+"""Starter script for Nova Console Proxy."""
36+
37+import eventlet
38+eventlet.monkey_patch()
39+
40+import gettext
41+import os
42+import sys
43+
44+# If ../nova/__init__.py exists, add ../ to Python search path, so that
45+# it will override what happens to be installed in /usr/(local/)lib/python...
46+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
47+ os.pardir,
48+ os.pardir))
49+if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
50+ sys.path.insert(0, possible_topdir)
51+
52+gettext.install('nova', unicode=1)
53+
54+from nova import service
55+from nova import utils
56+
57+if __name__ == '__main__':
58+ utils.default_flagfile()
59+ service.serve()
60+ service.wait()
61
62=== modified file 'nova/api/openstack/__init__.py'
63--- nova/api/openstack/__init__.py 2011-01-07 14:46:17 +0000
64+++ nova/api/openstack/__init__.py 2011-01-10 21:04:17 +0000
65@@ -31,6 +31,7 @@
66 from nova import wsgi
67 from nova.api.openstack import faults
68 from nova.api.openstack import backup_schedules
69+from nova.api.openstack import consoles
70 from nova.api.openstack import flavors
71 from nova.api.openstack import images
72 from nova.api.openstack import servers
73@@ -100,6 +101,11 @@
74 parent_resource=dict(member_name='server',
75 collection_name='servers'))
76
77+ mapper.resource("console", "consoles",
78+ controller=consoles.Controller(),
79+ parent_resource=dict(member_name='server',
80+ collection_name='servers'))
81+
82 mapper.resource("image", "images", controller=images.Controller(),
83 collection={'detail': 'GET'})
84 mapper.resource("flavor", "flavors", controller=flavors.Controller(),
85
86=== added file 'nova/api/openstack/consoles.py'
87--- nova/api/openstack/consoles.py 1970-01-01 00:00:00 +0000
88+++ nova/api/openstack/consoles.py 2011-01-10 21:04:17 +0000
89@@ -0,0 +1,96 @@
90+# vim: tabstop=4 shiftwidth=4 softtabstop=4
91+
92+# Copyright 2010 OpenStack LLC.
93+# All Rights Reserved.
94+#
95+# Licensed under the Apache License, Version 2.0 (the "License"); you may
96+# not use this file except in compliance with the License. You may obtain
97+# a copy of the License at
98+#
99+# http://www.apache.org/licenses/LICENSE-2.0
100+#
101+# Unless required by applicable law or agreed to in writing, software
102+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
103+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
104+# License for the specific language governing permissions and limitations
105+# under the License.
106+
107+from webob import exc
108+
109+from nova import console
110+from nova import exception
111+from nova import wsgi
112+from nova.api.openstack import faults
113+
114+
115+def _translate_keys(cons):
116+ """Coerces a console instance into proper dictionary format """
117+ pool = cons['pool']
118+ info = {'id': cons['id'],
119+ 'console_type': pool['console_type']}
120+ return dict(console=info)
121+
122+
123+def _translate_detail_keys(cons):
124+ """Coerces a console instance into proper dictionary format with
125+ correctly mapped attributes """
126+ pool = cons['pool']
127+ info = {'id': cons['id'],
128+ 'console_type': pool['console_type'],
129+ 'password': cons['password'],
130+ 'port': cons['port'],
131+ 'host': pool['public_hostname']}
132+ return dict(console=info)
133+
134+
135+class Controller(wsgi.Controller):
136+ """The Consoles Controller for the Openstack API"""
137+
138+ _serialization_metadata = {
139+ 'application/xml': {
140+ 'attributes': {
141+ 'console': []}}}
142+
143+ def __init__(self):
144+ self.console_api = console.API()
145+ super(Controller, self).__init__()
146+
147+ def index(self, req, server_id):
148+ """Returns a list of consoles for this instance"""
149+ consoles = self.console_api.get_consoles(
150+ req.environ['nova.context'],
151+ int(server_id))
152+ return dict(consoles=[_translate_keys(console)
153+ for console in consoles])
154+
155+ def create(self, req, server_id):
156+ """Creates a new console"""
157+ #info = self._deserialize(req.body, req)
158+ self.console_api.create_console(
159+ req.environ['nova.context'],
160+ int(server_id))
161+
162+ def show(self, req, server_id, id):
163+ """Shows in-depth information on a specific console"""
164+ try:
165+ console = self.console_api.get_console(
166+ req.environ['nova.context'],
167+ int(server_id),
168+ int(id))
169+ except exception.NotFound:
170+ return faults.Fault(exc.HTTPNotFound())
171+ return _translate_detail_keys(console)
172+
173+ def update(self, req, server_id, id):
174+ """You can't update a console"""
175+ raise faults.Fault(exc.HTTPNotImplemented())
176+
177+ def delete(self, req, server_id, id):
178+ """Deletes a console"""
179+ try:
180+ self.console_api.delete_console(req.environ['nova.context'],
181+ int(server_id),
182+ int(id))
183+ except exception.NotFound:
184+ return faults.Fault(exc.HTTPNotFound())
185+ return exc.HTTPAccepted()
186
187=== modified file 'nova/compute/manager.py'
188--- nova/compute/manager.py 2011-01-08 14:35:50 +0000
189+++ nova/compute/manager.py 2011-01-10 21:04:17 +0000
190@@ -35,6 +35,8 @@
191 """
192
193 import datetime
194+import logging
195+import socket
196 import functools
197
198 from nova import exception
199@@ -52,6 +54,9 @@
200 'Driver to use for controlling virtualization')
201 flags.DEFINE_string('stub_network', False,
202 'Stub network related code')
203+flags.DEFINE_string('console_host', socket.gethostname(),
204+ 'Console proxy host to use to connect to instances on'
205+ 'this host.')
206
207 LOG = logging.getLogger('nova.compute.manager')
208
209@@ -122,6 +127,15 @@
210 state = power_state.NOSTATE
211 self.db.instance_set_state(context, instance_id, state)
212
213+ def get_console_topic(self, context, **_kwargs):
214+ """Retrieves the console host for a project on this host
215+ Currently this is just set in the flags for each compute
216+ host."""
217+ #TODO(mdragon): perhaps make this variable by console_type?
218+ return self.db.queue_get_for(context,
219+ FLAGS.console_topic,
220+ FLAGS.console_host)
221+
222 def get_network_topic(self, context, **_kwargs):
223 """Retrieves the network host for a project on this host"""
224 # TODO(vish): This method should be memoized. This will make
225@@ -136,6 +150,9 @@
226 FLAGS.network_topic,
227 host)
228
229+ def get_console_pool_info(self, context, console_type):
230+ return self.driver.get_console_pool_info(console_type)
231+
232 @exception.wrap_exception
233 def refresh_security_group_rules(self, context,
234 security_group_id, **_kwargs):
235
236=== added directory 'nova/console'
237=== added file 'nova/console/__init__.py'
238--- nova/console/__init__.py 1970-01-01 00:00:00 +0000
239+++ nova/console/__init__.py 2011-01-10 21:04:17 +0000
240@@ -0,0 +1,13 @@
241+# vim: tabstop=4 shiftwidth=4 softtabstop=4
242+
243+"""
244+:mod:`nova.console` -- Console Prxy to set up VM console access (i.e. with xvp)
245+=====================================================
246+
247+.. automodule:: nova.console
248+ :platform: Unix
249+ :synopsis: Wrapper around console proxies such as xvp to set up
250+ multitenant VM console access
251+.. moduleauthor:: Monsyne Dragon <mdragon@rackspace.com>
252+"""
253+from nova.console.api import API
254
255=== added file 'nova/console/api.py'
256--- nova/console/api.py 1970-01-01 00:00:00 +0000
257+++ nova/console/api.py 2011-01-10 21:04:17 +0000
258@@ -0,0 +1,75 @@
259+# vim: tabstop=4 shiftwidth=4 softtabstop=4
260+
261+# Copyright (c) 2010 Openstack, LLC.
262+# All Rights Reserved.
263+#
264+# Licensed under the Apache License, Version 2.0 (the "License"); you may
265+# not use this file except in compliance with the License. You may obtain
266+# a copy of the License at
267+#
268+# http://www.apache.org/licenses/LICENSE-2.0
269+#
270+# Unless required by applicable law or agreed to in writing, software
271+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
272+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
273+# License for the specific language governing permissions and limitations
274+# under the License.
275+
276+"""
277+Handles ConsoleProxy API requests
278+"""
279+
280+from nova import exception
281+from nova.db import base
282+
283+
284+from nova import flags
285+from nova import rpc
286+
287+
288+FLAGS = flags.FLAGS
289+
290+
291+class API(base.Base):
292+ """API for spining up or down console proxy connections"""
293+
294+ def __init__(self, **kwargs):
295+ super(API, self).__init__(**kwargs)
296+
297+ def get_consoles(self, context, instance_id):
298+ return self.db.console_get_all_by_instance(context, instance_id)
299+
300+ def get_console(self, context, instance_id, console_id):
301+ return self.db.console_get(context, console_id, instance_id)
302+
303+ def delete_console(self, context, instance_id, console_id):
304+ console = self.db.console_get(context,
305+ console_id,
306+ instance_id)
307+ pool = console['pool']
308+ rpc.cast(context,
309+ self.db.queue_get_for(context,
310+ FLAGS.console_topic,
311+ pool['host']),
312+ {"method": "remove_console",
313+ "args": {"console_id": console['id']}})
314+
315+ def create_console(self, context, instance_id):
316+ instance = self.db.instance_get(context, instance_id)
317+ #NOTE(mdragon): If we wanted to return this the console info
318+ # here, as we would need to do a call.
319+ # They can just do an index later to fetch
320+ # console info. I am not sure which is better
321+ # here.
322+ rpc.cast(context,
323+ self._get_console_topic(context, instance['host']),
324+ {"method": "add_console",
325+ "args": {"instance_id": instance_id}})
326+
327+ def _get_console_topic(self, context, instance_host):
328+ topic = self.db.queue_get_for(context,
329+ FLAGS.compute_topic,
330+ instance_host)
331+ return rpc.call(context,
332+ topic,
333+ {"method": "get_console_topic", "args": {'fake': 1}})
334
335=== added file 'nova/console/fake.py'
336--- nova/console/fake.py 1970-01-01 00:00:00 +0000
337+++ nova/console/fake.py 2011-01-10 21:04:17 +0000
338@@ -0,0 +1,58 @@
339+# vim: tabstop=4 shiftwidth=4 softtabstop=4
340+
341+# Copyright (c) 2010 Openstack, LLC.
342+# All Rights Reserved.
343+#
344+# Licensed under the Apache License, Version 2.0 (the "License"); you may
345+# not use this file except in compliance with the License. You may obtain
346+# a copy of the License at
347+#
348+# http://www.apache.org/licenses/LICENSE-2.0
349+#
350+# Unless required by applicable law or agreed to in writing, software
351+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
352+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
353+# License for the specific language governing permissions and limitations
354+# under the License.
355+
356+"""
357+Fake ConsoleProxy driver for tests.
358+"""
359+
360+from nova import exception
361+
362+
363+class FakeConsoleProxy(object):
364+ """Fake ConsoleProxy driver."""
365+
366+ @property
367+ def console_type(self):
368+ return "fake"
369+
370+ def setup_console(self, context, console):
371+ """Sets up actual proxies"""
372+ pass
373+
374+ def teardown_console(self, context, console):
375+ """Tears down actual proxies"""
376+ pass
377+
378+ def init_host(self):
379+ """Start up any config'ed consoles on start"""
380+ pass
381+
382+ def generate_password(self, length=8):
383+ """Returns random console password"""
384+ return "fakepass"
385+
386+ def get_port(self, context):
387+ """get available port for consoles that need one"""
388+ return 5999
389+
390+ def fix_pool_password(self, password):
391+ """Trim password to length, and any other massaging"""
392+ return password
393+
394+ def fix_console_password(self, password):
395+ """Trim password to length, and any other massaging"""
396+ return password
397
398=== added file 'nova/console/manager.py'
399--- nova/console/manager.py 1970-01-01 00:00:00 +0000
400+++ nova/console/manager.py 2011-01-10 21:04:17 +0000
401@@ -0,0 +1,127 @@
402+# vim: tabstop=4 shiftwidth=4 softtabstop=4
403+
404+# Copyright (c) 2010 Openstack, LLC.
405+# All Rights Reserved.
406+#
407+# Licensed under the Apache License, Version 2.0 (the "License"); you may
408+# not use this file except in compliance with the License. You may obtain
409+# a copy of the License at
410+#
411+# http://www.apache.org/licenses/LICENSE-2.0
412+#
413+# Unless required by applicable law or agreed to in writing, software
414+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
415+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
416+# License for the specific language governing permissions and limitations
417+# under the License.
418+
419+"""
420+Console Proxy Service
421+"""
422+
423+import functools
424+import logging
425+import socket
426+
427+from nova import exception
428+from nova import flags
429+from nova import manager
430+from nova import rpc
431+from nova import utils
432+
433+FLAGS = flags.FLAGS
434+flags.DEFINE_string('console_driver',
435+ 'nova.console.xvp.XVPConsoleProxy',
436+ 'Driver to use for the console proxy')
437+flags.DEFINE_boolean('stub_compute', False,
438+ 'Stub calls to compute worker for tests')
439+flags.DEFINE_string('console_public_hostname',
440+ socket.gethostname(),
441+ 'Publicly visable name for this console host')
442+
443+
444+class ConsoleProxyManager(manager.Manager):
445+
446+ """ Sets up and tears down any proxy connections needed for accessing
447+ instance consoles securely"""
448+
449+ def __init__(self, console_driver=None, *args, **kwargs):
450+ if not console_driver:
451+ console_driver = FLAGS.console_driver
452+ self.driver = utils.import_object(console_driver)
453+ super(ConsoleProxyManager, self).__init__(*args, **kwargs)
454+ self.driver.host = self.host
455+
456+ def init_host(self):
457+ self.driver.init_host()
458+
459+ @exception.wrap_exception
460+ def add_console(self, context, instance_id, password=None,
461+ port=None, **kwargs):
462+ instance = self.db.instance_get(context, instance_id)
463+ host = instance['host']
464+ name = instance['name']
465+ pool = self.get_pool_for_instance_host(context, host)
466+ try:
467+ console = self.db.console_get_by_pool_instance(context,
468+ pool['id'],
469+ instance_id)
470+ except exception.NotFound:
471+ logging.debug("Adding console")
472+ if not password:
473+ password = self.driver.generate_password()
474+ if not port:
475+ port = self.driver.get_port(context)
476+ console_data = {'instance_name': name,
477+ 'instance_id': instance_id,
478+ 'password': password,
479+ 'pool_id': pool['id']}
480+ if port:
481+ console_data['port'] = port
482+ console = self.db.console_create(context, console_data)
483+ self.driver.setup_console(context, console)
484+ return console['id']
485+
486+ @exception.wrap_exception
487+ def remove_console(self, context, console_id, **_kwargs):
488+ try:
489+ console = self.db.console_get(context, console_id)
490+ except exception.NotFound:
491+ logging.debug(_('Tried to remove non-existant console '
492+ '%(console_id)s.') %
493+ {'console_id': console_id})
494+ return
495+ self.db.console_delete(context, console_id)
496+ self.driver.teardown_console(context, console)
497+
498+ def get_pool_for_instance_host(self, context, instance_host):
499+ context = context.elevated()
500+ console_type = self.driver.console_type
501+ try:
502+ pool = self.db.console_pool_get_by_host_type(context,
503+ instance_host,
504+ self.host,
505+ console_type)
506+ except exception.NotFound:
507+ #NOTE(mdragon): Right now, the only place this info exists is the
508+ # compute worker's flagfile, at least for
509+ # xenserver. Thus we ned to ask.
510+ if FLAGS.stub_compute:
511+ pool_info = {'address': '127.0.0.1',
512+ 'username': 'test',
513+ 'password': '1234pass'}
514+ else:
515+ pool_info = rpc.call(context,
516+ self.db.queue_get_for(context,
517+ FLAGS.compute_topic,
518+ instance_host),
519+ {"method": "get_console_pool_info",
520+ "args": {"console_type": console_type}})
521+ pool_info['password'] = self.driver.fix_pool_password(
522+ pool_info['password'])
523+ pool_info['host'] = self.host
524+ pool_info['public_hostname'] = FLAGS.console_public_hostname
525+ pool_info['console_type'] = self.driver.console_type
526+ pool_info['compute_host'] = instance_host
527+ pool = self.db.console_pool_create(context, pool_info)
528+ return pool
529
530=== added file 'nova/console/xvp.conf.template'
531--- nova/console/xvp.conf.template 1970-01-01 00:00:00 +0000
532+++ nova/console/xvp.conf.template 2011-01-10 21:04:17 +0000
533@@ -0,0 +1,16 @@
534+# One time password use with time window
535+OTP ALLOW IPCHECK HTTP 60
536+#if $multiplex_port
537+MULTIPLEX $multiplex_port
538+#end if
539+
540+#for $pool in $pools
541+POOL $pool.address
542+ DOMAIN $pool.address
543+ MANAGER root $pool.password
544+ HOST $pool.address
545+ VM - dummy 0123456789ABCDEF
546+ #for $console in $pool.consoles
547+ VM #if $multiplex_port then '-' else $console.port # $console.instance_name $pass_encode($console.password)
548+ #end for
549+#end for
550
551=== added file 'nova/console/xvp.py'
552--- nova/console/xvp.py 1970-01-01 00:00:00 +0000
553+++ nova/console/xvp.py 2011-01-10 21:04:17 +0000
554@@ -0,0 +1,194 @@
555+# vim: tabstop=4 shiftwidth=4 softtabstop=4
556+
557+# Copyright (c) 2010 Openstack, LLC.
558+# All Rights Reserved.
559+#
560+# Licensed under the Apache License, Version 2.0 (the "License"); you may
561+# not use this file except in compliance with the License. You may obtain
562+# a copy of the License at
563+#
564+# http://www.apache.org/licenses/LICENSE-2.0
565+#
566+# Unless required by applicable law or agreed to in writing, software
567+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
568+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
569+# License for the specific language governing permissions and limitations
570+# under the License.
571+
572+"""
573+XVP (Xenserver VNC Proxy) driver.
574+"""
575+
576+import fcntl
577+import logging
578+import os
579+import signal
580+import subprocess
581+
582+from Cheetah.Template import Template
583+
584+from nova import context
585+from nova import db
586+from nova import exception
587+from nova import flags
588+from nova import utils
589+
590+flags.DEFINE_string('console_xvp_conf_template',
591+ utils.abspath('console/xvp.conf.template'),
592+ 'XVP conf template')
593+flags.DEFINE_string('console_xvp_conf',
594+ '/etc/xvp.conf',
595+ 'generated XVP conf file')
596+flags.DEFINE_string('console_xvp_pid',
597+ '/var/run/xvp.pid',
598+ 'XVP master process pid file')
599+flags.DEFINE_string('console_xvp_log',
600+ '/var/log/xvp.log',
601+ 'XVP log file')
602+flags.DEFINE_integer('console_xvp_multiplex_port',
603+ 5900,
604+ "port for XVP to multiplex VNC connections on")
605+FLAGS = flags.FLAGS
606+
607+
608+class XVPConsoleProxy(object):
609+ """Sets up XVP config, and manages xvp daemon"""
610+
611+ def __init__(self):
612+ self.xvpconf_template = open(FLAGS.console_xvp_conf_template).read()
613+ self.host = FLAGS.host # default, set by manager.
614+ super(XVPConsoleProxy, self).__init__()
615+
616+ @property
617+ def console_type(self):
618+ return "vnc+xvp"
619+
620+ def get_port(self, context):
621+ """get available port for consoles that need one"""
622+ #TODO(mdragon): implement port selection for non multiplex ports,
623+ # we are not using that, but someone else may want
624+ # it.
625+ return FLAGS.console_xvp_multiplex_port
626+
627+ def setup_console(self, context, console):
628+ """Sets up actual proxies"""
629+ self._rebuild_xvp_conf(context.elevated())
630+
631+ def teardown_console(self, context, console):
632+ """Tears down actual proxies"""
633+ self._rebuild_xvp_conf(context.elevated())
634+
635+ def init_host(self):
636+ """Start up any config'ed consoles on start"""
637+ ctxt = context.get_admin_context()
638+ self._rebuild_xvp_conf(ctxt)
639+
640+ def fix_pool_password(self, password):
641+ """Trim password to length, and encode"""
642+ return self._xvp_encrypt(password, is_pool_password=True)
643+
644+ def fix_console_password(self, password):
645+ """Trim password to length, and encode"""
646+ return self._xvp_encrypt(password)
647+
648+ def generate_password(self, length=8):
649+ """Returns random console password"""
650+ return os.urandom(length * 2).encode('base64')[:length]
651+
652+ def _rebuild_xvp_conf(self, context):
653+ logging.debug("Rebuilding xvp conf")
654+ pools = [pool for pool in
655+ db.console_pool_get_all_by_host_type(context, self.host,
656+ self.console_type)
657+ if pool['consoles']]
658+ if not pools:
659+ logging.debug("No console pools!")
660+ self._xvp_stop()
661+ return
662+ conf_data = {'multiplex_port': FLAGS.console_xvp_multiplex_port,
663+ 'pools': pools,
664+ 'pass_encode': self.fix_console_password}
665+ config = str(Template(self.xvpconf_template, searchList=[conf_data]))
666+ self._write_conf(config)
667+ self._xvp_restart()
668+
669+ def _write_conf(self, config):
670+ logging.debug('Re-wrote %s' % FLAGS.console_xvp_conf)
671+ with open(FLAGS.console_xvp_conf, 'w') as cfile:
672+ cfile.write(config)
673+
674+ def _xvp_stop(self):
675+ logging.debug("Stopping xvp")
676+ pid = self._xvp_pid()
677+ if not pid:
678+ return
679+ try:
680+ os.kill(pid, signal.SIGTERM)
681+ except OSError:
682+ #if it's already not running, no problem.
683+ pass
684+
685+ def _xvp_start(self):
686+ if self._xvp_check_running():
687+ return
688+ logging.debug("Starting xvp")
689+ try:
690+ utils.execute('xvp -p %s -c %s -l %s' %
691+ (FLAGS.console_xvp_pid,
692+ FLAGS.console_xvp_conf,
693+ FLAGS.console_xvp_log))
694+ except exception.ProcessExecutionError, err:
695+ logging.error("Error starting xvp: %s" % err)
696+
697+ def _xvp_restart(self):
698+ logging.debug("Restarting xvp")
699+ if not self._xvp_check_running():
700+ logging.debug("xvp not running...")
701+ self._xvp_start()
702+ else:
703+ pid = self._xvp_pid()
704+ os.kill(pid, signal.SIGUSR1)
705+
706+ def _xvp_pid(self):
707+ try:
708+ with open(FLAGS.console_xvp_pid, 'r') as pidfile:
709+ pid = int(pidfile.read())
710+ except IOError:
711+ return None
712+ except ValueError:
713+ return None
714+ return pid
715+
716+ def _xvp_check_running(self):
717+ pid = self._xvp_pid()
718+ if not pid:
719+ return False
720+ try:
721+ os.kill(pid, 0)
722+ except OSError:
723+ return False
724+ return True
725+
726+ def _xvp_encrypt(self, password, is_pool_password=False):
727+ """Call xvp to obfuscate passwords for config file.
728+
729+ Args:
730+ - password: the password to encode, max 8 char for vm passwords,
731+ and 16 chars for pool passwords. passwords will
732+ be trimmed to max len before encoding.
733+ - is_pool_password: True if this this is the XenServer api password
734+ False if it's a VM console password
735+ (xvp uses different keys and max lengths for pool passwords)
736+
737+ Note that xvp's obfuscation should not be considered 'real' encryption.
738+ It simply DES encrypts the passwords with static keys plainly viewable
739+ in the xvp source code."""
740+ maxlen = 8
741+ flag = '-e'
742+ if is_pool_password:
743+ maxlen = 16
744+ flag = '-x'
745+ #xvp will blow up on passwords that are too long (mdragon)
746+ password = password[:maxlen]
747+ out, err = utils.execute('xvp %s' % flag, process_input=password)
748+ return out.strip()
749
750=== modified file 'nova/db/api.py'
751--- nova/db/api.py 2011-01-10 15:57:13 +0000
752+++ nova/db/api.py 2011-01-10 21:04:17 +0000
753@@ -906,3 +906,57 @@
754
755 """
756 return IMPL.host_get_networks(context, host)
757+
758+
759+##################
760+
761+
762+def console_pool_create(context, values):
763+ """Create console pool."""
764+ return IMPL.console_pool_create(context, values)
765+
766+
767+def console_pool_get(context, pool_id):
768+ """Get a console pool."""
769+ return IMPL.console_pool_get(context, pool_id)
770+
771+
772+def console_pool_get_by_host_type(context, compute_host, proxy_host,
773+ console_type):
774+ """Fetch a console pool for a given proxy host, compute host, and type."""
775+ return IMPL.console_pool_get_by_host_type(context,
776+ compute_host,
777+ proxy_host,
778+ console_type)
779+
780+
781+def console_pool_get_all_by_host_type(context, host, console_type):
782+ """Fetch all pools for given proxy host and type."""
783+ return IMPL.console_pool_get_all_by_host_type(context,
784+ host,
785+ console_type)
786+
787+
788+def console_create(context, values):
789+ """Create a console."""
790+ return IMPL.console_create(context, values)
791+
792+
793+def console_delete(context, console_id):
794+ """Delete a console."""
795+ return IMPL.console_delete(context, console_id)
796+
797+
798+def console_get_by_pool_instance(context, pool_id, instance_id):
799+ """Get console entry for a given instance and pool."""
800+ return IMPL.console_get_by_pool_instance(context, pool_id, instance_id)
801+
802+
803+def console_get_all_by_instance(context, instance_id):
804+ """Get consoles for a given instance."""
805+ return IMPL.console_get_all_by_instance(context, instance_id)
806+
807+
808+def console_get(context, console_id, instance_id=None):
809+ """Get a specific console (possibly on a given instance)."""
810+ return IMPL.console_get(context, console_id, instance_id)
811
812=== modified file 'nova/db/sqlalchemy/api.py'
813--- nova/db/sqlalchemy/api.py 2011-01-10 15:57:13 +0000
814+++ nova/db/sqlalchemy/api.py 2011-01-10 21:04:17 +0000
815@@ -1863,3 +1863,111 @@
816 filter_by(deleted=False).\
817 filter_by(host=host).\
818 all()
819+
820+
821+##################
822+
823+
824+def console_pool_create(context, values):
825+ pool = models.ConsolePool()
826+ pool.update(values)
827+ pool.save()
828+ return pool
829+
830+
831+def console_pool_get(context, pool_id):
832+ session = get_session()
833+ result = session.query(models.ConsolePool).\
834+ filter_by(deleted=False).\
835+ filter_by(id=pool_id).\
836+ first()
837+ if not result:
838+ raise exception.NotFound(_("No console pool with id %(pool_id)s") %
839+ {'pool_id': pool_id})
840+
841+ return result
842+
843+
844+def console_pool_get_by_host_type(context, compute_host, host,
845+ console_type):
846+ session = get_session()
847+ result = session.query(models.ConsolePool).\
848+ filter_by(host=host).\
849+ filter_by(console_type=console_type).\
850+ filter_by(compute_host=compute_host).\
851+ filter_by(deleted=False).\
852+ options(joinedload('consoles')).\
853+ first()
854+ if not result:
855+ raise exception.NotFound(_('No console pool of type %(type)s '
856+ 'for compute host %(compute_host)s '
857+ 'on proxy host %(host)s') %
858+ {'type': console_type,
859+ 'compute_host': compute_host,
860+ 'host': host})
861+ return result
862+
863+
864+def console_pool_get_all_by_host_type(context, host, console_type):
865+ session = get_session()
866+ return session.query(models.ConsolePool).\
867+ filter_by(host=host).\
868+ filter_by(console_type=console_type).\
869+ filter_by(deleted=False).\
870+ options(joinedload('consoles')).\
871+ all()
872+
873+
874+def console_create(context, values):
875+ console = models.Console()
876+ console.update(values)
877+ console.save()
878+ return console
879+
880+
881+def console_delete(context, console_id):
882+ session = get_session()
883+ with session.begin():
884+ # consoles are meant to be transient. (mdragon)
885+ session.execute('delete from consoles '
886+ 'where id=:id', {'id': console_id})
887+
888+
889+def console_get_by_pool_instance(context, pool_id, instance_id):
890+ session = get_session()
891+ result = session.query(models.Console).\
892+ filter_by(pool_id=pool_id).\
893+ filter_by(instance_id=instance_id).\
894+ options(joinedload('pool')).\
895+ first()
896+ if not result:
897+ raise exception.NotFound(_('No console for instance %(instance_id)s '
898+ 'in pool %(pool_id)s') %
899+ {'instance_id': instance_id,
900+ 'pool_id': pool_id})
901+ return result
902+
903+
904+def console_get_all_by_instance(context, instance_id):
905+ session = get_session()
906+ results = session.query(models.Console).\
907+ filter_by(instance_id=instance_id).\
908+ options(joinedload('pool')).\
909+ all()
910+ return results
911+
912+
913+def console_get(context, console_id, instance_id=None):
914+ session = get_session()
915+ query = session.query(models.Console).\
916+ filter_by(id=console_id)
917+ if instance_id:
918+ query = query.filter_by(instance_id=instance_id)
919+ result = query.options(joinedload('pool')).first()
920+ if not result:
921+ idesc = (_("on instance %s") % instance_id) if instance_id else ""
922+ raise exception.NotFound(_("No console with id %(console_id)s"
923+ " %(instance)s") %
924+ {'instance': idesc,
925+ 'console_id': console_id})
926+ return result
927
928=== modified file 'nova/db/sqlalchemy/models.py'
929--- nova/db/sqlalchemy/models.py 2011-01-06 22:35:48 +0000
930+++ nova/db/sqlalchemy/models.py 2011-01-10 21:04:17 +0000
931@@ -540,6 +540,31 @@
932 host = Column(String(255)) # , ForeignKey('hosts.id'))
933
934
935+class ConsolePool(BASE, NovaBase):
936+ """Represents pool of consoles on the same physical node."""
937+ __tablename__ = 'console_pools'
938+ id = Column(Integer, primary_key=True)
939+ address = Column(String(255))
940+ username = Column(String(255))
941+ password = Column(String(255))
942+ console_type = Column(String(255))
943+ public_hostname = Column(String(255))
944+ host = Column(String(255))
945+ compute_host = Column(String(255))
946+
947+
948+class Console(BASE, NovaBase):
949+ """Represents a console session for an instance."""
950+ __tablename__ = 'consoles'
951+ id = Column(Integer, primary_key=True)
952+ instance_name = Column(String(255))
953+ instance_id = Column(Integer)
954+ password = Column(String(255))
955+ port = Column(Integer, nullable=True)
956+ pool_id = Column(Integer, ForeignKey('console_pools.id'))
957+ pool = relationship(ConsolePool, backref=backref('consoles'))
958+
959+
960 def register_models():
961 """Register Models and create metadata.
962
963@@ -552,7 +577,7 @@
964 Volume, ExportDevice, IscsiTarget, FixedIp, FloatingIp,
965 Network, SecurityGroup, SecurityGroupIngressRule,
966 SecurityGroupInstanceAssociation, AuthToken, User,
967- Project, Certificate) # , Image, Host
968+ Project, Certificate, ConsolePool, Console) # , Image, Host
969 engine = create_engine(FLAGS.sql_connection, echo=False)
970 for model in models:
971 model.metadata.create_all(engine)
972
973=== modified file 'nova/flags.py'
974--- nova/flags.py 2011-01-04 05:23:35 +0000
975+++ nova/flags.py 2011-01-10 21:04:17 +0000
976@@ -216,6 +216,8 @@
977 DEFINE_string('s3_host', '127.0.0.1', 's3 host (for infrastructure)')
978 DEFINE_string('s3_dmz', '127.0.0.1', 's3 dmz ip (for instances)')
979 DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on')
980+DEFINE_string('console_topic', 'console',
981+ 'the topic console proxy nodes listen on')
982 DEFINE_string('scheduler_topic', 'scheduler',
983 'the topic scheduler nodes listen on')
984 DEFINE_string('volume_topic', 'volume', 'the topic volume nodes listen on')
985@@ -269,6 +271,8 @@
986
987 DEFINE_string('compute_manager', 'nova.compute.manager.ComputeManager',
988 'Manager for compute')
989+DEFINE_string('console_manager', 'nova.console.manager.ConsoleProxyManager',
990+ 'Manager for console proxy')
991 DEFINE_string('network_manager', 'nova.network.manager.VlanManager',
992 'Manager for network')
993 DEFINE_string('volume_manager', 'nova.volume.manager.VolumeManager',
994
995=== added file 'nova/tests/test_console.py'
996--- nova/tests/test_console.py 1970-01-01 00:00:00 +0000
997+++ nova/tests/test_console.py 2011-01-10 21:04:17 +0000
998@@ -0,0 +1,129 @@
999+# vim: tabstop=4 shiftwidth=4 softtabstop=4
1000+
1001+# Copyright (c) 2010 Openstack, LLC.
1002+# Administrator of the National Aeronautics and Space Administration.
1003+# All Rights Reserved.
1004+#
1005+# Licensed under the Apache License, Version 2.0 (the "License"); you may
1006+# not use this file except in compliance with the License. You may obtain
1007+# a copy of the License at
1008+#
1009+# http://www.apache.org/licenses/LICENSE-2.0
1010+#
1011+# Unless required by applicable law or agreed to in writing, software
1012+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
1013+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1014+# License for the specific language governing permissions and limitations
1015+# under the License.
1016+
1017+"""
1018+Tests For Console proxy.
1019+"""
1020+
1021+import datetime
1022+import logging
1023+
1024+from nova import context
1025+from nova import db
1026+from nova import exception
1027+from nova import flags
1028+from nova import test
1029+from nova import utils
1030+from nova.auth import manager
1031+from nova.console import manager as console_manager
1032+
1033+FLAGS = flags.FLAGS
1034+
1035+
1036+class ConsoleTestCase(test.TestCase):
1037+ """Test case for console proxy"""
1038+ def setUp(self):
1039+ logging.getLogger().setLevel(logging.DEBUG)
1040+ super(ConsoleTestCase, self).setUp()
1041+ self.flags(console_driver='nova.console.fake.FakeConsoleProxy',
1042+ stub_compute=True)
1043+ self.console = utils.import_object(FLAGS.console_manager)
1044+ self.manager = manager.AuthManager()
1045+ self.user = self.manager.create_user('fake', 'fake', 'fake')
1046+ self.project = self.manager.create_project('fake', 'fake', 'fake')
1047+ self.context = context.get_admin_context()
1048+ self.host = 'test_compute_host'
1049+
1050+ def tearDown(self):
1051+ self.manager.delete_user(self.user)
1052+ self.manager.delete_project(self.project)
1053+ super(ConsoleTestCase, self).tearDown()
1054+
1055+ def _create_instance(self):
1056+ """Create a test instance"""
1057+ inst = {}
1058+ #inst['host'] = self.host
1059+ #inst['name'] = 'instance-1234'
1060+ inst['image_id'] = 'ami-test'
1061+ inst['reservation_id'] = 'r-fakeres'
1062+ inst['launch_time'] = '10'
1063+ inst['user_id'] = self.user.id
1064+ inst['project_id'] = self.project.id
1065+ inst['instance_type'] = 'm1.tiny'
1066+ inst['mac_address'] = utils.generate_mac()
1067+ inst['ami_launch_index'] = 0
1068+ return db.instance_create(self.context, inst)['id']
1069+
1070+ def test_get_pool_for_instance_host(self):
1071+ pool = self.console.get_pool_for_instance_host(self.context, self.host)
1072+ self.assertEqual(pool['compute_host'], self.host)
1073+
1074+ def test_get_pool_creates_new_pool_if_needed(self):
1075+ self.assertRaises(exception.NotFound,
1076+ db.console_pool_get_by_host_type,
1077+ self.context,
1078+ self.host,
1079+ self.console.host,
1080+ self.console.driver.console_type)
1081+ pool = self.console.get_pool_for_instance_host(self.context,
1082+ self.host)
1083+ pool2 = db.console_pool_get_by_host_type(self.context,
1084+ self.host,
1085+ self.console.host,
1086+ self.console.driver.console_type)
1087+ self.assertEqual(pool['id'], pool2['id'])
1088+
1089+ def test_get_pool_does_not_create_new_pool_if_exists(self):
1090+ pool_info = {'address': '127.0.0.1',
1091+ 'username': 'test',
1092+ 'password': '1234pass',
1093+ 'host': self.console.host,
1094+ 'console_type': self.console.driver.console_type,
1095+ 'compute_host': 'sometesthostname'}
1096+ new_pool = db.console_pool_create(self.context, pool_info)
1097+ pool = self.console.get_pool_for_instance_host(self.context,
1098+ 'sometesthostname')
1099+ self.assertEqual(pool['id'], new_pool['id'])
1100+
1101+ def test_add_console(self):
1102+ instance_id = self._create_instance()
1103+ self.console.add_console(self.context, instance_id)
1104+ instance = db.instance_get(self.context, instance_id)
1105+ pool = db.console_pool_get_by_host_type(self.context,
1106+ instance['host'],
1107+ self.console.host,
1108+ self.console.driver.console_type)
1109+
1110+ console_instances = [con['instance_id'] for con in pool.consoles]
1111+ self.assert_(instance_id in console_instances)
1112+
1113+ def test_add_console_does_not_duplicate(self):
1114+ instance_id = self._create_instance()
1115+ cons1 = self.console.add_console(self.context, instance_id)
1116+ cons2 = self.console.add_console(self.context, instance_id)
1117+ self.assertEqual(cons1, cons2)
1118+
1119+ def test_remove_console(self):
1120+ instance_id = self._create_instance()
1121+ console_id = self.console.add_console(self.context, instance_id)
1122+ self.console.remove_console(self.context, console_id)
1123+
1124+ self.assertRaises(exception.NotFound,
1125+ db.console_get,
1126+ self.context,
1127+ console_id)
1128
1129=== modified file 'nova/tests/test_virt.py'
1130--- nova/tests/test_virt.py 2011-01-10 10:32:17 +0000
1131+++ nova/tests/test_virt.py 2011-01-10 21:04:17 +0000
1132@@ -249,7 +249,7 @@
1133 '-A FORWARD -o virbr0 -j REJECT --reject-with icmp-port-unreachable ',
1134 '-A FORWARD -i virbr0 -j REJECT --reject-with icmp-port-unreachable ',
1135 'COMMIT',
1136- '# Completed on Mon Dec 6 11:54:13 2010'
1137+ '# Completed on Mon Dec 6 11:54:13 2010',
1138 ]
1139
1140 def test_static_filters(self):
1141
1142=== modified file 'nova/virt/fake.py'
1143--- nova/virt/fake.py 2010-12-30 21:23:14 +0000
1144+++ nova/virt/fake.py 2011-01-10 21:04:17 +0000
1145@@ -289,6 +289,11 @@
1146 def get_console_output(self, instance):
1147 return 'FAKE CONSOLE OUTPUT'
1148
1149+ def get_console_pool_info(self, console_type):
1150+ return {'address': '127.0.0.1',
1151+ 'username': 'fakeuser',
1152+ 'password': 'fakepassword'}
1153+
1154
1155 class FakeInstance(object):
1156
1157
1158=== modified file 'nova/virt/hyperv.py'
1159--- nova/virt/hyperv.py 2011-01-07 14:46:17 +0000
1160+++ nova/virt/hyperv.py 2011-01-10 21:04:17 +0000
1161@@ -92,7 +92,7 @@
1162 'Reboot': 10,
1163 'Reset': 11,
1164 'Paused': 32768,
1165- 'Suspended': 32769
1166+ 'Suspended': 32769,
1167 }
1168
1169
1170
1171=== modified file 'nova/virt/libvirt_conn.py'
1172--- nova/virt/libvirt_conn.py 2011-01-08 14:35:50 +0000
1173+++ nova/virt/libvirt_conn.py 2011-01-10 21:04:17 +0000
1174@@ -707,6 +707,14 @@
1175 domain = self._conn.lookupByName(instance_name)
1176 return domain.interfaceStats(interface)
1177
1178+ def get_console_pool_info(self, console_type):
1179+ #TODO(mdragon): console proxy should be implemented for libvirt,
1180+ # in case someone wants to use it with kvm or
1181+ # such. For now return fake data.
1182+ return {'address': '127.0.0.1',
1183+ 'username': 'fakeuser',
1184+ 'password': 'fakepassword'}
1185+
1186 def refresh_security_group_rules(self, security_group_id):
1187 self.firewall_driver.refresh_security_group_rules(security_group_id)
1188
1189
1190=== modified file 'nova/virt/xenapi_conn.py'
1191--- nova/virt/xenapi_conn.py 2011-01-07 14:46:17 +0000
1192+++ nova/virt/xenapi_conn.py 2011-01-10 21:04:17 +0000
1193@@ -52,6 +52,7 @@
1194 """
1195
1196 import sys
1197+import urlparse
1198 import xmlrpclib
1199
1200 from eventlet import event
1201@@ -190,6 +191,12 @@
1202 """Detach volume storage to VM instance"""
1203 return self._volumeops.detach_volume(instance_name, mountpoint)
1204
1205+ def get_console_pool_info(self, console_type):
1206+ xs_url = urlparse.urlparse(FLAGS.xenapi_connection_url)
1207+ return {'address': xs_url.netloc,
1208+ 'username': FLAGS.xenapi_connection_username,
1209+ 'password': FLAGS.xenapi_connection_password}
1210+
1211
1212 class XenAPISession(object):
1213 """The session to invoke XenAPI SDK calls"""