blob: b69cd624b10e2ee14e673c2d0ddd6163df1aa9be [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
32object_store = cloud.object_store
33volume = cloud.volume
Ievgeniia Zadorozhnaab334132023-08-01 23:20:13 +030034load_balancer = cloud.load_balancer
Dmitriy Kruglovb811e642022-10-06 12:24:33 +020035
36mask = "cvp|s_rally|rally_|tempest-|tempest_|spt|fio"
37full_mask = f"^(?!.*(manual|-static-)).*({mask}).*$"
38mask_pattern = re.compile(full_mask, re.IGNORECASE)
39stack_mask = "api-[0-9]+-[a-z]+"
40stack_pattern = re.compile(stack_mask, re.IGNORECASE)
41
42
43def get_resource_value(resource_key, default):
44 try:
45 return cloud_config.config['custom_vars'][resource_key]
46 except KeyError:
47 return default
48
49
Ievgeniia Zadorozhnaa33b29a2023-08-29 18:19:47 +030050def items_to_object(items):
51 data = json.dumps(items)
52 return json.loads(data, object_hook=lambda d: SimpleNamespace(**d))
53
54
Dmitriy Kruglovb811e642022-10-06 12:24:33 +020055def _filter_test_resources(resources, attribute, pattern=mask_pattern):
56 filtered_resources = {}
57 for item in resources:
58 # If there is no attribute in object, use just empty string as value
59 value = getattr(item, attribute, '')
60 # If the attribute value is None, use empty string instead, to be
61 # able to run regex search
62 if value is None:
63 value = ''
64 found = pattern.match(value)
65 if found:
66 filtered_resources[item.id] = getattr(item, attribute)
67 return filtered_resources
68
69
70def _log_resources_count(count, resource, pattern=mask):
71 log.info(f"{count} {resource} containing '{pattern}' are found.")
72
73
74def _log_resource_delete(id_, name, type_):
75 log.info(f"... deleting {name} (id={id_}) {type_}")
76
77
Ievgeniia Zadorozhnaa33b29a2023-08-29 18:19:47 +030078def _get_volume_groups(all_tenants='true'):
79 ep = volume.get_endpoint()
80 uri = f"{ep}/groups/detail"
Ievgeniia Zadorozhnaab334132023-08-01 23:20:13 +030081 headers = {'X-Auth-Token': cloud.session.get_token(),
Ievgeniia Zadorozhnaa33b29a2023-08-29 18:19:47 +030082 'Accept': 'application/json',
83 'OpenStack-API-Version': f'volume {volume_api_version}'}
84 params = {'all_tenants': all_tenants}
85 response = cloud.session.request(url=uri, method='GET',
86 headers=headers, params=params).json()
87 for group in response['groups']:
88 yield group
89
90
91def _delete_volume_group(uuid, delete_volumes='false'):
92 ep = volume.get_endpoint()
93 uri = f"{ep}/groups/{uuid}/action"
94 headers = {'X-Auth-Token': cloud.session.get_token(),
95 'OpenStack-API-Version': f'volume {volume_api_version}',
96 'Content-Type': 'application/json',
97 'Accept': 'application/json'}
98 body = {"delete": {"delete-volumes": delete_volumes}}
99 cloud.session.request(
100 url=uri, method='POST', headers=headers, json=body)
101
102
103def _reset_volume_status(uuid, status='available', attach_status='detached',
104 migration_status='None'):
105 ep = volume.get_endpoint()
106 uri = f"{ep}/volumes/{uuid}/action"
107 headers = {'X-Auth-Token': cloud.session.get_token(),
108 'Content-Type': 'application/json',
109 'Accept': 'application/json'}
110 body = {"os-reset_status": {
111 "status": status, "attach_status": attach_status,
112 "migration_status": migration_status}}
113 cloud.session.request(
114 url=uri, method='POST', headers=headers, json=body)
Ievgeniia Zadorozhnaab334132023-08-01 23:20:13 +0300115
116
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200117def cleanup_users():
118 users = identity.users()
119 users_to_delete = _filter_test_resources(users, 'name')
120 _log_resources_count(len(users_to_delete), 'user(s)')
121 if args.dry_run:
122 return
123 for id_ in users_to_delete:
124 _log_resource_delete(id_, users_to_delete[id_], 'user')
125 identity.delete_user(id_)
126
127
128def cleanup_roles():
129 roles = identity.roles()
130 roles_to_delete = _filter_test_resources(roles, 'name')
131 _log_resources_count(len(roles_to_delete), 'role(s)')
132 if args.dry_run:
133 return
134 for id_ in roles_to_delete:
135 _log_resource_delete(id_, roles_to_delete[id_], 'role')
136 identity.delete_role(id_)
137
138
139def cleanup_projects():
140 projects = identity.projects()
141 projects_to_delete = _filter_test_resources(projects, 'name')
142 _log_resources_count(len(projects_to_delete), 'project(s)')
143 if args.dry_run:
144 return
145 for id_ in projects_to_delete:
146 _log_resource_delete(id_, projects_to_delete[id_], 'project')
147 identity.delete_project(id_)
148
149
150def cleanup_regions():
151 regions = identity.regions()
152 regions_to_delete = _filter_test_resources(regions, 'id')
153 _log_resources_count(len(regions_to_delete), 'region(s)')
154 if args.dry_run:
155 return
156 for id_ in regions_to_delete:
157 _log_resource_delete(id_, id_, 'region')
158 identity.delete_region(id_)
159
160
161def cleanup_services():
162 services = identity.services()
163 services_to_delete = _filter_test_resources(services, 'name')
164 _log_resources_count(len(services_to_delete), 'service(s)')
165 if args.dry_run:
166 return
167 for id_ in services_to_delete:
168 _log_resource_delete(id_, services_to_delete[id_], 'service')
169 identity.delete_service(id_)
170
171
172def cleanup_stacks(stacks_alt=False):
173 stacks = orchestration.stacks()
174 stacks_to_delete = _filter_test_resources(stacks, 'name')
175 _log_resources_count(len(stacks_to_delete), 'stack(s)')
176
177 # Use additional pattern for searching/deleting test Heat resources,
178 # if enabled
179 if stacks_alt:
180 stacks_alt_to_delete = _filter_test_resources(
181 stacks, 'name', stack_pattern)
182 _log_resources_count(len(stacks_alt_to_delete), 'stack(s)', stack_mask)
183 stacks_to_delete.update(stacks_alt_to_delete)
184
185 if args.dry_run:
186 return
187
188 for id_ in stacks_to_delete:
189 _log_resource_delete(id_, stacks_to_delete[id_], 'stack')
Ievgeniia Zadorozhna85020982023-08-03 22:04:12 +0300190 stack_obj = orchestration.get_stack(id_)
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200191 orchestration.delete_stack(id_)
Ievgeniia Zadorozhna85020982023-08-03 22:04:12 +0300192 orchestration.wait_for_delete(stack_obj)
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200193
194
195def cleanup_flavors():
196 flavors = compute.flavors()
197 flavors_to_delete = _filter_test_resources(flavors, 'name')
198 _log_resources_count(len(flavors_to_delete), 'flavor(s)')
199 if args.dry_run:
200 return
201 for id_ in flavors_to_delete:
202 _log_resource_delete(id_, flavors_to_delete[id_], 'flavor')
203 compute.delete_flavor(id_)
204
205
206def cleanup_images():
207 images = image.images()
208 images_to_delete = _filter_test_resources(images, 'name')
209 _log_resources_count(len(images_to_delete), 'image(s)')
210 if args.dry_run:
211 return
212 for id_ in images_to_delete:
213 _log_resource_delete(id_, images_to_delete[id_], 'image')
214 image.delete_image(id_)
215
216
217def cleanup_keypairs():
218 keypairs = compute.keypairs()
219 keypairs_to_delete = _filter_test_resources(keypairs, 'name')
220 _log_resources_count(len(keypairs_to_delete), 'keypair(s)')
221 if args.dry_run:
222 return
223 for id_ in keypairs_to_delete:
224 _log_resource_delete(id_, keypairs_to_delete[id_], 'keypair')
225 compute.delete_keypair(id_)
226
227
228def cleanup_servers():
229 servers = compute.servers(all_projects=True)
230 servers_to_delete = _filter_test_resources(servers, 'name')
231 _log_resources_count(len(servers_to_delete), 'server(s)')
232 if args.dry_run:
233 return
234 for id_ in servers_to_delete:
235 if args.servers_active:
236 log.info(
237 f"... resetting {servers_to_delete[id_]} (id={id_}) server "
238 "state to 'active'")
239 compute.reset_server_state(id_, 'active')
240 _log_resource_delete(id_, servers_to_delete[id_], 'server')
241 compute.delete_server(id_)
242 srv_obj = compute.get_server(id_)
243 compute.wait_for_delete(srv_obj)
244
245
246def cleanup_snapshots():
247 snapshots = volume.snapshots(all_projects=True)
248 snapshots_to_delete = _filter_test_resources(snapshots, 'name')
249 _log_resources_count(len(snapshots_to_delete), 'snapshot(s)')
250 if args.dry_run:
251 return
252 for id_ in snapshots_to_delete:
Ievgeniia Zadorozhna85020982023-08-03 22:04:12 +0300253 snapshot_obj = volume.get_snapshot(id_)
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200254 volume.reset_snapshot(id_, 'available')
255 _log_resource_delete(id_, snapshots_to_delete[id_], 'snapshot')
Ievgeniia Zadorozhna85020982023-08-03 22:04:12 +0300256 volume.delete_snapshot(id_, force=True)
257 volume.wait_for_delete(snapshot_obj)
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200258
259
260def cleanup_volumes():
261 volumes = volume.volumes(all_projects=True)
262 volumes_to_delete = _filter_test_resources(volumes, 'name')
263 _log_resources_count(len(volumes_to_delete), 'volume(s)')
264 if args.dry_run:
265 return
266 for id_ in volumes_to_delete:
Ievgeniia Zadorozhnaa33b29a2023-08-29 18:19:47 +0300267 _reset_volume_status(id_, 'available', 'detached', 'None')
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200268 _log_resource_delete(id_, volumes_to_delete[id_], 'volume')
269 volume.delete_volume(id_)
270 vol_obj = volume.get_volume(id_)
271 volume.wait_for_delete(vol_obj)
272
273
274def cleanup_volume_groups():
Ievgeniia Zadorozhnaa33b29a2023-08-29 18:19:47 +0300275 groups_in_response = _get_volume_groups()
276 groups = items_to_object([g for g in groups_in_response])
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200277 groups_to_delete = _filter_test_resources(groups, 'name')
278 _log_resources_count(len(groups_to_delete), 'volume group(s)')
279 if args.dry_run:
280 return
281 for id_ in groups_to_delete:
282 _log_resource_delete(id_, groups_to_delete[id_], 'volume group')
Ievgeniia Zadorozhnaa33b29a2023-08-29 18:19:47 +0300283 _delete_volume_group(id_)
284 time.sleep(10) # TODO(izadorozhna): need to add a proper waiter
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200285
286
Ievgeniia Zadorozhna85020982023-08-03 22:04:12 +0300287def cleanup_volume_backups():
288 backups = volume.backups(all_tenants=True)
289 backups_to_delete = _filter_test_resources(backups, 'name')
290 _log_resources_count(len(backups_to_delete), 'volume backup(s)')
291 if args.dry_run:
292 return
293 for id_ in backups_to_delete:
294 backup_obj = volume.get_backup(id_)
295 _log_resource_delete(id_, backups_to_delete[id_], 'volume backup')
296 volume.delete_backup(id_)
297 volume.wait_for_delete(backup_obj)
298
299
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200300def cleanup_volume_group_types():
301 group_types = volume.group_types()
302 group_types_to_delete = _filter_test_resources(group_types, 'name')
303 _log_resources_count(len(group_types_to_delete), 'volume group type(s)')
304 if args.dry_run:
305 return
306 for id_ in group_types_to_delete:
307 _log_resource_delete(
308 id_, group_types_to_delete[id_], 'volume group type')
309 volume.delete_group_type(id_)
310
311
312def cleanup_volume_types():
313 volume_types = volume.types()
314 volume_types_to_delete = _filter_test_resources(volume_types, 'name')
315 _log_resources_count(len(volume_types_to_delete), 'volume type(s)')
316 if args.dry_run:
317 return
318 for id_ in volume_types_to_delete:
319 _log_resource_delete(id_, volume_types_to_delete[id_], 'volume type')
320 volume.delete_type(id_)
321
322
323def cleanup_sec_groups():
324 sec_groups = network.security_groups()
325 sec_groups_to_delete = _filter_test_resources(sec_groups, 'name')
326 _log_resources_count(len(sec_groups_to_delete), 'security group(s)')
327 if args.dry_run:
328 return
329 for id_ in sec_groups_to_delete:
330 _log_resource_delete(id_, sec_groups_to_delete[id_], 'security group')
331 network.delete_security_group(id_)
332
333
334def cleanup_containers():
335 containers = object_store.containers()
336 containers_to_delete = _filter_test_resources(containers, 'name')
337 _log_resources_count(len(containers_to_delete), 'container(s)')
338 if args.dry_run:
339 return
340 for id_ in containers_to_delete:
341 _log_resource_delete(id_, containers_to_delete[id_], 'container')
342 object_store.delete_container(id_)
343
344
345def cleanup_routers():
346 routers = network.routers()
347 routers_to_delete = _filter_test_resources(routers, 'name')
348 _log_resources_count(len(routers_to_delete), 'router(s)')
349 if args.dry_run:
350 return
351 for id_ in routers_to_delete:
352 _log_resource_delete(id_, routers_to_delete[id_], 'router')
353
354 # Unset external gateway and remove ports from router
355 log.info("... ... removing external gateway from the router")
356 network.update_router(id_, external_gateway_info={})
357 ports = network.ports(device_id=id_)
358 for p in ports:
359 if p.device_owner != 'network:router_ha_interface':
360 log.info(f"... ... removing port {p.id} from the router")
361 network.remove_interface_from_router(id_, port_id=p.id)
362
363 network.delete_router(id_)
364
365
366def cleanup_networks():
367 nets = network.networks()
368 nets_to_delete = _filter_test_resources(nets, 'name')
369 _log_resources_count(len(nets_to_delete), 'network(s)')
370 if args.dry_run:
371 return
372 for id_ in nets_to_delete:
373 _log_resource_delete(id_, nets_to_delete[id_], 'network')
374
375 ports = network.ports(network_id=id_)
376 for p in ports:
377 log.info(
378 f"... ... removing port {p.id} from the network")
379 network.delete_port(p.id)
380 subnets = network.subnets(network_id=id_)
381 for s in subnets:
382 log.info(
383 f"... ... removing subnet {s.id} from the network")
384 network.delete_subnet(s.id)
385
386 network.delete_network(id_)
387
388
Ievgeniia Zadorozhnaab334132023-08-01 23:20:13 +0300389def cleanup_load_balancers():
390 lbs = load_balancer.load_balancers()
391 lbs_to_delete = _filter_test_resources(lbs, 'name')
392 _log_resources_count(len(lbs_to_delete), 'load_balancer(s)')
393 if args.dry_run:
394 return
395 for id_ in lbs_to_delete:
396 _log_resource_delete(id_, lbs_to_delete[id_], 'load_balancer')
397 try:
398 load_balancer.delete_load_balancer(id_, cascade=True)
399 except openstack.exceptions.ConflictException:
400 # force delete the LB in case it is in some PENDING_* state
Ievgeniia Zadorozhnaa33b29a2023-08-29 18:19:47 +0300401 log.info(f"... ... force deleting {id_} load balancer")
402 load_balancer.delete_load_balancer(id_, cascade=True, force=True)
Ievgeniia Zadorozhnaab334132023-08-01 23:20:13 +0300403 except Exception as e:
404 log.info(f"... ... could not delete {id_} load balancer: {e}")
405
406
Ievgeniia Zadorozhna9600b182023-08-04 17:56:24 +0300407def cleanup_floating_ips():
408 projects = identity.projects()
409 list_projects_to_delete = list(_filter_test_resources(projects, 'name'))
410 floating_ips = network.ips()
411 fips_to_delete = {}
412 for ip in floating_ips:
413 # filter only non-associated IPs, only inside target projects
414 if (ip.status == 'DOWN') and (ip.fixed_ip_address is None):
415 if ip.project_id in list_projects_to_delete:
416 fips_to_delete[ip.id] = ip.floating_ip_address
417 _log_resources_count(len(fips_to_delete), 'floating ip(s)')
418 if args.dry_run:
419 return
420 for id_ in fips_to_delete:
421 _log_resource_delete(id_, fips_to_delete[id_], 'floating ip')
422 network.delete_ip(id_)
423
424
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200425if __name__ == "__main__":
426 parser = argparse.ArgumentParser(
427 description='OpenStack test resources cleanup script')
428 parser.add_argument(
429 '-t', dest='dry_run', action='store_true',
430 help='Dry run mode, no cleanup is done')
431 parser.add_argument(
432 '-P', dest='projects', action='store_true',
433 help='Force cleanup of projects')
434 parser.add_argument(
435 '-S', dest='servers_active', action='store_true',
436 help='Set servers to ACTIVE before deletion (reqiured by bare metal)')
437 parser.add_argument(
438 '-f', dest='stacks_alt', action='store_true',
439 help='Use additional mask for stack cleanup')
440
441 args = parser.parse_args()
442
443 if args.dry_run:
444 log.info("Running in dry-run mode")
445 if args.servers_active:
446 log.info("Servers will be set to ACTIVE before cleanup")
447 if args.projects:
448 log.info("Project cleanup is enabled")
449 if args.stacks_alt:
450 log.info(
451 f"Stacks will be cleaned up using additional '{stack_mask}' mask")
452
453 cleanup_stacks(stacks_alt=args.stacks_alt)
Ievgeniia Zadorozhnad70f21c2023-08-09 20:24:38 +0300454 cleanup_load_balancers()
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200455 cleanup_servers()
456 cleanup_flavors()
Ievgeniia Zadorozhna85020982023-08-03 22:04:12 +0300457 cleanup_volume_backups()
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200458 cleanup_snapshots()
459 cleanup_volumes()
460 cleanup_volume_groups()
461 cleanup_volume_group_types()
462 cleanup_volume_types()
463 cleanup_images()
464 cleanup_sec_groups()
465 cleanup_keypairs()
466 cleanup_users()
467 cleanup_roles()
468 cleanup_services()
469 cleanup_regions()
470 cleanup_routers()
471 cleanup_networks()
472 cleanup_containers()
Ievgeniia Zadorozhna9600b182023-08-04 17:56:24 +0300473 cleanup_floating_ips()
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200474
475 if args.projects:
476 cleanup_projects()
477
478 msg = "Cleanup is FINISHED"
479 log.info(f"\n{'=' * len(msg)}\n{msg}")