Merge "Allow to specify 'user' for keypair resource"

This commit is contained in:
Jenkins 2017-04-20 12:42:42 +00:00 committed by Gerrit Code Review
commit a9c423c1c8
3 changed files with 107 additions and 23 deletions

View File

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

View File

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

View File

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