Drop bay and baymodel tests

Story: 2009104
Task: 42957
Signed-off-by: Diogo Guerra <diogo.filipe.tomas.guerra@cern.ch>
Change-Id: I5f5ec97e6e01f8dc571b53fddb8cf801d6cc1888
This commit is contained in:
Diogo Guerra 2021-08-06 16:45:34 +02:00 committed by Jake Yip
parent 61c7f7b34b
commit be00e209e0
23 changed files with 3 additions and 2997 deletions

View File

@ -1,8 +0,0 @@
{
"name":"k8s",
"discovery_url":null,
"master_count":2,
"baymodel_id":"0562d357-8641-4759-8fed-8173f02c9633",
"node_count":2,
"bay_create_timeout":60
}

View File

@ -1,24 +0,0 @@
{
"bays":[
{
"status":"CREATE_COMPLETE",
"uuid":"746e779a-751a-456b-a3e9-c883d734946f",
"links":[
{
"href":"http://10.164.180.104:9511/v1/bays/746e779a-751a-456b-a3e9-c883d734946f",
"rel":"self"
},
{
"href":"http://10.164.180.104:9511/bays/746e779a-751a-456b-a3e9-c883d734946f",
"rel":"bookmark"
}
],
"stack_id":"9c6f1169-7300-4d08-a444-d2be38758719",
"master_count":1,
"baymodel_id":"0562d357-8641-4759-8fed-8173f02c9633",
"node_count":1,
"bay_create_timeout":60,
"name":"k8s"
}
]
}

View File

@ -1,32 +0,0 @@
{
"status":"CREATE_COMPLETE",
"uuid":"746e779a-751a-456b-a3e9-c883d734946f",
"links":[
{
"href":"http://10.164.180.104:9511/v1/bays/746e779a-751a-456b-a3e9-c883d734946f",
"rel":"self"
},
{
"href":"http://10.164.180.104:9511/bays/746e779a-751a-456b-a3e9-c883d734946f",
"rel":"bookmark"
}
],
"stack_id":"9c6f1169-7300-4d08-a444-d2be38758719",
"created_at":"2016-08-29T06:51:31+00:00",
"api_address":"https://172.24.4.6:6443",
"discovery_url":"https://discovery.etcd.io/cbeb580da58915809d59ee69348a84f3",
"updated_at":"2016-08-29T06:53:24+00:00",
"master_count":1,
"coe_version": "v1.2.0",
"baymodel_id":"0562d357-8641-4759-8fed-8173f02c9633",
"master_addresses":[
"172.24.4.6"
],
"node_count":1,
"node_addresses":[
"172.24.4.13"
],
"status_reason":"Stack CREATE completed successfully",
"bay_create_timeout":60,
"name":"k8s"
}

View File

@ -1,44 +0,0 @@
{
"insecure_registry":null,
"links":[
{
"href":"http://10.164.180.104:9511/v1/baymodels/085e1c4d-4f68-4bfd-8462-74b9e14e4f39",
"rel":"self"
},
{
"href":"http://10.164.180.104:9511/baymodels/085e1c4d-4f68-4bfd-8462-74b9e14e4f39",
"rel":"bookmark"
}
],
"http_proxy":"http://10.164.177.169:8080",
"updated_at":null,
"floating_ip_enabled":true,
"fixed_subnet":null,
"master_flavor_id":null,
"uuid":"085e1c4d-4f68-4bfd-8462-74b9e14e4f39",
"no_proxy":"10.0.0.0/8,172.0.0.0/8,192.0.0.0/8,localhost",
"https_proxy":"http://10.164.177.169:8080",
"tls_disabled":false,
"keypair_id":"kp",
"public":false,
"labels":{
},
"docker_volume_size":3,
"server_type":"vm",
"external_network_id":"public",
"cluster_distro":"fedora-atomic",
"image_id":"fedora-atomic-latest",
"volume_driver":"cinder",
"registry_enabled":false,
"docker_storage_driver":"devicemapper",
"apiserver_port":null,
"name":"k8s-bm2",
"created_at":"2016-08-29T02:08:08+00:00",
"network_driver":"flannel",
"fixed_network":null,
"coe":"kubernetes",
"flavor_id":"m1.small",
"master_lb_enabled":true,
"dns_nameserver":"8.8.8.8"
}

View File

@ -1,48 +0,0 @@
{
"baymodels":[
{
"insecure_registry":null,
"links":[
{
"href":"http://10.164.180.104:9511/v1/baymodels/085e1c4d-4f68-4bfd-8462-74b9e14e4f39",
"rel":"self"
},
{
"href":"http://10.164.180.104:9511/baymodels/085e1c4d-4f68-4bfd-8462-74b9e14e4f39",
"rel":"bookmark"
}
],
"http_proxy":"http://10.164.177.169:8080",
"updated_at":null,
"floating_ip_enabled":true,
"fixed_subnet":null,
"master_flavor_id":null,
"uuid":"085e1c4d-4f68-4bfd-8462-74b9e14e4f39",
"no_proxy":"10.0.0.0/8,172.0.0.0/8,192.0.0.0/8,localhost",
"https_proxy":"http://10.164.177.169:8080",
"tls_disabled":false,
"keypair_id":"kp",
"public":false,
"labels":{
},
"docker_volume_size":3,
"server_type":"vm",
"external_network_id":"public",
"cluster_distro":"fedora-atomic",
"image_id":"fedora-atomic-latest",
"volume_driver":"cinder",
"registry_enabled":false,
"docker_storage_driver":"devicemapper",
"apiserver_port":null,
"name":"k8s-bm2",
"created_at":"2016-08-29T02:08:08+00:00",
"network_driver":"flannel",
"fixed_network":null,
"coe":"kubernetes",
"flavor_id":"m1.small",
"master_lb_enabled":true,
"dns_nameserver":"8.8.8.8"
}
]
}

View File

