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:
parent
61c7f7b34b
commit
be00e209e0
|
@ -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
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"bay_uuid":"0b4b766f-1500-44b3-9804-5a6e12fe6df4",
|
||||
"csr":"-----BEGIN CERTIFICATE REQUEST-----\nMIIEfzCCAmcCAQAwFDESMBAGA1UEAxMJWW91ciBOYW1lMIICIjANBgkqhkiG9w0B\n-----END CERTIFICATE REQUEST-----\n"
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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/',
|
||||
|
|
|
@ -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
|
@ -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, ):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue