Allow tempest cleanup delete resources based on prefix

A warning in command description of run cleanup first with dry-run
is added. The cleanup behavior is extended to allow users to delete
only resources if their name starts with a certain prefix.

Closes-Bug: #1945082
Change-Id: I65dfe051c891b3679538acec713e8616746c47f6
This commit is contained in:
Luigi Dino Tamagnone 2023-04-09 15:24:45 +00:00 committed by Martin Kopec
parent 01c2e2ff7e
commit 9052dfcc85
11 changed files with 277 additions and 100 deletions

View File

@ -17,6 +17,16 @@
# fail early if anything missing the IPv6 settings or deployments. # fail early if anything missing the IPv6 settings or deployments.
- devstack-ipv6-only-deployments-verification - devstack-ipv6-only-deployments-verification
tasks: tasks:
- name: Run tempest cleanup init-saved-state
include_role:
name: tempest-cleanup
vars:
init_saved_state: true
when: (run_tempest_dry_cleanup is defined and run_tempest_dry_cleanup | bool) or
(run_tempest_cleanup is defined and run_tempest_cleanup | bool) or
(run_tempest_fail_if_leaked_resources is defined and run_tempest_fail_if_leaked_resources | bool) or
(run_tempest_cleanup_prefix is defined and run_tempest_cleanup_prefix | bool)
- name: Run Tempest version <= 26.0.0 - name: Run Tempest version <= 26.0.0
include_role: include_role:
name: run-tempest-26 name: run-tempest-26
@ -30,3 +40,15 @@
when: when:
- zuul.branch is defined - zuul.branch is defined
- zuul.branch not in ["stable/ocata", "stable/pike", "stable/queens", "stable/rocky", "stable/stein"] - zuul.branch not in ["stable/ocata", "stable/pike", "stable/queens", "stable/rocky", "stable/stein"]
- name: Run tempest cleanup dry-run
include_role:
name: tempest-cleanup
vars:
dry_run: true
when: run_tempest_dry_cleanup is defined and run_tempest_dry_cleanup | bool
- name: Run tempest cleanup
include_role:
name: tempest-cleanup
when: run_tempest_cleanup is defined and run_tempest_cleanup | bool

View File

@ -27,7 +27,8 @@
init_saved_state: true init_saved_state: true
when: (run_tempest_dry_cleanup is defined and run_tempest_dry_cleanup | bool) or when: (run_tempest_dry_cleanup is defined and run_tempest_dry_cleanup | bool) or
(run_tempest_cleanup is defined and run_tempest_cleanup | bool) or (run_tempest_cleanup is defined and run_tempest_cleanup | bool) or
(run_tempest_fail_if_leaked_resources is defined and run_tempest_fail_if_leaked_resources | bool) (run_tempest_fail_if_leaked_resources is defined and run_tempest_fail_if_leaked_resources | bool) or
(run_tempest_cleanup_prefix is defined and run_tempest_cleanup_prefix | bool)
- name: Run Tempest version <= 26.0.0 - name: Run Tempest version <= 26.0.0
include_role: include_role:

View File

@ -0,0 +1,10 @@
---
features:
- |
We add a new argument, ``--prefix``, to ``tempest cleanup`` tool that will
allow users delete only resources that match the prefix. When this option
is used, ``saved_state.json`` file is not needed (no need to run with
``--init-saved-state`` first). If there is one, it will be ignored and the
cleanup will be done based on the given prefix only.
Note, that some resources are not named thus they will not be deleted when
filtering based on the prefix.

View File

@ -40,6 +40,12 @@ saved_state.json file.
some must have been leaked. This can be also used to verify that tempest some must have been leaked. This can be also used to verify that tempest
cleanup was successful. cleanup was successful.
.. zuul:rolevar:: run_tempest_cleanup_prefix
:default: false
When true, tempest cleanup will be called with '--prefix tempest' to delete
only resources with names that match the prefix. This option can be used
together with dry_run.
Role usage Role usage
---------- ----------

View File

@ -2,3 +2,4 @@ devstack_base_dir: /opt/stack
init_saved_state: false init_saved_state: false
dry_run: false dry_run: false
run_tempest_fail_if_leaked_resources: false run_tempest_fail_if_leaked_resources: false
run_tempest_cleanup_prefix: false

View File

@ -5,3 +5,12 @@
command: tox -evenv-tempest -- tempest cleanup --dry-run --debug command: tox -evenv-tempest -- tempest cleanup --dry-run --debug
args: args:
chdir: "{{ devstack_base_dir }}/tempest" chdir: "{{ devstack_base_dir }}/tempest"
when: not run_tempest_cleanup_prefix
- name: Run tempest cleanup dry-run with tempest prefix
become: yes
become_user: tempest
command: tox -evenv-tempest -- tempest cleanup --dry-run --debug --prefix tempest
args:
chdir: "{{ devstack_base_dir }}/tempest"
when: run_tempest_cleanup_prefix

