openstudio-aws 0.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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3f236cadc25bfed979fd052337a7cc9712186075
4
+ data.tar.gz: 79ece9147f4fc4408fb39cdba032c4ef5c4cfa0c
5
+ SHA512:
6
+ metadata.gz: 644b891e5a9cdb2476df32d3b7847bad5f30aa0d9ba9adab099ab9c99df4b9be44f87d78f0ca5ef74653eb0f3a97070b5bf848485e9b617f733ae361439fd54d
7
+ data.tar.gz: bdc69e4e5293cedb1ee3454143a7f226197e5adac1810378426c90a7f7ea809ad1044d0e1aa99949d16057d12408227c294cb2b424ff9b0dabeff1cf729f0324
@@ -0,0 +1,18 @@
1
+ OpenStudio AWS Gem
2
+ ==================
3
+
4
+ OpenStudio AWS uses the OpenStudio AWS ruby class to launch a server and multiple workers for doing
5
+ OpenStudio/EnergyPlus Analyses using Amazon AWS/EC2
6
+
7
+ Instructions
8
+ ------------
9
+
10
+ Typically this gem is used in conjunction with other gems such as OpenStudio-Analysis.
11
+
12
+ To use this make sure to have ruby 2.0 in your path.
13
+
14
+ Development Notes
15
+ -----------------
16
+
17
+ There is an underlying attempt to merge several OpenStudio based gems into one and have a general
18
+ OpenStudio namespace for ruby classes.
@@ -0,0 +1,75 @@
1
+ require "bundler"
2
+ Bundler.setup
3
+
4
+ require "rake"
5
+ require "rspec/core/rake_task"
6
+
7
+ $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
8
+ require "openstudio/aws/version"
9
+
10
+ task :gem => :build
11
+ task :build do
12
+ system "gem build openstudio-aws.gemspec"
13
+ end
14
+
15
+ desc "build and install gem locally"
16
+ task :install => :build do
17
+ system "gem install openstudio-aws-#{OpenStudio::Aws::VERSION}.gem --no-ri --no-rdoc"
18
+ end
19
+
20
+ task :release => :build do
21
+ # system "git tag -a v#{OpenStudio::Aws::VERSION} -m 'Tagging #{OpenStudio::Aws::VERSION}'"
22
+ # system "git push --tags"
23
+ system "gem push openstudio-aws-#{OpenStudio::Aws::VERSION}.gem"
24
+ system "rm openstudio-aws-#{OpenStudio::Aws::VERSION}.gem"
25
+ end
26
+
27
+
28
+ RSpec::Core::RakeTask.new("spec") do |spec|
29
+ spec.pattern = "spec/**/*_spec.rb"
30
+ end
31
+
32
+ RSpec::Core::RakeTask.new('spec:progress') do |spec|
33
+ spec.rspec_opts = %w(--format progress)
34
+ spec.pattern = "spec/**/*_spec.rb"
35
+ end
36
+
37
+ task :default => :spec
38
+
39
+ desc "import files from other repos"
40
+ task :import_files do
41
+ puts "Importing data from other repos until this repo is self contained"
42
+ # Copy data from github openstudio source
43
+
44
+ os_file = "./lib/openstudio/lib/os-aws.rb"
45
+ system "curl -S -s -L -o #{os_file} https://raw.github.com/NREL/OpenStudio/develop/openstudiocore/ruby/cloud/aws.rb.in"
46
+ if File.exists?(os_file)
47
+ system "ruby -i -pe 'puts \"# NOTE: Do not modify this file as it is copied over. Modify the source file and rerun rake import_files\" if $.==1' #{os_file}"
48
+ system "sed -i '' 's/\${CMAKE_VERSION_MAJOR}.\${CMAKE_VERSION_MINOR}.\${CMAKE_VERSION_PATCH}/#{OpenStudio::Aws::OPENSTUDIO_VERSION}/g' #{os_file}"
49
+ end
50
+
51
+ os_file = "./lib/openstudio/lib/mongoid.yml.template"
52
+ system "curl -S -s -L -o #{os_file} https://raw.github.com/NREL/OpenStudio/develop/openstudiocore/ruby/cloud/mongoid.yml.template"
53
+
54
+ os_file = "./lib/openstudio/lib/server_script.sh"
55
+ system "curl -S -s -L -o #{os_file} https://raw.github.com/NREL/OpenStudio/develop/openstudiocore/ruby/cloud/server_script.sh"
56
+ if File.exists?(os_file)
57
+ system "ruby -i -pe 'puts \"# NOTE: Do not modify this file as it is copied over. Modify the source file and rerun rake import_files\" if $.==2' #{os_file}"
58
+ end
59
+
60
+ os_file = "./lib/openstudio/lib/worker_script.sh.template"
61
+ system "curl -S -s -L -o #{os_file} https://raw.github.com/NREL/OpenStudio/develop/openstudiocore/ruby/cloud/worker_script.sh.template"
62
+ if File.exists?(os_file)
63
+ system "ruby -i -pe 'puts \"# NOTE: Do not modify this file as it is copied over. Modify the source file and rerun rake import_files\" if $.==2' #{os_file}"
64
+ end
65
+ end
66
+
67
+ desc "uninstall all openstudio-aws gems"
68
+ task :uninstall do
69
+
70
+ system "gem uninstall openstudio-aws -a"
71
+ end
72
+
73
+ desc "reinstall the gem (uninstall, build, and reinstall"
74
+ task :reinstall => [:uninstall, :install]
75
+
@@ -0,0 +1,9 @@
1
+ require 'json'
2
+ require 'net/scp'
3
+ require 'yaml'
4
+
5
+ require 'openstudio/aws/version'
6
+ require 'openstudio/aws/aws'
7
+ require 'openstudio/aws/config'
8
+ require 'openstudio/aws/send_data'
9
+
@@ -0,0 +1,97 @@
1
+ # This class is a wrapper around the command line version that is in the OpenStudio repository.
2
+
3
+
4
+ module OpenStudio
5
+ module Aws
6
+ class Aws
7
+ attr_reader :server_data
8
+ attr_reader :worker_data
9
+
10
+ def initialize()
11
+ # read in the config.yml file to get the secret/private key
12
+ @config = OpenStudio::Aws::Config.new()
13
+ @server_data = nil
14
+ @worker_data = nil
15
+ end
16
+
17
+ # command line call to create a new instance. This should be more tightly integrated with teh os-aws.rb gem
18
+ def create_server(instance_data = {})
19
+ # TODO: find a way to override the instance ids here in case we want to prototype
20
+ defaults = {instance_type: "m2.xlarge"}
21
+ instance_data = defaults.merge(instance_data)
22
+
23
+ # Since this is a command line call then make sure to escape the quotes in the JSON
24
+ instance_string = instance_data.to_json.gsub("\"", "\\\\\"")
25
+
26
+
27
+ # Call the openstudio script to start the ec2 instance
28
+ start_string = "ruby #{os_aws_file_location} #{@config.access_key} #{@config.secret_key} us-east-1 EC2 launch_server \"#{instance_string}\""
29
+ puts "Server Command: #{start_string}"
30
+ server_data_str = `#{start_string}`
31
+ @server_data = JSON.parse(server_data_str, :symbolize_names => true)
32
+
33
+ # Save pieces of the data for passing to the worker node
34
+ server_key_file = "ec2_server_key.pem"
35
+ File.open(server_key_file, "w") { |f| f << @server_data[:private_key] }
36
+ File.chmod(0600, server_key_file)
37
+
38
+ # Save off the server data to be loaded into the worker nodes. The Private key needs to e read from a
39
+ # file in the worker node, so save that name instead in the HASH along with a couple other changes
40
+ @server_data[:private_key] = server_key_file
41
+ if @server_data[:server]
42
+ @server_data[:server_id] = @server_data[:server][:id]
43
+ @server_data[:server_ip] = @server_data[:server][:ip]
44
+ @server_data[:server_dns] = @server_data[:server][:dns]
45
+ end
46
+
47
+ File.open("server_data.json", "w") { |f| f << JSON.pretty_generate(@server_data) }
48
+
49
+ # Print out some debugging commands (probably work on mac/linux only)
50
+ puts ""
51
+ puts "Server SSH Command:"
52
+
53
+ puts "ssh -i #{server_key_file} ubuntu@#{@server_data[:server_dns]}"
54
+ end
55
+
56
+ def create_workers(number_of_instances, instance_data = {})
57
+ defaults = {instance_type: "m2.4xlarge"}
58
+ instance_data = defaults.merge(instance_data)
59
+
60
+ raise "Can't create workers without a server instance running" if @server_data.nil?
61
+
62
+ # append the information to the server_data hash that already exists
63
+ @server_data[:instance_type] = instance_data[:instance_type]
64
+ @server_data[:num] = number_of_instances
65
+ server_string = @server_data.to_json.gsub("\"", "\\\\\"")
66
+
67
+ start_string = "ruby #{os_aws_file_location} #{@config.access_key} #{@config.secret_key} us-east-1 EC2 launch_workers \"#{server_string}\""
68
+ puts "Worker Command: #{start_string}"
69
+ worker_data_string = `#{start_string}`
70
+ @worker_data = JSON.parse(worker_data_string, :symbolize_names => true)
71
+ File.open("worker_data.json", "w") { |f| f << JSON.pretty_generate(worker_data) }
72
+
73
+ # Print out some debugging commands (probably work on mac/linux only)
74
+ @worker_data[:workers].each do |worker|
75
+ puts ""
76
+ puts "Worker SSH Command:"
77
+ puts "ssh -i #{@server_data[:private_key]} ubuntu@#{worker[:dns]}"
78
+ end
79
+ end
80
+
81
+ def kill_instances()
82
+ # Add this method to kill all the running instances
83
+ end
84
+
85
+ private
86
+
87
+ def os_aws_file_location
88
+ # Get the location of the os-aws.rb file. Use the relative path from where this file exists
89
+ os_aws_file = File.expand_path(File.join(File.dirname(__FILE__), "..", "lib", "os-aws.rb"))
90
+ raise "os_aws_file does not exist where it is expected: #{os_aws_file}" unless File.exists?(os_aws_file)
91
+
92
+ os_aws_file
93
+ end
94
+
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,39 @@
1
+ module OpenStudio
2
+ module Aws
3
+ class Config
4
+ attr_accessor :access_key
5
+ attr_accessor :secret_key
6
+
7
+ def initialize(yml_config_file = nil)
8
+ @yml_config_file = yml_config_file
9
+ @config = nil
10
+ if @yml_config_file.nil?
11
+ @yml_config_file = File.join(File.expand_path("~"), "aws_config.yml")
12
+ puts @yml_config_file
13
+ if !File.exists?(@yml_config_file)
14
+ write_config_file
15
+ puts "No Config File in user home directory. A template has been added, please edit and save: #{@yml_config_file}"
16
+ exit 1
17
+ end
18
+ end
19
+
20
+ begin
21
+ @config = YAML.load(File.read(@yml_config_file))
22
+ @access_key = @config['access_key_id']
23
+ @secret_key = @config['secret_access_key']
24
+ rescue
25
+ raise "Couldn't read config file #{@yml_config_file}. Delete file then recreate by rerunning script"
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def write_config_file
32
+ File.open(@yml_config_file, 'w') do |f|
33
+ f << "access_key_id: YOUR_ACCESS_KEY_ID\n"
34
+ f << "secret_access_key: YOUR_SECRET_ACCESS_KEY\n"
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,42 @@
1
+ # This is taken out of the OpenStudio AWS config file to do some testing
2
+ #def send_command(host, command, key)
3
+ # require 'net/http'
4
+ # require 'net/scp'
5
+ # require 'net/ssh'
6
+ #
7
+ # retries = 0
8
+ # begin
9
+ # puts "connecting.."
10
+ # output = ''
11
+ # Net::SSH.start(host, 'ubuntu', :key_data => [key]) do |ssh|
12
+ # response = ssh.exec!(command)
13
+ # output += response if !response.nil?
14
+ # end
15
+ # return output
16
+ # rescue Net::SSH::HostKeyMismatch => e
17
+ # e.remember_host!
18
+ # # key mismatch, retry
19
+ # return if retries == 2
20
+ # retries += 1
21
+ # sleep 1
22
+ # retry
23
+ # rescue Net::SSH::AuthenticationFailed
24
+ # error(-1, "Incorrect private key")
25
+ # rescue SystemCallError, Timeout::Error => e
26
+ # # port 22 might not be available immediately after the instance finishes launching
27
+ # return if retries == 2
28
+ # retries += 1
29
+ # sleep 1
30
+ # retry
31
+ # rescue Exception => e
32
+ # puts e.message
33
+ # puts e.backtrace.inspect
34
+ # end
35
+ #end
36
+ #
37
+ #server_json = JSON.parse(File.read("server_data.json"), :symbolize_names => true)
38
+ #
39
+ #b = Time.now
40
+ #delta = b.to_f - a.to_f
41
+ #puts "startup time is #{delta}"
42
+ ##puts send_command(server_json[:server_ip], 'nproc | tr -d "\n"', File.read("ec2_server_key.pem"))
@@ -0,0 +1,6 @@
1
+ module OpenStudio
2
+ module Aws
3
+ VERSION = "0.1"
4
+ OPENSTUDIO_VERSION = "1.1.2"
5
+ end
6
+ end
@@ -0,0 +1,19 @@
1
+ development:
2
+ sessions:
3
+ default:
4
+ database: os_dev
5
+ hosts:
6
+ - SERVER_IP:27017
7
+ options:
8
+ allow_dynamic_fields: true
9
+ raise_not_found_error: false
10
+ production:
11
+ sessions:
12
+ default:
13
+ database: os_prod
14
+ hosts:
15
+ - SERVER_IP:27017
16
+ options:
17
+ allow_dynamic_fields: true
18
+ raise_not_found_error: false
19
+
@@ -0,0 +1,556 @@
1
+ # NOTE: Do not modify this file as it is copied over. Modify the source file and rerun rake import_files
2
+ ######################################################################
3
+ # Copyright (c) 2008-2013, Alliance for Sustainable Energy.
4
+ # All rights reserved.
5
+ #
6
+ # This library is free software; you can redistribute it and/or
7
+ # modify it under the terms of the GNU Lesser General Public
8
+ # License as published by the Free Software Foundation; either
9
+ # version 2.1 of the License, or (at your option) any later version.
10
+ #
11
+ # This library is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
+ # Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public
17
+ # License along with this library; if not, write to the Free Software
18
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
+ ######################################################################
20
+
21
+ ######################################################################
22
+ # == Synopsis
23
+ #
24
+ # Uses the aws-sdk gem to communicate with AWS
25
+ #
26
+ # == Usage
27
+ #
28
+ # ruby aws.rb access_key secret_key us-east-1 EC2 launch_server "{\"instance_type\":\"t1.micro\"}"
29
+ #
30
+ # ARGV[0] - Access Key
31
+ # ARGV[1] - Secret Key
32
+ # ARGV[2] - Region
33
+ # ARGV[3] - Service (e.g. "EC2" or "CloudWatch")
34
+ # ARGV[4] - Command (e.g. "launch_server")
35
+ # ARGV[5] - Optional json with parameters associated with command
36
+ #
37
+ ######################################################################
38
+
39
+ require 'aws-sdk'
40
+ require 'json'
41
+ require 'logger'
42
+ require 'net/http'
43
+ require 'net/scp'
44
+ require 'net/ssh'
45
+ require 'tempfile'
46
+ require 'time'
47
+
48
+ OPENSTUDIO_VERSION='1.1.2'
49
+
50
+ def error(code, msg)
51
+ puts ({:error => {:code => code, :message => msg},
52
+ :arguments => ARGV[2..-1]}.to_json)
53
+ exit(1)
54
+ end
55
+
56
+ if ARGV.length < 5
57
+ error(-1, 'Invalid number of args')
58
+ end
59
+
60
+ if ARGV[0].empty? || ARGV[1].empty?
61
+ error(401, 'Missing authentication arguments')
62
+ end
63
+
64
+ if ARGV[2].empty?
65
+ error(-1, 'Missing region argument')
66
+ end
67
+
68
+ if ARGV[3].empty?
69
+ error(-1, 'Missing service argument')
70
+ end
71
+
72
+ if ARGV[4].empty?
73
+ error(-1, 'Missing command argument')
74
+ end
75
+
76
+ AWS.config(
77
+ :access_key_id => ARGV[0],
78
+ :secret_access_key => ARGV[1],
79
+ :region => ARGV[2],
80
+ :ssl_verify_peer => false
81
+ )
82
+
83
+ if ARGV[3] == 'EC2'
84
+ @aws = AWS::EC2.new
85
+ elsif ARGV[3] == 'CloudWatch'
86
+ @aws = AWS::CloudWatch.new
87
+ else
88
+ error(-1, "Unrecognized AWS service: #{ARGV[3]}")
89
+ end
90
+
91
+ if ARGV.length == 6
92
+ @params = JSON.parse(ARGV[5])
93
+ end
94
+
95
+ resp = Net::HTTP.get_response('developer.nrel.gov','/downloads/buildings/openstudio/rsrc/amis.json')
96
+ if resp.code == '200'
97
+ result = JSON.parse(resp.body)
98
+ version = result.has_key?(OPENSTUDIO_VERSION) ? OPENSTUDIO_VERSION : 'default'
99
+
100
+ @server_image_id = result[version]['server']
101
+ if ARGV.length >= 6 && @params['instance_type'] == 'cc2.8xlarge'
102
+ @worker_image_id = result[version]['cc2worker']
103
+ else
104
+ @worker_image_id = result[version]['worker']
105
+ end
106
+ else
107
+ error(resp.code, 'Unable to download AMI IDs')
108
+ end
109
+
110
+ def create_struct(instance, procs)
111
+ instance_struct = Struct.new(:instance, :id, :ip, :dns, :procs)
112
+ return instance_struct.new(instance, instance.instance_id, instance.ip_address, instance.dns_name, procs)
113
+ end
114
+
115
+ def find_processors(instance)
116
+ processors = 1
117
+ case instance
118
+ when 'cc2.8xlarge'
119
+ processors = 16
120
+ when 'c1.xlarge'
121
+ processors = 8
122
+ when 'm2.4xlarge'
123
+ processors = 8
124
+ when 'm2.2xlarge'
125
+ processors = 4
126
+ when 'm2.xlarge'
127
+ processors = 2
128
+ when 'm1.xlarge'
129
+ processors = 4
130
+ when 'm1.large'
131
+ processors = 2
132
+ when 'm3.xlarge'
133
+ processors = 2
134
+ when 'm3.2xlarge'
135
+ processors = 4
136
+ end
137
+
138
+ return processors
139
+ end
140
+
141
+ def launch_server
142
+ user_data = File.read(File.expand_path(File.dirname(__FILE__))+'/server_script.sh')
143
+ @logger.info("server user_data #{user_data.inspect}")
144
+ @server = @aws.instances.create(:image_id => @server_image_id,
145
+ :key_pair => @key_pair,
146
+ :security_groups => @group,
147
+ :user_data => user_data,
148
+ :instance_type => @server_instance_type)
149
+ @server.add_tag('Name', :value => "OpenStudio-Server V#{OPENSTUDIO_VERSION}")
150
+ sleep 5 while @server.status == :pending
151
+ if @server.status != :running
152
+ error(-1, "Server status: #{@server.status}")
153
+ end
154
+
155
+ processors = find_processors(@server_instance_type)
156
+ #processors = send_command(@server.ip_address, 'nproc | tr -d "\n"')
157
+ #processors = 0 if processors.nil? # sometimes this returns nothing, so put in a default
158
+ @server = create_struct(@server, processors)
159
+ end
160
+
161
+ def launch_workers(num, server_ip)
162
+ user_data = File.read(File.expand_path(File.dirname(__FILE__))+'/worker_script.sh.template')
163
+ user_data.gsub!(/SERVER_IP/, server_ip)
164
+ user_data.gsub!(/SERVER_HOSTNAME/, 'master')
165
+ user_data.gsub!(/SERVER_ALIAS/, '')
166
+ @logger.info("worker user_data #{user_data.inspect}")
167
+ instances = []
168
+ num.times do
169
+ worker = @aws.instances.create(:image_id => @worker_image_id,
170
+ :key_pair => @key_pair,
171
+ :security_groups => @group,
172
+ :user_data => user_data,
173
+ :instance_type => @worker_instance_type)
174
+ worker.add_tag('Name', :value => "OpenStudio-Worker V#{OPENSTUDIO_VERSION}")
175
+ instances.push(worker)
176
+ end
177
+ sleep 5 while instances.any? { |instance| instance.status == :pending }
178
+ if instances.any? { |instance| instance.status != :running }
179
+ error(-1, "Worker status: Not running")
180
+ end
181
+
182
+ # todo: fix this - sometimes returns nil
183
+ processors = find_processors(@worker_instance_type)
184
+ #processors = send_command(instances[0].ip_address, 'nproc | tr -d "\n"')
185
+ #processors = 0 if processors.nil? # sometimes this returns nothing, so put in a default
186
+ instances.each { |instance| @workers.push(create_struct(instance, processors)) }
187
+ end
188
+
189
+ def upload_file(host, local_path, remote_path)
190
+ retries = 0
191
+ begin
192
+ Net::SCP.start(host, 'ubuntu', :key_data => [@private_key]) do |scp|
193
+ scp.upload! local_path, remote_path
194
+ end
195
+ rescue SystemCallError, Timeout::Error => e
196
+ # port 22 might not be available immediately after the instance finishes launching
197
+ return if retries == 5
198
+ retries += 1
199
+ sleep 1
200
+ retry
201
+ rescue
202
+ # Unknown upload error, retry
203
+ return if retries == 5
204
+ retries += 1
205
+ sleep 1
206
+ retry
207
+ end
208
+ end
209
+
210
+
211
+ def send_command(host, command)
212
+ #retries = 0
213
+ begin
214
+ output = ''
215
+ Net::SSH.start(host, 'ubuntu', :key_data => [@private_key]) do |ssh|
216
+ response = ssh.exec!(command)
217
+ output += response if !response.nil?
218
+ end
219
+ return output
220
+ rescue Net::SSH::HostKeyMismatch => e
221
+ e.remember_host!
222
+ # key mismatch, retry
223
+ #return if retries == 5
224
+ #retries += 1
225
+ sleep 1
226
+ retry
227
+ rescue Net::SSH::AuthenticationFailed
228
+ error(-1, "Incorrect private key")
229
+ rescue SystemCallError, Timeout::Error => e
230
+ # port 22 might not be available immediately after the instance finishes launching
231
+ #return if retries == 5
232
+ #retries += 1
233
+ sleep 1
234
+ retry
235
+ rescue Exception => e
236
+ puts e.message
237
+ puts e.backtrace.inspect
238
+ end
239
+ end
240
+
241
+ #======================= send command ======================#
242
+ # Send a command through SSH Shell to an instance.
243
+ # Need to pass instance object and the command as a string.
244
+ def shell_command(host, command)
245
+ begin
246
+ @logger.info("ssh_command #{command}")
247
+ Net::SSH.start(host, 'ubuntu', :key_data => [@private_key]) do |ssh|
248
+ channel = ssh.open_channel do |ch|
249
+ ch.exec "#{command}" do |ch, success|
250
+ raise "could not execute #{command}" unless success
251
+
252
+ # "on_data" is called when the process writes something to stdout
253
+ ch.on_data do |c, data|
254
+ #$stdout.print data
255
+ @logger.info("#{data.inspect}")
256
+ end
257
+
258
+ # "on_extended_data" is called when the process writes something to stderr
259
+ ch.on_extended_data do |c, type, data|
260
+ #$stderr.print data
261
+ @logger.info("#{data.inspect}")
262
+ end
263
+ end
264
+ end
265
+ end
266
+ rescue Net::SSH::HostKeyMismatch => e
267
+ e.remember_host!
268
+ @logger.info("key mismatch, retry")
269
+ sleep 1
270
+ retry
271
+ rescue SystemCallError, Timeout::Error => e
272
+ # port 22 might not be available immediately after the instance finishes launching
273
+ sleep 1
274
+ @logger.info("Not Yet")
275
+ retry
276
+ end
277
+ end
278
+
279
+ def wait_command(host, command)
280
+ begin
281
+ flag = 0
282
+ while flag == 0 do
283
+ @logger.info("wait_command #{command}")
284
+ Net::SSH.start(host, 'ubuntu', :key_data => [@private_key]) do |ssh|
285
+ channel = ssh.open_channel do |ch|
286
+ ch.exec "#{command}" do |ch, success|
287
+ raise "could not execute #{command}" unless success
288
+
289
+ # "on_data" is called when the process writes something to stdout
290
+ ch.on_data do |c, data|
291
+ @logger.info("#{data.inspect}")
292
+ if data.chomp == "true"
293
+ @logger.info("wait_command #{command} is true")
294
+ flag = 1
295
+ else
296
+ sleep 5
297
+ end
298
+ end
299
+
300
+ # "on_extended_data" is called when the process writes something to stderr
301
+ ch.on_extended_data do |c, type, data|
302
+ @logger.info("#{data.inspect}")
303
+ if data == "true"
304
+ @logger.info("wait_command #{command} is true")
305
+ flag = 1
306
+ else
307
+ sleep 5
308
+ end
309
+ end
310
+ end
311
+ end
312
+ end
313
+ end
314
+ rescue Net::SSH::HostKeyMismatch => e
315
+ e.remember_host!
316
+ @logger.info("key mismatch, retry")
317
+ sleep 1
318
+ retry
319
+ rescue SystemCallError, Timeout::Error => e
320
+ # port 22 might not be available immediately after the instance finishes launching
321
+ sleep 1
322
+ @logger.info("Not Yet")
323
+ retry
324
+ end
325
+ end
326
+
327
+ def download_file(host, remote_path, local_path)
328
+ retries = 0
329
+ begin
330
+ Net::SCP.start(host, 'ubuntu', :key_data => [@private_key]) do |scp|
331
+ scp.download! remote_path, local_path
332
+ end
333
+ rescue SystemCallError, Timeout::Error => e
334
+ # port 22 might not be available immediately after the instance finishes launching
335
+ return if retries == 5
336
+ retries += 1
337
+ sleep 1
338
+ retry
339
+ rescue
340
+ return if retries == 5
341
+ retries += 1
342
+ sleep 1
343
+ retry
344
+ end
345
+ end
346
+
347
+ begin
348
+ @logger = Logger.new("aws.log")
349
+ @logger.info("initialized")
350
+ case ARGV[4]
351
+ when 'describe_availability_zones'
352
+ resp = @aws.client.describe_availability_zones
353
+ puts resp.data.to_json
354
+ @logger.info("availability_zones #{resp.data.to_json}")
355
+ when 'total_instances'
356
+ resp = @aws.client.describe_instance_status
357
+ puts ({:total_instances => resp.data[:instance_status_set].length,
358
+ :region => ARGV[2]}.to_json)
359
+ when 'instance_status'
360
+ resp = nil
361
+ if ARGV.length < 6
362
+ resp = @aws.client.describe_instance_status
363
+ else
364
+ resp = @aws.client.describe_instance_status({:instance_ids => [@params['instance_id']]})
365
+ end
366
+ output = Hash.new
367
+ resp.data[:instance_status_set].each { |instance|
368
+ output[instance[:instance_id]] = instance[:instance_state][:name]
369
+ }
370
+ puts output.to_json
371
+ when 'launch_server'
372
+ if ARGV.length < 6
373
+ error(-1, 'Invalid number of args')
374
+ end
375
+
376
+ @timestamp = Time.now.to_i
377
+
378
+ # find if an existing openstudio-server-vX security group exists and use that
379
+ @group = @aws.security_groups.filter('group-name', 'openstudio-server-sg-v1').first
380
+ if @group.nil?
381
+ @group = @aws.security_groups.create('openstudio-server-sg-v1')
382
+ @group.allow_ping() # allow ping
383
+ @group.authorize_ingress(:tcp, 1..65535) # all traffic
384
+ end
385
+ @logger.info("server_group #{@group}")
386
+ @server_instance_type = @params['instance_type']
387
+
388
+ @key_pair = @aws.key_pairs.create("key-pair-#{@timestamp}")
389
+ @private_key = @key_pair.private_key
390
+
391
+ launch_server()
392
+
393
+ puts ({:timestamp => @timestamp,
394
+ :private_key => @private_key,
395
+ :server => {
396
+ :id => @server.id,
397
+ :ip => 'http://' + @server.ip,
398
+ :dns => @server.dns,
399
+ :procs => @server.procs
400
+ }}.to_json)
401
+ @logger.info("server info #{({:timestamp => @timestamp, :private_key => @private_key, :server => {:id => @server.id, :ip => @server.ip, :dns => @server.dns, :procs => @server.procs}}.to_json)}")
402
+ when 'launch_workers'
403
+ if ARGV.length < 6
404
+ error(-1, 'Invalid number of args')
405
+ end
406
+ if @params['num'] < 1
407
+ error(-1, 'Invalid number of worker nodes, must be greater than 0')
408
+ end
409
+
410
+ @workers = []
411
+ @timestamp = @params['timestamp']
412
+
413
+ # find if an existing openstudio-server-vX security group exists and use that
414
+ @group = @aws.security_groups.filter('group-name', 'openstudio-worker-sg-v1').first
415
+ if @group.nil?
416
+ @group = @aws.security_groups.create('openstudio-worker-sg-v1')
417
+ @group.allow_ping() # allow ping
418
+ @group.authorize_ingress(:tcp, 1..65535) # all traffic
419
+ end
420
+ @logger.info("worker_group #{@group}")
421
+ @key_pair = @aws.key_pairs.filter('key-name', "key-pair-#{@timestamp}").first
422
+ @private_key = File.read(@params['private_key'])
423
+ @worker_instance_type = @params['instance_type']
424
+ @server = @aws.instances[@params['server_id']]
425
+ error(-1, 'Server node does not exist') unless @server.exists?
426
+ @server = create_struct(@server, @params['server_procs'])
427
+
428
+ launch_workers(@params['num'], @server.ip)
429
+ #@workers.push(create_struct(@aws.instances['i-xxxxxxxx'], 1))
430
+ #processors = send_command(@workers[0].ip, 'nproc | tr -d "\n"')
431
+ #@workers[0].procs = processors
432
+
433
+ #wait for user_data to complete execution
434
+ @logger.info("server user_data")
435
+ wait_command(@server.ip, '[ -e /home/ubuntu/user_data_done ] && echo "true"')
436
+ @logger.info("worker user_data")
437
+ @workers.each { |worker| wait_command(worker.ip, '[ -e /home/ubuntu/user_data_done ] && echo "true"') }
438
+ #wait_command(@workers.first.ip, "[ -e /home/ubuntu/user_data_done ] && echo 'true'")
439
+
440
+
441
+ ips = "master|#{@server.ip}|#{@server.dns}|#{@server.procs}|ubuntu|ubuntu\n"
442
+ @workers.each { |worker| ips << "worker|#{worker.ip}|#{worker.dns}|#{worker.procs}|ubuntu|ubuntu|true\n" }
443
+ file = Tempfile.new('ip_addresses')
444
+ file.write(ips)
445
+ file.close
446
+ upload_file(@server.ip, file.path, 'ip_addresses')
447
+ file.unlink
448
+ @logger.info("ips #{ips}")
449
+ shell_command(@server.ip, 'chmod 664 /home/ubuntu/ip_addresses')
450
+ shell_command(@server.ip, '~/setup-ssh-keys.sh')
451
+ shell_command(@server.ip, '~/setup-ssh-worker-nodes.sh ip_addresses')
452
+
453
+ mongoid = File.read(File.expand_path(File.dirname(__FILE__))+'/mongoid.yml.template')
454
+ mongoid.gsub!(/SERVER_IP/, @server.ip)
455
+ file = Tempfile.new('mongoid.yml')
456
+ file.write(mongoid)
457
+ file.close
458
+ upload_file(@server.ip, file.path, '/mnt/openstudio/rails-models/mongoid.yml')
459
+ @workers.each { |worker| upload_file(worker.ip, file.path, '/mnt/openstudio/rails-models/mongoid.yml') }
460
+ file.unlink
461
+
462
+ # Does this command crash it?
463
+ shell_command(@server.ip, 'chmod 664 /mnt/openstudio/rails-models/mongoid.yml')
464
+ @workers.each { |worker| shell_command(worker.ip, 'chmod 664 /mnt/openstudio/rails-models/mongoid.yml') }
465
+
466
+ worker_json = []
467
+ @workers.each { |worker|
468
+ worker_json.push({
469
+ :id => worker.id,
470
+ :ip => 'http://' + worker.ip,
471
+ :dns => worker.dns,
472
+ :procs => worker.procs
473
+ })
474
+ }
475
+ puts ({:workers => worker_json}.to_json)
476
+ @logger.info("workers #{({:workers => worker_json}.to_json)}")
477
+ when 'terminate_session'
478
+ if ARGV.length < 6
479
+ error(-1, 'Invalid number of args')
480
+ end
481
+ instances = []
482
+
483
+ server = @aws.instances[@params['server_id']]
484
+ error(-1, "Server node #{@params['server_id']} does not exist") unless server.exists?
485
+
486
+ #@timestamp = @aws.client.describe_instances({:instance_ids=>[@params['server_id']]}).data[:instance_index][@params['server_id']][:key_name][9,10]
487
+ @timestamp = server.key_name[9, 10]
488
+
489
+ instances.push(server)
490
+ @params['worker_ids'].each { |worker_id|
491
+ worker = @aws.instances[worker_id]
492
+ error(-1, "Worker node #{worker_id} does not exist") unless worker.exists?
493
+ instances.push(worker)
494
+ }
495
+
496
+ instances.each { |instance|
497
+ instance.terminate
498
+ }
499
+ sleep 5 while instances.any? { |instance| instance.status != :terminated }
500
+
501
+ # When session is fully terminated, then delete the key pair
502
+ #@aws.client.delete_security_group({:group_name=>'openstudio-server-sg-v1'}"})
503
+ #@aws.client.delete_security_group({:group_name=>'openstudio-worker-sg-v1'}"})
504
+ @aws.client.delete_key_pair({:key_name => "key-pair-#{@timestamp}"})
505
+
506
+ when 'termination_status'
507
+ if ARGV.length < 6
508
+ error(-1, 'Invalid number of args')
509
+ end
510
+ notTerminated = 0
511
+
512
+ server = @aws.instances[@params['server_id']]
513
+ notTerminated += 1 if (server.exists? && server.status != :terminated)
514
+
515
+ @params['worker_ids'].each { |worker_id|
516
+ worker = @aws.instances[worker_id]
517
+ notTerminated += 1 if (worker.exists? && worker.status != :terminated)
518
+ }
519
+
520
+ puts ({:all_instances_terminated => (notTerminated == 0)}.to_json)
521
+
522
+ when 'session_uptime'
523
+ if ARGV.length < 6
524
+ error(-1, 'Invalid number of args')
525
+ end
526
+ server_id = @params['server_id']
527
+ #No need to call AWS, but we can
528
+ #minutes = (Time.now.to_i - @aws.client.describe_instances({:instance_ids=>[server_id]}).data[:instance_index][server_id][:launch_time].to_i)/60
529
+ minutes = (Time.now.to_i - @params['timestamp'].to_i)/60
530
+ puts ({:session_uptime => minutes}.to_json)
531
+
532
+ when 'estimated_charges'
533
+ endTime = Time.now.utc
534
+ startTime = endTime - 86400
535
+ resp = @aws.client.get_metric_statistics({:dimensions => [{:name => 'ServiceName', :value => 'AmazonEC2'}, {:name => 'Currency', :value => 'USD'}], :metric_name => 'EstimatedCharges', :namespace => 'AWS/Billing', :start_time => startTime.iso8601, :end_time => endTime.iso8601, :period => 300, :statistics => ['Maximum']})
536
+ error(-1, 'No Billing Data') if resp.data[:datapoints].length == 0
537
+ datapoints = resp.data[:datapoints]
538
+ datapoints.sort! { |a, b| a[:timestamp] <=> b[:timestamp] }
539
+ puts ({:estimated_charges => datapoints[-1][:maximum],
540
+ :timestamp => datapoints[-1][:timestamp].to_i}.to_json)
541
+
542
+ else
543
+ error(-1, "Unknown command: #{ARGV[4]} (#{ARGV[3]})")
544
+ end
545
+ #puts \"Status: #{resp.http_response.status}\"
546
+ rescue SystemExit => e
547
+ rescue Exception => e
548
+ if e.message == 'getaddrinfo: No such host is known. '
549
+ error(503, 'Offline')
550
+ elsif defined? e.http_response
551
+ error(e.http_response.status, e.code)
552
+ else
553
+ error(-1, "#{e}: #{e.backtrace}")
554
+ end
555
+
556
+ end
@@ -0,0 +1,46 @@
1
+ #!/bin/sh
2
+ # NOTE: Do not modify this file as it is copied over. Modify the source file and rerun rake import_files
3
+
4
+ # Change Host File Entries
5
+ ENTRY="localhost localhost master"
6
+ FILE=/etc/hosts
7
+ if grep -q "$ENTRY" $FILE; then
8
+ echo "entry already exists"
9
+ else
10
+ sudo sh -c "echo $ENTRY >> /etc/hosts"
11
+ fi
12
+
13
+ # copy all the setup scripts to the appropriate home directory
14
+ cp /data/launch-instance/setup* /home/ubuntu/
15
+ chmod 775 /home/ubuntu/setup*
16
+ chown ubuntu:ubuntu /home/ubuntu/setup*
17
+
18
+ # Set permissions on rails apps folders
19
+ sudo chmod 777 /var/www/rails/openstudio/public
20
+
21
+ # Force the generation of OpenStudio on the EBS mount and copy worker files
22
+ sudo rm -rf /mnt/openstudio
23
+ sudo mkdir -p /mnt/openstudio
24
+ sudo chmod -R 777 /mnt/openstudio
25
+ cp -rf /data/worker-nodes/* /mnt/openstudio/
26
+
27
+ # Unzip the worker files (including Mongoid models) and set permissions one last time
28
+ # Note that the 777 is redundant but needed until we actually deploy ACL
29
+ cd /mnt/openstudio/rails-models && unzip -o rails-models.zip
30
+ sudo chmod -R 777 /mnt/openstudio
31
+
32
+ # Force the generation of MongoDB dbpath on the EBS mount
33
+ sudo rm -rf /mnt/mongodb/data
34
+ sudo mkdir -p /mnt/mongodb/data
35
+ sudo chown mongodb:nogroup /mnt/mongodb/data
36
+ sudo service mongodb restart
37
+ sudo service delayed_job restart
38
+
39
+ # Add database indexes after it gets mounted for the first time
40
+ cd /var/www/rails/openstudio
41
+ sudo rake db:mongoid:create_indexes
42
+
43
+ #file flag the user_data has completed
44
+ cat /dev/null > /home/ubuntu/user_data_done
45
+
46
+
@@ -0,0 +1,39 @@
1
+ #!/bin/sh
2
+ # NOTE: Do not modify this file as it is copied over. Modify the source file and rerun rake import_files
3
+
4
+ # AWS Worker Bootstrap File
5
+
6
+ # Change Host File Entries
7
+ ENTRY="SERVER_IP SERVER_HOSTNAME SERVER_ALIAS"
8
+ FILE=/etc/hosts
9
+ if grep -q "$ENTRY" $FILE; then
10
+ echo "entry already exists"
11
+ else
12
+ sudo sh -c "echo $ENTRY >> /etc/hosts"
13
+ fi
14
+
15
+ # copy all the setup scripts to the appropriate home directory
16
+ cp /data/launch-instance/setup* /home/ubuntu/
17
+ chmod 775 /home/ubuntu/setup*
18
+ chown ubuntu:ubuntu /home/ubuntu/setup*
19
+
20
+ # Force the generation of OpenStudio on the EBS mount and copy worker files
21
+ sudo rm -rf /mnt/openstudio
22
+ sudo mkdir -p /mnt/openstudio
23
+ sudo chmod -R 777 /mnt/openstudio
24
+ cp -rf /data/worker-nodes/* /mnt/openstudio/
25
+
26
+ # Unzip the worker files (including Mongoid models) and set permissions one last time
27
+ # Note that the 777 is redundant but needed until we actually deploy ACL
28
+ cd /mnt/openstudio/rails-models && unzip -o rails-models.zip
29
+ sudo chmod -R 777 /mnt/openstudio
30
+
31
+ #turn off hyperthreading
32
+ for cpunum in $(
33
+ cat /sys/devices/system/cpu/cpu*/topology/thread_siblings_list |
34
+ cut -s -d, -f2- | tr ',' '\n' | sort -un); do
35
+ echo 0 > /sys/devices/system/cpu/cpu$cpunum/online
36
+ done
37
+
38
+ #file flag the user_data has completed
39
+ cat /dev/null > /home/ubuntu/user_data_done
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ describe OpenStudio::Aws do
4
+ context "create a new instance" do
5
+ before(:all) do
6
+ @aws = OpenStudio::Aws::Aws.new
7
+ end
8
+
9
+ it "should create a new instance" do
10
+ @aws.should_not be_nil
11
+ end
12
+
13
+ it "should ask the user to create a new config file" do
14
+ end
15
+
16
+ it "should create a server" do
17
+ # use the default instance type
18
+ #options = {instance_type: "t1.micro" }
19
+ options = {instance_type: "m1.small" }
20
+ @aws.create_server(options)
21
+ @aws.server_data[:server_dns].should_not be_nil
22
+ end
23
+
24
+ it "should create a 1 worker" do
25
+ #options = {instance_type: "t1.micro" }
26
+ options = {instance_type: "m1.small" }
27
+ #server_json[:instance_type] = "m2.4xlarge"
28
+ #server_json[:instance_type] = "m2.2xlarge"
29
+ #server_json[:instance_type] = "t1.micro"
30
+ @aws.create_workers(1, options)
31
+
32
+ @aws.worker_data[:workers].size.should eq(1)
33
+ @aws.worker_data[:workers][0][:dns].should_not be_nil
34
+ end
35
+ end
36
+
37
+ context "workers before server" do
38
+ before(:all) do
39
+ @aws = OpenStudio::Aws::Aws.new
40
+ end
41
+
42
+ it "should not create any workers" do
43
+
44
+ expect {@aws.create_workers(5)}.to raise_error
45
+ end
46
+
47
+ end
48
+
49
+ context "larger cluster" do
50
+
51
+ end
52
+
53
+ end
@@ -0,0 +1,8 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
3
+
4
+ require 'rspec'
5
+ require 'openstudio-aws'
6
+
7
+
8
+
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: openstudio-aws
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Nicholas Long
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-11-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: net-scp
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: aws-sdk
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 1.26.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 1.26.0
55
+ description: Custom classes for configuring clusters for OpenStudio & EnergyPlus analyses
56
+ email:
57
+ - Nicholas.Long@nrel.gov
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - lib/openstudio/aws/aws.rb
63
+ - lib/openstudio/aws/config.rb
64
+ - lib/openstudio/aws/send_data.rb
65
+ - lib/openstudio/aws/version.rb
66
+ - lib/openstudio/lib/mongoid.yml.template
67
+ - lib/openstudio/lib/os-aws.rb
68
+ - lib/openstudio/lib/server_script.sh
69
+ - lib/openstudio/lib/worker_script.sh.template
70
+ - lib/openstudio-aws.rb
71
+ - README.md
72
+ - Rakefile
73
+ - spec/openstudio-aws/aws_spec.rb
74
+ - spec/spec_helper.rb
75
+ homepage: http://openstudio.nrel.gov
76
+ licenses:
77
+ - LGPL
78
+ metadata: {}
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '2.0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - '>='
91
+ - !ruby/object:Gem::Version
92
+ version: 1.3.6
93
+ requirements: []
94
+ rubyforge_project:
95
+ rubygems_version: 2.0.2
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: Start AWS EC2 instances for running distributed OpenStudio-based analyses
99
+ test_files:
100
+ - spec/openstudio-aws/aws_spec.rb
101
+ - spec/spec_helper.rb