Support scenarios for updating neutron quotas

* Add new scenario Quotas.neutron_update to exercise updating
  quotas for neutron
* Modify _update_quotas to be able to support clients which
  update quotas with an api different from nova and cinder
* Add test for ZaqarQueues list()

Co-Authored-By: Roman Vasilets <rvasilets@mirantis.com>
Co-Authored-By: Marco Morais <mmorais@yahoo-inc.com>

Change-Id: Ida19c02f831e1846bb96a239f4a2deb5ae417153
This commit is contained in:
Marco Morais 2014-05-08 08:45:11 +00:00 committed by Roman Vasilets
parent fb4058f7cf
commit 5951c7e043
13 changed files with 212 additions and 35 deletions

View File

@ -297,6 +297,22 @@
failure_rate:
max: 0
Quotas.neutron_update:
-
args:
max_quota: 1024
runner:
type: "constant"
times: 10
concurrency: 2
context:
users:
tenants: 3
users_per_tenant: 2
sla:
failure_rate:
max: 0
NovaServers.boot_server:
-
args:

View File

@ -87,11 +87,13 @@ class NovaQuotas(QuotaMixin, base.ResourceManager):
_neutron_order = get_order(300)
class NeutronMixin(SynchronizedDeletion):
@base.resource(service=None, resource=None, admin_required=True)
class NeutronMixin(SynchronizedDeletion, base.ResourceManager):
# Neutron has the best client ever, so we need to override everything
def _manager(self):
return getattr(self.user, self._service)()
client = self._admin_required and self.admin or self.user
return getattr(client, self._service)()
def id(self):
return self.raw_resource["id"]
@ -110,7 +112,7 @@ class NeutronMixin(SynchronizedDeletion):
@base.resource("neutron", "port", order=next(_neutron_order),
tenant_resource=True)
class NeutronPort(NeutronMixin, base.ResourceManager):
class NeutronPort(NeutronMixin):
def delete(self):
if self.raw_resource["device_owner"] == "network:router_interface":
@ -129,22 +131,30 @@ class NeutronPort(NeutronMixin, base.ResourceManager):
@base.resource("neutron", "router", order=next(_neutron_order),
tenant_resource=True)
class NeutronRouter(NeutronMixin, base.ResourceManager):
class NeutronRouter(NeutronMixin):
pass
@base.resource("neutron", "subnet", order=next(_neutron_order),
tenant_resource=True)
class NeutronSubnet(NeutronMixin, base.ResourceManager):
class NeutronSubnet(NeutronMixin):
pass
@base.resource("neutron", "network", order=next(_neutron_order),
tenant_resource=True)
class NeutronNetwork(NeutronMixin, base.ResourceManager):
class NeutronNetwork(NeutronMixin):
pass
@base.resource("neutron", "quota", order=next(_neutron_order),
admin_required=True, tenant_resource=True)
class NeutronQuota(QuotaMixin, NeutronMixin):
def delete(self):
self._manager().delete_quota(self.tenant_uuid)
# CINDER
_cinder_order = get_order(400)

View File

@ -30,8 +30,8 @@ class Quotas(utils.QuotasScenario):
:param max_quota: Max value to be updated for quota.
"""
tenant_id = self.context["user"]["tenant_id"]
self._update_quotas("nova", tenant_id, max_quota)
self._update_quotas("nova", self.context["tenant"]["id"],
max_quota)
@validation.required_services(consts.Service.NOVA)
@validation.required_openstack(admin=True, users=True)
@ -42,9 +42,9 @@ class Quotas(utils.QuotasScenario):
:param max_quota: Max value to be updated for quota.
"""
tenant_id = self.context["user"]["tenant_id"]
self._update_quotas("nova", tenant_id, max_quota)
self._delete_quotas("nova", tenant_id)
self._update_quotas("nova", self.context["tenant"]["id"],
max_quota)
self._delete_quotas("nova", self.context["tenant"]["id"])
@validation.required_services(consts.Service.CINDER)
@validation.required_openstack(admin=True, users=True)
@ -54,8 +54,8 @@ class Quotas(utils.QuotasScenario):
:param max_quota: Max value to be updated for quota.
"""
tenant_id = self.context["user"]["tenant_id"]
self._update_quotas("cinder", tenant_id, max_quota)
self._update_quotas("cinder", self.context["tenant"]["id"],
max_quota)
@validation.required_services(consts.Service.CINDER)
@validation.required_openstack(admin=True, users=True)
@ -65,6 +65,18 @@ class Quotas(utils.QuotasScenario):
:param max_quota: Max value to be updated for quota.
"""
tenant_id = self.context["user"]["tenant_id"]
self._update_quotas("cinder", tenant_id, max_quota)
self._delete_quotas("cinder", tenant_id)
self._update_quotas("cinder", self.context["tenant"]["id"],
max_quota)
self._delete_quotas("cinder", self.context["tenant"]["id"])
@validation.required_services(consts.Service.NEUTRON)
@validation.required_openstack(admin=True, users=True)
@base.scenario(context={"admin_cleanup": ["neutron.quota"]})
def neutron_update(self, max_quota=1024):
"""Update quotas for neutron.
:param max_quota: Max value to be updated for quota.
"""
quota_update_fn = self.admin_clients("neutron").update_quota
self._update_quotas("neutron", self.context["tenant"]["id"],
max_quota, quota_update_fn)

