[cleanup] Introduce NoName obj for res without name field

Instances of NoName obj should be used as a woraround for such resources
like floatingip, which doesn't have "name" field, to pass name matching
checks.

Change-Id: I535317ce09212a6f1cb4c6c3f180ba8f945c18f6
This commit is contained in:
Andrey Kurilin 2017-03-16 12:56:45 +02:00
parent 57f08ee7f3
commit 54a1254a5a
6 changed files with 104 additions and 7 deletions

View File

@ -32,6 +32,22 @@ CONF.register_group(cleanup_group)
CONF.register_opts(CLEANUP_OPTS, cleanup_group) CONF.register_opts(CLEANUP_OPTS, cleanup_group)
# NOTE(andreykurilin): There are cases when there is no way to use any kind
# of "name" for resource as an identifier of alignment resource to the
# particular task run and even to Rally itself. Previously, we used empty
# strings as a workaround for name matching specific templates, but
# theoretically such behaviour can hide other cases when resource should have
# a name property, but it is missed.
# Let's use instances of specific class to return as a name of resources
# which do not have names at all.
class NoName(object):
def __init__(self, resource_type):
self.resource_type = resource_type
def __repr__(self):
return "<NoName %s resource>" % self.resource_type
def resource(service, resource, order=0, admin_required=False, def resource(service, resource, order=0, admin_required=False,
perform_for_admin_only=False, tenant_resource=False, perform_for_admin_only=False, tenant_resource=False,
max_attempts=3, timeout=CONF.cleanup.resource_deletion_timeout, max_attempts=3, timeout=CONF.cleanup.resource_deletion_timeout,

View File

@ -183,9 +183,10 @@ class SeekAndDestroy(object):
user=self._get_cached_client(user), user=self._get_cached_client(user),
tenant_uuid=user and user["tenant_id"]) tenant_uuid=user and user["tenant_id"])
if manager.name() == "" or rutils.name_matches_object( if (isinstance(manager.name(), base.NoName) or
rutils.name_matches_object(
manager.name(), *self.resource_classes, manager.name(), *self.resource_classes,
task_id=self.task_id, exact=False): task_id=self.task_id, exact=False)):
self._delete_single_resource(manager) self._delete_single_resource(manager)
def exterminate(self): def exterminate(self):

View File

