Creating instance fail when inject ssh key in cells mode
The conductor service of compute host will try to find ssh key in child cells when creating instance. But the ssh key was only stored at parent api cells. Therefore the conductor service will throw ssh key not found exception. There are three solutions: 1. add keypair_type into the instance object, along side keypair_name, etc 2. Sync ssh keypair to every child cell database 3. consider sending a message to the parent cell to fetch the keypair This commit prefer to use third solution. Because of the data ssh keypair should be global. And cells v2 split key_pairs table into global API tables. Change-Id: I156a1c3cf3e31f34cea5e240b14a9575e9e45239 Closes-Bug: #1443816
This commit is contained in:
parent
b32b146a33
commit
371be3f4a9
@ -58,7 +58,6 @@ r="$r|(?:tempest\.api\.compute\.servers\.test_disk_config\.ServerDiskConfigTestJ
|
||||
r="$r|(?:tempest\.api\.compute\.servers\.test_create_server\.ServersTestJSON\.test_create_server_with_scheduler_hint_group*)"
|
||||
r="$r|(?:tempest\.api\.compute\.servers\.test_servers_negative\.ServersNegativeTestJSON\.test_shelve_shelved_server*)"
|
||||
r="$r|(?:tempest\.api\.compute\.servers\.test_create_server\.ServersTestManualDisk\.test_create_server_with_scheduler_hint_group*)"
|
||||
r="$r|(?:tempest\.api\.compute\.servers\.test_servers\.ServersTestJSON\.test_create_specify_keypair*)"
|
||||
r="$r|(?:tempest\.api\.compute\.servers\.test_virtual_interfaces\.VirtualInterfacesTestJSON\.test_list_virtual_interfaces*)"
|
||||
r="$r|(?:tempest\.api\.compute\.test_networks\.NetworksTestJSON\.test_list_networks*)"
|
||||
r="$r|(?:tempest\.scenario\.test_minimum_basic\.TestMinimumBasicScenario\.test_minimum_basic_scenario*)"
|
||||
|
@ -31,6 +31,8 @@ from nova.api.ec2 import ec2utils
|
||||
from nova.api.metadata import password
|
||||
from nova import availability_zones as az
|
||||
from nova import block_device
|
||||
from nova.cells import opts as cells_opts
|
||||
from nova.cells import rpcapi as cells_rpcapi
|
||||
from nova import context
|
||||
from nova import network
|
||||
from nova import objects
|
||||
@ -313,9 +315,15 @@ class InstanceMetadata(object):
|
||||
self.instance.key_name: self.instance.key_data
|
||||
}
|
||||
|
||||
keypair = keypair_obj.KeyPair.get_by_name(
|
||||
context.get_admin_context(), self.instance.user_id,
|
||||
self.instance.key_name)
|
||||
if cells_opts.get_cell_type() == 'compute':
|
||||
cells_api = cells_rpcapi.CellsAPI()
|
||||
keypair = cells_api.get_keypair_at_top(
|
||||
context.get_admin_context(), self.instance.user_id,
|
||||
self.instance.key_name)
|
||||
else:
|
||||
keypair = keypair_obj.KeyPair.get_by_name(
|
||||
context.get_admin_context(), self.instance.user_id,
|
||||
self.instance.key_name)
|
||||
metadata['keys'] = [
|
||||
{'name': keypair.name,
|
||||
'type': keypair.type,
|
||||
|
@ -76,7 +76,7 @@ class CellsManager(manager.Manager):
|
||||
Scheduling requests get passed to the scheduler class.
|
||||
"""
|
||||
|
||||
target = oslo_messaging.Target(version='1.36')
|
||||
target = oslo_messaging.Target(version='1.37')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
LOG.warning(_LW('The cells feature of Nova is considered experimental '
|
||||
@ -583,3 +583,19 @@ class CellsManager(manager.Manager):
|
||||
|
||||
def set_admin_password(self, ctxt, instance, new_pass):
|
||||
self.msg_runner.set_admin_password(ctxt, instance, new_pass)
|
||||
|
||||
def get_keypair_at_top(self, ctxt, user_id, name):
|
||||
responses = self.msg_runner.get_keypair_at_top(ctxt, user_id, name)
|
||||
keypairs = [resp.value for resp in responses if resp.value is not None]
|
||||
|
||||
if len(keypairs) == 0:
|
||||
return None
|
||||
elif len(keypairs) > 1:
|
||||
cell_names = ', '.join([resp.cell_name for resp in responses
|
||||
if resp.value is not None])
|
||||
LOG.warning(_LW("The same keypair name '%(name)s' exists in the "
|
||||
"following cells: %(cell_names)s. The keypair "
|
||||
"value from the first cell is returned."),
|
||||
{'name': name, 'cell_names': cell_names})
|
||||
|
||||
return keypairs[0]
|
||||
|
@ -1211,6 +1211,18 @@ class _BroadcastMessageMethods(_BaseMessageMethods):
|
||||
context = message.ctxt
|
||||
return self.compute_api.get_migrations(context, filters)
|
||||
|
||||
def get_keypair_at_top(self, message, user_id, name):
|
||||
"""Get keypair in API cells by name. Just return None if there is
|
||||
no match keypair.
|
||||
"""
|
||||
if not self._at_the_top():
|
||||
return
|
||||
|
||||
try:
|
||||
return objects.KeyPair.get_by_name(message.ctxt, user_id, name)
|
||||
except exception.KeypairNotFound:
|
||||
pass
|
||||
|
||||
|
||||
_CELL_MESSAGE_TYPE_TO_MESSAGE_CLS = {'targeted': _TargetedMessage,
|
||||
'broadcast': _BroadcastMessage,
|
||||
@ -1804,6 +1816,15 @@ class MessageRunner(object):
|
||||
self._instance_action(ctxt, instance, 'set_admin_password',
|
||||
extra_kwargs={'new_pass': new_pass})
|
||||
|
||||
def get_keypair_at_top(self, ctxt, user_id, name):
|
||||
"""Get Key Pair by name at top level cell."""
|
||||
message = _BroadcastMessage(self, ctxt,
|
||||
'get_keypair_at_top',
|
||||
dict(user_id=user_id, name=name),
|
||||
'up',
|
||||
need_response=True, run_locally=False)
|
||||
return message.process()
|
||||
|
||||
@staticmethod
|
||||
def get_message_types():
|
||||
return _CELL_MESSAGE_TYPE_TO_MESSAGE_CLS.keys()
|
||||
|
@ -117,6 +117,7 @@ class CellsAPI(object):
|
||||
* 1.35 - Make instance_update_at_top, instance_destroy_at_top
|
||||
and instance_info_cache_update_at_top use instance objects
|
||||
* 1.36 - Added 'delete_type' parameter to terminate_instance()
|
||||
* 1.37 - Add get_keypair_at_top to fetch keypair from api cell
|
||||
'''
|
||||
|
||||
VERSION_ALIASES = {
|
||||
@ -691,3 +692,15 @@ class CellsAPI(object):
|
||||
cctxt = self.client.prepare(version='1.29')
|
||||
cctxt.cast(ctxt, 'set_admin_password', instance=instance,
|
||||
new_pass=new_pass)
|
||||
|
||||
def get_keypair_at_top(self, ctxt, user_id, name):
|
||||
if not CONF.cells.enable:
|
||||
return
|
||||
|
||||
cctxt = self.client.prepare(version='1.37')
|
||||
keypair = cctxt.call(ctxt, 'get_keypair_at_top', user_id=user_id,
|
||||
name=name)
|
||||
if keypair is None:
|
||||
raise exception.KeypairNotFound(user_id=user_id,
|
||||
name=name)
|
||||
return keypair
|
||||
|
@ -880,3 +880,27 @@ class CellsManagerClassTestCase(test.NoDBTestCase):
|
||||
instance='fake-instance', new_pass='fake-password')
|
||||
set_admin_password.assert_called_once_with(self.ctxt,
|
||||
'fake-instance', 'fake-password')
|
||||
|
||||
def test_get_keypair_at_top(self):
|
||||
keypairs = [self._get_fake_response('fake_keypair'),
|
||||
self._get_fake_response('fake_keypair2')]
|
||||
with mock.patch.object(self.msg_runner,
|
||||
'get_keypair_at_top',
|
||||
return_value=keypairs) as fake_get_keypair:
|
||||
response = self.cells_manager.get_keypair_at_top(self.ctxt,
|
||||
'fake_user_id',
|
||||
'fake_name')
|
||||
fake_get_keypair.assert_called_once_with(self.ctxt, 'fake_user_id',
|
||||
'fake_name')
|
||||
self.assertEqual('fake_keypair', response)
|
||||
|
||||
def test_get_keypair_at_top_with_empty_responses(self):
|
||||
with mock.patch.object(self.msg_runner,
|
||||
'get_keypair_at_top',
|
||||
return_value=[]) as fake_get_keypair:
|
||||
self.assertIsNone(
|
||||
self.cells_manager.get_keypair_at_top(self.ctxt,
|
||||
'fake_user_id',
|
||||
'fake_name'))
|
||||
fake_get_keypair.assert_called_once_with(self.ctxt, 'fake_user_id',
|
||||
'fake_name')
|
||||
|
@ -2079,6 +2079,48 @@ class CellsBroadcastMethodsTestCase(test.TestCase):
|
||||
self.assertIn(response.value_or_raise(), [migrations_from_cell1,
|
||||
migrations_from_cell2])
|
||||
|
||||
@mock.patch.object(objects.KeyPair, 'get_by_name',
|
||||
return_value='fake_keypair')
|
||||
def test_get_keypair_at_top(self, fake_get_by_name):
|
||||
user_id = 'fake_user_id'
|
||||
name = 'fake_keypair_name'
|
||||
responses = self.src_msg_runner.get_keypair_at_top(self.ctxt,
|
||||
user_id, name)
|
||||
fake_get_by_name.assert_called_once_with(self.ctxt, user_id, name)
|
||||
|
||||
for response in responses:
|
||||
if response.value is not None:
|
||||
self.assertEqual('fake_keypair', response.value)
|
||||
|
||||
@mock.patch.object(objects.KeyPair, 'get_by_name')
|
||||
def test_get_keypair_at_top_with_objects_exception(self, fake_get_by_name):
|
||||
user_id = 'fake_user_id'
|
||||
name = 'fake_keypair_name'
|
||||
keypair_exception = exception.KeypairNotFound(user_id=user_id,
|
||||
name=name)
|
||||
fake_get_by_name.side_effect = keypair_exception
|
||||
responses = self.src_msg_runner.get_keypair_at_top(self.ctxt,
|
||||
user_id,
|
||||
name)
|
||||
fake_get_by_name.assert_called_once_with(self.ctxt, user_id, name)
|
||||
|
||||
for response in responses:
|
||||
self.assertIsNone(response.value)
|
||||
|
||||
@mock.patch.object(messaging._BroadcastMessage, 'process')
|
||||
def test_get_keypair_at_top_with_process_response(self, fake_process):
|
||||
user_id = 'fake_user_id'
|
||||
name = 'fake_keypair_name'
|
||||
response = messaging.Response(self.ctxt, 'cell', 'keypair', False)
|
||||
other_response = messaging.Response(self.ctxt, 'cell',
|
||||
'fake_other_keypair', False)
|
||||
fake_process.return_value = [response, other_response]
|
||||
|
||||
responses = self.src_msg_runner.get_keypair_at_top(self.ctxt,
|
||||
user_id, name)
|
||||
fake_process.assert_called_once_with()
|
||||
self.assertEqual(fake_process.return_value, responses)
|
||||
|
||||
|
||||
class CellsPublicInterfacesTestCase(test.TestCase):
|
||||
"""Test case for the public interfaces into cells messaging."""
|
||||
|
@ -760,3 +760,25 @@ class CellsAPITestCase(test.NoDBTestCase):
|
||||
'new_pass': 'fake-password'}
|
||||
self._check_result(call_info, 'set_admin_password',
|
||||
expected_args, version='1.29')
|
||||
|
||||
def test_get_keypair_at_top(self):
|
||||
call_info = self._stub_rpc_method('call', 'fake_response')
|
||||
result = self.cells_rpcapi.get_keypair_at_top(self.fake_context,
|
||||
'fake_user_id', 'fake_name')
|
||||
|
||||
expected_args = {'user_id': 'fake_user_id',
|
||||
'name': 'fake_name'}
|
||||
self._check_result(call_info, 'get_keypair_at_top',
|
||||
expected_args, version='1.37')
|
||||
self.assertEqual(result, 'fake_response')
|
||||
|
||||
def test_get_keypair_at_top_with_not_found(self):
|
||||
call_info = self._stub_rpc_method('call', None)
|
||||
self.assertRaises(exception.KeypairNotFound,
|
||||
self.cells_rpcapi.get_keypair_at_top,
|
||||
self.fake_context, 'fake_user_id', 'fake_name')
|
||||
|
||||
expected_args = {'user_id': 'fake_user_id',
|
||||
'name': 'fake_name'}
|
||||
self._check_result(call_info, 'get_keypair_at_top',
|
||||
expected_args, version='1.37')
|
||||
|
@ -57,6 +57,7 @@ CONF = cfg.CONF
|
||||
|
||||
USER_DATA_STRING = (b"This is an encoded string")
|
||||
ENCODE_USER_DATA_STRING = base64.b64encode(USER_DATA_STRING)
|
||||
FAKE_SEED = '7qtD24mpMR2'
|
||||
|
||||
|
||||
def fake_inst_obj(context):
|
||||
@ -93,6 +94,12 @@ def fake_inst_obj(context):
|
||||
return inst
|
||||
|
||||
|
||||
def fake_keypair_obj(name, data):
|
||||
return objects.KeyPair(name=name,
|
||||
type='fake_type',
|
||||
public_key=data)
|
||||
|
||||
|
||||
def return_non_existing_address(*args, **kwarg):
|
||||
raise exception.NotFound()
|
||||
|
||||
@ -153,6 +160,8 @@ class MetadataTestCase(test.TestCase):
|
||||
self.context = context.RequestContext('fake', 'fake')
|
||||
self.instance = fake_inst_obj(self.context)
|
||||
self.flags(use_local=True, group='conductor')
|
||||
self.keypair = fake_keypair_obj(self.instance.key_name,
|
||||
self.instance.key_data)
|
||||
fake_network.stub_out_nw_api_get_instance_nw_info(self.stubs)
|
||||
|
||||
def test_can_pickle_metadata(self):
|
||||
@ -360,6 +369,69 @@ class MetadataTestCase(test.TestCase):
|
||||
data = md.get_ec2_metadata(version='2009-04-04')
|
||||
self.assertEqual(data['meta-data']['local-ipv4'], expected_local)
|
||||
|
||||
@mock.patch.object(base64, 'b64encode', lambda data: FAKE_SEED)
|
||||
@mock.patch('nova.cells.rpcapi.CellsAPI.get_keypair_at_top')
|
||||
@mock.patch.object(objects.KeyPair, 'get_by_name')
|
||||
@mock.patch.object(jsonutils, 'dumps')
|
||||
def _test_as_json_with_options(self, mock_json_dumps,
|
||||
mock_keypair, mock_cells_keypair,
|
||||
is_cells=False, os_version=base.GRIZZLY):
|
||||
if is_cells:
|
||||
self.flags(enable=True, group='cells')
|
||||
self.flags(cell_type='compute', group='cells')
|
||||
mock_keypair = mock_cells_keypair
|
||||
|
||||
instance = self.instance
|
||||
keypair = self.keypair
|
||||
md = fake_InstanceMetadata(self.stubs, instance)
|
||||
|
||||
expected_metadata = {
|
||||
'uuid': md.uuid,
|
||||
'hostname': md._get_hostname(),
|
||||
'name': instance.display_name,
|
||||
'launch_index': instance.launch_index,
|
||||
'availability_zone': md.availability_zone,
|
||||
}
|
||||
if md.launch_metadata:
|
||||
expected_metadata['meta'] = md.launch_metadata
|
||||
if md.files:
|
||||
expected_metadata['files'] = md.files
|
||||
if md.extra_md:
|
||||
expected_metadata['extra_md'] = md.extra_md
|
||||
if md.network_config:
|
||||
expected_metadata['network_config'] = md.network_config
|
||||
if instance.key_name:
|
||||
expected_metadata['public_keys'] = {
|
||||
keypair.name: keypair.public_key
|
||||
}
|
||||
expected_metadata['keys'] = [{'type': keypair.type,
|
||||
'data': keypair.public_key,
|
||||
'name': keypair.name}]
|
||||
if md._check_os_version(base.GRIZZLY, os_version):
|
||||
expected_metadata['random_seed'] = FAKE_SEED
|
||||
if md._check_os_version(base.LIBERTY, os_version):
|
||||
expected_metadata['project_id'] = instance.project_id
|
||||
|
||||
mock_keypair.return_value = keypair
|
||||
md._metadata_as_json(os_version, 'non useless path parameter')
|
||||
if instance.key_name:
|
||||
mock_keypair.assert_called_once_with(mock.ANY,
|
||||
instance.user_id,
|
||||
instance.key_name)
|
||||
self.assertIsInstance(mock_keypair.call_args[0][0],
|
||||
context.RequestContext)
|
||||
self.assertEqual(md.md_mimetype, base.MIME_TYPE_APPLICATION_JSON)
|
||||
mock_json_dumps.assert_called_once_with(expected_metadata)
|
||||
|
||||
def test_as_json(self):
|
||||
for os_version in base.OPENSTACK_VERSIONS:
|
||||
self._test_as_json_with_options(os_version=os_version)
|
||||
|
||||
def test_as_json_with_cells_mode(self):
|
||||
for os_version in base.OPENSTACK_VERSIONS:
|
||||
self._test_as_json_with_options(is_cells=True,
|
||||
os_version=os_version)
|
||||
|
||||
|
||||
class OpenStackMetadataTestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
|
Loading…
x
Reference in New Issue
Block a user