Merge "Fix console support with cells"

This commit is contained in:
Jenkins 2013-03-19 22:40:24 +00:00 committed by Gerrit Code Review
commit 07ce83f7be
14 changed files with 283 additions and 28 deletions

View File

@ -66,7 +66,7 @@ class CellsManager(manager.Manager):
Scheduling requests get passed to the scheduler class.
"""
RPC_API_VERSION = '1.5'
RPC_API_VERSION = '1.6'
def __init__(self, *args, **kwargs):
# Mostly for tests.
@ -347,3 +347,18 @@ class CellsManager(manager.Manager):
response = self.msg_runner.action_events_get(ctxt, cell_name,
action_id)
return response.value_or_raise()
def consoleauth_delete_tokens(self, ctxt, instance_uuid):
"""Delete consoleauth tokens for an instance in API cells."""
self.msg_runner.consoleauth_delete_tokens(ctxt, instance_uuid)
def validate_console_port(self, ctxt, instance_uuid, console_port,
console_type):
"""Validate console port with child cell compute node."""
instance = self.db.instance_get_by_uuid(ctxt, instance_uuid)
if not instance['cell_name']:
raise exception.InstanceUnknownCell(instance_uuid=instance_uuid)
response = self.msg_runner.validate_console_port(ctxt,
instance['cell_name'], instance_uuid, console_port,
console_type)
return response.value_or_raise()

View File

@ -30,6 +30,8 @@ from oslo.config import cfg
from nova.cells import state as cells_state
from nova.cells import utils as cells_utils
from nova import compute
from nova.compute import rpcapi as compute_rpcapi
from nova.consoleauth import rpcapi as consoleauth_rpcapi
from nova import context
from nova.db import base
from nova import exception
@ -599,6 +601,8 @@ class _BaseMessageMethods(base.Base):
self.msg_runner = msg_runner
self.state_manager = msg_runner.state_manager
self.compute_api = compute.API()
self.compute_rpcapi = compute_rpcapi.ComputeAPI()
self.consoleauth_rpcapi = consoleauth_rpcapi.ConsoleAuthAPI()
def task_log_get_all(self, message, task_name, period_beginning,
period_ending, host, state):
@ -730,6 +734,25 @@ class _TargetedMessageMethods(_BaseMessageMethods):
action_events = self.db.action_events_get(message.ctxt, action_id)
return jsonutils.to_primitive(action_events)
def validate_console_port(self, message, instance_uuid, console_port,
console_type):
"""Validate console port with child cell compute node."""
# 1st arg is instance_uuid that we need to turn into the
# instance object.
try:
instance = self.db.instance_get_by_uuid(message.ctxt,
instance_uuid)
except exception.InstanceNotFound:
with excutils.save_and_reraise_exception():
# Must be a race condition. Let's try to resolve it by
# telling the top level cells that this instance doesn't
# exist.
instance = {'uuid': instance_uuid}
self.msg_runner.instance_destroy_at_top(message.ctxt,
instance)
return self.compute_rpcapi.validate_console_port(message.ctxt,
instance, console_port, console_type)
class _BroadcastMessageMethods(_BaseMessageMethods):
"""These are the methods that can be called as a part of a broadcast
@ -885,6 +908,13 @@ class _BroadcastMessageMethods(_BaseMessageMethods):
"""Return compute node stats from this cell."""
return self.db.compute_node_statistics(message.ctxt)
def consoleauth_delete_tokens(self, message, instance_uuid):
"""Delete consoleauth tokens for an instance in API cells."""
if not self._at_the_top():
return
self.consoleauth_rpcapi.delete_tokens_for_instance(message.ctxt,
instance_uuid)
_CELL_MESSAGE_TYPE_TO_MESSAGE_CLS = {'targeted': _TargetedMessage,
'broadcast': _BroadcastMessage,
@ -1224,6 +1254,24 @@ class MessageRunner(object):
cell_name, need_response=True)
return message.process()
def consoleauth_delete_tokens(self, ctxt, instance_uuid):
"""Delete consoleauth tokens for an instance in API cells."""
message = _BroadcastMessage(self, ctxt, 'consoleauth_delete_tokens',
dict(instance_uuid=instance_uuid),
'up', run_locally=False)
message.process()
def validate_console_port(self, ctxt, cell_name, instance_uuid,
console_port, console_type):
"""Validate console port with child cell compute node."""
method_kwargs = {'instance_uuid': instance_uuid,
'console_port': console_port,
'console_type': console_type}
message = _TargetedMessage(self, ctxt, 'validate_console_port',
method_kwargs, 'down',
cell_name, need_response=True)
return message.process()
@staticmethod
def get_message_types():
return _CELL_MESSAGE_TYPE_TO_MESSAGE_CLS.keys()

