Add Tempest zone tests
Change-Id: Ia68692503e31cc0eb34f06b6cb304b9d7b027ed4
This commit is contained in:
parent
72ff534a62
commit
4beca918e2
0
functionaltests/api/v2/clients/__init__.py
Normal file
0
functionaltests/api/v2/clients/__init__.py
Normal file
44
functionaltests/api/v2/clients/quotas_client.py
Normal file
44
functionaltests/api/v2/clients/quotas_client.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
"""
|
||||||
|
Copyright 2015 Rackspace
|
||||||
|
|
||||||
|
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 functionaltests.api.v2.models.quotas_model import QuotasModel
|
||||||
|
|
||||||
|
|
||||||
|
class QuotasClient(object):
|
||||||
|
|
||||||
|
def __init__(self, client):
|
||||||
|
self.client = client
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def quotas_uri(cls, tenant_id):
|
||||||
|
return "/v2/quotas/" + tenant_id
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def deserialize(cls, resp, body, model_type):
|
||||||
|
return resp, model_type.from_json(body)
|
||||||
|
|
||||||
|
def get_quotas(self, tenant_id, **kwargs):
|
||||||
|
resp, body = self.client.get(self.quotas_uri(tenant_id), **kwargs)
|
||||||
|
return self.deserialize(resp, body, QuotasModel)
|
||||||
|
|
||||||
|
def patch_quotas(self, tenant_id, quotas_model, **kwargs):
|
||||||
|
resp, body = self.client.patch(self.quotas_uri(tenant_id),
|
||||||
|
body=quotas_model.to_json(), **kwargs)
|
||||||
|
return self.deserialize(resp, body, QuotasModel)
|
||||||
|
|
||||||
|
def delete_quotas(self, tenant_id, **kwargs):
|
||||||
|
resp, body = self.client.patch(self.quotas_uri(tenant_id), **kwargs)
|
||||||
|
return self.deserialize(resp, body, QuotasModel)
|
58
functionaltests/api/v2/clients/zone_client.py
Normal file
58
functionaltests/api/v2/clients/zone_client.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
"""
|
||||||
|
Copyright 2015 Rackspace
|
||||||
|
|
||||||
|
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 functionaltests.api.v2.models.zone_model import ZoneModel
|
||||||
|
from functionaltests.api.v2.models.zone_model import ZoneListModel
|
||||||
|
|
||||||
|
|
||||||
|
class ZoneClient(object):
|
||||||
|
|
||||||
|
def __init__(self, client):
|
||||||
|
self.client = client
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def zones_uri(cls):
|
||||||
|
return "/v2/zones"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def zone_uri(cls, id):
|
||||||
|
return "{0}/{1}".format(cls.zones_uri(), id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def deserialize(cls, resp, body, model_type):
|
||||||
|
return resp, model_type.from_json(body)
|
||||||
|
|
||||||
|
def list_zones(self, **kwargs):
|
||||||
|
resp, body = self.client.get(self.zones_uri(), **kwargs)
|
||||||
|
return self.deserialize(resp, body, ZoneListModel)
|
||||||
|
|
||||||
|
def get_zone(self, id, **kwargs):
|
||||||
|
resp, body = self.client.get(self.zone_uri(id))
|
||||||
|
return self.deserialize(resp, body, ZoneModel)
|
||||||
|
|
||||||
|
def post_zone(self, zone_model, **kwargs):
|
||||||
|
resp, body = self.client.post(self.zones_uri(),
|
||||||
|
body=zone_model.to_json(), **kwargs)
|
||||||
|
return self.deserialize(resp, body, ZoneModel)
|
||||||
|
|
||||||
|
def patch_zone(self, id, zone_model, **kwargs):
|
||||||
|
resp, body = self.client.patch(self.zone_uri(id),
|
||||||
|
body=zone_model.to_json(), **kwargs)
|
||||||
|
return self.deserialize(resp, body, ZoneModel)
|
||||||
|
|
||||||
|
def delete_zone(self, id, **kwargs):
|
||||||
|
resp, body = self.client.delete(self.zone_uri(id), **kwargs)
|
||||||
|
return self.deserialize(resp, body, ZoneModel)
|
0
functionaltests/api/v2/models/__init__.py
Normal file
0
functionaltests/api/v2/models/__init__.py
Normal file
27
functionaltests/api/v2/models/quotas_model.py
Normal file
27
functionaltests/api/v2/models/quotas_model.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
"""
|
||||||
|
Copyright 2015 Rackspace
|
||||||
|
|
||||||
|
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 functionaltests.common.models import BaseModel
|
||||||
|
from functionaltests.common.models import EntityModel
|
||||||
|
|
||||||
|
|
||||||
|
class QuotasData(BaseModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class QuotasModel(EntityModel):
|
||||||
|
ENTITY_NAME = 'quota'
|
||||||
|
MODEL_TYPE = QuotasData
|
33
functionaltests/api/v2/models/zone_model.py
Normal file
33
functionaltests/api/v2/models/zone_model.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
"""
|
||||||
|
Copyright 2015 Rackspace
|
||||||
|
|
||||||
|
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 functionaltests.common.models import BaseModel
|
||||||
|
from functionaltests.common.models import CollectionModel
|
||||||
|
from functionaltests.common.models import EntityModel
|
||||||
|
|
||||||
|
|
||||||
|
class ZoneData(BaseModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ZoneModel(EntityModel):
|
||||||
|
ENTITY_NAME = 'zone'
|
||||||
|
MODEL_TYPE = ZoneData
|
||||||
|
|
||||||
|
|
||||||
|
class ZoneListModel(CollectionModel):
|
||||||
|
COLLECTION_NAME = 'zones'
|
||||||
|
MODEL_TYPE = ZoneData
|
@ -14,18 +14,72 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import tempest_lib
|
from functionaltests.api.v2.clients.zone_client import ZoneClient
|
||||||
import tempest_lib.base
|
from functionaltests.api.v2.clients.quotas_client import QuotasClient
|
||||||
|
from functionaltests.api.v2.models.quotas_model import QuotasModel
|
||||||
from functionaltests.common.client import DesignateClient
|
from functionaltests.common import datagen
|
||||||
|
from functionaltests.common.base import BaseDesignateTest
|
||||||
|
|
||||||
|
|
||||||
class ZoneTest(tempest_lib.base.BaseTestCase):
|
class ZoneTest(BaseDesignateTest):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(ZoneTest, self).__init__(*args, **kwargs)
|
super(ZoneTest, self).__init__(*args, **kwargs)
|
||||||
self.client = DesignateClient()
|
self.client = ZoneClient(self.base_client)
|
||||||
|
self.quotas_client = QuotasClient(self.base_client)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(ZoneTest, self).setUp()
|
||||||
|
self.quotas_client.patch_quotas(
|
||||||
|
self.quotas_client.client.tenant_id,
|
||||||
|
QuotasModel.from_dict({
|
||||||
|
'quota': {
|
||||||
|
'zones': 9999999,
|
||||||
|
'recordset_records': 9999999,
|
||||||
|
'zone_records': 9999999,
|
||||||
|
'zone_recordsets': 9999999}}))
|
||||||
|
|
||||||
|
def wait_for_zone(self, zone_id):
|
||||||
|
self.wait_for_condition(lambda: self.is_zone_active(zone_id))
|
||||||
|
|
||||||
|
def wait_for_404(self, zone_id):
|
||||||
|
self.wait_for_condition(lambda: self.is_zone_404(zone_id))
|
||||||
|
|
||||||
|
def _create_zone(self, zone_model):
|
||||||
|
resp, model = self.client.post_zone(zone_model)
|
||||||
|
self.assertEqual(resp.status, 202)
|
||||||
|
self.wait_for_zone(model.zone.id)
|
||||||
|
return resp, model
|
||||||
|
|
||||||
def test_list_zones(self):
|
def test_list_zones(self):
|
||||||
resp, body = self.client.get('/v2/zones')
|
self._create_zone(datagen.random_zone_data())
|
||||||
|
resp, model = self.client.list_zones()
|
||||||
self.assertEqual(resp.status, 200)
|
self.assertEqual(resp.status, 200)
|
||||||
|
self.assertGreater(len(model.zones), 0)
|
||||||
|
|
||||||
|
def test_create_zone(self):
|
||||||
|
self._create_zone(datagen.random_zone_data())
|
||||||
|
|
||||||
|
def test_update_zone(self):
|
||||||
|
post_model = datagen.random_zone_data()
|
||||||
|
resp, old_model = self._create_zone(post_model)
|
||||||
|
|
||||||
|
patch_model = datagen.random_zone_data()
|
||||||
|
del patch_model.zone.name # don't try to override the zone name
|
||||||
|
resp, new_model = self.client.patch_zone(old_model.zone.id,
|
||||||
|
patch_model)
|
||||||
|
self.assertEqual(resp.status, 202)
|
||||||
|
self.wait_for_zone(new_model.zone.id)
|
||||||
|
|
||||||
|
resp, model = self.client.get_zone(new_model.zone.id)
|
||||||
|
self.assertEqual(resp.status, 200)
|
||||||
|
self.assertEqual(new_model.zone.id, old_model.zone.id)
|
||||||
|
self.assertEqual(new_model.zone.name, old_model.zone.name)
|
||||||
|
self.assertEqual(new_model.zone.ttl, patch_model.zone.ttl)
|
||||||
|
self.assertEqual(new_model.zone.email, patch_model.zone.email)
|
||||||
|
|
||||||
|
def test_delete_zone(self):
|
||||||
|
resp, model = self._create_zone(datagen.random_zone_data())
|
||||||
|
resp, model = self.client.delete_zone(model.zone.id)
|
||||||
|
self.assertEqual(resp.status, 202)
|
||||||
|
self.wait_for_404(model.zone.id)
|
||||||
|
54
functionaltests/common/base.py
Normal file
54
functionaltests/common/base.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
"""
|
||||||
|
Copyright 2015 Rackspace
|
||||||
|
|
||||||
|
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 time
|
||||||
|
|
||||||
|
import tempest_lib.base
|
||||||
|
from tempest_lib.exceptions import NotFound
|
||||||
|
|
||||||
|
from functionaltests.common.client import DesignateClient
|
||||||
|
|
||||||
|
|
||||||
|
class BaseDesignateTest(tempest_lib.base.BaseTestCase):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(BaseDesignateTest, self).__init__(*args, **kwargs)
|
||||||
|
self.base_client = DesignateClient()
|
||||||
|
|
||||||
|
def wait_for_condition(self, condition, interval=1, timeout=20):
|
||||||
|
end_time = time.time() + timeout
|
||||||
|
while time.time() < end_time:
|
||||||
|
if condition():
|
||||||
|
return
|
||||||
|
time.sleep(interval)
|
||||||
|
raise Exception("Timed out after {0} seconds".format(timeout))
|
||||||
|
|
||||||
|
def is_zone_active(self, zone_id):
|
||||||
|
resp, model = self.client.get_zone(zone_id)
|
||||||
|
self.assertEqual(resp.status, 200)
|
||||||
|
if model.zone.status == 'ACTIVE':
|
||||||
|
return True
|
||||||
|
elif model.zone.status == 'ERROR':
|
||||||
|
raise Exception("Saw ERROR status")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_zone_404(self, zone_id):
|
||||||
|
try:
|
||||||
|
# tempest_lib rest client raises exceptions on bad status codes
|
||||||
|
resp, model = self.client.get_zone(zone_id)
|
||||||
|
except NotFound:
|
||||||
|
return True
|
||||||
|
return False
|
@ -24,9 +24,9 @@ class DesignateClient(RestClient):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
creds = KeystoneV2Credentials(
|
creds = KeystoneV2Credentials(
|
||||||
username=CONF.identity.username,
|
username=CONF.identity.admin_username,
|
||||||
password=CONF.identity.password,
|
password=CONF.identity.admin_password,
|
||||||
tenant_name=CONF.identity.tenant_name,
|
tenant_name=CONF.identity.admin_tenant_name,
|
||||||
)
|
)
|
||||||
auth_provider = tempest.manager.get_auth_provider(creds)
|
auth_provider = tempest.manager.get_auth_provider(creds)
|
||||||
auth_provider.fill_credentials()
|
auth_provider.fill_credentials()
|
||||||
|
55
functionaltests/common/datagen.py
Normal file
55
functionaltests/common/datagen.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
"""
|
||||||
|
Copyright 2015 Rackspace
|
||||||
|
|
||||||
|
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 random
|
||||||
|
|
||||||
|
from functionaltests.api.v2.models.zone_model import ZoneModel
|
||||||
|
|
||||||
|
|
||||||
|
def random_ip():
|
||||||
|
return ".".join(str(random.randrange(0, 256)) for _ in range(4))
|
||||||
|
|
||||||
|
|
||||||
|
def random_string(prefix='rand', n=8, suffix=''):
|
||||||
|
"""Return a string containing random digits
|
||||||
|
|
||||||
|
:param prefix: the exact text to start the string. Defaults to "rand"
|
||||||
|
:param n: the number of random digits to generate
|
||||||
|
:param suffix: the exact text to end the string
|
||||||
|
"""
|
||||||
|
digits = "".join(str(random.randrange(0, 10)) for _ in range(n))
|
||||||
|
return prefix + digits + suffix
|
||||||
|
|
||||||
|
|
||||||
|
def random_zone_data(name=None, email=None, ttl=None, description=None):
|
||||||
|
"""Generate random zone data, with optional overrides
|
||||||
|
|
||||||
|
:return: A ZoneModel
|
||||||
|
"""
|
||||||
|
if name is None:
|
||||||
|
name = random_string(prefix='testdomain', suffix='.com.')
|
||||||
|
if email is None:
|
||||||
|
email = ("admin@" + name).strip('.')
|
||||||
|
if description is None:
|
||||||
|
description = random_string(prefix='Description ')
|
||||||
|
if ttl is None:
|
||||||
|
ttl = random.randint(1200, 8400),
|
||||||
|
return ZoneModel.from_dict({
|
||||||
|
'zone': {
|
||||||
|
'name': name,
|
||||||
|
'email': email,
|
||||||
|
'ttl': random.randint(1200, 8400),
|
||||||
|
'description': description}})
|
101
functionaltests/common/models.py
Normal file
101
functionaltests/common/models.py
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
"""
|
||||||
|
Copyright 2015 Rackspace
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class BaseModel(object):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_json(cls, json_str):
|
||||||
|
return cls.from_dict(json.loads(json_str))
|
||||||
|
|
||||||
|
def to_json(self):
|
||||||
|
return json.dumps(self.to_dict())
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data):
|
||||||
|
model = cls()
|
||||||
|
for key in data:
|
||||||
|
setattr(model, key, data.get(key))
|
||||||
|
return model
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
result = {}
|
||||||
|
for key in self.__dict__:
|
||||||
|
result[key] = getattr(self, key)
|
||||||
|
if isinstance(result[key], BaseModel):
|
||||||
|
result[key] = result[key].to_dict()
|
||||||
|
return result
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "%s" % self.to_dict()
|
||||||
|
|
||||||
|
|
||||||
|
class LinksModel(BaseModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MetadataModel(BaseModel):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CollectionModel(BaseModel):
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
'collection_name' : [ <models> ],
|
||||||
|
'links': { <links> },
|
||||||
|
'metdata': { <metadata> },
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
SUB_MODELS = {
|
||||||
|
'links': LinksModel,
|
||||||
|
'metadata': MetadataModel,
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data):
|
||||||
|
model = super(CollectionModel, cls).from_dict(data)
|
||||||
|
|
||||||
|
# deserialize e.g. data['zones']
|
||||||
|
collection = []
|
||||||
|
if hasattr(model, cls.COLLECTION_NAME):
|
||||||
|
for d in getattr(model, cls.COLLECTION_NAME):
|
||||||
|
collection.append(cls.MODEL_TYPE.from_dict(d))
|
||||||
|
setattr(model, cls.COLLECTION_NAME, collection)
|
||||||
|
|
||||||
|
# deserialize data['
|
||||||
|
for key, model_type in cls.SUB_MODELS.iteritems():
|
||||||
|
if hasattr(model, key):
|
||||||
|
val = getattr(model, key)
|
||||||
|
setattr(model, key, model_type.from_dict(val))
|
||||||
|
|
||||||
|
return model
|
||||||
|
|
||||||
|
|
||||||
|
class EntityModel(BaseModel):
|
||||||
|
"""
|
||||||
|
{ 'entity_name': { <data> } }
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data):
|
||||||
|
model = super(EntityModel, cls).from_dict(data)
|
||||||
|
if hasattr(model, cls.ENTITY_NAME):
|
||||||
|
val = getattr(model, cls.ENTITY_NAME)
|
||||||
|
setattr(model, cls.ENTITY_NAME, cls.MODEL_TYPE.from_dict(val))
|
||||||
|
return model
|
Loading…
Reference in New Issue
Block a user