Skip to content
Browse files

add in xs-console worker and tests.

  • Loading branch information...
1 parent 675ca7c commit b437a98738c7a564205d1b27e36b844cd54445d1 @DragonDM DragonDM committed Jan 5, 2011
View
1 Authors
@@ -22,6 +22,7 @@ Joshua McKenty <jmckenty@gmail.com>
Justin Santa Barbara <justin@fathomdb.com>
Matt Dietz <matt.dietz@rackspace.com>
Michael Gundlach <michael.gundlach@rackspace.com>
+Monsyne Dragon <mdragon@rackspace.com>
Monty Taylor <mordred@inaugust.com>
Paul Voccio <paul@openstack.org>
Rick Clark <rick@openstack.org>
View
5 bin/nova-combined
@@ -56,11 +56,12 @@ if __name__ == '__main__':
compute = service.Service.create(binary='nova-compute')
network = service.Service.create(binary='nova-network')
- volume = service.Service.create(binary='nova-volume')
+# volume = service.Service.create(binary='nova-volume')
scheduler = service.Service.create(binary='nova-scheduler')
#objectstore = service.Service.create(binary='nova-objectstore')
- service.serve(compute, network, volume, scheduler)
+# service.serve(compute, network, volume, scheduler)
+ service.serve(compute, network, scheduler)
server = wsgi.Server()
server.start(api.API('os'), FLAGS.osapi_port, host=FLAGS.osapi_host)
View
44 bin/nova-console
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2010 Openstack, LLC.
+# All Rights Reserved.
+#
+# 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.
+
+"""Starter script for Nova Console Proxy."""
+
+import eventlet
+eventlet.monkey_patch()
+
+import gettext
+import os
+import sys
+
+# If ../nova/__init__.py exists, add ../ to Python search path, so that
+# it will override what happens to be installed in /usr/(local/)lib/python...
+possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
+ os.pardir,
+ os.pardir))
+if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
+ sys.path.insert(0, possible_topdir)
+
+gettext.install('nova', unicode=1)
+
+from nova import service
+from nova import utils
+
+if __name__ == '__main__':
+ utils.default_flagfile()
+ service.serve()
+ service.wait()
View
5 nova/compute/manager.py
@@ -99,6 +99,11 @@ def get_network_topic(self, context, **_kwargs):
FLAGS.network_topic,
host)
+
+ def get_console_pool_info(self, context, console_type):
+ return self.driver.get_console_pool_info(console_type)
+
+
@exception.wrap_exception
def refresh_security_group(self, context, security_group_id, **_kwargs):
"""This call passes stright through to the virtualization driver."""
View
11 nova/console/__init__.py
@@ -0,0 +1,11 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+"""
+:mod:`nova.console` -- Console Prxy to set up VM console access (i.e. with xvp)
+=====================================================
+
+.. automodule:: nova.console
+ :platform: Unix
+ :synopsis: Wrapper around console proxies such as xvp to set up multitenant VM console access
+.. moduleauthor:: Monsyne Dragon <mdragon@rackspace.com>
+"""
View
59 nova/console/driver.py
@@ -0,0 +1,59 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2010 Openstack, LLC.
+# All Rights Reserved.
+#
+# 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.
+
+"""
+ConsoleProxy base class that all ConsoleProxies should inherit from
+"""
+
+from nova import exception
+
+
+class ConsoleProxy(object):
+ """The base class for all ConsoleProxy driver classes."""
+
+ @property
+ def console_type(self):
+ raise NotImplementedError("Must specify type in subclass")
+
+ def setup_console(self, context, console):
+ """Sets up actual proxies"""
+ raise NotImplementedError("Must implement setup in subclass")
+
+ def teardown_console(self, context, console):
+ """Tears down actual proxies"""
+ raise NotImplementedError("Must implement teardown in subclass")
+
+ def init_host(self):
+ """Start up any config'ed consoles on start"""
+ pass
+
+ def generate_password(self, length=8):
+ """Returns random console password"""
+ return os.urandom(length*2).encode('base64')[:length]
+
+ def get_port(self, context):
+ """get available port for consoles that need one"""
+ return None
+
+ def fix_pool_password(self, password):
+ """Trim password to length, and any other massaging"""
+ return password
+
+ def fix_console_password(self, password):
+ """Trim password to length, and any other massaging"""
+ return password
+
View
59 nova/console/fake.py
@@ -0,0 +1,59 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2010 Openstack, LLC.
+# All Rights Reserved.
+#
+# 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.
+
+"""
+Fake ConsoleProxy driver for tests.
+"""
+
+from nova import exception
+from nova.console import driver
+
+class FakeConsoleProxy(driver.ConsoleProxy):
+ """Fake ConsoleProxy driver."""
+
+ @property
+ def console_type(self):
+ return "fake"
+
+ def setup_console(self, context, console):
+ """Sets up actual proxies"""
+ pass
+
+ def teardown_console(self, context, console):
+ """Tears down actual proxies"""
+ pass
+
+ def init_host(self):
+ """Start up any config'ed consoles on start"""
+ pass
+
+ def generate_password(self, length=8):
+ """Returns random console password"""
+ return "fakepass"
+
+ def get_port(self, context):
+ """get available port for consoles that need one"""
+ return 5999
+
+ def fix_pool_password(self, password):
+ """Trim password to length, and any other massaging"""
+ return password
+
+ def fix_console_password(self, password):
+ """Trim password to length, and any other massaging"""
+ return password
+
View
130 nova/console/manager.py
@@ -0,0 +1,130 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2010 Openstack, LLC.
+# All Rights Reserved.
+#
+# 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.
+
+"""
+Console Proxy Service
+"""
+
+import logging
+import functools
+
+from nova import exception
+from nova import flags
+from nova import manager
+from nova import rpc
+from nova import utils
+
+FLAGS = flags.FLAGS
+flags.DEFINE_string('console_driver',
+ 'nova.console.xvp.XVPConsoleProxy',
+ 'Driver to use for the console proxy')
+flags.DEFINE_boolean('stub_compute', False,
+ 'Stub calls to compute worker for tests')
+
+class ConsoleProxyManager(manager.Manager):
+
+ """ Sets up and tears down any proxy connections needed for accessing
+ instance consoles securely"""
+
+ def __init__(self, console_driver=None, *args, **kwargs):
+ if not console_driver:
+ console_driver = FLAGS.console_driver
+ self.driver = utils.import_object(console_driver)
+ super(ConsoleProxyManager, self).__init__(*args, **kwargs)
+ self.driver.host = self.host
+
+ def init_host(self):
+ self.driver.init_host()
+
+ @exception.wrap_exception
+ def add_console(self, context, instance_id, password = None,
+ port = None, **kwargs):
+ instance = self.db.instance_get(context, instance_id)
+ host = instance['host']
+ name = instance['name']
+ pool = self.get_pool_for_instance_host(context, host)
+ try:
+ console = self.db.console_get_by_pool_instance(context,
+ pool['id'],
+ instance_id)
+ except exception.NotFound:
+ logging.debug("Adding console")
+ if not password:
+ password = self.driver.generate_password()
+ if not port:
+ port = self.driver.get_port(context)
+ console_data = {'instance_name' : name,
+ 'instance_id' : instance_id,
+ 'password' : password,
+ 'pool_id' : pool['id']}
+ if port:
+ console_data['port'] = port
+ console = self.db.console_create(context, console_data)
+ self.driver.setup_console(context, console)
+ return console['id']
+
+ @exception.wrap_exception
+ def remove_console(self, context, instance_id, **_kwargs):
+ instance = self.db.instance_get(context, instance_id)
+ host = instance['host']
+ pool = self.get_pool_for_instance_host(context, host)
+ try:
+ console = self.db.console_get_by_pool_instance(context,
+ pool['id'],
+ instance_id)
+ except exception.NotFound:
+ logging.debug(_('Tried to remove non-existant console in pool '
+ '%(pool_id)s for instance %(instance_id)s.' %
+ {'instance_id' : instance_id,
+ 'pool_id' : pool['id']}))
+ return
+ self.db.console_delete(context, console['id'])
+ self.driver.teardown_console(context, console)
+
+
+ def get_pool_for_instance_host(self, context, instance_host):
+ context = context.elevated()
+ console_type = self.driver.console_type
+ try:
+ pool = self.db.console_pool_get_by_host_type(context,
+ instance_host,
+ self.host,
+ console_type)
+ except exception.NotFound:
+ #NOTE(mdragon): Right now, the only place this info exists is the
+ # compute worker's flagfile, at least for
+ # xenserver. Thus we ned to ask.
+ if FLAGS.stub_compute:
+ pool_info = {'address' : '127.0.0.1',
+ 'username' : 'test',
+ 'password' : '1234pass'}
+ else:
+ pool_info = rpc.call(context,
+ self.db.queue_get_for(context,
+ FLAGS.compute_topic,
+ instance_host),
+ {"method": "get_console_pool_info",
+ "args": {"console_type": console_type}})
+ pool_info['password'] = self.driver.fix_pool_password(
+ pool_info['password'])
+ pool_info['host'] = self.host
+ pool_info['console_type'] = self.driver.console_type
+ pool_info['compute_host'] = instance_host
+ pool = self.db.console_pool_create(context, pool_info)
+ return pool
+
+
View
16 nova/console/xvp.conf.template
@@ -0,0 +1,16 @@
+# One time password use with time window
+OTP ALLOW IPCHECK HTTP 60
+#if $multiplex_port
+MULTIPLEX $multiplex_port
+#end if
+
+#for $pool in $pools
+POOL $pool.address
+ DOMAIN $pool.address
+ MANAGER root $pool.password
+ HOST $pool.address
+ VM - dummy 0123456789ABCDEF
+ #for $console in $pool.consoles
+ VM #if $multiplex_port then '-' else $console.port # $console.instance_name $pass_encode($console.password)
+ #end for
+#end for
View
193 nova/console/xvp.py
@@ -0,0 +1,193 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2010 Openstack, LLC.
+# All Rights Reserved.
+#
+# 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.
+
+"""
+XVP (Xenserver VNC Proxy) driver.
+"""
+
+import fcntl
+import logging
+import os
+import signal
+import subprocess
+
+from Cheetah.Template import Template
+
+from nova import context
+from nova import db
+from nova import exception
+from nova import flags
+from nova import utils
+from nova.console import driver
+
+flags.DEFINE_string('console_xvp_conf_template',
+ utils.abspath('console/xvp.conf.template'),
+ 'XVP conf template')
+flags.DEFINE_string('console_xvp_conf',
+ '/etc/xvp.conf',
+ 'generated XVP conf file')
+flags.DEFINE_string('console_xvp_pid',
+ '/var/run/xvp.pid',
+ 'XVP master process pid file')
+flags.DEFINE_string('console_xvp_log',
+ '/var/log/xvp.log',
+ 'XVP log file')
+flags.DEFINE_integer('console_xvp_multiplex_port',
+ 5900,
+ "port for XVP to multiplex VNC connections on")
+FLAGS = flags.FLAGS
+
+class XVPConsoleProxy(driver.ConsoleProxy):
+ """Sets up XVP config, and manages xvp daemon"""
+
+ def __init__(self):
+ self.xvpconf_template = open(FLAGS.console_xvp_conf_template).read()
+ self.host = FLAGS.host #default, set by manager.
+ super(XVPConsoleProxy, self).__init__()
+
+ @property
+ def console_type(self):
+ return "vnc+xvp"
+
+ def get_port(self, context):
+ """get available port for consoles that need one"""
+ #TODO(mdragon): implement port selection for non multiplex ports,
+ # we are not using that, but someone else may want
+ # it.
+ return FLAGS.console_xvp_multiplex_port
+
+ def setup_console(self, context, console):
+ """Sets up actual proxies"""
+ self._rebuild_xvp_conf(context.elevated())
+
+ def teardown_console(self, context, console):
+ """Tears down actual proxies"""
+ self._rebuild_xvp_conf(context.elevated())
+
+ def init_host(self):
+ """Start up any config'ed consoles on start"""
+ ctxt = context.get_admin_context()
+ self._rebuild_xvp_conf(ctxt)
+
+ def fix_pool_password(self, password):
+ """Trim password to length, and encode"""
+ return self._xvp_encrypt(password, is_pool_password=True)
+
+ def fix_console_password(self, password):
+ """Trim password to length, and encode"""
+ return self._xvp_encrypt(password)
+
+ def _rebuild_xvp_conf(self, context):
+ logging.debug("Rebuilding xvp conf")
+ pools = [ pool for pool in
+ db.console_pool_get_all_by_host_type(context, self.host,
+ self.console_type)
+ if pool['consoles']]
+ if not pools:
+ logging.debug("No console pools!")
+ self._xvp_stop()
+ return
+ conf_data = {'multiplex_port': FLAGS.console_xvp_multiplex_port,
+ 'pools': pools,
+ 'pass_encode' : self.fix_console_password }
+ config = str(Template(self.xvpconf_template, searchList=[conf_data]))
+ self._write_conf(config)
+ self._xvp_restart()
+
+ def _write_conf(self, config):
+ logging.debug('Re-wrote %s' % FLAGS.console_xvp_conf)
+ with open(FLAGS.console_xvp_conf, 'w') as cfile:
+ cfile.write(config)
+
+ def _xvp_stop(self):
+ logging.debug("Stopping xvp")
+ pid = self._xvp_pid()
+ if not pid:
+ return
+ try:
+ os.kill(pid,signal.SIGTERM)
+ except OSError:
+ #if it's already not running, no problem.
+ pass
+
+ def _xvp_start(self):
+ if self._xvp_check_running():
+ return
+ logging.debug("Starting xvp")
+ try:
+ utils.execute('xvp -p %s -c %s -l %s' %
+ (FLAGS.console_xvp_pid,
+ FLAGS.console_xvp_conf,
+ FLAGS.console_xvp_log))
+ except exception.ProcessExecutionError, err:
+ logging.error("Error starting xvp: %s" % err)
+
+ def _xvp_restart(self):
+ logging.debug("Restarting xvp")
+ if not self._xvp_check_running():
+ logging.debug("xvp not running...")
+ self._xvp_start()
+ else:
+ pid = self._xvp_pid()
+ os.kill(pid, signal.SIGUSR1)
+
+ def _xvp_pid(self):
+ try:
+ with open(FLAGS.console_xvp_pid, 'r') as pidfile:
+ pid = int(pidfile.read())
+ except IOError:
+ return None
+ except ValueError:
+ return None
+ return pid
+
+ def _xvp_check_running(self):
+ pid = self._xvp_pid()
+ if not pid:
+ return False
+ try:
+ os.kill(pid,0)
+ except OSError:
+ return False
+ return True
+
+ def _xvp_encrypt(self, password, is_pool_password=False):
+ """Call xvp to obfuscate passwords for config file.
+
+ Args:
+ - password: the password to encode, max 8 char for vm passwords,
+ and 16 chars for pool passwords. passwords will
+ be trimmed to max len before encoding.
+ - is_pool_password: True if this this is the XenServer api password
+ False if it's a VM console password
+ (xvp uses different keys and max lengths for pool passwords)
+
+ Note that xvp's obfuscation should not be considered 'real' encryption.
+ It simply DES encrypts the passwords with static keys plainly viewable
+ in the xvp source code."""
+ maxlen = 8
+ flag = '-e'
+ if is_pool_password:
+ maxlen = 16
+ flag = '-x'
+ #xvp will blow up on passwords that are too long (mdragon)
+ password = password[:maxlen]
+ out, err = utils.execute('xvp %s' % flag, process_input=password)
+ #p = subprocess.Popen(['xvp', flag], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+ #out,err = p.communicate(password)
+ return out.strip()
+
View
41 nova/db/api.py
@@ -884,3 +884,44 @@ def host_get_networks(context, host):
"""
return IMPL.host_get_networks(context, host)
+
+##################
+
+def console_pool_create(context, values):
+ """Create console pool."""
+ return IMPL.console_pool_create(context, values)
+
+def console_pool_get(context, pool_id):
+ """Get a console pool."""
+ return IMPL.console_pool_get(context, pool_id)
+
+
+def console_pool_get_by_host_type(context, compute_host, proxy_host,
+ console_type):
+ """Fetch a console pool for a given proxy host, compute host, and type."""
+ return IMPL.console_pool_get_by_host_type(context,
+ compute_host,
+ proxy_host,
+ console_type)
+
+
+def console_pool_get_all_by_host_type(context, host, console_type):
+ """Fetch all pools for given proxy host and type."""
+ return IMPL.console_pool_get_all_by_host_type(context,
+ host,
+ console_type)
+
+
+def console_create(context,values):
+ """Create a console."""
+ return IMPL.console_create(context, values)
+
+def console_delete(context, console_id):
+ """Delete a console."""
+ return IMPL.console_delete(context, console_id)
+
+def console_get_by_pool_instance(context, pool_id, instance_id):
+ """Get console entry for a given instance and pool."""
+ return IMPL.console_get_by_pool_instance(context, pool_id, instance_id)
+
+
View
81 nova/db/sqlalchemy/api.py
@@ -1863,3 +1863,84 @@ def host_get_networks(context, host):
filter_by(deleted=False).\
filter_by(host=host).\
all()
+
+
+##################
+
+
+def console_pool_create(context, values):
+ pool = models.ConsolePool()
+ pool.update(values)
+ pool.save()
+ return pool
+
+
+def console_pool_get(context, pool_id):
+ session = get_session()
+ result = session.query(models.ConsolePool).\
+ filter_by(deleted=False).\
+ filter_by(id=pool_id).\
+ first()
+ if not result:
+ raise exception.NotFound(_("No console pool with id %(pool_id)s") % {'pool_id': pool_id})
+
+ return result
+
+def console_pool_get_by_host_type(context, compute_host, host,
+ console_type):
+ session = get_session()
+ result = session.query(models.ConsolePool).\
+ filter_by(host=host).\
+ filter_by(console_type=console_type).\
+ filter_by(compute_host=compute_host).\
+ filter_by(deleted=False).\
+ options(joinedload('consoles')).\
+ first()
+ if not result:
+ raise exception.NotFound(_('No console pool of type %(type)s '
+ 'for compute host %(compute_host)s '
+ 'on proxy host %(host)s') %
+ {'type' : console_type,
+ 'compute_host' : compute_host,
+ 'host' : host})
+ return result
+
+
+def console_pool_get_all_by_host_type(context, host, console_type):
+ session = get_session()
+ return session.query(models.ConsolePool).\
+ filter_by(host=host).\
+ filter_by(console_type=console_type).\
+ filter_by(deleted=False).\
+ options(joinedload('consoles')).\
+ all()
+
+
+def console_create(context, values):
+ console = models.Console()
+ console.update(values)
+ console.save()
+ return console
+
+def console_delete(context, console_id):
+ session = get_session()
+ with session.begin():
+ # consoles are meant to be transient. (mdragon)
+ session.execute('delete from consoles '
+ 'where id=:id', {'id': console_id})
+
+def console_get_by_pool_instance(context, pool_id, instance_id):
+ session = get_session()
+ result = session.query(models.Console).\
+ filter_by(pool_id=pool_id).\
+ filter_by(instance_id=instance_id).\
+ options(joinedload('pool')).\
+ first()
+ if not result:
+ raise exception.NotFound(_('No console for instance %(instance_id)s '
+ 'in pool %(pool_id)s') %
+ {'instance_id': instance_id,
+ 'pool_id': pool_id})
+ return result
+
+
View
23 nova/db/sqlalchemy/models.py
@@ -553,6 +553,27 @@ class FloatingIp(BASE, NovaBase):
project_id = Column(String(255))
host = Column(String(255)) # , ForeignKey('hosts.id'))
+class ConsolePool(BASE, NovaBase):
+ """Represents pool of consoles on the same physical node."""
+ __tablename__ = 'console_pools'
+ id = Column(Integer, primary_key=True)
+ address = Column(String(255))
+ username = Column(String(255))
+ password = Column(String(255))
+ console_type = Column(String(255))
+ host = Column(String(255))
+ compute_host = Column(String(255))
+
+class Console(BASE, NovaBase):
+ """Represents a console session for an instance."""
+ __tablename__ = 'consoles'
+ id = Column(Integer, primary_key=True)
+ instance_name = Column(String(255))
+ instance_id = Column(Integer)
+ password = Column(String(255))
+ port = Column(Integer,nullable=True)
+ pool_id = Column(Integer, ForeignKey('console_pools.id'))
+ pool = relationship(ConsolePool, backref=backref('consoles'))
def register_models():
"""Register Models and create metadata.
@@ -565,7 +586,7 @@ def register_models():
Volume, ExportDevice, IscsiTarget, FixedIp, FloatingIp,
Network, SecurityGroup, SecurityGroupIngressRule,
SecurityGroupInstanceAssociation, AuthToken, User,
- Project, Certificate) # , Image, Host
+ Project, Certificate, ConsolePool, Console) # , Image, Host
engine = create_engine(FLAGS.sql_connection, echo=False)
for model in models:
model.metadata.create_all(engine)
View
3 nova/flags.py
@@ -216,6 +216,7 @@ def DECLARE(name, module_string, flag_values=FLAGS):
DEFINE_string('s3_host', utils.get_my_ip(), 's3 host (for infrastructure)')
DEFINE_string('s3_dmz', utils.get_my_ip(), 's3 dmz ip (for instances)')
DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on')
+DEFINE_string('console_topic', 'console', 'the topic console proxy nodes listen on')
DEFINE_string('scheduler_topic', 'scheduler',
'the topic scheduler nodes listen on')
DEFINE_string('volume_topic', 'volume', 'the topic volume nodes listen on')
@@ -263,6 +264,8 @@ def DECLARE(name, module_string, flag_values=FLAGS):
DEFINE_string('compute_manager', 'nova.compute.manager.ComputeManager',
'Manager for compute')
+DEFINE_string('console_manager', 'nova.console.manager.ConsoleProxyManager',
+ 'Manager for console proxy')
DEFINE_string('network_manager', 'nova.network.manager.VlanManager',
'Manager for network')
DEFINE_string('volume_manager', 'nova.volume.manager.VolumeManager',
View
134 nova/tests/test_console.py
@@ -0,0 +1,134 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (c) 2010 Openstack, LLC.
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# 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.
+
+"""
+Tests For Console proxy.
+"""
+
+import datetime
+import logging
+
+from nova import context
+from nova import db
+from nova import exception
+from nova import flags
+from nova import test
+from nova import utils
+from nova.auth import manager
+from nova.console import manager as console_manager
+
+FLAGS = flags.FLAGS
+
+
+class ConsoleTestCase(test.TestCase):
+ """Test case for console proxy"""
+ def setUp(self):
+ logging.getLogger().setLevel(logging.DEBUG)
+ super(ConsoleTestCase, self).setUp()
+ self.flags(console_driver='nova.console.fake.FakeConsoleProxy',
+ stub_compute=True)
+ self.console = utils.import_object(FLAGS.console_manager)
+ self.manager = manager.AuthManager()
+ self.user = self.manager.create_user('fake', 'fake', 'fake')
+ self.project = self.manager.create_project('fake', 'fake', 'fake')
+ self.context = context.get_admin_context()
+ self.host = 'test_compute_host'
+
+ def tearDown(self):
+ self.manager.delete_user(self.user)
+ self.manager.delete_project(self.project)
+ super(ConsoleTestCase, self).tearDown()
+
+ def _create_instance(self):
+ """Create a test instance"""
+ inst = {}
+ #inst['host'] = self.host
+ #inst['name'] = 'instance-1234'
+ inst['image_id'] = 'ami-test'
+ inst['reservation_id'] = 'r-fakeres'
+ inst['launch_time'] = '10'
+ inst['user_id'] = self.user.id
+ inst['project_id'] = self.project.id
+ inst['instance_type'] = 'm1.tiny'
+ inst['mac_address'] = utils.generate_mac()
+ inst['ami_launch_index'] = 0
+ return db.instance_create(self.context, inst)['id']
+
+ def test_get_pool_for_instance_host(self):
+ pool = self.console.get_pool_for_instance_host(self.context, self.host)
+ self.assertEqual(pool['compute_host'], self.host)
+
+ def test_get_pool_creates_new_pool_if_needed(self):
+ self.assertRaises(exception.NotFound,
+ db.console_pool_get_by_host_type,
+ self.context,
+ self.host,
+ self.console.host,
+ self.console.driver.console_type)
+ pool = self.console.get_pool_for_instance_host(self.context,
+ self.host)
+ pool2 = db.console_pool_get_by_host_type(self.context,
+ self.host,
+ self.console.host,
+ self.console.driver.console_type)
+ self.assertEqual(pool['id'], pool2['id'])
+
+ def test_get_pool_does_not_create_new_pool_if_exists(self):
+ pool_info = {'address' : '127.0.0.1',
+ 'username' : 'test',
+ 'password' : '1234pass',
+ 'host' : self.console.host,
+ 'console_type' : self.console.driver.console_type,
+ 'compute_host' : 'sometesthostname' }
+ new_pool = db.console_pool_create(self.context, pool_info)
+ pool = self.console.get_pool_for_instance_host(self.context,
+ 'sometesthostname')
+ self.assertEqual(pool['id'], new_pool['id'])
+
+ def test_add_console(self):
+ instance_id = self._create_instance()
+ self.console.add_console(self.context, instance_id)
+ instance = db.instance_get(self.context, instance_id)
+ pool = db.console_pool_get_by_host_type(self.context,
+ instance['host'],
+ self.console.host,
+ self.console.driver.console_type)
+
+ console_instances = [con['instance_id'] for con in pool.consoles]
+ self.assert_(instance_id in console_instances)
+
+ def test_add_console_does_not_duplicate(self):
+ instance_id = self._create_instance()
+ cons1 = self.console.add_console(self.context, instance_id)
+ cons2 = self.console.add_console(self.context, instance_id)
+ self.assertEqual(cons1,cons2)
+
+ def test_remove_console(self):
+ instance_id = self._create_instance()
+ self.console.add_console(self.context, instance_id)
+ self.console.remove_console(self.context, instance_id)
+
+ instance = db.instance_get(self.context, instance_id)
+ pool = db.console_pool_get_by_host_type(self.context,
+ instance['host'],
+ self.console.host,
+ self.console.driver.console_type)
+
+ console_instances = [con['instance_id'] for con in pool.consoles]
+ self.assert_(instance_id not in console_instances)
+
View
5 nova/virt/fake.py
@@ -272,6 +272,11 @@ def interface_stats(self, instance_name, iface_id):
def get_console_output(self, instance):
return 'FAKE CONSOLE OUTPUT'
+ def get_console_pool_info(self, console_type):
+ return {'address' : '127.0.0.1',
+ 'username' : 'fakeuser',
+ 'password' : 'fakepassword'}
+
class FakeInstance(object):
View
8 nova/virt/libvirt_conn.py
@@ -671,6 +671,14 @@ def refresh_security_group(self, security_group_id):
fw = NWFilterFirewall(self._conn)
fw.ensure_security_group_filter(security_group_id)
+ def get_console_pool_info(self, console_type):
+ #TODO(mdragon): console proxy should be implemented for libvirt,
+ # in case someone wants to use it with kvm or
+ # such. For now return fake data.
+ return {'address' : '127.0.0.1',
+ 'username' : 'fakeuser',
+ 'password' : 'fakepassword'}
+
class NWFilterFirewall(object):
"""
View
7 nova/virt/xenapi_conn.py
@@ -52,6 +52,7 @@
import logging
import sys
+import urlparse
import xmlrpclib
from eventlet import event
@@ -177,6 +178,12 @@ def detach_volume(self, instance_name, mountpoint):
"""Detach volume storage to VM instance"""
return self._volumeops.detach_volume(instance_name, mountpoint)
+ def get_console_pool_info(self, console_type):
+ xs_url = urlparse.urlparse(FLAGS.xenapi_connection_url)
+ return {'address' : xs_url.netloc,
+ 'username' : FLAGS.xenapi_connection_username,
+ 'password' : FLAGS.xenapi_connection_password}
+
class XenAPISession(object):
"""The session to invoke XenAPI SDK calls"""

0 comments on commit b437a98

Please sign in to comment.
Something went wrong with that request. Please try again.