From b437a98738c7a564205d1b27e36b844cd54445d1 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Wed, 5 Jan 2011 14:16:14 -0600 Subject: add in xs-console worker and tests. --- nova/compute/manager.py | 5 ++ nova/console/__init__.py | 11 +++ nova/console/driver.py | 59 +++++++++++++ nova/console/fake.py | 59 +++++++++++++ nova/console/manager.py | 130 +++++++++++++++++++++++++++ nova/console/xvp.conf.template | 16 ++++ nova/console/xvp.py | 193 +++++++++++++++++++++++++++++++++++++++++ nova/db/api.py | 41 +++++++++ nova/db/sqlalchemy/api.py | 81 +++++++++++++++++ nova/db/sqlalchemy/models.py | 23 ++++- nova/flags.py | 3 + nova/tests/test_console.py | 134 ++++++++++++++++++++++++++++ nova/virt/fake.py | 5 ++ nova/virt/libvirt_conn.py | 8 ++ nova/virt/xenapi_conn.py | 7 ++ 15 files changed, 774 insertions(+), 1 deletion(-) create mode 100644 nova/console/__init__.py create mode 100644 nova/console/driver.py create mode 100644 nova/console/fake.py create mode 100644 nova/console/manager.py create mode 100644 nova/console/xvp.conf.template create mode 100644 nova/console/xvp.py create mode 100644 nova/tests/test_console.py (limited to 'nova') diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 70b175e7c..295e75eca 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -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.""" diff --git a/nova/console/__init__.py b/nova/console/__init__.py new file mode 100644 index 000000000..adce8621e --- /dev/null +++ b/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 +""" diff --git a/nova/console/driver.py b/nova/console/driver.py new file mode 100644 index 000000000..b92765b34 --- /dev/null +++ b/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 + diff --git a/nova/console/fake.py b/nova/console/fake.py new file mode 100644 index 000000000..4a9f1244c --- /dev/null +++ b/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 + diff --git a/nova/console/manager.py b/nova/console/manager.py new file mode 100644 index 000000000..93c6fabce --- /dev/null +++ b/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 + + diff --git a/nova/console/xvp.conf.template b/nova/console/xvp.conf.template new file mode 100644 index 000000000..695ddbe96 --- /dev/null +++ b/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 diff --git a/nova/console/xvp.py b/nova/console/xvp.py new file mode 100644 index 000000000..62ad3b2bb --- /dev/null +++ b/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() + diff --git a/nova/db/api.py b/nova/db/api.py index fde3f0852..af9856cb6 100644 --- a/nova/db/api.py +++ b/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) + + diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 7e945e4cb..25a3922c7 100644 --- a/nova/db/sqlalchemy/api.py +++ b/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 + + diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 693db8d23..e7f2d427e 100644 --- a/nova/db/sqlalchemy/models.py +++ b/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) diff --git a/nova/flags.py b/nova/flags.py index 76a98d35a..447cc6c6c 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -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', diff --git a/nova/tests/test_console.py b/nova/tests/test_console.py new file mode 100644 index 000000000..9f06a1771 --- /dev/null +++ b/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) + diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 706888b0d..acabb8034 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -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): diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 65cf65098..51353147f 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -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): """ diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 7f03d6c2b..abad0a08a 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -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""" -- cgit From f21f078113fc81c1dcee4f3a077bd555c0cf85f6 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Wed, 5 Jan 2011 19:45:46 -0600 Subject: Fix a bunch of pep8 stuff --- nova/api/openstack/consoles.py | 5 ----- nova/compute/manager.py | 2 -- nova/console/__init__.py | 7 ++++--- nova/console/api.py | 2 +- nova/console/driver.py | 3 +-- nova/console/fake.py | 2 +- nova/console/manager.py | 26 ++++++++++++-------------- nova/console/xvp.py | 18 ++++++++---------- nova/db/api.py | 11 ++++++++--- nova/db/sqlalchemy/api.py | 22 +++++++++++++--------- nova/db/sqlalchemy/models.py | 5 ++++- nova/flags.py | 3 ++- nova/tests/test_console.py | 19 +++++++++---------- nova/virt/fake.py | 6 +++--- nova/virt/libvirt_conn.py | 8 ++++---- nova/virt/xenapi_conn.py | 6 +++--- 16 files changed, 73 insertions(+), 72 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index bf3403655..49eefe09d 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -85,8 +85,3 @@ class Controller(wsgi.Controller): except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) return exc.HTTPAccepted() - -# def detail(self, req, id): -# """ Returns a complete list of consoles for this instance""" -# return _translate_detail_keys({}) - diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 3e73c351c..403b46b2a 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -112,11 +112,9 @@ 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.""" diff --git a/nova/console/__init__.py b/nova/console/__init__.py index adce8621e..491df075b 100644 --- a/nova/console/__init__.py +++ b/nova/console/__init__.py @@ -1,11 +1,12 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 """ -:mod:`nova.console` -- Console Prxy to set up VM console access (i.e. with xvp) +: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 + :synopsis: Wrapper around console proxies such as xvp to set up + multitenant VM console access +.. moduleauthor:: Monsyne Dragon """ diff --git a/nova/console/api.py b/nova/console/api.py index 78bfe636b..54317e3b3 100644 --- a/nova/console/api.py +++ b/nova/console/api.py @@ -29,6 +29,7 @@ from nova import rpc FLAGS = flags.FLAGS + class ConsoleAPI(base.Base): """API for spining up or down console proxy connections""" @@ -70,7 +71,6 @@ class ConsoleAPI(base.Base): {"method": "add_console", "args": {"instance_id": instance['id']}}) - def _get_console_topic(self, context, instance_host): topic = self.db.queue_get_for(context, FLAGS.compute_topic, diff --git a/nova/console/driver.py b/nova/console/driver.py index b92765b34..d2dafb1f3 100644 --- a/nova/console/driver.py +++ b/nova/console/driver.py @@ -43,7 +43,7 @@ class ConsoleProxy(object): def generate_password(self, length=8): """Returns random console password""" - return os.urandom(length*2).encode('base64')[:length] + return os.urandom(length * 2).encode('base64')[:length] def get_port(self, context): """get available port for consoles that need one""" @@ -56,4 +56,3 @@ class ConsoleProxy(object): def fix_console_password(self, password): """Trim password to length, and any other massaging""" return password - diff --git a/nova/console/fake.py b/nova/console/fake.py index 4a9f1244c..46782bdb3 100644 --- a/nova/console/fake.py +++ b/nova/console/fake.py @@ -22,6 +22,7 @@ Fake ConsoleProxy driver for tests. from nova import exception from nova.console import driver + class FakeConsoleProxy(driver.ConsoleProxy): """Fake ConsoleProxy driver.""" @@ -56,4 +57,3 @@ class FakeConsoleProxy(driver.ConsoleProxy): def fix_console_password(self, password): """Trim password to length, and any other massaging""" return password - diff --git a/nova/console/manager.py b/nova/console/manager.py index e3cbdae0e..b743e55b5 100644 --- a/nova/console/manager.py +++ b/nova/console/manager.py @@ -35,9 +35,10 @@ flags.DEFINE_string('console_driver', 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 + """ Sets up and tears down any proxy connections needed for accessing instance consoles securely""" def __init__(self, console_driver=None, *args, **kwargs): @@ -51,8 +52,8 @@ class ConsoleProxyManager(manager.Manager): self.driver.init_host() @exception.wrap_exception - def add_console(self, context, instance_id, password = None, - port = None, **kwargs): + 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'] @@ -67,10 +68,10 @@ class ConsoleProxyManager(manager.Manager): 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']} + 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) @@ -84,12 +85,11 @@ class ConsoleProxyManager(manager.Manager): except exception.NotFound: logging.debug(_('Tried to remove non-existant console ' '%(console_id)s.') % - {'console_id' : console_id}) + {'console_id': console_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 @@ -103,9 +103,9 @@ class ConsoleProxyManager(manager.Manager): # 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'} + pool_info = {'address': '127.0.0.1', + 'username': 'test', + 'password': '1234pass'} else: pool_info = rpc.call(context, self.db.queue_get_for(context, @@ -120,5 +120,3 @@ class ConsoleProxyManager(manager.Manager): pool_info['compute_host'] = instance_host pool = self.db.console_pool_create(context, pool_info) return pool - - diff --git a/nova/console/xvp.py b/nova/console/xvp.py index 62ad3b2bb..161b5ce20 100644 --- a/nova/console/xvp.py +++ b/nova/console/xvp.py @@ -51,12 +51,13 @@ flags.DEFINE_integer('console_xvp_multiplex_port', "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. + self.host = FLAGS.host # default, set by manager. super(XVPConsoleProxy, self).__init__() @property @@ -93,8 +94,8 @@ class XVPConsoleProxy(driver.ConsoleProxy): 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, + 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: @@ -103,7 +104,7 @@ class XVPConsoleProxy(driver.ConsoleProxy): return conf_data = {'multiplex_port': FLAGS.console_xvp_multiplex_port, 'pools': pools, - 'pass_encode' : self.fix_console_password } + 'pass_encode': self.fix_console_password} config = str(Template(self.xvpconf_template, searchList=[conf_data])) self._write_conf(config) self._xvp_restart() @@ -119,7 +120,7 @@ class XVPConsoleProxy(driver.ConsoleProxy): if not pid: return try: - os.kill(pid,signal.SIGTERM) + os.kill(pid, signal.SIGTERM) except OSError: #if it's already not running, no problem. pass @@ -129,7 +130,7 @@ class XVPConsoleProxy(driver.ConsoleProxy): return logging.debug("Starting xvp") try: - utils.execute('xvp -p %s -c %s -l %s' % + utils.execute('xvp -p %s -c %s -l %s' % (FLAGS.console_xvp_pid, FLAGS.console_xvp_conf, FLAGS.console_xvp_log)) @@ -160,7 +161,7 @@ class XVPConsoleProxy(driver.ConsoleProxy): if not pid: return False try: - os.kill(pid,0) + os.kill(pid, 0) except OSError: return False return True @@ -187,7 +188,4 @@ class XVPConsoleProxy(driver.ConsoleProxy): #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() - diff --git a/nova/db/api.py b/nova/db/api.py index 15b87520b..2c53fbeef 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -895,12 +895,15 @@ 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) @@ -922,24 +925,26 @@ def console_pool_get_all_by_host_type(context, host, console_type): console_type) -def console_create(context,values): +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) + def console_get_all_by_instance(context, instance_id): """Get consoles for a given instance.""" return IMPL.console_get_all_by_instance(context, instance_id) + def console_get(context, console_id, instance_id=None): """Get a specific console (possibly on a given instance).""" return IMPL.console_get(context, console_id, instance_id) - - diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 77dfe4a28..7b70566eb 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1835,10 +1835,12 @@ def console_pool_get(context, pool_id): filter_by(id=pool_id).\ first() if not result: - raise exception.NotFound(_("No console pool with id %(pool_id)s") % {'pool_id': pool_id}) + 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() @@ -1853,9 +1855,9 @@ def console_pool_get_by_host_type(context, compute_host, host, 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}) + {'type': console_type, + 'compute_host': compute_host, + 'host': host}) return result @@ -1875,13 +1877,15 @@ def console_create(context, values): console.save() return console + def console_delete(context, console_id): session = get_session() with session.begin(): - # consoles are meant to be transient. (mdragon) + # 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).\ @@ -1891,11 +1895,12 @@ def console_get_by_pool_instance(context, pool_id, instance_id): first() if not result: raise exception.NotFound(_('No console for instance %(instance_id)s ' - 'in pool %(pool_id)s') % + 'in pool %(pool_id)s') % {'instance_id': instance_id, 'pool_id': pool_id}) return result + def console_get_all_by_instance(context, instance_id): session = get_session() results = session.query(models.Console).\ @@ -1904,6 +1909,7 @@ def console_get_all_by_instance(context, instance_id): all() return results + def console_get(context, console_id, instance_id=None): session = get_session() query = session.query(models.Console).\ @@ -1914,7 +1920,5 @@ def console_get(context, console_id, instance_id=None): if not result: idesc = _(" on instance %(instance_id)s") if instance_id else "" raise exception.NotFound(_("No console with id %(instance)s") % - {'instance' : idesc}) + {'instance': idesc}) return result - - diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 58cf21b0d..dd75927d0 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -537,6 +537,7 @@ 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' @@ -548,6 +549,7 @@ class ConsolePool(BASE, NovaBase): host = Column(String(255)) compute_host = Column(String(255)) + class Console(BASE, NovaBase): """Represents a console session for an instance.""" __tablename__ = 'consoles' @@ -555,10 +557,11 @@ class Console(BASE, NovaBase): instance_name = Column(String(255)) instance_id = Column(Integer) password = Column(String(255)) - port = Column(Integer,nullable=True) + 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. diff --git a/nova/flags.py b/nova/flags.py index 58ba4d16d..f3a19c0c8 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -218,7 +218,8 @@ 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('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') diff --git a/nova/tests/test_console.py b/nova/tests/test_console.py index b23b1499b..31b5ca79c 100644 --- a/nova/tests/test_console.py +++ b/nova/tests/test_console.py @@ -89,12 +89,12 @@ class ConsoleTestCase(test.TestCase): 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' } + 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') @@ -107,16 +107,16 @@ class ConsoleTestCase(test.TestCase): pool = db.console_pool_get_by_host_type(self.context, instance['host'], self.console.host, - self.console.driver.console_type) + self.console.driver.console_type) - console_instances = [con['instance_id'] for con in pool.consoles] + 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) + self.assertEqual(cons1, cons2) def test_remove_console(self): instance_id = self._create_instance() @@ -127,4 +127,3 @@ class ConsoleTestCase(test.TestCase): db.console_get, self.context, console_id) - diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 13490b12e..849261f07 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -290,9 +290,9 @@ class FakeConnection(object): return 'FAKE CONSOLE OUTPUT' def get_console_pool_info(self, console_type): - return {'address' : '127.0.0.1', - 'username' : 'fakeuser', - 'password' : 'fakepassword'} + return {'address': '127.0.0.1', + 'username': 'fakeuser', + 'password': 'fakepassword'} class FakeInstance(object): diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index ded1004cd..0e3b6dff6 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -697,10 +697,10 @@ class LibvirtConnection(object): 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'} + # such. For now return fake data. + return {'address': '127.0.0.1', + 'username': 'fakeuser', + 'password': 'fakepassword'} class NWFilterFirewall(object): diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index c702059f7..86efb6b07 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -189,9 +189,9 @@ class XenAPIConnection(object): 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} + return {'address': xs_url.netloc, + 'username': FLAGS.xenapi_connection_username, + 'password': FLAGS.xenapi_connection_password} class XenAPISession(object): -- cgit From f9fa25f9a873c1e4831c342689f7b5adc8f41013 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Wed, 5 Jan 2011 20:14:36 -0600 Subject: add in separate public hostname for console hosts. flesh out console api data. --- nova/api/openstack/consoles.py | 17 +++++++++++++---- nova/console/manager.py | 7 ++++++- nova/db/sqlalchemy/models.py | 1 + 3 files changed, 20 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index 49eefe09d..e108bab86 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -23,15 +23,24 @@ from nova.console import api as console_api from nova.api.openstack import faults -def _translate_keys(inst): +def _translate_keys(cons): """Coerces a console instance into proper dictionary format """ - return dict(console=inst) + pool = cons['pool'] + info = {'id': cons['id'], + 'console_type': pool['console_type']} + return dict(console=info) -def _translate_detail_keys(inst): +def _translate_detail_keys(cons): """Coerces a console instance into proper dictionary format with correctly mapped attributes """ - return dict(console=inst) + pool = cons['pool'] + info = {'id': cons['id'], + 'console_type': pool['console_type'], + 'password': cons['password'], + 'port': cons['port'], + 'host': pool['public_hostname']} + return dict(console=info) class Controller(wsgi.Controller): diff --git a/nova/console/manager.py b/nova/console/manager.py index b743e55b5..c55ca8e8f 100644 --- a/nova/console/manager.py +++ b/nova/console/manager.py @@ -19,8 +19,9 @@ Console Proxy Service """ -import logging import functools +import logging +import socket from nova import exception from nova import flags @@ -34,6 +35,9 @@ flags.DEFINE_string('console_driver', 'Driver to use for the console proxy') flags.DEFINE_boolean('stub_compute', False, 'Stub calls to compute worker for tests') +flags.DEFINE_string('console_public_hostname', + socket.gethostname(), + 'Publicly visable name for this console host') class ConsoleProxyManager(manager.Manager): @@ -116,6 +120,7 @@ class ConsoleProxyManager(manager.Manager): pool_info['password'] = self.driver.fix_pool_password( pool_info['password']) pool_info['host'] = self.host + pool_info['public_hostname'] = FLAGS.console_public_hostname pool_info['console_type'] = self.driver.console_type pool_info['compute_host'] = instance_host pool = self.db.console_pool_create(context, pool_info) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index dd75927d0..0c55ee75e 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -546,6 +546,7 @@ class ConsolePool(BASE, NovaBase): username = Column(String(255)) password = Column(String(255)) console_type = Column(String(255)) + public_hostname = Column(String(255)) host = Column(String(255)) compute_host = Column(String(255)) -- cgit From b55940e8e3d977960ff60f4cb7cff4b6ea2e8fb8 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Wed, 5 Jan 2011 22:11:05 -0600 Subject: fix some glitches due to someone removing instanc.internal_id (not that I mind) remove accidental change to nova-combined script --- nova/console/api.py | 23 +++++++++-------------- nova/console/driver.py | 2 ++ nova/db/sqlalchemy/api.py | 6 +++--- 3 files changed, 14 insertions(+), 17 deletions(-) (limited to 'nova') diff --git a/nova/console/api.py b/nova/console/api.py index 54317e3b3..93e28ad64 100644 --- a/nova/console/api.py +++ b/nova/console/api.py @@ -36,20 +36,16 @@ class ConsoleAPI(base.Base): def __init__(self, **kwargs): super(ConsoleAPI, self).__init__(**kwargs) - def get_consoles(self, context, instance_internal_id): - instance = self.db.instance_get_by_internal_id(context, - instance_internal_id) - return self.db.console_get_all_by_instance(context, instance['id']) + def get_consoles(self, context, instance_id): + return self.db.console_get_all_by_instance(context, instance_id) - def get_console(self, context, instance_internal_id, console_id): - return self.db.console_get(context, console_id, instance_internal_id) + def get_console(self, context, instance_id, console_id): + return self.db.console_get(context, console_id, instance_id) - def delete_console(self, context, instance_internal_id, console_id): - instance = self.db.instance_get_by_internal_id(context, - instance_internal_id) + def delete_console(self, context, instance_id, console_id): console = self.db.console_get(context, console_id, - instance['id']) + instance_id) pool = console['pool'] rpc.cast(context, self.db.queue_get_for(context, @@ -58,9 +54,8 @@ class ConsoleAPI(base.Base): {"method": "remove_console", "args": {"console_id": console['id']}}) - def create_console(self, context, instance_internal_id): - instance = self.db.instance_get_by_internal_id(context, - instance_internal_id) + def create_console(self, context, instance_id): + instance = self.db.instance_get(context, instance_id) #NOTE(mdragon): If we wanted to return this the console info # here, as we would need to do a call. # They can just do an index later to fetch @@ -69,7 +64,7 @@ class ConsoleAPI(base.Base): rpc.cast(context, self._get_console_topic(context, instance['host']), {"method": "add_console", - "args": {"instance_id": instance['id']}}) + "args": {"instance_id": instance_id}}) def _get_console_topic(self, context, instance_host): topic = self.db.queue_get_for(context, diff --git a/nova/console/driver.py b/nova/console/driver.py index d2dafb1f3..c4cf5e5e9 100644 --- a/nova/console/driver.py +++ b/nova/console/driver.py @@ -19,6 +19,8 @@ ConsoleProxy base class that all ConsoleProxies should inherit from """ +import os + from nova import exception diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 7b70566eb..6f4d068f1 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1918,7 +1918,7 @@ def console_get(context, console_id, instance_id=None): query = query.filter_by(instance_id=instance_id) result = query.options(joinedload('pool')).first() if not result: - idesc = _(" on instance %(instance_id)s") if instance_id else "" - raise exception.NotFound(_("No console with id %(instance)s") % - {'instance': idesc}) + idesc = (_("on instance %s") % instance_id) if instance_id else "" + raise exception.NotFound(_("No console with id %(console_id)s %(instance)s") % + {'instance': idesc, 'console_id': console_id}) return result -- cgit From 1ce25cab7f1818aababb18d60959f44602f2e17c Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Thu, 6 Jan 2011 13:19:58 -0600 Subject: pep8 fix --- nova/db/sqlalchemy/api.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 6f4d068f1..c3dee4329 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1919,6 +1919,8 @@ def console_get(context, console_id, instance_id=None): result = query.options(joinedload('pool')).first() if not result: idesc = (_("on instance %s") % instance_id) if instance_id else "" - raise exception.NotFound(_("No console with id %(console_id)s %(instance)s") % - {'instance': idesc, 'console_id': console_id}) + raise exception.NotFound(_("No console with id %(console_id)s" + " %(instance)s") % + {'instance': idesc, + 'console_id': console_id}) return result -- cgit From b2d6bb841857599096467470ec704e6696317829 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Fri, 7 Jan 2011 19:04:22 -0600 Subject: change API classname to match the way other API's are done. --- nova/api/openstack/consoles.py | 4 ++-- nova/console/__init__.py | 1 + nova/console/api.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index e108bab86..9ebdbe710 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -17,9 +17,9 @@ from webob import exc +from nova import console from nova import exception from nova import wsgi -from nova.console import api as console_api from nova.api.openstack import faults @@ -52,7 +52,7 @@ class Controller(wsgi.Controller): 'console': []}}} def __init__(self): - self.console_api = console_api.ConsoleAPI() + self.console_api = console.API() super(Controller, self).__init__() def index(self, req, server_id): diff --git a/nova/console/__init__.py b/nova/console/__init__.py index 491df075b..dfc72cd61 100644 --- a/nova/console/__init__.py +++ b/nova/console/__init__.py @@ -10,3 +10,4 @@ multitenant VM console access .. moduleauthor:: Monsyne Dragon """ +from nova.console.api import API diff --git a/nova/console/api.py b/nova/console/api.py index 93e28ad64..3850d2c44 100644 --- a/nova/console/api.py +++ b/nova/console/api.py @@ -30,11 +30,11 @@ from nova import rpc FLAGS = flags.FLAGS -class ConsoleAPI(base.Base): +class API(base.Base): """API for spining up or down console proxy connections""" def __init__(self, **kwargs): - super(ConsoleAPI, self).__init__(**kwargs) + super(API, self).__init__(**kwargs) def get_consoles(self, context, instance_id): return self.db.console_get_all_by_instance(context, instance_id) -- cgit From 3b4582b5db905a6dcadda31be27c9f340d7fe5cf Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sun, 9 Jan 2011 18:08:54 -0800 Subject: Moved get_my_ip into flags because that is the only thing it is being used for and use it to set a new flag called my_ip --- nova/flags.py | 26 +++++++++++++++++++------- nova/network/linux_net.py | 2 +- nova/network/manager.py | 2 +- nova/tests/api/openstack/fakes.py | 2 +- nova/utils.py | 13 ------------- 5 files changed, 22 insertions(+), 23 deletions(-) (limited to 'nova') diff --git a/nova/flags.py b/nova/flags.py index f5c2d4233..0e6d3176c 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -200,10 +200,22 @@ def DECLARE(name, module_string, flag_values=FLAGS): "%s not defined by %s" % (name, module_string)) +def _get_my_ip(): + """Returns the actual ip of the local machine.""" + try: + csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + csock.connect(('8.8.8.8', 80)) + (addr, port) = csock.getsockname() + csock.close() + return addr + except socket.gaierror as ex: + return "127.0.0.1" + + # __GLOBAL FLAGS ONLY__ # Define any app-specific flags in their own files, docs at: -# http://code.google.com/p/python-gflags/source/browse/trunk/gflags.py#39 - +# http://code.google.com/p/python-gflags/source/browse/trunk/gflags.py#a9 +DEFINE_string('my_ip', _get_my_ip(), 'host ip address') DEFINE_list('region_list', [], 'list of region=url pairs separated by commas') @@ -211,10 +223,10 @@ DEFINE_string('connection_type', 'libvirt', 'libvirt, xenapi or fake') DEFINE_string('aws_access_key_id', 'admin', 'AWS Access ID') DEFINE_string('aws_secret_access_key', 'admin', 'AWS Access Key') DEFINE_integer('glance_port', 9292, 'glance port') -DEFINE_string('glance_host', '127.0.0.1', 'glance host') +DEFINE_string('glance_host', '$my_ip', 'glance host') DEFINE_integer('s3_port', 3333, 's3 port') -DEFINE_string('s3_host', '127.0.0.1', 's3 host (for infrastructure)') -DEFINE_string('s3_dmz', '127.0.0.1', 's3 dmz ip (for instances)') +DEFINE_string('s3_host', '$my_ip', 's3 host (for infrastructure)') +DEFINE_string('s3_dmz', '$my_ip', 's3 dmz ip (for instances)') DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on') DEFINE_string('scheduler_topic', 'scheduler', 'the topic scheduler nodes listen on') @@ -234,8 +246,8 @@ DEFINE_integer('rabbit_retry_interval', 10, 'rabbit connection retry interval') DEFINE_integer('rabbit_max_retries', 12, 'rabbit connection attempts') DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to') DEFINE_string('ec2_prefix', 'http', 'prefix for ec2') -DEFINE_string('cc_host', '127.0.0.1', 'ip of api server') -DEFINE_string('cc_dmz', '127.0.0.1', 'internal ip of api server') +DEFINE_string('cc_host', '$my_ip', 'ip of api server') +DEFINE_string('cc_dmz', '$my_ip', 'internal ip of api server') DEFINE_integer('cc_port', 8773, 'cloud controller port') DEFINE_string('ec2_suffix', '/services/Cloud', 'suffix for ec2') diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index c525d5dc8..a0648ca25 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -46,7 +46,7 @@ flags.DEFINE_string('vlan_interface', 'eth0', 'network device for vlans') flags.DEFINE_string('dhcpbridge', _bin_file('nova-dhcpbridge'), 'location of nova-dhcpbridge') -flags.DEFINE_string('routing_source_ip', utils.get_my_ip(), +flags.DEFINE_string('routing_source_ip', '$my_ip', 'Public IP of network host') flags.DEFINE_bool('use_nova_chains', False, 'use the nova_ routing chains instead of default') diff --git a/nova/network/manager.py b/nova/network/manager.py index fd286f210..c75ecc671 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -74,7 +74,7 @@ flags.DEFINE_string('flat_network_dhcp_start', '10.0.0.2', 'Dhcp start for FlatDhcp') flags.DEFINE_integer('vlan_start', 100, 'First VLAN for private networks') flags.DEFINE_integer('num_networks', 1000, 'Number of networks to support') -flags.DEFINE_string('vpn_ip', utils.get_my_ip(), +flags.DEFINE_string('vpn_ip', '$my_ip', 'Public IP for the cloudpipe VPN servers') flags.DEFINE_integer('vpn_start', 1000, 'First Vpn port for private networks') flags.DEFINE_integer('network_size', 256, diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index 291a0e468..194304e79 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -107,7 +107,7 @@ def stub_out_rate_limiting(stubs): def stub_out_networking(stubs): def get_my_ip(): return '127.0.0.1' - stubs.Set(nova.utils, 'get_my_ip', get_my_ip) + stubs.Set(nova.flags, '_get_my_ip', get_my_ip) def stub_out_compute_api_snapshot(stubs): diff --git a/nova/utils.py b/nova/utils.py index cc632b835..aadbec532 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -195,19 +195,6 @@ def last_octet(address): return int(address.split(".")[-1]) -def get_my_ip(): - """Returns the actual ip of the local machine.""" - try: - csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - csock.connect(('8.8.8.8', 80)) - (addr, port) = csock.getsockname() - csock.close() - return addr - except socket.gaierror as ex: - LOG.warn(_("Couldn't get IP, using 127.0.0.1 %s"), ex) - return "127.0.0.1" - - def utcnow(): """Overridable version of datetime.datetime.utcnow.""" if utcnow.override_time: -- cgit From 94f3782eb27fd63c64845f9ab59039d07ac7ba8c Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Mon, 10 Jan 2011 14:59:32 -0600 Subject: remove uneeded superclass --- nova/console/driver.py | 60 -------------------------------------------------- nova/console/fake.py | 3 +-- nova/console/xvp.py | 7 ++++-- 3 files changed, 6 insertions(+), 64 deletions(-) delete mode 100644 nova/console/driver.py (limited to 'nova') diff --git a/nova/console/driver.py b/nova/console/driver.py deleted file mode 100644 index c4cf5e5e9..000000000 --- a/nova/console/driver.py +++ /dev/null @@ -1,60 +0,0 @@ -# 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 -""" - -import os - -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 diff --git a/nova/console/fake.py b/nova/console/fake.py index 46782bdb3..7a90d5221 100644 --- a/nova/console/fake.py +++ b/nova/console/fake.py @@ -20,10 +20,9 @@ Fake ConsoleProxy driver for tests. """ from nova import exception -from nova.console import driver -class FakeConsoleProxy(driver.ConsoleProxy): +class FakeConsoleProxy(object): """Fake ConsoleProxy driver.""" @property diff --git a/nova/console/xvp.py b/nova/console/xvp.py index 161b5ce20..2a76223da 100644 --- a/nova/console/xvp.py +++ b/nova/console/xvp.py @@ -32,7 +32,6 @@ 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'), @@ -52,7 +51,7 @@ flags.DEFINE_integer('console_xvp_multiplex_port', FLAGS = flags.FLAGS -class XVPConsoleProxy(driver.ConsoleProxy): +class XVPConsoleProxy(object): """Sets up XVP config, and manages xvp daemon""" def __init__(self): @@ -92,6 +91,10 @@ class XVPConsoleProxy(driver.ConsoleProxy): """Trim password to length, and encode""" return self._xvp_encrypt(password) + def generate_password(self, length=8): + """Returns random console password""" + return os.urandom(length * 2).encode('base64')[:length] + def _rebuild_xvp_conf(self, context): logging.debug("Rebuilding xvp conf") pools = [pool for pool in -- cgit