Merge "Allow to specify 'user' for keypair resource"
This commit is contained in:
commit
a9c423c1c8
|
@ -59,9 +59,9 @@ class NovaClientPlugin(client_plugin.ClientPlugin):
|
|||
NOVA_API_VERSION = '2.1'
|
||||
|
||||
validate_versions = [
|
||||
V2_2, V2_15, V2_26, V2_8
|
||||
V2_2, V2_8, V2_10, V2_15, V2_26
|
||||
] = [
|
||||
'2.2', '2.15', '2.26', '2.8'
|
||||
'2.2', '2.8', '2.10', '2.15', '2.26'
|
||||
]
|
||||
|
||||
supported_versions = [NOVA_API_VERSION] + validate_versions
|
||||
|
|
|
@ -19,6 +19,7 @@ from heat.engine import constraints
|
|||
from heat.engine import properties
|
||||
from heat.engine import resource
|
||||
from heat.engine import support
|
||||
from heat.engine import translation
|
||||
|
||||
|
||||
class KeyPair(resource.Resource):
|
||||
|
@ -41,9 +42,9 @@ class KeyPair(resource.Resource):
|
|||
required_service_extension = 'os-keypairs'
|
||||
|
||||
PROPERTIES = (
|
||||
NAME, SAVE_PRIVATE_KEY, PUBLIC_KEY, KEY_TYPE,
|
||||
NAME, SAVE_PRIVATE_KEY, PUBLIC_KEY, KEY_TYPE, USER,
|
||||
) = (
|
||||
'name', 'save_private_key', 'public_key', 'type',
|
||||
'name', 'save_private_key', 'public_key', 'type', 'user',
|
||||
)
|
||||
|
||||
ATTRIBUTES = (
|
||||
|
@ -80,6 +81,14 @@ class KeyPair(resource.Resource):
|
|||
constraints.AllowedValues(['ssh', 'x509'])],
|
||||
support_status=support.SupportStatus(version='8.0.0')
|
||||
),
|
||||
USER: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('ID or name of user to whom to add key-pair. The usage of this '
|
||||
'property is limited to being used by administrators only. '
|
||||
'Supported since Nova api version 2.10.'),
|
||||
constraints=[constraints.CustomConstraint('keystone.user')],
|
||||
support_status=support.SupportStatus(version='9.0.0')
|
||||
),
|
||||
}
|
||||
|
||||
attributes_schema = {
|
||||
|
@ -102,6 +111,17 @@ class KeyPair(resource.Resource):
|
|||
super(KeyPair, self).__init__(name, json_snippet, stack)
|
||||
self._public_key = None
|
||||
|
||||
def translation_rules(self, props):
|
||||
return [
|
||||
translation.TranslationRule(
|
||||
props,
|
||||
translation.TranslationRule.RESOLVE,
|
||||
[self.USER],
|
||||
client_plugin=self.client_plugin('keystone'),
|
||||
finder='get_user_id'
|
||||
)
|
||||
]
|
||||
|
||||
@property
|
||||
def private_key(self):
|
||||
"""Return the private SSH key for the resource."""
|
||||
|
@ -125,29 +145,45 @@ class KeyPair(resource.Resource):
|
|||
super(KeyPair, self).validate()
|
||||
|
||||
# Check if key_type is allowed to use
|
||||
if self.properties[self.KEY_TYPE]:
|
||||
key_type = self.properties[self.KEY_TYPE]
|
||||
user = self.properties[self.USER]
|
||||
|
||||
nc_version = None
|
||||
validate_props = []
|
||||
if key_type:
|
||||
nc_version = self.client_plugin().V2_2
|
||||
validate_props.append(self.KEY_TYPE)
|
||||
if user:
|
||||
nc_version = self.client_plugin().V2_10
|
||||
validate_props.append(self.USER)
|
||||
if nc_version:
|
||||
try:
|
||||
self.client(
|
||||
version=self.client_plugin().V2_2)
|
||||
self.client(version=nc_version)
|
||||
except exception.InvalidServiceVersion as ex:
|
||||
msg = (_('Cannot use "%(type)s" property - nova does not '
|
||||
'support it: %(error)s') %
|
||||
{'error': six.text_type(ex), 'type': self.KEY_TYPE})
|
||||
msg = (_('Cannot use "%(prop)s" properties - nova does not '
|
||||
'support: %(error)s') %
|
||||
{'error': six.text_type(ex), 'prop': validate_props})
|
||||
raise exception.StackValidationFailed(message=msg)
|
||||
|
||||
def handle_create(self):
|
||||
pub_key = self.properties[self.PUBLIC_KEY] or None
|
||||
user_id = self.properties[self.USER]
|
||||
key_type = self.properties[self.KEY_TYPE]
|
||||
nc = self.client(
|
||||
version=self.client_plugin().V2_2) if key_type else self.client()
|
||||
|
||||
create_kwargs = {
|
||||
'name': self.properties[self.NAME],
|
||||
'public_key': pub_key
|
||||
}
|
||||
if key_type:
|
||||
create_kwargs[self.KEY_TYPE] = key_type
|
||||
|
||||
nc_version = None
|
||||
if key_type:
|
||||
nc_version = self.client_plugin().V2_2
|
||||
create_kwargs[self.KEY_TYPE] = key_type
|
||||
if user_id:
|
||||
nc_version = self.client_plugin().V2_10
|
||||
create_kwargs['user_id'] = user_id
|
||||
|
||||
nc = self.client(version=nc_version)
|
||||
new_keypair = nc.keypairs.create(**create_kwargs)
|
||||
|
||||
if (self.properties[self.SAVE_PRIVATE_KEY] and
|
||||
|
|
|
@ -17,6 +17,7 @@ import mock
|
|||
import six
|
||||
|
||||
from heat.common import exception
|
||||
from heat.engine.clients.os import keystone
|
||||
from heat.engine.clients.os import nova
|
||||
from heat.engine import resource
|
||||
from heat.engine.resources.openstack.nova import keypair
|
||||
|
@ -47,6 +48,8 @@ class NovaKeyPairTest(common.HeatTestCase):
|
|||
self.fake_nova.keypairs = self.fake_keypairs
|
||||
self.patchobject(nova.NovaClientPlugin, 'has_extension',
|
||||
return_value=True)
|
||||
self.cp_mock = self.patchobject(nova.NovaClientPlugin, '_create',
|
||||
return_value=self.fake_nova)
|
||||
|
||||
def _mock_key(self, name, pub=None, priv=None):
|
||||
mkey = mock.MagicMock()
|
||||
|
@ -62,12 +65,11 @@ class NovaKeyPairTest(common.HeatTestCase):
|
|||
self.stack = utils.parse_stack(template)
|
||||
definition = self.stack.t.resource_definitions(self.stack)['kp']
|
||||
kp_res = keypair.KeyPair('kp', definition, self.stack)
|
||||
self.patchobject(nova.NovaClientPlugin, '_create',
|
||||
return_value=self.fake_nova)
|
||||
return kp_res
|
||||
|
||||
def _get_mock_kp_for_create(self, key_name, public_key=None,
|
||||
priv_saved=False, key_type=None):
|
||||
priv_saved=False, key_type=None,
|
||||
user=None):
|
||||
template = copy.deepcopy(self.kp_template)
|
||||
template['resources']['kp']['properties']['name'] = key_name
|
||||
props = template['resources']['kp']['properties']
|
||||
|
@ -80,6 +82,8 @@ class NovaKeyPairTest(common.HeatTestCase):
|
|||
props['save_private_key'] = True
|
||||
if key_type:
|
||||
props['type'] = key_type
|
||||
if user:
|
||||
props['user'] = user
|
||||
kp_res = self._get_test_resource(template)
|
||||
self.patchobject(self.fake_keypairs, 'create',
|
||||
return_value=nova_key)
|
||||
|
@ -113,6 +117,35 @@ class NovaKeyPairTest(common.HeatTestCase):
|
|||
self.assertEqual(tp_test.resource_id, created_key.name)
|
||||
self.fake_keypairs.create.assert_called_once_with(
|
||||
name=key_name, public_key=None, type='ssh')
|
||||
self.cp_mock.assert_called_once_with(version='2.2')
|
||||
|
||||
def test_create_key_with_user_id(self):
|
||||
key_name = "create_with_user_id"
|
||||
tp_test, created_key = self._get_mock_kp_for_create(key_name,
|
||||
user='userA')
|
||||
self.patchobject(keystone.KeystoneClientPlugin, 'get_user_id',
|
||||
return_value='userA_ID')
|
||||
scheduler.TaskRunner(tp_test.create)()
|
||||
self.assertEqual((tp_test.CREATE, tp_test.COMPLETE), tp_test.state)
|
||||
self.assertEqual(tp_test.resource_id, created_key.name)
|
||||
self.fake_keypairs.create.assert_called_once_with(
|
||||
name=key_name, public_key=None, user_id='userA_ID')
|
||||
self.cp_mock.assert_called_once_with(version='2.10')
|
||||
|
||||
def test_create_key_with_user_and_type(self):
|
||||
key_name = "create_with_user_id_and_type"
|
||||
tp_test, created_key = self._get_mock_kp_for_create(key_name,
|
||||
user='userA',
|
||||
key_type='x509')
|
||||
self.patchobject(keystone.KeystoneClientPlugin, 'get_user_id',
|
||||
return_value='userA_ID')
|
||||
scheduler.TaskRunner(tp_test.create)()
|
||||
self.assertEqual((tp_test.CREATE, tp_test.COMPLETE), tp_test.state)
|
||||
self.assertEqual(tp_test.resource_id, created_key.name)
|
||||
self.fake_keypairs.create.assert_called_once_with(
|
||||
name=key_name, public_key=None, user_id='userA_ID',
|
||||
type='x509')
|
||||
self.cp_mock.assert_called_once_with(version='2.10')
|
||||
|
||||
def test_create_key_empty_name(self):
|
||||
"""Test creation of a keypair whose name is of length zero."""
|
||||
|
@ -142,23 +175,38 @@ class NovaKeyPairTest(common.HeatTestCase):
|
|||
self.assertIn("kp.properties.name: length (256) is out of "
|
||||
"range (min: 1, max: 255)", six.text_type(error))
|
||||
|
||||
def test_validate(self):
|
||||
def _test_validate(self, key_type=None, user=None, nc_version=None):
|
||||
template = copy.deepcopy(self.kp_template)
|
||||
template['resources']['kp']['properties']['type'] = 'x509'
|
||||
validate_props = []
|
||||
if key_type:
|
||||
template['resources']['kp']['properties']['type'] = key_type
|
||||
validate_props.append('type')
|
||||
if user:
|
||||
template['resources']['kp']['properties']['user'] = user
|
||||
validate_props.append('user')
|
||||
stack = utils.parse_stack(template)
|
||||
definition = stack.t.resource_definitions(stack)['kp']
|
||||
kp_res = keypair.KeyPair('kp', definition, stack)
|
||||
self.patchobject(nova.NovaClientPlugin, '_create',
|
||||
side_effect=exception.InvalidServiceVersion(
|
||||
service='compute',
|
||||
version='2.2'
|
||||
version=nc_version
|
||||
))
|
||||
|
||||
error = self.assertRaises(exception.StackValidationFailed,
|
||||
kp_res.validate)
|
||||
self.assertIn('Cannot use "type" property - nova does not support it: '
|
||||
'Invalid service compute version 2.2',
|
||||
six.text_type(error))
|
||||
msg = (('Cannot use "%(prop)s" properties - nova does not support: '
|
||||
'Invalid service compute version %(ver)s') %
|
||||
{'prop': validate_props, 'ver': nc_version})
|
||||
self.assertIn(msg, six.text_type(error))
|
||||
|
||||
def test_validate_key_type(self):
|
||||
self._test_validate(key_type='x509', nc_version='2.2')
|
||||
|
||||
def test_validate_user(self):
|
||||
self.patchobject(keystone.KeystoneClientPlugin, 'get_user_id',
|
||||
return_value='user_A')
|
||||
self._test_validate(user='user_A', nc_version='2.10')
|
||||
|
||||
def test_check_key(self):
|
||||
res = self._get_test_resource(self.kp_template)
|
||||
|
|
Loading…
Reference in New Issue