View File

@ -22,16 +22,23 @@ class QuotasScenario(base.Scenario):
"""Base class for quotas scenarios with basic atomic actions."""
@base.atomic_action_timer("quotas.update_quotas")
def _update_quotas(self, component, tenant_id, max_quota=1024):
"""Update quotas.
def _update_quotas(self, component, tenant_id, max_quota=1024,
quota_update_fn=None):
"""Updates quotas.
:param component: Component for the quotas.
:param tenant_id: The project_id for the quotas to be updated.
:param max_quota: Max value to be updated for quota.
:param quota_update_fn: Client quota update function.
Standard OpenStack clients use quotas.update().
Use `quota_update_fn` to override for non-standard clients.
:returns: Updated quotas dictionary.
"""
quotas = self._generate_quota_values(max_quota, component)
if quota_update_fn:
return quota_update_fn(tenant_id, **quotas)
return self.admin_clients(component).quotas.update(tenant_id, **quotas)
@base.atomic_action_timer("quotas.delete_quotas")
@ -62,4 +69,10 @@ class QuotasScenario(base.Scenario):
"snapshots": random.randint(-1, max_quota),
"gigabytes": random.randint(-1, max_quota),
}
elif component == "neutron":
quota = {}
for key in ["network", "subnet", "port", "router", "floatingip",
"security_group", "security_group_rule"]:
quota[key] = random.randint(-1, max_quota)
quotas = {"body": {"quota": quota}}
return quotas

View File

@ -92,7 +92,7 @@ class Tempest(object):
@staticmethod
def _is_git_repo(directory):
# will suppress git output
with open(os.devnull, 'w') as devnull:
with open(os.devnull, "w") as devnull:
return os.path.isdir(directory) and not subprocess.call(
"git status", shell=True,
stdout=devnull, stderr=subprocess.STDOUT,

View File

@ -0,0 +1,20 @@
{
"Quotas.neutron_update": [
{
"args": {
"max_quota": 1024
},
"runner": {
"type": "constant",
"times": 10,
"concurrency": 2
},
"context": {
"users": {
"tenants": 3,
"users_per_tenant": 2
}
}
}
]
}

View File

@ -0,0 +1,13 @@
---
Quotas.neutron_update:
-
args:
max_quota: 1024
runner:
type: "constant"
times: 10
concurrency: 2
context:
users:
tenants: 3
users_per_tenant: 2

View File

@ -172,6 +172,21 @@ class NeutronPortTestCase(test.TestCase):
raw_res["device_id"], {"port_id": raw_res["id"]})
class NeutronQuotaTestCase(test.TestCase):
@mock.patch("%s.NeutronQuota._manager" % BASE)
def test_delete(self, mock_manager):
user = mock.MagicMock()
resources.NeutronQuota(user=user, tenant_uuid="fake").delete()
mock_manager().delete_quota.assert_called_once_with("fake")
def test__manager(self):
admin = mock.MagicMock(neutron=mock.Mock(return_value="foo"))
res = resources.NeutronQuota(admin=admin, tenant_uuid="fake")
res._manager()
self.assertEqual("foo", getattr(admin, res._service)())
class GlanceImageTestCase(test.TestCase):
@mock.patch("%s.GlanceImage._manager" % BASE)
@ -205,6 +220,15 @@ class CeilometerTestCase(test.TestCase):
q=[{"field": "project_id", "op": "eq", "value": ceil.tenant_uuid}])
class ZaqarQueuesTestCase(test.TestCase):
def test_list(self):
user = mock.Mock()
zaqar = resources.ZaqarQueues(user=user)
zaqar.list()
user.zaqar().queues.assert_called_once_with()
class KeystoneMixinTestCase(test.TestCase):
def test_is_deleted(self):

View File

@ -235,7 +235,7 @@ class QuotasTestCase(test.TestCase):
@mock.patch("rally.benchmark.context.quotas.nova_quotas.NovaQuotas")
@mock.patch("rally.benchmark.context.quotas.cinder_quotas.CinderQuotas")
@mock.patch("rally.benchmark.context.quotas.neutron_quotas.NeutronQuotas")
def test_no_quotas(self, mock_neutron_quotas, mock_cinder_quotas,
def test_no_quotas(self, mock_neutron_quota, mock_cinder_quotas,
mock_nova_quotas, mock_osclients):
ctx = copy.deepcopy(self.context)
if "quotas" in ctx["config"]:
@ -245,14 +245,14 @@ class QuotasTestCase(test.TestCase):
quotas_ctx.setup()
self.assertFalse(mock_cinder_quotas.update.called)
self.assertFalse(mock_nova_quotas.update.called)
self.assertFalse(mock_neutron_quotas.update.called)
self.assertFalse(mock_neutron_quota.update.called)
self.assertFalse(mock_cinder_quotas.delete.called)
self.assertFalse(mock_nova_quotas.delete.called)
self.assertFalse(mock_neutron_quotas.delete.called)
self.assertFalse(mock_neutron_quota.delete.called)
@mock.patch("rally.benchmark.context.quotas.nova_quotas.NovaQuotas")
def test_exception_during_cleanup(self, mock_nova_quotas):
def test_exception_during_cleanup_nova(self, mock_nova_quotas):
mock_nova_quotas.delete.side_effect = Exception("boom")
@ -264,3 +264,17 @@ class QuotasTestCase(test.TestCase):
self.assertEqual(mock_nova_quotas().delete.call_count,
len(self.context["tenants"]))
@mock.patch("rally.benchmark.context.quotas.neutron_quotas.NeutronQuotas")
def test_exception_during_cleanup_neutron(self, mock_neutron_quota):
mock_neutron_quota.delete.side_effect = Exception("boom")
ctx = copy.deepcopy(self.context)
ctx["config"]["quotas"] = {"neutron": {"cpu": 1}}
# NOTE(boris-42): ensure that cleanup didn't raise exceptions.
quotas.Quotas(ctx).cleanup()
self.assertEqual(mock_neutron_quota().delete.call_count,
len(self.context["tenants"]))

View File

@ -217,7 +217,7 @@ class NovaServersTestCase(test.TestCase):
scenario._create_volume.assert_called_once_with(5, imageRef="img")
scenario._boot_server.assert_called_once_with(
"img", 0,
block_device_mapping={'vda': 'volume_id:::1'},
block_device_mapping={"vda": "volume_id:::1"},
fakearg="f")
scenario.sleep_between.assert_called_once_with(10, 20)
scenario._delete_server.assert_called_once_with(fake_server,

View File

@ -99,8 +99,8 @@ class NovaScenarioTestCase(test.TestCase):
def test__boot_server(self, mock_clients):
mock_clients("nova").servers.create.return_value = self.server
nova_scenario = utils.NovaScenario(context={})
return_server = nova_scenario._boot_server('image_id',
'flavor_id')
return_server = nova_scenario._boot_server("image_id",
"flavor_id")
self._test_assert_called_once_with(
self.wait_for.mock, self.server,
CONF.benchmark.nova_server_boot_poll_interval,
@ -145,7 +145,7 @@ class NovaScenarioTestCase(test.TestCase):
def test__boot_server_with_ssh(self, mock_clients):
mock_clients("nova").servers.create.return_value = self.server
nova_scenario = utils.NovaScenario(context={"allow_ssh": "test"})
return_server = nova_scenario._boot_server('image_id', 'flavor_id')
return_server = nova_scenario._boot_server("image_id", "flavor_id")
self._test_assert_called_once_with(
self.wait_for.mock, self.server,
CONF.benchmark.nova_server_boot_poll_interval,
@ -160,8 +160,8 @@ class NovaScenarioTestCase(test.TestCase):
mock_clients("nova").servers.create.return_value = self.server
nova_scenario = utils.NovaScenario(context={"allow_ssh": "new"})
return_server = nova_scenario._boot_server(
'image_id', 'flavor_id',
security_groups=['test1'])
"image_id", "flavor_id",
security_groups=["test1"])
self._test_assert_called_once_with(
self.wait_for.mock, self.server,
CONF.benchmark.nova_server_boot_poll_interval,
@ -176,8 +176,8 @@ class NovaScenarioTestCase(test.TestCase):
mock_clients("nova").servers.create.return_value = self.server
nova_scenario = utils.NovaScenario(context={"allow_ssh": "test1"})
return_server = nova_scenario._boot_server(
'image_id', 'flavor_id',
security_groups=['test1'])
"image_id", "flavor_id",
security_groups=["test1"])
self._test_assert_called_once_with(
self.wait_for.mock, self.server,
CONF.benchmark.nova_server_boot_poll_interval,

View File

@ -21,14 +21,21 @@ from tests.unit import test
class QuotasTestCase(test.TestCase):
def setUp(self):
super(QuotasTestCase, self).setUp()
self.context = {
"user": {"tenant_id": "fake"},
"tenant": {"id": "fake"}
}
def test_nova_update(self):
scenario = quotas.Quotas(context={"user": {"tenant_id": "fake"}})
scenario = quotas.Quotas(self.context)
scenario._update_quotas = mock.MagicMock()
scenario.nova_update(max_quota=1024)
scenario._update_quotas.assert_called_once_with("nova", "fake", 1024)
def test_nova_update_and_delete(self):
scenario = quotas.Quotas(context={"user": {"tenant_id": "fake"}})
scenario = quotas.Quotas(self.context)
scenario._update_quotas = mock.MagicMock()
scenario._delete_quotas = mock.MagicMock()
scenario.nova_update_and_delete(max_quota=1024)
@ -36,15 +43,26 @@ class QuotasTestCase(test.TestCase):
scenario._delete_quotas.assert_called_once_with("nova", "fake")
def test_cinder_update(self):
scenario = quotas.Quotas(context={"user": {"tenant_id": "fake"}})
scenario = quotas.Quotas(self.context)
scenario._update_quotas = mock.MagicMock()
scenario.cinder_update(max_quota=1024)
scenario._update_quotas.assert_called_once_with("cinder", "fake", 1024)
def test_cinder_update_and_delete(self):
scenario = quotas.Quotas(context={"user": {"tenant_id": "fake"}})
scenario = quotas.Quotas(self.context)
scenario._update_quotas = mock.MagicMock()
scenario._delete_quotas = mock.MagicMock()
scenario.cinder_update_and_delete(max_quota=1024)
scenario._update_quotas.assert_called_once_with("cinder", "fake", 1024)
scenario._delete_quotas.assert_called_once_with("cinder", "fake")
@mock.patch("rally.benchmark.scenarios.base.Scenario.admin_clients")
def test_neutron_update(self, mock_clients):
scenario = quotas.Quotas(self.context)
scenario._update_quotas = mock.MagicMock()
mock_quota_update_fn = mock_clients().update_quota
scenario.neutron_update(max_quota=1024)
scenario._update_quotas.assert_called_once_with("neutron", "fake",
1024,
mock_quota_update_fn)

View File

@ -52,6 +52,34 @@ class QuotasScenarioTestCase(test.TestCase):
self._test_atomic_action_timer(scenario.atomic_actions(),
"quotas.update_quotas")
def test__update_quotas_fn(self):
tenant_id = "fake_tenant"
quotas = {
"metadata_items": 10,
"key_pairs": 10,
"injected_file_content_bytes": 1024,
"injected_file_path_bytes": 1024,
"ram": 5120,
"instances": 10,
"injected_files": 10,
"cores": 10,
}
fake_nova = fakes.FakeNovaClient()
fake_nova.quotas.update = mock.MagicMock(return_value=quotas)
fake_clients = fakes.FakeClients()
fake_clients._nova = fake_nova
scenario = utils.QuotasScenario(admin_clients=fake_clients)
scenario._generate_quota_values = mock.MagicMock(return_value=quotas)
mock_quota = mock.Mock(return_value=quotas)
result = scenario._update_quotas("nova", tenant_id,
quota_update_fn=mock_quota)
self.assertEqual(quotas, result)
self._test_atomic_action_timer(scenario.atomic_actions(),
"quotas.update_quotas")
def test__generate_quota_values_nova(self):
max_quota = 1024
scenario = utils.QuotasScenario(admin_clients=fakes.FakeClients())
@ -66,6 +94,15 @@ class QuotasScenarioTestCase(test.TestCase):
for k, v in six.iteritems(quotas):
self.assertTrue(-1 <= v <= max_quota)
def test__generate_quota_values_neutron(self):
max_quota = 1024
scenario = utils.QuotasScenario(admin_clients=fakes.FakeClients())
quotas = scenario._generate_quota_values(max_quota, "neutron")
for v in six.itervalues(quotas):
for v1 in six.itervalues(v):
for v2 in six.itervalues(v1):
self.assertTrue(-1 <= v2 <= max_quota)
def test__delete_quotas(self):
tenant_id = "fake_tenant"
fake_nova = fakes.FakeNovaClient()