Merge "Allow labels on cluster create"

changes/33/501633/1
Jenkins 5 years ago committed by Gerrit Code Review
commit 04b4df0d74
  1. 2
      contrib/drivers/heat/dcos_centos_template_def.py
  2. 1
      doc/source/user/index.rst
  3. 10
      magnum/api/controllers/v1/bay.py
  4. 10
      magnum/api/controllers/v1/cluster.py
  5. 30
      magnum/db/sqlalchemy/alembic/versions/a0e7c8450ab1_add_labels_to_cluster.py
  6. 1
      magnum/db/sqlalchemy/models.py
  7. 2
      magnum/drivers/heat/k8s_fedora_template_def.py
  8. 2
      magnum/drivers/heat/k8s_template_def.py
  9. 4
      magnum/drivers/heat/swarm_fedora_template_def.py
  10. 2
      magnum/drivers/mesos_ubuntu_v1/template_def.py
  11. 4
      magnum/objects/cluster.py
  12. 26
      magnum/tests/unit/api/controllers/v1/test_cluster.py
  13. 26
      magnum/tests/unit/conductor/handlers/test_k8s_cluster_conductor.py
  14. 7
      magnum/tests/unit/conductor/handlers/test_mesos_cluster_conductor.py
  15. 6
      magnum/tests/unit/conductor/handlers/test_swarm_cluster_conductor.py
  16. 1
      magnum/tests/unit/db/utils.py
  17. 72
      magnum/tests/unit/drivers/test_template_definition.py
  18. 1
      magnum/tests/unit/objects/test_cluster.py
  19. 2
      magnum/tests/unit/objects/test_objects.py

@ -119,7 +119,7 @@ class DcosCentosTemplateDefinition(template_def.BaseTemplateDefinition):
'telemetry_enabled']
for label in label_list:
extra_params[label] = cluster_template.labels.get(label)
extra_params[label] = cluster.labels.get(label)
# By default, master_discovery is set to 'static'
# If --master-lb-enabled is specified,

@ -252,6 +252,7 @@ They are loosely grouped as: mandatory, infrastructure, COE specific.
way to pass additional parameters that are specific to a cluster driver.
Refer to the subsection on labels for a list of the supported
key/value pairs and their usage.
The value can be overridden at cluster creation.
--tls-disabled
Transport Layer Security (TLS) is normally enabled to secure the

