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:
parent
fb4058f7cf
commit
5951c7e043
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
20
samples/tasks/scenarios/quotas/neutron-update.json
Normal file
20
samples/tasks/scenarios/quotas/neutron-update.json
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
13
samples/tasks/scenarios/quotas/neutron-update.yaml
Normal file
13
samples/tasks/scenarios/quotas/neutron-update.yaml
Normal 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
|
@ -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):
|
||||
|
@ -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"]))
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user