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))