@ -92,6 +92,9 @@ class Bay(base.APIBase):
docker_volume_size = wtypes.IntegerType(minimum=1)
"""The size in GB of the docker volume"""
labels = wtypes.DictType(str, str)
"""One or more key/value pairs"""
bay_create_timeout = wsme.wsattr(wtypes.IntegerType(minimum=0), default=60)
"""Timeout for creating the bay in minutes. Default to 60 if not set"""
@ -174,7 +177,7 @@ class Bay(base.APIBase):
def _convert_with_links(bay, url, expand=True):
if not expand:
bay.unset_fields_except(['uuid', 'name', 'baymodel_id',
'docker_volume_size',
'docker_volume_size', 'labels',
'node_count', 'status',
'bay_create_timeout', 'master_count',
'stack_id'])
@ -199,6 +202,7 @@ class Bay(base.APIBase):
node_count=2,
master_count=1,
docker_volume_size=1,
labels={},
bay_create_timeout=15,
stack_id='49dc23f5-ffc9-40c3-9d34-7be7f9e34d63',
status=fields.ClusterStatus.CREATE_COMPLETE,
@ -424,6 +428,10 @@ class BaysController(base.Controller):
if bay.docker_volume_size == wtypes.Unset:
bay.docker_volume_size = baymodel.docker_volume_size
# If labels is not present, use baymodel value
if bay.labels is None:
bay.labels = baymodel.labels
bay_dict = bay.as_dict()
bay_dict['keypair'] = baymodel.keypair_id
attr_validator.validate_os_resources(context, baymodel.as_dict(),

@ -110,6 +110,9 @@ class Cluster(base.APIBase):
docker_volume_size = wtypes.IntegerType(minimum=1)
"""The size in GB of the docker volume"""
labels = wtypes.DictType(str, str)
"""One or more key/value pairs"""
create_timeout = wsme.wsattr(wtypes.IntegerType(minimum=0), default=60)
"""Timeout for creating the cluster in minutes. Default to 60 if not set"""
@ -162,7 +165,7 @@ class Cluster(base.APIBase):
if not expand:
cluster.unset_fields_except(['uuid', 'name', 'cluster_template_id',
'keypair', 'docker_volume_size',
'node_count', 'status',
'labels', 'node_count', 'status',
'create_timeout', 'master_count',
'stack_id'])
@ -188,6 +191,7 @@ class Cluster(base.APIBase):
node_count=2,
master_count=1,
docker_volume_size=1,
labels={},
create_timeout=15,
stack_id='49dc23f5-ffc9-40c3-9d34-7be7f9e34d63',
status=fields.ClusterStatus.CREATE_COMPLETE,
@ -403,6 +407,10 @@ class ClustersController(base.Controller):
if cluster.docker_volume_size == wtypes.Unset:
cluster.docker_volume_size = cluster_template.docker_volume_size
# If labels is not present, use cluster_template value
if cluster.labels == wtypes.Unset:
cluster.labels = cluster_template.labels
cluster_dict = cluster.as_dict()
attr_validator.validate_os_resources(context,

@ -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 labels to cluster
Revision ID: a0e7c8450ab1
Revises: bc46ba6cf949
Create Date: 2017-06-12 10:08:05.501441
"""
# revision identifiers, used by Alembic.
revision = 'a0e7c8450ab1'
down_revision = 'aa0cc27839af'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column('cluster', sa.Column('labels',
sa.Text(), nullable=True))

@ -116,6 +116,7 @@ class Cluster(Base):
cluster_template_id = Column(String(255))
keypair = Column(String(255))
docker_volume_size = Column(Integer())
labels = Column(JSONEncodedDict)
stack_id = Column(String(255))
api_address = Column(String(255))
node_addresses = Column(JSONEncodedList)

@ -74,7 +74,7 @@ class K8sFedoraTemplateDefinition(k8s_template_def.K8sTemplateDefinition):
# set docker_volume_type
# use the configuration default if None provided
docker_volume_type = cluster_template.labels.get(
docker_volume_type = cluster.labels.get(
'docker_volume_type', CONF.cinder.default_docker_volume_type)
extra_params['docker_volume_type'] = docker_volume_type

@ -116,7 +116,7 @@ class K8sTemplateDefinition(template_def.BaseTemplateDefinition):
'etcd_volume_size']
for label in label_list:
extra_params[label] = cluster_template.labels.get(label)
extra_params[label] = cluster.labels.get(label)
if cluster_template.registry_enabled:
extra_params['swift_region'] = CONF.docker_registry.swift_region

@ -105,12 +105,12 @@ class SwarmFedoraTemplateDefinition(template_def.BaseTemplateDefinition):
# set docker_volume_type
# use the configuration default if None provided
docker_volume_type = cluster_template.labels.get(
docker_volume_type = cluster.labels.get(
'docker_volume_type', CONF.cinder.default_docker_volume_type)
extra_params['docker_volume_type'] = docker_volume_type
for label in label_list:
extra_params[label] = cluster_template.labels.get(label)
extra_params[label] = cluster.labels.get(label)
if cluster_template.registry_enabled:
extra_params['swift_region'] = CONF.docker_registry.swift_region

@ -68,7 +68,7 @@ class UbuntuMesosTemplateDefinition(template_def.BaseTemplateDefinition):
'mesos_slave_executor_env_variables']
for label in label_list:
extra_params[label] = cluster_template.labels.get(label)
extra_params[label] = cluster.labels.get(label)
scale_mgr = kwargs.pop('scale_manager', None)
if scale_mgr:

@ -43,8 +43,9 @@ class Cluster(base.MagnumPersistentObject, base.MagnumObject,
# Version 1.12: Added 'get_stats' method
# Version 1.13: Added get_count_all method
# Version 1.14: Added 'docker_volume_size' field
# Version 1.15: Added 'labels' field
VERSION = '1.14'
VERSION = '1.15'
dbapi = dbapi.get_instance()
@ -57,6 +58,7 @@ class Cluster(base.MagnumPersistentObject, base.MagnumObject,
'cluster_template_id': fields.StringField(nullable=True),
'keypair': fields.StringField(nullable=True),
'docker_volume_size': fields.IntegerField(nullable=True),
'labels': fields.DictOfStringsField(nullable=True),
'stack_id': fields.StringField(nullable=True),
'status': m_fields.ClusterStatusField(nullable=True),
'status_reason': fields.StringField(nullable=True),

@ -790,6 +790,15 @@ class TestPost(api_base.FunctionalTest):
cluster, timeout = self.mock_cluster_create.call_args
self.assertEqual(3, cluster[0].docker_volume_size)
def test_create_cluster_with_labels(self):
bdict = apiutils.cluster_post_data()
bdict['labels'] = {'key': 'value'}
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({'key': 'value'}, cluster[0].labels)
def test_create_cluster_without_docker_volume_size(self):
bdict = apiutils.cluster_post_data()
# Remove the default docker_volume_size from the cluster dict.
@ -801,6 +810,16 @@ class TestPost(api_base.FunctionalTest):
# Verify docker_volume_size from ClusterTemplate is used
self.assertEqual(20, cluster[0].docker_volume_size)
def test_create_cluster_without_labels(self):
bdict = apiutils.cluster_post_data()
bdict.pop('labels')
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 labels from ClusterTemplate is used
self.assertEqual({'key1': u'val1', 'key2': u'val2'}, cluster[0].labels)
def test_create_cluster_with_invalid_docker_volume_size(self):
invalid_values = [(-1, None), ('notanint', None),
(1, 'devicemapper'), (2, 'devicemapper')]
@ -812,6 +831,13 @@ class TestPost(api_base.FunctionalTest):
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
def test_create_cluster_with_invalid_labels(self):
bdict = apiutils.cluster_post_data(labels='invalid')
response = self.post_json('/clusters', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
class TestDelete(api_base.FunctionalTest):
def setUp(self):

@ -56,7 +56,7 @@ class TestClusterConductorWithK8s(base.TestCase):
'grafana_admin_passwd': 'fake_pwd',
'kube_dashboard_enabled': 'True',
'docker_volume_type': 'lvmdriver-1',
'etcd_volume_size': '0'},
'etcd_volume_size': 0},
'tls_disabled': False,
'server_type': 'vm',
'registry_enabled': False,
@ -84,6 +84,16 @@ class TestClusterConductorWithK8s(base.TestCase):
'trustee_user_id': '7b489f04-b458-4541-8179-6a48a553e656',
'trust_id': 'bd11efc5-d4e2-4dac-bbce-25e348ddf7de',
'coe_version': 'fake-version',
'labels': {'flannel_network_cidr': '10.101.0.0/16',
'flannel_network_subnetlen': '26',
'flannel_backend': 'vxlan',
'system_pods_initial_delay': '15',
'system_pods_timeout': '1',
'admission_control_list': 'fake_list',
'prometheus_monitoring': 'False',
'grafana_admin_passwd': 'fake_pwd',
'kube_dashboard_enabled': 'True',
'docker_volume_type': 'lvmdriver-1'},
}
self.context.user_name = 'fake_user'
self.context.tenant = 'fake_tenant'
@ -160,7 +170,7 @@ class TestClusterConductorWithK8s(base.TestCase):
'grafana_admin_passwd': 'fake_pwd',
'kube_dashboard_enabled': 'True',
'docker_volume_type': 'lvmdriver-1',
'etcd_volume_size': '0'},
'etcd_volume_size': None},
'http_proxy': 'http_proxy',
'https_proxy': 'https_proxy',
'no_proxy': 'no_proxy',
@ -186,7 +196,7 @@ class TestClusterConductorWithK8s(base.TestCase):
'docker_volume_type': 'lvmdriver-1',
'docker_storage_driver': 'devicemapper',
'discovery_url': 'https://discovery.etcd.io/test',
'etcd_volume_size': '0',
'etcd_volume_size': None,
'flannel_network_cidr': '10.101.0.0/16',
'flannel_network_subnetlen': '26',
'flannel_backend': 'vxlan',
@ -269,7 +279,7 @@ class TestClusterConductorWithK8s(base.TestCase):
'docker_storage_driver': 'devicemapper',
'docker_volume_size': 20,
'docker_volume_type': 'lvmdriver-1',
'etcd_volume_size': '0',
'etcd_volume_size': None,
'external_network': 'external_network_id',
'fixed_network': 'fixed_network',
'fixed_subnet': 'fixed_subnet',
@ -370,7 +380,7 @@ class TestClusterConductorWithK8s(base.TestCase):
'grafana_admin_passwd': 'fake_pwd',
'kube_dashboard_enabled': 'True',
'docker_volume_type': 'lvmdriver-1',
'etcd_volume_size': '0',
'etcd_volume_size': None,
'insecure_registry_url': '10.0.0.1:5000',
'kube_version': 'fake-version',
'magnum_url': 'http://127.0.0.1:9511/v1',
@ -437,7 +447,7 @@ class TestClusterConductorWithK8s(base.TestCase):
'network_driver': 'network_driver',
'volume_driver': 'volume_driver',
'discovery_url': 'https://discovery.etcd.io/test',
'etcd_volume_size': '0',
'etcd_volume_size': None,
'http_proxy': 'http_proxy',
'https_proxy': 'https_proxy',
'no_proxy': 'no_proxy',
@ -508,7 +518,7 @@ class TestClusterConductorWithK8s(base.TestCase):
'network_driver': 'network_driver',
'volume_driver': 'volume_driver',
'discovery_url': 'http://tokentest/h1/h2/h3',
'etcd_volume_size': '0',
'etcd_volume_size': None,
'http_proxy': 'http_proxy',
'https_proxy': 'https_proxy',
'no_proxy': 'no_proxy',
@ -706,7 +716,7 @@ class TestClusterConductorWithK8s(base.TestCase):
'docker_volume_type': 'lvmdriver-1',
'docker_storage_driver': 'devicemapper',
'discovery_url': 'https://address/token',
'etcd_volume_size': '0',
'etcd_volume_size': None,
'http_proxy': 'http_proxy',
'https_proxy': 'https_proxy',
'no_proxy': 'no_proxy',

@ -66,6 +66,13 @@ class TestClusterConductorWithMesos(base.TestCase):
'trustee_password': 'fake_trustee_password',
'trustee_user_id': '7b489f04-b458-4541-8179-6a48a553e656',
'trust_id': 'bd11efc5-d4e2-4dac-bbce-25e348ddf7de',
'labels': {'rexray_preempt': 'False',
'mesos_slave_isolation':
'docker/runtime,filesystem/linux',
'mesos_slave_image_providers': 'docker',
'mesos_slave_executor_env_variables': '{}',
'mesos_slave_work_dir': '/tmp/mesos/slave'
},
}
self.context.user_name = 'mesos_user'
self.context.tenant = 'admin'

@ -74,6 +74,12 @@ class TestClusterConductorWithSwarm(base.TestCase):
'trustee_password': 'fake_trustee_password',
'trustee_user_id': '7b489f04-b458-4541-8179-6a48a553e656',
'trust_id': 'bd11efc5-d4e2-4dac-bbce-25e348ddf7de',
'labels': {'docker_volume_type': 'lvmdriver-1',
'flannel_network_cidr': '10.101.0.0/16',
'flannel_network_subnetlen': '26',
'flannel_backend': 'vxlan',
'rexray_preempt': 'False',
'swarm_strategy': 'spread'},
'coe_version': 'fake-version'
}

@ -97,6 +97,7 @@ def get_test_cluster(**kw):
'created_at': kw.get('created_at'),
'updated_at': kw.get('updated_at'),
'docker_volume_size': kw.get('docker_volume_size'),
'labels': kw.get('labels'),
}
# Only add Keystone trusts related attributes on demand since they may

@ -249,25 +249,25 @@ class AtomicK8sTemplateDefinitionTestCase(BaseTemplateDefinitionTestCase):
mock_context.auth_url = 'http://192.168.10.10:5000/v3'
mock_context.user_name = 'fake_user'
flannel_cidr = mock_cluster_template.labels.get('flannel_network_cidr')
flannel_subnet = mock_cluster_template.labels.get(
flannel_cidr = mock_cluster.labels.get('flannel_network_cidr')
flannel_subnet = mock_cluster.labels.get(
'flannel_network_subnetlen')
flannel_backend = mock_cluster_template.labels.get('flannel_backend')
system_pods_initial_delay = mock_cluster_template.labels.get(
flannel_backend = mock_cluster.labels.get('flannel_backend')
system_pods_initial_delay = mock_cluster.labels.get(
'system_pods_initial_delay')
system_pods_timeout = mock_cluster_template.labels.get(
system_pods_timeout = mock_cluster.labels.get(
'system_pods_timeout')
admission_control_list = mock_cluster_template.labels.get(
admission_control_list = mock_cluster.labels.get(
'admission_control_list')
prometheus_monitoring = mock_cluster_template.labels.get(
prometheus_monitoring = mock_cluster.labels.get(
'prometheus_monitoring')
grafana_admin_passwd = mock_cluster_template.labels.get(
grafana_admin_passwd = mock_cluster.labels.get(
'grafana_admin_passwd')
kube_dashboard_enabled = mock_cluster_template.labels.get(
kube_dashboard_enabled = mock_cluster.labels.get(
'kube_dashboard_enabled')
docker_volume_type = mock_cluster_template.labels.get(
docker_volume_type = mock_cluster.labels.get(
'docker_volume_type')
etcd_volume_size = mock_cluster_template.labels.get(
etcd_volume_size = mock_cluster.labels.get(
'etcd_volume_size')
kube_tag = mock_cluster_template.labels.get('kube_tag')
@ -329,25 +329,25 @@ class AtomicK8sTemplateDefinitionTestCase(BaseTemplateDefinitionTestCase):
mock_context.auth_url = 'http://192.168.10.10:5000/v3'
mock_context.user_name = 'fake_user'
flannel_cidr = mock_cluster_template.labels.get('flannel_network_cidr')
flannel_subnet = mock_cluster_template.labels.get(
flannel_cidr = mock_cluster.labels.get('flannel_network_cidr')
flannel_subnet = mock_cluster.labels.get(
'flannel_network_subnetlen')
flannel_backend = mock_cluster_template.labels.get('flannel_backend')
system_pods_initial_delay = mock_cluster_template.labels.get(
flannel_backend = mock_cluster.labels.get('flannel_backend')
system_pods_initial_delay = mock_cluster.labels.get(
'system_pods_initial_delay')
system_pods_timeout = mock_cluster_template.labels.get(
system_pods_timeout = mock_cluster.labels.get(
'system_pods_timeout')
admission_control_list = mock_cluster_template.labels.get(
admission_control_list = mock_cluster.labels.get(
'admission_control_list')
prometheus_monitoring = mock_cluster_template.labels.get(
prometheus_monitoring = mock_cluster.labels.get(
'prometheus_monitoring')
grafana_admin_passwd = mock_cluster_template.labels.get(
grafana_admin_passwd = mock_cluster.labels.get(
'grafana_admin_passwd')
kube_dashboard_enabled = mock_cluster_template.labels.get(
kube_dashboard_enabled = mock_cluster.labels.get(
'kube_dashboard_enabled')
docker_volume_type = mock_cluster_template.labels.get(
docker_volume_type = mock_cluster.labels.get(
'docker_volume_type')
etcd_volume_size = mock_cluster_template.labels.get(
etcd_volume_size = mock_cluster.labels.get(
'etcd_volume_size')
kube_tag = mock_cluster_template.labels.get('kube_tag')
@ -761,14 +761,14 @@ class AtomicSwarmTemplateDefinitionTestCase(base.TestCase):
mock_context.user_name = 'fake_user'
mock_context.tenant = 'fake_tenant'
docker_volume_type = mock_cluster_template.labels.get(
docker_volume_type = mock_cluster.labels.get(
'docker_volume_type')
flannel_cidr = mock_cluster_template.labels.get('flannel_network_cidr')
flannel_subnet = mock_cluster_template.labels.get(
flannel_cidr = mock_cluster.labels.get('flannel_network_cidr')
flannel_subnet = mock_cluster.labels.get(
'flannel_network_subnetlen')
flannel_backend = mock_cluster_template.labels.get('flannel_backend')
rexray_preempt = mock_cluster_template.labels.get('rexray_preempt')
swarm_strategy = mock_cluster_template.labels.get('swarm_strategy')
flannel_backend = mock_cluster.labels.get('flannel_backend')
rexray_preempt = mock_cluster.labels.get('rexray_preempt')
swarm_strategy = mock_cluster.labels.get('swarm_strategy')
swarm_def = swarm_tdef.AtomicSwarmTemplateDefinition()
@ -923,18 +923,18 @@ class UbuntuMesosTemplateDefinitionTestCase(base.TestCase):
mock_context.domain_name = 'domainname'
mock_cluster_template = mock.MagicMock()
mock_cluster_template.tls_disabled = False
rexray_preempt = mock_cluster_template.labels.get('rexray_preempt')
mesos_slave_isolation = mock_cluster_template.labels.get(
mock_cluster = mock.MagicMock()
mock_cluster.uuid = '5d12f6fd-a196-4bf0-ae4c-1f639a523a52'
del mock_cluster.stack_id
rexray_preempt = mock_cluster.labels.get('rexray_preempt')
mesos_slave_isolation = mock_cluster.labels.get(
'mesos_slave_isolation')
mesos_slave_work_dir = mock_cluster_template.labels.get(
mesos_slave_work_dir = mock_cluster.labels.get(
'mesos_slave_work_dir')
mesos_slave_image_providers = mock_cluster_template.labels.get(
mesos_slave_image_providers = mock_cluster.labels.get(
'image_providers')
mesos_slave_executor_env_variables = mock_cluster_template.labels.get(
mesos_slave_executor_env_variables = mock_cluster.labels.get(
'mesos_slave_executor_env_variables')
mock_cluster = mock.MagicMock()
mock_cluster.uuid = '5d12f6fd-a196-4bf0-ae4c-1f639a523a52'
del mock_cluster.stack_id
mock_osc = mock.MagicMock()
mock_osc.cinder_region_name.return_value = 'RegionOne'
mock_osc_class.return_value = mock_osc

@ -39,6 +39,7 @@ class TestClusterObject(base.DbTestCase):
uuid=cluster_template_id)
self.fake_cluster['keypair'] = 'keypair1'
self.fake_cluster['docker_volume_size'] = 3
self.fake_cluster['labels'] = {}
@mock.patch('magnum.objects.ClusterTemplate.get_by_uuid')
def test_get_by_id(self, mock_cluster_template_get):

@ -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.14-281c582b16291c4f0666371e53975a5c',
'Cluster': '1.15-a8ed124644a6a53be1789f44578863f2',
'ClusterTemplate': '1.17-f1ce5212b46506360b41ab5cb7658af4',
'Certificate': '1.1-1924dc077daa844f0f9076332ef96815',
'MyObj': '1.0-34c4b1aadefd177b13f9a2f894cc23cd',

Loading…
Cancel
Save