sumomo 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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