Merge lp:~sinzui/juju-ci-tools/cloudsigma-lib into lp:juju-ci-tools

Proposed by Curtis Hovey
Status: Work in progress
Proposed branch: lp:~sinzui/juju-ci-tools/cloudsigma-lib
Merge into: lp:juju-ci-tools
Diff against target: 179 lines (+167/-0)
2 files modified
cloudsigmaci.py (+166/-0)
requirements.txt (+1/-0)
To merge this branch: bzr merge lp:~sinzui/juju-ci-tools/cloudsigma-lib
Reviewer Review Type Date Requested Status
Juju Release Engineering Pending
Review via email: mp+274558@code.launchpad.net

Description of the change

Add support to list and delete servers in cloudsigma.

This branch adapts my personal script to find and delete servers Juju left behind in cloudsigma. The cloudsigma python lib does not allow us to explicitly set the conf location to get credentials and API points. There is an ugly work around in the code to make this script portable to other machines and users.

I am very unhappy with the python requirements specified by the cloudsigma lib. It required 8 packages by specific version I am not using the exact versions of packages on my computer nor on my OS X machine and the script works. I have already updated the s3 pip container [1]. I think we should create our own cloudsigma package with revised requirements.txt that sets minimum versions to accommodate all the ubuntu series, windows, os x and centos.

[1] https://console.aws.amazon.com/s3/home?region=eu-west-1#&bucket=juju-pip-archives&prefix=juju-ci-tools/

To post a comment you must log in.

Unmerged revisions

1121. By Curtis Hovey

