Move OpenStack cleanup utils out of context
These will be used to do out-of-band cleanup in the near future, so they are no longer specific to the cleanup context. Change-Id: I5b5c7c89b04a080c487936cc3df0df24929ced31 Implements: blueprint cleanup-refactoring
This commit is contained in:
parent
63bbd48892
commit
b4b746dae9
@ -94,27 +94,27 @@ cleanup process, but it demonstrates the basic idea:
|
||||
|
||||
A fair bit of functionality will need to be added to support this:
|
||||
|
||||
* ``rally.plugins.openstack.context.cleanup.manager.cleanup()`` will
|
||||
* ``rally.plugins.openstack.cleanup.manager.cleanup()`` will
|
||||
need to accept a keyword argument specifying the type of
|
||||
cleanup. This should be a superclass that will be used to discover
|
||||
the subclasses to delete resources for. It will be passed to
|
||||
``rally.plugins.openstack.context.cleanup.manager.SeekAndDestroy``,
|
||||
``rally.plugins.openstack.cleanup.manager.SeekAndDestroy``,
|
||||
which will also need to accept the argument and generate the list of
|
||||
classes.
|
||||
* ``rally.plugins.openstack.context.cleanup.base``,
|
||||
``rally.plugins.openstack.context.cleanup.manager`` and
|
||||
``rally.plugins.openstack.context.cleanup.resources`` need to be
|
||||
* ``rally.plugins.openstack.cleanup.base``,
|
||||
``rally.plugins.openstack.cleanup.manager`` and
|
||||
``rally.plugins.openstack.cleanup.resources`` need to be
|
||||
moved out of the context space, since they will be used not only by
|
||||
the cleanup context to do scenario cleanup, but also to do
|
||||
out-of-band cleanup of all resources.
|
||||
* A new function, ``name()``, will need to be added to
|
||||
``rally.plugins.openstack.context.cleanup.base.ResourceManager``
|
||||
``rally.plugins.openstack.cleanup.base.ResourceManager``
|
||||
so that we can determine the name of a resource in order to match it.
|
||||
* A ``task_id`` keyword argument will be added to
|
||||
``name_matches_object`` and ``name_matches_pattern`` in order to
|
||||
ensure that we only match names from the currently-running
|
||||
task. This will need to be passed along starting with
|
||||
``rally.plugins.openstack.context.cleanup.manager.cleanup()``, and
|
||||
``rally.plugins.openstack.cleanup.manager.cleanup()``, and
|
||||
added as a keyword argument to every intermediate function.
|
||||
|
||||
Additionally, a new top-level command will be added::
|
||||
@ -170,7 +170,7 @@ Work Items
|
||||
#. Modify ``name_matches_{object,pattern}`` to accept a task ID.
|
||||
#. Add ``name()`` functions to all ``ResourceManager`` subclasses.
|
||||
#. Move
|
||||
``rally.plugins.openstack.context.cleanup.manager.{base,manager,resources}``
|
||||
``rally.plugins.openstack.cleanup.manager.{base,manager,resources}``
|
||||
to ``rally.plugins.openstack.cleanup``.
|
||||
#. Modify ``rally.plugins.openstack.cleanup.manager.cleanup()`` to
|
||||
accept a task ID and a superclass, pass them along to
|
||||
|
121
rally/plugins/openstack/cleanup/base.py
Normal file
121
rally/plugins/openstack/cleanup/base.py
Normal file
@ -0,0 +1,121 @@
|
||||
# Copyright 2014: Mirantis Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 oslo_config import cfg
|
||||
|
||||
from rally.task import utils
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
CLEANUP_OPTS = [
|
||||
cfg.IntOpt("resource_deletion_timeout", default=600,
|
||||
help="A timeout in seconds for deleting resources")
|
||||
]
|
||||
cleanup_group = cfg.OptGroup(name="cleanup", title="Cleanup Options")
|
||||
CONF.register_group(cleanup_group)
|
||||
CONF.register_opts(CLEANUP_OPTS, cleanup_group)
|
||||
|
||||
|
||||
def resource(service, resource, order=0, admin_required=False,
|
||||
perform_for_admin_only=False, tenant_resource=False,
|
||||
max_attempts=3, timeout=CONF.cleanup.resource_deletion_timeout,
|
||||
interval=1, threads=20):
|
||||
"""Decorator that overrides resource specification.
|
||||
|
||||
Just put it on top of your resource class and specify arguments that you
|
||||
need.
|
||||
|
||||
:param service: It is equal to client name for corresponding service.
|
||||
E.g. "nova", "cinder" or "zaqar"
|
||||
:param resource: Client manager name for resource. E.g. in case of
|
||||
nova.servers you should write here "servers"
|
||||
:param order: Used to adjust priority of cleanup for different resource
|
||||
types
|
||||
:param admin_required: Admin user is required
|
||||
:param perform_for_admin_only: Perform cleanup for admin user only
|
||||
:param tenant_resource: Perform deletion only 1 time per tenant
|
||||
:param max_attempts: Max amount of attempts to delete single resource
|
||||
:param timeout: Max duration of deletion in seconds
|
||||
:param interval: Resource status pooling interval
|
||||
:param threads: Amount of threads (workers) that are deleting resources
|
||||
simultaneously
|
||||
"""
|
||||
|
||||
def inner(cls):
|
||||
# TODO(boris-42): This can be written better I believe =)
|
||||
cls._service = service
|
||||
cls._resource = resource
|
||||
cls._order = order
|
||||
cls._admin_required = admin_required
|
||||
cls._perform_for_admin_only = perform_for_admin_only
|
||||
cls._max_attempts = max_attempts
|
||||
cls._timeout = timeout
|
||||
cls._interval = interval
|
||||
cls._threads = threads
|
||||
cls._tenant_resource = tenant_resource
|
||||
|
||||
return cls
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
@resource(service=None, resource=None)
|
||||
class ResourceManager(object):
|
||||
"""Base class for cleanup plugins for specific resources.
|
||||
|
||||
You should use @resource decorator to specify major configuration of
|
||||
resource manager. Usually you should specify: service, resource and order.
|
||||
|
||||
If project python client is very specific, you can override delete(),
|
||||
list() and is_deleted() methods to make them fit to your case.
|
||||
"""
|
||||
|
||||
def __init__(self, resource=None, admin=None, user=None, tenant_uuid=None):
|
||||
self.admin = admin
|
||||
self.user = user
|
||||
self.raw_resource = resource
|
||||
self.tenant_uuid = tenant_uuid
|
||||
|
||||
def _manager(self):
|
||||
client = self._admin_required and self.admin or self.user
|
||||
return getattr(getattr(client, self._service)(), self._resource)
|
||||
|
||||
def id(self):
|
||||
"""Returns id of resource."""
|
||||
return self.raw_resource.id
|
||||
|
||||
def is_deleted(self):
|
||||
"""Checks if the resource is deleted.
|
||||
|
||||
Fetch resource by id from service and check it status.
|
||||
In case of NotFound or status is DELETED or DELETE_COMPLETE returns
|
||||
True, otherwise False.
|
||||
"""
|
||||
try:
|
||||
resource = self._manager().get(self.id())
|
||||
except Exception as e:
|
||||
return getattr(e, "code", getattr(e, "http_status", 400)) == 404
|
||||
|
||||
return utils.get_status(resource) in ("DELETED", "DELETE_COMPLETE")
|
||||
|
||||
def delete(self):
|
||||
"""Delete resource that corresponds to instance of this class."""
|
||||
self._manager().delete(self.id())
|
||||
|
||||
def list(self):
|
||||
"""List all resources specific for admin or user."""
|
||||
return self._manager().list()
|
282
rally/plugins/openstack/cleanup/manager.py
Normal file
282
rally/plugins/openstack/cleanup/manager.py
Normal file
@ -0,0 +1,282 @@
|
||||
# Copyright 2014: Mirantis Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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
|
||||
|
||||
from rally.common import broker
|
||||
from rally.common.i18n import _
|
||||
from rally.common import logging
|
||||
from rally.common.plugin import discover
|
||||
from rally.common import utils as rutils
|
||||
from rally import osclients
|
||||
from rally.plugins.openstack.cleanup import base
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SeekAndDestroy(object):
|
||||
|
||||
def __init__(self, manager_cls, admin, users):
|
||||
"""Resource deletion class.
|
||||
|
||||
This class contains method exterminate() that finds and deletes
|
||||
all resources created by Rally.
|
||||
|
||||
:param manager_cls: subclass of base.ResourceManager
|
||||
:param admin: admin credential like in context["admin"]
|
||||
:param users: users credentials like in context["users"]
|
||||
"""
|
||||
self.manager_cls = manager_cls
|
||||
self.admin = admin
|
||||
self.users = users or []
|
||||
|
||||
@staticmethod
|
||||
def _get_cached_client(user, cache=None):
|
||||
"""Simplifies initialization and caching OpenStack clients."""
|
||||
|
||||
if not user:
|
||||
return None
|
||||
|
||||
if not isinstance(cache, dict):
|
||||
return osclients.Clients(user["credential"])
|
||||
|
||||
key = user["credential"]
|
||||
if key not in cache:
|
||||
cache[key] = osclients.Clients(key)
|
||||
|
||||
return cache[key]
|
||||
|
||||
def _delete_single_resource(self, resource):
|
||||
"""Safe resource deletion with retries and timeouts.
|
||||
|
||||
Send request to delete resource, in case of failures repeat it few
|
||||
times. After that pull status of resource until it's deleted.
|
||||
|
||||
Writes in LOG warning with UUID of resource that wasn't deleted
|
||||
|
||||
:param resource: instance of resource manager initiated with resource
|
||||
that should be deleted.
|
||||
"""
|
||||
|
||||
msg_kw = {
|
||||
"uuid": resource.id(),
|
||||
"service": resource._service,
|
||||
"resource": resource._resource
|
||||
}
|
||||
|
||||
LOG.debug("Deleting %(service)s %(resource)s object %(uuid)s" %
|
||||
msg_kw)
|
||||
|
||||
try:
|
||||
rutils.retry(resource._max_attempts, resource.delete)
|
||||
except Exception as e:
|
||||
msg_kw["reason"] = e
|
||||
LOG.warning(
|
||||
_("Resource deletion failed, max retries exceeded for "
|
||||
"%(service)s.%(resource)s: %(uuid)s. Reason: %(reason)s")
|
||||
% msg_kw)
|
||||
if logging.is_debug():
|
||||
LOG.exception(e)
|
||||
else:
|
||||
started = time.time()
|
||||
failures_count = 0
|
||||
while time.time() - started < resource._timeout:
|
||||
try:
|
||||
if resource.is_deleted():
|
||||
return
|
||||
except Exception as e:
|
||||
LOG.warning(
|
||||
_("Seems like %s.%s.is_deleted(self) method is broken "
|
||||
"It shouldn't raise any exceptions.")
|
||||
% (resource.__module__, type(resource).__name__))
|
||||
LOG.exception(e)
|
||||
|
||||
# NOTE(boris-42): Avoid LOG spamming in case of bad
|
||||
# is_deleted() method
|
||||
failures_count += 1
|
||||
if failures_count > resource._max_attempts:
|
||||
break
|
||||
|
||||
finally:
|
||||
time.sleep(resource._interval)
|
||||
|
||||
LOG.warning(_("Resource deletion failed, timeout occurred for "
|
||||
"%(service)s.%(resource)s: %(uuid)s.")
|
||||
% msg_kw)
|
||||
|
||||
def _gen_publisher(self):
|
||||
"""Returns publisher for deletion jobs.
|
||||
|
||||
This method iterates over all users, lists all resources
|
||||
(using manager_cls) and puts jobs for deletion.
|
||||
|
||||
Every deletion job contains tuple with two values: user and resource
|
||||
uuid that should be deleted.
|
||||
|
||||
In case of tenant based resource, uuids are fetched only from one user
|
||||
per tenant.
|
||||
"""
|
||||
|
||||
def publisher(queue):
|
||||
|
||||
def _publish(admin, user, manager):
|
||||
try:
|
||||
for raw_resource in rutils.retry(3, manager.list):
|
||||
queue.append((admin, user, raw_resource))
|
||||
except Exception as e:
|
||||
LOG.warning(
|
||||
_("Seems like %s.%s.list(self) method is broken. "
|
||||
"It shouldn't raise any exceptions.")
|
||||
% (manager.__module__, type(manager).__name__))
|
||||
LOG.exception(e)
|
||||
|
||||
if self.admin and (not self.users
|
||||
or self.manager_cls._perform_for_admin_only):
|
||||
manager = self.manager_cls(
|
||||
admin=self._get_cached_client(self.admin))
|
||||
_publish(self.admin, None, manager)
|
||||
|
||||
else:
|
||||
visited_tenants = set()
|
||||
admin_client = self._get_cached_client(self.admin)
|
||||
for user in self.users:
|
||||
if (self.manager_cls._tenant_resource
|
||||
and user["tenant_id"] in visited_tenants):
|
||||
continue
|
||||
|
||||
visited_tenants.add(user["tenant_id"])
|
||||
manager = self.manager_cls(
|
||||
admin=admin_client,
|
||||
user=self._get_cached_client(user),
|
||||
tenant_uuid=user["tenant_id"])
|
||||
|
||||
_publish(self.admin, user, manager)
|
||||
|
||||
return publisher
|
||||
|
||||
def _gen_consumer(self):
|
||||
"""Generate method that consumes single deletion job."""
|
||||
|
||||
def consumer(cache, args):
|
||||
"""Execute deletion job."""
|
||||
admin, user, raw_resource = args
|
||||
|
||||
manager = self.manager_cls(
|
||||
resource=raw_resource,
|
||||
admin=self._get_cached_client(admin, cache=cache),
|
||||
user=self._get_cached_client(user, cache=cache),
|
||||
tenant_uuid=user and user["tenant_id"])
|
||||
|
||||
self._delete_single_resource(manager)
|
||||
|
||||
return consumer
|
||||
|
||||
def exterminate(self):
|
||||
"""Delete all resources for passed users, admin and resource_mgr."""
|
||||
|
||||
broker.run(self._gen_publisher(), self._gen_consumer(),
|
||||
consumers_count=self.manager_cls._threads)
|
||||
|
||||
|
||||
def list_resource_names(admin_required=None):
|
||||
"""List all resource managers names.
|
||||
|
||||
Returns all service names and all combination of service.resource names.
|
||||
|
||||
:param admin_required: None -> returns all ResourceManagers
|
||||
True -> returns only admin ResourceManagers
|
||||
False -> returns only non admin ResourceManagers
|
||||
"""
|
||||
res_mgrs = discover.itersubclasses(base.ResourceManager)
|
||||
if admin_required is not None:
|
||||
res_mgrs = filter(lambda cls: cls._admin_required == admin_required,
|
||||
res_mgrs)
|
||||
|
||||
names = set()
|
||||
for cls in res_mgrs:
|
||||
names.add(cls._service)
|
||||
names.add("%s.%s" % (cls._service, cls._resource))
|
||||
|
||||
return names
|
||||
|
||||
|
||||
def find_resource_managers(names=None, admin_required=None):
|
||||
"""Returns resource managers.
|
||||
|
||||
:param names: List of names in format <service> or <service>.<resource>
|
||||
that is used for filtering resource manager classes
|
||||
:param admin_required: None -> returns all ResourceManagers
|
||||
True -> returns only admin ResourceManagers
|
||||
False -> returns only non admin ResourceManagers
|
||||
"""
|
||||
names = set(names or [])
|
||||
|
||||
resource_managers = []
|
||||
for manager in discover.itersubclasses(base.ResourceManager):
|
||||
if admin_required is not None:
|
||||
if admin_required != manager._admin_required:
|
||||
continue
|
||||
|
||||
if (manager._service in names
|
||||
or "%s.%s" % (manager._service, manager._resource) in names):
|
||||
resource_managers.append(manager)
|
||||
|
||||
resource_managers.sort(key=lambda x: x._order)
|
||||
|
||||
found_names = set()
|
||||
for mgr in resource_managers:
|
||||
found_names.add(mgr._service)
|
||||
found_names.add("%s.%s" % (mgr._service, mgr._resource))
|
||||
|
||||
missing = names - found_names
|
||||
if missing:
|
||||
LOG.warning("Missing resource managers: %s" % ", ".join(missing))
|
||||
|
||||
return resource_managers
|
||||
|
||||
|
||||
def cleanup(names=None, admin_required=None, admin=None, users=None):
|
||||
"""Generic cleaner.
|
||||
|
||||
This method goes through all plugins. Filter those and left only plugins
|
||||
with _service from services or _resource from resources.
|
||||
|
||||
Then goes through all passed users and using cleaners cleans all related
|
||||
resources.
|
||||
|
||||
:param names: Use only resource manages that has name from this list.
|
||||
There are in as _service or
|
||||
(%s.%s % (_service, _resource)) from
|
||||
|
||||
:param admin_required: If None -> return all plugins
|
||||
If True -> return only admin plugins
|
||||
If False -> return only non admin plugins
|
||||
:param admin: rally.common.objects.Credential that corresponds to OpenStack
|
||||
admin.
|
||||
:param users: List of OpenStack users that was used during benchmarking.
|
||||
Every user has next structure:
|
||||
{
|
||||
"id": <uuid1>,
|
||||
"tenant_id": <uuid2>,
|
||||
"credential": <rally.common.objects.Credential>
|
||||
|
||||
}
|
||||
"""
|
||||
for manager in find_resource_managers(names, admin_required):
|
||||
LOG.debug("Cleaning up %(service)s %(resource)s objects" %
|
||||
{"service": manager._service,
|
||||
"resource": manager._resource})
|
||||
SeekAndDestroy(manager, admin, users).exterminate()
|
654
rally/plugins/openstack/cleanup/resources.py
Normal file
654
rally/plugins/openstack/cleanup/resources.py
Normal file
@ -0,0 +1,654 @@
|
||||
# Copyright 2014: Mirantis Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 boto import exception as boto_exception
|
||||
from neutronclient.common import exceptions as neutron_exceptions
|
||||
from saharaclient.api import base as saharaclient_base
|
||||
|
||||
from rally.common import logging
|
||||
from rally.common.plugin import discover
|
||||
from rally.common import utils
|
||||
from rally.plugins.openstack.cleanup import base
|
||||
from rally.plugins.openstack.scenarios.fuel import utils as futils
|
||||
from rally.plugins.openstack.scenarios.keystone import utils as kutils
|
||||
from rally.plugins.openstack.scenarios.nova import utils as nova_utils
|
||||
from rally.plugins.openstack.wrappers import keystone as keystone_wrapper
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_order(start):
|
||||
return iter(range(start, start + 99))
|
||||
|
||||
|
||||
class SynchronizedDeletion(object):
|
||||
|
||||
def is_deleted(self):
|
||||
return True
|
||||
|
||||
|
||||
class QuotaMixin(SynchronizedDeletion):
|
||||
|
||||
def id(self):
|
||||
return self.raw_resource
|
||||
|
||||
def delete(self):
|
||||
self._manager().delete(self.raw_resource)
|
||||
|
||||
def list(self):
|
||||
return [self.tenant_uuid] if self.tenant_uuid else []
|
||||
|
||||
|
||||
# HEAT
|
||||
|
||||
@base.resource("heat", "stacks", order=100, tenant_resource=True)
|
||||
class HeatStack(base.ResourceManager):
|
||||
pass
|
||||
|
||||
|
||||
# NOVA
|
||||
|
||||
_nova_order = get_order(200)
|
||||
|
||||
|
||||
@base.resource("nova", "servers", order=next(_nova_order))
|
||||
class NovaServer(base.ResourceManager):
|
||||
def list(self):
|
||||
"""List all servers."""
|
||||
|
||||
if hasattr(self._manager().api, "api_version"):
|
||||
# NOTE(andreykurilin): novaclient v2.27.0 includes ability to
|
||||
# return all servers(see https://review.openstack.org/#/c/217101
|
||||
# for more details). This release can be identified by presence
|
||||
# of "api_version" property of ``novaclient.client.Client`` cls.
|
||||
return self._manager().list(limit=-1)
|
||||
else:
|
||||
# FIXME(andreykurilin): Remove code below, when minimum version of
|
||||
# novaclient in requirements will allow it.
|
||||
# NOTE(andreykurilin): Nova API returns only limited number(
|
||||
# 'osapi_max_limit' option in nova.conf) of servers, so we need
|
||||
# to use 'marker' option to list all pages of servers.
|
||||
result = []
|
||||
marker = None
|
||||
while True:
|
||||
servers = self._manager().list(marker=marker)
|
||||
if not servers:
|
||||
break
|
||||
result.extend(servers)
|
||||
marker = servers[-1].id
|
||||
return result
|
||||
|
||||
def delete(self):
|
||||
if getattr(self.raw_resource, "OS-EXT-STS:locked", False):
|
||||
self.raw_resource.unlock()
|
||||
super(NovaServer, self).delete()
|
||||
|
||||
|
||||
@base.resource("nova", "floating_ips", order=next(_nova_order))
|
||||
class NovaFloatingIPs(SynchronizedDeletion, base.ResourceManager):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("nova", "keypairs", order=next(_nova_order))
|
||||
class NovaKeypair(SynchronizedDeletion, base.ResourceManager):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("nova", "security_groups", order=next(_nova_order))
|
||||
class NovaSecurityGroup(SynchronizedDeletion, base.ResourceManager):
|
||||
|
||||
def list(self):
|
||||
return filter(lambda x: x.name != "default",
|
||||
super(NovaSecurityGroup, self).list())
|
||||
|
||||
|
||||
@base.resource("nova", "quotas", order=next(_nova_order),
|
||||
admin_required=True, tenant_resource=True)
|
||||
class NovaQuotas(QuotaMixin, base.ResourceManager):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("nova", "floating_ips_bulk", order=next(_nova_order),
|
||||
admin_required=True)
|
||||
class NovaFloatingIpsBulk(SynchronizedDeletion, base.ResourceManager):
|
||||
|
||||
def id(self):
|
||||
return self.raw_resource.address
|
||||
|
||||
def list(self):
|
||||
return [floating_ip for floating_ip in self._manager().list()
|
||||
if utils.name_matches_object(floating_ip.pool,
|
||||
nova_utils.NovaScenario)]
|
||||
|
||||
|
||||
@base.resource("nova", "networks", order=next(_nova_order),
|
||||
admin_required=True, tenant_resource=True)
|
||||
class NovaNetworks(SynchronizedDeletion, base.ResourceManager):
|
||||
|
||||
def list(self):
|
||||
# NOTE(stpierre): any plugin can create a nova network via the
|
||||
# network wrapper, and that network's name will be created
|
||||
# according to its owner's random name generation
|
||||
# parameters. so we need to check if there are nova networks
|
||||
# whose name pattern matches those of any loaded plugin that
|
||||
# implements RandomNameGeneratorMixin
|
||||
classes = list(discover.itersubclasses(utils.RandomNameGeneratorMixin))
|
||||
return [net for net in self._manager().list()
|
||||
if utils.name_matches_object(net.label, *classes)]
|
||||
|
||||
|
||||
# EC2
|
||||
|
||||
_ec2_order = get_order(250)
|
||||
|
||||
|
||||
class EC2Mixin(object):
|
||||
|
||||
def _manager(self):
|
||||
return getattr(self.user, self._service)()
|
||||
|
||||
|
||||
@base.resource("ec2", "servers", order=next(_ec2_order))
|
||||
class EC2Server(EC2Mixin, base.ResourceManager):
|
||||
|
||||
def is_deleted(self):
|
||||
try:
|
||||
instances = self._manager().get_only_instances(
|
||||
instance_ids=[self.id()])
|
||||
except boto_exception.EC2ResponseError as e:
|
||||
# NOTE(wtakase): Nova EC2 API returns 'InvalidInstanceID.NotFound'
|
||||
# if instance not found. In this case, we consider
|
||||
# instance has already been deleted.
|
||||
return getattr(e, "error_code") == "InvalidInstanceID.NotFound"
|
||||
|
||||
# NOTE(wtakase): After instance deletion, instance can be 'terminated'
|
||||
# state. If all instance states are 'terminated', this
|
||||
# returns True. And if get_only_instaces() returns empty
|
||||
# list, this also returns True because we consider
|
||||
# instance has already been deleted.
|
||||
return all(map(lambda i: i.state == "terminated", instances))
|
||||
|
||||
def delete(self):
|
||||
self._manager().terminate_instances(instance_ids=[self.id()])
|
||||
|
||||
def list(self):
|
||||
return self._manager().get_only_instances()
|
||||
|
||||
|
||||
# NEUTRON
|
||||
|
||||
_neutron_order = get_order(300)
|
||||
|
||||
|
||||
@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 supports_extension(self, extension):
|
||||
exts = self._manager().list_extensions().get("extensions", [])
|
||||
if any(ext.get("alias") == extension for ext in exts):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _manager(self):
|
||||
client = self._admin_required and self.admin or self.user
|
||||
return getattr(client, self._service)()
|
||||
|
||||
def id(self):
|
||||
return self.raw_resource["id"]
|
||||
|
||||
def delete(self):
|
||||
delete_method = getattr(self._manager(), "delete_%s" % self._resource)
|
||||
delete_method(self.id())
|
||||
|
||||
def list(self):
|
||||
resources = self._resource + "s"
|
||||
list_method = getattr(self._manager(), "list_%s" % resources)
|
||||
|
||||
return filter(lambda r: r["tenant_id"] == self.tenant_uuid,
|
||||
list_method({"tenant_id": self.tenant_uuid})[resources])
|
||||
|
||||
|
||||
class NeutronLbaasV1Mixin(NeutronMixin):
|
||||
|
||||
def list(self):
|
||||
if self.supports_extension("lbaas"):
|
||||
return super(NeutronLbaasV1Mixin, self).list()
|
||||
return []
|
||||
|
||||
|
||||
@base.resource("neutron", "vip", order=next(_neutron_order),
|
||||
tenant_resource=True)
|
||||
class NeutronV1Vip(NeutronLbaasV1Mixin):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("neutron", "health_monitor", order=next(_neutron_order),
|
||||
tenant_resource=True)
|
||||
class NeutronV1Healthmonitor(NeutronLbaasV1Mixin):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("neutron", "pool", order=next(_neutron_order),
|
||||
tenant_resource=True)
|
||||
class NeutronV1Pool(NeutronLbaasV1Mixin):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("neutron", "port", order=next(_neutron_order),
|
||||
tenant_resource=True)
|
||||
class NeutronPort(NeutronMixin):
|
||||
|
||||
def delete(self):
|
||||
if (self.raw_resource["device_owner"] == "network:router_interface" or
|
||||
self.raw_resource["device_owner"] ==
|
||||
"network:router_interface_distributed"):
|
||||
self._manager().remove_interface_router(
|
||||
self.raw_resource["device_id"],
|
||||
{"port_id": self.raw_resource["id"]})
|
||||
else:
|
||||
try:
|
||||
self._manager().delete_port(self.id())
|
||||
except neutron_exceptions.PortNotFoundClient:
|
||||
# Port can be already auto-deleted, skip silently
|
||||
LOG.debug("Port %s was not deleted. Skip silently because "
|
||||
"port can be already auto-deleted."
|
||||
% self.id())
|
||||
|
||||
|
||||
@base.resource("neutron", "router", order=next(_neutron_order),
|
||||
tenant_resource=True)
|
||||
class NeutronRouter(NeutronMixin):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("neutron", "subnet", order=next(_neutron_order),
|
||||
tenant_resource=True)
|
||||
class NeutronSubnet(NeutronMixin):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("neutron", "network", order=next(_neutron_order),
|
||||
tenant_resource=True)
|
||||
class NeutronNetwork(NeutronMixin):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("neutron", "floatingip", order=next(_neutron_order),
|
||||
tenant_resource=True)
|
||||
class NeutronFloatingIP(NeutronMixin):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("neutron", "security_group", order=next(_neutron_order),
|
||||
tenant_resource=True)
|
||||
class NeutronSecurityGroup(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)
|
||||
|
||||
|
||||
@base.resource("cinder", "backups", order=next(_cinder_order),
|
||||
tenant_resource=True)
|
||||
class CinderVolumeBackup(base.ResourceManager):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("cinder", "volume_snapshots", order=next(_cinder_order),
|
||||
tenant_resource=True)
|
||||
class CinderVolumeSnapshot(base.ResourceManager):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("cinder", "transfers", order=next(_cinder_order),
|
||||
tenant_resource=True)
|
||||
class CinderVolumeTransfer(base.ResourceManager):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("cinder", "volumes", order=next(_cinder_order),
|
||||
tenant_resource=True)
|
||||
class CinderVolume(base.ResourceManager):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("cinder", "quotas", order=next(_cinder_order),
|
||||
admin_required=True, tenant_resource=True)
|
||||
class CinderQuotas(QuotaMixin, base.ResourceManager):
|
||||
pass
|
||||
|
||||
|
||||
# MANILA
|
||||
|
||||
_manila_order = get_order(450)
|
||||
|
||||
|
||||
@base.resource("manila", "shares", order=next(_manila_order),
|
||||
tenant_resource=True)
|
||||
class ManilaShare(base.ResourceManager):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("manila", "share_networks", order=next(_manila_order),
|
||||
tenant_resource=True)
|
||||
class ManilaShareNetwork(base.ResourceManager):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("manila", "security_services", order=next(_manila_order),
|
||||
tenant_resource=True)
|
||||
class ManilaSecurityService(base.ResourceManager):
|
||||
pass
|
||||
|
||||
|
||||
# GLANCE
|
||||
|
||||
@base.resource("glance", "images", order=500, tenant_resource=True)
|
||||
class GlanceImage(base.ResourceManager):
|
||||
|
||||
def list(self):
|
||||
return self._manager().list(owner=self.tenant_uuid)
|
||||
|
||||
|
||||
# SAHARA
|
||||
|
||||
_sahara_order = get_order(600)
|
||||
|
||||
|
||||
@base.resource("sahara", "job_executions", order=next(_sahara_order),
|
||||
tenant_resource=True)
|
||||
class SaharaJobExecution(SynchronizedDeletion, base.ResourceManager):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("sahara", "jobs", order=next(_sahara_order),
|
||||
tenant_resource=True)
|
||||
class SaharaJob(SynchronizedDeletion, base.ResourceManager):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("sahara", "job_binary_internals", order=next(_sahara_order),
|
||||
tenant_resource=True)
|
||||
class SaharaJobBinaryInternals(SynchronizedDeletion, base.ResourceManager):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("sahara", "job_binaries", order=next(_sahara_order),
|
||||
tenant_resource=True)
|
||||
class SaharaJobBinary(SynchronizedDeletion, base.ResourceManager):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("sahara", "data_sources", order=next(_sahara_order),
|
||||
tenant_resource=True)
|
||||
class SaharaDataSource(SynchronizedDeletion, base.ResourceManager):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("sahara", "clusters", order=next(_sahara_order),
|
||||
tenant_resource=True)
|
||||
class SaharaCluster(base.ResourceManager):
|
||||
|
||||
# Need special treatment for Sahara Cluster because of the way the
|
||||
# exceptions are described in:
|
||||
# https://github.com/openstack/python-saharaclient/blob/master/
|
||||
# saharaclient/api/base.py#L145
|
||||
|
||||
def is_deleted(self):
|
||||
try:
|
||||
self._manager().get(self.id())
|
||||
return False
|
||||
except saharaclient_base.APIException as e:
|
||||
return e.error_code == 404
|
||||
|
||||
|
||||
@base.resource("sahara", "cluster_templates", order=next(_sahara_order),
|
||||
tenant_resource=True)
|
||||
class SaharaClusterTemplate(SynchronizedDeletion, base.ResourceManager):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("sahara", "node_group_templates", order=next(_sahara_order),
|
||||
tenant_resource=True)
|
||||
class SaharaNodeGroup(SynchronizedDeletion, base.ResourceManager):
|
||||
pass
|
||||
|
||||
|
||||
# CEILOMETER
|
||||
|
||||
@base.resource("ceilometer", "alarms", order=700, tenant_resource=True)
|
||||
class CeilometerAlarms(SynchronizedDeletion, base.ResourceManager):
|
||||
|
||||
def id(self):
|
||||
return self.raw_resource.alarm_id
|
||||
|
||||
def list(self):
|
||||
query = [{
|
||||
"field": "project_id",
|
||||
"op": "eq",
|
||||
"value": self.tenant_uuid
|
||||
}]
|
||||
return self._manager().list(q=query)
|
||||
|
||||
|
||||
# ZAQAR
|
||||
|
||||
@base.resource("zaqar", "queues", order=800)
|
||||
class ZaqarQueues(SynchronizedDeletion, base.ResourceManager):
|
||||
|
||||
def list(self):
|
||||
return self.user.zaqar().queues()
|
||||
|
||||
|
||||
# DESIGNATE
|
||||
|
||||
_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),
|
||||
self._resource)
|
||||
|
||||
|
||||
@base.resource("designate_v1", "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),
|
||||
admin_required=True, perform_for_admin_only=True)
|
||||
class DesignateServer(DesignateResource):
|
||||
pass
|
||||
|
||||
|
||||
# SWIFT
|
||||
|
||||
_swift_order = get_order(1000)
|
||||
|
||||
|
||||
class SwiftMixin(SynchronizedDeletion, base.ResourceManager):
|
||||
|
||||
def _manager(self):
|
||||
client = self._admin_required and self.admin or self.user
|
||||
return getattr(client, self._service)()
|
||||
|
||||
def id(self):
|
||||
return self.raw_resource
|
||||
|
||||
def delete(self):
|
||||
delete_method = getattr(self._manager(), "delete_%s" % self._resource)
|
||||
# NOTE(weiwu): *self.raw_resource is required because for deleting
|
||||
# container we are passing only container name, to delete object we
|
||||
# should pass as first argument container and second is object name.
|
||||
delete_method(*self.raw_resource)
|
||||
|
||||
|
||||
@base.resource("swift", "object", order=next(_swift_order),
|
||||
tenant_resource=True)
|
||||
class SwiftObject(SwiftMixin):
|
||||
|
||||
def list(self):
|
||||
object_list = []
|
||||
containers = self._manager().get_account(full_listing=True)[1]
|
||||
for con in containers:
|
||||
objects = self._manager().get_container(con["name"],
|
||||
full_listing=True)[1]
|
||||
for obj in objects:
|
||||
raw_resource = [con["name"], obj["name"]]
|
||||
object_list.append(raw_resource)
|
||||
return object_list
|
||||
|
||||
|
||||
@base.resource("swift", "container", order=next(_swift_order),
|
||||
tenant_resource=True)
|
||||
class SwiftContainer(SwiftMixin):
|
||||
|
||||
def list(self):
|
||||
containers = self._manager().get_account(full_listing=True)[1]
|
||||
return [[con["name"]] for con in containers]
|
||||
|
||||
|
||||
# MISTRAL
|
||||
|
||||
@base.resource("mistral", "workbooks", order=1100, tenant_resource=True)
|
||||
class MistralWorkbooks(SynchronizedDeletion, base.ResourceManager):
|
||||
def delete(self):
|
||||
self._manager().delete(self.raw_resource.name)
|
||||
|
||||
|
||||
# MURANO
|
||||
|
||||
_murano_order = get_order(1200)
|
||||
|
||||
|
||||
@base.resource("murano", "environments", tenant_resource=True,
|
||||
order=next(_murano_order))
|
||||
class MuranoEnvironments(SynchronizedDeletion, base.ResourceManager):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("murano", "packages", tenant_resource=True,
|
||||
order=next(_murano_order))
|
||||
class MuranoPackages(base.ResourceManager):
|
||||
def list(self):
|
||||
return filter(lambda x: x.name != "Core library",
|
||||
super(MuranoPackages, self).list())
|
||||
|
||||
|
||||
# IRONIC
|
||||
|
||||
_ironic_order = get_order(1300)
|
||||
|
||||
|
||||
@base.resource("ironic", "node", admin_required=True,
|
||||
order=next(_ironic_order), perform_for_admin_only=True)
|
||||
class IronicNodes(base.ResourceManager):
|
||||
|
||||
def id(self):
|
||||
return self.raw_resource.uuid
|
||||
|
||||
|
||||
# FUEL
|
||||
|
||||
@base.resource("fuel", "environment", order=1400,
|
||||
admin_required=True, perform_for_admin_only=True)
|
||||
class FuelEnvironment(base.ResourceManager):
|
||||
"""Fuel environment.
|
||||
|
||||
That is the only resource that can be deleted by fuelclient explicitly.
|
||||
"""
|
||||
|
||||
def id(self):
|
||||
return self.raw_resource["id"]
|
||||
|
||||
def is_deleted(self):
|
||||
return not self._manager().get(self.id())
|
||||
|
||||
def list(self):
|
||||
return [env for env in self._manager().list()
|
||||
if utils.name_matches_object(env["name"],
|
||||
futils.FuelScenario)]
|
||||
|
||||
|
||||
# KEYSTONE
|
||||
|
||||
_keystone_order = get_order(9000)
|
||||
|
||||
|
||||
class KeystoneMixin(SynchronizedDeletion):
|
||||
|
||||
def _manager(self):
|
||||
return keystone_wrapper.wrap(getattr(self.admin, self._service)())
|
||||
|
||||
def delete(self):
|
||||
delete_method = getattr(self._manager(), "delete_%s" % self._resource)
|
||||
delete_method(self.id())
|
||||
|
||||
def list(self):
|
||||
# TODO(boris-42): We should use such stuff in all list commands.
|
||||
resources = self._resource + "s"
|
||||
list_method = getattr(self._manager(), "list_%s" % resources)
|
||||
|
||||
return filter(kutils.is_temporary, list_method())
|
||||
|
||||
|
||||
@base.resource("keystone", "user", order=next(_keystone_order),
|
||||
admin_required=True, perform_for_admin_only=True)
|
||||
class KeystoneUser(KeystoneMixin, base.ResourceManager):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("keystone", "project", order=next(_keystone_order),
|
||||
admin_required=True, perform_for_admin_only=True)
|
||||
class KeystoneProject(KeystoneMixin, base.ResourceManager):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("keystone", "service", order=next(_keystone_order),
|
||||
admin_required=True, perform_for_admin_only=True)
|
||||
class KeystoneService(KeystoneMixin, base.ResourceManager):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("keystone", "role", order=next(_keystone_order),
|
||||
admin_required=True, perform_for_admin_only=True)
|
||||
class KeystoneRole(KeystoneMixin, base.ResourceManager):
|
||||
pass
|
||||
|
||||
|
||||
@base.resource("keystone", "ec2", tenant_resource=True,
|
||||
order=next(_keystone_order))
|
||||
class KeystoneEc2(SynchronizedDeletion, base.ResourceManager):
|
||||
def list(self):
|
||||
return self._manager().list(self.raw_resource)
|
@ -16,7 +16,7 @@ from rally.common.i18n import _
|
||||
from rally.common import logging
|
||||
from rally.common import utils as rutils
|
||||
from rally import consts
|
||||
from rally.plugins.openstack.context.cleanup import manager as resource_manager
|
||||
from rally.plugins.openstack.cleanup import manager as resource_manager
|
||||
from rally.plugins.openstack.scenarios.cinder import utils as cinder_utils
|
||||
from rally.task import context
|
||||
|
||||
|
@ -0,0 +1,94 @@
|
||||
# Copyright 2014: Mirantis Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 sys
|
||||
|
||||
from rally.common.i18n import _
|
||||
from rally.common import logging
|
||||
from rally import consts
|
||||
from rally import exceptions
|
||||
from rally.plugins.openstack.cleanup import manager
|
||||
from rally.task import context
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NoSuchCleanupResources(exceptions.RallyException):
|
||||
msg_fmt = _("Missing cleanup resource managers: %(message)s")
|
||||
|
||||
|
||||
class CleanupMixin(object):
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
"type": "array",
|
||||
"$schema": consts.JSON_SCHEMA,
|
||||
"items": {
|
||||
"type": "string",
|
||||
},
|
||||
"additionalProperties": False
|
||||
}
|
||||
|
||||
def setup(self):
|
||||
pass
|
||||
|
||||
|
||||
# NOTE(amaretskiy): Set order to run this just before UserCleanup
|
||||
@context.configure(name="admin_cleanup", order=(sys.maxsize - 1), hidden=True)
|
||||
class AdminCleanup(CleanupMixin, context.Context):
|
||||
"""Context class for admin resources cleanup."""
|
||||
|
||||
@classmethod
|
||||
def validate(cls, config, non_hidden=False):
|
||||
super(AdminCleanup, cls).validate(config, non_hidden)
|
||||
|
||||
missing = set(config)
|
||||
missing -= manager.list_resource_names(admin_required=True)
|
||||
missing = ", ".join(missing)
|
||||
if missing:
|
||||
LOG.info(_("Couldn't find cleanup resource managers: %s")
|
||||
% missing)
|
||||
raise NoSuchCleanupResources(missing)
|
||||
|
||||
@logging.log_task_wrapper(LOG.info, _("admin resources cleanup"))
|
||||
def cleanup(self):
|
||||
manager.cleanup(names=self.config,
|
||||
admin_required=True,
|
||||
admin=self.context["admin"],
|
||||
users=self.context.get("users", []))
|
||||
|
||||
|
||||
# NOTE(amaretskiy): Set maximum order to run this last
|
||||
@context.configure(name="cleanup", order=sys.maxsize, hidden=True)
|
||||
class UserCleanup(CleanupMixin, context.Context):
|
||||
"""Context class for user resources cleanup."""
|
||||
|
||||
@classmethod
|
||||
def validate(cls, config, non_hidden=False):
|
||||
super(UserCleanup, cls).validate(config, non_hidden)
|
||||
|
||||
missing = set(config)
|
||||
missing -= manager.list_resource_names(admin_required=False)
|
||||
missing = ", ".join(missing)
|
||||
if missing:
|
||||
LOG.info(_("Couldn't find cleanup resource managers: %s")
|
||||
% missing)
|
||||
raise NoSuchCleanupResources(missing)
|
||||
|
||||
@logging.log_task_wrapper(LOG.info, _("user resources cleanup"))
|
||||
def cleanup(self):
|
||||
manager.cleanup(names=self.config,
|
||||
admin_required=False,
|
||||
users=self.context.get("users", []))
|
@ -1,4 +1,4 @@
|
||||
# Copyright 2014: Mirantis Inc.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
@ -13,109 +13,15 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
# NOTE(stpierre): This module is left for backward compatibility.
|
||||
|
||||
from oslo_config import cfg
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from rally.task import utils
|
||||
from rally.plugins.openstack.cleanup import base
|
||||
|
||||
warnings.warn("Module rally.plugins.openstack.context.cleanup.base has been "
|
||||
"moved to rally.plugins.openstack.cleanup.base, and will be "
|
||||
"removed at some point in the future.")
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
CLEANUP_OPTS = [
|
||||
cfg.IntOpt("resource_deletion_timeout", default=600,
|
||||
help="A timeout in seconds for deleting resources")
|
||||
]
|
||||
cleanup_group = cfg.OptGroup(name="cleanup", title="Cleanup Options")
|
||||
CONF.register_group(cleanup_group)
|
||||
CONF.register_opts(CLEANUP_OPTS, cleanup_group)
|
||||
|
||||
|
||||
def resource(service, resource, order=0, admin_required=False,
|
||||
perform_for_admin_only=False, tenant_resource=False,
|
||||
max_attempts=3, timeout=CONF.cleanup.resource_deletion_timeout,
|
||||
interval=1, threads=20):
|
||||
"""Decorator that overrides resource specification.
|
||||
|
||||
Just put it on top of your resource class and specify arguments that you
|
||||
need.
|
||||
|
||||
:param service: It is equal to client name for corresponding service.
|
||||
E.g. "nova", "cinder" or "zaqar"
|
||||
:param resource: Client manager name for resource. E.g. in case of
|
||||
nova.servers you should write here "servers"
|
||||
:param order: Used to adjust priority of cleanup for different resource
|
||||
types
|
||||
:param admin_required: Admin user is required
|
||||
:param perform_for_admin_only: Perform cleanup for admin user only
|
||||
:param tenant_resource: Perform deletion only 1 time per tenant
|
||||
:param max_attempts: Max amount of attempts to delete single resource
|
||||
:param timeout: Max duration of deletion in seconds
|
||||
:param interval: Resource status pooling interval
|
||||
:param threads: Amount of threads (workers) that are deleting resources
|
||||
simultaneously
|
||||
"""
|
||||
|
||||
def inner(cls):
|
||||
# TODO(boris-42): This can be written better I believe =)
|
||||
cls._service = service
|
||||
cls._resource = resource
|
||||
cls._order = order
|
||||
cls._admin_required = admin_required
|
||||
cls._perform_for_admin_only = perform_for_admin_only
|
||||
cls._max_attempts = max_attempts
|
||||
cls._timeout = timeout
|
||||
cls._interval = interval
|
||||
cls._threads = threads
|
||||
cls._tenant_resource = tenant_resource
|
||||
|
||||
return cls
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
@resource(service=None, resource=None)
|
||||
class ResourceManager(object):
|
||||
"""Base class for cleanup plugins for specific resources.
|
||||
|
||||
You should use @resource decorator to specify major configuration of
|
||||
resource manager. Usually you should specify: service, resource and order.
|
||||
|
||||
If project python client is very specific, you can override delete(),
|
||||
list() and is_deleted() methods to make them fit to your case.
|
||||
"""
|
||||
|
||||
def __init__(self, resource=None, admin=None, user=None, tenant_uuid=None):
|
||||
self.admin = admin
|
||||
self.user = user
|
||||
self.raw_resource = resource
|
||||
self.tenant_uuid = tenant_uuid
|
||||
|
||||
def _manager(self):
|
||||
client = self._admin_required and self.admin or self.user
|
||||
return getattr(getattr(client, self._service)(), self._resource)
|
||||
|
||||
def id(self):
|
||||
"""Returns id of resource."""
|
||||
return self.raw_resource.id
|
||||
|
||||
def is_deleted(self):
|
||||
"""Checks if the resource is deleted.
|
||||
|
||||
Fetch resource by id from service and check it status.
|
||||
In case of NotFound or status is DELETED or DELETE_COMPLETE returns
|
||||
True, otherwise False.
|
||||
"""
|
||||
try:
|
||||
resource = self._manager().get(self.id())
|
||||
except Exception as e:
|
||||
return getattr(e, "code", getattr(e, "http_status", 400)) == 404
|
||||
|
||||
return utils.get_status(resource) in ("DELETED", "DELETE_COMPLETE")
|
||||
|
||||
def delete(self):
|
||||
"""Delete resource that corresponds to instance of this class."""
|
||||
self._manager().delete(self.id())
|
||||
|
||||
def list(self):
|
||||
"""List all resources specific for admin or user."""
|
||||
return self._manager().list()
|
||||
sys.modules["rally.plugins.openstack.context.cleanup.base"] = base
|
||||
|
@ -16,7 +16,7 @@ from rally.common.i18n import _
|
||||
from rally.common import logging
|
||||
from rally.common import utils as rutils
|
||||
from rally import consts
|
||||
from rally.plugins.openstack.context.cleanup import manager as resource_manager
|
||||
from rally.plugins.openstack.cleanup import manager as resource_manager
|
||||
from rally.plugins.openstack.scenarios.designate import utils
|
||||
from rally.task import context
|
||||
|
||||
|
@ -17,7 +17,7 @@ from rally.common import logging
|
||||
from rally.common import utils as rutils
|
||||
from rally import consts
|
||||
from rally import osclients
|
||||
from rally.plugins.openstack.context.cleanup import manager as resource_manager
|
||||
from rally.plugins.openstack.cleanup import manager as resource_manager
|
||||
from rally.plugins.openstack.scenarios.ec2 import utils as ec2_utils
|
||||
from rally.task import context
|
||||
from rally.task import types
|
||||
|
@ -16,7 +16,7 @@ from rally.common.i18n import _
|
||||
from rally.common import logging
|
||||
from rally.common import utils as rutils
|
||||
from rally import consts
|
||||
from rally.plugins.openstack.context.cleanup import manager as resource_manager
|
||||
from rally.plugins.openstack.cleanup import manager as resource_manager
|
||||
from rally.plugins.openstack.scenarios.glance import utils as glance_utils
|
||||
from rally.task import context
|
||||
|
||||
|
@ -17,7 +17,7 @@ from rally.common.i18n import _
|
||||
from rally.common import logging
|
||||
from rally.common import utils as rutils
|
||||
from rally import consts
|
||||
from rally.plugins.openstack.context.cleanup import manager as resource_manager
|
||||
from rally.plugins.openstack.cleanup import manager as resource_manager
|
||||
from rally.plugins.openstack.scenarios.heat import utils as heat_utils
|
||||
from rally.task import context
|
||||
|
||||
|
@ -24,7 +24,7 @@ from rally.common import utils
|
||||
from rally import consts
|
||||
from rally import exceptions
|
||||
from rally import osclients
|
||||
from rally.plugins.openstack.context.cleanup import manager as resource_manager
|
||||
from rally.plugins.openstack.cleanup import manager as resource_manager
|
||||
from rally.task import context
|
||||
|
||||
|
||||
|
@ -18,7 +18,7 @@ import novaclient.exceptions
|
||||
from rally.common.i18n import _
|
||||
from rally.common import logging
|
||||
from rally import osclients
|
||||
from rally.plugins.openstack.context.cleanup import manager as resource_manager
|
||||
from rally.plugins.openstack.cleanup import manager as resource_manager
|
||||
from rally.task import context
|
||||
|
||||
|
||||
|
@ -17,7 +17,7 @@ from rally.common import logging
|
||||
from rally.common import utils as rutils
|
||||
from rally import consts
|
||||
from rally import osclients
|
||||
from rally.plugins.openstack.context.cleanup import manager as resource_manager
|
||||
from rally.plugins.openstack.cleanup import manager as resource_manager
|
||||
from rally.plugins.openstack.scenarios.nova import utils as nova_utils
|
||||
from rally.task import context
|
||||
from rally.task import types
|
||||
|
@ -20,7 +20,7 @@ from rally.common import logging
|
||||
from rally.common import utils as rutils
|
||||
from rally import consts
|
||||
from rally import exceptions
|
||||
from rally.plugins.openstack.context.cleanup import manager as resource_manager
|
||||
from rally.plugins.openstack.cleanup import manager as resource_manager
|
||||
from rally.plugins.openstack.scenarios.sahara import utils
|
||||
from rally.task import context
|
||||
from rally.task import utils as bench_utils
|
||||
|
@ -18,7 +18,7 @@ from rally.common import utils as rutils
|
||||
from rally import consts
|
||||
from rally import exceptions
|
||||
from rally import osclients
|
||||
from rally.plugins.openstack.context.cleanup import manager as resource_manager
|
||||
from rally.plugins.openstack.cleanup import manager as resource_manager
|
||||
from rally.plugins.openstack.scenarios.glance import utils as glance_utils
|
||||
from rally.plugins.openstack.scenarios.sahara import utils
|
||||
from rally.task import context
|
||||
|
@ -21,8 +21,8 @@ from rally.common import logging
|
||||
from rally.common import utils as rutils
|
||||
from rally import consts
|
||||
from rally import osclients
|
||||
from rally.plugins.openstack.context.cleanup import manager as resource_manager
|
||||
from rally.plugins.openstack.context.cleanup import resources as res_cleanup
|
||||
from rally.plugins.openstack.cleanup import manager as resource_manager
|
||||
from rally.plugins.openstack.cleanup import resources as res_cleanup
|
||||
from rally.plugins.openstack.scenarios.sahara import utils
|
||||
from rally.plugins.openstack.scenarios.swift import utils as swift_utils
|
||||
from rally.task import context
|
||||
|
@ -21,7 +21,7 @@ from rally.common import utils as rutils
|
||||
from rally import consts
|
||||
from rally import exceptions
|
||||
from rally import osclients
|
||||
from rally.plugins.openstack.context.cleanup import manager as resource_manager
|
||||
from rally.plugins.openstack.cleanup import manager as resource_manager
|
||||
from rally.plugins.openstack.scenarios.sahara import utils
|
||||
from rally.task import context
|
||||
|
||||
|
@ -18,8 +18,8 @@ from rally.common import logging
|
||||
from rally.common import utils as rutils
|
||||
from rally import consts
|
||||
from rally import osclients
|
||||
from rally.plugins.openstack.context.cleanup import manager as resource_manager
|
||||
from rally.plugins.openstack.context.cleanup import resources as res_cleanup
|
||||
from rally.plugins.openstack.cleanup import manager as resource_manager
|
||||
from rally.plugins.openstack.cleanup import resources as res_cleanup
|
||||
from rally.plugins.openstack.scenarios.sahara import utils
|
||||
from rally.plugins.openstack.scenarios.swift import utils as swift_utils
|
||||
from rally.task import context
|
||||
|
0
tests/unit/plugins/openstack/cleanup/__init__.py
Normal file
0
tests/unit/plugins/openstack/cleanup/__init__.py
Normal file
116
tests/unit/plugins/openstack/cleanup/test_base.py
Normal file
116
tests/unit/plugins/openstack/cleanup/test_base.py
Normal file
@ -0,0 +1,116 @@
|
||||
# Copyright 2014: Mirantis Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 mock
|
||||
|
||||
from rally.plugins.openstack.cleanup import base
|
||||
from tests.unit import test
|
||||
|
||||
|
||||
BASE = "rally.plugins.openstack.cleanup.base"
|
||||
|
||||
|
||||
class ResourceDecoratorTestCase(test.TestCase):
|
||||
|
||||
def test_resource(self):
|
||||
|
||||
@base.resource("service", "res")
|
||||
class Fake(object):
|
||||
pass
|
||||
|
||||
self.assertEqual(Fake._service, "service")
|
||||
self.assertEqual(Fake._resource, "res")
|
||||
|
||||
|
||||
class ResourceManagerTestCase(test.TestCase):
|
||||
|
||||
def test__manager(self):
|
||||
user = mock.MagicMock()
|
||||
user.service1().resource1 = "user_res"
|
||||
|
||||
manager = base.ResourceManager(user=user)
|
||||
manager._service = "service1"
|
||||
manager._resource = "resource1"
|
||||
|
||||
self.assertEqual("user_res", manager._manager())
|
||||
|
||||
def test__manager_admin(self):
|
||||
admin = mock.MagicMock()
|
||||
admin.service1().resource1 = "admin_res"
|
||||
|
||||
manager = base.ResourceManager(admin=admin)
|
||||
manager._service = "service1"
|
||||
manager._resource = "resource1"
|
||||
manager._admin_required = True
|
||||
|
||||
self.assertEqual("admin_res", manager._manager())
|
||||
|
||||
def test_id(self):
|
||||
resource = mock.MagicMock(id="test_id")
|
||||
|
||||
manager = base.ResourceManager(resource=resource)
|
||||
self.assertEqual(resource.id, manager.id())
|
||||
|
||||
@mock.patch("%s.ResourceManager._manager" % BASE)
|
||||
def test_is_deleted(self, mock_resource_manager__manager):
|
||||
raw_res = mock.MagicMock(status="deleted")
|
||||
mock_resource_manager__manager().get.return_value = raw_res
|
||||
mock_resource_manager__manager.reset_mock()
|
||||
|
||||
resource = mock.MagicMock(id="test_id")
|
||||
|
||||
manager = base.ResourceManager(resource=resource)
|
||||
self.assertTrue(manager.is_deleted())
|
||||
raw_res.status = "DELETE_COMPLETE"
|
||||
self.assertTrue(manager.is_deleted())
|
||||
raw_res.status = "ACTIVE"
|
||||
self.assertFalse(manager.is_deleted())
|
||||
|
||||
mock_resource_manager__manager.assert_has_calls(
|
||||
[mock.call(), mock.call().get(resource.id)] * 3)
|
||||
self.assertEqual(mock_resource_manager__manager.call_count, 3)
|
||||
|
||||
@mock.patch("%s.ResourceManager._manager" % BASE)
|
||||
def test_is_deleted_exceptions(self, mock_resource_manager__manager):
|
||||
|
||||
class Fake500Exc(Exception):
|
||||
code = 500
|
||||
|
||||
class Fake404Exc(Exception):
|
||||
code = 404
|
||||
|
||||
mock_resource_manager__manager.side_effect = [
|
||||
Exception, Fake500Exc, Fake404Exc]
|
||||
|
||||
manager = base.ResourceManager(resource=mock.MagicMock())
|
||||
self.assertFalse(manager.is_deleted())
|
||||
self.assertFalse(manager.is_deleted())
|
||||
self.assertTrue(manager.is_deleted())
|
||||
|
||||
@mock.patch("%s.ResourceManager._manager" % BASE)
|
||||
def test_delete(self, mock_resource_manager__manager):
|
||||
res = mock.MagicMock(id="test_id")
|
||||
|
||||
manager = base.ResourceManager(resource=res)
|
||||
manager.delete()
|
||||
|
||||
mock_resource_manager__manager.assert_has_calls(
|
||||
[mock.call(), mock.call().delete(res.id)])
|
||||
|
||||
@mock.patch("%s.ResourceManager._manager" % BASE)
|
||||
def test_list(self, mock_resource_manager__manager):
|
||||
base.ResourceManager().list()
|
||||
mock_resource_manager__manager.assert_has_calls(
|
||||
[mock.call(), mock.call().list()])
|
354
tests/unit/plugins/openstack/cleanup/test_manager.py
Normal file
354
tests/unit/plugins/openstack/cleanup/test_manager.py
Normal file
@ -0,0 +1,354 @@
|
||||
# Copyright 2014: Mirantis Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 mock
|
||||
import six
|
||||
|
||||
from rally.plugins.openstack.cleanup import base
|
||||
from rally.plugins.openstack.cleanup import manager
|
||||
from tests.unit import test
|
||||
|
||||
|
||||
BASE = "rally.plugins.openstack.cleanup.manager"
|
||||
|
||||
|
||||
class SeekAndDestroyTestCase(test.TestCase):
|
||||
|
||||
@mock.patch("%s.osclients.Clients" % BASE,
|
||||
side_effect=[mock.MagicMock(), mock.MagicMock()])
|
||||
def test__get_cached_client(self, mock_clients):
|
||||
self.assertIsNone(manager.SeekAndDestroy._get_cached_client(None))
|
||||
|
||||
users = [{"credential": "a"}, {"credential": "b"}]
|
||||
cache = {}
|
||||
|
||||
self.assertEqual(
|
||||
manager.SeekAndDestroy._get_cached_client(users[0], cache=cache),
|
||||
manager.SeekAndDestroy._get_cached_client(users[0], cache=cache))
|
||||
|
||||
self.assertEqual(
|
||||
manager.SeekAndDestroy._get_cached_client(users[1], cache=cache),
|
||||
manager.SeekAndDestroy._get_cached_client(users[1], cache=cache))
|
||||
|
||||
self.assertNotEqual(
|
||||
manager.SeekAndDestroy._get_cached_client(users[0], cache=cache),
|
||||
manager.SeekAndDestroy._get_cached_client(users[1], cache=cache))
|
||||
|
||||
@mock.patch("%s.LOG" % BASE)
|
||||
def test__delete_single_resource(self, mock_log):
|
||||
mock_resource = mock.MagicMock(_max_attempts=3, _timeout=10,
|
||||
_interval=0.01)
|
||||
mock_resource.delete.side_effect = [Exception, Exception, True]
|
||||
mock_resource.is_deleted.side_effect = [False, False, True]
|
||||
|
||||
manager.SeekAndDestroy(None, None, None)._delete_single_resource(
|
||||
mock_resource)
|
||||
|
||||
mock_resource.delete.assert_has_calls([mock.call()] * 3)
|
||||
self.assertEqual(mock_resource.delete.call_count, 3)
|
||||
mock_resource.is_deleted.assert_has_calls([mock.call()] * 3)
|
||||
self.assertEqual(mock_resource.is_deleted.call_count, 3)
|
||||
|
||||
# NOTE(boris-42): No logs and no exceptions means no bugs!
|
||||
self.assertEqual(0, mock_log.call_count)
|
||||
|
||||
@mock.patch("%s.LOG" % BASE)
|
||||
def test__delete_single_resource_timeout(self, mock_log):
|
||||
|
||||
mock_resource = mock.MagicMock(_max_attempts=1, _timeout=0.02,
|
||||
_interval=0.025)
|
||||
|
||||
mock_resource.delete.return_value = True
|
||||
mock_resource.is_deleted.side_effect = [False, False, True]
|
||||
|
||||
manager.SeekAndDestroy(None, None, None)._delete_single_resource(
|
||||
mock_resource)
|
||||
|
||||
mock_resource.delete.assert_called_once_with()
|
||||
mock_resource.is_deleted.assert_called_once_with()
|
||||
|
||||
self.assertEqual(1, mock_log.warning.call_count)
|
||||
|
||||
@mock.patch("%s.LOG" % BASE)
|
||||
def test__delete_single_resource_excpetion_in_is_deleted(self, mock_log):
|
||||
mock_resource = mock.MagicMock(_max_attempts=3, _timeout=10,
|
||||
_interval=0)
|
||||
mock_resource.delete.return_value = True
|
||||
mock_resource.is_deleted.side_effect = [Exception] * 4
|
||||
manager.SeekAndDestroy(None, None, None)._delete_single_resource(
|
||||
mock_resource)
|
||||
|
||||
mock_resource.delete.assert_called_once_with()
|
||||
self.assertEqual(4, mock_resource.is_deleted.call_count)
|
||||
|
||||
self.assertEqual(5, mock_log.warning.call_count)
|
||||
self.assertEqual(4, mock_log.exception.call_count)
|
||||
|
||||
def _manager(self, list_side_effect, **kw):
|
||||
mock_mgr = mock.MagicMock()
|
||||
mock_mgr().list.side_effect = list_side_effect
|
||||
mock_mgr.reset_mock()
|
||||
|
||||
for k, v in six.iteritems(kw):
|
||||
setattr(mock_mgr, k, v)
|
||||
|
||||
return mock_mgr
|
||||
|
||||
@mock.patch("%s.SeekAndDestroy._get_cached_client" % BASE)
|
||||
def test__gen_publisher_admin(self, mock__get_cached_client):
|
||||
mock_mgr = self._manager([Exception, Exception, [1, 2, 3]],
|
||||
_perform_for_admin_only=False)
|
||||
admin = mock.MagicMock()
|
||||
publish = manager.SeekAndDestroy(
|
||||
mock_mgr, admin, None)._gen_publisher()
|
||||
|
||||
queue = []
|
||||
publish(queue)
|
||||
mock__get_cached_client.assert_called_once_with(admin)
|
||||
mock_mgr.assert_called_once_with(
|
||||
admin=mock__get_cached_client.return_value)
|
||||
self.assertEqual(queue, [(admin, None, x) for x in range(1, 4)])
|
||||
|
||||
@mock.patch("%s.SeekAndDestroy._get_cached_client" % BASE)
|
||||
def test__gen_publisher_admin_only(self, mock__get_cached_client):
|
||||
mock_mgr = self._manager([Exception, Exception, [1, 2, 3]],
|
||||
_perform_for_admin_only=True)
|
||||
admin = mock.MagicMock()
|
||||
publish = manager.SeekAndDestroy(
|
||||
mock_mgr, admin, ["u1", "u2"])._gen_publisher()
|
||||
|
||||
queue = []
|
||||
publish(queue)
|
||||
mock__get_cached_client.assert_called_once_with(admin)
|
||||
mock_mgr.assert_called_once_with(
|
||||
admin=mock__get_cached_client.return_value)
|
||||
self.assertEqual(queue, [(admin, None, x) for x in range(1, 4)])
|
||||
|
||||
@mock.patch("%s.SeekAndDestroy._get_cached_client" % BASE)
|
||||
def test__gen_publisher_user_resource(self, mock__get_cached_client):
|
||||
mock_mgr = self._manager([Exception, Exception, [1, 2, 3],
|
||||
Exception, Exception, [4, 5]],
|
||||
_perform_for_admin_only=False,
|
||||
_tenant_resource=True)
|
||||
|
||||
admin = mock.MagicMock()
|
||||
users = [{"tenant_id": 1, "id": 1}, {"tenant_id": 2, "id": 2}]
|
||||
publish = manager.SeekAndDestroy(
|
||||
mock_mgr, admin, users)._gen_publisher()
|
||||
|
||||
queue = []
|
||||
publish(queue)
|
||||
|
||||
mock_client = mock__get_cached_client.return_value
|
||||
mock_mgr.assert_has_calls([
|
||||
mock.call(admin=mock_client, user=mock_client,
|
||||
tenant_uuid=users[0]["tenant_id"]),
|
||||
mock.call().list(),
|
||||
mock.call().list(),
|
||||
mock.call().list(),
|
||||
mock.call(admin=mock_client, user=mock_client,
|
||||
tenant_uuid=users[1]["tenant_id"]),
|
||||
mock.call().list(),
|
||||
mock.call().list()
|
||||
])
|
||||
mock__get_cached_client.assert_has_calls([
|
||||
mock.call(admin),
|
||||
mock.call(users[0]),
|
||||
mock.call(users[1])
|
||||
])
|
||||
expected_queue = [(admin, users[0], x) for x in range(1, 4)]
|
||||
expected_queue += [(admin, users[1], x) for x in range(4, 6)]
|
||||
self.assertEqual(queue, expected_queue)
|
||||
|
||||
@mock.patch("%s.SeekAndDestroy._get_cached_client" % BASE)
|
||||
def test__gen_publisher_tenant_resource(self, mock__get_cached_client):
|
||||
mock_mgr = self._manager([Exception, [1, 2, 3],
|
||||
Exception, Exception, Exception,
|
||||
["this shouldn't be in results"]],
|
||||
_perform_for_admin_only=False,
|
||||
_tenant_resource=True)
|
||||
users = [{"tenant_id": 1, "id": 1},
|
||||
{"tenant_id": 1, "id": 2},
|
||||
{"tenant_id": 2, "id": 3}]
|
||||
|
||||
publish = manager.SeekAndDestroy(
|
||||
mock_mgr, None, users)._gen_publisher()
|
||||
|
||||
queue = []
|
||||
publish(queue)
|
||||
|
||||
mock_client = mock__get_cached_client.return_value
|
||||
mock_mgr.assert_has_calls([
|
||||
mock.call(admin=mock_client, user=mock_client,
|
||||
tenant_uuid=users[0]["tenant_id"]),
|
||||
mock.call().list(),
|
||||
mock.call().list(),
|
||||
mock.call(admin=mock_client, user=mock_client,
|
||||
tenant_uuid=users[2]["tenant_id"]),
|
||||
mock.call().list(),
|
||||
mock.call().list(),
|
||||
mock.call().list()
|
||||
])
|
||||
mock__get_cached_client.assert_has_calls([
|
||||
mock.call(None),
|
||||
mock.call(users[0]),
|
||||
mock.call(users[2])
|
||||
])
|
||||
self.assertEqual(queue, [(None, users[0], x) for x in range(1, 4)])
|
||||
|
||||
@mock.patch("%s.SeekAndDestroy._get_cached_client" % BASE)
|
||||
@mock.patch("%s.SeekAndDestroy._delete_single_resource" % BASE)
|
||||
def test__gen_consumer(self, mock__delete_single_resource,
|
||||
mock__get_cached_client):
|
||||
mock_mgr = mock.MagicMock(__name__="Test")
|
||||
|
||||
consumer = manager.SeekAndDestroy(mock_mgr, None, None)._gen_consumer()
|
||||
|
||||
admin = mock.MagicMock()
|
||||
user1 = {"id": "a", "tenant_id": "uuid1"}
|
||||
cache = {}
|
||||
|
||||
consumer(cache, (admin, user1, "res"))
|
||||
mock_mgr.assert_called_once_with(
|
||||
resource="res",
|
||||
admin=mock__get_cached_client.return_value,
|
||||
user=mock__get_cached_client.return_value,
|
||||
tenant_uuid=user1["tenant_id"])
|
||||
mock__get_cached_client.assert_has_calls([
|
||||
mock.call(admin, cache=cache),
|
||||
mock.call(user1, cache=cache)
|
||||
])
|
||||
mock__delete_single_resource.assert_called_once_with(
|
||||
mock_mgr.return_value)
|
||||
|
||||
mock_mgr.reset_mock()
|
||||
mock__get_cached_client.reset_mock()
|
||||
mock__delete_single_resource.reset_mock()
|
||||
|
||||
consumer(cache, (admin, None, "res2"))
|
||||
mock_mgr.assert_called_once_with(
|
||||
resource="res2",
|
||||
admin=mock__get_cached_client.return_value,
|
||||
user=mock__get_cached_client.return_value,
|
||||
tenant_uuid=None)
|
||||
|
||||
mock__get_cached_client.assert_has_calls([
|
||||
mock.call(admin, cache=cache),
|
||||
mock.call(None, cache=cache)
|
||||
])
|
||||
mock__delete_single_resource.assert_called_once_with(
|
||||
mock_mgr.return_value)
|
||||
|
||||
@mock.patch("%s.SeekAndDestroy._gen_consumer" % BASE)
|
||||
@mock.patch("%s.SeekAndDestroy._gen_publisher" % BASE)
|
||||
@mock.patch("%s.broker.run" % BASE)
|
||||
def test_exterminate(self, mock_broker_run, mock__gen_publisher,
|
||||
mock__gen_consumer):
|
||||
|
||||
manager_cls = mock.MagicMock(_threads=5)
|
||||
manager.SeekAndDestroy(manager_cls, None, None).exterminate()
|
||||
|
||||
mock__gen_publisher.assert_called_once_with()
|
||||
mock__gen_consumer.assert_called_once_with()
|
||||
mock_broker_run.assert_called_once_with(
|
||||
mock__gen_publisher.return_value,
|
||||
mock__gen_consumer.return_value,
|
||||
consumers_count=5)
|
||||
|
||||
|
||||
class ResourceManagerTestCase(test.TestCase):
|
||||
|
||||
def _get_res_mock(self, **kw):
|
||||
_mock = mock.MagicMock()
|
||||
for k, v in six.iteritems(kw):
|
||||
setattr(_mock, k, v)
|
||||
return _mock
|
||||
|
||||
def _list_res_names_helper(self, names, admin_required, mock_iter):
|
||||
self.assertEqual(set(names),
|
||||
manager.list_resource_names(admin_required))
|
||||
mock_iter.assert_called_once_with(base.ResourceManager)
|
||||
mock_iter.reset_mock()
|
||||
|
||||
@mock.patch("%s.discover.itersubclasses" % BASE)
|
||||
def test_list_resource_names(self, mock_itersubclasses):
|
||||
mock_itersubclasses.return_value = [
|
||||
self._get_res_mock(_service="fake", _resource="1",
|
||||
_admin_required=True),
|
||||
self._get_res_mock(_service="fake", _resource="2",
|
||||
_admin_required=False),
|
||||
self._get_res_mock(_service="other", _resource="2",
|
||||
_admin_required=False)
|
||||
]
|
||||
|
||||
self._list_res_names_helper(
|
||||
["fake", "other", "fake.1", "fake.2", "other.2"],
|
||||
None, mock_itersubclasses)
|
||||
self._list_res_names_helper(
|
||||
["fake", "fake.1"],
|
||||
True, mock_itersubclasses)
|
||||
self._list_res_names_helper(
|
||||
["fake", "other", "fake.2", "other.2"],
|
||||
False, mock_itersubclasses)
|
||||
|
||||
@mock.patch("%s.discover.itersubclasses" % BASE)
|
||||
def test_find_resource_managers(self, mock_itersubclasses):
|
||||
mock_itersubclasses.return_value = [
|
||||
self._get_res_mock(_service="fake", _resource="1", _order=1,
|
||||
_admin_required=True),
|
||||
self._get_res_mock(_service="fake", _resource="2", _order=3,
|
||||
_admin_required=False),
|
||||
self._get_res_mock(_service="other", _resource="2", _order=2,
|
||||
_admin_required=False)
|
||||
]
|
||||
|
||||
self.assertEqual(mock_itersubclasses.return_value[0:2],
|
||||
manager.find_resource_managers(names=["fake"]))
|
||||
|
||||
self.assertEqual(mock_itersubclasses.return_value[0:1],
|
||||
manager.find_resource_managers(names=["fake.1"]))
|
||||
|
||||
self.assertEqual(
|
||||
[mock_itersubclasses.return_value[0],
|
||||
mock_itersubclasses.return_value[2],
|
||||
mock_itersubclasses.return_value[1]],
|
||||
manager.find_resource_managers(names=["fake", "other"]))
|
||||
|
||||
self.assertEqual(mock_itersubclasses.return_value[0:1],
|
||||
manager.find_resource_managers(names=["fake"],
|
||||
admin_required=True))
|
||||
self.assertEqual(mock_itersubclasses.return_value[1:2],
|
||||
manager.find_resource_managers(names=["fake"],
|
||||
admin_required=False))
|
||||
|
||||
@mock.patch("%s.SeekAndDestroy" % BASE)
|
||||
@mock.patch("%s.find_resource_managers" % BASE,
|
||||
return_value=[mock.MagicMock(), mock.MagicMock()])
|
||||
def test_cleanup(self, mock_find_resource_managers, mock_seek_and_destroy):
|
||||
manager.cleanup(names=["a", "b"], admin_required=True,
|
||||
admin="admin", users=["user"])
|
||||
|
||||
mock_find_resource_managers.assert_called_once_with(["a", "b"], True)
|
||||
|
||||
mock_seek_and_destroy.assert_has_calls([
|
||||
mock.call(
|
||||
mock_find_resource_managers.return_value[0], "admin", ["user"]
|
||||
),
|
||||
mock.call().exterminate(),
|
||||
mock.call(
|
||||
mock_find_resource_managers.return_value[1], "admin", ["user"]
|
||||
),
|
||||
mock.call().exterminate()
|
||||
])
|
637
tests/unit/plugins/openstack/cleanup/test_resources.py
Normal file
637
tests/unit/plugins/openstack/cleanup/test_resources.py
Normal file
@ -0,0 +1,637 @@
|
||||
# Copyright 2014: Mirantis Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 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):
|
||||
self.assertTrue(resources.SynchronizedDeletion().is_deleted())
|
||||
|
||||
|
||||
class QuotaMixinTestCase(test.TestCase):
|
||||
|
||||
def test_id(self):
|
||||
quota = resources.QuotaMixin()
|
||||
quota.raw_resource = mock.MagicMock()
|
||||
self.assertEqual(quota.raw_resource, quota.id())
|
||||
|
||||
def test_delete(self):
|
||||
quota = resources.QuotaMixin()
|
||||
mock_manager = mock.MagicMock()
|
||||
quota._manager = lambda: mock_manager
|
||||
quota.raw_resource = mock.MagicMock()
|
||||
|
||||
quota.delete()
|
||||
mock_manager.delete.assert_called_once_with(quota.raw_resource)
|
||||
|
||||
def test_list(self):
|
||||
quota = resources.QuotaMixin()
|
||||
quota.tenant_uuid = None
|
||||
self.assertEqual([], quota.list())
|
||||
|
||||
quota.tenant_uuid = mock.MagicMock()
|
||||
self.assertEqual([quota.tenant_uuid], quota.list())
|
||||
|
||||
|
||||
class NovaServerTestCase(test.TestCase):
|
||||
|
||||
def test_list(self):
|
||||
server = resources.NovaServer()
|
||||
server._manager = mock.MagicMock()
|
||||
|
||||
server.list()
|
||||
|
||||
server._manager.return_value.list.assert_called_once_with(limit=-1)
|
||||
|
||||
def test_list_old_novaclient(self):
|
||||
servers = [mock.MagicMock(), mock.MagicMock(), mock.MagicMock(),
|
||||
mock.MagicMock()]
|
||||
server = resources.NovaServer()
|
||||
server._manager = mock.MagicMock()
|
||||
server._manager.return_value.api = None
|
||||
server._manager.return_value.list.side_effect = (
|
||||
servers[:2], servers[2:4], [])
|
||||
|
||||
self.assertEqual(servers, server.list())
|
||||
self.assertEqual(
|
||||
[mock.call(marker=None), mock.call(marker=servers[1].id),
|
||||
mock.call(marker=servers[3].id)],
|
||||
server._manager.return_value.list.call_args_list)
|
||||
|
||||
def test_delete(self):
|
||||
server = resources.NovaServer()
|
||||
server.raw_resource = mock.Mock()
|
||||
server._manager = mock.Mock()
|
||||
server.delete()
|
||||
|
||||
server._manager.return_value.delete.assert_called_once_with(
|
||||
server.raw_resource.id)
|
||||
|
||||
def test_delete_locked(self):
|
||||
server = resources.NovaServer()
|
||||
server.raw_resource = mock.Mock()
|
||||
setattr(server.raw_resource, "OS-EXT-STS:locked", True)
|
||||
server._manager = mock.Mock()
|
||||
server.delete()
|
||||
|
||||
server.raw_resource.unlock.assert_called_once_with()
|
||||
server._manager.return_value.delete.assert_called_once_with(
|
||||
server.raw_resource.id)
|
||||
|
||||
|
||||
class NovaSecurityGroupTestCase(test.TestCase):
|
||||
|
||||
@mock.patch("%s.base.ResourceManager._manager" % BASE)
|
||||
def test_list(self, mock_resource_manager__manager):
|
||||
secgroups = [mock.MagicMock(), mock.MagicMock(), mock.MagicMock()]
|
||||
secgroups[0].name = "a"
|
||||
secgroups[1].name = "b"
|
||||
secgroups[2].name = "default"
|
||||
|
||||
mock_resource_manager__manager().list.return_value = secgroups
|
||||
self.assertSequenceEqual(secgroups[:2],
|
||||
resources.NovaSecurityGroup().list())
|
||||
|
||||
|
||||
class NovaFloatingIpsBulkTestCase(test.TestCase):
|
||||
|
||||
def test_id(self):
|
||||
ip_range = resources.NovaFloatingIpsBulk()
|
||||
ip_range.raw_resource = mock.MagicMock()
|
||||
self.assertEqual(ip_range.raw_resource.address, ip_range.id())
|
||||
|
||||
@mock.patch("%s.base.ResourceManager._manager" % BASE)
|
||||
@mock.patch("rally.common.utils.name_matches_object")
|
||||
def test_list(self, mock_name_matches_object,
|
||||
mock_resource_manager__manager):
|
||||
ip_range = [mock.MagicMock(), mock.MagicMock(), mock.MagicMock()]
|
||||
ip_range[0].pool = "a"
|
||||
ip_range[1].pool = "rally_fip_pool_a"
|
||||
ip_range[2].pool = "rally_fip_pool_b"
|
||||
mock_name_matches_object.side_effect = (lambda n, o:
|
||||
n.startswith("rally"))
|
||||
|
||||
mock_resource_manager__manager().list.return_value = ip_range
|
||||
self.assertEqual(ip_range[1:], resources.NovaFloatingIpsBulk().list())
|
||||
|
||||
|
||||
class NovaNetworksTestCase(test.TestCase):
|
||||
|
||||
@mock.patch("rally.common.plugin.discover.itersubclasses")
|
||||
def test_list(self, mock_itersubclasses):
|
||||
nova_nets = resources.NovaNetworks()
|
||||
|
||||
networks = [mock.Mock(label="rally_abcdefgh_12345678"),
|
||||
mock.Mock(label="rally_12345678_abcdefgh"),
|
||||
mock.Mock(label="foobar")]
|
||||
nova_nets._manager = mock.Mock()
|
||||
nova_nets._manager.return_value.list.return_value = networks
|
||||
|
||||
mock_itersubclasses.return_value = iter(
|
||||
[utils.RandomNameGeneratorMixin])
|
||||
|
||||
self.assertEqual(networks[:2], nova_nets.list())
|
||||
nova_nets._manager.return_value.list.assert_called_once_with()
|
||||
mock_itersubclasses.assert_called_once_with(
|
||||
utils.RandomNameGeneratorMixin)
|
||||
|
||||
|
||||
class EC2MixinTestCase(test.TestCase):
|
||||
|
||||
def get_ec2_mixin(self):
|
||||
ec2 = resources.EC2Mixin()
|
||||
ec2._service = "ec2"
|
||||
return ec2
|
||||
|
||||
def test__manager(self):
|
||||
ec2 = self.get_ec2_mixin()
|
||||
ec2.user = mock.MagicMock()
|
||||
self.assertEqual(ec2.user.ec2.return_value, ec2._manager())
|
||||
|
||||
|
||||
class EC2ServerTestCase(test.TestCase):
|
||||
|
||||
@mock.patch("%s.EC2Server._manager" % BASE)
|
||||
def test_is_deleted(self, mock_ec2_server__manager):
|
||||
raw_res1 = mock.MagicMock(state="terminated")
|
||||
raw_res2 = mock.MagicMock(state="terminated")
|
||||
resource = mock.MagicMock(id="test_id")
|
||||
manager = resources.EC2Server(resource=resource)
|
||||
|
||||
mock_ec2_server__manager().get_only_instances.return_value = [raw_res1]
|
||||
self.assertTrue(manager.is_deleted())
|
||||
|
||||
raw_res1.state = "running"
|
||||
self.assertFalse(manager.is_deleted())
|
||||
|
||||
mock_ec2_server__manager().get_only_instances.return_value = [
|
||||
raw_res1, raw_res2]
|
||||
self.assertFalse(manager.is_deleted())
|
||||
|
||||
raw_res1.state = "terminated"
|
||||
self.assertTrue(manager.is_deleted())
|
||||
|
||||
mock_ec2_server__manager().get_only_instances.return_value = []
|
||||
self.assertTrue(manager.is_deleted())
|
||||
|
||||
@mock.patch("%s.EC2Server._manager" % BASE)
|
||||
def test_is_deleted_exceptions(self, mock_ec2_server__manager):
|
||||
mock_ec2_server__manager.side_effect = [
|
||||
boto_exception.EC2ResponseError(
|
||||
status="fake", reason="fake",
|
||||
body={"Error": {"Code": "fake_code"}}),
|
||||
boto_exception.EC2ResponseError(
|
||||
status="fake", reason="fake",
|
||||
body={"Error": {"Code": "InvalidInstanceID.NotFound"}})
|
||||
]
|
||||
manager = resources.EC2Server(resource=mock.MagicMock())
|
||||
self.assertFalse(manager.is_deleted())
|
||||
self.assertTrue(manager.is_deleted())
|
||||
|
||||
@mock.patch("%s.EC2Server._manager" % BASE)
|
||||
def test_delete(self, mock_ec2_server__manager):
|
||||
resource = mock.MagicMock(id="test_id")
|
||||
manager = resources.EC2Server(resource=resource)
|
||||
manager.delete()
|
||||
mock_ec2_server__manager().terminate_instances.assert_called_once_with(
|
||||
instance_ids=["test_id"])
|
||||
|
||||
@mock.patch("%s.EC2Server._manager" % BASE)
|
||||
def test_list(self, mock_ec2_server__manager):
|
||||
manager = resources.EC2Server()
|
||||
mock_ec2_server__manager().get_only_instances.return_value = [
|
||||
"a", "b", "c"]
|
||||
self.assertEqual(["a", "b", "c"], manager.list())
|
||||
|
||||
|
||||
class NeutronMixinTestCase(test.TestCase):
|
||||
|
||||
def get_neutron_mixin(self):
|
||||
neut = resources.NeutronMixin()
|
||||
neut._service = "neutron"
|
||||
return neut
|
||||
|
||||
def test_manager(self):
|
||||
neut = self.get_neutron_mixin()
|
||||
neut.user = mock.MagicMock()
|
||||
self.assertEqual(neut.user.neutron.return_value, neut._manager())
|
||||
|
||||
@mock.patch("%s.NeutronMixin._manager" % BASE)
|
||||
def test_supports_extension(self, mock__manager):
|
||||
mock__manager().list_extensions.return_value = {
|
||||
"extensions": [{"alias": "foo"}, {"alias": "bar"}]
|
||||
}
|
||||
neut = self.get_neutron_mixin()
|
||||
self.assertTrue(neut.supports_extension("foo"))
|
||||
self.assertTrue(neut.supports_extension("bar"))
|
||||
self.assertFalse(neut.supports_extension("foobar"))
|
||||
|
||||
def test_id(self):
|
||||
neut = self.get_neutron_mixin()
|
||||
neut.raw_resource = {"id": "test"}
|
||||
self.assertEqual("test", neut.id())
|
||||
|
||||
def test_delete(self):
|
||||
neut = self.get_neutron_mixin()
|
||||
neut.user = mock.MagicMock()
|
||||
neut._resource = "some_resource"
|
||||
neut.raw_resource = {"id": "42"}
|
||||
|
||||
neut.delete()
|
||||
neut.user.neutron().delete_some_resource.assert_called_once_with("42")
|
||||
|
||||
def test_list(self):
|
||||
neut = self.get_neutron_mixin()
|
||||
neut.user = mock.MagicMock()
|
||||
neut._resource = "some_resource"
|
||||
neut.tenant_uuid = "user_tenant"
|
||||
|
||||
some_resources = [{"tenant_id": neut.tenant_uuid}, {"tenant_id": "a"}]
|
||||
neut.user.neutron().list_some_resources.return_value = {
|
||||
"some_resources": some_resources
|
||||
}
|
||||
|
||||
self.assertEqual([some_resources[0]], list(neut.list()))
|
||||
|
||||
neut.user.neutron().list_some_resources.assert_called_once_with(
|
||||
{"tenant_id": neut.tenant_uuid})
|
||||
|
||||
|
||||
class NeutronLbaasV1MixinTestCase(test.TestCase):
|
||||
|
||||
def get_neutron_lbaasv1_mixin(self, extensions=None):
|
||||
if extensions is None:
|
||||
extensions = []
|
||||
neut = resources.NeutronLbaasV1Mixin()
|
||||
neut._service = "neutron"
|
||||
neut._resource = "some_resource"
|
||||
neut._manager = mock.Mock()
|
||||
neut._manager().list_extensions.return_value = {
|
||||
"extensions": [{"alias": ext} for ext in extensions]
|
||||
}
|
||||
return neut
|
||||
|
||||
def test_list_lbaas_available(self):
|
||||
neut = self.get_neutron_lbaasv1_mixin(extensions=["lbaas"])
|
||||
neut.tenant_uuid = "user_tenant"
|
||||
|
||||
some_resources = [{"tenant_id": neut.tenant_uuid}, {"tenant_id": "a"}]
|
||||
neut._manager().list_some_resources.return_value = {
|
||||
"some_resources": some_resources
|
||||
}
|
||||
|
||||
self.assertEqual([some_resources[0]], list(neut.list()))
|
||||
neut._manager().list_some_resources.assert_called_once_with(
|
||||
{"tenant_id": neut.tenant_uuid})
|
||||
|
||||
def test_list_lbaas_unavailable(self):
|
||||
neut = self.get_neutron_lbaasv1_mixin()
|
||||
|
||||
self.assertEqual([], list(neut.list()))
|
||||
self.assertFalse(neut._manager().list_some_resources.called)
|
||||
|
||||
|
||||
class NeutronPortTestCase(test.TestCase):
|
||||
|
||||
def test_delete(self):
|
||||
raw_res = {"device_owner": "abbabaab", "id": "some_id"}
|
||||
user = mock.MagicMock()
|
||||
|
||||
resources.NeutronPort(resource=raw_res, user=user).delete()
|
||||
|
||||
user.neutron().delete_port.assert_called_once_with(raw_res["id"])
|
||||
|
||||
def test_delete_port_raise_exception(self):
|
||||
raw_res = {"device_owner": "abbabaab", "id": "some_id"}
|
||||
user = mock.MagicMock()
|
||||
user.neutron().delete_port.side_effect = (
|
||||
neutron_exceptions.PortNotFoundClient)
|
||||
|
||||
resources.NeutronPort(resource=raw_res, user=user).delete()
|
||||
|
||||
user.neutron().delete_port.assert_called_once_with(raw_res["id"])
|
||||
|
||||
def test_delete_port_device_owner(self):
|
||||
raw_res = {
|
||||
"device_owner": "network:router_interface",
|
||||
"id": "some_id",
|
||||
"device_id": "dev_id"
|
||||
}
|
||||
user = mock.MagicMock()
|
||||
|
||||
resources.NeutronPort(resource=raw_res, user=user).delete()
|
||||
|
||||
user.neutron().remove_interface_router.assert_called_once_with(
|
||||
raw_res["device_id"], {"port_id": raw_res["id"]})
|
||||
|
||||
|
||||
class NeutronQuotaTestCase(test.TestCase):
|
||||
|
||||
@mock.patch("%s.NeutronQuota._manager" % BASE)
|
||||
def test_delete(self, mock_neutron_quota__manager):
|
||||
user = mock.MagicMock()
|
||||
resources.NeutronQuota(user=user, tenant_uuid="fake").delete()
|
||||
mock_neutron_quota__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)
|
||||
def test_list(self, mock_glance_image__manager):
|
||||
glance = resources.GlanceImage()
|
||||
glance.tenant_uuid = mock.MagicMock()
|
||||
|
||||
mock_glance_image__manager().list.return_value = ["a", "b", "c"]
|
||||
|
||||
self.assertEqual(["a", "b", "c"], glance.list())
|
||||
mock_glance_image__manager().list.assert_called_once_with(
|
||||
owner=glance.tenant_uuid)
|
||||
|
||||
|
||||
class CeilometerTestCase(test.TestCase):
|
||||
|
||||
def test_id(self):
|
||||
ceil = resources.CeilometerAlarms()
|
||||
ceil.raw_resource = mock.MagicMock()
|
||||
self.assertEqual(ceil.raw_resource.alarm_id, ceil.id())
|
||||
|
||||
@mock.patch("%s.CeilometerAlarms._manager" % BASE)
|
||||
def test_list(self, mock_ceilometer_alarms__manager):
|
||||
|
||||
ceil = resources.CeilometerAlarms()
|
||||
ceil.tenant_uuid = mock.MagicMock()
|
||||
mock_ceilometer_alarms__manager().list.return_value = ["a", "b", "c"]
|
||||
mock_ceilometer_alarms__manager.reset_mock()
|
||||
|
||||
self.assertEqual(["a", "b", "c"], ceil.list())
|
||||
mock_ceilometer_alarms__manager().list.assert_called_once_with(
|
||||
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):
|
||||
self.assertTrue(resources.KeystoneMixin().is_deleted())
|
||||
|
||||
def get_keystone_mixin(self):
|
||||
kmixin = resources.KeystoneMixin()
|
||||
kmixin._service = "keystone"
|
||||
return kmixin
|
||||
|
||||
@mock.patch("%s.keystone_wrapper.wrap" % BASE)
|
||||
def test_manager(self, mock_wrap):
|
||||
keystone_mixin = self.get_keystone_mixin()
|
||||
keystone_mixin.admin = mock.MagicMock()
|
||||
self.assertEqual(mock_wrap.return_value, keystone_mixin._manager())
|
||||
mock_wrap.assert_called_once_with(
|
||||
keystone_mixin.admin.keystone.return_value)
|
||||
|
||||
@mock.patch("%s.keystone_wrapper.wrap" % BASE)
|
||||
def test_delete(self, mock_wrap):
|
||||
keystone_mixin = self.get_keystone_mixin()
|
||||
keystone_mixin._resource = "some_resource"
|
||||
keystone_mixin.id = lambda: "id_a"
|
||||
keystone_mixin.admin = mock.MagicMock()
|
||||
|
||||
keystone_mixin.delete()
|
||||
mock_wrap.assert_called_once_with(
|
||||
keystone_mixin.admin.keystone.return_value)
|
||||
mock_wrap().delete_some_resource.assert_called_once_with("id_a")
|
||||
|
||||
@mock.patch(
|
||||
"rally.plugins.openstack.scenarios.keystone.utils.is_temporary")
|
||||
@mock.patch("%s.keystone_wrapper.wrap" % BASE)
|
||||
def test_list(self, mock_wrap, mock_is_temporary):
|
||||
keystone_mixin = self.get_keystone_mixin()
|
||||
keystone_mixin._resource = "some_resource2"
|
||||
keystone_mixin.admin = mock.MagicMock()
|
||||
|
||||
result = [mock.MagicMock(), mock.MagicMock(), mock.MagicMock()]
|
||||
mock_is_temporary.side_effect = [True, True, False]
|
||||
|
||||
mock_wrap().list_some_resource2s.return_value = result
|
||||
|
||||
self.assertSequenceEqual(result[:2], keystone_mixin.list())
|
||||
mock_wrap().list_some_resource2s.assert_called_once_with()
|
||||
|
||||
mock_is_temporary.assert_has_calls([mock.call(r) for r in result])
|
||||
|
||||
|
||||
class SwiftMixinTestCase(test.TestCase):
|
||||
|
||||
def get_swift_mixin(self):
|
||||
swift_mixin = resources.SwiftMixin()
|
||||
swift_mixin._service = "swift"
|
||||
return swift_mixin
|
||||
|
||||
def test_manager(self):
|
||||
swift_mixin = self.get_swift_mixin()
|
||||
swift_mixin.user = mock.MagicMock()
|
||||
self.assertEqual(swift_mixin.user.swift.return_value,
|
||||
swift_mixin._manager())
|
||||
|
||||
def test_id(self):
|
||||
swift_mixin = self.get_swift_mixin()
|
||||
swift_mixin.raw_resource = mock.MagicMock()
|
||||
self.assertEqual(swift_mixin.raw_resource, swift_mixin.id())
|
||||
|
||||
def test_delete(self):
|
||||
swift_mixin = self.get_swift_mixin()
|
||||
swift_mixin.user = mock.MagicMock()
|
||||
swift_mixin._resource = "some_resource"
|
||||
swift_mixin.raw_resource = mock.MagicMock()
|
||||
swift_mixin.delete()
|
||||
swift_mixin.user.swift().delete_some_resource.assert_called_once_with(
|
||||
*swift_mixin.raw_resource)
|
||||
|
||||
|
||||
class SwiftObjectTestCase(test.TestCase):
|
||||
|
||||
@mock.patch("%s.SwiftMixin._manager" % BASE)
|
||||
def test_list(self, mock_swift_mixin__manager):
|
||||
containers = [mock.MagicMock(), mock.MagicMock()]
|
||||
objects = [mock.MagicMock(), mock.MagicMock(), mock.MagicMock()]
|
||||
mock_swift_mixin__manager().get_account.return_value = (
|
||||
"header", containers)
|
||||
mock_swift_mixin__manager().get_container.return_value = (
|
||||
"header", objects)
|
||||
self.assertEqual(len(containers),
|
||||
len(resources.SwiftContainer().list()))
|
||||
self.assertEqual(len(containers) * len(objects),
|
||||
len(resources.SwiftObject().list()))
|
||||
|
||||
|
||||
class SwiftContainerTestCase(test.TestCase):
|
||||
|
||||
@mock.patch("%s.SwiftMixin._manager" % BASE)
|
||||
def test_list(self, mock_swift_mixin__manager):
|
||||
containers = [mock.MagicMock(), mock.MagicMock(), mock.MagicMock()]
|
||||
mock_swift_mixin__manager().get_account.return_value = (
|
||||
"header", containers)
|
||||
self.assertEqual(len(containers),
|
||||
len(resources.SwiftContainer().list()))
|
||||
|
||||
|
||||
class ManilaShareTestCase(test.TestCase):
|
||||
|
||||
def test_list(self):
|
||||
share_resource = resources.ManilaShare()
|
||||
share_resource._manager = mock.MagicMock()
|
||||
|
||||
share_resource.list()
|
||||
|
||||
self.assertEqual("shares", share_resource._resource)
|
||||
share_resource._manager.return_value.list.assert_called_once_with()
|
||||
|
||||
def test_delete(self):
|
||||
share_resource = resources.ManilaShare()
|
||||
share_resource._manager = mock.MagicMock()
|
||||
share_resource.id = lambda: "fake_id"
|
||||
|
||||
share_resource.delete()
|
||||
|
||||
self.assertEqual("shares", share_resource._resource)
|
||||
share_resource._manager.return_value.delete.assert_called_once_with(
|
||||
"fake_id")
|
||||
|
||||
|
||||
class ManilaShareNetworkTestCase(test.TestCase):
|
||||
|
||||
def test_list(self):
|
||||
sn_resource = resources.ManilaShareNetwork()
|
||||
sn_resource._manager = mock.MagicMock()
|
||||
|
||||
sn_resource.list()
|
||||
|
||||
self.assertEqual("share_networks", sn_resource._resource)
|
||||
sn_resource._manager.return_value.list.assert_called_once_with()
|
||||
|
||||
def test_delete(self):
|
||||
sn_resource = resources.ManilaShareNetwork()
|
||||
sn_resource._manager = mock.MagicMock()
|
||||
sn_resource.id = lambda: "fake_id"
|
||||
|
||||
sn_resource.delete()
|
||||
|
||||
self.assertEqual("share_networks", sn_resource._resource)
|
||||
sn_resource._manager.return_value.delete.assert_called_once_with(
|
||||
"fake_id")
|
||||
|
||||
|
||||
class ManilaSecurityServiceTestCase(test.TestCase):
|
||||
|
||||
def test_list(self):
|
||||
ss_resource = resources.ManilaSecurityService()
|
||||
ss_resource._manager = mock.MagicMock()
|
||||
|
||||
ss_resource.list()
|
||||
|
||||
self.assertEqual("security_services", ss_resource._resource)
|
||||
ss_resource._manager.return_value.list.assert_called_once_with()
|
||||
|
||||
def test_delete(self):
|
||||
ss_resource = resources.ManilaSecurityService()
|
||||
ss_resource._manager = mock.MagicMock()
|
||||
ss_resource.id = lambda: "fake_id"
|
||||
|
||||
ss_resource.delete()
|
||||
|
||||
self.assertEqual("security_services", ss_resource._resource)
|
||||
ss_resource._manager.return_value.delete.assert_called_once_with(
|
||||
"fake_id")
|
||||
|
||||
|
||||
class FuelEnvironmentTestCase(test.TestCase):
|
||||
|
||||
def test_id(self):
|
||||
fres = resources.FuelEnvironment()
|
||||
fres.raw_resource = {"id": 42, "name": "chavez"}
|
||||
self.assertEqual(42, fres.id())
|
||||
|
||||
@mock.patch("%s.FuelEnvironment._manager" % BASE)
|
||||
def test_is_deleted(self, mock__manager):
|
||||
mock__manager.return_value.get.return_value = None
|
||||
fres = resources.FuelEnvironment()
|
||||
fres.id = mock.Mock()
|
||||
self.assertTrue(fres.is_deleted())
|
||||
mock__manager.return_value.get.return_value = "env"
|
||||
self.assertFalse(fres.is_deleted())
|
||||
mock__manager.return_value.get.assert_called_with(fres.id.return_value)
|
||||
|
||||
@mock.patch("%s.FuelEnvironment._manager" % BASE)
|
||||
@mock.patch("rally.common.utils.name_matches_object")
|
||||
def test_list(self, mock_name_matches_object, mock__manager):
|
||||
envs = [{"name": "rally_one"}, {"name": "rally_two"},
|
||||
{"name": "three"}]
|
||||
mock__manager.return_value.list.return_value = envs
|
||||
mock_name_matches_object.side_effect = (
|
||||
lambda n, o: n.startswith("rally_"))
|
||||
|
||||
fres = resources.FuelEnvironment()
|
||||
self.assertEqual(envs[:-1], fres.list())
|
Loading…
x
Reference in New Issue
Block a user