From bea867c8d19ba5dc6897c381c8b684cb2938b275 Mon Sep 17 00:00:00 2001 From: Jaycen Grant Date: Wed, 10 Aug 2016 13:29:31 -0700 Subject: [PATCH] Rename Bay to Cluster in api This is the first of several patches to add new Cluster commands that will replace the Bay terminalogy in Magnum. This patch adds the new Cluster and ClusterTemplate commands in addition to the Bay and Baymodel commands. Additional patches will be created for client, docs, and additional functional tests. Change-Id: Ie686281a6f98a1a9931158d2a79eee6ac21ed9a1 Implements: blueprint rename-bay-to-cluster --- .../api/v1/clients/cluster_client.py | 176 +++++++++++ .../api/v1/clients/cluster_template_client.py | 113 +++++++ .../api/v1/models/cluster_id_model.py | 24 ++ .../functional/api/v1/models/cluster_model.py | 30 ++ .../api/v1/models/cluster_template_model.py | 30 ++ .../v1/models/cluster_templatepatch_model.py | 77 +++++ .../api/v1/models/clusterpatch_model.py | 76 +++++ magnum/tests/functional/api/v1/test_bay.py | 85 ------ .../tests/functional/api/v1/test_cluster.py | 240 +++++++++++++++ .../api/v1/test_cluster_template.py | 239 +++++++++++++++ magnum/tests/functional/common/base.py | 6 +- magnum/tests/functional/common/datagen.py | 278 ++++++++++++++++++ magnum/tests/functional/common/manager.py | 18 +- 13 files changed, 1298 insertions(+), 94 deletions(-) create mode 100644 magnum/tests/functional/api/v1/clients/cluster_client.py create mode 100644 magnum/tests/functional/api/v1/clients/cluster_template_client.py create mode 100644 magnum/tests/functional/api/v1/models/cluster_id_model.py create mode 100644 magnum/tests/functional/api/v1/models/cluster_model.py create mode 100644 magnum/tests/functional/api/v1/models/cluster_template_model.py create mode 100644 magnum/tests/functional/api/v1/models/cluster_templatepatch_model.py create mode 100644 magnum/tests/functional/api/v1/models/clusterpatch_model.py create mode 100644 magnum/tests/functional/api/v1/test_cluster.py create mode 100644 magnum/tests/functional/api/v1/test_cluster_template.py diff --git a/magnum/tests/functional/api/v1/clients/cluster_client.py b/magnum/tests/functional/api/v1/clients/cluster_client.py new file mode 100644 index 0000000..9b39c9d --- /dev/null +++ b/magnum/tests/functional/api/v1/clients/cluster_client.py @@ -0,0 +1,176 @@ +# 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 oslo_log import log as logging +from tempest.lib import exceptions + +from magnum.i18n import _LE +from magnum.i18n import _LI +from magnum.i18n import _LW +from magnum.tests.functional.api.v1.models import cluster_id_model +from magnum.tests.functional.api.v1.models import cluster_model +from magnum.tests.functional.common import client +from magnum.tests.functional.common import utils + + +class ClusterClient(client.MagnumClient): + """Encapsulates REST calls and maps JSON to/from models""" + + LOG = logging.getLogger(__name__) + + @classmethod + def clusters_uri(cls, filters=None): + """Construct clusters uri with optional filters + + :param filters: Optional k:v dict that's converted to url query + :returns: url string + """ + + url = "/clusters" + if filters: + url = cls.add_filters(url, filters) + return url + + @classmethod + def cluster_uri(cls, cluster_id): + """Construct cluster uri + + :param cluster_id: cluster uuid or name + :returns: url string + """ + + return "{0}/{1}".format(cls.clusters_uri(), cluster_id) + + def list_clusters(self, filters=None, **kwargs): + """Makes GET /clusters request and returns ClusterCollection + + Abstracts REST call to return all clusters + + :param filters: Optional k:v dict that's converted to url query + :returns: response object and ClusterCollection object + """ + + resp, body = self.get(self.clusters_uri(filters), **kwargs) + return self.deserialize(resp, body, cluster_model.ClusterCollection) + + def get_cluster(self, cluster_id, **kwargs): + """Makes GET /cluster request and returns ClusterEntity + + Abstracts REST call to return a single cluster based on uuid or name + + :param cluster_id: cluster uuid or name + :returns: response object and ClusterCollection object + """ + + resp, body = self.get(self.cluster_uri(cluster_id)) + return self.deserialize(resp, body, cluster_model.ClusterEntity) + + def post_cluster(self, model, **kwargs): + """Makes POST /cluster request and returns ClusterIdEntity + + Abstracts REST call to create new cluster + + :param model: ClusterEntity + :returns: response object and ClusterIdEntity object + """ + + resp, body = self.post( + self.clusters_uri(), + body=model.to_json(), **kwargs) + return self.deserialize(resp, body, cluster_id_model.ClusterIdEntity) + + def patch_cluster(self, cluster_id, clusterpatch_listmodel, **kwargs): + """Makes PATCH /cluster request and returns ClusterIdEntity + + Abstracts REST call to update cluster attributes + + :param cluster_id: UUID of cluster + :param clusterpatch_listmodel: ClusterPatchCollection + :returns: response object and ClusterIdEntity object + """ + + resp, body = self.patch( + self.cluster_uri(cluster_id), + body=clusterpatch_listmodel.to_json(), **kwargs) + return self.deserialize(resp, body, cluster_id_model.ClusterIdEntity) + + def delete_cluster(self, cluster_id, **kwargs): + """Makes DELETE /cluster request and returns response object + + Abstracts REST call to delete cluster based on uuid or name + + :param cluster_id: UUID or name of cluster + :returns: response object + """ + + return self.delete(self.cluster_uri(cluster_id), **kwargs) + + def wait_for_cluster_to_delete(self, cluster_id): + utils.wait_for_condition( + lambda: self.does_cluster_not_exist(cluster_id), 10, 600) + + def wait_for_created_cluster(self, cluster_id, delete_on_error=True): + try: + utils.wait_for_condition( + lambda: self.does_cluster_exist(cluster_id), 10, 1800) + except Exception: + # In error state. Clean up the cluster id if desired + self.LOG.error(_LE('Cluster %s entered an exception state.') % + cluster_id) + if delete_on_error: + self.LOG.error(_LE('We will attempt to delete clusters now.')) + self.delete_cluster(cluster_id) + self.wait_for_cluster_to_delete(cluster_id) + raise + + def wait_for_final_state(self, cluster_id): + utils.wait_for_condition( + lambda: self.is_cluster_in_final_state(cluster_id), 10, 1800) + + def is_cluster_in_final_state(self, cluster_id): + try: + resp, model = self.get_cluster(cluster_id) + if model.status in ['CREATED', 'CREATE_COMPLETE', + 'ERROR', 'CREATE_FAILED']: + self.LOG.info(_LI('Cluster %s succeeded.') % cluster_id) + return True + else: + return False + except exceptions.NotFound: + self.LOG.warning(_LW('Cluster %s is not found.') % cluster_id) + return False + + def does_cluster_exist(self, cluster_id): + try: + resp, model = self.get_cluster(cluster_id) + if model.status in ['CREATED', 'CREATE_COMPLETE']: + self.LOG.info(_LI('Cluster %s is created.') % cluster_id) + return True + elif model.status in ['ERROR', 'CREATE_FAILED']: + self.LOG.error(_LE('Cluster %s is in fail state.') % + cluster_id) + raise exceptions.ServerFault( + "Got into an error condition: %s for %s" % + (model.status, cluster_id)) + else: + return False + except exceptions.NotFound: + self.LOG.warning(_LW('Cluster %s is not found.') % cluster_id) + return False + + def does_cluster_not_exist(self, cluster_id): + try: + self.get_cluster(cluster_id) + except exceptions.NotFound: + self.LOG.warning(_LW('Cluster %s is not found.') % cluster_id) + return True + return False diff --git a/magnum/tests/functional/api/v1/clients/cluster_template_client.py b/magnum/tests/functional/api/v1/clients/cluster_template_client.py new file mode 100644 index 0000000..e3b8e17 --- /dev/null +++ b/magnum/tests/functional/api/v1/clients/cluster_template_client.py @@ -0,0 +1,113 @@ +# 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 magnum.tests.functional.api.v1.models import cluster_template_model +from magnum.tests.functional.common import client + + +class ClusterTemplateClient(client.MagnumClient): + """Encapsulates REST calls and maps JSON to/from models""" + + @classmethod + def cluster_templates_uri(cls, filters=None): + """Construct clustertemplates uri with optional filters + + :param filters: Optional k:v dict that's converted to url query + :returns: url string + """ + + url = "/clustertemplates" + if filters: + url = cls.add_filters(url, filters) + return url + + @classmethod + def cluster_template_uri(cls, cluster_template_id): + """Construct cluster_template uri + + :param cluster_template_id: cluster_template uuid or name + :returns: url string + """ + + return "{0}/{1}".format(cls.cluster_templates_uri(), + cluster_template_id) + + def list_cluster_templates(self, filters=None, **kwargs): + """Makes GET /clustertemplates request + + Abstracts REST call to return all clustertemplates + + :param filters: Optional k:v dict that's converted to url query + :returns: response object and ClusterTemplateCollection object + """ + + resp, body = self.get(self.cluster_templates_uri(filters), **kwargs) + collection = cluster_template_model.ClusterTemplateCollection + return self.deserialize(resp, body, collection) + + def get_cluster_template(self, cluster_template_id, **kwargs): + """Makes GET /clustertemplate request and returns ClusterTemplateEntity + + Abstracts REST call to return a single clustertempalte based on uuid + or name + + :param cluster_template_id: clustertempalte uuid or name + :returns: response object and ClusterTemplateCollection object + """ + + resp, body = self.get(self.cluster_template_uri(cluster_template_id)) + return self.deserialize(resp, body, + cluster_template_model.ClusterTemplateEntity) + + def post_cluster_template(self, model, **kwargs): + """Makes POST /clustertemplate request + + Abstracts REST call to create new clustertemplate + + :param model: ClusterTemplateEntity + :returns: response object and ClusterTemplateEntity object + """ + + resp, body = self.post( + self.cluster_templates_uri(), + body=model.to_json(), **kwargs) + entity = cluster_template_model.ClusterTemplateEntity + return self.deserialize(resp, body, entity) + + def patch_cluster_template(self, cluster_template_id, + cluster_templatepatch_listmodel, **kwargs): + """Makes PATCH /clustertemplate and returns ClusterTemplateEntity + + Abstracts REST call to update clustertemplate attributes + + :param cluster_template_id: UUID of clustertemplate + :param cluster_templatepatch_listmodel: ClusterTemplatePatchCollection + :returns: response object and ClusterTemplateEntity object + """ + + resp, body = self.patch( + self.cluster_template_uri(cluster_template_id), + body=cluster_templatepatch_listmodel.to_json(), **kwargs) + return self.deserialize(resp, body, + cluster_template_model.ClusterTemplateEntity) + + def delete_cluster_template(self, cluster_template_id, **kwargs): + """Makes DELETE /clustertemplate request and returns response object + + Abstracts REST call to delete clustertemplate based on uuid or name + + :param cluster_template_id: UUID or name of clustertemplate + :returns: response object + """ + + return self.delete(self.cluster_template_uri(cluster_template_id), + **kwargs) diff --git a/magnum/tests/functional/api/v1/models/cluster_id_model.py b/magnum/tests/functional/api/v1/models/cluster_id_model.py new file mode 100644 index 0000000..d103f97 --- /dev/null +++ b/magnum/tests/functional/api/v1/models/cluster_id_model.py @@ -0,0 +1,24 @@ +# 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 magnum.tests.functional.common import models + + +class ClusterIdData(models.BaseModel): + """Data that encapsulates ClusterId attributes""" + pass + + +class ClusterIdEntity(models.EntityModel): + """Entity Model that represents a single instance of CertData""" + ENTITY_NAME = 'clusterid' + MODEL_TYPE = ClusterIdData diff --git a/magnum/tests/functional/api/v1/models/cluster_model.py b/magnum/tests/functional/api/v1/models/cluster_model.py new file mode 100644 index 0000000..af80c94 --- /dev/null +++ b/magnum/tests/functional/api/v1/models/cluster_model.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. + +from magnum.tests.functional.common import models + + +class ClusterData(models.BaseModel): + """Data that encapsulates cluster attributes""" + pass + + +class ClusterEntity(models.EntityModel): + """Entity Model that represents a single instance of ClusterData""" + ENTITY_NAME = 'cluster' + MODEL_TYPE = ClusterData + + +class ClusterCollection(models.CollectionModel): + """Collection Model that represents a list of ClusterData objects""" + COLLECTION_NAME = 'clusterlists' + MODEL_TYPE = ClusterData diff --git a/magnum/tests/functional/api/v1/models/cluster_template_model.py b/magnum/tests/functional/api/v1/models/cluster_template_model.py new file mode 100644 index 0000000..4471f1c --- /dev/null +++ b/magnum/tests/functional/api/v1/models/cluster_template_model.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. + +from magnum.tests.functional.common import models + + +class ClusterTemplateData(models.BaseModel): + """Data that encapsulates clustertemplate attributes""" + pass + + +class ClusterTemplateEntity(models.EntityModel): + """Entity Model that represents a single instance of ClusterTemplateData""" + ENTITY_NAME = 'clustertemplate' + MODEL_TYPE = ClusterTemplateData + + +class ClusterTemplateCollection(models.CollectionModel): + """Collection that represents a list of ClusterTemplateData objects""" + COLLECTION_NAME = 'clustertemplatelists' + MODEL_TYPE = ClusterTemplateData diff --git a/magnum/tests/functional/api/v1/models/cluster_templatepatch_model.py b/magnum/tests/functional/api/v1/models/cluster_templatepatch_model.py new file mode 100644 index 0000000..83a6b67 --- /dev/null +++ b/magnum/tests/functional/api/v1/models/cluster_templatepatch_model.py @@ -0,0 +1,77 @@ +# 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. + +import json + +from magnum.tests.functional.common import models + + +class ClusterTemplatePatchData(models.BaseModel): + """Data that encapsulates clustertemplatepatch attributes""" + pass + + +class ClusterTemplatePatchEntity(models.EntityModel): + """Model that represents a single instance of ClusterTemplatePatchData""" + ENTITY_NAME = 'clustertemplatepatch' + MODEL_TYPE = ClusterTemplatePatchData + + +class ClusterTemplatePatchCollection(models.CollectionModel): + """Model that represents a list of ClusterTemplatePatchData objects""" + MODEL_TYPE = ClusterTemplatePatchData + COLLECTION_NAME = 'clustertemplatepatchlist' + + def to_json(self): + """Converts ClusterTemplatePatchCollection to json + + Retrieves list from COLLECTION_NAME attribute and converts each object + to dict, appending it to a list. Then converts the entire list to + json + + This is required due to COLLECTION_NAME holding a list of objects that + needed to be converted to dict individually + + :returns: json object + """ + + data = getattr(self, ClusterTemplatePatchCollection.COLLECTION_NAME) + collection = [] + for d in data: + collection.append(d.to_dict()) + return json.dumps(collection) + + @classmethod + def from_dict(cls, data): + """Converts dict to ClusterTemplatePatchData + + Converts data dict to list of ClusterTemplatePatchData objects and + stores it in COLLECTION_NAME + + Example of dict data: + + [{ + "path": "/name", + "value": "myname", + "op": "replace" + }] + + :param data: dict of patch data + :returns: json object + """ + + model = cls() + collection = [] + for d in data: + collection.append(cls.MODEL_TYPE.from_dict(d)) + setattr(model, cls.COLLECTION_NAME, collection) + return model diff --git a/magnum/tests/functional/api/v1/models/clusterpatch_model.py b/magnum/tests/functional/api/v1/models/clusterpatch_model.py new file mode 100644 index 0000000..6e93f37 --- /dev/null +++ b/magnum/tests/functional/api/v1/models/clusterpatch_model.py @@ -0,0 +1,76 @@ +# 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. + +import json + +from magnum.tests.functional.common import models + + +class ClusterPatchData(models.BaseModel): + """Data that encapsulates clusterpatch attributes""" + pass + + +class ClusterPatchEntity(models.EntityModel): + """Entity Model that represents a single instance of ClusterPatchData""" + ENTITY_NAME = 'clusterpatch' + MODEL_TYPE = ClusterPatchData + + +class ClusterPatchCollection(models.CollectionModel): + """Collection Model that represents a list of ClusterPatchData objects""" + MODEL_TYPE = ClusterPatchData + COLLECTION_NAME = 'clusterpatchlist' + + def to_json(self): + """Converts ClusterPatchCollection to json + + Retrieves list from COLLECTION_NAME attribute and converts each object + to dict, appending it to a list. Then converts the entire list to json + + This is required due to COLLECTION_NAME holding a list of objects that + needed to be converted to dict individually + + :returns: json object + """ + + data = getattr(self, ClusterPatchCollection.COLLECTION_NAME) + collection = [] + for d in data: + collection.append(d.to_dict()) + return json.dumps(collection) + + @classmethod + def from_dict(cls, data): + """Converts dict to ClusterPatchData + + Converts data dict to list of ClusterPatchData objects and stores it + in COLLECTION_NAME + + Example of dict data: + + [{ + "path": "/name", + "value": "myname", + "op": "replace" + }] + + :param data: dict of patch data + :returns: json object + """ + + model = cls() + collection = [] + for d in data: + collection.append(cls.MODEL_TYPE.from_dict(d)) + setattr(model, cls.COLLECTION_NAME, collection) + return model diff --git a/magnum/tests/functional/api/v1/test_bay.py b/magnum/tests/functional/api/v1/test_bay.py index 7827d08..33cd8a5 100644 --- a/magnum/tests/functional/api/v1/test_bay.py +++ b/magnum/tests/functional/api/v1/test_bay.py @@ -18,7 +18,6 @@ from tempest.lib.common.utils import data_utils from tempest.lib import exceptions import testtools -from magnum.objects.fields import BayStatus from magnum.tests.functional.api import base from magnum.tests.functional.common import config from magnum.tests.functional.common import datagen @@ -131,56 +130,6 @@ class BayTest(base.BaseTempestTest): resp, model = self.bay_client.get_bay(bay_id) return resp, model - # (dimtruck) Combining all these tests in one because - # they time out on the gate (2 hours not enough) - @testtools.testcase.attr('positive') - def test_create_list_and_delete_bays(self): - gen_model = datagen.valid_bay_data( - baymodel_id=self.baymodel.uuid, node_count=1) - - # test bay create - _, temp_model = self._create_bay(gen_model) - self.assertEqual(BayStatus.CREATE_IN_PROGRESS, temp_model.status) - self.assertIsNone(temp_model.status_reason) - - # test bay list - resp, model = self.bay_client.list_bays() - self.assertEqual(200, resp.status) - self.assertGreater(len(model.bays), 0) - self.assertIn( - temp_model.uuid, list([x['uuid'] for x in model.bays])) - - # test invalid bay update - patch_model = datagen.bay_name_patch_data() - self.assertRaises( - exceptions.BadRequest, - self.bay_client.patch_bay, - temp_model.uuid, patch_model) - - # test bay delete - self._delete_bay(temp_model.uuid) - self.bays.remove(temp_model.uuid) - - @testtools.testcase.attr('positive') - def test_create_delete_bays_async(self): - gen_model = datagen.valid_bay_data( - baymodel_id=self.baymodel.uuid, node_count=1) - - # test bay create - _, temp_model = self._create_bay(gen_model, is_async=True) - self.assertNotIn('status', temp_model) - - # test bay list - resp, model = self.bay_client.list_bays() - self.assertEqual(200, resp.status) - self.assertGreater(len(model.bays), 0) - self.assertIn( - temp_model.uuid, list([x['uuid'] for x in model.bays])) - - # test bay delete - self._delete_bay(temp_model.uuid) - self.bays.remove(temp_model.uuid) - @testtools.testcase.attr('negative') def test_create_bay_for_nonexisting_baymodel(self): gen_model = datagen.valid_bay_data(baymodel_id='this-does-not-exist') @@ -265,37 +214,3 @@ class BayTest(base.BaseTempestTest): self.assertRaises( exceptions.NotFound, self.bay_client.delete_bay, data_utils.rand_uuid()) - - @testtools.testcase.attr('positive') - def test_certificate_sign_and_show(self): - first_model = datagen.valid_bay_data(baymodel_id=self.baymodel.uuid, - name='test') - _, bay_model = self._create_bay(first_model) - - # test ca show - resp, model = self.cert_client.get_cert( - bay_model.uuid) - self.LOG.debug("cert resp: %s" % resp) - self.assertEqual(200, resp.status) - self.assertEqual(model.bay_uuid, bay_model.uuid) - self.assertIsNotNone(model.pem) - self.assertIn('-----BEGIN CERTIFICATE-----', model.pem) - self.assertIn('-----END CERTIFICATE-----', model.pem) - - # test ca sign - model = datagen.cert_data(bay_uuid=bay_model.uuid) - resp, model = self.cert_client.post_cert(model) - self.LOG.debug("cert resp: %s" % resp) - self.assertEqual(201, resp.status) - self.assertEqual(model.bay_uuid, bay_model.uuid) - self.assertIsNotNone(model.pem) - self.assertIn('-----BEGIN CERTIFICATE-----', model.pem) - self.assertIn('-----END CERTIFICATE-----', model.pem) - - # test ca sign invalid - model = datagen.cert_data(bay_uuid=bay_model.uuid, - csr_data="invalid_csr") - self.assertRaises( - exceptions.BadRequest, - self.cert_client.post_cert, - model) diff --git a/magnum/tests/functional/api/v1/test_cluster.py b/magnum/tests/functional/api/v1/test_cluster.py new file mode 100644 index 0000000..9c94362 --- /dev/null +++ b/magnum/tests/functional/api/v1/test_cluster.py @@ -0,0 +1,240 @@ +# 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. + +import fixtures + +from oslo_log import log as logging +from oslo_utils import uuidutils +from tempest.lib.common.utils import data_utils +from tempest.lib import exceptions +import testtools + +from magnum.tests.functional.api import base +from magnum.tests.functional.common import config +from magnum.tests.functional.common import datagen + + +class ClusterTest(base.BaseTempestTest): + + """Tests for cluster CRUD.""" + + LOG = logging.getLogger(__name__) + + def __init__(self, *args, **kwargs): + super(ClusterTest, self).__init__(*args, **kwargs) + self.clusters = [] + self.creds = None + self.keypair = None + self.cluster_template = None + self.cluster_template_client = None + self.keypairs_client = None + self.cluster_client = None + self.cert_client = None + + def setUp(self): + try: + super(ClusterTest, self).setUp() + (self.creds, self.keypair) = self.get_credentials_with_keypair( + type_of_creds='default') + (self.cluster_template_client, + self.keypairs_client) = self.get_clients_with_existing_creds( + creds=self.creds, + type_of_creds='default', + request_type='cluster_template') + (self.cluster_client, _) = self.get_clients_with_existing_creds( + creds=self.creds, + type_of_creds='default', + request_type='cluster') + (self.cert_client, _) = self.get_clients_with_existing_creds( + creds=self.creds, + type_of_creds='default', + request_type='cert') + model = datagen.valid_swarm_cluster_template() + _, self.cluster_template = self._create_cluster_template(model) + + # NOTE (dimtruck) by default tempest sets timeout to 20 mins. + # We need more time. + test_timeout = 1800 + self.useFixture(fixtures.Timeout(test_timeout, gentle=True)) + except Exception: + self.tearDown() + raise + + def tearDown(self): + try: + cluster_list = self.clusters[:] + for cluster_id in cluster_list: + self._delete_cluster(cluster_id) + self.clusters.remove(cluster_id) + if self.cluster_template: + self._delete_cluster_template(self.cluster_template.uuid) + finally: + super(ClusterTest, self).tearDown() + + def _create_cluster_template(self, cm_model): + self.LOG.debug('We will create a clustertemplate for %s' % cm_model) + resp, model = self.cluster_template_client.post_cluster_template( + cm_model) + return resp, model + + def _delete_cluster_template(self, cm_id): + self.LOG.debug('We will delete a clustertemplate for %s' % cm_id) + resp, model = self.cluster_template_client.delete_cluster_template( + cm_id) + return resp, model + + def _create_cluster(self, cluster_model): + self.LOG.debug('We will create cluster for %s' % cluster_model) + resp, model = self.cluster_client.post_cluster(cluster_model) + self.LOG.debug('Response: %s' % resp) + self.assertEqual(202, resp.status) + self.assertIsNotNone(model.uuid) + self.assertTrue(uuidutils.is_uuid_like(model.uuid)) + self.clusters.append(model.uuid) + self.cluster_uuid = model.uuid + if config.Config.copy_logs: + self.addOnException(self.copy_logs_handler( + lambda: list( + [self._get_cluster_by_id(model.uuid)[1].master_addresses, + self._get_cluster_by_id(model.uuid)[1].node_addresses]), + self.cluster_template.coe, + self.keypair)) + self.cluster_client.wait_for_created_cluster(model.uuid, + delete_on_error=False) + return resp, model + + def _delete_cluster(self, cluster_id): + self.LOG.debug('We will delete a cluster for %s' % cluster_id) + resp, model = self.cluster_client.delete_cluster(cluster_id) + self.assertEqual(204, resp.status) + self.cluster_client.wait_for_cluster_to_delete(cluster_id) + self.assertRaises(exceptions.NotFound, self.cert_client.get_cert, + cluster_id) + return resp, model + + def _get_cluster_by_id(self, cluster_id): + resp, model = self.cluster_client.get_cluster(cluster_id) + return resp, model + + # (dimtruck) Combining all these tests in one because + # they time out on the gate (2 hours not enough) + @testtools.testcase.attr('positive') + def test_create_list_sign_delete_clusters(self): + gen_model = datagen.valid_cluster_data( + cluster_template_id=self.cluster_template.uuid, node_count=1) + + # test cluster create + _, cluster_model = self._create_cluster(gen_model) + self.assertNotIn('status', cluster_model) + + # test cluster list + resp, cluster_list_model = self.cluster_client.list_clusters() + self.assertEqual(200, resp.status) + self.assertGreater(len(cluster_list_model.clusters), 0) + self.assertIn( + cluster_model.uuid, list([x['uuid'] + for x in cluster_list_model.clusters])) + + # test invalid cluster update + patch_model = datagen.cluster_name_patch_data() + self.assertRaises( + exceptions.BadRequest, + self.cluster_client.patch_cluster, + cluster_model.uuid, patch_model) + + # test ca show + resp, cert_model = self.cert_client.get_cert( + cluster_model.uuid) + self.LOG.debug("cert resp: %s" % resp) + self.assertEqual(200, resp.status) + self.assertEqual(cert_model.bay_uuid, cluster_model.uuid) + self.assertIsNotNone(cert_model.pem) + self.assertIn('-----BEGIN CERTIFICATE-----', cert_model.pem) + self.assertIn('-----END CERTIFICATE-----', cert_model.pem) + + # test ca sign + cert_data_model = datagen.cert_data(cluster_model.uuid) + resp, cert_model = self.cert_client.post_cert(cert_data_model) + self.LOG.debug("cert resp: %s" % resp) + self.assertEqual(201, resp.status) + self.assertEqual(cert_model.bay_uuid, cluster_model.uuid) + self.assertIsNotNone(cert_model.pem) + self.assertIn('-----BEGIN CERTIFICATE-----', cert_model.pem) + self.assertIn('-----END CERTIFICATE-----', cert_model.pem) + + # test ca sign invalid + cert_data_model = datagen.cert_data(cluster_model.uuid, + csr_data="invalid_csr") + self.assertRaises( + exceptions.BadRequest, + self.cert_client.post_cert, + cert_data_model) + + # test cluster delete + self._delete_cluster(cluster_model.uuid) + self.clusters.remove(cluster_model.uuid) + + @testtools.testcase.attr('negative') + def test_create_cluster_for_nonexisting_cluster_template(self): + cm_id = 'this-does-not-exist' + gen_model = datagen.valid_cluster_data(cluster_template_id=cm_id) + self.assertRaises( + exceptions.BadRequest, + self.cluster_client.post_cluster, gen_model) + + @testtools.testcase.attr('negative') + def test_create_cluster_with_node_count_0(self): + gen_model = datagen.valid_cluster_data( + cluster_template_id=self.cluster_template.uuid, node_count=0) + self.assertRaises( + exceptions.BadRequest, + self.cluster_client.post_cluster, gen_model) + + @testtools.testcase.attr('negative') + def test_create_cluster_with_zero_masters(self): + uuid = self.cluster_template.uuid + gen_model = datagen.valid_cluster_data(cluster_template_id=uuid, + master_count=0) + self.assertRaises( + exceptions.BadRequest, + self.cluster_client.post_cluster, gen_model) + + @testtools.testcase.attr('negative') + def test_create_cluster_with_nonexisting_flavor(self): + gen_model = \ + datagen.cluster_template_data_with_valid_keypair_image_flavor() + resp, cluster_template = self._create_cluster_template(gen_model) + self.assertEqual(201, resp.status) + self.assertIsNotNone(cluster_template.uuid) + + uuid = cluster_template.uuid + gen_model = datagen.valid_cluster_data(cluster_template_id=uuid) + gen_model.flavor_id = 'aaa' + self.assertRaises(exceptions.BadRequest, + self.cluster_client.post_cluster, gen_model) + + resp, _ = self._delete_cluster_template(cluster_template.uuid) + self.assertEqual(204, resp.status) + + @testtools.testcase.attr('negative') + def test_update_cluster_for_nonexisting_cluster(self): + patch_model = datagen.cluster_name_patch_data() + + self.assertRaises( + exceptions.NotFound, + self.cluster_client.patch_cluster, 'fooo', patch_model) + + @testtools.testcase.attr('negative') + def test_delete_cluster_for_nonexisting_cluster(self): + self.assertRaises( + exceptions.NotFound, + self.cluster_client.delete_cluster, data_utils.rand_uuid()) diff --git a/magnum/tests/functional/api/v1/test_cluster_template.py b/magnum/tests/functional/api/v1/test_cluster_template.py new file mode 100644 index 0000000..79b5a92 --- /dev/null +++ b/magnum/tests/functional/api/v1/test_cluster_template.py @@ -0,0 +1,239 @@ +# 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 tempest.lib.common.utils import data_utils +from tempest.lib import exceptions +import testtools + +from magnum.tests.functional.api import base +from magnum.tests.functional.common import datagen + + +class ClusterTemplateTest(base.BaseTempestTest): + + """Tests for clustertemplate CRUD.""" + + def __init__(self, *args, **kwargs): + super(ClusterTemplateTest, self).__init__(*args, **kwargs) + self.cluster_templates = [] + self.cluster_template_client = None + self.keypairs_client = None + + def setUp(self): + try: + super(ClusterTemplateTest, self).setUp() + (self.cluster_template_client, + self.keypairs_client) = self.get_clients_with_new_creds( + type_of_creds='default', + request_type='cluster_template') + except Exception: + self.tearDown() + raise + + def tearDown(self): + for cluster_template_id in self.cluster_templates: + self._delete_cluster_template(cluster_template_id) + self.cluster_templates.remove(cluster_template_id) + super(ClusterTemplateTest, self).tearDown() + + def _create_cluster_template(self, cmodel_model): + resp, model = \ + self.cluster_template_client.post_cluster_template(cmodel_model) + self.assertEqual(201, resp.status) + self.cluster_templates.append(model.uuid) + return resp, model + + def _delete_cluster_template(self, model_id): + resp, model = \ + self.cluster_template_client.delete_cluster_template(model_id) + self.assertEqual(204, resp.status) + return resp, model + + @testtools.testcase.attr('positive') + def test_list_cluster_templates(self): + gen_model = \ + datagen.cluster_template_data_with_valid_keypair_image_flavor() + _, temp_model = self._create_cluster_template(gen_model) + resp, model = self.cluster_template_client.list_cluster_templates() + self.assertEqual(200, resp.status) + self.assertGreater(len(model.clustertemplates), 0) + self.assertIn( + temp_model.uuid, + list([x['uuid'] for x in model.clustertemplates])) + + @testtools.testcase.attr('positive') + def test_create_cluster_template(self): + gen_model = \ + datagen.cluster_template_data_with_valid_keypair_image_flavor() + resp, model = self._create_cluster_template(gen_model) + + @testtools.testcase.attr('positive') + def test_create_get_public_cluster_template(self): + gen_model = datagen.valid_swarm_cluster_template(is_public=True) + resp, model = self._create_cluster_template(gen_model) + + resp, model = \ + self.cluster_template_client.get_cluster_template(model.uuid) + self.assertEqual(200, resp.status) + self.assertTrue(model.public) + + @testtools.testcase.attr('positive') + def test_update_cluster_template_public_by_uuid(self): + path = "/public" + gen_model = \ + datagen.cluster_template_data_with_valid_keypair_image_flavor() + resp, old_model = self._create_cluster_template(gen_model) + + patch_model = datagen.cluster_template_replace_patch_data(path, + value=True) + resp, new_model = self.cluster_template_client.patch_cluster_template( + old_model.uuid, patch_model) + self.assertEqual(200, resp.status) + + resp, model = self.cluster_template_client.get_cluster_template( + new_model.uuid) + self.assertEqual(200, resp.status) + self.assertTrue(model.public) + + @testtools.testcase.attr('positive') + def test_update_cluster_template_by_uuid(self): + gen_model = \ + datagen.cluster_template_data_with_valid_keypair_image_flavor() + resp, old_model = self._create_cluster_template(gen_model) + + patch_model = datagen.cluster_template_name_patch_data() + resp, new_model = self.cluster_template_client.patch_cluster_template( + old_model.uuid, patch_model) + self.assertEqual(200, resp.status) + + resp, model = \ + self.cluster_template_client.get_cluster_template(new_model.uuid) + self.assertEqual(200, resp.status) + self.assertEqual(old_model.uuid, new_model.uuid) + self.assertEqual(model.name, new_model.name) + + @testtools.testcase.attr('positive') + def test_delete_cluster_template_by_uuid(self): + gen_model = \ + datagen.cluster_template_data_with_valid_keypair_image_flavor() + resp, model = self._create_cluster_template(gen_model) + resp, _ = self.cluster_template_client.delete_cluster_template( + model.uuid) + self.assertEqual(204, resp.status) + self.cluster_templates.remove(model.uuid) + + @testtools.testcase.attr('positive') + def test_delete_cluster_template_by_name(self): + gen_model = \ + datagen.cluster_template_data_with_valid_keypair_image_flavor() + resp, model = self._create_cluster_template(gen_model) + resp, _ = self.cluster_template_client.delete_cluster_template( + model.name) + self.assertEqual(204, resp.status) + self.cluster_templates.remove(model.uuid) + + @testtools.testcase.attr('negative') + def test_get_cluster_template_by_uuid_404(self): + self.assertRaises( + exceptions.NotFound, + self.cluster_template_client.get_cluster_template, + data_utils.rand_uuid()) + + @testtools.testcase.attr('negative') + def test_update_cluster_template_404(self): + patch_model = datagen.cluster_template_name_patch_data() + + self.assertRaises( + exceptions.NotFound, + self.cluster_template_client.patch_cluster_template, + data_utils.rand_uuid(), patch_model) + + @testtools.testcase.attr('negative') + def test_delete_cluster_template_404(self): + self.assertRaises( + exceptions.NotFound, + self.cluster_template_client.delete_cluster_template, + data_utils.rand_uuid()) + + @testtools.testcase.attr('negative') + def test_get_cluster_template_by_name_404(self): + self.assertRaises( + exceptions.NotFound, + self.cluster_template_client.get_cluster_template, 'fooo') + + @testtools.testcase.attr('negative') + def test_update_cluster_template_name_not_found(self): + patch_model = datagen.cluster_template_name_patch_data() + + self.assertRaises( + exceptions.NotFound, + self.cluster_template_client.patch_cluster_template, + 'fooo', patch_model) + + @testtools.testcase.attr('negative') + def test_delete_cluster_template_by_name_404(self): + self.assertRaises( + exceptions.NotFound, + self.cluster_template_client.get_cluster_template, 'fooo') + + @testtools.testcase.attr('negative') + def test_create_cluster_template_missing_image(self): + gen_model = datagen.cluster_template_data_with_missing_image() + self.assertRaises( + exceptions.BadRequest, + self.cluster_template_client.post_cluster_template, gen_model) + + @testtools.testcase.attr('negative') + def test_create_cluster_template_missing_flavor(self): + gen_model = datagen.cluster_template_data_with_missing_flavor() + self.assertRaises( + exceptions.BadRequest, + self.cluster_template_client.post_cluster_template, gen_model) + + @testtools.testcase.attr('negative') + 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) + + @testtools.testcase.attr('negative') + def test_update_cluster_template_invalid_patch(self): + # get json object + gen_model = \ + datagen.cluster_template_data_with_valid_keypair_image_flavor() + resp, old_model = self._create_cluster_template(gen_model) + + self.assertRaises( + exceptions.BadRequest, + self.cluster_template_client.patch_cluster_template, + data_utils.rand_uuid(), gen_model) + + @testtools.testcase.attr('negative') + def test_create_cluster_template_invalid_network_driver(self): + gen_model = \ + datagen.cluster_template_data_with_valid_keypair_image_flavor() + gen_model.network_driver = 'invalid_network_driver' + self.assertRaises( + exceptions.BadRequest, + self.cluster_template_client.post_cluster_template, gen_model) + + @testtools.testcase.attr('negative') + def test_create_cluster_template_invalid_volume_driver(self): + gen_model = \ + datagen.cluster_template_data_with_valid_keypair_image_flavor() + gen_model.volume_driver = 'invalid_volume_driver' + self.assertRaises( + exceptions.BadRequest, + self.cluster_template_client.post_cluster_template, gen_model) diff --git a/magnum/tests/functional/common/base.py b/magnum/tests/functional/common/base.py index cd4a3c6..ed496e6 100644 --- a/magnum/tests/functional/common/base.py +++ b/magnum/tests/functional/common/base.py @@ -79,9 +79,9 @@ class BaseMagnumTest(base.BaseTestCase): ]) except Exception: cls.LOG.error(msg) - msg = (_LE("failed to copy from %{node_address}s " - "to %{base_path}s%{log_name}s-" - "%{node_address}s") % + msg = (_LE("failed to copy from %(node_address)s " + "to %(base_path)s%(log_name)s-" + "%(node_address)s") % {'node_address': node_address, 'base_path': "/opt/stack/logs/bay-nodes/", 'log_name': log_name}) diff --git a/magnum/tests/functional/common/datagen.py b/magnum/tests/functional/common/datagen.py index 417ea71..14a03c8 100644 --- a/magnum/tests/functional/common/datagen.py +++ b/magnum/tests/functional/common/datagen.py @@ -23,6 +23,10 @@ from magnum.tests.functional.api.v1.models import baymodel_model from magnum.tests.functional.api.v1.models import baymodelpatch_model from magnum.tests.functional.api.v1.models import baypatch_model from magnum.tests.functional.api.v1.models import cert_model +from magnum.tests.functional.api.v1.models import cluster_model +from magnum.tests.functional.api.v1.models import cluster_template_model +from magnum.tests.functional.api.v1.models import cluster_templatepatch_model +from magnum.tests.functional.api.v1.models import clusterpatch_model from magnum.tests.functional.common import config @@ -334,3 +338,277 @@ def cert_data(bay_uuid, csr_data=None): model = cert_model.CertEntity.from_dict(data) return model + + +def cluster_template_data(**kwargs): + """Generates random cluster_template data + + Keypair and image id cannot be random for the cluster_template to be valid + due to validations for the presence of keypair and image id prior to + cluster_template creation. + + :param keypair_id: keypair name + :param image_id: image id or name + :returns: ClusterTemplateEntity with generated data + """ + + data = { + "name": data_utils.rand_name('cluster'), + "coe": "swarm", + "tls_disabled": False, + "network_driver": None, + "volume_driver": None, + "docker_volume_size": 3, + "labels": {}, + "public": False, + "fixed_network": "192.168.0.0/24", + "dns_nameserver": "8.8.8.8", + "flavor_id": data_utils.rand_name('cluster'), + "master_flavor_id": data_utils.rand_name('cluster'), + "external_network_id": config.Config.nic_id, + "keypair_id": data_utils.rand_name('cluster'), + "image_id": data_utils.rand_name('cluster') + } + + data.update(kwargs) + model = cluster_template_model.ClusterTemplateEntity.from_dict(data) + + return model + + +def cluster_template_replace_patch_data(path, + value=data_utils.rand_name('cluster')): + """Generates random ClusterTemplate patch data + + :param path: path to replace + :param value: value to replace in patch + :returns: ClusterTemplatePatchCollection with generated data + """ + + data = [{ + "path": path, + "value": value, + "op": "replace" + }] + collection = cluster_templatepatch_model.ClusterTemplatePatchCollection + return collection.from_dict(data) + + +def cluster_template_remove_patch_data(path): + """Generates ClusterTempalte patch data by removing value + + :param path: path to remove + :returns: BayModelPatchCollection with generated data + """ + + data = [{ + "path": path, + "op": "remove" + }] + collection = cluster_templatepatch_model.ClusterTemplatePatchCollection + return collection.from_dict(data) + + +def cluster_template_name_patch_data(name=data_utils.rand_name('cluster')): + """Generates random cluster_template patch data + + :param name: name to replace in patch + :returns: ClusterTemplatePatchCollection with generated data + """ + + data = [{ + "path": "/name", + "value": name, + "op": "replace" + }] + collection = cluster_templatepatch_model.ClusterTemplatePatchCollection + return collection.from_dict(data) + + +def cluster_template_flavor_patch_data(flavor=data_utils.rand_name('cluster')): + """Generates random cluster_template patch data + + :param flavor: flavor to replace in patch + :returns: ClusterTemplatePatchCollection with generated data + """ + + data = [{ + "path": "/flavor_id", + "value": flavor, + "op": "replace" + }] + collection = cluster_templatepatch_model.ClusterTemplatePatchCollection + return collection.from_dict(data) + + +def cluster_template_data_with_valid_keypair_image_flavor(): + """Generates random clustertemplate data with valid data + + :returns: ClusterTemplateEntity with generated data + """ + master_flavor = config.Config.master_flavor_id + return cluster_template_data(keypair_id=config.Config.keypair_id, + image_id=config.Config.image_id, + flavor_id=config.Config.flavor_id, + master_flavor_id=master_flavor) + + +def cluster_template_data_with_missing_image(): + """Generates random cluster_template data with missing image + + :returns: ClusterTemplateEntity with generated data + """ + + return cluster_template_data( + keypair_id=config.Config.keypair_id, + flavor_id=config.Config.flavor_id, + master_flavor_id=config.Config.master_flavor_id) + + +def cluster_template_data_with_missing_flavor(): + """Generates random cluster_template data with missing flavor + + :returns: ClusterTemplateEntity with generated data + """ + + return cluster_template_data(keypair_id=config.Config.keypair_id, + image_id=config.Config.image_id) + + +def cluster_template_data_with_missing_keypair(): + """Generates random cluster_template data with missing keypair + + :returns: ClusterTemplateEntity with generated data + """ + + return cluster_template_data( + image_id=config.Config.image_id, + flavor_id=config.Config.flavor_id, + master_flavor_id=config.Config.master_flavor_id) + + +def cluster_template_valid_data_with_specific_coe(coe): + """Generates random cluster_template data with valid keypair and image + + :param coe: coe + :returns: ClusterTemplateEntity with generated data + """ + + return cluster_template_data(keypair_id=config.Config.keypair_id, + image_id=config.Config.image_id, coe=coe) + + +def valid_swarm_cluster_template(is_public=False): + """Generates a valid swarm cluster_template with valid data + + :returns: ClusterTemplateEntity with generated data + """ + master_flavor_id = config.Config.master_flavor_id + return cluster_template_data(image_id=config.Config.image_id, + fixed_network="192.168.0.0/24", + flavor_id=config.Config.flavor_id, + 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, + http_proxy=None, https_proxy=None, + no_proxy=None, network_driver=None, + volume_driver=None, labels={}, + tls_disabled=False) + + +def cluster_data(name=data_utils.rand_name('cluster'), + cluster_template_id=data_utils.rand_uuid(), + node_count=random_int(1, 5), discovery_url=gen_random_ip(), + create_timeout=random_int(1, 30), + master_count=random_int(1, 5)): + """Generates random cluster data + + cluster_template_id cannot be random for the cluster to be valid due to + validations for the presence of clustertemplate prior to clustertemplate + creation. + + :param name: cluster name (must be unique) + :param cluster_template_id: clustertemplate unique id (must already exist) + :param node_count: number of agents for cluster + :param discovery_url: url provided for node discovery + :param create_timeout: timeout in minutes for cluster create + :param master_count: number of master nodes for the cluster + :returns: ClusterEntity with generated data + """ + + data = { + "name": name, + "cluster_template_id": cluster_template_id, + "node_count": node_count, + "discovery_url": None, + "create_timeout": create_timeout, + "master_count": master_count + } + model = cluster_model.ClusterEntity.from_dict(data) + + return model + + +def valid_cluster_data(cluster_template_id, + name=data_utils.rand_name('cluster'), + node_count=1, master_count=1, create_timeout=None): + """Generates random cluster data with valid + + :param cluster_template_id: clustertemplate unique id that already exists + :param name: cluster name (must be unique) + :param node_count: number of agents for cluster + :returns: ClusterEntity with generated data + """ + + return cluster_data(cluster_template_id=cluster_template_id, name=name, + master_count=master_count, node_count=node_count, + create_timeout=create_timeout) + + +def cluster_name_patch_data(name=data_utils.rand_name('cluster')): + """Generates random clustertemplate patch data + + :param name: name to replace in patch + :returns: ClusterPatchCollection with generated data + """ + + data = [{ + "path": "/name", + "value": name, + "op": "replace" + }] + return clusterpatch_model.ClusterPatchCollection.from_dict(data) + + +def cluster_api_addy_patch_data(address='0.0.0.0'): + """Generates random cluster patch data + + :param name: name to replace in patch + :returns: ClusterPatchCollection with generated data + """ + + data = [{ + "path": "/api_address", + "value": address, + "op": "replace" + }] + return clusterpatch_model.ClusterPatchCollection.from_dict(data) + + +def cluster_node_count_patch_data(node_count=2): + """Generates random cluster patch data + + :param name: name to replace in patch + :returns: ClusterPatchCollection with generated data + """ + + data = [{ + "path": "/node_count", + "value": node_count, + "op": "replace" + }] + return clusterpatch_model.ClusterPatchCollection.from_dict(data) diff --git a/magnum/tests/functional/common/manager.py b/magnum/tests/functional/common/manager.py index 27cb28a..fd04467 100644 --- a/magnum/tests/functional/common/manager.py +++ b/magnum/tests/functional/common/manager.py @@ -16,6 +16,8 @@ from tempest.common import credentials_factory as common_creds from magnum.tests.functional.api.v1.clients import bay_client from magnum.tests.functional.api.v1.clients import baymodel_client from magnum.tests.functional.api.v1.clients import cert_client +from magnum.tests.functional.api.v1.clients import cluster_client +from magnum.tests.functional.api.v1.clients import cluster_template_client from magnum.tests.functional.api.v1.clients import magnum_service_client from magnum.tests.functional.common import client from magnum.tests.functional.common import config @@ -29,17 +31,21 @@ class Manager(clients.Manager): super(Manager, self).__init__(credentials, 'container-infra') self.auth_provider.orig_base_url = self.auth_provider.base_url self.auth_provider.base_url = self.bypassed_base_url + auth = self.auth_provider if request_type == 'baymodel': - self.client = baymodel_client.BayModelClient(self.auth_provider) + self.client = baymodel_client.BayModelClient(auth) elif request_type == 'bay': - self.client = bay_client.BayClient(self.auth_provider) + self.client = bay_client.BayClient(auth) elif request_type == 'cert': - self.client = cert_client.CertClient(self.auth_provider) + self.client = cert_client.CertClient(auth) + elif request_type == 'cluster_template': + self.client = cluster_template_client.ClusterTemplateClient(auth) + elif request_type == 'cluster': + self.client = cluster_client.ClusterClient(auth) elif request_type == 'service': - self.client = magnum_service_client.MagnumServiceClient( - self.auth_provider) + self.client = magnum_service_client.MagnumServiceClient(auth) else: - self.client = client.MagnumClient(self.auth_provider) + self.client = client.MagnumClient(auth) def bypassed_base_url(self, filters, auth_data=None): if (config.Config.magnum_url and