Added script used to cleanup cloudsigma.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'cloudsigmaci.py'
2--- cloudsigmaci.py 1970-01-01 00:00:00 +0000
3+++ cloudsigmaci.py 2015-10-15 13:51:05 +0000
4@@ -0,0 +1,166 @@
5+#!/usr/bin/python
6+
7+from __future__ import print_function
8+from argparse import ArgumentParser
9+from datetime import (
10+ datetime,
11+ timedelta,
12+)
13+import os
14+import sys
15+
16+
17+__metaclass__ = type
18+
19+
20+OLD_MACHINE_AGE = 12
21+ISO_8601_FORMAT = '%Y-%m-%dT%H:%M:%S+00:00'
22+
23+
24+class CSServer:
25+
26+ def __init__(self, data):
27+ self.data = data
28+
29+ def __repr__(self):
30+ return '<{} uuid={} name={} status={}>'.format(
31+ self.__class__.__name__, self.uuid, self.name, self.status)
32+
33+ def __str__(self):
34+ return '{} {}'.format(self.name, self.status)
35+
36+ @property
37+ def status(self):
38+ return self.data['status']
39+
40+ @property
41+ def name(self):
42+ return self.data['name']
43+
44+ @property
45+ def uuid(self):
46+ return self.data['uuid']
47+
48+ @property
49+ def owner(self):
50+ """Return a dict of owner uuid and resource_url."""
51+ return self.data['owner']
52+
53+ @property
54+ def runtime(self):
55+ """Return None or the runtime dict."""
56+ return self.data['runtime']
57+
58+ @property
59+ def active_since(self):
60+ """Return None or the datetime since the server was activated.
61+
62+ As runtime is not guaranteed. We presume that "Running" servers have
63+ a runtime, and an active_since date.
64+ """
65+ if self.runtime:
66+ return datetime.strptime(
67+ self.runtime['active_since'], ISO_8601_FORMAT)
68+ return None
69+
70+ @property
71+ def resource_url(self):
72+ return self.data['resource_url']
73+
74+
75+class CloudSigmaManager:
76+
77+ def __init__(self, config_path):
78+ # CLOUDSIGMA_CONFIG must be in the env before cloudsigma is imported
79+ # because the cloudsigma.conf.config is set when the module is parsed.
80+ os.environ['CLOUDSIGMA_CONFIG'] = config_path
81+ import cloudsigma
82+ self.cloudsigma = cloudsigma
83+
84+ def get_servers(self):
85+ """Return a list of CSServers."""
86+ server = self.cloudsigma.resource.Server()
87+ return [CSServer(s) for s in server.list()]
88+
89+ def list_servers(self):
90+ """Print a list of CSServers."""
91+ servers = self.get_servers()
92+ for instance in servers:
93+ print(instance)
94+
95+ def delete_servers(self, old_age):
96+ """Delete stopped and old servers."""
97+ now = datetime.utcnow()
98+ ago = timedelta(hours=old_age)
99+ stopping = []
100+ server = self.cloudsigma.resource.Server()
101+ # Stop running servers.
102+ servers = self.get_servers()
103+ print('Found %d servers' % len(servers))
104+ for instance in servers:
105+ if instance.status == 'running':
106+ active_since = instance.active_since
107+ if active_since and (now - active_since) > ago:
108+ # The active_since date is not guaranteed and presumed
109+ # to be relevant to Running servers. Do not delete young
110+ # running servers; delete everything else.
111+ continue
112+ print('Stopping {}'.format(instance))
113+ server.stop(instance.uuid)
114+ stopping.append(instance.uuid)
115+ while stopping:
116+ for uuid in list(stopping):
117+ sdata = server.get(uuid)
118+ if sdata:
119+ instance = CSServer(sdata)
120+ if instance.status == 'stopped':
121+ stopping.remove(uuid)
122+ # Delete stopped servers.
123+ servers = self.get_servers()
124+ for instance in servers:
125+ if instance.status == 'stopped':
126+ print('Deleting {}'.format(instance))
127+ server.delete_with_all_drives(instance.uuid)
128+ # Report what was left behind.
129+ servers = self.get_servers()
130+ for instance in servers:
131+ print('Remaining {}'.format(instance))
132+
133+
134+def parse_args(argv=None):
135+ """Return the argument parser for this program."""
136+ parser = ArgumentParser('Query and manage joyent.')
137+ parser.add_argument(
138+ '-d', '--dry-run', action='store_true', default=False,
139+ help='Do not make changes.')
140+ parser.add_argument(
141+ '-v', '--verbose', action="store_true", help='Increase verbosity.')
142+ parser.add_argument(
143+ "-c", "--config", dest="config", type=os.path.expanduser,
144+ default=os.path.expanduser('~/cloud-city/cloudsigma.conf'),
145+ help="Path to cloudsigma.conf")
146+ subparsers = parser.add_subparsers(help='sub-command help', dest="command")
147+ subparsers.add_parser('list-servers', help='List servers')
148+ parser_delete_old_machine = subparsers.add_parser(
149+ 'delete-servers',
150+ help='Delete servers older than %d hours' % OLD_MACHINE_AGE)
151+ parser_delete_old_machine.add_argument(
152+ '-o', '--old-age', default=OLD_MACHINE_AGE, type=int,
153+ help='Set old machine age to n hours.')
154+ args = parser.parse_args(argv)
155+ return args
156+
157+
158+def main(argv):
159+ args = parse_args()
160+ csm = CloudSigmaManager(args.config)
161+ if args.command == 'list-servers':
162+ csm.list_servers()
163+ elif args.command == 'delete-servers':
164+ csm.delete_servers(args.old_age)
165+ else:
166+ print("action not understood.")
167+
168+
169+if __name__ == '__main__':
170+ sys.exit(main(sys.argv[1:]))
171
172=== modified file 'requirements.txt'
173--- requirements.txt 2015-09-16 23:42:58 +0000
174+++ requirements.txt 2015-10-15 13:51:05 +0000
175@@ -1,3 +1,4 @@
176 azure==0.8.0
177+cloudsigma==0.4
178 coverage==3.7.1
179 pywinrm==0.0.3

Subscribers

People subscribed via source and target branches