openstudio-aws 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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