@ -1,7 +1,6 @@
{
"cluster_uuid":"0b4b766f-1500-44b3-9804-5a6e12fe6df4",
"pem":"-----BEGIN CERTIFICATE-----\nMIICzDCCAbSgAwIBAgIQOOkVcEN7TNa9E80GoUs4xDANBgkqhkiG9w0BAQsFADAO\n-----END CERTIFICATE-----\n",
"bay_uuid":"0b4b766f-1500-44b3-9804-5a6e12fe6df4",
"links":[
{
"href":"http://10.164.180.104:9511/v1/certificates/0b4b766f-1500-44b3-9804-5a6e12fe6df4",

View File

@ -1,4 +0,0 @@
{
"bay_uuid":"0b4b766f-1500-44b3-9804-5a6e12fe6df4",
"csr":"-----BEGIN CERTIFICATE REQUEST-----\nMIIEfzCCAmcCAQAwFDESMBAGA1UEAxMJWW91ciBOYW1lMIICIjANBgkqhkiG9w0B\n-----END CERTIFICATE REQUEST-----\n"
}

View File

@ -1,15 +0,0 @@
{
"pem":"-----BEGIN CERTIFICATE-----\nMIIDxDCCAqygAwIBAgIRALgUbIjdKUy8lqErJmCxVfkwDQYJKoZIhvcNAQELBQAw\n-----END CERTIFICATE-----\n",
"bay_uuid":"0b4b766f-1500-44b3-9804-5a6e12fe6df4",
"links":[
{
"href":"http://10.164.180.104:9511/v1/certificates/0b4b766f-1500-44b3-9804-5a6e12fe6df4",
"rel":"self"
},
{
"href":"http://10.164.180.104:9511/certificates/0b4b766f-1500-44b3-9804-5a6e12fe6df4",
"rel":"bookmark"
}
],
"csr":"-----BEGIN CERTIFICATE REQUEST-----\nMIIEfzCCAmcCAQAwFDESMBAGA1UEAxMJWW91ciBOYW1lMIICIjANBgkqhkiG9w0B\n-----END CERTIFICATE REQUEST-----\n"
}

View File

@ -26,16 +26,6 @@
"rel":"bookmark"
}
],
"bays":[
{
"href":"http://10.164.180.104:9511/v1/bays/",
"rel":"self"
},
{
"href":"http://10.164.180.104:9511/bays/",
"rel":"bookmark"
}
],
"clustertemplates":[
{
"href":"http://10.164.180.104:9511/v1/clustertemplates/",
@ -66,15 +56,5 @@
"rel":"bookmark"
}
],
"baymodels":[
{
"href":"http://10.164.180.104:9511/v1/baymodels/",
"rel":"self"
},
{
"href":"http://10.164.180.104:9511/baymodels/",
"rel":"bookmark"
}
],
"id":"v1"
}
}

View File

@ -1,170 +0,0 @@
# 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.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"""
LOG = logging.getLogger(__name__)
@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, 600)
def wait_for_created_bay(self, bay_id, delete_on_error=True):
try:
utils.wait_for_condition(
lambda: self.does_bay_exist(bay_id), 10, 1800)
except Exception:
# In error state. Clean up the bay id if desired
self.LOG.error('Bay %s entered an exception state.', bay_id)
if delete_on_error:
self.LOG.error('We will attempt to delete bays now.')
self.delete_bay(bay_id)
self.wait_for_bay_to_delete(bay_id)
raise
def wait_for_final_state(self, bay_id):
utils.wait_for_condition(
lambda: self.is_bay_in_final_state(bay_id), 10, 1800)
def is_bay_in_final_state(self, bay_id):
try:
resp, model = self.get_bay(bay_id)
if model.status in ['CREATED', 'CREATE_COMPLETE',
'ERROR', 'CREATE_FAILED']:
self.LOG.info('Bay %s succeeded.', bay_id)
return True
else:
return False
except exceptions.NotFound:
self.LOG.warning('Bay %s is not found.', bay_id)
return False
def does_bay_exist(self, bay_id):
try:
resp, model = self.get_bay(bay_id)
if model.status in ['CREATED', 'CREATE_COMPLETE']:
self.LOG.info('Bay %s is created.', bay_id)
return True
elif model.status in ['ERROR', 'CREATE_FAILED']:
self.LOG.error('Bay %s is in fail state.', bay_id)
raise exceptions.ServerFault(
"Got into an error condition: %s for %s",
(model.status, bay_id))
else:
return False
except exceptions.NotFound:
self.LOG.warning('Bay %s is not found.', bay_id)
return False
def does_bay_not_exist(self, bay_id):
try:
self.get_bay(bay_id)
except exceptions.NotFound:
self.LOG.warning('Bay %s is not found.', bay_id)
return True
return False

View File

@ -1,105 +0,0 @@
# 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 baymodel_model
from magnum.tests.functional.common import client
class BayModelClient(client.MagnumClient):
"""Encapsulates REST calls and maps JSON to/from models"""
@classmethod
def baymodels_uri(cls, filters=None):
"""Construct baymodels uri with optional filters
:param filters: Optional k:v dict that's converted to url query
:returns: url string
"""
url = "/baymodels"
if filters:
url = cls.add_filters(url, filters)
return url
@classmethod
def baymodel_uri(cls, baymodel_id):
"""Construct baymodel uri
:param baymodel_id: baymodel uuid or name
:returns: url string
"""
return "{0}/{1}".format(cls.baymodels_uri(), baymodel_id)
def list_baymodels(self, filters=None, **kwargs):
"""Makes GET /baymodels request and returns BayModelCollection
Abstracts REST call to return all baymodels
:param filters: Optional k:v dict that's converted to url query
:returns: response object and BayModelCollection object
"""
resp, body = self.get(self.baymodels_uri(filters), **kwargs)
return self.deserialize(resp, body, baymodel_model.BayModelCollection)
def get_baymodel(self, baymodel_id, **kwargs):
"""Makes GET /baymodel request and returns BayModelEntity
Abstracts REST call to return a single baymodel based on uuid or name
:param baymodel_id: baymodel uuid or name
:returns: response object and BayModelCollection object
"""
resp, body = self.get(self.baymodel_uri(baymodel_id))
return self.deserialize(resp, body, baymodel_model.BayModelEntity)
def post_baymodel(self, model, **kwargs):
"""Makes POST /baymodel request and returns BayModelEntity
Abstracts REST call to create new baymodel
:param model: BayModelEntity
:returns: response object and BayModelEntity object
"""
resp, body = self.post(
self.baymodels_uri(),
body=model.to_json(), **kwargs)
return self.deserialize(resp, body, baymodel_model.BayModelEntity)
def patch_baymodel(self, baymodel_id, baymodelpatch_listmodel, **kwargs):
"""Makes PATCH /baymodel request and returns BayModelEntity
Abstracts REST call to update baymodel attributes
:param baymodel_id: UUID of baymodel
:param baymodelpatch_listmodel: BayModelPatchCollection
:returns: response object and BayModelEntity object
"""
resp, body = self.patch(
self.baymodel_uri(baymodel_id),
body=baymodelpatch_listmodel.to_json(), **kwargs)
return self.deserialize(resp, body, baymodel_model.BayModelEntity)
def delete_baymodel(self, baymodel_id, **kwargs):
"""Makes DELETE /baymodel request and returns response object
Abstracts REST call to delete baymodel based on uuid or name
:param baymodel_id: UUID or name of baymodel
:returns: response object
"""
return self.delete(self.baymodel_uri(baymodel_id), **kwargs)

View File

@ -1,30 +0,0 @@
# 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

View File

