From: Vladimir Khlyunev Date: Mon, 19 Jul 2021 23:10:31 +0000 (+0400) Subject: Add keypair cleanup feature X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=refs%2Fchanges%2F62%2F41762%2F13;p=tools%2Fsustaining.git Add keypair cleanup feature ./shell.py cleanup keypair --lifetime 60d --name testname Change-Id: I4c911dd49d11911c67540751bd410a5883e753ea --- diff --git a/os_cloud_cleaner/cleaner.py b/os_cloud_cleaner/cleaner.py index 8b61206..2f76cc0 100644 --- a/os_cloud_cleaner/cleaner.py +++ b/os_cloud_cleaner/cleaner.py @@ -2,11 +2,16 @@ from __future__ import unicode_literals import os from collections import OrderedDict -from datetime import datetime +import datetime + +import yaml +from dateutil import parser as date_parser from os_connector import OpenStackActions from logger import logger +from helpers import get_lifetime_delta + try: import prehooks except ImportError: @@ -23,12 +28,18 @@ class Cleaner: ignorelist_file=None): self.ignorelist_uuids = [] - if ignorelist_file is not None and os.path.exists(ignorelist_file): - with open(ignorelist_file) as f: - for line in f.readlines(): - if line and not line.startswith("#"): - self.ignorelist_uuids.append(line.split(" ")[0]) + if ignorelist_file is not None: + if os.path.exists(ignorelist_file): + with open(ignorelist_file) as f: + for line in f.readlines(): + if line and not line.startswith("#"): + self.ignorelist_uuids.append(line.split(" ")[0]) + else: + logger.error("ERROR: ignorelist file is passed but not found! " + "(could be working dir issue") + raise AttributeError + self.os_user = os_user self.os_conn = OpenStackActions( auth_url=os_auth_url, user=os_user, @@ -187,6 +198,9 @@ class Cleaner: 'routers': routers, 'subnets': subnets} + def get_keypairs(self): + return self.os_conn.get_keypairs() + def get_servers(self): return self.os_conn.get_servers() @@ -261,3 +275,56 @@ class Cleaner: self.cleanup_stack_parallel(uuid) else: logger.info("Can't find anything with is {}".format(uuid)) + + def get_user_keypair_keeplist(self, raise_exc=True): + file_exists = os.path.exists('keypair_keeplist.yml') + if not file_exists: + raise AssertionError("keypair_keeplist not found, " + "too dangerous to proceed") + with open("keypair_keeplist.yml") as f: + keeplist = yaml.safe_load(f) + current_user_keeplist = keeplist.get(self.os_user, []) + if not current_user_keeplist: + if raise_exc: + raise AssertionError( + "keeplist for user {} empty, too dangerous " + "for cleanup operation".format(self.os_user)) + else: + logger.warning("keeplist for user {} empty; cleanup operation " + "will be unavailable".format(self.os_user)) + return current_user_keeplist + + def search_keypairs(self, name='', fingerprint='', lifetime=''): + current_user_keeplist = self.get_user_keypair_keeplist(raise_exc=False) + + keypairs = self.get_keypairs() + result = [] + now = datetime.datetime.utcnow() + + def check_gen_req(name, keypair_name): + if name == "DETECT_128": + return len(keypair_name) == 128 + return name in keypair_name + + for keypair in keypairs: + if keypair.name in current_user_keeplist: + logger.info("IGNORE > keypair {}".format(keypair.name)) + continue + if check_gen_req(name, + keypair.name) and fingerprint in keypair.fingerprint: + if lifetime: + lifetime_delta = get_lifetime_delta(lifetime) + if now - date_parser.parse( + keypair.created_at) > lifetime_delta: + result.append(keypair) + else: + result.append(keypair) + return result + + def cleanup_keypairs(self, name='', fingerprint='', lifetime=''): + keypairs_to_delete = self.search_keypairs(name, fingerprint, lifetime) + # use get user keypair keeplist to stop script if there is no keeplist + self.get_user_keypair_keeplist(raise_exc=True) + for keypair in keypairs_to_delete: + logger.info("Deleting keypair {}".format(keypair.name)) + keypair.delete() diff --git a/os_cloud_cleaner/helpers.py b/os_cloud_cleaner/helpers.py index d209d63..4caeeca 100644 --- a/os_cloud_cleaner/helpers.py +++ b/os_cloud_cleaner/helpers.py @@ -1,3 +1,4 @@ +import datetime import signal import time @@ -125,3 +126,16 @@ def _check_wait_args(predicate, predicate_args, predicate_kwargs, timeout)) + + +def get_lifetime_delta(lifetime): + multipliers = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400} + if lifetime[-1] not in multipliers: + raise ValueError( + 'Value should end with ' + 'one of "{}", got "{}"'.format( + " ".join(multipliers.keys()), lifetime + )) + num = int(lifetime[:-1]) + mul = lifetime[-1] + return datetime.timedelta(seconds=num*multipliers[mul]) diff --git a/os_cloud_cleaner/ignorelist.txt b/os_cloud_cleaner/ignorelist.txt index a1b46a4..2d8aea8 100644 --- a/os_cloud_cleaner/ignorelist.txt +++ b/os_cloud_cleaner/ignorelist.txt @@ -6,6 +6,9 @@ ba308971-abd7-4630-a1f1-06d2caa3be37 # dstremkouski-k8s-node01 3fb4af2b-40d4-4ec3-88a0-17c3edd8ed67 # dstremkouski-k8s-node02 1589214e-43bf-4d57-80ac-68b28030c536 # sre-monitoring 5b222dd9-aeb9-42bd-8fac-e8f3874238d9 # dench-desktop +d6c676ff-d47d-4d64-8624-977b4d1ff27b # sre-team-infra +64b25fd1-d59f-4eb0-a732-cc500fd87ba1 # pkgs-ci-xenial-slave01 +a680c184-60f0-4ac8-9c0d-a6cc7a083bb1 # model-manager-prod # Networks 0563f790-92a3-4c92-ab41-74478442a75e # physnet1-402 b7b8b2e1-fb2e-4edb-9540-500363155fe3 # cvp-internal @@ -23,9 +26,16 @@ e977057f-ab64-466f-b953-1d9e44ac9f36 # system-phys-2404 5d4a2029-6c32-4bee-89dc-855829cc2233 # physnet1-432 c11bf78a-de07-4e67-8047-a28b247c27bb # sre-internal bf6b85a1-39db-4582-b0d1-f4291dddb9cf # public +07545b24-decb-4b19-848e-30937aa85359 # sre-team-default-internal-net +30e7b0b7-4fab-4dbb-bc38-4c64d489d3df # pkgs-ci-xenial-slave-net +c3799996-dc8e-4477-a309-09ea6dd71946 # public +af8a9bba-627f-4b79-9416-8595897cb5e2 # model-manager-net # Routers d92fc584-371b-4c0b-aa85-9c5c49292129 # sre-router 808a47c2-7ee4-46c2-b232-b625c70bd5d7 # vdrok-unittest +1c7803d2-0fcc-4084-9a89-e4b133270a77 # model-manager-router +4b78d573-2435-429b-8e9d-5f4ca88f2271 # pkgs-ci-xenial-slave-router +49851843-4a8a-411b-8d62-c47e536f9902 # sre-team-default-router # Volumes 83bada1a-3a04-413f-b3ef-d0f26adf69e2 # dstremkouski-convert-qcow2-raw 0d0665c2-c684-4a66-a224-72462ccb016d # dstremkouski-500G-stripe @@ -34,7 +44,12 @@ da6ff39c-01cc-4323-9c76-ab7fc243fae4 # dstremkouski-jumphost-eu d0eb9a28-8ea1-4728-9fcc-5e89d1fd773d # dstremkouski-500G d32349f4-74eb-4acb-a7a4-b9ae32029000 # windows-vw-jump 48a15d0c-5c48-45dc-b8db-13462239278c # sre-monitoring +5eb4a3c6-4dc3-437e-9ac5-0b15a1ffe873 # pkgs-ci-xenial-slave01 as /dev/vda +36a7a3a0-0c0e-4999-a793-d95003025100 # model-manager-prod-11092019 # Subnets +04999e63-a03f-4916-9923-01eb2e71a362 # model-manager-subnet +a5f29252-f0c0-4bff-887a-44217bd8c151 # pkgs-ci-xenial-slave-subnet +d83983b6-16b6-4a72-a6df-71583bbd1f88 # sre-team-default-internal-subnet 4e248805-d684-4411-bb3f-143ec8da5bad # cvp-subnet-internal-2 7d7e65ba-d6cf-458f-b963-50614fac5e00 # baremetal-subnet 0822b2c1-4676-46f1-a382-54d3c9ab1458 # physnet1-432-subnet diff --git a/os_cloud_cleaner/keypair_keeplist.yml b/os_cloud_cleaner/keypair_keeplist.yml new file mode 100644 index 0000000..a77fbb5 --- /dev/null +++ b/os_cloud_cleaner/keypair_keeplist.yml @@ -0,0 +1,20 @@ +# mentioned here keypairs will never be deleted by cleaner; add your keys if needed here +# username: +# - keypair_name +# - keypair_name +vkhlyunev: + - jenkins-patching-ci # US + - vkhlyunev # US,EU +maintenance-ci-robot: + - maintenance-ci-public-key + - system-key-8133 +drivetrain-jenkins: + - oscore-devcloud-env-ssh-public + - jenkins-mk + - k8s-helm-aio + - mcp-scale-jenkins + - mcpng2cmp + - system-key-8133 + - system_key_8133 + - mosk-ga + - scale-team-heat-mcp-2 \ No newline at end of file diff --git a/os_cloud_cleaner/os_connector.py b/os_cloud_cleaner/os_connector.py index 2b7d5c6..8942033 100644 --- a/os_cloud_cleaner/os_connector.py +++ b/os_cloud_cleaner/os_connector.py @@ -1,7 +1,5 @@ from __future__ import unicode_literals -import time - from cinderclient.exceptions import NotFound from cinderclient.v2.client import Client as CinderClient from heatclient.v1.client import Client as HeatClient @@ -117,6 +115,16 @@ class OpenStackActions(object): if f_ip_data['floating_ip_address'] == floating_ip: return f_ip_data['id'] + def get_keypairs(self): + keypairs = self.nova.keypairs.list() + resp = [] + for keypair in keypairs: + resp.append(self.nova.keypairs.get(keypair.name)) + return resp + + def get_one_keypair(self, name): + return self.nova.keypairs.get(name) + def get_ports(self): response = self.neutron.list_ports() ports = response['ports'] @@ -190,12 +198,6 @@ class OpenStackActions(object): return True return False - def check_subnet_exists(self, subnet_uuid): - resp = self.neutron.list_subnets(id=subnet_uuid)['subnets'] - if resp: - return True - return False - def check_any_network_exists(self, uuids): return any( [self.check_network_exists(uuid) for uuid in uuids] diff --git a/os_cloud_cleaner/shell.py b/os_cloud_cleaner/shell.py index 85d7856..c80b7e6 100644 --- a/os_cloud_cleaner/shell.py +++ b/os_cloud_cleaner/shell.py @@ -1,11 +1,14 @@ import argparse +from dateutil import parser as date_parser import os import sys + from prettytable import PrettyTable from cleaner import Cleaner from logger import logger + try: import prehooks except ImportError: @@ -37,6 +40,16 @@ def do_search(name, only_uuids): print table +def do_search_keypair(name='', fingerprint='', lifetime=''): + keypairs = cleaner.search_keypairs(name, fingerprint, lifetime) + table = PrettyTable() + table.field_names = ['name', 'created_at', 'fingerprint'] + table.align = 'l' + for value in sorted(keypairs, reverse=True, key=lambda x: date_parser.parse(x.created_at)): + table.add_row([value.name, value.created_at, value.fingerprint]) + print table + + parser = argparse.ArgumentParser() parser.add_argument('--os-auth-url', type=str) parser.add_argument('--os-username', type=str) @@ -53,15 +66,55 @@ cleanup_subparser.add_argument('resource_type', type=str, help="allowed: stack, misc") cleanup_subparser.add_argument('ids', nargs='*', type=str, help="uuids or names, error if name duplicates") -# cleanup_subparser.add_argument('--force', '-f', default=False, -# action='store_true') + +cleanup_subparser.add_argument('--name', + '-n', + type=str, + help='Search resources that only contains given name;' + 'usable for: search non-stack keypair operation; ' + 'for keypair there is pre-defined DETECT_128 for ' + '128char generated names', + default='') +cleanup_subparser.add_argument('--lifetime', + '-l', + type=str, + help='Usable for keypairs only; search/cleanuo keypairs only ' + 'older than given timeframe; ' + '30d - older than 30 days', + default='') +cleanup_subparser.add_argument('--fingerprint', + type=str, + help='Usable for keypairs only; Search resources ' + 'that only contains given fingerprint', + default='') + search_subparser = subparsers.add_parser('search') -search_subparser.add_argument('--name', '-n', type=str, - help='part of name to search for', - default='') +search_subparser.add_argument('resource_type', type=str, + help="allowed: misc, keypair") + search_subparser.add_argument('--only-uuids', '-i', help='print only uuids for chain xargs call', default=False, action='store_true') +search_subparser.add_argument('--name', + '-n', + type=str, + help='Search resources that only contains given name;' + 'usable for: search non-stack keypair operation; ' + 'for keypair there is pre-defined DETECT_128 for ' + '128char generated names', + default='') +search_subparser.add_argument('--lifetime', + '-l', + type=str, + help='Usable for keypairs only; search/cleanuo keypairs only ' + 'older than given timeframe; ' + '30d - older than 30 days', + default='') +search_subparser.add_argument('--fingerprint', + type=str, + help='Usable for keypairs only; Search resources ' + 'that only contains given fingerprint', + default='') # args = parser.parse_args("cleanup stack bm-cicd-pike-ovs-maas".split(" ")) # args = parser.parse_args("search -i -n bm-cicd-pike-ovs-maas".split(" ")) @@ -70,7 +123,10 @@ search_subparser.add_argument('--only-uuids', '-i', # args = parser.parse_args("search".split(" ")) # debug_args = "cleanup stack heat-cicd-queens-dvr-sl" -# debug_args = "search -n vkhlyunev" +# debug_args = "search non-stack -n vkhlyunev" +# debug_args = "search non-stack" +# debug_args = "search keypair -l 1d" +# debug_args = "cleanup keypair --name test -l 1m" # args = parser.parse_args(debug_args.split(" ")) args = parser.parse_args() @@ -85,9 +141,9 @@ auth_data['user_domain_name'] = args.os_user_domain_name or os.environ.get( 'OS_USER_DOMAIN_NAME') for validated_item in auth_data: - if not auth_data[validated_item]: - logger.error("Parameter {} NOT defined.".format(validated_item)) - sys.exit(124) + if not auth_data[validated_item]: + logger.error("Parameter {} NOT defined.".format(validated_item)) + sys.exit(124) cleaner = Cleaner(os_auth_url=auth_data['auth_url'], os_user=auth_data['username'], @@ -116,7 +172,7 @@ if args.action == "cleanup": os_id)) else: logger.info("Stack not found, nothing to delete") - if args.resource_type == "misc": + elif args.resource_type == "misc": os_ids = args.ids if not os_ids: data = sys.stdin.read() @@ -125,5 +181,12 @@ if args.action == "cleanup": if os_id: logger.info("Processing {}...".format(os_id)) cleaner.process_resource(os_id) + elif args.resource_type == "keypair": + cleaner.cleanup_keypairs(args.name, args.fingerprint, args.lifetime) + elif args.action == "search": - do_search(args.name, args.only_uuids) + if args.resource_type == "non-stack": + do_search(args.name, args.only_uuids) + elif args.resource_type == "keypair": + do_search_keypair(args.name, args.fingerprint, args.lifetime) +