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.
|
||||
"""
|
||||
|
||||
import tempest_lib
|
||||
import tempest_lib.base
|
||||
|
||||
from functionaltests.common.client import DesignateClient
|
||||
from functionaltests.api.v2.clients.zone_client import ZoneClient
|
||||
from functionaltests.api.v2.clients.quotas_client import QuotasClient
|
||||
from functionaltests.api.v2.models.quotas_model import QuotasModel
|
||||
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):
|
||||
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):
|
||||
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.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):
|
||||
creds = KeystoneV2Credentials(
|
||||
username=CONF.identity.username,
|
||||
password=CONF.identity.password,
|
||||
tenant_name=CONF.identity.tenant_name,
|
||||
username=CONF.identity.admin_username,
|
||||
password=CONF.identity.admin_password,
|
||||
tenant_name=CONF.identity.admin_tenant_name,
|
||||
)
|
||||
auth_provider = tempest.manager.get_auth_provider(creds)
|
||||
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