sumomo 0.8.12 → 0.8.13

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.
data/lib/sumomo.rb CHANGED
@@ -117,6 +117,7 @@ module Sumomo
117
117
  stack_name: name,
118
118
  template_url: store.url('cloudformation/template'),
119
119
  parameters: hidden_values,
120
+ disable_rollback: true,
120
121
  capabilities: ['CAPABILITY_IAM']
121
122
  }
122
123
 
data/sumomo.gemspec CHANGED
@@ -27,15 +27,17 @@ Gem::Specification.new do |spec|
27
27
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
28
  spec.require_paths = ['lib']
29
29
 
30
- spec.add_development_dependency 'bundler', '~> 1.11'
31
- spec.add_development_dependency 'rake', '~> 10.0'
32
- spec.add_development_dependency 'rspec', '~> 3.0'
30
+ spec.add_development_dependency 'bundler'
31
+ spec.add_development_dependency 'rake'
32
+ spec.add_development_dependency 'rspec'
33
33
 
34
34
  spec.add_dependency 'activesupport'
35
- spec.add_dependency 'aws-sdk', '~> 2'
35
+ spec.add_dependency 'aws-sdk', '~> 3'
36
+ spec.add_dependency 'ox'
36
37
  spec.add_dependency 'hashie'
37
38
  spec.add_dependency 'momo', '0.4.1'
38
39
  spec.add_dependency 'rubyzip'
39
40
  spec.add_dependency 's3cabinet'
40
41
  spec.add_dependency 'trollop'
42
+ spec.add_dependency 'webrick'
41
43
  end
