Designate V2 - Add recordset scenarios

Add create_and_(list|delete)_recordset scenarios
Remove the test also that checks the allowed methods, this is in order for us
to be able to have a private method _walk_pages that will do fetching of pages
for us vs attempting to fetch 1 giant list at once.

Change-Id: I43b111ebe07f3842fce48af4898978130d1341d7
This commit is contained in:
Endre Karlson 2015-11-24 15:30:26 +01:00
parent a58d3e3c57
commit 55fb754a2d
11 changed files with 413 additions and 54 deletions

View File

@ -134,7 +134,7 @@
runner:
type: "constant"
times: 10
concurrency: 1
concurrency: 2
context:
users:
tenants: 2
@ -157,6 +157,38 @@
failure_rate:
max: 0
DesignateBasic.create_and_list_recordsets:
-
runner:
type: "constant"
times: 10
concurrency: 2
context:
users:
tenants: 2
users_per_tenant: 2
zones:
zones_per_tenant: 1
sla:
failure_rate:
max: 0
DesignateBasic.create_and_delete_recordsets:
-
runner:
type: "constant"
times: 10
concurrency: 2
context:
users:
tenants: 2
users_per_tenant: 2
zones:
zones_per_tenant: 1
sla:
failure_rate:
max: 0
DesignateBasic.list_zones:
-
runner:

View File

