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