Merge "Rename Bay to Cluster in api"

This commit is contained in:
Jenkins 2016-08-21 03:19:20 +00:00 committed by Gerrit Code Review
commit 7001074e4c
13 changed files with 1298 additions and 94 deletions

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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())

View File

@ -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)

View File

@ -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})

View File

@ -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)

View File

@ -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