@ -20,6 +20,7 @@ from oslo_config import cfg
from saharaclient.api import base as saharaclient_base from saharaclient.api import base as saharaclient_base
from rally.common import logging from rally.common import logging
from rally import consts
from rally.plugins.openstack.cleanup import base from rally.plugins.openstack.cleanup import base
from rally.plugins.openstack.services.identity import identity from rally.plugins.openstack.services.identity import identity
from rally.plugins.openstack.wrappers import glance as glance_wrapper from rally.plugins.openstack.wrappers import glance as glance_wrapper
@ -317,7 +318,7 @@ class NeutronMixin(SynchronizedDeletion, base.ResourceManager):
return self.raw_resource["id"] return self.raw_resource["id"]
def name(self): def name(self):
return self.raw_resource.get("name", "") return self.raw_resource["name"]
def delete(self): def delete(self):
delete_method = getattr(self._manager(), "delete_%s" % self._resource) delete_method = getattr(self._manager(), "delete_%s" % self._resource)
@ -378,16 +379,33 @@ class NeutronV2Loadbalancer(NeutronLbaasV2Mixin):
return False return False
# NOTE(andreykurilin): There are scenarios which uses unified way for creating
# and associating floating ips. They do not care about nova-net and neutron.
# We should clean floating IPs for them, but hardcoding "neutron.floatingip"
# cleanup resource should not work in case of Nova-Net.
# Since we are planning to abandon support of Nova-Network in next rally
# release, let's apply dirty workaround to handle all resources.
@base.resource("neutron", "floatingip", order=next(_neutron_order), @base.resource("neutron", "floatingip", order=next(_neutron_order),
tenant_resource=True) tenant_resource=True)
class NeutronFloatingIP(NeutronMixin): class NeutronFloatingIP(NeutronMixin):
pass def name(self):
return base.NoName(self._resource)
def list(self):
if consts.ServiceType.NETWORK not in self.user.services():
return []
return super(NeutronFloatingIP, self).list()
@base.resource("neutron", "port", order=next(_neutron_order), @base.resource("neutron", "port", order=next(_neutron_order),
tenant_resource=True) tenant_resource=True)
class NeutronPort(NeutronMixin): class NeutronPort(NeutronMixin):
def name(self):
# TODO(andreykurilin): return NoName instance only in case of "name"
# field is missed
return base.NoName(self._resource)
def delete(self): def delete(self):
if (self.raw_resource["device_owner"] in if (self.raw_resource["device_owner"] in
("network:router_interface", ("network:router_interface",

View File

@ -813,7 +813,7 @@ class BootAndRebuildServer(utils.NovaScenario, cinder_utils.CinderScenario):
@validation.required_services(consts.Service.NOVA) @validation.required_services(consts.Service.NOVA)
@validation.required_openstack(users=True) @validation.required_openstack(users=True)
@validation.required_contexts("network") @validation.required_contexts("network")
@scenario.configure(context={"cleanup": ["nova"]}, @scenario.configure(context={"cleanup": ["nova", "neutron.floatingip"]},
name="NovaServers.boot_and_associate_floating_ip") name="NovaServers.boot_and_associate_floating_ip")
class BootAndAssociateFloatingIp(utils.NovaScenario, class BootAndAssociateFloatingIp(utils.NovaScenario,
cinder_utils.CinderScenario): cinder_utils.CinderScenario):
@ -947,7 +947,7 @@ class BootServerFromVolumeSnapshot(utils.NovaScenario,
@validation.required_services(consts.Service.NOVA) @validation.required_services(consts.Service.NOVA)
@validation.required_openstack(users=True) @validation.required_openstack(users=True)
@validation.required_contexts("network") @validation.required_contexts("network")
@scenario.configure(context={"cleanup": ["nova"]}, @scenario.configure(context={"cleanup": ["nova", "neutron.floatingip"]},
name="NovaServers.boot_server_associate_and" name="NovaServers.boot_server_associate_and"
"_dissociate_floating_ip") "_dissociate_floating_ip")
class BootServerAssociateAndDissociateFloatingIP(utils.NovaScenario): class BootServerAssociateAndDissociateFloatingIP(utils.NovaScenario):

View File

@ -328,6 +328,28 @@ class SeekAndDestroyTestCase(test.TestCase):
mock__delete_single_resource.assert_called_once_with( mock__delete_single_resource.assert_called_once_with(
mock_mgr.return_value) mock_mgr.return_value)
@mock.patch("rally.common.utils.name_matches_object")
@mock.patch("%s.SeekAndDestroy._get_cached_client" % BASE)
@mock.patch("%s.SeekAndDestroy._delete_single_resource" % BASE)
def test__consumer_with_noname_resource(self, mock__delete_single_resource,
mock__get_cached_client,
mock_name_matches_object):
mock_mgr = mock.MagicMock(__name__="Test")
mock_mgr.return_value.name.return_value = True
task_id = "task_id"
mock_name_matches_object.return_value = False
consumer = manager.SeekAndDestroy(mock_mgr, None, None,
task_id=task_id)._consumer
consumer(None, (None, None, "res"))
self.assertFalse(mock__delete_single_resource.called)
mock_mgr.return_value.name.return_value = base.NoName("foo")
consumer(None, (None, None, "res"))
mock__delete_single_resource.assert_called_once_with(
mock_mgr.return_value)
@mock.patch("%s.broker.run" % BASE) @mock.patch("%s.broker.run" % BASE)
def test_exterminate(self, mock_broker_run): def test_exterminate(self, mock_broker_run):
manager_cls = mock.MagicMock(_threads=5) manager_cls = mock.MagicMock(_threads=5)

View File

@ -20,6 +20,7 @@ from neutronclient.common import exceptions as neutron_exceptions
from novaclient import exceptions as nova_exc from novaclient import exceptions as nova_exc
from watcherclient.common.apiclient import exceptions as watcher_exceptions from watcherclient.common.apiclient import exceptions as watcher_exceptions
from rally import consts
from rally.plugins.openstack.cleanup import resources from rally.plugins.openstack.cleanup import resources
from tests.unit import test from tests.unit import test
@ -439,6 +440,32 @@ class NeutronV2LoadbalancerTestCase(test.TestCase):
neutron_lb.id()) neutron_lb.id())
class NeutronFloatingIPTestCase(test.TestCase):
def test_name(self):
fips = resources.NeutronFloatingIP({"name": "foo"})
self.assertIsInstance(fips.name(), resources.base.NoName)
def test_list(self):
fips = {"floatingips": [{"tenant_id": "foo", "id": "foo"}]}
user = mock.MagicMock()
user.services.return_value = {}
user.neutron.return_value.list_floatingips.return_value = fips
self.assertEqual(
[], resources.NeutronFloatingIP(user=user,
tenant_uuid="foo").list())
self.assertFalse(user.neutron.return_value.list_floatingips.called)
user.services.return_value = {
consts.ServiceType.NETWORK: consts.Service.NEUTRON}
self.assertEqual(fips["floatingips"], list(
resources.NeutronFloatingIP(user=user, tenant_uuid="foo").list()))
user.neutron.return_value.list_floatingips.assert_called_once_with(
tenant_id="foo")
class NeutronPortTestCase(test.TestCase): class NeutronPortTestCase(test.TestCase):
def test_delete(self): def test_delete(self):
@ -472,6 +499,19 @@ class NeutronPortTestCase(test.TestCase):
user.neutron().remove_interface_router.assert_called_once_with( user.neutron().remove_interface_router.assert_called_once_with(
raw_res["device_id"], {"port_id": raw_res["id"]}) raw_res["device_id"], {"port_id": raw_res["id"]})
def test_name(self):
raw_res = {
"device_owner": "network:router_interface",
"id": "some_id",
"device_id": "dev_id",
"name": "foo"
}
user = mock.MagicMock()
self.assertIsInstance(
resources.NeutronPort(resource=raw_res, user=user).name(),
resources.base.NoName)
@ddt.ddt @ddt.ddt
class NeutronSecurityGroupTestCase(test.TestCase): class NeutronSecurityGroupTestCase(test.TestCase):