Merge "Allow keypair to be added during cluster create"
This commit is contained in:
commit
8083fc2971
|
@ -101,13 +101,15 @@ They are loosely grouped as: mandatory, infrastructure, COE specific.
|
|||
Mesos Ubuntu
|
||||
========== =====================
|
||||
|
||||
This is a mandatory parameter and there is no default value.
|
||||
This is a mandatory parameter and there is no default value.
|
||||
|
||||
--keypair-id \<keypair-id\>
|
||||
The name or UUID of the SSH keypair to configure in the cluster servers
|
||||
for ssh access. You will need the key to be able to ssh to the
|
||||
servers in the cluster. The login name is specific to the cluster
|
||||
driver. This is a mandatory parameter and there is no default value.
|
||||
driver. If keypair is not provided in template it will be required at
|
||||
Cluster create. This value will be overridden by any keypair value that
|
||||
is provided during Cluster create.
|
||||
|
||||
--external-network-id \<external-network-id\>
|
||||
The name or network ID of a Neutron network to provide connectivity
|
||||
|
@ -427,6 +429,15 @@ follows:
|
|||
name will be generated using a string and a number, for example
|
||||
"gamma-7-cluster".
|
||||
|
||||
--keypair \<keypair\>
|
||||
The name or UUID of the SSH keypair to configure in the cluster servers
|
||||
for ssh access. You will need the key to be able to ssh to the
|
||||
servers in the cluster. The login name is specific to the cluster
|
||||
driver. If keypair is not provided it will attempt to use the value in
|
||||
the ClusterTemplate. If the ClusterTemplate is also missing a keypair value
|
||||
then an error will be returned. The keypair value provided here will
|
||||
override the keypair value from the ClusterTemplate.
|
||||
|
||||
--node-count \<node-count\>
|
||||
The number of servers that will serve as node in the cluster.
|
||||
The default is 1.
|
||||
|
|
|
@ -166,7 +166,7 @@ def validate_labels_executor_env_variables(labels):
|
|||
raise exception.InvalidParameterValue(err)
|
||||
|
||||
|
||||
def validate_os_resources(context, cluster_template):
|
||||
def validate_os_resources(context, cluster_template, cluster=None):
|
||||
"""Validate ClusterTemplate's OpenStack Resources"""
|
||||
|
||||
cli = clients.OpenStackClients(context)
|
||||
|
@ -178,6 +178,9 @@ def validate_os_resources(context, cluster_template):
|
|||
else:
|
||||
validate_method(cluster_template[attr])
|
||||
|
||||
if cluster:
|
||||
validate_keypair(cli, cluster['keypair'])
|
||||
|
||||
|
||||
def validate_master_count(cluster, cluster_template):
|
||||
if cluster['master_count'] > 1 and \
|
||||
|
@ -190,7 +193,6 @@ def validate_master_count(cluster, cluster_template):
|
|||
validators = {'image_id': validate_image,
|
||||
'flavor_id': validate_flavor,
|
||||
'master_flavor_id': validate_flavor,
|
||||
'keypair_id': validate_keypair,
|
||||
'external_network_id': validate_external_network,
|
||||
'fixed_network': validate_fixed_network,
|
||||
'labels': validate_labels}
|
||||
|
|
|
@ -415,9 +415,13 @@ class BaysController(base.Controller):
|
|||
action='bay:create')
|
||||
baymodel = objects.ClusterTemplate.get_by_uuid(context,
|
||||
bay.baymodel_id)
|
||||
attr_validator.validate_os_resources(context, baymodel.as_dict())
|
||||
attr_validator.validate_master_count(bay.as_dict(), baymodel.as_dict())
|
||||
|
||||
bay_dict = bay.as_dict()
|
||||
bay_dict['keypair'] = baymodel.keypair_id
|
||||
attr_validator.validate_os_resources(context, baymodel.as_dict(),
|
||||
bay_dict)
|
||||
attr_validator.validate_master_count(bay.as_dict(), baymodel.as_dict())
|
||||
|
||||
bay_dict['project_id'] = context.project_id
|
||||
bay_dict['user_id'] = context.user_id
|
||||
# NOTE(yuywz): We will generate a random human-readable name for
|
||||
|
@ -426,7 +430,6 @@ class BaysController(base.Controller):
|
|||
bay_dict['name'] = name
|
||||
bay_dict['coe_version'] = None
|
||||
bay_dict['container_version'] = None
|
||||
|
||||
new_bay = objects.Cluster(context, **bay_dict)
|
||||
new_bay.uuid = uuid.uuid4()
|
||||
return new_bay
|
||||
|
|
|
@ -95,6 +95,10 @@ class Cluster(base.APIBase):
|
|||
mandatory=True)
|
||||
"""The cluster_template UUID"""
|
||||
|
||||
keypair = wsme.wsattr(wtypes.StringType(min_length=1, max_length=255),
|
||||
default=None)
|
||||
"""The name or id of the nova ssh keypair"""
|
||||
|
||||
node_count = wsme.wsattr(wtypes.IntegerType(minimum=1), default=1)
|
||||
"""The node count for this cluster. Default to 1 if not set"""
|
||||
|
||||
|
@ -152,7 +156,7 @@ class Cluster(base.APIBase):
|
|||
def _convert_with_links(cluster, url, expand=True):
|
||||
if not expand:
|
||||
cluster.unset_fields_except(['uuid', 'name', 'cluster_template_id',
|
||||
'node_count', 'status',
|
||||
'keypair', 'node_count', 'status',
|
||||
'create_timeout', 'master_count',
|
||||
'stack_id'])
|
||||
|
||||
|
@ -174,6 +178,7 @@ class Cluster(base.APIBase):
|
|||
sample = cls(uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
|
||||
name='example',
|
||||
cluster_template_id=temp_id,
|
||||
keypair=None,
|
||||
node_count=2,
|
||||
master_count=1,
|
||||
create_timeout=15,
|
||||
|
@ -360,10 +365,15 @@ class ClustersController(base.Controller):
|
|||
temp_id = cluster.cluster_template_id
|
||||
cluster_template = objects.ClusterTemplate.get_by_uuid(context,
|
||||
temp_id)
|
||||
# If keypair not present, use cluster_template value
|
||||
if cluster.keypair is None:
|
||||
cluster.keypair = cluster_template.keypair_id
|
||||
|
||||
cluster_dict = cluster.as_dict()
|
||||
|
||||
attr_validator.validate_os_resources(context,
|
||||
cluster_template.as_dict())
|
||||
cluster_template.as_dict(),
|
||||
cluster_dict)
|
||||
attr_validator.validate_master_count(cluster_dict,
|
||||
cluster_template.as_dict())
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ class ClusterTemplate(base.APIBase):
|
|||
"""The DNS nameserver address"""
|
||||
|
||||
keypair_id = wsme.wsattr(wtypes.StringType(min_length=1, max_length=255),
|
||||
mandatory=True)
|
||||
default=None)
|
||||
"""The name or id of the nova ssh keypair"""
|
||||
|
||||
external_network_id = wtypes.StringType(min_length=1, max_length=255)
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
#
|
||||
# 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.
|
||||
|
||||
"""add keypair to cluster
|
||||
|
||||
Revision ID: bc46ba6cf949
|
||||
Revises: 720f640f43d1
|
||||
Create Date: 2016-10-03 10:47:08.584635
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'bc46ba6cf949'
|
||||
down_revision = '720f640f43d1'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column('cluster', sa.Column('keypair', sa.String(length=255),
|
||||
nullable=True))
|
|
@ -115,6 +115,7 @@ class Cluster(Base):
|
|||
uuid = Column(String(36))
|
||||
name = Column(String(255))
|
||||
cluster_template_id = Column(String(255))
|
||||
keypair = Column(String(255))
|
||||
stack_id = Column(String(255))
|
||||
api_address = Column(String(255))
|
||||
node_addresses = Column(JSONEncodedList)
|
||||
|
|
|
@ -329,7 +329,7 @@ class BaseTemplateDefinition(TemplateDefinition):
|
|||
self._osc = None
|
||||
|
||||
self.add_parameter('ssh_key_name',
|
||||
cluster_template_attr='keypair_id',
|
||||
cluster_attr='keypair',
|
||||
required=True)
|
||||
self.add_parameter('server_image',
|
||||
cluster_template_attr='image_id')
|
||||
|
|
|
@ -41,8 +41,9 @@ class Cluster(base.MagnumPersistentObject, base.MagnumObject,
|
|||
# Version 1.9: Rename table name from 'bay' to 'cluster'
|
||||
# Rename 'baymodel_id' to 'cluster_template_id'
|
||||
# Rename 'bay_create_timeout' to 'create_timeout'
|
||||
# Version 1.10: Added 'keypair' field
|
||||
|
||||
VERSION = '1.9'
|
||||
VERSION = '1.10'
|
||||
|
||||
dbapi = dbapi.get_instance()
|
||||
|
||||
|
@ -53,6 +54,7 @@ class Cluster(base.MagnumPersistentObject, base.MagnumObject,
|
|||
'project_id': fields.StringField(nullable=True),
|
||||
'user_id': fields.StringField(nullable=True),
|
||||
'cluster_template_id': fields.StringField(nullable=True),
|
||||
'keypair': fields.StringField(nullable=True),
|
||||
'stack_id': fields.StringField(nullable=True),
|
||||
'status': m_fields.ClusterStatusField(nullable=True),
|
||||
'status_reason': fields.StringField(nullable=True),
|
||||
|
|
|
@ -185,13 +185,6 @@ class BayModelTest(base.BaseTempestTest):
|
|||
exceptions.BadRequest,
|
||||
self.baymodel_client.post_baymodel, gen_model)
|
||||
|
||||
@testtools.testcase.attr('negative')
|
||||
def test_create_baymodel_missing_keypair(self):
|
||||
gen_model = datagen.baymodel_data_with_missing_keypair()
|
||||
self.assertRaises(
|
||||
exceptions.NotFound,
|
||||
self.baymodel_client.post_baymodel, gen_model)
|
||||
|
||||
@testtools.testcase.attr('negative')
|
||||
def test_update_baymodel_invalid_patch(self):
|
||||
# get json object
|
||||
|
|
|
@ -200,13 +200,11 @@ class ClusterTemplateTest(base.BaseTempestTest):
|
|||
exceptions.BadRequest,
|
||||
self.cluster_template_client.post_cluster_template, gen_model)
|
||||
|
||||
@testtools.testcase.attr('negative')
|
||||
@testtools.testcase.attr('positive')
|
||||
def test_create_cluster_template_missing_keypair(self):
|
||||
gen_model = \
|
||||
datagen.cluster_template_data_with_missing_keypair()
|
||||
self.assertRaises(
|
||||
exceptions.NotFound,
|
||||
self.cluster_template_client.post_cluster_template, gen_model)
|
||||
resp, model = self._create_cluster_template(gen_model)
|
||||
|
||||
@testtools.testcase.attr('negative')
|
||||
def test_update_cluster_template_invalid_patch(self):
|
||||
|
|
|
@ -502,7 +502,6 @@ def valid_swarm_cluster_template(is_public=False):
|
|||
public=is_public,
|
||||
dns_nameserver=config.Config.dns_nameserver,
|
||||
master_flavor_id=master_flavor_id,
|
||||
keypair_id=config.Config.keypair_id,
|
||||
coe="swarm", docker_volume_size=3,
|
||||
cluster_distro=None,
|
||||
external_network_id=config.Config.nic_id,
|
||||
|
@ -535,6 +534,7 @@ def cluster_data(name=data_utils.rand_name('cluster'),
|
|||
data = {
|
||||
"name": name,
|
||||
"cluster_template_id": cluster_template_id,
|
||||
"keypair": config.Config.keypair_id,
|
||||
"node_count": node_count,
|
||||
"discovery_url": None,
|
||||
"create_timeout": create_timeout,
|
||||
|
|
|
@ -741,6 +741,24 @@ class TestPost(api_base.FunctionalTest):
|
|||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual(400, response.status_int)
|
||||
|
||||
def test_create_cluster_with_keypair(self):
|
||||
bdict = apiutils.cluster_post_data()
|
||||
bdict['keypair'] = 'keypair2'
|
||||
response = self.post_json('/clusters', bdict)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual(202, response.status_int)
|
||||
cluster, timeout = self.mock_cluster_create.call_args
|
||||
self.assertEqual('keypair2', cluster[0].keypair)
|
||||
|
||||
def test_create_cluster_without_keypair(self):
|
||||
bdict = apiutils.cluster_post_data()
|
||||
response = self.post_json('/clusters', bdict)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual(202, response.status_int)
|
||||
cluster, timeout = self.mock_cluster_create.call_args
|
||||
# Verify keypair from ClusterTemplate is used
|
||||
self.assertEqual('keypair1', cluster[0].keypair)
|
||||
|
||||
|
||||
class TestDelete(api_base.FunctionalTest):
|
||||
def setUp(self):
|
||||
|
|
|
@ -442,7 +442,7 @@ class TestPatch(api_base.FunctionalTest):
|
|||
self.assertTrue(response.json['errors'])
|
||||
|
||||
def test_remove_mandatory_property_fail(self):
|
||||
mandatory_properties = ('/image_id', '/keypair_id', '/coe',
|
||||
mandatory_properties = ('/image_id', '/coe',
|
||||
'/external_network_id', '/server_type',
|
||||
'/tls_disabled', '/public',
|
||||
'/registry_enabled',
|
||||
|
@ -860,12 +860,15 @@ class TestPost(api_base.FunctionalTest):
|
|||
expect_errors=True)
|
||||
self.assertEqual(400, response.status_int)
|
||||
|
||||
def test_create_cluster_template_without_keypair_id(self):
|
||||
@mock.patch('magnum.api.attr_validator.validate_image')
|
||||
def test_create_cluster_template_without_keypair_id(self,
|
||||
mock_image_data):
|
||||
mock_image_data.return_value = {'name': 'mock_name',
|
||||
'os_distro': 'fedora-atomic'}
|
||||
bdict = apiutils.cluster_template_post_data()
|
||||
del bdict['keypair_id']
|
||||
response = self.post_json('/clustertemplates', bdict,
|
||||
expect_errors=True)
|
||||
self.assertEqual(400, response.status_int)
|
||||
response = self.post_json('/clustertemplates', bdict)
|
||||
self.assertEqual(201, response.status_int)
|
||||
|
||||
@mock.patch('magnum.api.attr_validator.validate_image')
|
||||
def test_create_cluster_template_with_dns(self,
|
||||
|
|
|
@ -297,3 +297,18 @@ class TestAttrValidator(base.BaseTestCase):
|
|||
mock_context = mock.MagicMock()
|
||||
attr_validator.validate_os_resources(mock_context,
|
||||
mock_cluster_template)
|
||||
|
||||
@mock.patch('magnum.common.clients.OpenStackClients')
|
||||
def test_validate_os_resources_with_cluster(self, mock_os_cli):
|
||||
mock_cluster_template = {}
|
||||
mock_cluster = {'keypair': 'test-keypair'}
|
||||
mock_keypair = mock.MagicMock()
|
||||
mock_keypair.id = 'test-keypair'
|
||||
mock_nova = mock.MagicMock()
|
||||
mock_nova.keypairs.get.return_value = mock_keypair
|
||||
mock_os_cli = mock.MagicMock()
|
||||
mock_os_cli.nova.return_value = mock_nova
|
||||
mock_context = mock.MagicMock()
|
||||
attr_validator.validate_os_resources(mock_context,
|
||||
mock_cluster_template,
|
||||
mock_cluster)
|
||||
|
|
|
@ -56,6 +56,7 @@ class TestClusterConductorWithK8s(base.TestCase):
|
|||
self.cluster_dict = {
|
||||
'uuid': '5d12f6fd-a196-4bf0-ae4c-1f639a523a52',
|
||||
'cluster_template_id': 'xx-xx-xx-xx',
|
||||
'keypair': 'keypair_id',
|
||||
'name': 'cluster1',
|
||||
'stack_id': 'xx-xx-xx-xx',
|
||||
'api_address': '172.17.2.3',
|
||||
|
|
|
@ -52,6 +52,7 @@ class TestClusterConductorWithMesos(base.TestCase):
|
|||
'id': 1,
|
||||
'uuid': '5d12f6fd-a196-4bf0-ae4c-1f639a523a52',
|
||||
'cluster_template_id': 'xx-xx-xx-xx',
|
||||
'keypair': 'keypair_id',
|
||||
'name': 'cluster1',
|
||||
'stack_id': 'xx-xx-xx-xx',
|
||||
'api_address': '172.17.2.3',
|
||||
|
|
|
@ -55,6 +55,7 @@ class TestClusterConductorWithSwarm(base.TestCase):
|
|||
'id': 1,
|
||||
'uuid': '5d12f6fd-a196-4bf0-ae4c-1f639a523a52',
|
||||
'cluster_template_id': 'xx-xx-xx-xx',
|
||||
'keypair': 'keypair_id',
|
||||
'name': 'cluster1',
|
||||
'stack_id': 'xx-xx-xx-xx',
|
||||
'api_address': '172.17.2.3',
|
||||
|
|
|
@ -37,6 +37,7 @@ class TestClusterObject(base.DbTestCase):
|
|||
cluster_template_id = self.fake_cluster['cluster_template_id']
|
||||
self.fake_cluster_template = objects.ClusterTemplate(
|
||||
uuid=cluster_template_id)
|
||||
self.fake_cluster['keypair'] = 'keypair1'
|
||||
|
||||
@mock.patch('magnum.objects.ClusterTemplate.get_by_uuid')
|
||||
def test_get_by_id(self, mock_cluster_template_get):
|
||||
|
|
|
@ -362,7 +362,7 @@ class TestObject(test_base.TestCase, _TestObject):
|
|||
# For more information on object version testing, read
|
||||
# http://docs.openstack.org/developer/magnum/objects.html
|
||||
object_data = {
|
||||
'Cluster': '1.9-f9838e23eef5f1a7d9606c1ccce21800',
|
||||
'Cluster': '1.10-377082b6d7895cd800a39fa004765538',
|
||||
'ClusterTemplate': '1.17-65a95ef932dd08800a83871eb3cf312b',
|
||||
'Certificate': '1.1-1924dc077daa844f0f9076332ef96815',
|
||||
'MyObj': '1.0-b43567e512438205e32f4e95ca616697',
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
prelude: >
|
||||
Magnum's keypair-override-on-create blueprint [1]
|
||||
allows for optional keypair value in ClusterTemplates
|
||||
and the ability to specify a keypair value during
|
||||
cluster creation.
|
||||
features:
|
||||
- Added parameter in cluster-create to specify the
|
||||
keypair. If keypair is not provided, the default
|
||||
value from the matching ClusterTemplate will be used.
|
||||
- Keypair is now optional for ClusterTemplate, in order
|
||||
to allow Clusters to use keypairs separate from their
|
||||
parent ClusterTemplate.
|
||||
deprecations:
|
||||
- --keypair-id parameter in magnum CLI
|
||||
cluster-template-create has been renamed to
|
||||
--keypair.
|
Loading…
Reference in New Issue