blob: 6f8282313fc0e9a38b771325349a10016ddc81f3 [file] [log] [blame]
Dmitriy Kruglovb811e642022-10-06 12:24:33 +02001import argparse
Ievgeniia Zadorozhnac230e2e2023-09-18 15:18:19 +03002import json
Dmitriy Kruglovb811e642022-10-06 12:24:33 +02003import os
4import re
5import sys
Ievgeniia Zadorozhnac230e2e2023-09-18 15:18:19 +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 Zadorozhnac230e2e2023-09-18 15:18:19 +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 Zadorozhnac230e2e2023-09-18 15:18:19 +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 Zadorozhnac230e2e2023-09-18 15:18:19 +030078def _generate_volume_headers():
Ievgeniia Zadorozhnaab334132023-08-01 23:20:13 +030079 headers = {'X-Auth-Token': cloud.session.get_token(),
Ievgeniia Zadorozhnac230e2e2023-09-18 15:18:19 +030080 'Accept': 'application/json',
81 'Content-Type': 'application/json',
82 'OpenStack-API-Version': f'volume {volume_api_version}'}
83 return headers
84
85
86def _get_volume_group_types(all_tenants='true'):
87 # this method is not implemented in Victoria yet in openstacksdk
88 ep = volume.get_endpoint()
89 uri = f"{ep}/group_types"
90 headers = _generate_volume_headers()
91 params = {'all_tenants': all_tenants}
92 response = cloud.session.request(url=uri, method='GET',
93 headers=headers, params=params).json()
94 for g_type in response['group_types']:
95 yield g_type
96
97
98def _delete_volume_group_type(uuid):
99 # this method is not implemented in Victoria yet in openstacksdk
100 ep = volume.get_endpoint()
101 uri = f"{ep}/group_types/{uuid}"
102 headers = _generate_volume_headers()
103 cloud.session.request(url=uri, method='DELETE', headers=headers)
104
105
106def _get_volume_groups(all_tenants='true'):
107 # this method is not implemented in Victoria yet in openstacksdk
108 ep = volume.get_endpoint()
109 uri = f"{ep}/groups/detail"
110 headers = _generate_volume_headers()
111 params = {'all_tenants': all_tenants}
112 response = cloud.session.request(url=uri, method='GET',
113 headers=headers, params=params).json()
114 for group in response['groups']:
115 yield group
116
117
118def _delete_volume_group(uuid, delete_volumes='false'):
119 # this method is not implemented in Victoria yet in openstacksdk
120 ep = volume.get_endpoint()
121 uri = f"{ep}/groups/{uuid}/action"
122 headers = _generate_volume_headers()
123 body = {"delete": {"delete-volumes": delete_volumes}}
124 cloud.session.request(
125 url=uri, method='POST', headers=headers, json=body)
126
127
128def _reset_volume_status(uuid, status='available', attach_status='detached',
129 migration_status='None'):
130 # this method is not implemented in Victoria yet in openstacksdk
131 ep = volume.get_endpoint()
132 uri = f"{ep}/volumes/{uuid}/action"
133 headers = _generate_volume_headers()
134 body = {"os-reset_status": {
135 "status": status, "attach_status": attach_status,
136 "migration_status": migration_status}}
137 cloud.session.request(
138 url=uri, method='POST', headers=headers, json=body)
Ievgeniia Zadorozhnaab334132023-08-01 23:20:13 +0300139
140
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200141def cleanup_users():
142 users = identity.users()
143 users_to_delete = _filter_test_resources(users, 'name')
144 _log_resources_count(len(users_to_delete), 'user(s)')
145 if args.dry_run:
146 return
147 for id_ in users_to_delete:
148 _log_resource_delete(id_, users_to_delete[id_], 'user')
149 identity.delete_user(id_)
150
151
152def cleanup_roles():
153 roles = identity.roles()
154 roles_to_delete = _filter_test_resources(roles, 'name')
155 _log_resources_count(len(roles_to_delete), 'role(s)')
156 if args.dry_run:
157 return
158 for id_ in roles_to_delete:
159 _log_resource_delete(id_, roles_to_delete[id_], 'role')
160 identity.delete_role(id_)
161
162
163def cleanup_projects():
164 projects = identity.projects()
165 projects_to_delete = _filter_test_resources(projects, 'name')
166 _log_resources_count(len(projects_to_delete), 'project(s)')
167 if args.dry_run:
168 return
169 for id_ in projects_to_delete:
170 _log_resource_delete(id_, projects_to_delete[id_], 'project')
171 identity.delete_project(id_)
172
173
174def cleanup_regions():
175 regions = identity.regions()
176 regions_to_delete = _filter_test_resources(regions, 'id')
177 _log_resources_count(len(regions_to_delete), 'region(s)')
178 if args.dry_run:
179 return
180 for id_ in regions_to_delete:
181 _log_resource_delete(id_, id_, 'region')
182 identity.delete_region(id_)
183
184
185def cleanup_services():
186 services = identity.services()
187 services_to_delete = _filter_test_resources(services, 'name')
188 _log_resources_count(len(services_to_delete), 'service(s)')
189 if args.dry_run:
190 return
191 for id_ in services_to_delete:
192 _log_resource_delete(id_, services_to_delete[id_], 'service')
193 identity.delete_service(id_)
194
195
196def cleanup_stacks(stacks_alt=False):
197 stacks = orchestration.stacks()
198 stacks_to_delete = _filter_test_resources(stacks, 'name')
199 _log_resources_count(len(stacks_to_delete), 'stack(s)')
200
201 # Use additional pattern for searching/deleting test Heat resources,
202 # if enabled
203 if stacks_alt:
204 stacks_alt_to_delete = _filter_test_resources(
205 stacks, 'name', stack_pattern)
206 _log_resources_count(len(stacks_alt_to_delete), 'stack(s)', stack_mask)
207 stacks_to_delete.update(stacks_alt_to_delete)
208
209 if args.dry_run:
210 return
211
212 for id_ in stacks_to_delete:
213 _log_resource_delete(id_, stacks_to_delete[id_], 'stack')
Ievgeniia Zadorozhna85020982023-08-03 22:04:12 +0300214 stack_obj = orchestration.get_stack(id_)
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200215 orchestration.delete_stack(id_)
Ievgeniia Zadorozhna85020982023-08-03 22:04:12 +0300216 orchestration.wait_for_delete(stack_obj)
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200217
218
219def cleanup_flavors():
220 flavors = compute.flavors()
221 flavors_to_delete = _filter_test_resources(flavors, 'name')
222 _log_resources_count(len(flavors_to_delete), 'flavor(s)')
223 if args.dry_run:
224 return
225 for id_ in flavors_to_delete:
226 _log_resource_delete(id_, flavors_to_delete[id_], 'flavor')
227 compute.delete_flavor(id_)
228
229
230def cleanup_images():
231 images = image.images()
232 images_to_delete = _filter_test_resources(images, 'name')
233 _log_resources_count(len(images_to_delete), 'image(s)')
234 if args.dry_run:
235 return
236 for id_ in images_to_delete:
237 _log_resource_delete(id_, images_to_delete[id_], 'image')
238 image.delete_image(id_)
239
240
241def cleanup_keypairs():
242 keypairs = compute.keypairs()
243 keypairs_to_delete = _filter_test_resources(keypairs, 'name')
244 _log_resources_count(len(keypairs_to_delete), 'keypair(s)')
245 if args.dry_run:
246 return
247 for id_ in keypairs_to_delete:
248 _log_resource_delete(id_, keypairs_to_delete[id_], 'keypair')
249 compute.delete_keypair(id_)
250
251
252def cleanup_servers():
253 servers = compute.servers(all_projects=True)
254 servers_to_delete = _filter_test_resources(servers, 'name')
255 _log_resources_count(len(servers_to_delete), 'server(s)')
256 if args.dry_run:
257 return
258 for id_ in servers_to_delete:
259 if args.servers_active:
260 log.info(
261 f"... resetting {servers_to_delete[id_]} (id={id_}) server "
262 "state to 'active'")
263 compute.reset_server_state(id_, 'active')
264 _log_resource_delete(id_, servers_to_delete[id_], 'server')
265 compute.delete_server(id_)
266 srv_obj = compute.get_server(id_)
267 compute.wait_for_delete(srv_obj)
268
269
270def cleanup_snapshots():
271 snapshots = volume.snapshots(all_projects=True)
272 snapshots_to_delete = _filter_test_resources(snapshots, 'name')
273 _log_resources_count(len(snapshots_to_delete), 'snapshot(s)')
274 if args.dry_run:
275 return
276 for id_ in snapshots_to_delete:
Ievgeniia Zadorozhna85020982023-08-03 22:04:12 +0300277 snapshot_obj = volume.get_snapshot(id_)
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200278 volume.reset_snapshot(id_, 'available')
279 _log_resource_delete(id_, snapshots_to_delete[id_], 'snapshot')
Ievgeniia Zadorozhna85020982023-08-03 22:04:12 +0300280 volume.delete_snapshot(id_, force=True)
281 volume.wait_for_delete(snapshot_obj)
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200282
283
284def cleanup_volumes():
285 volumes = volume.volumes(all_projects=True)
286 volumes_to_delete = _filter_test_resources(volumes, 'name')
287 _log_resources_count(len(volumes_to_delete), 'volume(s)')
288 if args.dry_run:
289 return
290 for id_ in volumes_to_delete:
Ievgeniia Zadorozhnac230e2e2023-09-18 15:18:19 +0300291 _reset_volume_status(id_, 'available', 'detached', 'None')
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200292 _log_resource_delete(id_, volumes_to_delete[id_], 'volume')
293 volume.delete_volume(id_)
294 vol_obj = volume.get_volume(id_)
295 volume.wait_for_delete(vol_obj)
296
297
298def cleanup_volume_groups():
Ievgeniia Zadorozhnac230e2e2023-09-18 15:18:19 +0300299 groups_in_response = _get_volume_groups()
300 groups = items_to_object([g for g in groups_in_response])
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200301 groups_to_delete = _filter_test_resources(groups, 'name')
302 _log_resources_count(len(groups_to_delete), 'volume group(s)')
303 if args.dry_run:
304 return
305 for id_ in groups_to_delete:
306 _log_resource_delete(id_, groups_to_delete[id_], 'volume group')
Ievgeniia Zadorozhnac230e2e2023-09-18 15:18:19 +0300307 _delete_volume_group(id_)
308 time.sleep(10) # TODO : need to add a proper waiter
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200309
310
Ievgeniia Zadorozhna85020982023-08-03 22:04:12 +0300311def cleanup_volume_backups():
312 backups = volume.backups(all_tenants=True)
313 backups_to_delete = _filter_test_resources(backups, 'name')
314 _log_resources_count(len(backups_to_delete), 'volume backup(s)')
315 if args.dry_run:
316 return
317 for id_ in backups_to_delete:
318 backup_obj = volume.get_backup(id_)
319 _log_resource_delete(id_, backups_to_delete[id_], 'volume backup')
320 volume.delete_backup(id_)
321 volume.wait_for_delete(backup_obj)
322
323
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200324def cleanup_volume_group_types():
Ievgeniia Zadorozhnac230e2e2023-09-18 15:18:19 +0300325 types_in_response = _get_volume_group_types()
326 group_types = items_to_object([g for g in types_in_response])
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200327 group_types_to_delete = _filter_test_resources(group_types, 'name')
328 _log_resources_count(len(group_types_to_delete), 'volume group type(s)')
329 if args.dry_run:
330 return
331 for id_ in group_types_to_delete:
332 _log_resource_delete(
333 id_, group_types_to_delete[id_], 'volume group type')
Ievgeniia Zadorozhnac230e2e2023-09-18 15:18:19 +0300334 _delete_volume_group_type(id_)
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200335
336
337def cleanup_volume_types():
338 volume_types = volume.types()
339 volume_types_to_delete = _filter_test_resources(volume_types, 'name')
340 _log_resources_count(len(volume_types_to_delete), 'volume type(s)')
341 if args.dry_run:
342 return
343 for id_ in volume_types_to_delete:
344 _log_resource_delete(id_, volume_types_to_delete[id_], 'volume type')
345 volume.delete_type(id_)
346
347
348def cleanup_sec_groups():
349 sec_groups = network.security_groups()
350 sec_groups_to_delete = _filter_test_resources(sec_groups, 'name')
351 _log_resources_count(len(sec_groups_to_delete), 'security group(s)')
352 if args.dry_run:
353 return
354 for id_ in sec_groups_to_delete:
355 _log_resource_delete(id_, sec_groups_to_delete[id_], 'security group')
356 network.delete_security_group(id_)
357
358
359def cleanup_containers():
360 containers = object_store.containers()
361 containers_to_delete = _filter_test_resources(containers, 'name')
362 _log_resources_count(len(containers_to_delete), 'container(s)')
363 if args.dry_run:
364 return
365 for id_ in containers_to_delete:
366 _log_resource_delete(id_, containers_to_delete[id_], 'container')
367 object_store.delete_container(id_)
368
369
370def cleanup_routers():
371 routers = network.routers()
372 routers_to_delete = _filter_test_resources(routers, 'name')
373 _log_resources_count(len(routers_to_delete), 'router(s)')
374 if args.dry_run:
375 return
376 for id_ in routers_to_delete:
377 _log_resource_delete(id_, routers_to_delete[id_], 'router')
378
379 # Unset external gateway and remove ports from router
380 log.info("... ... removing external gateway from the router")
381 network.update_router(id_, external_gateway_info={})
382 ports = network.ports(device_id=id_)
383 for p in ports:
384 if p.device_owner != 'network:router_ha_interface':
385 log.info(f"... ... removing port {p.id} from the router")
386 network.remove_interface_from_router(id_, port_id=p.id)
387
388 network.delete_router(id_)
389
390
391def cleanup_networks():
392 nets = network.networks()
393 nets_to_delete = _filter_test_resources(nets, 'name')
394 _log_resources_count(len(nets_to_delete), 'network(s)')
395 if args.dry_run:
396 return
397 for id_ in nets_to_delete:
398 _log_resource_delete(id_, nets_to_delete[id_], 'network')
399
400 ports = network.ports(network_id=id_)
401 for p in ports:
402 log.info(
403 f"... ... removing port {p.id} from the network")
404 network.delete_port(p.id)
405 subnets = network.subnets(network_id=id_)
406 for s in subnets:
407 log.info(
408 f"... ... removing subnet {s.id} from the network")
409 network.delete_subnet(s.id)
410
411 network.delete_network(id_)
412
413
Ievgeniia Zadorozhnaab334132023-08-01 23:20:13 +0300414def cleanup_load_balancers():
415 lbs = load_balancer.load_balancers()
416 lbs_to_delete = _filter_test_resources(lbs, 'name')
417 _log_resources_count(len(lbs_to_delete), 'load_balancer(s)')
418 if args.dry_run:
419 return
420 for id_ in lbs_to_delete:
421 _log_resource_delete(id_, lbs_to_delete[id_], 'load_balancer')
422 try:
423 load_balancer.delete_load_balancer(id_, cascade=True)
424 except openstack.exceptions.ConflictException:
425 # force delete the LB in case it is in some PENDING_* state
Ievgeniia Zadorozhnac230e2e2023-09-18 15:18:19 +0300426 log.info(f"... ... force deleting {id_} load balancer")
427 load_balancer.delete_load_balancer(id_, cascade=True, force=True)
Ievgeniia Zadorozhnaab334132023-08-01 23:20:13 +0300428 except Exception as e:
429 log.info(f"... ... could not delete {id_} load balancer: {e}")
430
431
Ievgeniia Zadorozhna9600b182023-08-04 17:56:24 +0300432def cleanup_floating_ips():
433 projects = identity.projects()
434 list_projects_to_delete = list(_filter_test_resources(projects, 'name'))
435 floating_ips = network.ips()
436 fips_to_delete = {}
437 for ip in floating_ips:
438 # filter only non-associated IPs, only inside target projects
439 if (ip.status == 'DOWN') and (ip.fixed_ip_address is None):
440 if ip.project_id in list_projects_to_delete:
441 fips_to_delete[ip.id] = ip.floating_ip_address
442 _log_resources_count(len(fips_to_delete), 'floating ip(s)')
443 if args.dry_run:
444 return
445 for id_ in fips_to_delete:
446 _log_resource_delete(id_, fips_to_delete[id_], 'floating ip')
447 network.delete_ip(id_)
448
449
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200450if __name__ == "__main__":
451 parser = argparse.ArgumentParser(
452 description='OpenStack test resources cleanup script')
453 parser.add_argument(
454 '-t', dest='dry_run', action='store_true',
455 help='Dry run mode, no cleanup is done')
456 parser.add_argument(
457 '-P', dest='projects', action='store_true',
458 help='Force cleanup of projects')
459 parser.add_argument(
460 '-S', dest='servers_active', action='store_true',
461 help='Set servers to ACTIVE before deletion (reqiured by bare metal)')
462 parser.add_argument(
463 '-f', dest='stacks_alt', action='store_true',
464 help='Use additional mask for stack cleanup')
465
466 args = parser.parse_args()
467
468 if args.dry_run:
469 log.info("Running in dry-run mode")
470 if args.servers_active:
471 log.info("Servers will be set to ACTIVE before cleanup")
472 if args.projects:
473 log.info("Project cleanup is enabled")
474 if args.stacks_alt:
475 log.info(
476 f"Stacks will be cleaned up using additional '{stack_mask}' mask")
477
478 cleanup_stacks(stacks_alt=args.stacks_alt)
Ievgeniia Zadorozhnad70f21c2023-08-09 20:24:38 +0300479 cleanup_load_balancers()
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200480 cleanup_servers()
481 cleanup_flavors()
Ievgeniia Zadorozhna85020982023-08-03 22:04:12 +0300482 cleanup_volume_backups()
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200483 cleanup_snapshots()
484 cleanup_volumes()
485 cleanup_volume_groups()
486 cleanup_volume_group_types()
487 cleanup_volume_types()
488 cleanup_images()
489 cleanup_sec_groups()
490 cleanup_keypairs()
491 cleanup_users()
492 cleanup_roles()
493 cleanup_services()
494 cleanup_regions()
495 cleanup_routers()
496 cleanup_networks()
497 cleanup_containers()
Ievgeniia Zadorozhna9600b182023-08-04 17:56:24 +0300498 cleanup_floating_ips()
Dmitriy Kruglovb811e642022-10-06 12:24:33 +0200499
500 if args.projects:
501 cleanup_projects()
502
503 msg = "Cleanup is FINISHED"
504 log.info(f"\n{'=' * len(msg)}\n{msg}")