sumomo 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+
2
+ if (request.RequestType == "Create")
3
+ {
4
+ Cloudformation.send(request, context, Cloudformation.SUCCESS, {}, "Success", String((new Date).getTime()));
5
+ }
6
+
7
+ if (request.RequestType == "Update")
8
+ {
9
+ Cloudformation.send(request, context, Cloudformation.SUCCESS, {}, "Success", String((new Date).getTime()));
10
+ }
11
+
12
+ if (request.RequestType == "Delete")
13
+ {
14
+ Cloudformation.send(request, context, Cloudformation.SUCCESS, {}, "Success", String((new Date).getTime()));
15
+ }
@@ -0,0 +1,294 @@
1
+ var ec2 = new aws.EC2({region: request.ResourceProperties.Region});
2
+
3
+ if (request.RequestType == "Delete")
4
+ {
5
+ Cloudformation.send(request, context, Cloudformation.SUCCESS, {}, "Success");
6
+ return;
7
+ }
8
+
9
+ var prices = []
10
+
11
+ var exclude_string = request.ResourceProperties.ExcludeString || ""
12
+ var look_back = request.ResourceProperties.LookBack;
13
+ var want_to_pay = request.ResourceProperties.TargetPrice;
14
+
15
+ var exclude = exclude_string.split(",")
16
+
17
+ var typeToCapability = {
18
+ "t1.micro" : { cpu: 1024, memory: 1024, gen: 1, ecu: 1024},
19
+ "t2.nano" : { cpu: 1024, memory: 512, gen: 2, ecu: 1024},
20
+ "t2.micro" : { cpu: 1024, memory: 1024, gen: 2, ecu: 1024},
21
+ "t2.small" : { cpu: 1024, memory: 2048, gen: 2, ecu: 1024},
22
+ "t2.medium" : { cpu: 2048, memory: 4096, gen: 2, ecu: 2048},
23
+ "t2.large" : { cpu: 2048, memory: 8192, gen: 2, ecu: 2048},
24
+ "m1.small" : { cpu: 1024, memory: 1740, gen: 1, ecu: 1024},
25
+ "m1.medium" : { cpu: 1024, memory: 3840, gen: 1, ecu: 2048},
26
+ "m1.large" : { cpu: 2048, memory: 7680, gen: 1, ecu: 4096},
27
+ "m1.xlarge" : { cpu: 4096, memory: 15360, gen: 1, ecu: 8192},
28
+ "m2.xlarge" : { cpu: 2048, memory: 17510, gen: 2, ecu: 6656},
29
+ "m2.2xlarge" : { cpu: 4096, memory: 35020, gen: 2, ecu: 13312},
30
+ "m2.4xlarge" : { cpu: 8192, memory: 70040, gen: 2, ecu: 26624},
31
+ "m3.medium" : { cpu: 1024, memory: 3840, gen: 3, ecu: 3072},
32
+ "m3.large" : { cpu: 2048, memory: 7680, gen: 3, ecu: 6656},
33
+ "m3.xlarge" : { cpu: 4096, memory: 15360, gen: 3, ecu: 13312},
34
+ "m3.2xlarge" : { cpu: 8192, memory: 30720, gen: 3, ecu: 26624},
35
+ "m4.large" : { cpu: 2048, memory: 8192, gen: 4, ecu: 6656},
36
+ "m4.xlarge" : { cpu: 4096, memory: 16384, gen: 4, ecu: 13312},
37
+ "m4.2xlarge" : { cpu: 8192, memory: 32768, gen: 4, ecu: 26624},
38
+ "m4.4xlarge" : { cpu: 16384, memory: 65536, gen: 4, ecu: 54784},
39
+ "m4.10xlarge" : { cpu: 40960, memory:163840, gen: 4, ecu: 127488},
40
+ "c1.medium" : { cpu: 2048, memory: 1740, gen: 1, ecu: 5120},
41
+ "c1.xlarge" : { cpu: 8192, memory: 7168, gen: 1, ecu: 20480},
42
+ "c3.large" : { cpu: 2048, memory: 3840, gen: 3, ecu: 7168},
43
+ "c3.xlarge" : { cpu: 4096, memory: 7680, gen: 3, ecu: 14336},
44
+ "c3.2xlarge" : { cpu: 8192, memory: 15360, gen: 3, ecu: 28672},
45
+ "c3.4xlarge" : { cpu: 16384, memory: 30720, gen: 3, ecu: 56320},
46
+ "c3.8xlarge" : { cpu: 32768, memory: 61440, gen: 3, ecu: 110592},
47
+ "c4.large" : { cpu: 2048, memory: 3840, gen: 4, ecu: 8192},
48
+ "c4.xlarge" : { cpu: 4096, memory: 7680, gen: 4, ecu: 16384},
49
+ "c4.2xlarge" : { cpu: 8192, memory: 15360, gen: 4, ecu: 31744},
50
+ "c4.4xlarge" : { cpu: 16384, memory: 30720, gen: 4, ecu: 63488},
51
+ "c4.8xlarge" : { cpu: 36864, memory: 61440, gen: 4, ecu: 135168},
52
+ "g2.2xlarge" : { cpu: 8192, memory: 15360, gen: 2, ecu: 26624},
53
+ "g2.8xlarge" : { cpu: 32768, memory: 61440, gen: 2, ecu: 106496},
54
+ "r3.large" : { cpu: 2048, memory: 15616, gen: 3, ecu: 6656},
55
+ "r3.xlarge" : { cpu: 4096, memory: 31232, gen: 3, ecu: 13312},
56
+ "r3.2xlarge" : { cpu: 8192, memory: 62464, gen: 3, ecu: 26624},
57
+ "r3.4xlarge" : { cpu: 16384, memory:124928, gen: 3, ecu: 53248},
58
+ "r3.8xlarge" : { cpu: 32768, memory:249856, gen: 3, ecu: 106496},
59
+ "i2.xlarge" : { cpu: 4096, memory: 31232, gen: 2, ecu: 14336},
60
+ "i2.2xlarge" : { cpu: 8192, memory: 62464, gen: 2, ecu: 27648},
61
+ "i2.4xlarge" : { cpu: 16384, memory:124928, gen: 2, ecu: 54272},
62
+ "i2.8xlarge" : { cpu: 32768, memory:249856, gen: 2, ecu: 106496},
63
+ "d2.xlarge" : { cpu: 4096, memory: 31232, gen: 2, ecu: 14336},
64
+ "d2.2xlarge" : { cpu: 8192, memory: 62464, gen: 2, ecu: 28672},
65
+ "d2.4xlarge" : { cpu: 16384, memory:124928, gen: 2, ecu: 57344},
66
+ "d2.8xlarge" : { cpu: 32768, memory:249856, gen: 2, ecu: 118784},
67
+ "hi1.4xlarge" : { cpu: 16384, memory: 61952, gen: 1, ecu: 35840},
68
+ "hs1.8xlarge" : { cpu: 16384, memory:119808, gen: 1, ecu: 35840},
69
+ "cr1.8xlarge" : { cpu: 32768, memory:249856, gen: 1, ecu: 90112},
70
+ "cc2.8xlarge" : { cpu: 32768, memory: 62464, gen: 1, ecu: 90112}
71
+ }
72
+
73
+ function pick_lowest_prices(prices)
74
+ {
75
+ var item_map = {};
76
+
77
+ for(var i in prices)
78
+ {
79
+ var price = prices[i];
80
+ if (!item_map[price.type] || item_map[price.type].price > price.price)
81
+ {
82
+ item_map[price.type] = price;
83
+ }
84
+ }
85
+
86
+ var result = [];
87
+
88
+ for(var i in item_map)
89
+ {
90
+ result.push(item_map[i]);
91
+ }
92
+
93
+ return result;
94
+ }
95
+
96
+ function score_type(type)
97
+ {
98
+ return (typeToCapability[type].cpu + typeToCapability[type].memory) * (1 + typeToCapability[type].gen * 0.05);
99
+ }
100
+
101
+ function difference(v1, v2)
102
+ {
103
+ return Math.abs(v1-v2);
104
+ }
105
+
106
+ function complete()
107
+ {
108
+ prices = pick_lowest_prices(prices);
109
+
110
+ prices.sort(function(a,b) {
111
+ if (a.price < b.price) {
112
+ return -1;
113
+ }
114
+ if (a.price > b.price) {
115
+ return 1;
116
+ }
117
+ return 0;
118
+ });
119
+
120
+ var index = 0;
121
+ for(var i in prices)
122
+ {
123
+ index = i;
124
+ if(prices[i].price > want_to_pay)
125
+ {
126
+ break;
127
+ }
128
+ }
129
+
130
+
131
+ shortlist = prices.splice(0, index)
132
+
133
+ var result = prices[0];
134
+
135
+ if (shortlist.length != 0)
136
+ {
137
+ shortlist.sort(function(a,b) {
138
+ if (score_type(a.type) > score_type(b.type)) {
139
+ return -1;
140
+ }
141
+ if (score_type(a.type) < score_type(b.type)) {
142
+ return 1;
143
+ }
144
+ return 0;
145
+ });
146
+
147
+ //for(var i in shortlist)
148
+ //{
149
+ // console.log(shortlist[i]);
150
+ //}
151
+ result = shortlist[0];
152
+ }
153
+
154
+ response = {
155
+ Price: String(result.price + 0.0001),
156
+ Zone: String(result.zone),
157
+ AveragePrice: String(result.average_price),
158
+ HighLine: String(result.high_line),
159
+ HighTime: String(result.high_time),
160
+ LowLine: String(result.low_line),
161
+ LowTime: String(result.low_time),
162
+ CpuUnits: String(typeToCapability[result.type].cpu),
163
+ CpuRating: String(typeToCapability[result.type].ecu),
164
+ MemRating: String(typeToCapability[result.type].memory)
165
+ }
166
+ Cloudformation.send(request, context, Cloudformation.SUCCESS, response, "Success", result.type);
167
+
168
+ console.log(JSON.stringify(prices));
169
+
170
+ }
171
+
172
+ function get_spot_prices(zones)
173
+ {
174
+ var newest_price={};
175
+ var xtotal={};
176
+ var xcount={};
177
+ var xhigh={};
178
+ var xlow={};
179
+ var xhightime={};
180
+ var xlowtime={};
181
+
182
+ function collect_data(zone, remaining, next_token)
183
+ {
184
+ if (remaining === 0 || next_token === null)
185
+ {
186
+ for(var type in newest_price)
187
+ {
188
+ var included = true;
189
+ for(var i=0;i<exclude.length;i++)
190
+ {
191
+ if(type.indexOf(exclude[i]) != -1)
192
+ {
193
+ included = false;
194
+ break;
195
+ }
196
+ }
197
+
198
+ if (!included)
199
+ {
200
+ continue;
201
+ }
202
+
203
+ prices.push({
204
+ type: type,
205
+ price: newest_price[type],
206
+ zone: zones[0],
207
+ average_price: xtotal[type]/xcount[type],
208
+ high_line: xhigh[type],
209
+ high_time: xhightime[type],
210
+ low_line: xlow[type],
211
+ low_time: xlowtime[type],
212
+ });
213
+ }
214
+
215
+ zones.splice(0,1);
216
+ get_spot_prices(zones);
217
+ }
218
+ else
219
+ {
220
+ ec2.describeSpotPriceHistory({
221
+ AvailabilityZone: zone,
222
+ MaxResults: 1000,
223
+ NextToken: next_token
224
+ }, function(err, data) {
225
+ if (err)
226
+ {
227
+ console.log(err, err.stack); // an error occurred
228
+ Cloudformation.send(request, context, Cloudformation.FAILED, {}, JSON.stringify(err));
229
+ }
230
+ else
231
+ {
232
+
233
+ //console.log(JSON.stringify(data.SpotPriceHistory))
234
+ for(var i=0;i<data.SpotPriceHistory.length;i++)
235
+ {
236
+ var history = data.SpotPriceHistory[i];
237
+
238
+ if (!newest_price[history.InstanceType])
239
+ {
240
+ newest_price[history.InstanceType] = Number(history.SpotPrice);
241
+ xtotal[history.InstanceType] = Number(history.SpotPrice);
242
+ xcount[history.InstanceType] = 1;
243
+ xhigh[history.InstanceType] = Number(history.SpotPrice);
244
+ xlow[history.InstanceType] = Number(history.SpotPrice);
245
+ xhightime[history.InstanceType] = history.Timestamp;
246
+ xlowtime[history.InstanceType] = history.Timestamp;
247
+ }
248
+ else
249
+ {
250
+ xtotal[history.InstanceType]+= Number(history.SpotPrice);
251
+ xcount[history.InstanceType]+= 1;
252
+ if (xhigh[history.InstanceType] < Number(history.SpotPrice))
253
+ {
254
+ xhigh[history.InstanceType] = Number(history.SpotPrice);
255
+ xhightime[history.InstanceType] = history.Timestamp;
256
+ }
257
+ if (xlow[history.InstanceType] > Number(history.SpotPrice))
258
+ {
259
+ xlow[history.InstanceType] = Number(history.SpotPrice);
260
+ xlowtime[history.InstanceType] = history.Timestamp;
261
+ }
262
+ }
263
+ }
264
+
265
+ collect_data(zone, remaining-1, data.NextToken);
266
+ }
267
+ });
268
+ }
269
+ }
270
+
271
+ if (zones.length == 0)
272
+ {
273
+ complete();
274
+ }
275
+ else
276
+ {
277
+ collect_data(zones[0], look_back);
278
+ }
279
+ }
280
+
281
+ ec2.describeAvailabilityZones({}, function(err, data) {
282
+ if (err)
283
+ {
284
+ console.log(err, err.stack); // an error occurred
285
+ Cloudformation.send(request, context, Cloudformation.FAILED, {}, JSON.stringify(err));
286
+ }
287
+ else
288
+ {
289
+ var zones = data.AvailabilityZones.map(function(x) {return x.ZoneName;});
290
+ get_spot_prices(zones);
291
+ }
292
+ });
293
+
294
+
@@ -0,0 +1,22 @@
1
+
2
+ #!/bin/bash
3
+ while [ 1 ]
4
+ do
5
+ string=`curl http://169.254.169.254/latest/meta-data/spot/termination-time`
6
+ if [[ $string == *"Not Found"* ]]
7
+ then
8
+ # no problem
9
+ message="MESSAGE=Heartbeat,INSTANCE=`curl http://169.254.169.254/latest/meta-data/instance-id`"
10
+ #aws sns publish --topic-arn "{{ sns_arn }}" --region {{ region }} --message $message
11
+ else
12
+ message="MESSAGE=Terminate,INSTANCE=`curl http://169.254.169.254/latest/meta-data/instance-id`"
13
+ aws sns publish --topic-arn "{{ sns_arn }}" --region {{ region }} --message $message
14
+ break
15
+ fi
16
+ sleep 2
17
+ done
18
+
19
+ while [ 1 ]
20
+ do
21
+ sleep 10
22
+ done
@@ -0,0 +1,45 @@
1
+ #!/bin/bash
2
+ #
3
+ # chkconfig: 35 90 12
4
+ # description: Spot instance watcher
5
+ #
6
+ # Get function from functions library
7
+ . /etc/init.d/functions
8
+ # Start the service spot-watcher
9
+ start() {
10
+ initlog -c "echo -n Starting spot-watcher: "
11
+ /bin/spot-watcher 2> /var/log/spot-watcher_error.log 1> var/log/spot-watcher.log &
12
+ ### Create the lock file ###
13
+ echo $! > /var/run/spot-watcher.pid
14
+ touch /var/lock/subsys/spot-watcher
15
+ success $"spot-watcher startup"
16
+ echo
17
+ }
18
+ # Restart the service spot-watcher
19
+ stop() {
20
+ initlog -c "echo -n Stopping spot-watcher: "
21
+ killproc spot-watcher
22
+ ### Now, delete the lock file ###
23
+ rm -f /var/lock/subsys/spot-watcher
24
+ echo
25
+ }
26
+ ### main logic ###
27
+ case "$1" in
28
+ start)
29
+ start
30
+ ;;
31
+ stop)
32
+ stop
33
+ ;;
34
+ status)
35
+ status spot-watcher
36
+ ;;
37
+ restart|reload|condrestart)
38
+ stop
39
+ start
40
+ ;;
41
+ *)
42
+ echo $"Usage: $0 {start|stop|restart|reload|status}"
43
+ exit 1
44
+ esac
45
+ exit 0
@@ -1,5 +1,197 @@
1
+ require 'momo'
2
+ require 's3cabinet'
3
+ require 'aws-sdk'
4
+ require 'zip'
5
+ require 'yaml'
6
+
1
7
  require "sumomo/version"
