blob: f641a95bd9eb6836cba3bf2935616c28bfa87312 [file] [log] [blame]
Dmitriy Kruglovb811e642022-10-06 12:24:33 +02001import argparse
Ievgeniia Zadorozhnaa33b29a2023-08-29 18:19:47 +03002import json
Dmitriy Kruglovb811e642022-10-06 12:24:33 +02003import os
4import re
5import sys
Ievgeniia Zadorozhnaa33b29a2023-08-29 18:19:47 +03006import time
7
8from types import SimpleNamespace
Dmitriy Kruglovb811e642022-10-06 12:24:33 +02009
10import openstack
11
12
13# Send logs to both, a log file and stdout
14openstack.enable_logging(debug=False, path='openstack.log', stream=sys.stdout)
15
Ievgeniia Zadorozhnaa33b29a2023-08-29 18:19:47 +030016volume_api_version = "3.43"
17
Dmitriy Kruglovb811e642022-10-06 12:24:33 +020018# Connect to cloud
19TEST_CLOUD = os.getenv('OS_TEST_CLOUD', 'os-cloud')
20cloud = openstack.connect(cloud=TEST_CLOUD)
21log = cloud.log
22
23# Get cloud config (clouds.yaml vars)
24config_obj = openstack.config.loader.OpenStackConfig()
25cloud_config = config_obj.get_one(cloud=TEST_CLOUD)
26
27compute = cloud.compute
28identity = cloud.identity
29image = cloud.image
30network = cloud.network
31orchestration = cloud.orchestration
Dmitriy Kruglovb811e642022-10-06 12:24:33 +020032volume = cloud.volume
Ievgeniia Zadorozhnaab334132023-08-01 23:20:13 +030033load_balancer = cloud.load_balancer
Dmitriy Kruglovb811e642022-10-06 12:24:33 +020034
Ievgeniia Zadorozhna66dbe122024-01-22 23:59:05 +010035# Check if Object Storage is present on the cloud, else skip
36object_store_present = any(service.type == 'object-store' for service
37 in list(identity.services()))
38if object_store_present:
39 object_store = cloud.object_store
40
Dmitriy Kruglovb811e642022-10-06 12:24:33 +020041mask = "cvp|s_rally|rally_|tempest-|tempest_|spt|fio"
42full_mask = f"^(?!.*(manual|-static-)).*({mask}).*$"
43mask_pattern = re.compile(full_mask, re.IGNORECASE)
44stack_mask = "api-[0-9]+-[a-z]+"
45stack_pattern = re.compile(stack_mask, re.IGNORECASE)
46
47
48def get_resource_value(resource_key, default):
49 try:
50 return cloud_config.config['custom_vars'][resource_key]
51 except KeyError:
52 return default
53
54
Ievgeniia Zadorozhnaa33b29a2023-08-29 18:19:47 +030055def items_to_object(items):
56 data = json.dumps(items)
57 return json.loads(data, object_hook=lambda d: SimpleNamespace(**d))
58
59
Dmitriy Kruglovb811e642022-10-06 12:24:33 +020060def _filter_test_resources(resources, attribute, pattern=mask_pattern):
61 filtered_resources = {}
62 for item in resources:
63 # If there is no attribute in object, use just empty string as value
64 value = getattr(item, attribute, '')
65 # If the attribute value is None, use empty string instead, to be
66 # able to run regex search
67 if value is None:
68 value = ''
69 found = pattern.match(value)
70 if found:
71 filtered_resources[item.id] = getattr(item, attribute)
72 return filtered_resources
73
74
75def _log_resources_count(count, resource, pattern=mask):
76 log.info(f"{count} {resource} containing '{pattern}' are found.")
77
78
79def _log_resource_delete(id_, name, type_):
80 log.info(f"... deleting {name} (id={id_}) {type_}")
81
82
Ievgeniia Zadorozhnaa33b29a2023-08-29 18:19:47 +030083def _get_volume_groups(all_tenants='true'):
84 ep = volume.get_endpoint()
85 uri = f"{ep}/groups/detail"
Ievgeniia Zadorozhnaab334132023-08-01 23:20:13 +030086 headers = {'X-Auth-Token': cloud.session.get_token(),
Ievgeniia Zadorozhnaa33b29a2023-08-29 18:19:47 +030087 'Accept': 'application/json',
88 'OpenStack-API-Version': f'volume {volume_api_version}'}
89 params = {'all_tenants': all_tenants}
90 response = cloud.session.request(url=uri, method='GET',
91 headers=headers, params=params).json()
92 for group in response['groups']:
93 yield group
94
95
96def _delete_volume_group(uuid, delete_volumes='false'):
97 ep = volume.get_endpoint()
98 uri = f"{ep}/groups/{uuid}/action"
99 headers = {'X-Auth-Token': cloud.session.get_token(),
100 'OpenStack-API-Version': f'volume {volume_api_version}',
101 'Content-Type': 'application/json',
102 'Accept': 'application/json'}
103 body = {"delete": {"delete-volumes": delete_volumes}}
104 cloud.session.request(
105 url=uri, method='POST', headers=headers, json=body)
106
107
108def _reset_volume_status(uuid, status='available', attach_status='detached',
109 migration_status='None'):
110 ep = volume.get_endpoint()
111 uri = f"{ep}/volumes/{uuid}/action"
112 headers = {'X-Auth-Token': cloud.session.get_token(),
113 'Content-Type': 'application/json',
114 'Accept': 'application/json'}
115 body = {"os-reset_status": {
116 "status": status, "attach_status": attach_status,
117 "migration_status": migration_status}}
118 cloud.session.request(
119 url=uri, method='POST', headers=headers, json=body)
Ievgeniia Zadorozhnaab334132023-08-01 23:20:13 +0300120
121
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200122def cleanup_users():
123 users = identity.users()
124 users_to_delete = _filter_test_resources(users, 'name')
125 _log_resources_count(len(users_to_delete), 'user(s)')
126 if args.dry_run:
127 return
128 for id_ in users_to_delete:
129 _log_resource_delete(id_, users_to_delete[id_], 'user')
130 identity.delete_user(id_)
131
132
133def cleanup_roles():
134 roles = identity.roles()
135 roles_to_delete = _filter_test_resources(roles, 'name')
136 _log_resources_count(len(roles_to_delete), 'role(s)')
137 if args.dry_run:
138 return
139 for id_ in roles_to_delete:
140 _log_resource_delete(id_, roles_to_delete[id_], 'role')
141 identity.delete_role(id_)
142
143
144def cleanup_projects():
145 projects = identity.projects()
146 projects_to_delete = _filter_test_resources(projects, 'name')
147 _log_resources_count(len(projects_to_delete), 'project(s)')
148 if args.dry_run:
149 return
150 for id_ in projects_to_delete:
151 _log_resource_delete(id_, projects_to_delete[id_], 'project')
152 identity.delete_project(id_)
153
154
155def cleanup_regions():
156 regions = identity.regions()
157 regions_to_delete = _filter_test_resources(regions, 'id')
158 _log_resources_count(len(regions_to_delete), 'region(s)')
159 if args.dry_run:
160 return
161 for id_ in regions_to_delete:
162 _log_resource_delete(id_, id_, 'region')
163 identity.delete_region(id_)
164
165
166def cleanup_services():
167 services = identity.services()
168 services_to_delete = _filter_test_resources(services, 'name')
169 _log_resources_count(len(services_to_delete), 'service(s)')
170 if args.dry_run:
171 return
172 for id_ in services_to_delete:
173 _log_resource_delete(id_, services_to_delete[id_], 'service')
174 identity.delete_service(id_)
175
176
177def cleanup_stacks(stacks_alt=False):
178 stacks = orchestration.stacks()
179 stacks_to_delete = _filter_test_resources(stacks, 'name')
180 _log_resources_count(len(stacks_to_delete), 'stack(s)')
181
182 # Use additional pattern for searching/deleting test Heat resources,
183 # if enabled
184 if stacks_alt:
185 stacks_alt_to_delete = _filter_test_resources(
186 stacks, 'name', stack_pattern)
187 _log_resources_count(len(stacks_alt_to_delete), 'stack(s)', stack_mask)
188 stacks_to_delete.update(stacks_alt_to_delete)
189
190 if args.dry_run:
191 return
192
193 for id_ in stacks_to_delete:
194 _log_resource_delete(id_, stacks_to_delete[id_], 'stack')
Ievgeniia Zadorozhna85020982023-08-03 22:04:12 +0300195 stack_obj = orchestration.get_stack(id_)
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200196 orchestration.delete_stack(id_)
Ievgeniia Zadorozhna85020982023-08-03 22:04:12 +0300197 orchestration.wait_for_delete(stack_obj)
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200198
199
200def cleanup_flavors():
201 flavors = compute.flavors()
202 flavors_to_delete = _filter_test_resources(flavors, 'name')
203 _log_resources_count(len(flavors_to_delete), 'flavor(s)')
204 if args.dry_run:
205 return
206 for id_ in flavors_to_delete:
207 _log_resource_delete(id_, flavors_to_delete[id_], 'flavor')
208 compute.delete_flavor(id_)
209
210
211def cleanup_images():
212 images = image.images()
213 images_to_delete = _filter_test_resources(images, 'name')
214 _log_resources_count(len(images_to_delete), 'image(s)')
215 if args.dry_run:
216 return
217 for id_ in images_to_delete:
218 _log_resource_delete(id_, images_to_delete[id_], 'image')
219 image.delete_image(id_)
220
221
222def cleanup_keypairs():
223 keypairs = compute.keypairs()
224 keypairs_to_delete = _filter_test_resources(keypairs, 'name')
225 _log_resources_count(len(keypairs_to_delete), 'keypair(s)')
226 if args.dry_run:
227 return
228 for id_ in keypairs_to_delete:
229 _log_resource_delete(id_, keypairs_to_delete[id_], 'keypair')
230 compute.delete_keypair(id_)
231
232
233def cleanup_servers():
234 servers = compute.servers(all_projects=True)
235 servers_to_delete = _filter_test_resources(servers, 'name')
236 _log_resources_count(len(servers_to_delete), 'server(s)')
237 if args.dry_run:
238 return
239 for id_ in servers_to_delete:
240 if args.servers_active:
241 log.info(
242 f"... resetting {servers_to_delete[id_]} (id={id_}) server "
243 "state to 'active'")
244 compute.reset_server_state(id_, 'active')
245 _log_resource_delete(id_, servers_to_delete[id_], 'server')
246 compute.delete_server(id_)
247 srv_obj = compute.get_server(id_)
248 compute.wait_for_delete(srv_obj)
249
250
251def cleanup_snapshots():
252 snapshots = volume.snapshots(all_projects=True)
253 snapshots_to_delete = _filter_test_resources(snapshots, 'name')
254 _log_resources_count(len(snapshots_to_delete), 'snapshot(s)')
255 if args.dry_run:
256 return
257 for id_ in snapshots_to_delete:
Ievgeniia Zadorozhna85020982023-08-03 22:04:12 +0300258 snapshot_obj = volume.get_snapshot(id_)
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200259 volume.reset_snapshot(id_, 'available')
260 _log_resource_delete(id_, snapshots_to_delete[id_], 'snapshot')
Ievgeniia Zadorozhna85020982023-08-03 22:04:12 +0300261 volume.delete_snapshot(id_, force=True)
262 volume.wait_for_delete(snapshot_obj)
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200263
264
265def cleanup_volumes():
266 volumes = volume.volumes(all_projects=True)
267 volumes_to_delete = _filter_test_resources(volumes, 'name')
268 _log_resources_count(len(volumes_to_delete), 'volume(s)')
269 if args.dry_run:
270 return
271 for id_ in volumes_to_delete:
Ievgeniia Zadorozhnaa33b29a2023-08-29 18:19:47 +0300272 _reset_volume_status(id_, 'available', 'detached', 'None')
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200273 _log_resource_delete(id_, volumes_to_delete[id_], 'volume')
274 volume.delete_volume(id_)
275 vol_obj = volume.get_volume(id_)
276 volume.wait_for_delete(vol_obj)
277
278
279def cleanup_volume_groups():
Ievgeniia Zadorozhnaa33b29a2023-08-29 18:19:47 +0300280 groups_in_response = _get_volume_groups()
281 groups = items_to_object([g for g in groups_in_response])
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200282 groups_to_delete = _filter_test_resources(groups, 'name')
283 _log_resources_count(len(groups_to_delete), 'volume group(s)')
284 if args.dry_run:
285 return
286 for id_ in groups_to_delete:
287 _log_resource_delete(id_, groups_to_delete[id_], 'volume group')
Ievgeniia Zadorozhnaa33b29a2023-08-29 18:19:47 +0300288 _delete_volume_group(id_)
289 time.sleep(10) # TODO(izadorozhna): need to add a proper waiter
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200290
291
Ievgeniia Zadorozhna85020982023-08-03 22:04:12 +0300292def cleanup_volume_backups():
293 backups = volume.backups(all_tenants=True)
294 backups_to_delete = _filter_test_resources(backups, 'name')
295 _log_resources_count(len(backups_to_delete), 'volume backup(s)')
296 if args.dry_run:
297 return
298 for id_ in backups_to_delete:
299 backup_obj = volume.get_backup(id_)
300 _log_resource_delete(id_, backups_to_delete[id_], 'volume backup')
301 volume.delete_backup(id_)
302 volume.wait_for_delete(backup_obj)
303
304
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200305def cleanup_volume_group_types():
306 group_types = volume.group_types()
307 group_types_to_delete = _filter_test_resources(group_types, 'name')
308 _log_resources_count(len(group_types_to_delete), 'volume group type(s)')
309 if args.dry_run:
310 return
311 for id_ in group_types_to_delete:
312 _log_resource_delete(
313 id_, group_types_to_delete[id_], 'volume group type')
314 volume.delete_group_type(id_)
315
316
317def cleanup_volume_types():
318 volume_types = volume.types()
319 volume_types_to_delete = _filter_test_resources(volume_types, 'name')
320 _log_resources_count(len(volume_types_to_delete), 'volume type(s)')
321 if args.dry_run:
322 return
323 for id_ in volume_types_to_delete:
324 _log_resource_delete(id_, volume_types_to_delete[id_], 'volume type')
325 volume.delete_type(id_)
326
327
328def cleanup_sec_groups():
329 sec_groups = network.security_groups()
330 sec_groups_to_delete = _filter_test_resources(sec_groups, 'name')
331 _log_resources_count(len(sec_groups_to_delete), 'security group(s)')
332 if args.dry_run:
333 return
334 for id_ in sec_groups_to_delete:
335 _log_resource_delete(id_, sec_groups_to_delete[id_], 'security group')
336 network.delete_security_group(id_)
337
338
339def cleanup_containers():
340 containers = object_store.containers()
341 containers_to_delete = _filter_test_resources(containers, 'name')
342 _log_resources_count(len(containers_to_delete), 'container(s)')
343 if args.dry_run:
344 return
345 for id_ in containers_to_delete:
346 _log_resource_delete(id_, containers_to_delete[id_], 'container')
347 object_store.delete_container(id_)
348
349
350def cleanup_routers():
351 routers = network.routers()
352 routers_to_delete = _filter_test_resources(routers, 'name')
353 _log_resources_count(len(routers_to_delete), 'router(s)')
354 if args.dry_run:
355 return
356 for id_ in routers_to_delete:
357 _log_resource_delete(id_, routers_to_delete[id_], 'router')
358
359 # Unset external gateway and remove ports from router
360 log.info("... ... removing external gateway from the router")
361 network.update_router(id_, external_gateway_info={})
362 ports = network.ports(device_id=id_)
363 for p in ports:
364 if p.device_owner != 'network:router_ha_interface':
365 log.info(f"... ... removing port {p.id} from the router")
366 network.remove_interface_from_router(id_, port_id=p.id)
367
368 network.delete_router(id_)
369
370
371def cleanup_networks():
372 nets = network.networks()
373 nets_to_delete = _filter_test_resources(nets, 'name')
374 _log_resources_count(len(nets_to_delete), 'network(s)')
375 if args.dry_run:
376 return
377 for id_ in nets_to_delete:
378 _log_resource_delete(id_, nets_to_delete[id_], 'network')
379
380 ports = network.ports(network_id=id_)
381 for p in ports:
382 log.info(
383 f"... ... removing port {p.id} from the network")
384 network.delete_port(p.id)
385 subnets = network.subnets(network_id=id_)
386 for s in subnets:
387 log.info(
388 f"... ... removing subnet {s.id} from the network")
389 network.delete_subnet(s.id)
390
391 network.delete_network(id_)
392
393
Ievgeniia Zadorozhnaab334132023-08-01 23:20:13 +0300394def cleanup_load_balancers():
395 lbs = load_balancer.load_balancers()
396 lbs_to_delete = _filter_test_resources(lbs, 'name')
397 _log_resources_count(len(lbs_to_delete), 'load_balancer(s)')
398 if args.dry_run:
399 return
400 for id_ in lbs_to_delete:
401 _log_resource_delete(id_, lbs_to_delete[id_], 'load_balancer')
402 try:
403 load_balancer.delete_load_balancer(id_, cascade=True)
404 except openstack.exceptions.ConflictException:
405 # force delete the LB in case it is in some PENDING_* state
Ievgeniia Zadorozhnaa33b29a2023-08-29 18:19:47 +0300406 log.info(f"... ... force deleting {id_} load balancer")
407 load_balancer.delete_load_balancer(id_, cascade=True, force=True)
Ievgeniia Zadorozhnaab334132023-08-01 23:20:13 +0300408 except Exception as e:
409 log.info(f"... ... could not delete {id_} load balancer: {e}")
410
411
Ievgeniia Zadorozhna9600b182023-08-04 17:56:24 +0300412def cleanup_floating_ips():
413 projects = identity.projects()
414 list_projects_to_delete = list(_filter_test_resources(projects, 'name'))
415 floating_ips = network.ips()
416 fips_to_delete = {}
417 for ip in floating_ips:
418 # filter only non-associated IPs, only inside target projects
419 if (ip.status == 'DOWN') and (ip.fixed_ip_address is None):
420 if ip.project_id in list_projects_to_delete:
421 fips_to_delete[ip.id] = ip.floating_ip_address
422 _log_resources_count(len(fips_to_delete), 'floating ip(s)')
423 if args.dry_run:
424 return
425 for id_ in fips_to_delete:
426 _log_resource_delete(id_, fips_to_delete[id_], 'floating ip')
427 network.delete_ip(id_)
428
429
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200430if __name__ == "__main__":
431 parser = argparse.ArgumentParser(
432 description='OpenStack test resources cleanup script')
433 parser.add_argument(
434 '-t', dest='dry_run', action='store_true',
435 help='Dry run mode, no cleanup is done')
436 parser.add_argument(
437 '-P', dest='projects', action='store_true',
438 help='Force cleanup of projects')
439 parser.add_argument(
440 '-S', dest='servers_active', action='store_true',
441 help='Set servers to ACTIVE before deletion (reqiured by bare metal)')
442 parser.add_argument(
443 '-f', dest='stacks_alt', action='store_true',
444 help='Use additional mask for stack cleanup')
445
446 args = parser.parse_args()
447
448 if args.dry_run:
449 log.info("Running in dry-run mode")
450 if args.servers_active:
451 log.info("Servers will be set to ACTIVE before cleanup")
452 if args.projects:
453 log.info("Project cleanup is enabled")
454 if args.stacks_alt:
455 log.info(
456 f"Stacks will be cleaned up using additional '{stack_mask}' mask")
457
458 cleanup_stacks(stacks_alt=args.stacks_alt)
Ievgeniia Zadorozhnad70f21c2023-08-09 20:24:38 +0300459 cleanup_load_balancers()
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200460 cleanup_servers()
461 cleanup_flavors()
Ievgeniia Zadorozhna66dbe122024-01-22 23:59:05 +0100462 try: # Skip if cinder-backup service is not enabled
463 cleanup_volume_backups()
464 except openstack.exceptions.ResourceNotFound:
465 pass
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200466 cleanup_snapshots()
467 cleanup_volumes()
468 cleanup_volume_groups()
469 cleanup_volume_group_types()
470 cleanup_volume_types()
471 cleanup_images()
472 cleanup_sec_groups()
473 cleanup_keypairs()
474 cleanup_users()
475 cleanup_roles()
476 cleanup_services()
477 cleanup_regions()
478 cleanup_routers()
479 cleanup_networks()
Ievgeniia Zadorozhna66dbe122024-01-22 23:59:05 +0100480 if object_store_present:
481 cleanup_containers()
Ievgeniia Zadorozhna9600b182023-08-04 17:56:24 +0300482 cleanup_floating_ips()
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200483
484 if args.projects:
485 cleanup_projects()
486
487 msg = "Cleanup is FINISHED"
488 log.info(f"\n{'=' * len(msg)}\n{msg}")