diff --git a/api-ref/source/clusters.inc b/api-ref/source/clusters.inc index 7a8260520f..905051f6c5 100644 --- a/api-ref/source/clusters.inc +++ b/api-ref/source/clusters.inc @@ -39,6 +39,7 @@ Request - node_count: node_count - create_timeout: create_timeout - keypair: keypair_id + - master_flavor_id: master_flavor_id .. note:: diff --git a/api-ref/source/samples/cluster-create-req.json b/api-ref/source/samples/cluster-create-req.json index fa1f36ddcd..ac1da91014 100644 --- a/api-ref/source/samples/cluster-create-req.json +++ b/api-ref/source/samples/cluster-create-req.json @@ -5,5 +5,6 @@ "cluster_template_id":"0562d357-8641-4759-8fed-8173f02c9633", "node_count":2, "create_timeout":60, - "keypair":"my_keypair" + "keypair":"my_keypair", + "master_flavor_id":null } diff --git a/doc/source/user/index.rst b/doc/source/user/index.rst index cf8b18db93..505c63c53b 100644 --- a/doc/source/user/index.rst +++ b/doc/source/user/index.rst @@ -211,7 +211,7 @@ They are loosely grouped as: mandatory, infrastructure, COE specific. --master-flavor \ The nova flavor id for booting the master or manager servers. The - default is 'm1.small'. + default is 'm1.small'. This value can be overridden at cluster creation. --http-proxy \ The IP address for a proxy to use when direct http access from the diff --git a/magnum/api/controllers/v1/bay.py b/magnum/api/controllers/v1/bay.py index 3752d9d543..47aba45a57 100755 --- a/magnum/api/controllers/v1/bay.py +++ b/magnum/api/controllers/v1/bay.py @@ -95,6 +95,9 @@ class Bay(base.APIBase): labels = wtypes.DictType(str, str) """One or more key/value pairs""" + master_flavor_id = wtypes.StringType(min_length=1, max_length=255) + """The master flavor of this Bay""" + bay_create_timeout = wsme.wsattr(wtypes.IntegerType(minimum=0), default=60) """Timeout for creating the bay in minutes. Default to 60 if not set""" @@ -178,6 +181,7 @@ class Bay(base.APIBase): if not expand: bay.unset_fields_except(['uuid', 'name', 'baymodel_id', 'docker_volume_size', 'labels', + 'master_flavor_id', 'node_count', 'status', 'bay_create_timeout', 'master_count', 'stack_id']) @@ -203,6 +207,7 @@ class Bay(base.APIBase): master_count=1, docker_volume_size=1, labels={}, + master_flavor_id=None, bay_create_timeout=15, stack_id='49dc23f5-ffc9-40c3-9d34-7be7f9e34d63', status=fields.ClusterStatus.CREATE_COMPLETE, @@ -432,6 +437,10 @@ class BaysController(base.Controller): if bay.labels is None: bay.labels = baymodel.labels + # If master_flavor_id is not present, use baymodel value + if bay.master_flavor_id == wtypes.Unset or not bay.master_flavor_id: + bay.master_flavor_id = baymodel.master_flavor_id + bay_dict = bay.as_dict() bay_dict['keypair'] = baymodel.keypair_id attr_validator.validate_os_resources(context, baymodel.as_dict(), diff --git a/magnum/api/controllers/v1/cluster.py b/magnum/api/controllers/v1/cluster.py index 08caaee7fa..f178238614 100755 --- a/magnum/api/controllers/v1/cluster.py +++ b/magnum/api/controllers/v1/cluster.py @@ -113,6 +113,9 @@ class Cluster(base.APIBase): labels = wtypes.DictType(str, str) """One or more key/value pairs""" + master_flavor_id = wtypes.StringType(min_length=1, max_length=255) + """The flavor of the master node for this Cluster""" + create_timeout = wsme.wsattr(wtypes.IntegerType(minimum=0), default=60) """Timeout for creating the cluster in minutes. Default to 60 if not set""" @@ -166,6 +169,7 @@ class Cluster(base.APIBase): cluster.unset_fields_except(['uuid', 'name', 'cluster_template_id', 'keypair', 'docker_volume_size', 'labels', 'node_count', 'status', + 'master_flavor_id', 'create_timeout', 'master_count', 'stack_id']) @@ -192,6 +196,7 @@ class Cluster(base.APIBase): master_count=1, docker_volume_size=1, labels={}, + master_flavor_id='m1.small', create_timeout=15, stack_id='49dc23f5-ffc9-40c3-9d34-7be7f9e34d63', status=fields.ClusterStatus.CREATE_COMPLETE, @@ -411,6 +416,11 @@ class ClustersController(base.Controller): if cluster.labels == wtypes.Unset: cluster.labels = cluster_template.labels + # If master_flavor_id is not present, use cluster_template value + if (cluster.master_flavor_id == wtypes.Unset or + not cluster.master_flavor_id): + cluster.master_flavor_id = cluster_template.master_flavor_id + cluster_dict = cluster.as_dict() attr_validator.validate_os_resources(context, diff --git a/magnum/db/sqlalchemy/alembic/versions/52bcaf58fecb_add_master_flavor_id_to_cluster.py b/magnum/db/sqlalchemy/alembic/versions/52bcaf58fecb_add_master_flavor_id_to_cluster.py new file mode 100644 index 0000000000..fd87195c39 --- /dev/null +++ b/magnum/db/sqlalchemy/alembic/versions/52bcaf58fecb_add_master_flavor_id_to_cluster.py @@ -0,0 +1,30 @@ +# 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 master_flavor_id to cluster + +Revision ID: 52bcaf58fecb +Revises: a0e7c8450ab1 +Create Date: 2017-08-01 11:22:31.277745 + +""" + +# revision identifiers, used by Alembic. +revision = '52bcaf58fecb' +down_revision = 'a0e7c8450ab1' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.add_column('cluster', sa.Column('master_flavor_id', + sa.String(length=255), nullable=True)) diff --git a/magnum/db/sqlalchemy/models.py b/magnum/db/sqlalchemy/models.py index 0b6aada925..986b771b34 100644 --- a/magnum/db/sqlalchemy/models.py +++ b/magnum/db/sqlalchemy/models.py @@ -117,6 +117,7 @@ class Cluster(Base): keypair = Column(String(255)) docker_volume_size = Column(Integer()) labels = Column(JSONEncodedDict) + master_flavor_id = Column(String(255)) stack_id = Column(String(255)) api_address = Column(String(255)) node_addresses = Column(JSONEncodedList) diff --git a/magnum/drivers/heat/k8s_template_def.py b/magnum/drivers/heat/k8s_template_def.py index 64a71908fd..8eaafbf066 100644 --- a/magnum/drivers/heat/k8s_template_def.py +++ b/magnum/drivers/heat/k8s_template_def.py @@ -52,7 +52,7 @@ class K8sTemplateDefinition(template_def.BaseTemplateDefinition): def __init__(self): super(K8sTemplateDefinition, self).__init__() self.add_parameter('master_flavor', - cluster_template_attr='master_flavor_id') + cluster_attr='master_flavor_id') self.add_parameter('minion_flavor', cluster_template_attr='flavor_id') self.add_parameter('number_of_minions', diff --git a/magnum/drivers/heat/swarm_fedora_template_def.py b/magnum/drivers/heat/swarm_fedora_template_def.py index d3128684ff..b5904dafa4 100644 --- a/magnum/drivers/heat/swarm_fedora_template_def.py +++ b/magnum/drivers/heat/swarm_fedora_template_def.py @@ -48,7 +48,7 @@ class SwarmFedoraTemplateDefinition(template_def.BaseTemplateDefinition): self.add_parameter('number_of_nodes', cluster_attr='node_count') self.add_parameter('master_flavor', - cluster_template_attr='master_flavor_id') + cluster_attr='master_flavor_id') self.add_parameter('node_flavor', cluster_template_attr='flavor_id') self.add_parameter('docker_volume_size', diff --git a/magnum/drivers/heat/swarm_mode_template_def.py b/magnum/drivers/heat/swarm_mode_template_def.py index 68ab5ec11f..23d86830b2 100644 --- a/magnum/drivers/heat/swarm_mode_template_def.py +++ b/magnum/drivers/heat/swarm_mode_template_def.py @@ -61,7 +61,7 @@ class SwarmModeTemplateDefinition(template_def.BaseTemplateDefinition): self.add_parameter('number_of_nodes', cluster_attr='node_count') self.add_parameter('master_flavor', - cluster_template_attr='master_flavor_id') + cluster_attr='master_flavor_id') self.add_parameter('node_flavor', cluster_template_attr='flavor_id') self.add_parameter('docker_volume_size', diff --git a/magnum/drivers/mesos_ubuntu_v1/template_def.py b/magnum/drivers/mesos_ubuntu_v1/template_def.py index 04eb360277..318f259fb7 100644 --- a/magnum/drivers/mesos_ubuntu_v1/template_def.py +++ b/magnum/drivers/mesos_ubuntu_v1/template_def.py @@ -31,7 +31,7 @@ class UbuntuMesosTemplateDefinition(template_def.BaseTemplateDefinition): self.add_parameter('number_of_slaves', cluster_attr='node_count') self.add_parameter('master_flavor', - cluster_template_attr='master_flavor_id') + cluster_attr='master_flavor_id') self.add_parameter('slave_flavor', cluster_template_attr='flavor_id') self.add_parameter('cluster_name', diff --git a/magnum/objects/cluster.py b/magnum/objects/cluster.py index 7cb9ee4438..07facc9f46 100644 --- a/magnum/objects/cluster.py +++ b/magnum/objects/cluster.py @@ -44,8 +44,9 @@ class Cluster(base.MagnumPersistentObject, base.MagnumObject, # Version 1.13: Added get_count_all method # Version 1.14: Added 'docker_volume_size' field # Version 1.15: Added 'labels' field + # Version 1.16: Added 'master_flavor_id' field - VERSION = '1.15' + VERSION = '1.16' dbapi = dbapi.get_instance() @@ -59,6 +60,7 @@ class Cluster(base.MagnumPersistentObject, base.MagnumObject, 'keypair': fields.StringField(nullable=True), 'docker_volume_size': fields.IntegerField(nullable=True), 'labels': fields.DictOfStringsField(nullable=True), + 'master_flavor_id': fields.StringField(nullable=True), 'stack_id': fields.StringField(nullable=True), 'status': m_fields.ClusterStatusField(nullable=True), 'status_reason': fields.StringField(nullable=True), diff --git a/magnum/tests/unit/api/controllers/v1/test_cluster.py b/magnum/tests/unit/api/controllers/v1/test_cluster.py index d01c3911c2..bec0b9e42d 100644 --- a/magnum/tests/unit/api/controllers/v1/test_cluster.py +++ b/magnum/tests/unit/api/controllers/v1/test_cluster.py @@ -838,6 +838,24 @@ class TestPost(api_base.FunctionalTest): self.assertEqual(400, response.status_int) self.assertTrue(response.json['errors']) + def test_create_cluster_with_master_flavor_id(self): + bdict = apiutils.cluster_post_data() + bdict['master_flavor_id'] = 'm2.small' + 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('m2.small', cluster[0].master_flavor_id) + + def test_create_cluster_without_master_flavor_id(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 master_flavor_id from ClusterTemplate is used + self.assertEqual('m1.small', cluster[0].master_flavor_id) + class TestDelete(api_base.FunctionalTest): def setUp(self): diff --git a/magnum/tests/unit/conductor/handlers/test_k8s_cluster_conductor.py b/magnum/tests/unit/conductor/handlers/test_k8s_cluster_conductor.py index 395bd05a95..cef6d0c56b 100644 --- a/magnum/tests/unit/conductor/handlers/test_k8s_cluster_conductor.py +++ b/magnum/tests/unit/conductor/handlers/test_k8s_cluster_conductor.py @@ -39,6 +39,7 @@ class TestClusterConductorWithK8s(base.TestCase): 'network_driver': 'network_driver', 'volume_driver': 'volume_driver', 'docker_volume_size': 20, + 'master_flavor_id': 'flavor_id', 'docker_storage_driver': 'devicemapper', 'cluster_distro': 'fedora-atomic', 'coe': 'kubernetes', @@ -94,6 +95,7 @@ class TestClusterConductorWithK8s(base.TestCase): 'grafana_admin_passwd': 'fake_pwd', 'kube_dashboard_enabled': 'True', 'docker_volume_type': 'lvmdriver-1'}, + 'master_flavor_id': 'master_flavor_id', } self.context.user_name = 'fake_user' self.context.tenant = 'fake_tenant' @@ -369,6 +371,7 @@ class TestClusterConductorWithK8s(base.TestCase): 'cluster_uuid': '5d12f6fd-a196-4bf0-ae4c-1f639a523a52', 'discovery_url': 'https://discovery.etcd.io/test', 'docker_volume_size': 20, + 'master_flavor': 'master_flavor_id', 'external_network': 'external_network_id', 'flannel_backend': 'vxlan', 'flannel_network_cidr': '10.101.0.0/16', @@ -611,21 +614,6 @@ class TestClusterConductorWithK8s(base.TestCase): mock_get, missing_attr='docker_storage_driver') - @patch('requests.get') - @patch('magnum.objects.ClusterTemplate.get_by_uuid') - @patch('magnum.drivers.common.driver.Driver.get_driver') - def test_extract_template_definition_without_master_flavor( - self, - mock_driver, - mock_objects_cluster_template_get_by_uuid, - mock_get): - mock_driver.return_value = k8s_dr.Driver() - self._test_extract_template_definition( - mock_driver, - mock_objects_cluster_template_get_by_uuid, - mock_get, - missing_attr='master_flavor_id') - @patch('requests.get') @patch('magnum.objects.ClusterTemplate.get_by_uuid') @patch('magnum.drivers.common.driver.Driver.get_driver') diff --git a/magnum/tests/unit/conductor/handlers/test_mesos_cluster_conductor.py b/magnum/tests/unit/conductor/handlers/test_mesos_cluster_conductor.py index c13af0f556..2ecb1b21d5 100644 --- a/magnum/tests/unit/conductor/handlers/test_mesos_cluster_conductor.py +++ b/magnum/tests/unit/conductor/handlers/test_mesos_cluster_conductor.py @@ -56,6 +56,7 @@ class TestClusterConductorWithMesos(base.TestCase): 'uuid': '5d12f6fd-a196-4bf0-ae4c-1f639a523a52', 'cluster_template_id': 'xx-xx-xx-xx', 'keypair': 'keypair_id', + 'master_flavor_id': 'master_flavor_id', 'name': 'cluster1', 'stack_id': 'xx-xx-xx-xx', 'api_address': '172.17.2.3', @@ -189,7 +190,8 @@ class TestClusterConductorWithMesos(base.TestCase): 'mesos_slave_isolation': 'docker/runtime,filesystem/linux', 'mesos_slave_executor_env_variables': '{}', 'mesos_slave_work_dir': '/tmp/mesos/slave', - 'mesos_slave_image_providers': 'docker' + 'mesos_slave_image_providers': 'docker', + 'master_flavor': 'master_flavor_id', } self.assertEqual(expected, definition) self.assertEqual( diff --git a/magnum/tests/unit/conductor/handlers/test_swarm_cluster_conductor.py b/magnum/tests/unit/conductor/handlers/test_swarm_cluster_conductor.py index db9d5c8220..0b2bcbecfa 100644 --- a/magnum/tests/unit/conductor/handlers/test_swarm_cluster_conductor.py +++ b/magnum/tests/unit/conductor/handlers/test_swarm_cluster_conductor.py @@ -63,6 +63,7 @@ class TestClusterConductorWithSwarm(base.TestCase): 'cluster_template_id': 'xx-xx-xx-xx', 'keypair': 'keypair_id', 'docker_volume_size': 20, + 'master_flavor_id': 'master_flavor_id', 'name': 'cluster1', 'stack_id': 'xx-xx-xx-xx', 'api_address': '172.17.2.3', @@ -304,6 +305,7 @@ class TestClusterConductorWithSwarm(base.TestCase): 'rexray_preempt': 'False', 'docker_volume_type': 'lvmdriver-1', 'docker_volume_size': 20, + 'master_flavor': 'master_flavor_id', } self.assertEqual(expected, definition) self.assertEqual( diff --git a/magnum/tests/unit/db/utils.py b/magnum/tests/unit/db/utils.py index d82156e847..db1e85d843 100644 --- a/magnum/tests/unit/db/utils.py +++ b/magnum/tests/unit/db/utils.py @@ -98,6 +98,7 @@ def get_test_cluster(**kw): 'updated_at': kw.get('updated_at'), 'docker_volume_size': kw.get('docker_volume_size'), 'labels': kw.get('labels'), + 'master_flavor_id': kw.get('master_flavor_id', None), } # Only add Keystone trusts related attributes on demand since they may diff --git a/magnum/tests/unit/objects/test_objects.py b/magnum/tests/unit/objects/test_objects.py index b865bb18d9..0f9c3debd0 100644 --- a/magnum/tests/unit/objects/test_objects.py +++ b/magnum/tests/unit/objects/test_objects.py @@ -355,7 +355,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.15-a8ed124644a6a53be1789f44578863f2', + 'Cluster': '1.16-7a544c5059697c464810470980f81ba1', 'ClusterTemplate': '1.17-f1ce5212b46506360b41ab5cb7658af4', 'Certificate': '1.1-1924dc077daa844f0f9076332ef96815', 'MyObj': '1.0-34c4b1aadefd177b13f9a2f894cc23cd',