@ -1,30 +0,0 @@
# 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 BayModelData(models.BaseModel):
"""Data that encapsulates baymodel attributes"""
pass
class BayModelEntity(models.EntityModel):
"""Entity Model that represents a single instance of BayModelData"""
ENTITY_NAME = 'baymodel'
MODEL_TYPE = BayModelData
class BayModelCollection(models.CollectionModel):
"""Collection Model that represents a list of BayModelData objects"""
COLLECTION_NAME = 'baymodellists'
MODEL_TYPE = BayModelData

View File

@ -1,76 +0,0 @@
# 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_serialization import jsonutils
from magnum.tests.functional.common import models
class BayModelPatchData(models.BaseModel):
"""Data that encapsulates baymodelpatch attributes"""
pass
class BayModelPatchEntity(models.EntityModel):
"""Entity Model that represents a single instance of BayModelPatchData"""
ENTITY_NAME = 'baymodelpatch'
MODEL_TYPE = BayModelPatchData
class BayModelPatchCollection(models.CollectionModel):
"""Collection Model that represents a list of BayModelPatchData objects"""
MODEL_TYPE = BayModelPatchData
COLLECTION_NAME = 'baymodelpatchlist'
def to_json(self):
"""Converts BayModelPatchCollection 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, BayModelPatchCollection.COLLECTION_NAME)
collection = []
for d in data:
collection.append(d.to_dict())
return jsonutils.dumps(collection)
@classmethod
def from_dict(cls, data):
"""Converts dict to BayModelPatchData
Converts data dict to list of BayModelPatchData 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

@ -1,76 +0,0 @@
# 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_serialization import jsonutils
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 jsonutils.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

View File