@ -470,28 +470,72 @@ _designate_order = get_order(900)
class DesignateResource(SynchronizedDeletion, base.ResourceManager):
def _manager(self):
# NOTE: service name contains version, so we should split them
service_name, version = self._service.split("_v")
return getattr(getattr(self.user, service_name)(version),
# Map resource names to api / client version
resource_versions = {
"domains": "1",
"servers": "1",
"recordsets": 2,
"zones": "2"
}
version = resource_versions[self._resource]
return getattr(getattr(self.user, self._service)(version),
self._resource)
def _walk_pages(self, func, *args, **kwargs):
"""Generator that keeps fetching pages until there's none left."""
marker = None
@base.resource("designate_v1", "domains", order=next(_designate_order))
while True:
items = func(marker=marker, limit=100, *args, **kwargs)
if not items:
break
for item in items:
yield item
marker = items[-1]["id"]
@base.resource("designate", "domains", order=next(_designate_order))
class DesignateDomain(DesignateResource):
pass
@base.resource("designate_v2", "zones", order=next(_designate_order))
class DesignateZones(DesignateResource):
pass
@base.resource("designate_v1", "servers", order=next(_designate_order),
@base.resource("designate", "servers", order=next(_designate_order),
admin_required=True, perform_for_admin_only=True)
class DesignateServer(DesignateResource):
pass
@base.resource("designate", "recordsets", order=next(_designate_order),
tenant_resource=True)
class DesignateRecordSets(DesignateResource):
def _client(self):
# Map resource names to api / client version
resource_versions = {
"domains": "1",
"servers": "1",
"recordsets": 2,
"zones": "2"
}
version = resource_versions[self._resource]
return getattr(self.user, self._service)(version)
def list(self):
criterion = {"name": "s_rally_*"}
for zone in self._walk_pages(self._client().zones.list,
criterion=criterion):
for recordset in self._walk_pages(self._client().recordsets.list,
zone["id"]):
yield recordset
@base.resource("designate", "zones", order=next(_designate_order),
tenant_resource=True)
class DesignateZones(DesignateResource):
def list(self):
criterion = {"name": "s_rally_*"}
return self._walk_pages(self._manager().list, criterion=criterion)
# SWIFT
_swift_order = get_order(1000)

View File

@ -13,6 +13,7 @@
# 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 rally import consts
from rally.plugins.openstack import scenario
@ -26,7 +27,7 @@ class DesignateBasic(utils.DesignateScenario):
@validation.required_services(consts.Service.DESIGNATE)
@validation.required_openstack(users=True)
@scenario.configure(context={"cleanup": ["designate_v1"]})
@scenario.configure(context={"cleanup": ["designate"]})
def create_and_list_domains(self):
"""Create a domain and list all domains.
@ -43,7 +44,7 @@ class DesignateBasic(utils.DesignateScenario):
@validation.required_services(consts.Service.DESIGNATE)
@validation.required_openstack(users=True)
@scenario.configure(context={"cleanup": ["designate_v1"]})
@scenario.configure(context={"cleanup": ["designate"]})
def list_domains(self):
"""List Designate domains.
@ -59,7 +60,7 @@ class DesignateBasic(utils.DesignateScenario):
@validation.required_services(consts.Service.DESIGNATE)
@validation.required_openstack(users=True)
@scenario.configure(context={"cleanup": ["designate_v1"]})
@scenario.configure(context={"cleanup": ["designate"]})
def create_and_delete_domain(self):
"""Create and then delete a domain.
@ -71,7 +72,7 @@ class DesignateBasic(utils.DesignateScenario):
@validation.required_services(consts.Service.DESIGNATE)
@validation.required_openstack(users=True)
@scenario.configure(context={"cleanup": ["designate_v1"]})
@scenario.configure(context={"cleanup": ["designate"]})
def create_and_update_domain(self):
"""Create and then update a domain.
@ -83,7 +84,7 @@ class DesignateBasic(utils.DesignateScenario):
@validation.required_services(consts.Service.DESIGNATE)
@validation.required_openstack(users=True)
@scenario.configure(context={"cleanup": ["designate_v1"]})
@scenario.configure(context={"cleanup": ["designate"]})
def create_and_delete_records(self, records_per_domain=5):
"""Create and then delete records.
@ -110,7 +111,7 @@ class DesignateBasic(utils.DesignateScenario):
@validation.required_services(consts.Service.DESIGNATE)
@validation.required_openstack(users=True)
@scenario.configure(context={"cleanup": ["designate_v1"]})
@scenario.configure(context={"cleanup": ["designate"]})
def list_records(self, domain_id):
"""List Designate records.
@ -128,7 +129,7 @@ class DesignateBasic(utils.DesignateScenario):
@validation.required_services(consts.Service.DESIGNATE)
@validation.required_openstack(users=True)
@scenario.configure(context={"cleanup": ["designate_v1"]})
@scenario.configure(context={"cleanup": ["designate"]})
def create_and_list_records(self, records_per_domain=5):
"""Create and then list records.
@ -151,7 +152,7 @@ class DesignateBasic(utils.DesignateScenario):
@validation.required_services(consts.Service.DESIGNATE)
@validation.required_openstack(admin=True)
@scenario.configure(context={"cleanup": ["designate_v1"]})
@scenario.configure(context={"cleanup": ["designate"]})
def create_and_list_servers(self):
"""Create a Designate server and list all servers.
@ -166,7 +167,7 @@ class DesignateBasic(utils.DesignateScenario):
@validation.required_services(consts.Service.DESIGNATE)
@validation.required_openstack(admin=True)
@scenario.configure(context={"cleanup": ["designate_v1"]})
@scenario.configure(context={"cleanup": ["designate"]})
def create_and_delete_server(self):
"""Create and then delete a server.
@ -178,7 +179,7 @@ class DesignateBasic(utils.DesignateScenario):
@validation.required_services(consts.Service.DESIGNATE)
@validation.required_openstack(admin=True)
@scenario.configure(context={"cleanup": ["designate_v1"]})
@scenario.configure(context={"cleanup": ["designate"]})
def list_servers(self):
"""List Designate servers.
@ -190,7 +191,7 @@ class DesignateBasic(utils.DesignateScenario):
# NOTE: API V2
@validation.required_services(consts.Service.DESIGNATE)
@validation.required_openstack(users=True)
@scenario.configure(context={"cleanup": ["designate_v2"]})
@scenario.configure(context={"cleanup": ["designate"]})
def create_and_list_zones(self):
"""Create a zone and list all zones.
@ -207,7 +208,7 @@ class DesignateBasic(utils.DesignateScenario):
@validation.required_services(consts.Service.DESIGNATE)
@validation.required_openstack(users=True)
@scenario.configure(context={"cleanup": ["designate_v2"]})
@scenario.configure(context={"cleanup": ["designate"]})
def list_zones(self):
"""List Designate zones.
@ -219,7 +220,7 @@ class DesignateBasic(utils.DesignateScenario):
@validation.required_services(consts.Service.DESIGNATE)
@validation.required_openstack(users=True)
@scenario.configure(context={"cleanup": ["designate_v2"]})
@scenario.configure(context={"cleanup": ["designate"]})
def create_and_delete_zone(self):
"""Create and then delete a zone.
@ -231,7 +232,7 @@ class DesignateBasic(utils.DesignateScenario):
@validation.required_services(consts.Service.DESIGNATE)
@validation.required_openstack(users=True)
@scenario.configure(context={"cleanup": ["designate_v2"]})
@scenario.configure(context={"cleanup": ["designate"]})
def list_recordsets(self, zone_id):
"""List Designate recordsets.
@ -242,3 +243,55 @@ class DesignateBasic(utils.DesignateScenario):
"""
self._list_recordsets(zone_id)
@validation.required_services(consts.Service.DESIGNATE)
@validation.required_openstack(users=True)
@validation.required_contexts("zones")
@scenario.configure(context={"cleanup": ["designate"]})
def create_and_delete_recordsets(self, recordsets_per_zone=5):
"""Create and then delete recordsets.
Measure the performance of creating and deleting recordsets
with different level of load.
:param recordsets_per_zone: recordsets to create pr zone.
"""
zone = random.choice(self.context["tenant"]["zones"])
recordsets = []
key = "designate.create_%s_recordsets" % recordsets_per_zone
with atomic.ActionTimer(self, key):
for i in range(recordsets_per_zone):
recordset = self._create_recordset(zone, atomic_action=False)
recordsets.append(recordset)
key = "designate.delete_%s_recordsets" % recordsets_per_zone
with atomic.ActionTimer(self, key):
for recordset in recordsets:
self._delete_recordset(
zone["id"], recordset["id"], atomic_action=False)
@validation.required_services(consts.Service.DESIGNATE)
@validation.required_openstack(users=True)
@validation.required_contexts("zones")
@scenario.configure(context={"cleanup": ["designate"]})
def create_and_list_recordsets(self, recordsets_per_zone=5):
"""Create and then list recordsets.
If you have only 1 user in your context, you will
add 1 recordset on every iteration. So you will have more
and more recordsets and will be able to measure the
performance of the "openstack recordset list" command depending on
the number of zones/recordsets owned by users.
:param recordsets_per_zone: recordsets to create pr zone.
"""
zone = random.choice(self.context["tenant"]["zones"])
key = "designate.create_%s_recordsets" % recordsets_per_zone
with atomic.ActionTimer(self, key):
for i in range(recordsets_per_zone):
self._create_recordset(zone, atomic_action=False)
self._list_recordsets(zone["id"])

View File

@ -187,3 +187,44 @@ class DesignateScenario(scenario.OpenStackScenario):
"""
return self.clients("designate", version="2").recordsets.list(
zone_id, criterion=criterion, marker=marker, limit=limit)
def _create_recordset(self, zone, recordset=None, atomic_action=True):
"""Create a recordset in a zone.
:param zone: zone dict
:param recordset: recordset dict
:param atomic_action: True if the recordset creation should be tracked
as an atomic action
:returns: Designate recordset dict
"""
recordset = recordset or {}
recordset.setdefault("type_", recordset.pop("type", "A"))
if "name" not in recordset:
recordset["name"] = "%s.%s" % (self.generate_random_name(),
zone["name"])
if "records" not in recordset:
recordset["records"] = ["10.0.0.1"]
client = self.clients("designate", version="2")
if atomic_action:
with atomic.ActionTimer(self, "designate.create_recordset"):
return client.recordsets.create(zone["id"], **recordset)
return client.recordsets.create(zone["id"], **recordset)
def _delete_recordset(self, zone_id, recordset_id, atomic_action=True):
"""Delete a zone recordset.
:param zone_id: Zone ID
:param recordset_id: Recordset ID
:param atomic_action: True if the recordset creation should be tracked
as an atomic action
"""
client = self.clients("designate", version="2")
if atomic_action:
with atomic.ActionTimer(self, "designate.delete_recordset"):
client.recordsets.delete(zone_id, recordset_id)
else:
client.recordsets.delete(zone_id, recordset_id)

View File

@ -0,0 +1,31 @@
{
"DesignateBasic.create_and_delete_recordsets": [
{
"args": {
"recordsets_per_zone": 10
},
"runner": {
"type": "constant",
"times": 10,
"concurrency": 10
},
"context": {
"quotas": {
"designate": {
"domains": 100,
"domain_recordsets": 2000,
"domain_records": 2000,
"recordset_records": 2000
}
},
"users": {
"tenants": 2,
"users_per_tenant": 2
},
"zones": {
"zones_per_tenant": 1
}
}
}
]
}

View File

@ -0,0 +1,21 @@
---
DesignateBasic.create_and_delete_recordsets:
-
args:
recordsets_per_zone: 10
runner:
type: "constant"
times: 10
concurrency: 10
context:
quotas:
designate:
domains: 100
domain_recordsets: 2000
domain_records: 2000
recordset_records: 2000
users:
tenants: 2
users_per_tenant: 2
zones:
zones_per_tenant: 1

View File

@ -0,0 +1,31 @@
{
"DesignateBasic.create_and_list_recordsets": [
{
"args": {
"recordsets_per_zone": 10
},
"runner": {
"type": "constant",
"times": 10,
"concurrency": 10
},
"context": {
"quotas": {
"designate": {
"domains": 100,
"domain_recordsets": 2000,
"domain_records": 2000,
"recordset_records": 2000
}
},
"users": {
"tenants": 2,
"users_per_tenant": 2
},
"zones": {
"zones_per_tenant": 1
}
}
}
]
}

View File

@ -0,0 +1,21 @@
---
DesignateBasic.create_and_list_recordsets:
-
args:
recordsets_per_zone: 10
runner:
type: "constant"
times: 10
concurrency: 10
context:
quotas:
designate:
domains: 100
domain_recordsets: 2000
domain_records: 2000
recordset_records: 2000
users:
tenants: 2
users_per_tenant: 2
zones:
zones_per_tenant: 1

View File

@ -17,41 +17,13 @@ from boto import exception as boto_exception
import mock
from neutronclient.common import exceptions as neutron_exceptions
from rally.common.plugin import discover
from rally.common import utils
from rally.plugins.openstack.cleanup import base
from rally.plugins.openstack.cleanup import resources
from tests.unit import test
BASE = "rally.plugins.openstack.cleanup.resources"
class AllResourceManagerTestCase(test.TestCase):
def test_res_manager_special_field(self):
for res_mgr in discover.itersubclasses(base.ResourceManager):
manager_name = "%s.%s" % (res_mgr.__module__, res_mgr.__name__)
fields = filter(lambda x: not x.startswith("__"), dir(res_mgr))
available_opts = set([
"_admin_required", "_perform_for_admin_only",
"_tenant_resource", "_service", "_resource", "_order",
"_max_attempts", "_timeout", "_interval", "_threads",
"_manager", "id", "is_deleted", "delete", "list",
"supports_extension"
])
extra_opts = set(fields) - available_opts
self.assertFalse(
extra_opts,
("ResourceManager %(name)s contains extra fields: %(opts)s."
" Remove them to pass this test")
% {"name": manager_name, "opts": ", ".join(extra_opts)})
class SynchronizedDeletionTestCase(test.TestCase):
def test_is_deleted(self):

View File

@ -24,7 +24,6 @@ DESIGNATE_BASIC = ("rally.plugins.openstack.scenarios.designate.basic"
class DesignateBasicTestCase(test.ScenarioTestCase):
@mock.patch(DESIGNATE_BASIC + "._list_domains")
@mock.patch(DESIGNATE_BASIC + "._create_domain")
def test_create_and_list_domains(self, mock_designate_basic__create_domain,
@ -214,3 +213,53 @@ class DesignateBasicTestCase(test.ScenarioTestCase):
# Default options
scenario.list_recordsets("123")
mock_designate_basic__list_recordsets.assert_called_once_with("123")
@mock.patch(DESIGNATE_BASIC + "._delete_recordset")
@mock.patch(DESIGNATE_BASIC + "._create_recordset")
def test_create_and_delete_recordsets(
self, mock_designate_basic__create_recordset,
mock_designate_basic__delete_recordset):
zone = {"id": "1234"}
self.context.update({
"tenant": {
"zones": [zone]
}
})
scenario = basic.DesignateBasic(self.context)
mock_designate_basic__create_recordset.return_value = {"id": "321"}
recordsets_per_zone = 5
scenario.create_and_delete_recordsets(
recordsets_per_zone=recordsets_per_zone)
self.assertEqual(
mock_designate_basic__create_recordset.mock_calls,
[mock.call(zone, atomic_action=False)]
* recordsets_per_zone)
self.assertEqual(
mock_designate_basic__delete_recordset.mock_calls,
[mock.call(zone["id"], "321", atomic_action=False)]
* recordsets_per_zone)
@mock.patch(DESIGNATE_BASIC + "._list_recordsets")
@mock.patch(DESIGNATE_BASIC + "._create_recordset")
def test_create_and_list_recordsets(
self, mock_designate_basic__create_recordset,
mock_designate_basic__list_recordsets):
zone = {"id": "1234"}
self.context.update({
"tenant": {
"zones": [zone]
}
})
scenario = basic.DesignateBasic(self.context)
recordsets_per_zone = 5
scenario.create_and_list_recordsets(
recordsets_per_zone=recordsets_per_zone)
self.assertEqual(
mock_designate_basic__create_recordset.mock_calls,
[mock.call(zone, atomic_action=False)]
* recordsets_per_zone)
mock_designate_basic__list_recordsets.assert_called_once_with(
zone["id"])

View File

@ -221,3 +221,67 @@ class DesignateScenarioTestCase(test.ScenarioTestCase):
return_recordsets_list)
self._test_atomic_action_timer(scenario.atomic_actions(),
"designate.list_recordsets")
@ddt.data(
{},
{"data": "127.0.0.1"})
def test_create_recordset(self, recordset_data):
scenario = utils.DesignateScenario()
random_name = "foo"
zone_name = "zone.name."
random_recordset_name = "%s.%s" % (random_name, zone_name)
scenario = utils.DesignateScenario(context=self.context)
scenario.generate_random_name = mock.Mock(return_value=random_name)
zone = {"name": zone_name, "id": "123"}
# Create with randoms (name and type)
scenario._create_recordset(zone)
self.client.recordsets.create.assert_called_once_with(
zone["id"],
name=random_recordset_name,
type_="A",
records=["10.0.0.1"])
self._test_atomic_action_timer(scenario.atomic_actions(),
"designate.create_recordset")
self.client.recordsets.create.reset_mock()
# Specify name
recordset = {"name": "www.zone.name.", "type_": "ASD"}
scenario._create_recordset(zone, recordset)
self.client.recordsets.create.assert_called_once_with(
zone["id"],
name="www.zone.name.",
type_="ASD",
records=["10.0.0.1"])
self.client.recordsets.create.reset_mock()
# Specify type without underscore
scenario._create_recordset(zone, {"type": "A"})
self.client.recordsets.create.assert_called_once_with(
zone["id"],
name="foo.zone.name.",
type_="A",
records=["10.0.0.1"])
def test_delete_recordset(self):
scenario = utils.DesignateScenario(context=self.context)
zone_id = mock.Mock()
recordset_id = mock.Mock()
scenario._delete_recordset(zone_id, recordset_id)
self.client.recordsets.delete.assert_called_once_with(
zone_id, recordset_id)
self._test_atomic_action_timer(scenario.atomic_actions(),
"designate.delete_recordset")
self.client.recordsets.delete.reset_mock()
scenario._delete_recordset(zone_id, recordset_id, atomic_action=False)
self.client.recordsets.delete.assert_called_once_with(
zone_id, recordset_id)