View File

@ -50,6 +50,7 @@ class CellsAPI(rpc_proxy.RpcProxy):
compute_node_stats()
1.5 - Adds actions_get(), action_get_by_request_id(), and
action_events_get()
1.6 - Adds consoleauth_delete_tokens() and validate_console_port()
'''
BASE_RPC_API_VERSION = '1.0'
@ -247,3 +248,19 @@ class CellsAPI(rpc_proxy.RpcProxy):
cell_name=instance['cell_name'],
action_id=action_id),
version='1.5')
def consoleauth_delete_tokens(self, ctxt, instance_uuid):
"""Delete consoleauth tokens for an instance in API cells."""
self.cast(ctxt, self.make_msg('consoleauth_delete_tokens',
instance_uuid=instance_uuid),
version='1.6')
def validate_console_port(self, ctxt, instance_uuid, console_port,
console_type):
"""Validate console port with child cell compute node."""
return self.call(ctxt,
self.make_msg('validate_console_port',
instance_uuid=instance_uuid,
console_port=console_port,
console_type=console_type),
version='1.6')

View File

@ -40,6 +40,7 @@ from eventlet import greenthread
from oslo.config import cfg
from nova import block_device
from nova.cells import rpcapi as cells_rpcapi
from nova.cloudpipe import pipelib
from nova import compute
from nova.compute import instance_types
@ -176,6 +177,7 @@ CONF.import_opt('host', 'nova.netconf')
CONF.import_opt('my_ip', 'nova.netconf')
CONF.import_opt('vnc_enabled', 'nova.vnc')
CONF.import_opt('enabled', 'nova.spice', group='spice')
CONF.import_opt('enable', 'nova.cells.opts', group='cells')
LOG = logging.getLogger(__name__)
@ -340,6 +342,7 @@ class ComputeManager(manager.SchedulerDependentManager):
self.is_quantum_security_groups = (
openstack_driver.is_quantum_security_groups())
self.consoleauth_rpcapi = consoleauth.rpcapi.ConsoleAuthAPI()
self.cells_rpcapi = cells_rpcapi.CellsAPI()
super(ComputeManager, self).__init__(service_name="compute",
*args, **kwargs)
@ -1301,8 +1304,12 @@ class ComputeManager(manager.SchedulerDependentManager):
system_metadata=system_meta)
if CONF.vnc_enabled or CONF.spice.enabled:
self.consoleauth_rpcapi.delete_tokens_for_instance(context,
instance['uuid'])
if CONF.cells.enable:
self.cells_rpcapi.consoleauth_delete_tokens(context,
instance['uuid'])
else:
self.consoleauth_rpcapi.delete_tokens_for_instance(context,
instance['uuid'])
@exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
@wrap_instance_event

View File

@ -161,7 +161,7 @@ class ComputeAPI(nova.openstack.common.rpc.proxy.RpcProxy):
2.23 - Remove network_info from reboot_instance
2.24 - Added get_spice_console method
2.25 - Add attach_interface() and detach_interface()
2.26 - Add validate_console_token to ensure the service connects to
2.26 - Add validate_console_port to ensure the service connects to
vnc on the correct port
2.27 - Adds 'reservations' to terminate_instance() and
soft_delete_instance()
@ -329,8 +329,7 @@ class ComputeAPI(nova.openstack.common.rpc.proxy.RpcProxy):
instance_p = jsonutils.to_primitive(instance)
return self.call(ctxt, self.make_msg('validate_console_port',
instance=instance_p, port=port, console_type=console_type),
topic=_compute_topic(self.topic, ctxt,
None, instance),
topic=_compute_topic(self.topic, ctxt, None, instance),
version='2.26')
def host_maintenance_mode(self, ctxt, host_param, mode, host):

View File

@ -22,6 +22,7 @@ import time
from oslo.config import cfg
from nova.cells import rpcapi as cells_rpcapi
from nova.compute import rpcapi as compute_rpcapi
from nova.conductor import api as conductor_api
from nova import manager
@ -43,6 +44,7 @@ consoleauth_opts = [
CONF = cfg.CONF
CONF.register_opts(consoleauth_opts)
CONF.import_opt('enable', 'nova.cells.opts', group='cells')
class ConsoleAuthManager(manager.Manager):
@ -53,8 +55,9 @@ class ConsoleAuthManager(manager.Manager):
def __init__(self, scheduler_driver=None, *args, **kwargs):
super(ConsoleAuthManager, self).__init__(*args, **kwargs)
self.mc = memorycache.get_client()
self.compute_rpcapi = compute_rpcapi.ComputeAPI()
self.conductor_api = conductor_api.API()
self.compute_rpcapi = compute_rpcapi.ComputeAPI()
self.cells_rpcapi = cells_rpcapi.CellsAPI()
def _get_tokens_for_instance(self, instance_uuid):
tokens_str = self.mc.get(instance_uuid.encode('UTF-8'))
@ -88,8 +91,16 @@ class ConsoleAuthManager(manager.Manager):
instance_uuid = token['instance_uuid']
if instance_uuid is None:
return False
# NOTE(comstud): consoleauth was meant to run in API cells. So,
# if cells is enabled, we must call down to the child cell for
# the instance.
if CONF.cells.enable:
return self.cells_rpcapi.validate_console_port(context,
instance_uuid, token['port'], token['console_type'])
instance = self.conductor_api.instance_get_by_uuid(context,
instance_uuid)
instance_uuid)
return self.compute_rpcapi.validate_console_port(context,
instance,
token['port'],

View File

@ -67,7 +67,7 @@ class ConsoleAuthAPI(nova.openstack.common.rpc.proxy.RpcProxy):
return self.call(ctxt, self.make_msg('check_token', token=token))
def delete_tokens_for_instance(self, ctxt, instance_uuid):
return self.call(ctxt,
return self.cast(ctxt,
self.make_msg('delete_tokens_for_instance',
instance_uuid=instance_uuid),
version="1.2")

View File

@ -473,3 +473,37 @@ class CellsManagerClassTestCase(test.TestCase):
response = self.cells_manager.action_events_get(self.ctxt, 'fake-cell',
'fake-action')
self.assertEqual(expected_response, response)
def test_consoleauth_delete_tokens(self):
instance_uuid = 'fake-instance-uuid'
self.mox.StubOutWithMock(self.msg_runner,
'consoleauth_delete_tokens')
self.msg_runner.consoleauth_delete_tokens(self.ctxt, instance_uuid)
self.mox.ReplayAll()
self.cells_manager.consoleauth_delete_tokens(self.ctxt,
instance_uuid=instance_uuid)
def test_validate_console_port(self):
instance_uuid = 'fake-instance-uuid'
cell_name = 'fake-cell-name'
instance = {'cell_name': cell_name}
console_port = 'fake-console-port'
console_type = 'fake-console-type'
self.mox.StubOutWithMock(self.msg_runner,
'validate_console_port')
self.mox.StubOutWithMock(self.cells_manager.db,
'instance_get_by_uuid')
fake_response = self._get_fake_response()
self.cells_manager.db.instance_get_by_uuid(self.ctxt,
instance_uuid).AndReturn(instance)
self.msg_runner.validate_console_port(self.ctxt, cell_name,
instance_uuid, console_port,
console_type).AndReturn(fake_response)
self.mox.ReplayAll()
response = self.cells_manager.validate_console_port(self.ctxt,
instance_uuid=instance_uuid, console_port=console_port,
console_type=console_type)
self.assertEqual('fake-response', response)

View File

@ -593,6 +593,7 @@ class CellsTargetedMethodsTestCase(test.TestCase):
self.tgt_methods_cls = methods_cls
self.tgt_compute_api = methods_cls.compute_api
self.tgt_db_inst = methods_cls.db
self.tgt_c_rpcapi = methods_cls.compute_rpcapi
def test_schedule_run_instance(self):
host_sched_kwargs = {'filter_properties': {},
@ -872,6 +873,28 @@ class CellsTargetedMethodsTestCase(test.TestCase):
result = response.value_or_raise()
self.assertEqual(fake_events, result)
def test_validate_console_port(self):
instance_uuid = 'fake_instance_uuid'
instance = {'uuid': instance_uuid}
console_port = 'fake-port'
console_type = 'fake-type'
self.mox.StubOutWithMock(self.tgt_c_rpcapi, 'validate_console_port')
self.mox.StubOutWithMock(self.tgt_db_inst, 'instance_get_by_uuid')
self.tgt_db_inst.instance_get_by_uuid(self.ctxt,
instance_uuid).AndReturn(instance)
self.tgt_c_rpcapi.validate_console_port(self.ctxt,
instance, console_port, console_type).AndReturn('fake_result')
self.mox.ReplayAll()
response = self.src_msg_runner.validate_console_port(self.ctxt,
self.tgt_cell_name, instance_uuid, console_port,
console_type)
result = response.value_or_raise()
self.assertEqual('fake_result', result)
class CellsBroadcastMethodsTestCase(test.TestCase):
"""Test case for _BroadcastMessageMethods class. Most of these
@ -900,6 +923,7 @@ class CellsBroadcastMethodsTestCase(test.TestCase):
self.src_methods_cls = methods_cls
self.src_db_inst = methods_cls.db
self.src_compute_api = methods_cls.compute_api
self.src_ca_rpcapi = methods_cls.consoleauth_rpcapi
if not up:
# fudge things so we only have 1 child to broadcast to
@ -913,12 +937,14 @@ class CellsBroadcastMethodsTestCase(test.TestCase):
self.mid_methods_cls = methods_cls
self.mid_db_inst = methods_cls.db
self.mid_compute_api = methods_cls.compute_api
self.mid_ca_rpcapi = methods_cls.consoleauth_rpcapi
self.tgt_msg_runner = fakes.get_message_runner(tgt_cell)
methods_cls = self.tgt_msg_runner.methods_by_type['broadcast']
self.tgt_methods_cls = methods_cls
self.tgt_db_inst = methods_cls.db
self.tgt_compute_api = methods_cls.compute_api
self.tgt_ca_rpcapi = methods_cls.consoleauth_rpcapi
def test_at_the_top(self):
self.assertTrue(self.tgt_methods_cls._at_the_top())
@ -1283,3 +1309,20 @@ class CellsBroadcastMethodsTestCase(test.TestCase):
('api-cell!child-cell2', [3]),
('api-cell', [1, 2])]
self.assertEqual(expected, response_values)
def test_consoleauth_delete_tokens(self):
fake_uuid = 'fake-instance-uuid'
# To show these should not be called in src/mid-level cell
self.mox.StubOutWithMock(self.src_ca_rpcapi,
'delete_tokens_for_instance')
self.mox.StubOutWithMock(self.mid_ca_rpcapi,
'delete_tokens_for_instance')
self.mox.StubOutWithMock(self.tgt_ca_rpcapi,
'delete_tokens_for_instance')
self.tgt_ca_rpcapi.delete_tokens_for_instance(self.ctxt, fake_uuid)
self.mox.ReplayAll()
self.src_msg_runner.consoleauth_delete_tokens(self.ctxt, fake_uuid)

View File

@ -360,3 +360,26 @@ class CellsAPITestCase(test.TestCase):
self.assertRaises(exception.InstanceUnknownCell,
self.cells_rpcapi.action_events_get,
self.fake_context, fake_instance, 'fake-action')
def test_consoleauth_delete_tokens(self):
call_info = self._stub_rpc_method('cast', None)
self.cells_rpcapi.consoleauth_delete_tokens(self.fake_context,
'fake-uuid')
expected_args = {'instance_uuid': 'fake-uuid'}
self._check_result(call_info, 'consoleauth_delete_tokens',
expected_args, version='1.6')
def test_validate_console_port(self):
call_info = self._stub_rpc_method('call', 'fake_response')
result = self.cells_rpcapi.validate_console_port(self.fake_context,
'fake-uuid', 'fake-port', 'fake-type')
expected_args = {'instance_uuid': 'fake-uuid',
'console_port': 'fake-port',
'console_type': 'fake-type'}
self._check_result(call_info, 'validate_console_port',
expected_args, version='1.6')
self.assertEqual(result, 'fake_response')

View File

@ -1966,6 +1966,26 @@ class ComputeTestCase(BaseTestCase):
self.assertTrue(self.tokens_deleted)
def test_delete_instance_deletes_console_auth_tokens_cells(self):
instance = self._create_fake_instance()
self.flags(vnc_enabled=True)
self.flags(enable=True, group='cells')
self.tokens_deleted = False
def fake_delete_tokens(*args, **kwargs):
self.tokens_deleted = True
cells_rpcapi = self.compute.cells_rpcapi
self.stubs.Set(cells_rpcapi, 'consoleauth_delete_tokens',
fake_delete_tokens)
self.compute._delete_instance(self.context,
instance=jsonutils.to_primitive(instance),
bdms={})
self.assertTrue(self.tokens_deleted)
def test_instance_termination_exception_sets_error(self):
"""Test that we handle InstanceTerminationFailure
which is propagated up from the underlying driver.
@ -5437,6 +5457,14 @@ class ComputeAPITestCase(BaseTestCase):
self.assertEqual(instance_properties['progress'], 0)
self.assertIn('host2', filter_properties['ignore_hosts'])
def _noop(*args, **kwargs):
pass
self.stubs.Set(self.compute.cells_rpcapi,
'consoleauth_delete_tokens', _noop)
self.stubs.Set(self.compute.consoleauth_rpcapi,
'delete_tokens_for_instance', _noop)
self.stubs.Set(rpc, 'cast', _fake_cast)
instance = self._create_fake_instance(dict(host='host2'))
@ -5472,6 +5500,14 @@ class ComputeAPITestCase(BaseTestCase):
self.assertEqual(instance_properties['progress'], 0)
self.assertNotIn('host2', filter_properties['ignore_hosts'])
def _noop(*args, **kwargs):
pass
self.stubs.Set(self.compute.cells_rpcapi,
'consoleauth_delete_tokens', _noop)
self.stubs.Set(self.compute.consoleauth_rpcapi,
'delete_tokens_for_instance', _noop)
self.stubs.Set(rpc, 'cast', _fake_cast)
self.flags(allow_resize_to_same_host=True)

View File

@ -18,6 +18,8 @@ Tests For Compute w/ Cells
"""
import functools
from oslo.config import cfg
from nova.compute import api as compute_api
from nova.compute import cells_api as compute_cells_api
from nova import db
@ -31,6 +33,7 @@ from nova.tests.compute import test_compute
LOG = logging.getLogger('nova.tests.test_compute_cells')
ORIG_COMPUTE_API = None
cfg.CONF.import_opt('enable', 'nova.cells.opts', group='cells')
def stub_call_to_cells(context, instance, method, *args, **kwargs):
@ -112,6 +115,7 @@ class CellsComputeAPITestCase(test_compute.ComputeAPITestCase):
super(CellsComputeAPITestCase, self).setUp()
global ORIG_COMPUTE_API
ORIG_COMPUTE_API = self.compute_api
self.flags(enable=True, group='cells')
def _fake_cell_read_only(*args, **kwargs):
return False

View File

@ -43,11 +43,7 @@ class ConsoleauthTestCase(test.TestCase):
token = 'mytok'
self.flags(console_token_ttl=1)
def fake_validate_console_port(*args, **kwargs):
return True
self.stubs.Set(self.manager.compute_rpcapi,
"validate_console_port",
fake_validate_console_port)
self._stub_validate_console_port(True)
self.manager.authorize_console(self.context, token, 'novnc',
'127.0.0.1', '8080', 'host',
@ -56,16 +52,20 @@ class ConsoleauthTestCase(test.TestCase):
timeutils.advance_time_seconds(1)
self.assertFalse(self.manager.check_token(self.context, token))
def _stub_validate_console_port(self, result):
def fake_validate_console_port(ctxt, instance, port, console_type):
return result
self.stubs.Set(self.manager.compute_rpcapi,
'validate_console_port',
fake_validate_console_port)
def test_multiple_tokens_for_instance(self):
tokens = ["token" + str(i) for i in xrange(10)]
instance = "12345"
def fake_validate_console_port(*args, **kwargs):
return True
self._stub_validate_console_port(True)
self.stubs.Set(self.manager.compute_rpcapi,
"validate_console_port",
fake_validate_console_port)
for token in tokens:
self.manager.authorize_console(self.context, token, 'novnc',
'127.0.0.1', '8080', 'host',
@ -92,12 +92,7 @@ class ConsoleauthTestCase(test.TestCase):
def test_wrong_token_has_port(self):
token = 'mytok'
def fake_validate_console_port(*args, **kwargs):
return False
self.stubs.Set(self.manager.compute_rpcapi,
"validate_console_port",
fake_validate_console_port)
self._stub_validate_console_port(False)
self.manager.authorize_console(self.context, token, 'novnc',
'127.0.0.1', '8080', 'host',
@ -114,3 +109,20 @@ class ConsoleauthTestCase(test.TestCase):
self.manager.backdoor_port = 59697
port = self.manager.get_backdoor_port(self.context)
self.assertEqual(port, self.manager.backdoor_port)
class CellsConsoleauthTestCase(ConsoleauthTestCase):
"""Test Case for consoleauth w/ cells enabled."""
def setUp(self):
super(CellsConsoleauthTestCase, self).setUp()
self.flags(enable=True, group='cells')
def _stub_validate_console_port(self, result):
def fake_validate_console_port(ctxt, instance_uuid, console_port,
console_type):
return result
self.stubs.Set(self.manager.cells_rpcapi,
'validate_console_port',
fake_validate_console_port)

View File

@ -30,6 +30,7 @@ CONF = cfg.CONF
class ConsoleAuthRpcAPITestCase(test.TestCase):
def _test_consoleauth_api(self, method, **kwargs):
do_cast = kwargs.pop('_do_cast', False)
ctxt = context.RequestContext('fake_user', 'fake_project')
rpcapi = consoleauth_rpcapi.ConsoleAuthAPI()
expected_retval = 'foo'
@ -45,18 +46,22 @@ class ConsoleAuthRpcAPITestCase(test.TestCase):
self.call_msg = None
self.call_timeout = None
def _fake_call(_ctxt, _topic, _msg, _timeout):
def _fake_call(_ctxt, _topic, _msg, _timeout=None):
self.call_ctxt = _ctxt
self.call_topic = _topic
self.call_msg = _msg
self.call_timeout = _timeout
return expected_retval
self.stubs.Set(rpc, 'call', _fake_call)
if do_cast:
self.stubs.Set(rpc, 'cast', _fake_call)
else:
self.stubs.Set(rpc, 'call', _fake_call)
retval = getattr(rpcapi, method)(ctxt, **kwargs)
self.assertEqual(retval, expected_retval)
if not do_cast:
self.assertEqual(retval, expected_retval)
self.assertEqual(self.call_ctxt, ctxt)
self.assertEqual(self.call_topic, CONF.consoleauth_topic)
self.assertEqual(self.call_msg, expected_msg)
@ -73,6 +78,7 @@ class ConsoleAuthRpcAPITestCase(test.TestCase):
def test_delete_tokens_for_instnace(self):
self._test_consoleauth_api('delete_tokens_for_instance',
_do_cast=True,
instance_uuid="instance",
version='1.2')