8
+ require 'sumomo/ec2'
9
+ require 'sumomo/stack'
10
+ require 'sumomo/network'
11
+ require 'sumomo/momo_extensions/resource'
12
+ require 'sumomo/momo_extensions/stack'
2
13
 
3
14
  module Sumomo
4
- # Your code goes here...
15
+
16
+ def self.make_master_key_name(name:)
17
+ "#{name}_master_key"
18
+ end
19
+
20
+ def self.make_master_key_key(name:)
21
+ "cloudformation/#{make_master_key_name(name: name)}.pem"
22
+ end
23
+
24
+ def self.update_stack(name:, region:, sns_arn:nil, &block)
25
+
26
+ cf = Aws::CloudFormation::Client.new(region: region)
27
+ s3 = Aws::S3::Client.new(region: region)
28
+ ec2 = Aws::EC2::Client.new(region: region)
29
+
30
+ begin
31
+ s3.head_bucket(bucket: name)
32
+ rescue Aws::S3::Errors::NotFound => e
33
+ s3.create_bucket(bucket: name)
34
+ end
35
+
36
+ store = S3Cabinet::S3Cabinet.new(nil, nil, name, region)
37
+
38
+ master_key_name = make_master_key_name(name: name)
39
+ master_key_key = make_master_key_key(name: name)
40
+
41
+ if !store.get(master_key_key)
42
+
43
+ resp = nil
44
+ begin
45
+ puts "No master key found, creating..."
46
+ resp = ec2.create_key_pair(key_name: master_key_name)
47
+ rescue
48
+ puts "Master key conflict! Deleting old one"
49
+ ec2.delete_key_pair(key_name: master_key_name)
50
+ resp = ec2.create_key_pair(key_name: master_key_name)
51
+ end
52
+
53
+ store.set(master_key_key, resp.key_material)
54
+ store.set("#{master_key_key}.fingerprint", resp.key_fingerprint)
55
+
56
+ end
57
+
58
+ dummy_number = store.get("cloudformation/dummy_number")
59
+ if dummy_number == nil
60
+ dummy_number = 0
61
+ end
62
+ dummy_number += 1
63
+ store.set("cloudformation/dummy_number", dummy_number)
64
+
65
+ hidden_values = []
66
+
67
+ template = Momo::cfl do
68
+ inject Sumomo::Stack
69
+
70
+ @region = region
71
+ @version_number = dummy_number
72
+ @custom_resources = {}
73
+ @bucket_name = name
74
+ @store = store
75
+ @master_key_name = master_key_name
76
+ @ec2 = ec2
77
+ @cf = cf
78
+ @s3 = s3
79
+
80
+ make "AWS::EC2::SecurityGroup", name: "DummyResource" do
81
+ GroupDescription "Dummy thing for Cloudformation Deployment."
82
+ Tags [{"Key" => "Name", "Value" => "dummyfordeploy#{dummy_number}"}]
83
+ end
84
+
85
+ make_exec_role
86
+
87
+ instance_eval(&block)
88
+
89
+ hidden_values = @hidden_values
90
+
91
+ end.templatize
92
+
93
+ #puts JSON.parse(template).to_yaml
94
+
95
+ store.set_raw("cloudformation/template", template)
96
+
97
+ update_options = {
98
+ stack_name: name,
99
+ template_url: store.url("cloudformation/template"),
100
+ parameters: hidden_values,
101
+ capabilities: ["CAPABILITY_IAM"]
102
+ }
103
+
104
+
105
+ begin
106
+ cf.update_stack(update_options)
107
+ rescue => e
108
+ update_options[:timeout_in_minutes] = 30
109
+ update_options[:notification_arns] = sns_arn if sns_arn
110
+ cf.create_stack(update_options)
111
+ end
112
+ end
113
+
114
+ def self.wait_for_stack(name:, region:)
115
+ cf = Aws::CloudFormation::Client.new(region: region)
116
+
117
+ stack_id = name
118
+
119
+ begin
120
+ resp = cf.describe_stack_events(stack_name: stack_id)
121
+ top_event = resp.stack_events[0]
122
+ top_event_id = top_event.event_id
123
+ puts "#{top_event.logical_resource_id} #{top_event.resource_status} #{top_event.resource_status_reason}"
124
+ rescue => e
125
+ puts "describe_stack_events: #{e.message}"
126
+ end
127
+
128
+ failure_count = 0
129
+ loop do
130
+ begin
131
+ unless /^arn\:/.match(stack_id)
132
+ stack_id = cf.describe_stacks(stack_name: stack_id).stacks[0].stack_id
133
+ #puts "Unique Stack ID: #{stack_id}"
134
+ end
135
+
136
+ resp = cf.describe_stack_events(stack_name: stack_id)
137
+ curr = 0
138
+ lines = []
139
+ loop do
140
+ curr_event = resp.stack_events[curr]
141
+ break if curr_event.event_id == top_event_id
142
+ lines << "#{curr_event.logical_resource_id} #{curr_event.resource_status} #{curr_event.resource_status_reason}"
143
+ break if curr == resp.stack_events.length - 1
144
+ curr += 1
145
+ end
146
+
147
+ lines.reverse.each { |x| puts x }
148
+
149
+ top_event_id = resp.stack_events[0].event_id
150
+ rescue => e
151
+ puts "describe_stack_events: #{e.message}"
152
+ failure_count += 1
153
+ break if failure_count > 5
154
+ end
155
+
156
+
157
+ sleep 1
158
+ begin
159
+ resp = cf.describe_stacks(stack_name: stack_id)
160
+
161
+ break if /(COMPLETE$)|(FAILED$)/.match(resp.stacks[0].stack_status)
162
+
163
+ rescue => e
164
+ puts "describe_stacks: #{e.message}"
165
+ break
166
+ end
167
+ end
168
+ end
169
+
170
+ def self.delete_stack(name:, region:, retain_bucket: false)
171
+ cf = Aws::CloudFormation::Client.new(region: region)
172
+ ec2 = Aws::EC2::Client.new(region: region)
173
+
174
+ cf.delete_stack(stack_name: name)
175
+ ec2.delete_key_pair(key_name: make_master_key_name(name: name))
176
+
177
+ if !retain_bucket
178
+ self.wait_for_stack(name: name, region: region)
179
+ s3 = Aws::S3::Resource.new(region: region)
180
+ bucket = s3.bucket(name)
181
+ bucket.delete!
182
+ end
183
+ end
184
+
185
+ def self.get_stack_outputs(name:, region:)
186
+ cf = Aws::CloudFormation::Client.new(region: region)
187
+
188
+ map = {}
189
+ cf.describe_stacks(stack_name: name).stacks[0].outputs.each do |x|
190
+ map[x.output_key] = x.output_value
191
+ end
192
+
193
+ map
194
+ end
195
+
196
+ singleton_class.send(:alias_method, :create_stack, :update_stack)
5
197
  end