~nskaggs/juju-ci-tools/add-assess-terms

1730.1.1 by Curtis Hovey
List gce resources.
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
1730.1.2 by Curtis Hovey
Revised tag and destroy rules.
15
from dateutil import parser as date_parser
1730.1.5 by Curtis Hovey
added tests for is_young.
16
from dateutil import tz
17
1730.1.1 by Curtis Hovey
List gce resources.
18
19
__metaclass__ = type
20
21
1730.1.2 by Curtis Hovey
Revised tag and destroy rules.
22
PERMANENT = 'permanent'
1730.1.11 by Curtis Hovey
OLD_MACHINE_AGE = 14
23
OLD_MACHINE_AGE = 14
1730.1.1 by Curtis Hovey
List gce resources.
24
25
1730.1.2 by Curtis Hovey
Revised tag and destroy rules.
26
# This logger strictly reports the activity of this script.
1730.1.1 by Curtis Hovey
List gce resources.
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
1730.1.2 by Curtis Hovey
Revised tag and destroy rules.
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
1730.1.5 by Curtis Hovey
added tests for is_young.
42
def is_young(node, old_age):
1730.1.2 by Curtis Hovey
Revised tag and destroy rules.
43
    """Return True if the node is young."""
1730.1.5 by Curtis Hovey
added tests for is_young.
44
    now = datetime.now(tz.gettz('UTC'))
1730.1.2 by Curtis Hovey
Revised tag and destroy rules.
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))
1730.1.1 by Curtis Hovey
List gce resources.
53
        ago = timedelta(hours=old_age)
1730.1.2 by Curtis Hovey
Revised tag and destroy rules.
54
        if age > ago:
55
            young = False
56
    return young
57
58
1768.1.1 by Seman Said
Added region to gce.py.
59
def get_client(sa_email, pem_path, project_id, region=None):
1730.1.2 by Curtis Hovey
Revised tag and destroy rules.
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)
1768.1.1 by Seman Said
Added region to gce.py.
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
1730.1.2 by Curtis Hovey
Revised tag and destroy rules.
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.
1730.1.1 by Curtis Hovey
List gce resources.
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 = []
1768.1.1 by Seman Said
Added region to gce.py.
81
    for node in client.list_nodes():
1730.1.1 by Curtis Hovey
List gce resources.
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')
1730.1.6 by Curtis Hovey
Added test for stdout printing.
89
            zone = node.extra.get('zone')
90
            if zone:
91
                zone_name = zone.name
92
            else:
93
                zone_name = 'UNKNOWN'
1730.1.1 by Curtis Hovey
List gce resources.
94
            print('{}\t{}\t{}\t{}'.format(
95
                node.name, zone_name, created, node.state))
96
    return nodes
97
98
1730.1.2 by Curtis Hovey
Revised tag and destroy rules.
99
def delete_instances(client, name_id, old_age=OLD_MACHINE_AGE, dry_run=False):
100
    """Delete a node instance.
1730.1.1 by Curtis Hovey
List gce resources.
101
1730.1.2 by Curtis Hovey
Revised tag and destroy rules.
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.
1730.1.1 by Curtis Hovey
List gce resources.
105
    """
1730.1.2 by Curtis Hovey
Revised tag and destroy rules.
106
    nodes = list_instances(client, glob=name_id)
1730.1.8 by Curtis Hovey
Added edge cases for delete_instance.
107
    deleted_count = 0
1730.1.2 by Curtis Hovey
Revised tag and destroy rules.
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:
1730.1.8 by Curtis Hovey
Added edge cases for delete_instance.
118
        log.warning(
1730.1.2 by Curtis Hovey
Revised tag and destroy rules.
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
1730.1.1 by Curtis Hovey
List gce resources.
131
            else:
1730.1.2 by Curtis Hovey
Revised tag and destroy rules.
132
                log.error('Cannot delete {}'.format(node_name))
1730.1.1 by Curtis Hovey
List gce resources.
133
    return deleted_count
134
135
136
def parse_args(argv):
137
    """Return the argument parser for this program."""
1730.1.2 by Curtis Hovey
Revised tag and destroy rules.
138
    parser = ArgumentParser(description='Query and manage GCE.')
1730.1.1 by Curtis Hovey
List gce resources.
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',
1730.1.2 by Curtis Hovey
Revised tag and destroy rules.
153
        help=("The path to the PEM file or a json file with PEM data. "
1730.1.1 by Curtis Hovey
List gce resources.
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'))
1768.1.1 by Seman Said
Added region to gce.py.
161
    parser.add_argument('--region', help="The compute engine region.")
1730.1.1 by Curtis Hovey
List gce resources.
162
    subparsers = parser.add_subparsers(help='sub-command help', dest="command")
163
    ls_parser = subparsers.add_parser(
1730.1.2 by Curtis Hovey
Revised tag and destroy rules.
164
        'list-instances', help='List vm instances.')
1730.1.1 by Curtis Hovey
List gce resources.
165
    ls_parser.add_argument(
166
        'filter', default='*', nargs='?',
167
        help='A glob pattern to match services to.')
1730.1.2 by Curtis Hovey
Revised tag and destroy rules.
168
    di_parser = subparsers.add_parser(
169
        'delete-instances',
1730.1.1 by Curtis Hovey
List gce resources.
170
        help='delete old resource groups and their vm, networks, etc.')
1730.1.2 by Curtis Hovey
Revised tag and destroy rules.
171
    di_parser.add_argument(
1730.1.1 by Curtis Hovey
List gce resources.
172
        '-o', '--old-age', default=OLD_MACHINE_AGE, type=int,
173
        help='Set old machine age to n hours.')
1730.1.2 by Curtis Hovey
Revised tag and destroy rules.
174
    di_parser.add_argument(
175
        'filter',
176
        help='A glob pattern to select gce name or juju instance-id')
1730.1.1 by Curtis Hovey
List gce resources.
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)
1768.1.1 by Seman Said
Added region to gce.py.
188
    client = get_client(args.sa_email, args.pem_path, args.project_id,
189
                        region=args.region)
1730.1.1 by Curtis Hovey
List gce resources.
190
    try:
1730.1.2 by Curtis Hovey
Revised tag and destroy rules.
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)
1730.1.1 by Curtis Hovey
List gce resources.
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))