View File

@ -27,6 +27,15 @@
command: tox -evenv-tempest -- tempest cleanup --debug command: tox -evenv-tempest -- tempest cleanup --debug
args: args:
chdir: "{{ devstack_base_dir }}/tempest" chdir: "{{ devstack_base_dir }}/tempest"
when: not run_tempest_cleanup_prefix
- name: Run tempest cleanup with tempest prefix
become: yes
become_user: tempest
command: tox -evenv-tempest -- tempest cleanup --debug --prefix tempest
args:
chdir: "{{ devstack_base_dir }}/tempest"
when: run_tempest_cleanup_prefix
- when: - when:
- run_tempest_fail_if_leaked_resources - run_tempest_fail_if_leaked_resources

View File

@ -26,6 +26,10 @@ specified in ``tempest.conf`` is never deleted.
Example Run Example Run
----------- -----------
.. warning::
We advice not to run tempest cleanup on production environments.
.. warning:: .. warning::
If step 1 is skipped in the example below, the cleanup procedure If step 1 is skipped in the example below, the cleanup procedure
@ -45,7 +49,10 @@ Runtime Arguments
* ``--init-saved-state``: Initializes the saved state of the OpenStack * ``--init-saved-state``: Initializes the saved state of the OpenStack
deployment and will output a ``saved_state.json`` file containing resources deployment and will output a ``saved_state.json`` file containing resources
from your deployment that will be preserved from the cleanup command. This from your deployment that will be preserved from the cleanup command. This
should be done prior to running Tempest tests. should be done prior to running Tempest tests. Note, that if other users of
your cloud could have created resources after running ``--init-saved-state``,
it would not protect those resources as they wouldn't be present in the
saved_state.json file.
* ``--delete-tempest-conf-objects``: If option is present, then the command * ``--delete-tempest-conf-objects``: If option is present, then the command
will delete the admin project in addition to the resources associated with will delete the admin project in addition to the resources associated with
@ -58,7 +65,27 @@ Runtime Arguments
global objects that will be removed (domains, flavors, images, roles, global objects that will be removed (domains, flavors, images, roles,
projects, and users). Once the cleanup command is executed (e.g. run without projects, and users). Once the cleanup command is executed (e.g. run without
parameters), running it again with ``--dry-run`` should yield an empty parameters), running it again with ``--dry-run`` should yield an empty
report. report. We STRONGLY ENCOURAGE to run ``tempest cleanup`` with ``--dry-run``
first and then verify that the resources listed in the ``dry_run.json`` file
are meant to be deleted.
* ``--prefix``: Only resources that match the prefix will be deleted. When this
option is used, ``saved_state.json`` file is not needed (no need to run with
``--init-saved-state`` first).
All tempest resources are created with the prefix value from the config
option ``resource_name_prefix`` in tempest.conf. To cleanup only the
resources created by tempest, you should use the prefix set in your
tempest.conf (the default value of ``resource_name_prefix`` is ``tempest``.
Note, that some resources are not named thus they will not be deleted when
filtering based on the prefix. This option will be ignored when
``--init-saved-state`` is used so that it can capture the true init state -
all resources present at that moment. If there is any ``saved_state.json``
file present (e.g. if you ran the tempest cleanup with ``--init-saved-state``
before) and you run the tempest cleanup with ``--prefix``, the
``saved_state.json`` file will be ignored and cleanup will be done based on
the passed prefix only.
* ``--help``: Print the help text for the command and parameters. * ``--help``: Print the help text for the command and parameters.
@ -157,6 +184,7 @@ class TempestCleanup(command.Command):
is_dry_run = self.options.dry_run is_dry_run = self.options.dry_run
is_preserve = not self.options.delete_tempest_conf_objects is_preserve = not self.options.delete_tempest_conf_objects
is_save_state = False is_save_state = False
cleanup_prefix = self.options.prefix
if is_dry_run: if is_dry_run:
self.dry_run_data["_projects_to_clean"] = {} self.dry_run_data["_projects_to_clean"] = {}
@ -168,7 +196,8 @@ class TempestCleanup(command.Command):
'is_dry_run': is_dry_run, 'is_dry_run': is_dry_run,
'saved_state_json': self.json_data, 'saved_state_json': self.json_data,
'is_preserve': False, 'is_preserve': False,
'is_save_state': is_save_state} 'is_save_state': is_save_state,
'prefix': cleanup_prefix}
project_service = cleanup_service.ProjectService(admin_mgr, **kwargs) project_service = cleanup_service.ProjectService(admin_mgr, **kwargs)
projects = project_service.list() projects = project_service.list()
LOG.info("Processing %s projects", len(projects)) LOG.info("Processing %s projects", len(projects))
@ -182,6 +211,7 @@ class TempestCleanup(command.Command):
'saved_state_json': self.json_data, 'saved_state_json': self.json_data,
'is_preserve': is_preserve, 'is_preserve': is_preserve,
'is_save_state': is_save_state, 'is_save_state': is_save_state,
'prefix': cleanup_prefix,
'got_exceptions': self.GOT_EXCEPTIONS} 'got_exceptions': self.GOT_EXCEPTIONS}
LOG.info("Processing global services") LOG.info("Processing global services")
for service in self.global_services: for service in self.global_services:
@ -206,6 +236,7 @@ class TempestCleanup(command.Command):
project_id = project['id'] project_id = project['id']
project_name = project['name'] project_name = project['name']
project_data = None project_data = None
cleanup_prefix = self.options.prefix
if is_dry_run: if is_dry_run:
project_data = dry_run_data["_projects_to_clean"][project_id] = {} project_data = dry_run_data["_projects_to_clean"][project_id] = {}
project_data['name'] = project_name project_data['name'] = project_name
@ -216,6 +247,7 @@ class TempestCleanup(command.Command):
'is_preserve': is_preserve, 'is_preserve': is_preserve,
'is_save_state': False, 'is_save_state': False,
'project_id': project_id, 'project_id': project_id,
'prefix': cleanup_prefix,
'got_exceptions': self.GOT_EXCEPTIONS} 'got_exceptions': self.GOT_EXCEPTIONS}
for service in self.project_associated_services: for service in self.project_associated_services:
svc = service(self.admin_mgr, **kwargs) svc = service(self.admin_mgr, **kwargs)
@ -243,10 +275,26 @@ class TempestCleanup(command.Command):
help="Generate JSON file:" + DRY_RUN_JSON + help="Generate JSON file:" + DRY_RUN_JSON +
", that reports the objects that would have " ", that reports the objects that would have "
"been deleted had a full cleanup been run.") "been deleted had a full cleanup been run.")
parser.add_argument('--prefix', dest='prefix', default=None,
help="Only resources that match the prefix will "
"be deleted (resources in saved_state.json are "
"not taken into account). All tempest resources "
"are created with the prefix value set by "
"resource_name_prefix in tempest.conf, default "
"prefix is tempest. Note that some resources are "
"not named thus they will not be deleted when "
"filtering based on the prefix. This opt will be "
"ignored when --init-saved-state is used so that "
"it can capture the true init state - all "
"resources present at that moment.")
return parser return parser
def get_description(self): def get_description(self):
return 'Cleanup after tempest run' return ('tempest cleanup tool, read the full documentation before '
'using this tool. We advice not to run it on production '
'environments. On environments where also other users may '
'create resources, we strongly advice using --dry-run '
'argument first and verify the content of dry_run.json file.')
def _init_state(self): def _init_state(self):
LOG.info("Initializing saved state.") LOG.info("Initializing saved state.")
@ -257,6 +305,10 @@ class TempestCleanup(command.Command):
'saved_state_json': data, 'saved_state_json': data,
'is_preserve': False, 'is_preserve': False,
'is_save_state': True, 'is_save_state': True,
# must be None as we want to capture true init state
# (all resources present) thus no filtering based
# on the prefix
'prefix': None,
'got_exceptions': self.GOT_EXCEPTIONS} 'got_exceptions': self.GOT_EXCEPTIONS}
for service in self.global_services: for service in self.global_services:
svc = service(admin_mgr, **kwargs) svc = service(admin_mgr, **kwargs)

