github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/acceptancetests/gce.py (about) 1 #!/usr/bin/python 2 3 from __future__ import print_function 4 5 from argparse import ArgumentParser 6 from datetime import ( 7 datetime, 8 timedelta, 9 ) 10 import fnmatch 11 import logging 12 import os 13 import sys 14 15 from dateutil import parser as date_parser 16 from dateutil import tz 17 18 19 __metaclass__ = type 20 21 22 PERMANENT = 'permanent' 23 OLD_MACHINE_AGE = 14 24 25 26 # This logger strictly reports the activity of this script. 27 log = logging.getLogger("gce") 28 handler = logging.StreamHandler(sys.stderr) 29 handler.setFormatter(logging.Formatter( 30 fmt='%(asctime)s %(levelname)s %(message)s', 31 datefmt='%Y-%m-%d %H:%M:%S')) 32 log.addHandler(handler) 33 34 35 def is_permanent(node): 36 """Return True of the node is permanent.""" 37 # the tags keys only exists if there are tags. 38 tags = node.extra.get('tags', []) 39 return PERMANENT in tags 40 41 42 def is_young(node, old_age): 43 """Return True if the node is young.""" 44 now = datetime.now(tz.gettz('UTC')) 45 young = True 46 # The value is not guaranteed, but is always present in running instances. 47 created = node.extra.get('creationTimestamp') 48 if created: 49 creation_time = date_parser.parse(created) 50 age = now - creation_time 51 hours = age.total_seconds() // 3600 52 log.debug('{} is {} old'.format(node.name, hours)) 53 ago = timedelta(hours=old_age) 54 if age > ago: 55 young = False 56 return young 57 58 59 def get_client(sa_email, pem_path, project_id, region=None): 60 """Delay imports and activation of GCE client as needed.""" 61 import libcloud 62 gce = libcloud.compute.providers.get_driver( 63 libcloud.compute.types.Provider.GCE) 64 client = gce(sa_email, pem_path, project=project_id, datacenter=region) 65 if region and client.ex_get_zone(region) is None: 66 raise ValueError("Unknown region: ", region) 67 return client 68 69 70 def list_instances(client, glob='*', print_out=False): 71 """Return a list of cloud Nodes. 72 73 Use print_out=True to print a listing of nodes. 74 75 :param client: The GCE client. 76 :param glob: The glob to find matching resource groups to delete. 77 :param print_out: Print the found resources to STDOUT? 78 :return: A list of Nodes 79 """ 80 nodes = [] 81 for node in client.list_nodes(): 82 if not fnmatch.fnmatch(node.name, glob): 83 log.debug('Skipping {}'.format(node.name)) 84 continue 85 nodes.append(node) 86 if print_out: 87 for node in nodes: 88 created = node.extra.get('creationTimestamp') 89 zone = node.extra.get('zone') 90 if zone: 91 zone_name = zone.name 92 else: 93 zone_name = 'UNKNOWN' 94 print('{}\t{}\t{}\t{}'.format( 95 node.name, zone_name, created, node.state)) 96 return nodes 97 98 99 def delete_instances(client, name_id, old_age=OLD_MACHINE_AGE, dry_run=False): 100 """Delete a node instance. 101 102 :param name_id: A glob to match the gce name or Juju instance-id. 103 :param old_age: The minimum age to delete. 104 :param dry_run: Do not make changes when True. 105 """ 106 nodes = list_instances(client, glob=name_id) 107 deleted_count = 0 108 deletable = [] 109 for node in nodes: 110 if is_permanent(node): 111 log.debug('Skipping {} because it is permanent'.format(node.name)) 112 continue 113 if is_young(node, old_age): 114 log.debug('Skipping {} because it is young:'.format(node.name)) 115 continue 116 deletable.append(node) 117 if not deletable: 118 log.warning( 119 'The no machines match {} that are older than {}'.format( 120 name_id, old_age)) 121 for node in deletable: 122 node_name = node.name 123 log.debug('Deleting {}'.format(node_name)) 124 if not dry_run: 125 # Do not pass destroy_boot_disk=True unless the node has a special 126 # boot disk that is not set to autodestroy. 127 success = client.destroy_node(node) 128 if success: 129 log.debug('Deleted {}'.format(node_name)) 130 deleted_count += 1 131 else: 132 log.error('Cannot delete {}'.format(node_name)) 133 return deleted_count 134 135 136 def parse_args(argv): 137 """Return the argument parser for this program.""" 138 parser = ArgumentParser(description='Query and manage GCE.') 139 parser.add_argument( 140 '-d', '--dry-run', action='store_true', default=False, 141 help='Do not make changes.') 142 parser.add_argument( 143 '-v', '--verbose', action='store_const', 144 default=logging.INFO, const=logging.DEBUG, 145 help='Verbose test harness output.') 146 parser.add_argument( 147 '--sa-email', 148 help=("The service account email address." 149 "Environment: $GCE_SA_EMAIL."), 150 default=os.environ.get('GCE_SA_EMAIL')) 151 parser.add_argument( 152 '--pem-path', 153 help=("The path to the PEM file or a json file with PEM data. " 154 "Environment: $GCE_PEM_PATH."), 155 default=os.environ.get('GCE_PEM_PATH')) 156 parser.add_argument( 157 '--project-id', 158 help=("The secret to make requests with. " 159 "Environment: $GCE_PROJECT_ID."), 160 default=os.environ.get('GCE_PROJECT_ID')) 161 parser.add_argument('--region', help="The compute engine region.") 162 subparsers = parser.add_subparsers(help='sub-command help', dest="command") 163 ls_parser = subparsers.add_parser( 164 'list-instances', help='List vm instances.') 165 ls_parser.add_argument( 166 'filter', default='*', nargs='?', 167 help='A glob pattern to match services to.') 168 di_parser = subparsers.add_parser( 169 'delete-instances', 170 help='delete old resource groups and their vm, networks, etc.') 171 di_parser.add_argument( 172 '-o', '--old-age', default=OLD_MACHINE_AGE, type=int, 173 help='Set old machine age to n hours.') 174 di_parser.add_argument( 175 'filter', 176 help='A glob pattern to select gce name or juju instance-id') 177 args = parser.parse_args(argv[1:]) 178 if not all( 179 [args.sa_email, args.pem_path, args.project_id]): 180 log.error("$GCE_SA_EMAIL, $GCE_PEM_PATH, $GCE_PROJECT_ID " 181 "was not provided.") 182 return args 183 184 185 def main(argv): 186 args = parse_args(argv) 187 log.setLevel(args.verbose) 188 client = get_client(args.sa_email, args.pem_path, args.project_id, 189 region=args.region) 190 try: 191 if args.command == 'list-instances': 192 list_instances(client, glob=args.filter, print_out=True) 193 elif args.command == 'delete-instances': 194 delete_instances( 195 client, args.filter, 196 old_age=args.old_age, dry_run=args.dry_run) 197 except Exception as e: 198 print(e) 199 return 1 200 return 0 201 202 203 if __name__ == '__main__': 204 sys.exit(main(sys.argv))