@ -17,10 +17,6 @@ import struct
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.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
@ -91,236 +87,6 @@ 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
validations for the presence of keypair and image id prior to baymodel
creation.
:param keypair_id: keypair name
:param image_id: image id or name
:returns: BayModelEntity with generated data
"""
data = {
"name": data_utils.rand_name('bay'),
"coe": "swarm-mode",
"tls_disabled": False,
"network_driver": None,
"volume_driver": None,
"labels": {},
"public": False,
"dns_nameserver": "8.8.8.8",
"flavor_id": data_utils.rand_name('bay'),
"master_flavor_id": data_utils.rand_name('bay'),
"external_network_id": config.Config.nic_id,
"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 baymodel_replace_patch_data(path, value=data_utils.rand_name('bay')):
"""Generates random baymodel patch data
:param path: path to replace
:param value: value to replace in patch
:returns: BayModelPatchCollection with generated data
"""
data = [{
"path": path,
"value": value,
"op": "replace"
}]
return baymodelpatch_model.BayModelPatchCollection.from_dict(data)
def baymodel_remove_patch_data(path):
"""Generates baymodel patch data by removing value
:param path: path to remove
:returns: BayModelPatchCollection with generated data
"""
data = [{
"path": path,
"op": "remove"
}]
return baymodelpatch_model.BayModelPatchCollection.from_dict(data)
def baymodel_data_with_valid_keypair_image_flavor():
"""Generates random baymodel data with valid keypair,image and flavor
:returns: BayModelEntity with generated data
"""
return baymodel_data(keypair_id=config.Config.keypair_id,
image_id=config.Config.image_id,
flavor_id=config.Config.flavor_id,
master_flavor_id=config.Config.master_flavor_id)
def baymodel_data_with_missing_image():
"""Generates random baymodel data with missing image
:returns: BayModelEntity with generated data
"""
return baymodel_data(keypair_id=config.Config.keypair_id,
flavor_id=config.Config.flavor_id,
master_flavor_id=config.Config.master_flavor_id)
def baymodel_data_with_missing_flavor():
"""Generates random baymodel data with missing flavor
:returns: BayModelEntity with generated data
"""
return baymodel_data(keypair_id=config.Config.keypair_id,
image_id=config.Config.image_id)
def baymodel_data_with_missing_keypair():
"""Generates random baymodel data with missing keypair
:returns: BayModelEntity with generated data
"""
return baymodel_data(image_id=config.Config.image_id,
flavor_id=config.Config.flavor_id,
master_flavor_id=config.Config.master_flavor_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 valid_swarm_mode_baymodel(is_public=False):
"""Generates a valid swarm mode baymodel with valid data
:returns: BayModelEntity with generated data
"""
return baymodel_data(image_id=config.Config.image_id,
flavor_id=config.Config.flavor_id, public=is_public,
dns_nameserver=config.Config.dns_nameserver,
master_flavor_id=config.Config.master_flavor_id,
keypair_id=config.Config.keypair_id, coe="swarm-mode",
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 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=1,
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)
def cert_data(cluster_uuid, csr_data):
data = {
"cluster_uuid": cluster_uuid,

View File

@ -13,8 +13,6 @@
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 cert_client
from magnum.tests.functional.api.v1.clients import cluster_client
from magnum.tests.functional.api.v1.clients import cluster_template_client
@ -32,11 +30,7 @@ class Manager(clients.Manager):
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(auth)
elif request_type == 'bay':
self.client = bay_client.BayClient(auth)
elif request_type == 'cert':
if request_type == 'cert':
self.client = cert_client.CertClient(auth)
elif request_type == 'cluster_template':
self.client = cluster_template_client.ClusterTemplateClient(auth)

View File

@ -58,14 +58,6 @@ class TestRootController(api_base.FunctionalTest):
u'rel': u'self'},
{u'href': u'http://localhost/stats/',
u'rel': u'bookmark'}],
u'bays': [{u'href': u'http://localhost/v1/bays/',
u'rel': u'self'},
{u'href': u'http://localhost/bays/',
u'rel': u'bookmark'}],
u'baymodels': [{u'href': u'http://localhost/v1/baymodels/',
u'rel': u'self'},
{u'href': u'http://localhost/baymodels/',
u'rel': u'bookmark'}],
u'clusters': [{u'href': u'http://localhost/v1/clusters/',
u'rel': u'self'},
{u'href': u'http://localhost/clusters/',

View File

@ -1,971 +0,0 @@
# 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 datetime
from unittest import mock
from oslo_config import cfg
from oslo_utils import timeutils
from oslo_utils import uuidutils
from wsme import types as wtypes
from magnum.api import attr_validator
from magnum.api.controllers.v1 import bay as api_bay
from magnum.common import exception
from magnum.conductor import api as rpcapi
from magnum import objects
from magnum.tests import base
from magnum.tests.unit.api import base as api_base
from magnum.tests.unit.api import utils as apiutils
from magnum.tests.unit.db import utils as db_utils
from magnum.tests.unit.objects import utils as obj_utils
class TestBayObject(base.TestCase):
def test_bay_init(self):
bay_dict = apiutils.bay_post_data(baymodel_id=None)
del bay_dict['node_count']
del bay_dict['master_count']
del bay_dict['bay_create_timeout']
bay = api_bay.Bay(**bay_dict)
self.assertEqual(1, bay.node_count)
self.assertEqual(1, bay.master_count)
self.assertEqual(60, bay.bay_create_timeout)
# test unset value for baymodel_id
bay.baymodel_id = wtypes.Unset
self.assertEqual(wtypes.Unset, bay.baymodel_id)
# test backwards compatibility of bay fields with new objects
bay_dict['bay_create_timeout'] = 15
bay_dict['bay_faults'] = {'testfault': 'fault'}
bay = api_bay.Bay(**bay_dict)
self.assertEqual(15, bay.bay_create_timeout)
self.assertEqual(15, bay.create_timeout)
self.assertIn('testfault', bay.bay_faults)
self.assertIn('testfault', bay.faults)
def test_as_dict_faults(self):
bay_dict = apiutils.bay_post_data(baymodel_id=None)
del bay_dict['node_count']
del bay_dict['master_count']
del bay_dict['bay_create_timeout']
bay = api_bay.Bay(**bay_dict)
bay.bay_faults = {'testfault': 'fault'}
dict = bay.as_dict()
self.assertEqual({'testfault': 'fault'}, dict['faults'])
class TestListBay(api_base.FunctionalTest):
_bay_attrs = ("name", "baymodel_id", "node_count", "status",
"master_count", "stack_id", "bay_create_timeout")
_expand_bay_attrs = ("name", "baymodel_id", "node_count", "status",
"api_address", "discovery_url", "node_addresses",
"master_count", "master_addresses", "stack_id",
"bay_create_timeout", "status_reason")
def setUp(self):
super(TestListBay, self).setUp()
obj_utils.create_test_cluster_template(self.context)
def test_empty(self):
response = self.get_json('/bays')
self.assertEqual([], response['bays'])
def test_one(self):
bay = obj_utils.create_test_cluster(self.context)
response = self.get_json('/bays')
self.assertEqual(bay.uuid, response['bays'][0]["uuid"])
self._verify_attrs(self._bay_attrs, response['bays'][0])
# Verify atts that should not appear from bay's get_all response
none_attrs = set(self._expand_bay_attrs) - set(self._bay_attrs)
self._verify_attrs(none_attrs, response['bays'][0], positive=False)
def test_get_one(self):
bay = obj_utils.create_test_cluster(self.context)
response = self.get_json('/bays/%s' % bay['uuid'])
self.assertEqual(bay.uuid, response['uuid'])
self._verify_attrs(self._expand_bay_attrs, response)
@mock.patch('magnum.common.clients.OpenStackClients.heat')
def test_get_one_failed_bay(self, mock_heat):
fake_resources = mock.MagicMock()
fake_resources.resource_name = 'fake_name'
fake_resources.resource_status_reason = 'fake_reason'
ht = mock.MagicMock()
ht.resources.list.return_value = [fake_resources]
mock_heat.return_value = ht
bay = obj_utils.create_test_cluster(self.context,
status='CREATE_FAILED')
response = self.get_json('/bays/%s' % bay['uuid'])
self.assertEqual(bay.uuid, response['uuid'])
self.assertEqual({'fake_name': 'fake_reason'}, response['bay_faults'])
@mock.patch('magnum.common.clients.OpenStackClients.heat')
def test_get_one_failed_bay_heatclient_exception(self, mock_heat):
mock_heat.resources.list.side_effect = Exception('fake')
bay = obj_utils.create_test_cluster(self.context,
status='CREATE_FAILED')
response = self.get_json('/bays/%s' % bay['uuid'])
self.assertEqual(bay.uuid, response['uuid'])
self.assertEqual({}, response['bay_faults'])
def test_get_one_by_name(self):
bay = obj_utils.create_test_cluster(self.context)
response = self.get_json('/bays/%s' % bay['name'])
self.assertEqual(bay.uuid, response['uuid'])
self._verify_attrs(self._expand_bay_attrs, response)
def test_get_one_by_name_not_found(self):
response = self.get_json(
'/bays/not_found',
expect_errors=True)
self.assertEqual(404, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
def test_get_one_by_name_multiple_bay(self):
obj_utils.create_test_cluster(self.context, name='test_bay',
uuid=uuidutils.generate_uuid())
obj_utils.create_test_cluster(self.context, name='test_bay',
uuid=uuidutils.generate_uuid())
response = self.get_json('/bays/test_bay', expect_errors=True)
self.assertEqual(409, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
def test_get_all_with_pagination_marker(self):
bay_list = []
for id_ in range(4):
bay = obj_utils.create_test_cluster(self.context, id=id_,
uuid=uuidutils.generate_uuid())
bay_list.append(bay)
response = self.get_json('/bays?limit=3&marker=%s'
% bay_list[2].uuid)
self.assertEqual(1, len(response['bays']))
self.assertEqual(bay_list[-1].uuid, response['bays'][0]['uuid'])
def test_detail(self):
bay = obj_utils.create_test_cluster(self.context)
response = self.get_json('/bays/detail')
self.assertEqual(bay.uuid, response['bays'][0]["uuid"])
self._verify_attrs(self._expand_bay_attrs, response['bays'][0])
def test_detail_with_pagination_marker(self):
bay_list = []
for id_ in range(4):
bay = obj_utils.create_test_cluster(self.context, id=id_,
uuid=uuidutils.generate_uuid())
bay_list.append(bay)
response = self.get_json('/bays/detail?limit=3&marker=%s'
% bay_list[2].uuid)
self.assertEqual(1, len(response['bays']))
self.assertEqual(bay_list[-1].uuid, response['bays'][0]['uuid'])
self._verify_attrs(self._expand_bay_attrs, response['bays'][0])
def test_detail_against_single(self):
bay = obj_utils.create_test_cluster(self.context)
response = self.get_json('/bays/%s/detail' % bay['uuid'],
expect_errors=True)
self.assertEqual(404, response.status_int)
def test_many(self):
bm_list = []
for id_ in range(5):
bay = obj_utils.create_test_cluster(self.context, id=id_,
uuid=uuidutils.generate_uuid())
bm_list.append(bay.uuid)
response = self.get_json('/bays')
self.assertEqual(len(bm_list), len(response['bays']))
uuids = [b['uuid'] for b in response['bays']]
self.assertEqual(sorted(bm_list), sorted(uuids))
def test_links(self):
uuid = uuidutils.generate_uuid()
obj_utils.create_test_cluster(self.context, id=1, uuid=uuid)
response = self.get_json('/bays/%s' % uuid)
self.assertIn('links', response.keys())
self.assertEqual(2, len(response['links']))
self.assertIn(uuid, response['links'][0]['href'])
for link in response['links']:
bookmark = link['rel'] == 'bookmark'
self.assertTrue(self.validate_link(link['href'],
bookmark=bookmark))
def test_collection_links(self):
for id_ in range(5):
obj_utils.create_test_cluster(self.context, id=id_,
uuid=uuidutils.generate_uuid())
response = self.get_json('/bays/?limit=3')
self.assertEqual(3, len(response['bays']))
next_marker = response['bays'][-1]['uuid']
self.assertIn(next_marker, response['next'])
def test_collection_links_default_limit(self):
cfg.CONF.set_override('max_limit', 3, 'api')
for id_ in range(5):
obj_utils.create_test_cluster(self.context, id=id_,
uuid=uuidutils.generate_uuid())
response = self.get_json('/bays')
self.assertEqual(3, len(response['bays']))
next_marker = response['bays'][-1]['uuid']
self.assertIn(next_marker, response['next'])
class TestPatch(api_base.FunctionalTest):
def setUp(self):
super(TestPatch, self).setUp()
self.cluster_template = obj_utils.create_test_cluster_template(
self.context)
self.bay = obj_utils.create_test_cluster(self.context,
name='bay_example_A',
node_count=3)
p = mock.patch.object(rpcapi.API, 'cluster_update')
self.mock_bay_update = p.start()
self.mock_bay_update.side_effect = self._simulate_rpc_bay_update
self.addCleanup(p.stop)
def _simulate_rpc_bay_update(self, bay, node_count, rollback=False):
bay.status = 'UPDATE_IN_PROGRESS'
bay.save()
default_ng_worker = bay.default_ng_worker
default_ng_worker.node_count = node_count
default_ng_worker.save()
return bay
@mock.patch('oslo_utils.timeutils.utcnow')
def test_replace_ok(self, mock_utcnow):
new_node_count = 4
test_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = test_time
response = self.patch_json('/bays/%s' % self.bay.uuid,
[{'path': '/node_count',
'value': new_node_count,
'op': 'replace'}])
self.assertEqual('application/json', response.content_type)
self.assertEqual(200, response.status_code)
response = self.get_json('/bays/%s' % self.bay.uuid)
self.assertEqual(new_node_count, response['node_count'])
return_updated_at = timeutils.parse_isotime(
response['updated_at']).replace(tzinfo=None)
self.assertEqual(test_time, return_updated_at)
# Assert nothing else was changed
self.assertEqual(self.bay.uuid, response['uuid'])
self.assertEqual(self.bay.cluster_template_id, response['baymodel_id'])
@mock.patch('oslo_utils.timeutils.utcnow')
def test_replace_ok_by_name(self, mock_utcnow):
new_node_count = 4
test_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = test_time
response = self.patch_json('/bays/%s' % self.bay.name,
[{'path': '/node_count',
'value': new_node_count,
'op': 'replace'}])
self.assertEqual('application/json', response.content_type)
self.assertEqual(200, response.status_code)
response = self.get_json('/bays/%s' % self.bay.uuid)
self.assertEqual(new_node_count, response['node_count'])
return_updated_at = timeutils.parse_isotime(
response['updated_at']).replace(tzinfo=None)
self.assertEqual(test_time, return_updated_at)
# Assert nothing else was changed
self.assertEqual(self.bay.uuid, response['uuid'])
self.assertEqual(self.bay.cluster_template_id, response['baymodel_id'])
@mock.patch('oslo_utils.timeutils.utcnow')
def test_replace_ok_by_name_not_found(self, mock_utcnow):
name = 'not_found'
test_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = test_time
response = self.patch_json('/bays/%s' % name,
[{'path': '/name', 'value': name,
'op': 'replace'}],
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(404, response.status_code)
def test_replace_baymodel_id_failed(self):
cluster_template = obj_utils.create_test_cluster_template(
self.context,
uuid=uuidutils.generate_uuid())
response = self.patch_json('/bays/%s' % self.bay.uuid,
[{'path': '/baymodel_id',
'value': cluster_template.uuid,
'op': 'replace'}],
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_code)
self.assertTrue(response.json['errors'])
@mock.patch('oslo_utils.timeutils.utcnow')
def test_replace_ok_by_name_multiple_bay(self, mock_utcnow):
test_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = test_time
obj_utils.create_test_cluster(self.context, name='test_bay',
uuid=uuidutils.generate_uuid())
obj_utils.create_test_cluster(self.context, name='test_bay',
uuid=uuidutils.generate_uuid())
response = self.patch_json('/bays/test_bay',
[{'path': '/name', 'value': 'test_bay',
'op': 'replace'}],
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(409, response.status_code)
def test_replace_non_existent_baymodel_id(self):
response = self.patch_json('/bays/%s' % self.bay.uuid,
[{'path': '/baymodel_id',
'value': uuidutils.generate_uuid(),
'op': 'replace'}],
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_code)
self.assertTrue(response.json['errors'])
def test_replace_invalid_node_count(self):
response = self.patch_json('/bays/%s' % self.bay.uuid,
[{'path': '/node_count', 'value': -1,
'op': 'replace'}],
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_code)
self.assertTrue(response.json['errors'])
def test_replace_non_existent_bay(self):
response = self.patch_json('/bays/%s' % uuidutils.generate_uuid(),
[{'path': '/name',
'value': 'bay_example_B',
'op': 'replace'}],
expect_errors=True)
self.assertEqual(404, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
def test_replace_bay_name_failed(self):
response = self.patch_json('/bays/%s' % self.bay.uuid,
[{'path': '/name',
'value': 'bay_example_B',
'op': 'replace'}],
expect_errors=True)
self.assertEqual(400, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
def test_add_non_existent_property(self):
response = self.patch_json(
'/bays/%s' % self.bay.uuid,
[{'path': '/foo', 'value': 'bar', 'op': 'add'}],
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
@mock.patch.object(rpcapi.API, 'cluster_update_async')
def test_update_bay_async(self, mock_update):
response = self.patch_json(
'/bays/%s' % self.bay.name,
[{'path': '/node_count', 'value': 4,
'op': 'replace'}],
headers={'OpenStack-API-Version': 'container-infra 1.2'})
self.assertEqual(202, response.status_code)
@mock.patch.object(rpcapi.API, 'cluster_update_async')
def test_update_bay_with_rollback_enabled(self, mock_update):
node_count = 4
response = self.patch_json(
'/bays/%s/?rollback=True' % self.bay.name,
[{'path': '/node_count', 'value': node_count,
'op': 'replace'}],
headers={'OpenStack-API-Version': 'container-infra 1.3'})
mock_update.assert_called_once_with(mock.ANY, node_count,
rollback=True)
self.assertEqual(202, response.status_code)
def test_remove_ok(self):
response = self.get_json('/bays/%s' % self.bay.uuid)
self.assertIsNotNone(response['name'])
response = self.patch_json('/bays/%s' % self.bay.uuid,
[{'path': '/node_count', 'op': 'remove'}])
self.assertEqual('application/json', response.content_type)
self.assertEqual(200, response.status_code)
response = self.get_json('/bays/%s' % self.bay.uuid)
# only allow node_count for bay, and default value is 1
self.assertEqual(1, response['node_count'])
# Assert nothing else was changed
self.assertEqual(self.bay.uuid, response['uuid'])
self.assertEqual(self.bay.cluster_template_id, response['baymodel_id'])
self.assertEqual(self.bay.name, response['name'])
self.assertEqual(self.bay.master_count, response['master_count'])
def test_remove_mandatory_property_fail(self):
mandatory_properties = ('/uuid', '/baymodel_id')
for p in mandatory_properties:
response = self.patch_json('/bays/%s' % self.bay.uuid,
[{'path': p, 'op': 'remove'}],
expect_errors=True)
self.assertEqual(400, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
def test_remove_non_existent_property(self):
response = self.patch_json(
'/bays/%s' % self.bay.uuid,
[{'path': '/non-existent', 'op': 'remove'}],
expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_code)
self.assertTrue(response.json['errors'])
class TestPost(api_base.FunctionalTest):
def setUp(self):
super(TestPost, self).setUp()
self.cluster_template = obj_utils.create_test_cluster_template(
self.context)
p = mock.patch.object(rpcapi.API, 'cluster_create')
self.mock_bay_create = p.start()
self.mock_bay_create.side_effect = self._simulate_rpc_bay_create
self.addCleanup(p.stop)
p = mock.patch.object(attr_validator, 'validate_os_resources')
self.mock_valid_os_res = p.start()
self.addCleanup(p.stop)
def _simulate_rpc_bay_create(self, bay, master_count, node_count,
bay_create_timeout):
bay.create()
db_utils.create_nodegroups_for_cluster(
cluster_id=bay.uuid, node_count=node_count,
master_count=master_count)
return bay
@mock.patch('oslo_utils.timeutils.utcnow')
def test_create_bay(self, mock_utcnow):
bdict = apiutils.bay_post_data()
test_time = datetime.datetime(2000, 1, 1, 0, 0)
mock_utcnow.return_value = test_time
response = self.post_json('/bays', bdict)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
# Check location header
self.assertIsNotNone(response.location)
self.assertTrue(uuidutils.is_uuid_like(response.json['uuid']))
self.assertNotIn('updated_at', response.json.keys)
return_created_at = timeutils.parse_isotime(
response.json['created_at']).replace(tzinfo=None)
self.assertEqual(test_time, return_created_at)
self.assertEqual(bdict['bay_create_timeout'],
response.json['bay_create_timeout'])
def test_create_bay_set_project_id_and_user_id(self):
bdict = apiutils.bay_post_data()
def _simulate_rpc_bay_create(bay, node_count, master_count,
bay_create_timeout):
self.assertEqual(self.context.project_id, bay.project_id)
self.assertEqual(self.context.user_id, bay.user_id)
bay.create()
db_utils.create_nodegroups_for_cluster(
cluster_id=bay.uuid, node_count=node_count,
master_count=master_count)
return bay
self.mock_bay_create.side_effect = _simulate_rpc_bay_create
self.post_json('/bays', bdict)
def test_create_bay_doesnt_contain_id(self):
with mock.patch.object(self.dbapi, 'create_cluster',
wraps=self.dbapi.create_cluster) as cc_mock:
bdict = apiutils.bay_post_data(name='bay_example_A')
response = self.post_json('/bays', bdict)
self.assertEqual(bdict['name'], response.json['name'])
cc_mock.assert_called_once_with(mock.ANY)
# Check that 'id' is not in first arg of positional args
self.assertNotIn('id', cc_mock.call_args[0][0])
def test_create_bay_generate_uuid(self):
bdict = apiutils.bay_post_data()
del bdict['uuid']
response = self.post_json('/bays', bdict)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
self.assertEqual(bdict['name'], response.json['name'])
self.assertTrue(uuidutils.is_uuid_like(response.json['uuid']))
def test_create_bay_no_baymodel_id(self):
bdict = apiutils.bay_post_data()
del bdict['baymodel_id']
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
def test_create_bay_with_non_existent_baymodel_id(self):
bdict = apiutils.bay_post_data(baymodel_id=uuidutils.generate_uuid())
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
def test_create_bay_with_baymodel_name(self):
bdict = apiutils.bay_post_data(baymodel_id=self.cluster_template.name)
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
def test_create_bay_with_node_count_zero(self):
bdict = apiutils.bay_post_data()
bdict['node_count'] = 0
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
def test_create_bay_with_node_count_negative(self):
bdict = apiutils.bay_post_data()
bdict['node_count'] = -1
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
def test_create_bay_with_no_node_count(self):
bdict = apiutils.bay_post_data()
del bdict['node_count']
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
self.assertEqual(1, response.json['node_count'])
def test_create_bay_with_master_count_zero(self):
bdict = apiutils.bay_post_data()
bdict['master_count'] = 0
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
def test_create_bay_with_no_master_count(self):
bdict = apiutils.bay_post_data()
del bdict['master_count']
response = self.post_json('/bays', bdict)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
self.assertEqual(1, response.json['master_count'])
def test_create_bay_with_invalid_long_name(self):
bdict = apiutils.bay_post_data(name='x' * 243)
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
def test_create_bay_with_invalid_integer_name(self):
bdict = apiutils.bay_post_data(name='123456')
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
def test_create_bay_with_invalid_integer_str_name(self):
bdict = apiutils.bay_post_data(name='123456test_bay')
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
def test_create_bay_with_hyphen_invalid_at_start_name(self):
bdict = apiutils.bay_post_data(name='-test_bay')
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
def test_create_bay_with_period_invalid_at_start_name(self):
bdict = apiutils.bay_post_data(name='.test_bay')
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
def test_create_bay_with_underscore_invalid_at_start_name(self):
bdict = apiutils.bay_post_data(name='_test_bay')
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
def test_create_bay_with_valid_str_int_name(self):
bdict = apiutils.bay_post_data(name='test_bay123456')
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
self.assertEqual(response.json['name'], bdict['name'])
def test_create_bay_with_hyphen_valid_name(self):
bdict = apiutils.bay_post_data(name='test-bay')
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
self.assertEqual(response.json['name'], bdict['name'])
def test_create_bay_with_period_valid_name(self):
bdict = apiutils.bay_post_data(name='test.bay')
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
self.assertEqual(response.json['name'], bdict['name'])
def test_create_bay_with_period_at_end_valid_name(self):
bdict = apiutils.bay_post_data(name='testbay.')
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
self.assertEqual(response.json['name'], bdict['name'])
def test_create_bay_with_hyphen_at_end_valid_name(self):
bdict = apiutils.bay_post_data(name='testbay-')
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
self.assertEqual(response.json['name'], bdict['name'])
def test_create_bay_with_underscore_at_end_valid_name(self):
bdict = apiutils.bay_post_data(name='testbay_')
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
self.assertEqual(response.json['name'], bdict['name'])
def test_create_bay_with_mix_special_char_valid_name(self):
bdict = apiutils.bay_post_data(name='test.-_bay')
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
self.assertEqual(response.json['name'], bdict['name'])
def test_create_bay_with_capital_letter_start_valid_name(self):
bdict = apiutils.bay_post_data(name='Testbay')
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
self.assertEqual(response.json['name'], bdict['name'])
def test_create_bay_with_invalid_empty_name(self):
bdict = apiutils.bay_post_data(name='')
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
def test_create_bay_without_name(self):
bdict = apiutils.bay_post_data()
del bdict['name']
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
self.assertIsNotNone(response.json['name'])
def test_create_bay_with_timeout_none(self):
bdict = apiutils.bay_post_data()
bdict['bay_create_timeout'] = None
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
def test_create_bay_with_no_timeout(self):
def _simulate_rpc_bay_create(bay, node_count, master_count,
bay_create_timeout):
self.assertEqual(60, bay_create_timeout)
bay.create()
db_utils.create_nodegroups_for_cluster(
cluster_id=bay.uuid, node_count=node_count,
master_count=master_count)
return bay
self.mock_bay_create.side_effect = _simulate_rpc_bay_create
bdict = apiutils.bay_post_data()
del bdict['bay_create_timeout']
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
def test_create_bay_with_timeout_negative(self):
bdict = apiutils.bay_post_data()
bdict['bay_create_timeout'] = -1
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
self.assertTrue(response.json['errors'])
def test_create_bay_with_timeout_zero(self):
bdict = apiutils.bay_post_data()
bdict['bay_create_timeout'] = 0
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
def test_create_bay_with_invalid_flavor(self):
bdict = apiutils.bay_post_data()
self.mock_valid_os_res.side_effect = exception.FlavorNotFound(
'test-flavor')
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertTrue(self.mock_valid_os_res.called)
self.assertEqual(400, response.status_int)
def test_create_bay_with_invalid_ext_network(self):
bdict = apiutils.bay_post_data()
self.mock_valid_os_res.side_effect = exception.ExternalNetworkNotFound(
'test-net')
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertTrue(self.mock_valid_os_res.called)
self.assertEqual(400, response.status_int)
def test_create_bay_with_invalid_keypair(self):
bdict = apiutils.bay_post_data()
self.mock_valid_os_res.side_effect = exception.KeyPairNotFound(
'test-key')
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertTrue(self.mock_valid_os_res.called)
self.assertEqual(404, response.status_int)
def test_create_bay_with_nonexist_image(self):
bdict = apiutils.bay_post_data()
self.mock_valid_os_res.side_effect = exception.ImageNotFound(
'test-img')
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertTrue(self.mock_valid_os_res.called)
self.assertEqual(400, response.status_int)
def test_create_bay_with_multi_images_same_name(self):
bdict = apiutils.bay_post_data()
self.mock_valid_os_res.side_effect = exception.Conflict('test-img')
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertTrue(self.mock_valid_os_res.called)
self.assertEqual(409, response.status_int)
def test_create_bay_with_on_os_distro_image(self):
bdict = apiutils.bay_post_data()
self.mock_valid_os_res.side_effect = exception.OSDistroFieldNotFound(
'img')
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertTrue(self.mock_valid_os_res.called)
self.assertEqual(400, response.status_int)
def test_create_bay_with_no_lb_one_node(self):
cluster_template = obj_utils.create_test_cluster_template(
self.context, name='foo', uuid='foo', master_lb_enabled=False)
bdict = apiutils.bay_post_data(baymodel_id=cluster_template.name,
master_count=1)
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
def test_create_bay_with_no_lb_multi_node(self):
cluster_template = obj_utils.create_test_cluster_template(
self.context, name='foo', uuid='foo', master_lb_enabled=False)
bdict = apiutils.bay_post_data(baymodel_id=cluster_template.name,
master_count=3, master_lb_enabled=False)
response = self.post_json('/bays', bdict, expect_errors=True)
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
def test_create_bay_with_docker_volume_size(self):
bdict = apiutils.bay_post_data()
bdict['docker_volume_size'] = 3
response = self.post_json('/bays', bdict)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
bay, timeout = self.mock_bay_create.call_args
self.assertEqual(3, bay[0].docker_volume_size)
def test_create_bay_without_docker_volume_size(self):
bdict = apiutils.bay_post_data()
# Remove the default docker_volume_size from the bay dict.
del bdict['docker_volume_size']
response = self.post_json('/bays', bdict)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
bay, timeout = self.mock_bay_create.call_args
# Verify docker_volume_size from BayModel is used
self.assertEqual(20, bay[0].docker_volume_size)
class TestDelete(api_base.FunctionalTest):
def setUp(self):
super(TestDelete, self).setUp()
self.cluster_template = obj_utils.create_test_cluster_template(
self.context)
self.bay = obj_utils.create_test_cluster(self.context)
p = mock.patch.object(rpcapi.API, 'cluster_delete')
self.mock_bay_delete = p.start()
self.mock_bay_delete.side_effect = self._simulate_rpc_bay_delete
self.addCleanup(p.stop)
def _simulate_rpc_bay_delete(self, bay_uuid):
bay = objects.Cluster.get_by_uuid(self.context, bay_uuid)
bay.destroy()
ngs = objects.NodeGroup.list(self.context, bay_uuid)
for ng in ngs:
ng.destroy()
def test_delete_bay(self):
self.delete('/bays/%s' % self.bay.uuid)
response = self.get_json('/bays/%s' % self.bay.uuid,
expect_errors=True)
self.assertEqual(404, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
def test_delete_bay_not_found(self):
uuid = uuidutils.generate_uuid()
response = self.delete('/bays/%s' % uuid, expect_errors=True)
self.assertEqual(404, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
def test_delete_bay_with_name_not_found(self):
response = self.delete('/bays/not_found', expect_errors=True)
self.assertEqual(404, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
def test_delete_bay_with_name(self):
response = self.delete('/bays/%s' % self.bay.name,
expect_errors=True)
self.assertEqual(204, response.status_int)
def test_delete_multiple_bay_by_name(self):
obj_utils.create_test_cluster(self.context, name='test_bay',
uuid=uuidutils.generate_uuid())
obj_utils.create_test_cluster(self.context, name='test_bay',
uuid=uuidutils.generate_uuid())
response = self.delete('/bays/test_bay', expect_errors=True)
self.assertEqual(409, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(response.json['errors'])
class TestBayPolicyEnforcement(api_base.FunctionalTest):
def setUp(self):
super(TestBayPolicyEnforcement, self).setUp()
obj_utils.create_test_cluster_template(self.context)
def _common_policy_check(self, rule, func, *arg, **kwarg):
self.policy.set_rules({rule: "project:non_fake"})
response = func(*arg, **kwarg)
self.assertEqual(403, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(
"Policy doesn't allow %s to be performed." % rule,
response.json['errors'][0]['detail'])
def test_policy_disallow_get_all(self):
self._common_policy_check(
"bay:get_all", self.get_json, '/bays', expect_errors=True)
def test_policy_disallow_get_one(self):
self.bay = obj_utils.create_test_cluster(self.context)
self._common_policy_check(
"bay:get", self.get_json, '/bays/%s' % self.bay.uuid,
expect_errors=True)
def test_policy_disallow_detail(self):
self._common_policy_check(
"bay:detail", self.get_json,
'/bays/%s/detail' % uuidutils.generate_uuid(),
expect_errors=True)
def test_policy_disallow_update(self):
self.bay = obj_utils.create_test_cluster(self.context,
name='bay_example_A',
node_count=3)
self._common_policy_check(
"bay:update", self.patch_json, '/bays/%s' % self.bay.name,
[{'path': '/name', 'value': "new_name", 'op': 'replace'}],
expect_errors=True)
def test_policy_disallow_create(self):
bdict = apiutils.bay_post_data(name='bay_example_A')
self._common_policy_check(
"bay:create", self.post_json, '/bays', bdict, expect_errors=True)
def _simulate_rpc_bay_delete(self, bay_uuid):
bay = objects.Cluster.get_by_uuid(self.context, bay_uuid)
bay.destroy()
def test_policy_disallow_delete(self):
p = mock.patch.object(rpcapi.API, 'cluster_delete')
self.mock_bay_delete = p.start()
self.mock_bay_delete.side_effect = self._simulate_rpc_bay_delete
self.addCleanup(p.stop)
self.bay = obj_utils.create_test_cluster(self.context)
self._common_policy_check(
"bay:delete", self.delete, '/bays/%s' % self.bay.uuid,
expect_errors=True)
def _owner_check(self, rule, func, *args, **kwargs):
self.policy.set_rules({rule: "user_id:%(user_id)s"})
response = func(*args, **kwargs)
self.assertEqual(403, response.status_int)
self.assertEqual('application/json', response.content_type)
self.assertTrue(
"Policy doesn't allow %s to be performed." % rule,
response.json['errors'][0]['detail'])
def test_policy_only_owner_get_one(self):
bay = obj_utils.create_test_cluster(self.context, user_id='another')
self._owner_check("bay:get", self.get_json, '/bays/%s' % bay.uuid,
expect_errors=True)
def test_policy_only_owner_update(self):
bay = obj_utils.create_test_cluster(self.context, user_id='another')
self._owner_check(
"bay:update", self.patch_json, '/bays/%s' % bay.uuid,
[{'path': '/name', 'value': "new_name", 'op': 'replace'}],
expect_errors=True)
def test_policy_only_owner_delete(self):
bay = obj_utils.create_test_cluster(self.context, user_id='another')
self._owner_check("bay:delete", self.delete, '/bays/%s' % bay.uuid,
expect_errors=True)

File diff suppressed because it is too large Load Diff

View File

@ -62,8 +62,6 @@ class TestGetCaCertificate(api_base.FunctionalTest):
headers=HEADERS)
self.assertEqual(self.cluster.uuid, response['cluster_uuid'])
# check that bay is still valid as well
self.assertEqual(self.cluster.uuid, response['bay_uuid'])
self.assertEqual(fake_cert['csr'], response['csr'])
self.assertEqual(fake_cert['pem'], response['pem'])
@ -77,8 +75,6 @@ class TestGetCaCertificate(api_base.FunctionalTest):
headers=HEADERS)
self.assertEqual(self.cluster.uuid, response['cluster_uuid'])
# check that bay is still valid as well
self.assertEqual(self.cluster.uuid, response['bay_uuid'])
self.assertEqual(fake_cert['csr'], response['csr'])
self.assertEqual(fake_cert['pem'], response['pem'])
@ -149,23 +145,6 @@ class TestPost(api_base.FunctionalTest):
self.assertEqual(201, response.status_int)
self.assertEqual(new_cert['cluster_uuid'],
response.json['cluster_uuid'])
# verify bay_uuid is still valid as well
self.assertEqual(new_cert['cluster_uuid'], response.json['bay_uuid'])
self.assertEqual('fake-pem', response.json['pem'])
# Test that bay_uuid is still backward compatible
def test_create_cert_by_bay_name(self, ):
new_cert = api_utils.cert_post_data(cluster_uuid=self.cluster.uuid)
del new_cert['pem']
new_cert['bay_uuid'] = new_cert['cluster_uuid']
del new_cert['cluster_uuid']
response = self.post_json('/certificates', new_cert, headers=HEADERS)
self.assertEqual('application/json', response.content_type)
self.assertEqual(201, response.status_int)
self.assertEqual(self.cluster.uuid, response.json['cluster_uuid'])
# verify bay_uuid is still valid as well
self.assertEqual(self.cluster.uuid, response.json['bay_uuid'])
self.assertEqual('fake-pem', response.json['pem'])
def test_create_cert_by_cluster_name(self, ):

View File

@ -49,7 +49,7 @@ class TestClusterObject(base.TestCase):
cluster.cluster_template_id = wtypes.Unset
self.assertEqual(wtypes.Unset, cluster.cluster_template_id)
# test backwards compatibility of bay fields with new objects
# test backwards compatibility of cluster fields with new objects
cluster_dict['create_timeout'] = 15
cluster = api_cluster.Cluster(**cluster_dict)
self.assertEqual(15, cluster.create_timeout)

View File

@ -16,8 +16,6 @@ import datetime
import pytz
from magnum.api.controllers.v1 import bay as bay_controller
from magnum.api.controllers.v1 import baymodel as baymodel_controller
from magnum.api.controllers.v1 import cluster as cluster_controller
from magnum.api.controllers.v1 import cluster_template as cluster_tmp_ctrl
from magnum.api.controllers.v1 import federation as federation_controller
@ -30,29 +28,12 @@ def remove_internal(values, internal):
return {k: v for (k, v) in values.items() if k not in int_attr}
def baymodel_post_data(**kw):
baymodel = utils.get_test_cluster_template(**kw)
internal = baymodel_controller.BayModelPatchType.internal_attrs()
return remove_internal(baymodel, internal)
def cluster_template_post_data(**kw):
cluster_template = utils.get_test_cluster_template(**kw)
internal = cluster_tmp_ctrl.ClusterTemplatePatchType.internal_attrs()
return remove_internal(cluster_template, internal)
def bay_post_data(**kw):
kw.update({'for_api_use': True})
bay = utils.get_test_cluster(**kw)
bay['baymodel_id'] = kw.get('baymodel_id', bay['cluster_template_id'])
bay['bay_create_timeout'] = kw.get('bay_create_timeout', 15)
del bay['cluster_template_id']
del bay['create_timeout']
internal = bay_controller.BayPatchType.internal_attrs()
return remove_internal(bay, internal)
def cluster_post_data(**kw):
kw.update({'for_api_use': True})
cluster = utils.get_test_cluster(**kw)