add in xs-console worker and tests.

This commit is contained in:
Monsyne Dragon 2011-01-05 14:16:14 -06:00
parent 675ca7c5f3
commit b437a98738
18 changed files with 822 additions and 3 deletions

View File

@ -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 File

@ -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)

44
bin/nova-console Executable file
View File

@ -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 File

@ -99,6 +99,11 @@ class ComputeManager(manager.Manager):
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."""

11
nova/console/__init__.py Normal file
View File

@ -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>
"""

59
nova/console/driver.py Normal file
View File

@ -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

59
nova/console/fake.py Normal file
View File

@ -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

130
nova/console/manager.py Normal file
View File

@ -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 File

@ -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

193
nova/console/xvp.py Normal file
View File

@ -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 File

@ -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 File

@ -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 File

@ -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 File

@ -216,6 +216,7 @@ DEFINE_integer('s3_port', 3333, 's3 port')
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 @@ DEFINE_string('sql_connection',
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',

134
nova/tests/test_console.py Normal file
View File

@ -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 File

@ -272,6 +272,11 @@ class FakeConnection(object):
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 File

@ -671,6 +671,14 @@ class LibvirtConnection(object):
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 File

@ -52,6 +52,7 @@ reactor thread if the VM.get_by_name_label or VM.get_record calls block.
import logging
import sys
import urlparse
import xmlrpclib
from eventlet import event
@ -177,6 +178,12 @@ class XenAPIConnection(object):
"""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"""