data/update-spec.rb ADDED
@@ -0,0 +1,381 @@
1
+ require 'json'
2
+
3
+ puts "Updates the data in the custom_resources folder"
4
+
5
+ `aws ec2 describe-instance-types > itypes`
6
+
7
+ data = JSON.parse(File.read('itypes'))
8
+
9
+ start = {
10
+ a: 2.0,
11
+ c: 2.5,
12
+ d: 2.0,
13
+ f: 2.0,
14
+ g: 2.0,
15
+ h: 2.0,
16
+ i: 3.0,
17
+ m: 2.0,
18
+ p: 2.0,
19
+ r: 2.0,
20
+ t: 1.0,
21
+ v: 2.0,
22
+ x: 2.0,
23
+ z: 2.0
24
+ }
25
+
26
+ types = []
27
+ types2 = []
28
+ data['InstanceTypes'].each do |info|
29
+
30
+ virt = 'pv'
31
+ if info["GpuInfo"]
32
+ virt = 'ecs-gpu-hvm'
33
+ elsif info["SupportedVirtualizationTypes"].last == 'hvm'
34
+ virt = 'ecs-hvm'
35
+ end
36
+
37
+ mount = 'ebs'
38
+
39
+ arch = info["ProcessorInfo"]["SupportedArchitectures"].last
40
+
41
+ raise 'UNKNOWN ARCH COMBINATION' if arch == 'arm64' && mount == 's3'
42
+
43
+ pattern = "amzn2-ami-#{virt}-2.0.*#{arch}-#{mount}"
44
+
45
+ if arch == 'x86_64_mac'
46
+ pattern = 'amzn-ec2-macos-*'
47
+ end
48
+
49
+ gennum = /[0-9]/.match(info['InstanceType']).to_s.to_i
50
+ ecu = (( start[ info['InstanceType'][0].to_sym ] + gennum.to_f * 0.5) * (info['VCpuInfo']['DefaultVCpus']).to_f * 1024).floor
51
+ types << " '#{info['InstanceType']}': { cpu: #{info['VCpuInfo']['DefaultVCpus'] * 1024}, memory: #{info["MemoryInfo"]["SizeInMiB"]}, gen: #{gennum}, ecu: #{ecu}, arch: '#{info["ProcessorInfo"]["SupportedArchitectures"].last}' }"
52
+ types2 << " '#{info['InstanceType']}': { pattern: '#{pattern}' }"
53
+ end
54
+
55
+ def align(arr)
56
+ result = []
57
+ lengths = arr[0].split(':').map{|x| x.length}
58
+
59
+ arr.each do |x|
60
+ tokens = x.split(':')
61
+ tokens.each_with_index do |y,i|
62
+ if y.length > lengths[i]
63
+ lengths[i] = y.length
64
+ end
65
+ end
66
+ end
67
+
68
+ arr.each do |x|
69
+ tokens = x.split(':')
70
+ newtokens = []
71
+ tokens.each_with_index do |y,i|
72
+ newtokens << (' ' * (lengths[i] - y.length) + y)
73
+ end
74
+ result << newtokens.join(':')
75
+ end
76
+
77
+ result
78
+ end
79
+
80
+ types.sort!
81
+ types2.sort!
82
+
83
+ types = align(types)
84
+ types2 = align(types2)
85
+
86
+ selectspot = <<~SELECTSPOT
87
+ var ec2 = new aws.EC2({region: request.ResourceProperties.Region});
88
+
89
+ if (request.RequestType == "Delete")
90
+ {
91
+ Cloudformation.send(request, context, Cloudformation.SUCCESS, {}, "Success");
92
+ return;
93
+ }
94
+
95
+ var prices = []
96
+
97
+ var exclude_string = request.ResourceProperties.ExcludeString || ""
98
+ var look_back = request.ResourceProperties.LookBack;
99
+ var want_to_pay = request.ResourceProperties.TargetPrice;
100
+
101
+ var exclude = exclude_string.split(",")
102
+
103
+ var typeToCapability = {
104
+ #{types.join(",\n")}
105
+ }
106
+
107
+ function pick_lowest_prices(prices)
108
+ {
109
+ var item_map = {};
110
+
111
+ for(var i in prices)
112
+ {
113
+ var price = prices[i];
114
+ if (!item_map[price.type] || item_map[price.type].price > price.price)
115
+ {
116
+ item_map[price.type] = price;
117
+ }
118
+ }
119
+
120
+ var result = [];
121
+
122
+ for(var i in item_map)
123
+ {
124
+ result.push(item_map[i]);
125
+ }
126
+
127
+ return result;
128
+ }
129
+
130
+ function score_type(type)
131
+ {
132
+ return (typeToCapability[type].cpu + typeToCapability[type].memory) * (1 + typeToCapability[type].gen * 0.05);
133
+ }
134
+
135
+ function difference(v1, v2)
136
+ {
137
+ return Math.abs(v1-v2);
138
+ }
139
+
140
+ function complete()
141
+ {
142
+ prices = pick_lowest_prices(prices);
143
+
144
+ prices.sort(function(a,b) {
145
+ if (a.price < b.price) {
146
+ return -1;
147
+ }
148
+ if (a.price > b.price) {
149
+ return 1;
150
+ }
151
+ return 0;
152
+ });
153
+
154
+ var index = 0;
155
+ for(var i in prices)
156
+ {
157
+ index = i;
158
+ if(prices[i].price > want_to_pay)
159
+ {
160
+ break;
161
+ }
162
+ }
163
+
164
+
165
+ shortlist = prices.splice(0, index)
166
+
167
+ var result = prices[0];
168
+
169
+ if (shortlist.length != 0)
170
+ {
171
+ shortlist.sort(function(a,b) {
172
+ if (score_type(a.type) > score_type(b.type)) {
173
+ return -1;
174
+ }
175
+ if (score_type(a.type) < score_type(b.type)) {
176
+ return 1;
177
+ }
178
+ return 0;
179
+ });
180
+
181
+ //for(var i in shortlist)
182
+ //{
183
+ // console.log(shortlist[i]);
184
+ //}
185
+ result = shortlist[0];
186
+ }
187
+
188
+ response = {
189
+ Price: String(result.price + 0.0001),
190
+ Zone: String(result.zone),
191
+ AveragePrice: String(result.average_price),
192
+ HighLine: String(result.high_line),
193
+ HighTime: String(result.high_time),
194
+ LowLine: String(result.low_line),
195
+ LowTime: String(result.low_time),
196
+ CpuUnits: String(typeToCapability[result.type].cpu),
197
+ CpuRating: String(typeToCapability[result.type].ecu),
198
+ MemRating: String(typeToCapability[result.type].memory)
199
+ }
200
+ Cloudformation.send(request, context, Cloudformation.SUCCESS, response, "Success", result.type);
201
+
202
+ console.log(JSON.stringify(prices));
203
+
204
+ }
205
+
206
+ function get_spot_prices(zones)
207
+ {
208
+ var newest_price={};
209
+ var xtotal={};
210
+ var xcount={};
211
+ var xhigh={};
212
+ var xlow={};
213
+ var xhightime={};
214
+ var xlowtime={};
215
+
216
+ function collect_data(zone, remaining, next_token)
217
+ {
218
+ if (remaining === 0 || next_token === null)
219
+ {
220
+ for(var type in newest_price)
221
+ {
222
+ var included = true;
223
+ for(var i=0;i<exclude.length;i++)
224
+ {
225
+ if(type.indexOf(exclude[i]) != -1)
226
+ {
227
+ included = false;
228
+ break;
229
+ }
230
+ }
231
+
232
+ if (!included)
233
+ {
234
+ continue;
235
+ }
236
+
237
+ prices.push({
238
+ type: type,
239
+ price: newest_price[type],
240
+ zone: zones[0],
241
+ average_price: xtotal[type]/xcount[type],
242
+ high_line: xhigh[type],
243
+ high_time: xhightime[type],
244
+ low_line: xlow[type],
245
+ low_time: xlowtime[type],
246
+ });
247
+ }
248
+
249
+ zones.splice(0,1);
250
+ get_spot_prices(zones);
251
+ }
252
+ else
253
+ {
254
+ ec2.describeSpotPriceHistory({
255
+ AvailabilityZone: zone,
256
+ MaxResults: 1000,
257
+ NextToken: next_token
258
+ }, function(err, data) {
259
+ if (err)
260
+ {
261
+ console.log(err, err.stack); // an error occurred
262
+ Cloudformation.send(request, context, Cloudformation.FAILED, {}, JSON.stringify(err));
263
+ }
264
+ else
265
+ {
266
+
267
+ //console.log(JSON.stringify(data.SpotPriceHistory))
268
+ for(var i=0;i<data.SpotPriceHistory.length;i++)
269
+ {
270
+ var history = data.SpotPriceHistory[i];
271
+
272
+ if (!newest_price[history.InstanceType])
273
+ {
274
+ newest_price[history.InstanceType] = Number(history.SpotPrice);
275
+ xtotal[history.InstanceType] = Number(history.SpotPrice);
276
+ xcount[history.InstanceType] = 1;
277
+ xhigh[history.InstanceType] = Number(history.SpotPrice);
278
+ xlow[history.InstanceType] = Number(history.SpotPrice);
279
+ xhightime[history.InstanceType] = history.Timestamp;
280
+ xlowtime[history.InstanceType] = history.Timestamp;
281
+ }
282
+ else
283
+ {
284
+ xtotal[history.InstanceType]+= Number(history.SpotPrice);
285
+ xcount[history.InstanceType]+= 1;
286
+ if (xhigh[history.InstanceType] < Number(history.SpotPrice))
287
+ {
288
+ xhigh[history.InstanceType] = Number(history.SpotPrice);
289
+ xhightime[history.InstanceType] = history.Timestamp;
290
+ }
291
+ if (xlow[history.InstanceType] > Number(history.SpotPrice))
292
+ {
293
+ xlow[history.InstanceType] = Number(history.SpotPrice);
294
+ xlowtime[history.InstanceType] = history.Timestamp;
295
+ }
296
+ }
297
+ }
298
+
299
+ collect_data(zone, remaining-1, data.NextToken);
300
+ }
301
+ });
302
+ }
303
+ }
304
+
305
+ if (zones.length == 0)
306
+ {
307
+ complete();
308
+ }
309
+ else
310
+ {
311
+ collect_data(zones[0], look_back);
312
+ }
313
+ }
314
+
315
+ ec2.describeAvailabilityZones({}, function(err, data) {
316
+ if (err)
317
+ {
318
+ console.log(err, err.stack); // an error occurred
319
+ Cloudformation.send(request, context, Cloudformation.FAILED, {}, JSON.stringify(err));
320
+ }
321
+ else
322
+ {
323
+ var zones = data.AvailabilityZones.map(function(x) {return x.ZoneName;});
324
+ get_spot_prices(zones);
325
+ }
326
+ });
327
+
328
+ SELECTSPOT
329
+
330
+ lookup = <<~AMILOOKUP
331
+ var typeToPattern = {
332
+ #{types2.join(",\n")}
333
+ }
334
+
335
+ var ec2 = new aws.EC2({region: request.ResourceProperties.Region});
336
+
337
+ var describeImagesParams = {
338
+ Filters: [{ Name: "name", Values: [typeToPattern[request.ResourceProperties.InstanceType].pattern]}],
339
+ Owners: [request.ResourceProperties.Architecture == "HVMG2" ? "679593333241" : "amazon"]
340
+ };
341
+
342
+ // Check if the image is a beta or rc image. The Lambda function won't return any of those images.
343
+ function isBeta(imageName) {
344
+ return imageName.toLowerCase().indexOf("beta") > -1 || imageName.toLowerCase().indexOf(".rc") > -1;
345
+ }
346
+
347
+ // Get AMI IDs with the specified name pattern and owner
348
+ ec2.describeImages(describeImagesParams, function(err, describeImagesResult)
349
+ {
350
+ if (err)
351
+ {
352
+ console.log(err, err.stack); // an error occurred
353
+ Cloudformation.send(request, context, Cloudformation.FAILED, {}, JSON.stringify(err));
354
+ }
355
+ else
356
+ {
357
+ var response = {}
358
+ var id = "NONE";
359
+ var images = describeImagesResult.Images;
360
+ // Sort images by name in descending order. The names contain the AMI version, formatted as YYYY.MM.Ver.
361
+ images.sort(function(x, y) { return y.Name.localeCompare(x.Name); });
362
+ for (var j = 0; j < images.length; j++)
363
+ {
364
+ if (isBeta(images[j].Name)) continue;
365
+ id = images[j].ImageId;
366
+ response["Name"] = images[j].Name;
367
+ response["ImageType"] = images[j].ImageType;
368
+ response["CreationDate"] = images[j].CreationDate;
369
+ response["Description"] = images[j].Description;
370
+ response["RootDeviceType"] = images[j].RootDeviceType;
371
+ response["RootDeviceName"] = images[j].RootDeviceName;
372
+ response["VirtualizationType"] = images[j].VirtualizationType;
373
+ break;
374
+ }
375
+ Cloudformation.send(request, context, Cloudformation.SUCCESS, response, "Success", id);
376
+ }
377
+ });
378
+ AMILOOKUP
379
+
380
+ File.write('data/sumomo/custom_resources/SelectSpot.js', selectspot);
381
+ File.write('data/sumomo/custom_resources/AMILookup.js', lookup);
metadata CHANGED
@@ -1,57 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sumomo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.12
4
+ version: 0.8.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Siaw
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-08-26 00:00:00.000000000 Z
11
+ date: 2021-11-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.11'
19
+ version: '0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '1.11'
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '3.0'
47
+ version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '3.0'
54
+ version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: activesupport
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -72,14 +72,28 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: '2'
75
+ version: '3'
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: '2'
82
+ version: '3'
83
+ - !ruby/object:Gem::Dependency
84
+ name: ox
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: hashie
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -150,6 +164,20 @@ dependencies:
150
164
  - - ">="
151
165
  - !ruby/object:Gem::Version
152
166
  version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: webrick
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :runtime
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
153
181
  description: Sumomo
154
182
  email:
155
183
  - davidsiaw@gmail.com
@@ -2544,6 +2572,7 @@ files:
2544
2572
  - lib/sumomo/stack.rb
2545
2573
  - lib/sumomo/version.rb
2546
2574
  - sumomo.gemspec
2575
+ - update-spec.rb
2547
2576
  homepage: https://github.com/davidsiaw/sumomo
2548
2577
  licenses: []
2549
2578
  metadata: