diff --git a/magnum/tests/functional/api/v1/clients/bay_client.py b/magnum/tests/functional/api/v1/clients/bay_client.py new file mode 100644 index 0000000000..2ed98fdeb7 --- /dev/null +++ b/magnum/tests/functional/api/v1/clients/bay_client.py @@ -0,0 +1,143 @@ +# 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 import exceptions + +from magnum.tests.functional.api.v1.models import bay_model +from magnum.tests.functional.common import client +from magnum.tests.functional.common import utils + + +class BayClient(client.MagnumClient): + """Encapsulates REST calls and maps JSON to/from models""" + + @classmethod + def bays_uri(cls, filters=None): + """Construct bays uri with optional filters + + :param filters: Optional k:v dict that's converted to url query + :returns: url string + """ + + url = "/bays" + if filters: + url = cls.add_filters(url, filters) + return url + + @classmethod + def bay_uri(cls, bay_id): + """Construct bay uri + + :param bay_id: bay uuid or name + :returns: url string + """ + + return "{0}/{1}".format(cls.bays_uri(), bay_id) + + def list_bays(self, filters=None, **kwargs): + """Makes GET /bays request and returns BayCollection + + Abstracts REST call to return all bays + + :param filters: Optional k:v dict that's converted to url query + :returns: response object and BayCollection object + """ + + resp, body = self.get(self.bays_uri(filters), **kwargs) + return self.deserialize(resp, body, bay_model.BayCollection) + + def get_bay(self, bay_id, **kwargs): + """Makes GET /bay request and returns BayEntity + + Abstracts REST call to return a single bay based on uuid or name + + :param bay_id: bay uuid or name + :returns: response object and BayCollection object + """ + + resp, body = self.get(self.bay_uri(bay_id)) + return self.deserialize(resp, body, bay_model.BayEntity) + + def post_bay(self, model, **kwargs): + """Makes POST /bay request and returns BayEntity + + Abstracts REST call to create new bay + + :param model: BayEntity + :returns: response object and BayEntity object + """ + + resp, body = self.post( + self.bays_uri(), + body=model.to_json(), **kwargs) + return self.deserialize(resp, body, bay_model.BayEntity) + + def patch_bay(self, bay_id, baypatch_listmodel, **kwargs): + """Makes PATCH /bay request and returns BayEntity + + Abstracts REST call to update bay attributes + + :param bay_id: UUID of bay + :param baypatch_listmodel: BayPatchCollection + :returns: response object and BayEntity object + """ + + resp, body = self.patch( + self.bay_uri(bay_id), + body=baypatch_listmodel.to_json(), **kwargs) + return self.deserialize(resp, body, bay_model.BayEntity) + + def delete_bay(self, bay_id, **kwargs): + """Makes DELETE /bay request and returns response object + + Abstracts REST call to delete bay based on uuid or name + + :param bay_id: UUID or name of bay + :returns: response object + """ + + return self.delete(self.bay_uri(bay_id), **kwargs) + + def wait_for_bay_to_delete(self, bay_id): + utils.wait_for_condition( + lambda: self.does_bay_not_exist(bay_id), 10, 3600) + + def wait_for_created_bay(self, bay_id): + try: + utils.wait_for_condition( + lambda: self.does_bay_exist(bay_id), 10, 3600) + except Exception: + # In error state. Clean up the bay id + self.delete_bay(bay_id) + self.wait_for_bay_to_delete(bay_id) + raise + + def does_bay_exist(self, bay_id): + try: + resp, model = self.get_bay(bay_id) + if model.status in ['CREATED', 'CREATE_COMPLETE']: + return True + elif model.status in ['ERROR', 'CREATE_FAILED']: + raise exceptions.ServerFault( + "Got into an error condition: %s for %s" % + (model.status, bay_id)) + else: + return False + except exceptions.NotFound: + return False + + def does_bay_not_exist(self, bay_id): + try: + self.get_bay(bay_id) + except exceptions.NotFound: + return True + return False diff --git a/magnum/tests/functional/api/v1/models/bay_model.py b/magnum/tests/functional/api/v1/models/bay_model.py new file mode 100644 index 0000000000..4c89acf305 --- /dev/null +++ b/magnum/tests/functional/api/v1/models/bay_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 BayData(models.BaseModel): + """Data that encapsulates bay attributes""" + pass + + +class BayEntity(models.EntityModel): + """Entity Model that represents a single instance of BayData""" + ENTITY_NAME = 'bay' + MODEL_TYPE = BayData + + +class BayCollection(models.CollectionModel): + """Collection Model that represents a list of BayData objects""" + COLLECTION_NAME = 'baylists' + MODEL_TYPE = BayData diff --git a/magnum/tests/functional/api/v1/models/baypatch_model.py b/magnum/tests/functional/api/v1/models/baypatch_model.py new file mode 100644 index 0000000000..4a0f137446 --- /dev/null +++ b/magnum/tests/functional/api/v1/models/baypatch_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 BayPatchData(models.BaseModel): + """Data that encapsulates baypatch attributes""" + pass + + +class BayPatchEntity(models.EntityModel): + """Entity Model that represents a single instance of BayPatchData""" + ENTITY_NAME = 'baypatch' + MODEL_TYPE = BayPatchData + + +class BayPatchCollection(models.CollectionModel): + """Collection Model that represents a list of BayPatchData objects""" + MODEL_TYPE = BayPatchData + COLLECTION_NAME = 'baypatchlist' + + def to_json(self): + """Converts BayPatchCollection 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, BayPatchCollection.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 BayPatchData + + Converts data dict to list of BayPatchData 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 new file mode 100644 index 0000000000..131af1e2f8 --- /dev/null +++ b/magnum/tests/functional/api/v1/test_bay.py @@ -0,0 +1,171 @@ +# 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 tempest_lib.common.utils import data_utils +from tempest_lib import exceptions +import testtools + +from magnum.tests.functional.common import base +from magnum.tests.functional.common import datagen + + +class BayTest(base.BaseMagnumTest): + + """Tests for bay CRUD.""" + + LOG = logging.getLogger(__name__) + + def __init__(self, *args, **kwargs): + super(BayTest, self).__init__(*args, **kwargs) + self.bays = [] + self.credentials = None + self.baymodel = None + self.baymodel_client = None + self.keypairs_client = None + self.bay_client = None + + def setUp(self): + super(BayTest, self).setUp() + self.credentials = self.get_credentials(type_of_creds='default') + (self.baymodel_client, + self.keypairs_client) = self.get_clients_with_existing_creds( + creds=self.credentials, + type_of_creds='default', + request_type='baymodel') + (self.bay_client, _) = self.get_clients_with_existing_creds( + creds=self.credentials, + type_of_creds='default', + request_type='bay') + model = datagen.valid_swarm_baymodel() + _, self.baymodel = self._create_baymodel(model) + + # NOTE (dimtruck) by default tempest sets timeout to 20 mins. + # We need more time. + test_timeout = 3600 + self.useFixture(fixtures.Timeout(test_timeout, gentle=True)) + + def tearDown(self): + bay_list = self.bays[:] + for bay_id in bay_list: + self._delete_bay(bay_id) + self.bays.remove(bay_id) + self._delete_baymodel(self.baymodel.uuid) + super(BayTest, self).tearDown() + + def _create_baymodel(self, baymodel_model): + self.keypairs_client.create_keypair(name='default') + resp, model = self.baymodel_client.post_baymodel(baymodel_model) + return resp, model + + def _delete_baymodel(self, baymodel_id): + resp, model = self.baymodel_client.delete_baymodel(baymodel_id) + return resp, model + + def _create_bay(self, bay_model): + resp, model = self.bay_client.post_bay(bay_model) + self.LOG.info('Response: %s' % resp) + self.LOG.info('Model: %s ' % model) + self.assertEqual(resp.status, 201) + self.assertIsNotNone(model.uuid) + self.assertIsNone(model.status) + self.assertIsNone(model.status_reason) + self.assertEqual(model.baymodel_id, self.baymodel.uuid) + self.bay_client.wait_for_created_bay(model.uuid) + self.bays.append(model.uuid) + return resp, model + + def _delete_bay(self, bay_id): + resp, model = self.bay_client.delete_bay(bay_id) + self.assertEqual(resp.status, 204) + self.bay_client.wait_for_bay_to_delete(bay_id) + return resp, model + + def _get_bay_by_id(self, bay_id): + resp, model = self.bay_client.get_bay(bay_id) + self.assertEqual(resp.status, 404) + 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) + _, temp_model = self._create_bay(gen_model) + resp, model = self.bay_client.list_bays() + self.assertEqual(resp.status, 200) + self.assertGreater(len(model.bays), 0) + self.assertIn( + temp_model.uuid, list([x['uuid'] for x in model.bays])) + 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') + self.assertRaises( + exceptions.BadRequest, + self.bay_client.post_bay, gen_model) + + @testtools.testcase.attr('negative') + def test_create_bay_with_node_count_0(self): + gen_model = datagen.valid_bay_data( + baymodel_id=self.baymodel.uuid, node_count=0) + self.assertRaises( + exceptions.BadRequest, + self.bay_client.post_bay, gen_model) + + @testtools.testcase.attr('negative') + def test_create_bay_with_zero_masters(self): + gen_model = datagen.valid_bay_data(baymodel_id=self.baymodel.uuid, + master_count=0) + self.assertRaises( + exceptions.BadRequest, + self.bay_client.post_bay, gen_model) + + @testtools.testcase.attr('negative') + def test_create_bay_with_missing_name(self): + self.skipTest('This is currently an error! ' + 'Should throw a 400 instead of a 500') + gen_model = datagen.valid_bay_data(baymodel_id=self.baymodel.uuid, + name=None) + self.assertRaises( + exceptions.BadRequest, + self.bay_client.post_bay, gen_model) + + @testtools.testcase.attr('negative') + def test_update_bay_name_for_existing_bay(self): + first_model = datagen.valid_bay_data(baymodel_id=self.baymodel.uuid, + name='test') + _, old_model = self._create_bay(first_model) + + patch_model = datagen.bay_name_patch_data() + self.assertRaises( + exceptions.BadRequest, + self.bay_client.patch_bay, + old_model.uuid, patch_model) + + @testtools.testcase.attr('negative') + def test_update_bay_for_nonexisting_bay(self): + patch_model = datagen.bay_name_patch_data() + + self.assertRaises( + exceptions.NotFound, + self.bay_client.patch_bay, 'fooo', patch_model) + + @testtools.testcase.attr('negative') + def test_delete_bay_for_nonexisting_bay(self): + self.assertRaises( + exceptions.NotFound, + self.bay_client.delete_bay, data_utils.rand_uuid()) diff --git a/magnum/tests/functional/api/v1/test_baymodel.py b/magnum/tests/functional/api/v1/test_baymodel.py index 0113293787..854b77e9ef 100644 --- a/magnum/tests/functional/api/v1/test_baymodel.py +++ b/magnum/tests/functional/api/v1/test_baymodel.py @@ -11,6 +11,7 @@ # under the License. +from tempest_lib.common.utils import data_utils from tempest_lib import exceptions import testtools @@ -31,7 +32,7 @@ class BayModelTest(base.BaseMagnumTest): def setUp(self): super(BayModelTest, self).setUp() (self.baymodel_client, - self.keypairs_client) = self.get_clients_with_isolated_creds( + self.keypairs_client) = self.get_clients_with_new_creds( type_of_creds='default', request_type='baymodel') @@ -55,7 +56,7 @@ class BayModelTest(base.BaseMagnumTest): @testtools.testcase.attr('positive') def test_list_baymodels(self): self.keypairs_client.create_keypair(name='default') - gen_model = datagen.random_baymodel_data_w_valid_keypair_and_image_id() + gen_model = datagen.baymodel_data_with_valid_keypair_and_image_id() _, temp_model = self._create_baymodel(gen_model) resp, model = self.baymodel_client.list_baymodels() self.assertEqual(200, resp.status) @@ -66,16 +67,16 @@ class BayModelTest(base.BaseMagnumTest): @testtools.testcase.attr('positive') def test_create_baymodel(self): self.keypairs_client.create_keypair(name='default') - gen_model = datagen.random_baymodel_data_w_valid_keypair_and_image_id() + gen_model = datagen.baymodel_data_with_valid_keypair_and_image_id() resp, model = self._create_baymodel(gen_model) @testtools.testcase.attr('positive') def test_update_baymodel_by_uuid(self): self.keypairs_client.create_keypair(name='default') - gen_model = datagen.random_baymodel_data_w_valid_keypair_and_image_id() + gen_model = datagen.baymodel_data_with_valid_keypair_and_image_id() resp, old_model = self._create_baymodel(gen_model) - patch_model = datagen.random_baymodel_name_patch_data() + patch_model = datagen.baymodel_name_patch_data() resp, new_model = self.baymodel_client.patch_baymodel( old_model.uuid, patch_model) self.assertEqual(200, resp.status) @@ -88,7 +89,7 @@ class BayModelTest(base.BaseMagnumTest): @testtools.testcase.attr('positive') def test_delete_baymodel_by_uuid(self): self.keypairs_client.create_keypair(name='default') - gen_model = datagen.random_baymodel_data_w_valid_keypair_and_image_id() + gen_model = datagen.baymodel_data_with_valid_keypair_and_image_id() resp, model = self._create_baymodel(gen_model) resp, _ = self.baymodel_client.delete_baymodel(model.uuid) self.assertEqual(204, resp.status) @@ -97,7 +98,7 @@ class BayModelTest(base.BaseMagnumTest): @testtools.testcase.attr('positive') def test_delete_baymodel_by_name(self): self.keypairs_client.create_keypair(name='default') - gen_model = datagen.random_baymodel_data_w_valid_keypair_and_image_id() + gen_model = datagen.baymodel_data_with_valid_keypair_and_image_id() resp, model = self._create_baymodel(gen_model) resp, _ = self.baymodel_client.delete_baymodel(model.name) self.assertEqual(204, resp.status) @@ -107,22 +108,22 @@ class BayModelTest(base.BaseMagnumTest): def test_get_baymodel_by_uuid_404(self): self.assertRaises( exceptions.NotFound, - self.baymodel_client.get_baymodel, datagen.random_uuid()) + self.baymodel_client.get_baymodel, data_utils.rand_uuid()) @testtools.testcase.attr('negative') def test_update_baymodel_404(self): - patch_model = datagen.random_baymodel_name_patch_data() + patch_model = datagen.baymodel_name_patch_data() self.assertRaises( exceptions.NotFound, self.baymodel_client.patch_baymodel, - datagen.random_uuid(), patch_model) + data_utils.rand_uuid(), patch_model) @testtools.testcase.attr('negative') def test_delete_baymodel_404(self): self.assertRaises( exceptions.NotFound, - self.baymodel_client.delete_baymodel, datagen.random_uuid()) + self.baymodel_client.delete_baymodel, data_utils.rand_uuid()) @testtools.testcase.attr('negative') def test_get_baymodel_by_name_404(self): @@ -132,7 +133,7 @@ class BayModelTest(base.BaseMagnumTest): @testtools.testcase.attr('negative') def test_update_baymodel_name_not_found(self): - patch_model = datagen.random_baymodel_name_patch_data() + patch_model = datagen.baymodel_name_patch_data() self.assertRaises( exceptions.NotFound, @@ -147,14 +148,14 @@ class BayModelTest(base.BaseMagnumTest): @testtools.testcase.attr('negative') def test_create_baymodel_missing_image(self): self.keypairs_client.create_keypair(name='default') - gen_model = datagen.random_baymodel_data_w_valid_keypair() + gen_model = datagen.baymodel_data_with_valid_keypair() self.assertRaises( exceptions.NotFound, self.baymodel_client.post_baymodel, gen_model) @testtools.testcase.attr('negative') def test_create_baymodel_missing_keypair(self): - gen_model = datagen.random_baymodel_data_w_valid_image_id() + gen_model = datagen.baymodel_data_with_valid_image_id() self.assertRaises( exceptions.NotFound, self.baymodel_client.post_baymodel, gen_model) @@ -163,18 +164,18 @@ class BayModelTest(base.BaseMagnumTest): def test_update_baymodel_invalid_patch(self): # get json object self.keypairs_client.create_keypair(name='default') - gen_model = datagen.random_baymodel_data_w_valid_keypair_and_image_id() + gen_model = datagen.baymodel_data_with_valid_keypair_and_image_id() resp, old_model = self._create_baymodel(gen_model) self.assertRaises( exceptions.BadRequest, - self.baymodel_client.patch_baymodel, datagen.random_uuid(), + self.baymodel_client.patch_baymodel, data_utils.rand_uuid(), gen_model) @testtools.testcase.attr('negative') def test_create_baymodel_invalid_network_driver(self): self.keypairs_client.create_keypair(name='default') - gen_model = datagen.random_baymodel_data_w_valid_keypair_and_image_id() + gen_model = datagen.baymodel_data_with_valid_keypair_and_image_id() gen_model.network_driver = 'invalid_network_driver' self.assertRaises( exceptions.BadRequest, diff --git a/magnum/tests/functional/api/v1/test_magnum_service.py b/magnum/tests/functional/api/v1/test_magnum_service.py index 56f8bf0035..fcf99efc4d 100644 --- a/magnum/tests/functional/api/v1/test_magnum_service.py +++ b/magnum/tests/functional/api/v1/test_magnum_service.py @@ -33,7 +33,7 @@ class MagnumServiceTest(base.BaseMagnumTest): @testtools.testcase.attr('negative') def test_magnum_service_list_needs_admin(self): # Ensure that policy enforcement does not allow 'default' user - (self.service_client, _) = self.get_clients_with_isolated_creds( + (self.service_client, _) = self.get_clients_with_new_creds( type_of_creds='default', request_type='service') self.assertRaises(exceptions.ServerFault, @@ -42,7 +42,7 @@ class MagnumServiceTest(base.BaseMagnumTest): @testtools.testcase.attr('positive') def test_magnum_service_list(self): # get json object - (self.service_client, _) = self.get_clients_with_isolated_creds( + (self.service_client, _) = self.get_clients_with_new_creds( type_of_creds='admin', request_type='service') resp, msvcs = self.service_client.magnum_service_list() diff --git a/magnum/tests/functional/common/base.py b/magnum/tests/functional/common/base.py index c2216b3ce7..37cf6304dc 100644 --- a/magnum/tests/functional/common/base.py +++ b/magnum/tests/functional/common/base.py @@ -41,18 +41,7 @@ class BaseMagnumTest(base.BaseTestCase): if self.ic is not None: self.ic.clear_creds() - def get_clients_with_isolated_creds(self, - name=None, - type_of_creds="default", - request_type=None): - """Creates isolated creds. - - :param name: name, will be used for dynamic creds - :param type_of_creds: admin, alt or default - :param request_type: baymodel or service - :returns: MagnumClient -- client with isolated creds. - :returns: KeypairClient -- allows for creating of keypairs - """ + def get_credentials(self, name=None, type_of_creds="default"): if name is None: # Get name of test method name = inspect.stack()[1][3] @@ -66,22 +55,58 @@ class BaseMagnumTest(base.BaseTestCase): admin_role=config.Config.admin_role, admin_creds=common_creds.get_configured_credentials( 'identity_admin')) + + creds = None if "admin" == type_of_creds: creds = self.ic.get_admin_creds() + elif "alt" == type_of_creds: + creds = self.ic.get_alt_creds() + elif "default" == type_of_creds: + creds = self.ic.get_primary_creds() + else: + creds = self.ic.self.get_credentials(type_of_creds) + return creds + + def get_clients(self, creds, type_of_creds, request_type): + if "admin" == type_of_creds: manager_inst = manager.AdminManager(credentials=creds, request_type=request_type) elif "alt" == type_of_creds: - creds = self.ic.get_alt_creds() manager_inst = manager.AltManager(credentials=creds, request_type=request_type) elif "default" == type_of_creds: - creds = self.ic.get_primary_creds() manager_inst = manager.DefaultManager(credentials=creds, request_type=request_type) else: - creds = self.ic.self.get_credentials(type_of_creds) manager_inst = manager.DefaultManager(credentials=creds, request_type=request_type) # create client with isolated creds return (manager_inst.client, manager_inst.keypairs_client) + + def get_clients_with_existing_creds(self, + name=None, + creds=None, + type_of_creds="default", + request_type=None): + if creds is None: + return self.get_clients_with_isolated_creds(name, + type_of_creds, + request_type) + else: + return self.get_clients(creds, type_of_creds, request_type) + + def get_clients_with_new_creds(self, + name=None, + type_of_creds="default", + request_type=None): + """Creates isolated creds. + + :param name: name, will be used for dynamic creds + :param type_of_creds: admin, alt or default + :param request_type: baymodel or service + :returns: MagnumClient -- client with isolated creds. + :returns: KeypairClient -- allows for creating of keypairs + """ + creds = self.get_credentials(name, type_of_creds) + return self.get_clients(creds, type_of_creds, request_type) diff --git a/magnum/tests/functional/common/config.py b/magnum/tests/functional/common/config.py index 1568716444..75199f4c9d 100644 --- a/magnum/tests/functional/common/config.py +++ b/magnum/tests/functional/common/config.py @@ -81,6 +81,12 @@ class Config(object): raise Exception('config missing keypair_id key') cls.keypair_id = CONF.magnum.keypair_id + @classmethod + def set_flavor_id(cls, config): + if 'flavor_id' not in CONF.magnum: + raise Exception('config missing flavor_id key') + cls.flavor_id = CONF.magnum.flavor_id + @classmethod def setUp(cls): cls.set_admin_creds(config) @@ -93,3 +99,4 @@ class Config(object): cls.set_image_id(config) cls.set_nic_id(config) cls.set_keypair_id(config) + cls.set_flavor_id(config) diff --git a/magnum/tests/functional/common/datagen.py b/magnum/tests/functional/common/datagen.py index f110d7070d..c282101be6 100644 --- a/magnum/tests/functional/common/datagen.py +++ b/magnum/tests/functional/common/datagen.py @@ -14,39 +14,21 @@ import random import socket import string import struct -import uuid +from tempest_lib.common.utils import data_utils + +from magnum.tests.functional.api.v1.models import bay_model 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.common import config -def random_uuid(): - return uuid.uuid4() +def random_int(min_int=1, max_int=100): + return random.randrange(min_int, max_int) -def random_string(prefix='rand', n=8, suffix=''): - """Return a string containing random digits - - :param prefix: the exact text to start the string. Defaults to "rand" - :param n: the number of random digits to generate - :param suffix: the exact text to end the string - """ - digits = "".join(str(random.randrange(0, 10)) for _ in range(n)) - return prefix + digits + suffix - - -def generate_random_network(): - network_list = ["public", "private"] - return network_list[random.randrange(0, len(network_list))] - - -def generate_random_coe(): - coe_list = ["swarm", "kubernetes", "mesos"] - return coe_list[random.randrange(0, len(coe_list))] - - -def generate_random_coe_dep_network_driver(coe): +def gen_coe_dep_network_driver(coe): allowed_driver_types = { 'kubernetes': ['flannel', None], 'swarm': ['docker', 'flannel', None], @@ -56,15 +38,15 @@ def generate_random_coe_dep_network_driver(coe): return driver_types[random.randrange(0, len(driver_types))] -def generate_random_port(): - return random.randrange(49152, 65535) +def gen_random_port(): + return random_int(49152, 65535) -def generate_random_docker_volume_size(): - return random.randrange(1, 3) +def gen_docker_volume_size(min_int=1, max_int=3): + return random_int(min_int, max_int) -def generate_fake_ssh_pubkey(): +def gen_fake_ssh_pubkey(): chars = "".join( random.choice(string.ascii_uppercase + string.ascii_letters + string.digits + '/+=') @@ -72,11 +54,27 @@ def generate_fake_ssh_pubkey(): return "ssh-rsa " + chars -def generate_random_ip(): +def gen_random_ip(): return socket.inet_ntoa(struct.pack('>I', random.randint(1, 0xffffffff))) -def random_baymodel_data(keypair_id=random_string(), image_id=random_string()): +def gen_url(scheme="http", domain="example.com", port=80): + return "%s://%s:%s" % (scheme, domain, port) + + +def gen_http_proxy(): + return gen_url(port=gen_random_port()) + + +def gen_https_proxy(): + return gen_url(scheme="https", port=gen_random_port()) + + +def gen_no_proxy(): + return ",".join(gen_random_ip() for x in range(3)) + + +def baymodel_data(**kwargs): """Generates random baymodel data Keypair and image id cannot be random for the baymodel to be valid due to @@ -88,33 +86,28 @@ def random_baymodel_data(keypair_id=random_string(), image_id=random_string()): :returns: BayModelEntity with generated data """ - coe = generate_random_coe() data = { - "name": random_string(), - "image_id": image_id, - "flavor_id": random_string(), - "master_flavor_id": random_string(), - "dns_nameserver": generate_random_ip(), - "keypair_id": keypair_id, - "external_network_id": str(random_uuid()), - "fixed_network": generate_random_network(), - "apiserver_port": generate_random_port(), - "docker_volume_size": generate_random_docker_volume_size(), - "cluster_distro": random_string(), - "ssh_authorized_key": generate_fake_ssh_pubkey(), - "coe": coe, - "http_proxy": "http://proxy.com:%s" % generate_random_port(), - "https_proxy": "https://proxy.com:%s" % generate_random_port(), - "no_proxy": ",".join(generate_random_ip() for x in range(3)), - "network_driver": generate_random_coe_dep_network_driver(coe), - "labels": {"K1": "V1", "K2": "V2"}, + "name": data_utils.rand_name('bay'), + "coe": "swarm", + "tls_disabled": False, + "network_driver": None, + "docker_volume_size": 1, + "labels": {}, + "fixed_network": "192.168.0.0/24", + "dns_nameserver": "8.8.8.8", + "flavor_id": data_utils.rand_name('bay'), + "external_network_id": str(data_utils.rand_uuid()), + "keypair_id": data_utils.rand_name('bay'), + "image_id": data_utils.rand_name('bay') } + + data.update(kwargs) model = baymodel_model.BayModelEntity.from_dict(data) return model -def random_baymodel_name_patch_data(name=random_string()): +def baymodel_name_patch_data(name=data_utils.rand_name('bay')): """Generates random baymodel patch data :param name: name to replace in patch @@ -129,29 +122,150 @@ def random_baymodel_name_patch_data(name=random_string()): return baymodelpatch_model.BayModelPatchCollection.from_dict(data) -def random_baymodel_data_w_valid_keypair_and_image_id(): +def baymodel_data_with_valid_keypair_and_image_id(): """Generates random baymodel data with valid keypair and image :returns: BayModelEntity with generated data """ - return random_baymodel_data(keypair_id=config.Config.keypair_id, - image_id=config.Config.image_id) + return baymodel_data(keypair_id=config.Config.keypair_id, + image_id=config.Config.image_id) -def random_baymodel_data_w_valid_keypair(): +def baymodel_data_with_valid_keypair(): """Generates random baymodel data with valid keypair :returns: BayModelEntity with generated data """ - return random_baymodel_data(keypair_id=config.Config.keypair_id) + return baymodel_data(keypair_id=config.Config.keypair_id) -def random_baymodel_data_w_valid_image_id(): +def baymodel_valid_data_with_specific_coe(coe): + """Generates random baymodel data with valid keypair and image + + :param coe: coe + :returns: BayModelEntity with generated data + """ + + return baymodel_data(keypair_id=config.Config.keypair_id, + image_id=config.Config.image_id, coe=coe) + + +def baymodel_data_with_valid_image_id(): """Generates random baymodel data with valid image :returns: BayModelEntity with generated data """ - return random_baymodel_data(image_id=config.Config.image_id) + return baymodel_data(image_id=config.Config.image_id) + + +def valid_swarm_baymodel(): + """Generates a valid swarm baymodel with valid data + + :returns: BayModelEntity with generated data + """ + + return baymodel_data(image_id=config.Config.image_id, + fixed_network="192.168.0.0/24", + flavor_id=config.Config.flavor_id, public=False, + dns_nameserver="8.8.8.8", master_flavor_id=None, + keypair_id=config.Config.keypair_id, coe="swarm", + docker_volume_size=2, cluster_distro=None, + ssh_authorized_key=None, external_network_id="public", + http_proxy=None, https_proxy=None, no_proxy=None, + network_driver=None, labels={}, tls_disabled=False) + + +def bay_data(name=data_utils.rand_name('bay'), + baymodel_id=data_utils.rand_uuid(), + node_count=random_int(1, 5), discovery_url=gen_random_ip(), + bay_create_timeout=random_int(1, 30), + master_count=random_int(1, 5)): + """Generates random bay data + + BayModel_id cannot be random for the bay to be valid due to + validations for the presence of baymodel prior to baymodel + creation. + + :param name: bay name (must be unique) + :param baymodel_id: baymodel unique id (must already exist) + :param node_count: number of agents for bay + :param discovery_url: url provided for node discovery + :param bay_create_timeout: timeout in minutes for bay create + :param master_count: number of master nodes for the bay + :returns: BayEntity with generated data + """ + + data = { + "name": name, + "baymodel_id": baymodel_id, + "node_count": node_count, + "discovery_url": None, + "bay_create_timeout": bay_create_timeout, + "master_count": master_count + } + model = bay_model.BayEntity.from_dict(data) + + return model + + +def valid_bay_data(baymodel_id, name=data_utils.rand_name('bay'), node_count=2, + master_count=1, bay_create_timeout=None): + """Generates random bay data with valid + + :param baymodel_id: baymodel unique id that already exists + :param name: bay name (must be unique) + :param node_count: number of agents for bay + :returns: BayEntity with generated data + """ + + return bay_data(baymodel_id=baymodel_id, name=name, + master_count=master_count, node_count=node_count, + bay_create_timeout=bay_create_timeout) + + +def bay_name_patch_data(name=data_utils.rand_name('bay')): + """Generates random baymodel patch data + + :param name: name to replace in patch + :returns: BayPatchCollection with generated data + """ + + data = [{ + "path": "/name", + "value": name, + "op": "replace" + }] + return baypatch_model.BayPatchCollection.from_dict(data) + + +def bay_api_addy_patch_data(address='0.0.0.0'): + """Generates random bay patch data + + :param name: name to replace in patch + :returns: BayPatchCollection with generated data + """ + + data = [{ + "path": "/api_address", + "value": address, + "op": "replace" + }] + return baypatch_model.BayPatchCollection.from_dict(data) + + +def bay_node_count_patch_data(node_count=2): + """Generates random bay patch data + + :param name: name to replace in patch + :returns: BayPatchCollection with generated data + """ + + data = [{ + "path": "/node_count", + "value": node_count, + "op": "replace" + }] + return baypatch_model.BayPatchCollection.from_dict(data) diff --git a/magnum/tests/functional/common/manager.py b/magnum/tests/functional/common/manager.py index d46bcffdc1..cf38168163 100644 --- a/magnum/tests/functional/common/manager.py +++ b/magnum/tests/functional/common/manager.py @@ -13,6 +13,7 @@ from tempest import clients 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 magnum_service_client from magnum.tests.functional.common import client @@ -27,6 +28,8 @@ class Manager(clients.Manager): super(Manager, self).__init__(credentials, 'container') if request_type == 'baymodel': self.client = baymodel_client.BayModelClient(self.auth_provider) + elif request_type == 'bay': + self.client = bay_client.BayClient(self.auth_provider) elif request_type == 'service': self.client = magnum_service_client.MagnumServiceClient( self.auth_provider) diff --git a/magnum/tests/functional/common/utils.py b/magnum/tests/functional/common/utils.py index 8bba9d66d0..415bf8cf8d 100644 --- a/magnum/tests/functional/common/utils.py +++ b/magnum/tests/functional/common/utils.py @@ -82,13 +82,15 @@ def parameterized(data): def wait_for_condition(condition, interval=1, timeout=40): + start_time = time.time() end_time = time.time() + timeout while time.time() < end_time: result = condition() if result: return result time.sleep(interval) - raise Exception("Timed out after {0} seconds".format(timeout)) + raise Exception("Timed out after %s seconds. Started " + + "on %s and ended on %s" % (timeout, start_time, end_time)) def memoized(func):