From b66c6cc847bc0bfd9ffdbc25bfb74a0bf568d6e3 Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Tue, 24 Jan 2023 18:34:46 +0100 Subject: [PATCH] Add magnum cluster templates resource Implement support for magnum clustertemplate. - drop support for bays. Those were never tested properly in SDK and are deprecated since Newton. Change-Id: I8a7198231fd60abf5ac2dd44985961c8c47db657 --- openstack/cloud/_coe.py | 114 ++++----------- .../v1/_proxy.py | 100 +++++++++++++ .../v1/cluster.py | 9 +- .../v1/cluster_template.py | 116 +++++++++++++++ .../cloud/test_cluster_templates.py | 2 +- .../unit/cloud/test_cluster_templates.py | 135 +++++++----------- .../v1/test_cluster.py | 2 +- .../v1/test_cluster_template.py | 98 +++++++++++++ .../v1/test_proxy.py | 48 ++++++- 9 files changed, 442 insertions(+), 182 deletions(-) create mode 100644 openstack/container_infrastructure_management/v1/cluster_template.py create mode 100644 openstack/tests/unit/container_infrastructure_management/v1/test_cluster_template.py diff --git a/openstack/cloud/_coe.py b/openstack/cloud/_coe.py index cbd547cd5..401564ecc 100644 --- a/openstack/cloud/_coe.py +++ b/openstack/cloud/_coe.py @@ -201,22 +201,8 @@ class CoeCloudMixin: :raises: ``OpenStackCloudException``: if something goes wrong during the OpenStack API call. """ - with _utils.shade_exceptions("Error fetching cluster template list"): - try: - data = self._container_infra_client.get('/clustertemplates') - # NOTE(flwang): Magnum adds /clustertemplates and /cluster - # to deprecate /baymodels and /bay since Newton release. So - # we're using a small tag to indicate if current - # cloud has those two new API endpoints. - self._container_infra_client._has_magnum_after_newton = True - return self._normalize_cluster_templates( - self._get_and_munchify('clustertemplates', data)) - except exc.OpenStackCloudURINotFound: - data = self._container_infra_client.get('/baymodels/detail') - return self._normalize_cluster_templates( - self._get_and_munchify('baymodels', data)) - list_baymodels = list_cluster_templates - list_coe_cluster_templates = list_cluster_templates + return list( + self.container_infrastructure_management.cluster_templates()) def search_cluster_templates( self, name_or_id=None, filters=None, detail=False): @@ -235,8 +221,6 @@ class CoeCloudMixin: cluster_templates = self.list_cluster_templates(detail=detail) return _utils._filter_list( cluster_templates, name_or_id, filters) - search_baymodels = search_cluster_templates - search_coe_cluster_templates = search_cluster_templates def get_cluster_template(self, name_or_id, filters=None, detail=False): """Get a cluster template by name or ID. @@ -260,10 +244,9 @@ class CoeCloudMixin: :returns: A cluster template dict or None if no matching cluster template is found. """ - return _utils._get_entity(self, 'cluster_template', name_or_id, - filters=filters, detail=detail) - get_baymodel = get_cluster_template - get_coe_cluster_template = get_cluster_template + return _utils._get_entity( + self, 'cluster_template', name_or_id, + filters=filters, detail=detail) def create_cluster_template( self, name, image_id=None, keypair_id=None, coe=None, **kwargs): @@ -280,28 +263,16 @@ class CoeCloudMixin: :raises: ``OpenStackCloudException`` if something goes wrong during the OpenStack API call """ - error_message = ("Error creating cluster template of name" - " {cluster_template_name}".format( - cluster_template_name=name)) - with _utils.shade_exceptions(error_message): - body = kwargs.copy() - body['name'] = name - body['image_id'] = image_id - body['keypair_id'] = keypair_id - body['coe'] = coe + cluster_template = self.container_infrastructure_management \ + .create_cluster_template( + name=name, + image_id=image_id, + keypair_id=keypair_id, + coe=coe, + **kwargs, + ) - try: - cluster_template = self._container_infra_client.post( - '/clustertemplates', json=body) - self._container_infra_client._has_magnum_after_newton = True - except exc.OpenStackCloudURINotFound: - cluster_template = self._container_infra_client.post( - '/baymodels', json=body) - - self.list_cluster_templates.invalidate(self) - return self._normalize_cluster_template(cluster_template) - create_baymodel = create_cluster_template - create_coe_cluster_template = create_cluster_template + return cluster_template def delete_cluster_template(self, name_or_id): """Delete a cluster template. @@ -322,68 +293,31 @@ class CoeCloudMixin: exc_info=True) return False - with _utils.shade_exceptions("Error in deleting cluster template"): - if getattr(self._container_infra_client, - '_has_magnum_after_newton', False): - self._container_infra_client.delete( - '/clustertemplates/{id}'.format(id=cluster_template['id'])) - else: - self._container_infra_client.delete( - '/baymodels/{id}'.format(id=cluster_template['id'])) - self.list_cluster_templates.invalidate(self) - + self.container_infrastructure_management.delete_cluster_template( + cluster_template) return True - delete_baymodel = delete_cluster_template - delete_coe_cluster_template = delete_cluster_template - @_utils.valid_kwargs('name', 'image_id', 'flavor_id', 'master_flavor_id', - 'keypair_id', 'external_network_id', 'fixed_network', - 'dns_nameserver', 'docker_volume_size', 'labels', - 'coe', 'http_proxy', 'https_proxy', 'no_proxy', - 'network_driver', 'tls_disabled', 'public', - 'registry_enabled', 'volume_driver') - def update_cluster_template(self, name_or_id, operation, **kwargs): + def update_cluster_template(self, name_or_id, **kwargs): """Update a cluster template. :param name_or_id: Name or ID of the cluster template being updated. - :param operation: Operation to perform - add, remove, replace. - Other arguments will be passed with kwargs. - :returns: a dict representing the updated cluster template. + :returns: an update cluster template. :raises: OpenStackCloudException on operation error. """ - self.list_cluster_templates.invalidate(self) cluster_template = self.get_cluster_template(name_or_id) if not cluster_template: raise exc.OpenStackCloudException( "Cluster template %s not found." % name_or_id) - if operation not in ['add', 'replace', 'remove']: - raise TypeError( - "%s operation not in 'add', 'replace', 'remove'" % operation) + cluster_template = self.container_infrastructure_management \ + .update_cluster_template( + cluster_template, + **kwargs + ) - patches = _utils.generate_patches_from_kwargs(operation, **kwargs) - # No need to fire an API call if there is an empty patch - if not patches: - return cluster_template - - with _utils.shade_exceptions( - "Error updating cluster template {0}".format(name_or_id)): - if getattr(self._container_infra_client, - '_has_magnum_after_newton', False): - self._container_infra_client.patch( - '/clustertemplates/{id}'.format(id=cluster_template['id']), - json=patches) - else: - self._container_infra_client.patch( - '/baymodels/{id}'.format(id=cluster_template['id']), - json=patches) - - new_cluster_template = self.get_cluster_template(name_or_id) - return new_cluster_template - update_baymodel = update_cluster_template - update_coe_cluster_template = update_cluster_template + return cluster_template def list_magnum_services(self): """List all Magnum services. diff --git a/openstack/container_infrastructure_management/v1/_proxy.py b/openstack/container_infrastructure_management/v1/_proxy.py index 7d3d2783b..ca40a8227 100644 --- a/openstack/container_infrastructure_management/v1/_proxy.py +++ b/openstack/container_infrastructure_management/v1/_proxy.py @@ -13,6 +13,9 @@ from openstack.container_infrastructure_management.v1 import ( cluster as _cluster ) +from openstack.container_infrastructure_management.v1 import ( + cluster_template as _cluster_template +) from openstack import proxy @@ -20,6 +23,7 @@ class Proxy(proxy.Proxy): _resource_registry = { "cluster": _cluster.Cluster, + "cluster_template": _cluster_template.ClusterTemplate, } def create_cluster(self, **attrs): @@ -107,3 +111,99 @@ class Proxy(proxy.Proxy): :class:`~openstack.container_infrastructure_management.v1.cluster.Cluster` """ return self._update(_cluster.Cluster, cluster, **attrs) + + # ============== Cluster Templates ============== + def create_cluster_template(self, **attrs): + """Create a new cluster_template from attributes + + :param dict attrs: Keyword arguments which will be used to create a + :class:`~openstack.container_infrastructure_management.v1.cluster_template.ClusterTemplate`, + comprised of the properties on the ClusterTemplate class. + :returns: The results of cluster_template creation + :rtype: + :class:`~openstack.container_infrastructure_management.v1.cluster_template.ClusterTemplate` + """ + return self._create(_cluster_template.ClusterTemplate, **attrs) + + def delete_cluster_template(self, cluster_template, ignore_missing=True): + """Delete a cluster_template + + :param cluster_template: The value can be either the ID of a + cluster_template or a + :class:`~openstack.container_infrastructure_management.v1.cluster_template.ClusterTemplate` + instance. + :param bool ignore_missing: When set to ``False`` + :class:`~openstack.exceptions.ResourceNotFound` will be raised when + the cluster_template does not exist. When set to ``True``, no + exception will be set when attempting to delete a nonexistent + cluster_template. + :returns: ``None`` + """ + self._delete( + _cluster_template.ClusterTemplate, + cluster_template, + ignore_missing=ignore_missing, + ) + + def find_cluster_template(self, name_or_id, ignore_missing=True): + """Find a single cluster_template + + :param name_or_id: The name or ID of a cluster_template. + :param bool ignore_missing: When set to ``False`` + :class:`~openstack.exceptions.ResourceNotFound` will be + raised when the resource does not exist. + When set to ``True``, None will be returned when + attempting to find a nonexistent resource. + :returns: One + :class:`~openstack.container_infrastructure_management.v1.cluster_template.ClusterTemplate` + or None + """ + return self._find( + _cluster_template.ClusterTemplate, + name_or_id, + ignore_missing=ignore_missing, + ) + + def get_cluster_template(self, cluster_template): + """Get a single cluster_template + + :param cluster_template: The value can be the ID of a cluster_template + or a + :class:`~openstack.container_infrastructure_management.v1.cluster_template.ClusterTemplate` + instance. + + :returns: One + :class:`~openstack.container_infrastructure_management.v1.cluster_template.ClusterTemplate` + :raises: :class:`~openstack.exceptions.ResourceNotFound` + when no resource can be found. + """ + return self._get(_cluster_template.ClusterTemplate, cluster_template) + + def cluster_templates(self, **query): + """Return a generator of cluster_templates + + :param kwargs query: Optional query parameters to be sent to limit + the resources being returned. + + :returns: A generator of cluster_template objects + :rtype: + :class:`~openstack.container_infrastructure_management.v1.cluster_template.ClusterTemplate` + """ + return self._list(_cluster_template.ClusterTemplate, **query) + + def update_cluster_template(self, cluster_template, **attrs): + """Update a cluster_template + + :param cluster_template: Either the id of a cluster_template or a + :class:`~openstack.container_infrastructure_management.v1.cluster_template.ClusterTemplate` + instance. + :param attrs: The attributes to update on the cluster_template + represented by ``cluster_template``. + + :returns: The updated cluster_template + :rtype: + :class:`~openstack.container_infrastructure_management.v1.cluster_template.ClusterTemplate` + """ + return self._update( + _cluster_template.ClusterTemplate, cluster_template, **attrs + ) diff --git a/openstack/container_infrastructure_management/v1/cluster.py b/openstack/container_infrastructure_management/v1/cluster.py index 684f096d0..bd7618555 100644 --- a/openstack/container_infrastructure_management/v1/cluster.py +++ b/openstack/container_infrastructure_management/v1/cluster.py @@ -88,11 +88,10 @@ class Cluster(resource.Resource): #: the bay/cluster. The login name is specific to the bay/cluster driver. #: For example, with fedora-atomic image the default login name is fedora. keypair = resource.Body('keypair') - #: Arbitrary labels in the form of key=value pairs. The accepted keys and - #: valid values are defined in the bay/cluster drivers. They are used as a - #: way to pass additional parameters that are specific to a bay/cluster - #: driver. - labels = resource.Body('labels', type=list) + #: Arbitrary labels. The accepted keys and valid values are defined in the + #: bay/cluster drivers. They are used as a way to pass additional + #: parameters that are specific to a bay/cluster driver. + labels = resource.Body('labels', type=dict) #: A list of floating IPs of all master nodes. master_addresses = resource.Body('master_addresses', type=list) #: The number of servers that will serve as master for the bay/cluster. Set diff --git a/openstack/container_infrastructure_management/v1/cluster_template.py b/openstack/container_infrastructure_management/v1/cluster_template.py new file mode 100644 index 000000000..31acf376f --- /dev/null +++ b/openstack/container_infrastructure_management/v1/cluster_template.py @@ -0,0 +1,116 @@ +# 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. + +from openstack import resource + + +class ClusterTemplate(resource.Resource): + + resources_key = 'clustertemplates' + base_path = '/clustertemplates' + + # capabilities + allow_create = True + allow_fetch = True + allow_commit = True + allow_delete = True + allow_list = True + allow_patch = True + + commit_method = 'PATCH' + commit_jsonpatch = True + + #: The exposed port of COE API server. + apiserver_port = resource.Body('apiserver_port', type=int) + #: Display the attribute os_distro defined as appropriate metadata in image + #: for the bay/cluster driver. + cluster_distro = resource.Body('cluster_distro') + #: Specify the Container Orchestration Engine to use. Supported COEs + #: include kubernetes, swarm, mesos. + coe = resource.Body('coe') + #: The date and time when the resource was created. + created_at = resource.Body('created_at') + #: The name of a driver to manage the storage for the images and the + #: container’s writable layer. + docker_storage_driver = resource.Body('docker_storage_driver') + #: The size in GB for the local storage on each server for the Docker + #: daemon to cache the images and host the containers. + docker_volume_size = resource.Body('docker_volume_size', type=int) + #: The DNS nameserver for the servers and containers in the bay/cluster to + #: use. + dns_nameserver = resource.Body('dns_nameserver') + #: The name or network ID of a Neutron network to provide connectivity to + #: the external internet for the bay/cluster. + external_network_id = resource.Body('external_network_id') + #: The name or network ID of a Neutron network to provide connectivity to + #: the internal network for the bay/cluster. + fixed_network = resource.Body('fixed_network') + #: Fixed subnet that are using to allocate network address for nodes in + #: bay/cluster. + fixed_subnet = resource.Body('fixed_subnet') + #: The nova flavor ID or name for booting the node servers. + flavor_id = resource.Body('flavor_id') + #: The IP address for a proxy to use when direct http access + #: from the servers to sites on the external internet is blocked. + #: This may happen in certain countries or enterprises, and the + #: proxy allows the servers and containers to access these sites. + #: The format is a URL including a port number. The default is + #: None. + http_proxy = resource.Body('http_proxy') + #: The IP address for a proxy to use when direct https access from the + #: servers to sites on the external internet is blocked. + https_proxy = resource.Body('https_proxy') + #: The name or UUID of the base image in Glance to boot the servers for the + #: bay/cluster. + image_id = resource.Body('image_id') + #: The URL pointing to users’s own private insecure docker + #: registry to deploy and run docker containers. + insecure_registry = resource.Body('insecure_registry') + #: Whether enable or not using the floating IP of cloud provider. + is_floating_ip_enabled = resource.Body('floating_ip_enabled') + #: Indicates whether the ClusterTemplate is hidden or not. + is_hidden = resource.Body('hidden', type=bool) + #: this option can be set to false to create a bay/cluster without the load + #: balancer. + is_master_lb_enabled = resource.Body('master_lb_enabled', type=bool) + #: Specifying this parameter will disable TLS so that users can access the + #: COE endpoints without a certificate. + is_tls_disabled = resource.Body('tls_disabled', type=bool) + #: Setting this flag makes the baymodel/cluster template public and + #: accessible by other users. + is_public = resource.Body('public', type=bool) + #: This option provides an alternative registry based on the Registry V2 + is_registry_enabled = resource.Body('registry_enabled', type=bool) + #: The name of the SSH keypair to configure in the bay/cluster servers for + #: ssh access. + keypair_id = resource.Body('keypair_id') + #: Arbitrary labels. The accepted keys and valid values are defined in the + #: bay/cluster drivers. They are used as a way to pass additional + #: parameters that are specific to a bay/cluster driver. + labels = resource.Body('labels', type=dict) + #: The flavor of the master node for this baymodel/cluster template. + master_flavor_id = resource.Body('master_flavor_id') + #: The name of a network driver for providing the networks for the + #: containers. + network_driver = resource.Body('network_driver') + #: When a proxy server is used, some sites should not go through the proxy + #: and should be accessed normally. + no_proxy = resource.Body('no_proxy') + #: The servers in the bay/cluster can be vm or baremetal. + server_type = resource.Body('server_type') + #: The date and time when the resource was updated. + updated_at = resource.Body('updated_at') + #: The UUID of the cluster template. + uuid = resource.Body('uuid', alternate_id=True) + #: The name of a volume driver for managing the persistent storage for the + #: containers. + volume_driver = resource.Body('volume_driver') diff --git a/openstack/tests/functional/cloud/test_cluster_templates.py b/openstack/tests/functional/cloud/test_cluster_templates.py index 2d2c2a479..afc304bba 100644 --- a/openstack/tests/functional/cloud/test_cluster_templates.py +++ b/openstack/tests/functional/cloud/test_cluster_templates.py @@ -90,7 +90,7 @@ class TestClusterTemplate(base.BaseFunctionalTest): # Test we can update a field on the cluster_template and only that # field is updated cluster_template_update = self.user_cloud.update_cluster_template( - self.ct['uuid'], 'replace', tls_disabled=True) + self.ct, tls_disabled=True) self.assertEqual( cluster_template_update['uuid'], self.ct['uuid']) self.assertTrue(cluster_template_update['tls_disabled']) diff --git a/openstack/tests/unit/cloud/test_cluster_templates.py b/openstack/tests/unit/cloud/test_cluster_templates.py index 15e41d836..c6f5ad70f 100644 --- a/openstack/tests/unit/cloud/test_cluster_templates.py +++ b/openstack/tests/unit/cloud/test_cluster_templates.py @@ -10,14 +10,14 @@ # License for the specific language governing permissions and limitations # under the License. -import munch import testtools +from openstack.container_infrastructure_management.v1 import cluster_template from openstack import exceptions from openstack.tests.unit import base -cluster_template_obj = munch.Munch( +cluster_template_obj = dict( apiserver_port=12345, cluster_distro='fake-distro', coe='fake-coe', @@ -50,6 +50,12 @@ cluster_template_obj = munch.Munch( class TestClusterTemplates(base.TestCase): + def _compare_clustertemplates(self, exp, real): + self.assertDictEqual( + cluster_template.ClusterTemplate(**exp).to_dict(computed=False), + real.to_dict(computed=False), + ) + def get_mock_url( self, service_type='container-infrastructure-management', @@ -64,15 +70,12 @@ class TestClusterTemplates(base.TestCase): dict( method='GET', uri=self.get_mock_url(resource='clustertemplates'), - status_code=404), - dict( - method='GET', - uri=self.get_mock_url(resource='baymodels/detail'), - json=dict(baymodels=[cluster_template_obj.toDict()]))]) + json=dict(clustertemplates=[cluster_template_obj]))]) cluster_templates_list = self.cloud.list_cluster_templates() - self.assertEqual( + self._compare_clustertemplates( + cluster_template_obj, cluster_templates_list[0], - self.cloud._normalize_cluster_template(cluster_template_obj)) + ) self.assert_calls() def test_list_cluster_templates_with_detail(self): @@ -80,15 +83,12 @@ class TestClusterTemplates(base.TestCase): dict( method='GET', uri=self.get_mock_url(resource='clustertemplates'), - status_code=404), - dict( - method='GET', - uri=self.get_mock_url(resource='baymodels/detail'), - json=dict(baymodels=[cluster_template_obj.toDict()]))]) + json=dict(clustertemplates=[cluster_template_obj]))]) cluster_templates_list = self.cloud.list_cluster_templates(detail=True) - self.assertEqual( + self._compare_clustertemplates( + cluster_template_obj, cluster_templates_list[0], - self.cloud._normalize_cluster_template(cluster_template_obj)) + ) self.assert_calls() def test_search_cluster_templates_by_name(self): @@ -96,11 +96,7 @@ class TestClusterTemplates(base.TestCase): dict( method='GET', uri=self.get_mock_url(resource='clustertemplates'), - status_code=404), - dict( - method='GET', - uri=self.get_mock_url(resource='baymodels/detail'), - json=dict(baymodels=[cluster_template_obj.toDict()]))]) + json=dict(clustertemplates=[cluster_template_obj]))]) cluster_templates = self.cloud.search_cluster_templates( name_or_id='fake-cluster-template') @@ -115,11 +111,7 @@ class TestClusterTemplates(base.TestCase): dict( method='GET', uri=self.get_mock_url(resource='clustertemplates'), - status_code=404), - dict( - method='GET', - uri=self.get_mock_url(resource='baymodels/detail'), - json=dict(baymodels=[cluster_template_obj.toDict()]))]) + json=dict(clustertemplates=[cluster_template_obj]))]) cluster_templates = self.cloud.search_cluster_templates( name_or_id='non-existent') @@ -132,16 +124,14 @@ class TestClusterTemplates(base.TestCase): dict( method='GET', uri=self.get_mock_url(resource='clustertemplates'), - status_code=404), - dict( - method='GET', - uri=self.get_mock_url(resource='baymodels/detail'), - json=dict(baymodels=[cluster_template_obj.toDict()]))]) + json=dict(clustertemplates=[cluster_template_obj]))]) r = self.cloud.get_cluster_template('fake-cluster-template') self.assertIsNotNone(r) - self.assertDictEqual( - r, self.cloud._normalize_cluster_template(cluster_template_obj)) + self._compare_clustertemplates( + cluster_template_obj, + r, + ) self.assert_calls() def test_get_cluster_template_not_found(self): @@ -149,35 +139,29 @@ class TestClusterTemplates(base.TestCase): dict( method='GET', uri=self.get_mock_url(resource='clustertemplates'), - status_code=404), - dict( - method='GET', - uri=self.get_mock_url(resource='baymodels/detail'), - json=dict(baymodels=[]))]) + json=dict(clustertemplates=[]))]) r = self.cloud.get_cluster_template('doesNotExist') self.assertIsNone(r) self.assert_calls() def test_create_cluster_template(self): - json_response = cluster_template_obj.toDict() - kwargs = dict(name=cluster_template_obj.name, - image_id=cluster_template_obj.image_id, - keypair_id=cluster_template_obj.keypair_id, - coe=cluster_template_obj.coe) + json_response = cluster_template_obj.copy() + kwargs = dict(name=cluster_template_obj['name'], + image_id=cluster_template_obj['image_id'], + keypair_id=cluster_template_obj['keypair_id'], + coe=cluster_template_obj['coe']) self.register_uris([ dict( method='POST', uri=self.get_mock_url(resource='clustertemplates'), - status_code=404), - dict( - method='POST', - uri=self.get_mock_url(resource='baymodels'), json=json_response, - validate=dict(json=kwargs)), - ]) - expected = self.cloud._normalize_cluster_template(json_response) + validate=dict(json=kwargs))]) response = self.cloud.create_cluster_template(**kwargs) - self.assertEqual(response, expected) + self._compare_clustertemplates( + json_response, + response + ) + self.assert_calls() def test_create_cluster_template_exception(self): @@ -185,10 +169,6 @@ class TestClusterTemplates(base.TestCase): dict( method='POST', uri=self.get_mock_url(resource='clustertemplates'), - status_code=404), - dict( - method='POST', - uri=self.get_mock_url(resource='baymodels'), status_code=403)]) # TODO(mordred) requests here doens't give us a great story # for matching the old error message text. Investigate plumbing @@ -207,14 +187,10 @@ class TestClusterTemplates(base.TestCase): dict( method='GET', uri=self.get_mock_url(resource='clustertemplates'), - status_code=404), - dict( - method='GET', - uri=self.get_mock_url(resource='baymodels/detail'), - json=dict(baymodels=[cluster_template_obj.toDict()])), + json=dict(clustertemplates=[cluster_template_obj])), dict( method='DELETE', - uri=self.get_mock_url(resource='baymodels/fake-uuid')), + uri=self.get_mock_url(resource='clustertemplates/fake-uuid')), ]) self.cloud.delete_cluster_template('fake-uuid') self.assert_calls() @@ -224,43 +200,36 @@ class TestClusterTemplates(base.TestCase): dict( method='GET', uri=self.get_mock_url(resource='clustertemplates'), - status_code=404), - dict( - method='GET', - uri=self.get_mock_url(resource='baymodels/detail'), - json=dict(baymodels=[cluster_template_obj.toDict()])), + json=dict(clustertemplates=[cluster_template_obj])), dict( method='PATCH', - uri=self.get_mock_url(resource='baymodels/fake-uuid'), + uri=self.get_mock_url(resource='clustertemplates/fake-uuid'), status_code=200, validate=dict( json=[{ - u'op': u'replace', - u'path': u'/name', - u'value': u'new-cluster-template' + 'op': 'replace', + 'path': '/name', + 'value': 'new-cluster-template' }] )), - dict( - method='GET', - uri=self.get_mock_url(resource='clustertemplates'), - # This json value is not meaningful to the test - it just has - # to be valid. - json=dict(baymodels=[cluster_template_obj.toDict()])), ]) new_name = 'new-cluster-template' - self.cloud.update_cluster_template( - 'fake-uuid', 'replace', name=new_name) + updated = self.cloud.update_cluster_template( + 'fake-uuid', name=new_name) + self.assertEqual(new_name, updated.name) self.assert_calls() - def test_get_coe_cluster_template(self): + def test_coe_get_cluster_template(self): self.register_uris([ dict( method='GET', uri=self.get_mock_url(resource='clustertemplates'), - json=dict(clustertemplates=[cluster_template_obj.toDict()]))]) + json=dict(clustertemplates=[cluster_template_obj]))]) - r = self.cloud.get_coe_cluster_template('fake-cluster-template') + r = self.cloud.get_cluster_template('fake-cluster-template') self.assertIsNotNone(r) - self.assertDictEqual( - r, self.cloud._normalize_cluster_template(cluster_template_obj)) + self._compare_clustertemplates( + cluster_template_obj, + r, + ) self.assert_calls() diff --git a/openstack/tests/unit/container_infrastructure_management/v1/test_cluster.py b/openstack/tests/unit/container_infrastructure_management/v1/test_cluster.py index 8e370877b..809eeb099 100644 --- a/openstack/tests/unit/container_infrastructure_management/v1/test_cluster.py +++ b/openstack/tests/unit/container_infrastructure_management/v1/test_cluster.py @@ -19,7 +19,7 @@ EXAMPLE = { "discovery_url": None, "flavor_id": None, "keypair": "my_keypair", - "labels": [], + "labels": {}, "master_count": 2, "master_flavor_id": None, "name": "k8s", diff --git a/openstack/tests/unit/container_infrastructure_management/v1/test_cluster_template.py b/openstack/tests/unit/container_infrastructure_management/v1/test_cluster_template.py new file mode 100644 index 000000000..a3a8b5272 --- /dev/null +++ b/openstack/tests/unit/container_infrastructure_management/v1/test_cluster_template.py @@ -0,0 +1,98 @@ +# 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. + +from openstack.container_infrastructure_management.v1 import cluster_template +from openstack.tests.unit import base + +EXAMPLE = { + "insecure_registry": None, + "http_proxy": "http://10.164.177.169:8080", + "updated_at": None, + "floating_ip_enabled": True, + "fixed_subnet": None, + "master_flavor_id": None, + "uuid": "085e1c4d-4f68-4bfd-8462-74b9e14e4f39", + "no_proxy": "10.0.0.0/8,172.0.0.0/8,192.0.0.0/8,localhost", + "https_proxy": "http://10.164.177.169:8080", + "tls_disabled": False, + "keypair_id": "kp", + "public": False, + "labels": {}, + "docker_volume_size": 3, + "server_type": "vm", + "external_network_id": "public", + "cluster_distro": "fedora-atomic", + "image_id": "fedora-atomic-latest", + "volume_driver": "cinder", + "registry_enabled": False, + "docker_storage_driver": "devicemapper", + "apiserver_port": None, + "name": "k8s-bm2", + "created_at": "2016-08-29T02:08:08+00:00", + "network_driver": "flannel", + "fixed_network": None, + "coe": "kubernetes", + "flavor_id": "m1.small", + "master_lb_enabled": True, + "dns_nameserver": "8.8.8.8", + "hidden": True, +} + + +class TestClusterTemplate(base.TestCase): + def test_basic(self): + sot = cluster_template.ClusterTemplate() + self.assertIsNone(sot.resource_key) + self.assertEqual('clustertemplates', sot.resources_key) + self.assertEqual('/clustertemplates', sot.base_path) + self.assertTrue(sot.allow_create) + self.assertTrue(sot.allow_fetch) + self.assertTrue(sot.allow_commit) + self.assertTrue(sot.allow_delete) + self.assertTrue(sot.allow_list) + + def test_make_it(self): + sot = cluster_template.ClusterTemplate(**EXAMPLE) + + self.assertEqual(EXAMPLE['apiserver_port'], sot.apiserver_port) + self.assertEqual(EXAMPLE['cluster_distro'], sot.cluster_distro) + self.assertEqual(EXAMPLE['coe'], sot.coe) + self.assertEqual(EXAMPLE['created_at'], sot.created_at) + self.assertEqual(EXAMPLE['docker_storage_driver'], + sot.docker_storage_driver) + self.assertEqual(EXAMPLE['docker_volume_size'], sot.docker_volume_size) + self.assertEqual(EXAMPLE['dns_nameserver'], sot.dns_nameserver) + self.assertEqual(EXAMPLE['external_network_id'], + sot.external_network_id) + self.assertEqual(EXAMPLE['fixed_network'], sot.fixed_network) + self.assertEqual(EXAMPLE['fixed_subnet'], sot.fixed_subnet) + self.assertEqual(EXAMPLE['flavor_id'], sot.flavor_id) + self.assertEqual(EXAMPLE['http_proxy'], sot.http_proxy) + self.assertEqual(EXAMPLE['https_proxy'], sot.https_proxy) + self.assertEqual(EXAMPLE['image_id'], sot.image_id) + self.assertEqual(EXAMPLE['insecure_registry'], sot.insecure_registry) + self.assertEqual(EXAMPLE['floating_ip_enabled'], + sot.is_floating_ip_enabled) + self.assertEqual(EXAMPLE['hidden'], sot.is_hidden) + self.assertEqual(EXAMPLE['master_lb_enabled'], + sot.is_master_lb_enabled) + self.assertEqual(EXAMPLE['tls_disabled'], sot.is_tls_disabled) + self.assertEqual(EXAMPLE['public'], sot.is_public) + self.assertEqual(EXAMPLE['registry_enabled'], sot.is_registry_enabled) + self.assertEqual(EXAMPLE['keypair_id'], sot.keypair_id) + self.assertEqual(EXAMPLE['master_flavor_id'], sot.master_flavor_id) + self.assertEqual(EXAMPLE['network_driver'], sot.network_driver) + self.assertEqual(EXAMPLE['no_proxy'], sot.no_proxy) + self.assertEqual(EXAMPLE['server_type'], sot.server_type) + self.assertEqual(EXAMPLE['updated_at'], sot.updated_at) + self.assertEqual(EXAMPLE['uuid'], sot.uuid) + self.assertEqual(EXAMPLE['volume_driver'], sot.volume_driver) diff --git a/openstack/tests/unit/container_infrastructure_management/v1/test_proxy.py b/openstack/tests/unit/container_infrastructure_management/v1/test_proxy.py index 3f2555693..337bdee72 100644 --- a/openstack/tests/unit/container_infrastructure_management/v1/test_proxy.py +++ b/openstack/tests/unit/container_infrastructure_management/v1/test_proxy.py @@ -12,16 +12,17 @@ from openstack.container_infrastructure_management.v1 import _proxy from openstack.container_infrastructure_management.v1 import cluster +from openstack.container_infrastructure_management.v1 import cluster_template from openstack.tests.unit import test_proxy_base -class TestClusterProxy(test_proxy_base.TestProxyBase): +class TestMagnumProxy(test_proxy_base.TestProxyBase): def setUp(self): super().setUp() self.proxy = _proxy.Proxy(self.session) -class TestCluster(TestClusterProxy): +class TestCluster(TestMagnumProxy): def test_cluster_get(self): self.verify_get(self.proxy.get_cluster, cluster.Cluster) @@ -49,3 +50,46 @@ class TestCluster(TestClusterProxy): def test_cluster_delete_ignore(self): self.verify_delete(self.proxy.delete_cluster, cluster.Cluster, True) + + +class TestClusterTemplate(TestMagnumProxy): + def test_cluster_template_get(self): + self.verify_get( + self.proxy.get_cluster_template, cluster_template.ClusterTemplate + ) + + def test_cluster_template_find(self): + self.verify_find( + self.proxy.find_cluster_template, + cluster_template.ClusterTemplate, + method_kwargs={}, + expected_kwargs={}, + ) + + def test_cluster_templates(self): + self.verify_list( + self.proxy.cluster_templates, + cluster_template.ClusterTemplate, + method_kwargs={"query": 1}, + expected_kwargs={"query": 1}, + ) + + def test_cluster_template_create_attrs(self): + self.verify_create( + self.proxy.create_cluster_template, + cluster_template.ClusterTemplate, + ) + + def test_cluster_template_delete(self): + self.verify_delete( + self.proxy.delete_cluster_template, + cluster_template.ClusterTemplate, + False, + ) + + def test_cluster_template_delete_ignore(self): + self.verify_delete( + self.proxy.delete_cluster_template, + cluster_template.ClusterTemplate, + True, + )