View File

@ -115,6 +115,16 @@ class BaseService(object):
return [item for item in item_list return [item for item in item_list
if item['tenant_id'] == self.tenant_id] if item['tenant_id'] == self.tenant_id]
def _filter_by_prefix(self, item_list):
items = [item for item in item_list
if item['name'].startswith(self.prefix)]
return items
def _filter_out_ids_from_saved(self, item_list, attr):
items = [item for item in item_list if item['id']
not in self.saved_state_json[attr].keys()]
return items
def list(self): def list(self):
pass pass
@ -156,10 +166,11 @@ class SnapshotService(BaseService):
def list(self): def list(self):
client = self.client client = self.client
snaps = client.list_snapshots()['snapshots'] snaps = client.list_snapshots()['snapshots']
if not self.is_save_state: if self.prefix:
snaps = self._filter_by_prefix(snaps)
elif not self.is_save_state:
# recreate list removing saved snapshots # recreate list removing saved snapshots
snaps = [snap for snap in snaps if snap['id'] snaps = self._filter_out_ids_from_saved(snaps, 'snapshots')
not in self.saved_state_json['snapshots'].keys()]
LOG.debug("List count, %s Snapshots", len(snaps)) LOG.debug("List count, %s Snapshots", len(snaps))
return snaps return snaps
@ -194,10 +205,11 @@ class ServerService(BaseService):
client = self.client client = self.client
servers_body = client.list_servers() servers_body = client.list_servers()
servers = servers_body['servers'] servers = servers_body['servers']
if not self.is_save_state: if self.prefix:
servers = self._filter_by_prefix(servers)
elif not self.is_save_state:
# recreate list removing saved servers # recreate list removing saved servers
servers = [server for server in servers if server['id'] servers = self._filter_out_ids_from_saved(servers, 'servers')
not in self.saved_state_json['servers'].keys()]
LOG.debug("List count, %s Servers", len(servers)) LOG.debug("List count, %s Servers", len(servers))
return servers return servers
@ -227,10 +239,11 @@ class ServerGroupService(ServerService):
def list(self): def list(self):
client = self.server_groups_client client = self.server_groups_client
sgs = client.list_server_groups()['server_groups'] sgs = client.list_server_groups()['server_groups']
if not self.is_save_state: if self.prefix:
sgs = self._filter_by_prefix(sgs)
elif not self.is_save_state:
# recreate list removing saved server_groups # recreate list removing saved server_groups
sgs = [sg for sg in sgs if sg['id'] sgs = self._filter_out_ids_from_saved(sgs, 'server_groups')
not in self.saved_state_json['server_groups'].keys()]
LOG.debug("List count, %s Server Groups", len(sgs)) LOG.debug("List count, %s Server Groups", len(sgs))
return sgs return sgs
@ -263,7 +276,9 @@ class KeyPairService(BaseService):
def list(self): def list(self):
client = self.client client = self.client
keypairs = client.list_keypairs()['keypairs'] keypairs = client.list_keypairs()['keypairs']
if not self.is_save_state: if self.prefix:
keypairs = self._filter_by_prefix(keypairs)
elif not self.is_save_state:
# recreate list removing saved keypairs # recreate list removing saved keypairs
keypairs = [keypair for keypair in keypairs keypairs = [keypair for keypair in keypairs
if keypair['keypair']['name'] if keypair['keypair']['name']
@ -302,10 +317,11 @@ class VolumeService(BaseService):
def list(self): def list(self):
client = self.client client = self.client
vols = client.list_volumes()['volumes'] vols = client.list_volumes()['volumes']
if not self.is_save_state: if self.prefix:
vols = self._filter_by_prefix(vols)
elif not self.is_save_state:
# recreate list removing saved volumes # recreate list removing saved volumes
vols = [vol for vol in vols if vol['id'] vols = self._filter_out_ids_from_saved(vols, 'volumes')
not in self.saved_state_json['volumes'].keys()]
LOG.debug("List count, %s Volumes", len(vols)) LOG.debug("List count, %s Volumes", len(vols))
return vols return vols
@ -336,6 +352,10 @@ class VolumeQuotaService(BaseService):
self.client = manager.volume_quotas_client_latest self.client = manager.volume_quotas_client_latest
def delete(self): def delete(self):
if self.prefix:
# this means we're cleaning resources based on a certain prefix,
# this resource doesn't have a name, therefore do nothing
return
client = self.client client = self.client
try: try:
LOG.debug("Deleting Volume Quotas for project with id %s", LOG.debug("Deleting Volume Quotas for project with id %s",
@ -346,6 +366,10 @@ class VolumeQuotaService(BaseService):
self.project_id) self.project_id)
def dry_run(self): def dry_run(self):
if self.prefix:
# this means we're cleaning resources based on a certain prefix,
# this resource doesn't have a name, therefore do nothing
return
quotas = self.client.show_quota_set( quotas = self.client.show_quota_set(
self.project_id, params={'usage': True})['quota_set'] self.project_id, params={'usage': True})['quota_set']
self.data['volume_quotas'] = quotas self.data['volume_quotas'] = quotas
@ -358,6 +382,10 @@ class NovaQuotaService(BaseService):
self.limits_client = manager.limits_client self.limits_client = manager.limits_client
def delete(self): def delete(self):
if self.prefix:
# this means we're cleaning resources based on a certain prefix,
# this resource doesn't have a name, therefore do nothing
return
client = self.client client = self.client
try: try:
LOG.debug("Deleting Nova Quotas for project with id %s", LOG.debug("Deleting Nova Quotas for project with id %s",
@ -368,6 +396,10 @@ class NovaQuotaService(BaseService):
self.project_id) self.project_id)
def dry_run(self): def dry_run(self):
if self.prefix:
# this means we're cleaning resources based on a certain prefix,
# this resource doesn't have a name, therefore do nothing
return
client = self.limits_client client = self.limits_client
quotas = client.show_limits()['limits'] quotas = client.show_limits()['limits']
self.data['compute_quotas'] = quotas['absolute'] self.data['compute_quotas'] = quotas['absolute']
@ -379,6 +411,10 @@ class NetworkQuotaService(BaseService):
self.client = manager.network_quotas_client self.client = manager.network_quotas_client
def delete(self): def delete(self):
if self.prefix:
# this means we're cleaning resources based on a certain prefix,
# this resource doesn't have a name, therefore do nothing
return
client = self.client client = self.client
try: try:
LOG.debug("Deleting Network Quotas for project with id %s", LOG.debug("Deleting Network Quotas for project with id %s",
@ -389,6 +425,10 @@ class NetworkQuotaService(BaseService):
self.project_id) self.project_id)
def dry_run(self): def dry_run(self):
if self.prefix:
# this means we're cleaning resources based on a certain prefix,
# this resource doesn't have a name, therefore do nothing
return
resp = [quota for quota in self.client.list_quotas()['quotas'] resp = [quota for quota in self.client.list_quotas()['quotas']
if quota['project_id'] == self.project_id] if quota['project_id'] == self.project_id]
self.data['network_quotas'] = resp self.data['network_quotas'] = resp
@ -422,11 +462,13 @@ class NetworkService(BaseNetworkService):
client = self.networks_client client = self.networks_client
networks = client.list_networks(**self.tenant_filter) networks = client.list_networks(**self.tenant_filter)
networks = networks['networks'] networks = networks['networks']
if self.prefix:
if not self.is_save_state: networks = self._filter_by_prefix(networks)
# recreate list removing saved networks else:
networks = [network for network in networks if network['id'] if not self.is_save_state:
not in self.saved_state_json['networks'].keys()] # recreate list removing saved networks
networks = self._filter_out_ids_from_saved(
networks, 'networks')
# filter out networks declared in tempest.conf # filter out networks declared in tempest.conf
if self.is_preserve: if self.is_preserve:
networks = [network for network in networks networks = [network for network in networks
@ -458,14 +500,17 @@ class NetworkService(BaseNetworkService):
class NetworkFloatingIpService(BaseNetworkService): class NetworkFloatingIpService(BaseNetworkService):
def list(self): def list(self):
if self.prefix:
# this means we're cleaning resources based on a certain prefix,
# this resource doesn't have a name, therefore return empty list
return []
client = self.floating_ips_client client = self.floating_ips_client
flips = client.list_floatingips(**self.tenant_filter) flips = client.list_floatingips(**self.tenant_filter)
flips = flips['floatingips'] flips = flips['floatingips']
if not self.is_save_state: if not self.is_save_state:
# recreate list removing saved flips # recreate list removing saved flips
flips = [flip for flip in flips if flip['id'] flips = self._filter_out_ids_from_saved(flips, 'floatingips')
not in self.saved_state_json['floatingips'].keys()]
LOG.debug("List count, %s Network Floating IPs", len(flips)) LOG.debug("List count, %s Network Floating IPs", len(flips))
return flips return flips
@ -498,15 +543,15 @@ class NetworkRouterService(BaseNetworkService):
client = self.routers_client client = self.routers_client
routers = client.list_routers(**self.tenant_filter) routers = client.list_routers(**self.tenant_filter)
routers = routers['routers'] routers = routers['routers']
if self.prefix:
if not self.is_save_state: routers = self._filter_by_prefix(routers)
# recreate list removing saved routers else:
routers = [router for router in routers if router['id'] if not self.is_save_state:
not in self.saved_state_json['routers'].keys()] # recreate list removing saved routers
routers = self._filter_out_ids_from_saved(routers, 'routers')
if self.is_preserve: if self.is_preserve:
routers = [router for router in routers routers = [router for router in routers
if router['id'] != CONF_PUB_ROUTER] if router['id'] != CONF_PUB_ROUTER]
LOG.debug("List count, %s Routers", len(routers)) LOG.debug("List count, %s Routers", len(routers))
return routers return routers
@ -547,15 +592,19 @@ class NetworkRouterService(BaseNetworkService):
class NetworkMeteringLabelRuleService(NetworkService): class NetworkMeteringLabelRuleService(NetworkService):
def list(self): def list(self):
if self.prefix:
# this means we're cleaning resources based on a certain prefix,
# this resource doesn't have a name, therefore return empty list
return []
client = self.metering_label_rules_client client = self.metering_label_rules_client
rules = client.list_metering_label_rules() rules = client.list_metering_label_rules()
rules = rules['metering_label_rules'] rules = rules['metering_label_rules']
rules = self._filter_by_tenant_id(rules) rules = self._filter_by_tenant_id(rules)
if not self.is_save_state: if not self.is_save_state:
saved_rules = self.saved_state_json['metering_label_rules'].keys() rules = self._filter_out_ids_from_saved(
rules, 'metering_label_rules')
# recreate list removing saved rules # recreate list removing saved rules
rules = [rule for rule in rules if rule['id'] not in saved_rules]
LOG.debug("List count, %s Metering Label Rules", len(rules)) LOG.debug("List count, %s Metering Label Rules", len(rules))
return rules return rules
@ -589,11 +638,12 @@ class NetworkMeteringLabelService(BaseNetworkService):
labels = client.list_metering_labels() labels = client.list_metering_labels()
labels = labels['metering_labels'] labels = labels['metering_labels']
labels = self._filter_by_tenant_id(labels) labels = self._filter_by_tenant_id(labels)
if self.prefix:
if not self.is_save_state: labels = self._filter_by_prefix(labels)
elif not self.is_save_state:
# recreate list removing saved labels # recreate list removing saved labels
labels = [label for label in labels if label['id'] labels = self._filter_out_ids_from_saved(
not in self.saved_state_json['metering_labels'].keys()] labels, 'metering_labels')
LOG.debug("List count, %s Metering Labels", len(labels)) LOG.debug("List count, %s Metering Labels", len(labels))
return labels return labels
@ -627,14 +677,14 @@ class NetworkPortService(BaseNetworkService):
client.list_ports(**self.tenant_filter)['ports'] client.list_ports(**self.tenant_filter)['ports']
if port["device_owner"] == "" or if port["device_owner"] == "" or
port["device_owner"].startswith("compute:")] port["device_owner"].startswith("compute:")]
if self.prefix:
if not self.is_save_state: ports = self._filter_by_prefix(ports)
# recreate list removing saved ports else:
ports = [port for port in ports if port['id'] if not self.is_save_state:
not in self.saved_state_json['ports'].keys()] # recreate list removing saved ports
ports = self._filter_out_ids_from_saved(ports, 'ports')
if self.is_preserve: if self.is_preserve:
ports = self._filter_by_conf_networks(ports) ports = self._filter_by_conf_networks(ports)
LOG.debug("List count, %s Ports", len(ports)) LOG.debug("List count, %s Ports", len(ports))
return ports return ports
@ -667,16 +717,18 @@ class NetworkSecGroupService(BaseNetworkService):
secgroups = [secgroup for secgroup in secgroups = [secgroup for secgroup in
client.list_security_groups(**filter)['security_groups'] client.list_security_groups(**filter)['security_groups']
if secgroup['name'] != 'default'] if secgroup['name'] != 'default']
if self.prefix:
if not self.is_save_state: secgroups = self._filter_by_prefix(secgroups)
# recreate list removing saved security_groups else:
secgroups = [secgroup for secgroup in secgroups if secgroup['id'] if not self.is_save_state:
not in self.saved_state_json['security_groups'].keys() # recreate list removing saved security_groups
] secgroups = self._filter_out_ids_from_saved(
secgroups, 'security_groups')
if self.is_preserve: if self.is_preserve:
secgroups = [secgroup for secgroup in secgroups secgroups = [
if secgroup['security_group_rules'][0]['project_id'] secgroup for secgroup in secgroups
not in CONF_PROJECTS] if secgroup['security_group_rules'][0]['project_id']
not in CONF_PROJECTS]
LOG.debug("List count, %s security_groups", len(secgroups)) LOG.debug("List count, %s security_groups", len(secgroups))
return secgroups return secgroups
@ -708,10 +760,12 @@ class NetworkSubnetService(BaseNetworkService):
client = self.subnets_client client = self.subnets_client
subnets = client.list_subnets(**self.tenant_filter) subnets = client.list_subnets(**self.tenant_filter)
subnets = subnets['subnets'] subnets = subnets['subnets']
if not self.is_save_state: if self.prefix:
# recreate list removing saved subnets subnets = self._filter_by_prefix(subnets)
subnets = [subnet for subnet in subnets if subnet['id'] else:
not in self.saved_state_json['subnets'].keys()] if not self.is_save_state:
# recreate list removing saved subnets
subnets = self._filter_out_ids_from_saved(subnets, 'subnets')
if self.is_preserve: if self.is_preserve:
subnets = self._filter_by_conf_networks(subnets) subnets = self._filter_by_conf_networks(subnets)
LOG.debug("List count, %s Subnets", len(subnets)) LOG.debug("List count, %s Subnets", len(subnets))
@ -743,10 +797,12 @@ class NetworkSubnetPoolsService(BaseNetworkService):
def list(self): def list(self):
client = self.subnetpools_client client = self.subnetpools_client
pools = client.list_subnetpools(**self.tenant_filter)['subnetpools'] pools = client.list_subnetpools(**self.tenant_filter)['subnetpools']
if not self.is_save_state: if self.prefix:
# recreate list removing saved subnet pools pools = self._filter_by_prefix(pools)
pools = [pool for pool in pools if pool['id'] else:
not in self.saved_state_json['subnetpools'].keys()] if not self.is_save_state:
# recreate list removing saved subnet pools
pools = self._filter_out_ids_from_saved(pools, 'subnetpools')
if self.is_preserve: if self.is_preserve:
pools = [pool for pool in pools if pool['project_id'] pools = [pool for pool in pools if pool['project_id']
not in CONF_PROJECTS] not in CONF_PROJECTS]
@ -782,11 +838,15 @@ class RegionService(BaseService):
self.client = manager.regions_client self.client = manager.regions_client
def list(self): def list(self):
if self.prefix:
# this means we're cleaning resources based on a certain prefix,
# this resource doesn't have a name, therefore return empty list
return []
client = self.client client = self.client
regions = client.list_regions() regions = client.list_regions()
if not self.is_save_state: if not self.is_save_state:
regions = [region for region in regions['regions'] if region['id'] regions = self._filter_out_ids_from_saved(
not in self.saved_state_json['regions'].keys()] regions['regions'], 'regions')
LOG.debug("List count, %s Regions", len(regions)) LOG.debug("List count, %s Regions", len(regions))
return regions return regions
else: else:
@ -824,11 +884,12 @@ class FlavorService(BaseService):
def list(self): def list(self):
client = self.client client = self.client
flavors = client.list_flavors({"is_public": None})['flavors'] flavors = client.list_flavors({"is_public": None})['flavors']
if not self.is_save_state: if self.prefix:
# recreate list removing saved flavors flavors = self._filter_by_prefix(flavors)
flavors = [flavor for flavor in flavors if flavor['id'] else:
not in self.saved_state_json['flavors'].keys()] if not self.is_save_state:
# recreate list removing saved flavors
flavors = self._filter_out_ids_from_saved(flavors, 'flavors')
if self.is_preserve: if self.is_preserve:
flavors = [flavor for flavor in flavors flavors = [flavor for flavor in flavors
if flavor['id'] not in CONF_FLAVORS] if flavor['id'] not in CONF_FLAVORS]
@ -871,10 +932,11 @@ class ImageService(BaseService):
marker = urllib.parse_qs(parsed.query)['marker'][0] marker = urllib.parse_qs(parsed.query)['marker'][0]
response = client.list_images(params={"marker": marker}) response = client.list_images(params={"marker": marker})
images.extend(response['images']) images.extend(response['images'])
if self.prefix:
if not self.is_save_state: images = self._filter_by_prefix(images)
images = [image for image in images if image['id'] else:
not in self.saved_state_json['images'].keys()] if not self.is_save_state:
images = self._filter_out_ids_from_saved(images, 'images')
if self.is_preserve: if self.is_preserve:
images = [image for image in images images = [image for image in images
if image['id'] not in CONF_IMAGES] if image['id'] not in CONF_IMAGES]
@ -910,19 +972,17 @@ class UserService(BaseService):
def list(self): def list(self):
users = self.client.list_users()['users'] users = self.client.list_users()['users']
if self.prefix:
if not self.is_save_state: users = self._filter_by_prefix(users)
users = [user for user in users if user['id'] else:
not in self.saved_state_json['users'].keys()] if not self.is_save_state:
users = self._filter_out_ids_from_saved(users, 'users')
if self.is_preserve: if self.is_preserve:
users = [user for user in users if user['name'] users = [user for user in users if user['name']
not in CONF_USERS] not in CONF_USERS]
elif not self.is_save_state: # Never delete admin user elif not self.is_save_state: # Never delete admin user
users = [user for user in users if user['name'] != users = [user for user in users if user['name'] !=
CONF.auth.admin_username] CONF.auth.admin_username]
LOG.debug("List count, %s Users after reconcile", len(users)) LOG.debug("List count, %s Users after reconcile", len(users))
return users return users
@ -955,13 +1015,14 @@ class RoleService(BaseService):
def list(self): def list(self):
try: try:
roles = self.client.list_roles()['roles'] roles = self.client.list_roles()['roles']
# reconcile roles with saved state and never list admin role if self.prefix:
if not self.is_save_state: roles = self._filter_by_prefix(roles)
roles = [role for role in roles if elif not self.is_save_state:
(role['id'] not in # reconcile roles with saved state and never list admin role
self.saved_state_json['roles'].keys() and roles = self._filter_out_ids_from_saved(roles, 'roles')
role['name'] != CONF.identity.admin_role)] roles = [role for role in roles
LOG.debug("List count, %s Roles after reconcile", len(roles)) if role['name'] != CONF.identity.admin_role]
LOG.debug("List count, %s Roles after reconcile", len(roles))
return roles return roles
except Exception: except Exception:
LOG.exception("Cannot retrieve Roles.") LOG.exception("Cannot retrieve Roles.")
@ -995,18 +1056,17 @@ class ProjectService(BaseService):
def list(self): def list(self):
projects = self.client.list_projects()['projects'] projects = self.client.list_projects()['projects']
if not self.is_save_state: if self.prefix:
project_ids = self.saved_state_json['projects'] projects = self._filter_by_prefix(projects)
projects = [project else:
for project in projects if not self.is_save_state:
if (project['id'] not in project_ids and projects = self._filter_out_ids_from_saved(
project['name'] != CONF.auth.admin_project_name)] projects, 'projects')
projects = [project for project in projects
if project['name'] != CONF.auth.admin_project_name]
if self.is_preserve: if self.is_preserve:
projects = [project projects = [project for project in projects
for project in projects
if project['name'] not in CONF_PROJECTS] if project['name'] not in CONF_PROJECTS]
LOG.debug("List count, %s Projects after reconcile", len(projects)) LOG.debug("List count, %s Projects after reconcile", len(projects))
return projects return projects
@ -1039,10 +1099,10 @@ class DomainService(BaseService):
def list(self): def list(self):
client = self.client client = self.client
domains = client.list_domains()['domains'] domains = client.list_domains()['domains']
if not self.is_save_state: if self.prefix:
domains = [domain for domain in domains if domain['id'] domains = self._filter_by_prefix(domains)
not in self.saved_state_json['domains'].keys()] elif not self.is_save_state:
domains = self._filter_out_ids_from_saved(domains, 'domains')
LOG.debug("List count, %s Domains after reconcile", len(domains)) LOG.debug("List count, %s Domains after reconcile", len(domains))
return domains return domains

View File

@ -44,6 +44,7 @@ class TestBaseService(base.TestCase):
'saved_state_json': {'saved': 'data'}, 'saved_state_json': {'saved': 'data'},
'is_preserve': False, 'is_preserve': False,
'is_save_state': True, 'is_save_state': True,
'prefix': 'tempest',
'tenant_id': 'project_id', 'tenant_id': 'project_id',
'got_exceptions': []} 'got_exceptions': []}
base = cleanup_service.BaseService(kwargs) base = cleanup_service.BaseService(kwargs)
@ -54,6 +55,7 @@ class TestBaseService(base.TestCase):
self.assertTrue(base.is_save_state) self.assertTrue(base.is_save_state)
self.assertEqual(base.tenant_filter['project_id'], kwargs['tenant_id']) self.assertEqual(base.tenant_filter['project_id'], kwargs['tenant_id'])
self.assertEqual(base.got_exceptions, kwargs['got_exceptions']) self.assertEqual(base.got_exceptions, kwargs['got_exceptions'])
self.assertEqual(base.prefix, kwargs['prefix'])
def test_not_implemented_ex(self): def test_not_implemented_ex(self):
kwargs = {'data': {'data': 'test'}, kwargs = {'data': {'data': 'test'},
@ -61,6 +63,7 @@ class TestBaseService(base.TestCase):
'saved_state_json': {'saved': 'data'}, 'saved_state_json': {'saved': 'data'},
'is_preserve': False, 'is_preserve': False,
'is_save_state': False, 'is_save_state': False,
'prefix': 'tempest',
'tenant_id': 'project_id', 'tenant_id': 'project_id',
'got_exceptions': []} 'got_exceptions': []}
base = self.TestException(kwargs) base = self.TestException(kwargs)
@ -188,7 +191,8 @@ class BaseCmdServiceTests(MockFunctionsBase):
service_name = 'default' service_name = 'default'
def _create_cmd_service(self, service_type, is_save_state=False, def _create_cmd_service(self, service_type, is_save_state=False,
is_preserve=False, is_dry_run=False): is_preserve=False, is_dry_run=False,
prefix=''):
creds = fake_credentials.FakeKeystoneV3Credentials() creds = fake_credentials.FakeKeystoneV3Credentials()
os = clients.Manager(creds) os = clients.Manager(creds)
return getattr(cleanup_service, service_type)( return getattr(cleanup_service, service_type)(
@ -196,6 +200,7 @@ class BaseCmdServiceTests(MockFunctionsBase):
is_save_state=is_save_state, is_save_state=is_save_state,
is_preserve=is_preserve, is_preserve=is_preserve,
is_dry_run=is_dry_run, is_dry_run=is_dry_run,
prefix=prefix,
project_id='b8e3ece07bb049138d224436756e3b57', project_id='b8e3ece07bb049138d224436756e3b57',
data={}, data={},
saved_state_json=self.saved_state saved_state_json=self.saved_state

View File

@ -58,6 +58,8 @@
Base integration test with Neutron networking, IPv6 and py3. Base integration test with Neutron networking, IPv6 and py3.
vars: vars:
tox_envlist: full tox_envlist: full
run_tempest_cleanup: true
run_tempest_cleanup_prefix: true
devstack_localrc: devstack_localrc:
USE_PYTHON3: true USE_PYTHON3: true
FORCE_CONFIG_DRIVE: true FORCE_CONFIG_DRIVE: true