|
106.1.3
by Dave Walker (Daviey)
Added LICENCE, AUTHORS and boilterplate to .py files |
1 |
# AWSTrial, A mechanism and service for offering a cloud image trial
|
2 |
#
|
|
3 |
# Copyright (C) 2010 Scott Moser <smoser@ubuntu.com>
|
|
4 |
# Copyright (C) 2010 Dave Walker (Daviey) <DaveWalker@ubuntu.com>
|
|
5 |
# Copyright (C) 2010 Michael Hall <mhall119@gmail.com>
|
|
6 |
# Copyright (C) 2010 Dustin Kirkland <kirkland@ubuntu.com>
|
|
7 |
# Copyright (C) 2010 Andreas Hasenack <andreas@canonical.com>
|
|
8 |
#
|
|
9 |
# This program is free software: you can redistribute it and/or modify
|
|
10 |
# it under the terms of the GNU Affero General Public License as
|
|
11 |
# published by the Free Software Foundation, either version 3 of the
|
|
12 |
# License, or (at your option) any later version.
|
|
13 |
#
|
|
14 |
# This program is distributed in the hope that it will be useful,
|
|
15 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
16 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
17 |
# GNU Affero General Public License for more details.
|
|
18 |
#
|
|
19 |
# You should have received a copy of the GNU Affero General Public License
|
|
20 |
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
21 |
||
|
289.1.11
by Hampton Paulk
remove dead imports |
22 |
from trial.models import Campaign, Instances |
|
1
by Dave Walker (Daviey)
Initial commit |
23 |
from django.contrib.auth.models import User |
|
263.1.1
by Michael Hall
Use the Site.name for the django.contrib.sites defined in settings.SITE_ID as the base url for callbacks |
24 |
from django.contrib.sites.models import Site |
|
36
by Scott Moser
make 'runit' use multipart input to cloud-init |
25 |
from django.template.loader import render_to_string |
|
1
by Dave Walker (Daviey)
Initial commit |
26 |
|
|
229.4.2
by Matthew Nuzum
Using from django.conf import settings instead of import settings |
27 |
from django.conf import settings |
|
1
by Dave Walker (Daviey)
Initial commit |
28 |
|
29 |
from boto.ec2.connection import EC2Connection, RegionInfo |
|
30 |
from boto import connect_ec2 |
|
|
9
by Scott Moser
add 'region' info most of the way through. add ami id to Instance |
31 |
import boto |
|
1
by Dave Walker (Daviey)
Initial commit |
32 |
import time |
|
229.4.1
by Matthew Nuzum
Adding awstrial, fixing paths to modules so that they're not prefixed with awstrial |
33 |
from trial import util |
|
1
by Dave Walker (Daviey)
Initial commit |
34 |
|
|
74
by Scott Moser
[UGLY] persist "config" information in the database. |
35 |
import yaml |
|
1
by Dave Walker (Daviey)
Initial commit |
36 |
|
37 |
||
|
9
by Scott Moser
add 'region' info most of the way through. add ami id to Instance |
38 |
def ec2_connection(region): |
|
235.2.1
by Matthew Nuzum
Alternate cloud configuration. |
39 |
a = 1 |
40 |
if settings.ALTERNATE_CLOUD: |
|
41 |
# For testing against euca
|
|
42 |
region = RegionInfo( |
|
43 |
name=settings.ALTERNATE_CLOUD['region'], |
|
44 |
endpoint=settings.ALTERNATE_CLOUD['endpoint'] |
|
45 |
)
|
|
46 |
||
47 |
connection = boto.ec2.EC2Connection( |
|
|
239.2.1
by Michael Hall
Adds test cases for making a connection to AWS or an alternate cloud. Fixes dateutils version dependency and adds dependency for mock |
48 |
aws_access_key_id=settings.AWS_ACCESS_KEY_ID, |
49 |
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY, |
|
|
235.2.1
by Matthew Nuzum
Alternate cloud configuration. |
50 |
region=region, |
51 |
is_secure=settings.ALTERNATE_CLOUD['is_secure'], |
|
52 |
port=settings.ALTERNATE_CLOUD['port'], |
|
53 |
path=settings.ALTERNATE_CLOUD['endpoint_path'] |
|
54 |
)
|
|
55 |
||
56 |
else: |
|
57 |
||
58 |
reginfo = RegionInfo(name=region, endpoint='ec2.%s.amazonaws.com' % region) |
|
59 |
connection = boto.ec2.EC2Connection( |
|
|
239.2.1
by Michael Hall
Adds test cases for making a connection to AWS or an alternate cloud. Fixes dateutils version dependency and adds dependency for mock |
60 |
aws_access_key_id=settings.AWS_ACCESS_KEY_ID, |
61 |
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY, |
|
|
235.2.1
by Matthew Nuzum
Alternate cloud configuration. |
62 |
region = reginfo) |
|
12
by Scott Moser
add ec2_helper routines |
63 |
|
|
1
by Dave Walker (Daviey)
Initial commit |
64 |
return connection |
65 |
||
|
264.2.1
by Michael Hall
Convert byobu-enable script to just installing the countdown widget, include it all the time |
66 |
def runit(campaign,lpid=None,regions=None,config=None, password=None): |
|
36
by Scott Moser
make 'runit' use multipart input to cloud-init |
67 |
sec_key = util.rand_str(32) |
|
102
by Scott Moser
add "region roll over" |
68 |
if regions is None: |
69 |
regions = [r for r in settings.REGIONS_TRY_ORDER |
|
70 |
if settings.REGION2AMI.has_key(r)] |
|
71 |
regions = [r for r in settings.REGIONS_TRY_ORDER |
|
72 |
if settings.REGION2AMI.has_key(r)] |
|
|
36
by Scott Moser
make 'runit' use multipart input to cloud-init |
73 |
|
|
74
by Scott Moser
[UGLY] persist "config" information in the database. |
74 |
cust = { } |
75 |
cust['config'] = config |
|
|
157.1.2
by Scott Moser
add setting of one time use password |
76 |
cust['password'] = password |
|
74
by Scott Moser
[UGLY] persist "config" information in the database. |
77 |
|
|
36
by Scott Moser
make 'runit' use multipart input to cloud-init |
78 |
parts=[] |
79 |
parts.append( |
|
80 |
util.partItem( |
|
81 |
render_to_string("import-launchpad-ssh-keys", |
|
|
259.1.1
by Michael Hall
Add framework for automating the collection of test data on launched instances |
82 |
{ 'debug' : settings.DEBUG, 'launchpad_id' : lpid, 'password' : password } ), |
|
144
by Scott Moser
provide number prefix on user-scripts to give ordering to run-parts |
83 |
part_type=util.CI_SCRIPT, filename="10-launchpad-ssh-keys")) |
|
263.1.1
by Michael Hall
Use the Site.name for the django.contrib.sites defined in settings.SITE_ID as the base url for callbacks |
84 |
|
85 |
site = Site.objects.get(pk=settings.SITE_ID) |
|
86 |
callback_url = "%s/info_callback/%s" % (site.name, sec_key) |
|
|
36
by Scott Moser
make 'runit' use multipart input to cloud-init |
87 |
parts.append( |
88 |
util.partItem( |
|
|
130
by Scott Moser
rename phone-home to info-callback |
89 |
render_to_string("info-callback", |
|
259.1.1
by Michael Hall
Add framework for automating the collection of test data on launched instances |
90 |
{ 'debug' : settings.DEBUG, 'callback_url' : "%s/initial/" % callback_url } ), |
|
144
by Scott Moser
provide number prefix on user-scripts to give ordering to run-parts |
91 |
part_type=util.CI_SCRIPT, filename="99-info-callback")) |
|
36
by Scott Moser
make 'runit' use multipart input to cloud-init |
92 |
|
|
43
by Scott Moser
schedule at jobs for warning/shutdown. add first login message to console. |
93 |
parts.append( |
94 |
util.partItem( |
|
95 |
render_to_string("schedule-warnings", |
|
|
259.1.1
by Michael Hall
Add framework for automating the collection of test data on launched instances |
96 |
{ 'debug' : settings.DEBUG, 'launch_time' : util.dtnow().strftime("%H:%M %Y-%m-%d UTC") } ), |
|
144
by Scott Moser
provide number prefix on user-scripts to give ordering to run-parts |
97 |
part_type=util.CI_SCRIPT, filename="50-schedule-warnings")) |
|
43
by Scott Moser
schedule at jobs for warning/shutdown. add first login message to console. |
98 |
|
99 |
parts.append( |
|
100 |
util.partItem( |
|
|
218.1.1
by Ubuntu
add hook to callback with IP of ssh login |
101 |
render_to_string("log-login", |
|
259.1.1
by Michael Hall
Add framework for automating the collection of test data on launched instances |
102 |
{ 'debug' : settings.DEBUG, 'callback_url' : "%s/login/" % callback_url }), |
|
218.1.1
by Ubuntu
add hook to callback with IP of ssh login |
103 |
part_type=util.CI_BOOTHOOK, filename="50-log-login")) |
|
43
by Scott Moser
schedule at jobs for warning/shutdown. add first login message to console. |
104 |
|
|
105
by Andreas Hasenack
Added personal-hello to the parts so that it is actually used. |
105 |
parts.append( |
106 |
util.partItem( |
|
|
259.1.1
by Michael Hall
Add framework for automating the collection of test data on launched instances |
107 |
render_to_string("personal-hello", { 'debug' : settings.DEBUG, 'launchpad_id' : lpid }), |
|
144
by Scott Moser
provide number prefix on user-scripts to give ordering to run-parts |
108 |
part_type=util.CI_SCRIPT, filename="50-personal-hello")) |
|
105
by Andreas Hasenack
Added personal-hello to the parts so that it is actually used. |
109 |
|
|
157.1.2
by Scott Moser
add setting of one time use password |
110 |
parts.append( |
111 |
util.partItem( |
|
112 |
render_to_string("password-enable", |
|
|
259.1.1
by Michael Hall
Add framework for automating the collection of test data on launched instances |
113 |
{ 'debug' : settings.DEBUG, 'user': 'ubuntu', 'password' : password }), |
|
157.1.2
by Scott Moser
add setting of one time use password |
114 |
part_type=util.CI_SCRIPT, filename="55-password-enable")) |
115 |
||
|
264.2.1
by Michael Hall
Convert byobu-enable script to just installing the countdown widget, include it all the time |
116 |
parts.append( |
117 |
util.partItem( |
|
118 |
render_to_string("byobu-countdown", { 'debug' : settings.DEBUG, }), |
|
119 |
part_type=util.CI_BOOTHOOK, filename="byobu-countdown")) |
|
120 |
cust['byobu']=True |
|
|
56
by Scott Moser
add byobu button |
121 |
|
|
45
by Scott Moser
add select box with configs on launch page. |
122 |
instcfg = None |
|
229.4.4
by Matthew Nuzum
Updating references to settings.configs to be settings.CONFIGS in order to work properly with imports of settings |
123 |
for c in settings.CONFIGS: |
|
74
by Scott Moser
[UGLY] persist "config" information in the database. |
124 |
if c['name'] == config: |
125 |
instcfg = c |
|
126 |
break
|
|
127 |
||
|
45
by Scott Moser
add select box with configs on launch page. |
128 |
if not instcfg: |
129 |
raise Exception("Invalid config %s" % config) |
|
130 |
||
131 |
if instcfg['template']: |
|
132 |
parts.append( |
|
133 |
util.partItem( |
|
|
259.1.1
by Michael Hall
Add framework for automating the collection of test data on launched instances |
134 |
render_to_string(instcfg['template'], { 'debug' : settings.DEBUG, }), |
|
45
by Scott Moser
add select box with configs on launch page. |
135 |
part_type=util.CI_CLOUDCONFIG, filename=instcfg['template'])) |
136 |
||
|
246.1.2
by Michael Hall
Allow null values for Instances.ami_id and Instances.region so we can create the record before we have an actual reservation. Creating the record first provides a locking mechanism while we wait for the reservation in EC2, so that the user can not launch another one while we wait |
137 |
# Create the instance record in the database before creating it in ec2
|
138 |
i = Instances.objects.create(instance_id='i-00000000', |
|
139 |
campaign=Campaign.objects.get(name=campaign), |
|
140 |
owner = User.objects.get(username=str(lpid)), |
|
141 |
reservation_time=util.dtnow(), |
|
142 |
secret=sec_key, |
|
143 |
config_info=yaml.dump(cust)) |
|
144 |
||
|
3
by Dave Walker (Daviey)
Settings for AWS with hard coded keys :( |
145 |
if True: #settings.DEBUG == False: |
|
9
by Scott Moser
add 'region' info most of the way through. add ami id to Instance |
146 |
|
|
102
by Scott Moser
add "region roll over" |
147 |
region = None |
148 |
reservation = None |
|
149 |
ex = None |
|
150 |
||
|
246.1.2
by Michael Hall
Allow null values for Instances.ami_id and Instances.region so we can create the record before we have an actual reservation. Creating the record first provides a locking mechanism while we wait for the reservation in EC2, so that the user can not launch another one while we wait |
151 |
try: |
152 |
for region in regions: |
|
153 |
if settings.ALTERNATE_CLOUD: |
|
154 |
ami = settings.ALTERNATE_CLOUD['ami'] |
|
155 |
else: |
|
156 |
ami = settings.REGION2AMI[region] |
|
157 |
||
158 |
try: |
|
159 |
ec2 = ec2_connection(region) |
|
160 |
reservation = ec2.run_instances(ami, |
|
161 |
instance_type=settings.INSTANCE_TYPE, |
|
162 |
key_name=settings.INSTANCE_KEY_NAME, |
|
163 |
security_groups=settings.INSTANCE_SECURITY_GROUPS, |
|
164 |
user_data=util.parts2mime(parts) |
|
165 |
)
|
|
166 |
||
167 |
break
|
|
168 |
except boto.exception.EC2ResponseError as ex: |
|
169 |
if ex.error_code == 'InstanceLimitExceeded': continue |
|
170 |
if ex.error_code == 'InsufficientInstanceCapacity': continue |
|
171 |
raise
|
|
172 |
||
173 |
if reservation is None: |
|
174 |
raise ex |
|
175 |
||
176 |
instance = reservation.instances[0] |
|
177 |
i.instance_id=instance.id |
|
178 |
i.ami_id=ami |
|
179 |
i.region=region |
|
180 |
i.reservation_time=util.dtnow() |
|
181 |
i.save() |
|
182 |
#while not instance.update() == 'running':
|
|
183 |
# time.sleep(5)
|
|
184 |
except boto.exception.EC2ResponseError as ex: |
|
185 |
i.delete() |
|
|
102
by Scott Moser
add "region roll over" |
186 |
raise ex |
|
1
by Dave Walker (Daviey)
Initial commit |
187 |
else: |
|
246.1.2
by Michael Hall
Allow null values for Instances.ami_id and Instances.region so we can create the record before we have an actual reservation. Creating the record first provides a locking mechanism while we wait for the reservation in EC2, so that the user can not launch another one while we wait |
188 |
i.ami = "ami-BADCOFFEE" |
189 |
i.instance = "i-406D088D" |
|
190 |
i.save() |
|
|
1
by Dave Walker (Daviey)
Initial commit |
191 |
|
|
12
by Scott Moser
add ec2_helper routines |
192 |
# take an 'Instances' object, and a of a boto Instance
|
193 |
# and updates Instances object iff it has changed
|
|
194 |
def update_instance_from_aws(instance,aws_data): |
|
195 |
aws2inst = { |
|
196 |
'id' : 'instance_id', |
|
197 |
'launch_time' : 'reservation_time', |
|
|
24
by Dave Walker (Daviey)
add population of hostname and ip in aws_poll |
198 |
'image_id' : 'ami_id', |
199 |
'ip_address' : 'ip', |
|
200 |
'public_dns_name' : 'hostname', |
|
|
12
by Scott Moser
add ec2_helper routines |
201 |
}
|
202 |
update = False |
|
203 |
for aattr,iattr in aws2inst.items(): |
|
|
18
by Scott Moser
committing code for killing instances. (broken). |
204 |
if aattr == "launch_time": |
205 |
aval = util.aws2datetime(getattr(aws_data,aattr,None)) |
|
206 |
else: |
|
207 |
aval = getattr(aws_data,aattr,None) |
|
|
15
by Scott Moser
ec2_helper.py: 2 fixes for tracebacks on bad variable names |
208 |
ival = getattr(instance,iattr,None) |
|
27
by Dave Walker (Daviey)
do not set null/empty values to instance |
209 |
if aval and aval != ival: |
|
12
by Scott Moser
add ec2_helper routines |
210 |
update = True |
|
18
by Scott Moser
committing code for killing instances. (broken). |
211 |
setattr(instance,iattr,aval) |
|
12
by Scott Moser
add ec2_helper routines |
212 |
state2datemap = { |
|
17
by Scott Moser
database changes: add fields, remove one |
213 |
"pending" : "reservation_time", |
|
12
by Scott Moser
add ec2_helper routines |
214 |
"running" : "running_time", |
|
38
by Scott Moser
address 'stopped' state in aws_poll (I think). remove some printfs there |
215 |
"stopped" : "running_time", |
|
12
by Scott Moser
add ec2_helper routines |
216 |
"shutting-down" : "shutdown_time", |
|
28
by Dave Walker (Daviey)
squash a couple bugs resulting in terminated time not getting set |
217 |
"terminated" : "terminated_time" |
|
12
by Scott Moser
add ec2_helper routines |
218 |
}
|
|
200
by Dave Walker (Daviey)
Made aws_poll.py silent, needs -v argument support |
219 |
#print "state=%s" % aws_data.state
|
|
12
by Scott Moser
add ec2_helper routines |
220 |
for state,iattr in state2datemap.items(): |
221 |
if ( aws_data.state == state and |
|
222 |
not getattr(instance,iattr,None) ): |
|
|
18
by Scott Moser
committing code for killing instances. (broken). |
223 |
setattr(instance,iattr,util.dtnow()) |
|
12
by Scott Moser
add ec2_helper routines |
224 |
update = True |
225 |
return update |
|
226 |
||
227 |
def update_instances(instances,region,conn=None): |
|
228 |
resultset = query_instances(instances, region, conn) |
|
229 |
aws_ihash = { } |
|
230 |
for res in resultset: |
|
231 |
for instance in res.instances: |
|
232 |
aws_ihash[instance.id]=instance |
|
233 |
||
|
200
by Dave Walker (Daviey)
Made aws_poll.py silent, needs -v argument support |
234 |
#import sys; sys.stderr.write("%-15s: aws had %s, inst list had %s\n"
|
235 |
# % (region,len(aws_ihash), len(instances)))
|
|
|
12
by Scott Moser
add ec2_helper routines |
236 |
|
237 |
for inst in instances: |
|
|
202
by Scott Moser
significantly reduce window for race condition on aws_poll (LP: #658362) |
238 |
# re-fetch the instance, in case it changed between
|
239 |
# the original 'query_instances' and now. It might have
|
|
240 |
# changed if console_poll, or info_callback updated it (LP: #658362)
|
|
241 |
inst = Instances.objects.get(instance_id=inst.instance_id, |
|
242 |
region=region) |
|
243 |
||
|
12
by Scott Moser
add ec2_helper routines |
244 |
if inst.instance_id not in aws_ihash: |
|
101
by Scott Moser
update terminated_time database for instances missing form aws query results |
245 |
# update the instance here also as it could be we lauched
|
246 |
# then never updated until it was completely gone from the
|
|
247 |
# aws side (hours later)
|
|
248 |
inst.terminated_time = util.dtnow() |
|
|
157.1.1
by Scott Moser
fix 2 errors in ec2_helper.py. one in simple syntax one in logic. |
249 |
import sys; sys.stderr.write("instance %s not seen in results, marking as terminated\n" % inst.instance_id) |
|
101
by Scott Moser
update terminated_time database for instances missing form aws query results |
250 |
inst.save() |
|
157.1.1
by Scott Moser
fix 2 errors in ec2_helper.py. one in simple syntax one in logic. |
251 |
elif update_instance_from_aws(inst,aws_ihash[inst.instance_id]): |
|
200
by Dave Walker (Daviey)
Made aws_poll.py silent, needs -v argument support |
252 |
#import sys; sys.stderr.write("saving state of %s\n" % inst.instance_id)
|
|
15
by Scott Moser
ec2_helper.py: 2 fixes for tracebacks on bad variable names |
253 |
inst.save() |
|
12
by Scott Moser
add ec2_helper routines |
254 |
|
255 |
# return boto response for DescribeInstances of all instances
|
|
256 |
def query_instances(instances,region,conn=None): |
|
257 |
instanceList = [] |
|
258 |
if not conn: |
|
259 |
conn = ec2_connection(region) |
|
260 |
for i in instances: |
|
261 |
instanceList.append(i.instance_id) |
|
|
18
by Scott Moser
committing code for killing instances. (broken). |
262 |
return (conn.get_all_instances(instance_ids=instanceList)) |
263 |
||
264 |
||
265 |
def terminate_instances(instances,region,conn=None): |
|
|
12
by Scott Moser
add ec2_helper routines |
266 |
""" Should reuse query_interface """
|
|
18
by Scott Moser
committing code for killing instances. (broken). |
267 |
if not len(instances): |
268 |
emp=() |
|
269 |
return emp |
|
270 |
||
271 |
if not conn: |
|
272 |
conn = ec2_connection(region) |
|
273 |
||
274 |
idlist = [ ] |
|
275 |
for i in instances: idlist.append(i.instance_id) |
|
276 |
||
277 |
return(conn.terminate_instances(idlist)) |
|
|
9
by Scott Moser
add 'region' info most of the way through. add ami id to Instance |
278 |
|
|
26
by Dave Walker (Daviey)
add console_poll utility to pull console logs |
279 |
def get_console_output(instance,region,conn=None): |
280 |
if not conn: |
|
281 |
conn = ec2_connection(region) |
|
282 |
response=conn.get_console_output(instance.instance_id) |
|
|
228
by Scott Moser
pull back console_poll improvements from natty to trunk |
283 |
if not response.output: return("") |
|
215
by Scott Moser
fix trace when some characters are in console output: |
284 |
output = response.output.decode('utf-8','replace') |
285 |
return "%s\n%s\n" % (response.timestamp, output) |
|
|
26
by Dave Walker (Daviey)
add console_poll utility to pull console logs |
286 |
|
|
9
by Scott Moser
add 'region' info most of the way through. add ami id to Instance |
287 |
# vi: ts=4 expandtab
|