appscale-tools 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +37 -0
- data/README +17 -0
- data/bin/appscale-add-keypair +15 -0
- data/bin/appscale-describe-instances +16 -0
- data/bin/appscale-remove-app +13 -0
- data/bin/appscale-reset-pwd +13 -0
- data/bin/appscale-run-instances +15 -0
- data/bin/appscale-terminate-instances +14 -0
- data/bin/appscale-upload-app +13 -0
- data/doc/AdvancedNode.html +163 -0
- data/doc/AppControllerClient.html +831 -0
- data/doc/AppEngineConfigException.html +165 -0
- data/doc/AppScaleException.html +165 -0
- data/doc/AppScaleTools.html +768 -0
- data/doc/BadCommandLineArgException.html +166 -0
- data/doc/BadConfigurationException.html +166 -0
- data/doc/CommonFunctions.html +2559 -0
- data/doc/EncryptionHelper.html +332 -0
- data/doc/GodInterface.html +443 -0
- data/doc/InfrastructureException.html +166 -0
- data/doc/Node.html +470 -0
- data/doc/NodeLayout.html +1297 -0
- data/doc/Object.html +539 -0
- data/doc/ParseArgs.html +268 -0
- data/doc/RemoteLogging.html +268 -0
- data/doc/SimpleNode.html +163 -0
- data/doc/UsageText.html +1204 -0
- data/doc/UserAppClient.html +993 -0
- data/doc/VMTools.html +1365 -0
- data/doc/bin/appscale-add-keypair.html +56 -0
- data/doc/bin/appscale-describe-instances.html +56 -0
- data/doc/bin/appscale-remove-app.html +56 -0
- data/doc/bin/appscale-reset-pwd.html +56 -0
- data/doc/bin/appscale-run-instances.html +56 -0
- data/doc/bin/appscale-terminate-instances.html +56 -0
- data/doc/bin/appscale-upload-app.html +56 -0
- data/doc/created.rid +21 -0
- data/doc/images/add.png +0 -0
- data/doc/images/brick.png +0 -0
- data/doc/images/brick_link.png +0 -0
- data/doc/images/bug.png +0 -0
- data/doc/images/bullet_black.png +0 -0
- data/doc/images/bullet_toggle_minus.png +0 -0
- data/doc/images/bullet_toggle_plus.png +0 -0
- data/doc/images/date.png +0 -0
- data/doc/images/delete.png +0 -0
- data/doc/images/find.png +0 -0
- data/doc/images/loadingAnimation.gif +0 -0
- data/doc/images/macFFBgHack.png +0 -0
- data/doc/images/package.png +0 -0
- data/doc/images/page_green.png +0 -0
- data/doc/images/page_white_text.png +0 -0
- data/doc/images/page_white_width.png +0 -0
- data/doc/images/plugin.png +0 -0
- data/doc/images/ruby.png +0 -0
- data/doc/images/tag_blue.png +0 -0
- data/doc/images/tag_green.png +0 -0
- data/doc/images/transparent.png +0 -0
- data/doc/images/wrench.png +0 -0
- data/doc/images/wrench_orange.png +0 -0
- data/doc/images/zoom.png +0 -0
- data/doc/index.html +116 -0
- data/doc/js/darkfish.js +153 -0
- data/doc/js/jquery.js +18 -0
- data/doc/js/navigation.js +142 -0
- data/doc/js/quicksearch.js +114 -0
- data/doc/js/search.js +94 -0
- data/doc/js/search_index.js +1 -0
- data/doc/js/searcher.js +228 -0
- data/doc/js/thickbox-compressed.js +10 -0
- data/doc/lib/app_controller_client_rb.html +60 -0
- data/doc/lib/appscale_tools_rb.html +88 -0
- data/doc/lib/common_functions_rb.html +78 -0
- data/doc/lib/custom_exceptions_rb.html +54 -0
- data/doc/lib/encryption_helper_rb.html +60 -0
- data/doc/lib/godinterface_rb.html +52 -0
- data/doc/lib/node_layout_rb.html +55 -0
- data/doc/lib/parse_args_rb.html +58 -0
- data/doc/lib/remote_log_rb.html +58 -0
- data/doc/lib/sshcopyid.html +174 -0
- data/doc/lib/usage_text_rb.html +58 -0
- data/doc/lib/user_app_client_rb.html +62 -0
- data/doc/lib/vm_tools_rb.html +62 -0
- data/doc/table_of_contents.html +496 -0
- data/lib/app_controller_client.rb +181 -0
- data/lib/appscale_tools.rb +403 -0
- data/lib/common_functions.rb +1467 -0
- data/lib/custom_exceptions.rb +25 -0
- data/lib/encryption_helper.rb +86 -0
- data/lib/godinterface.rb +152 -0
- data/lib/node_layout.rb +665 -0
- data/lib/parse_args.rb +415 -0
- data/lib/remote_log.rb +46 -0
- data/lib/sshcopyid +65 -0
- data/lib/usage_text.rb +144 -0
- data/lib/user_app_client.rb +245 -0
- data/lib/vm_tools.rb +549 -0
- data/test/tc_app_controller_client.rb +10 -0
- data/test/tc_appscale_add_keypair.rb +44 -0
- data/test/tc_appscale_describe_instances.rb +69 -0
- data/test/tc_appscale_remove_app.rb +128 -0
- data/test/tc_appscale_reset_pwd.rb +156 -0
- data/test/tc_appscale_run_instances.rb +48 -0
- data/test/tc_appscale_terminate_instances.rb +104 -0
- data/test/tc_appscale_upload_app.rb +166 -0
- data/test/tc_common_functions.rb +56 -0
- data/test/tc_encryption_helper.rb +10 -0
- data/test/tc_god_interface.rb +10 -0
- data/test/tc_node_layout.rb +93 -0
- data/test/tc_parse_args.rb +160 -0
- data/test/tc_user_app_client.rb +10 -0
- data/test/tc_vm_tools.rb +10 -0
- data/test/ts_all.rb +20 -0
- metadata +211 -0
@@ -0,0 +1,1467 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
# Programmer: Chris Bunch
|
3
|
+
|
4
|
+
|
5
|
+
require 'digest/sha1'
|
6
|
+
require 'net/http'
|
7
|
+
require 'openssl'
|
8
|
+
require 'open-uri'
|
9
|
+
require 'socket'
|
10
|
+
require 'timeout'
|
11
|
+
require 'yaml'
|
12
|
+
|
13
|
+
|
14
|
+
require 'app_controller_client'
|
15
|
+
require 'custom_exceptions'
|
16
|
+
require 'user_app_client'
|
17
|
+
|
18
|
+
|
19
|
+
require 'rubygems'
|
20
|
+
require 'json'
|
21
|
+
|
22
|
+
|
23
|
+
NO_SSH_KEY_FOUND = "No SSH key was found that could be used to log in to " +
|
24
|
+
"your machine."
|
25
|
+
MALFORMED_YAML = "The yaml file you provided was malformed. Please correct " +
|
26
|
+
"any errors in it and try again."
|
27
|
+
NO_CONFIG_FILE = "We could not find a valid app.yaml or web.xml file in " +
|
28
|
+
"your application."
|
29
|
+
|
30
|
+
|
31
|
+
# The username and password that will be used as the cloud administrator's
|
32
|
+
# credentials if the --test flag are used, which should only be used when
|
33
|
+
# developing AppScale (and not in production environments).
|
34
|
+
DEFAULT_USERNAME = "a@a.a"
|
35
|
+
DEFAULT_PASSWORD = "aaaaaa"
|
36
|
+
|
37
|
+
|
38
|
+
MAX_FILE_SIZE = 1000000
|
39
|
+
|
40
|
+
|
41
|
+
EMAIL_REGEX = /\A[[:print:]]+@[[:print:]]+\.[[:print:]]+\Z/
|
42
|
+
PASSWORD_REGEX = /\A[[:print:]]{6,}\Z/
|
43
|
+
IP_REGEX = /\d+\.\d+\.\d+\.\d+/
|
44
|
+
FQDN_REGEX = /[\w\d\.\-]+/
|
45
|
+
IP_OR_FQDN = /#{IP_REGEX}|#{FQDN_REGEX}/
|
46
|
+
|
47
|
+
|
48
|
+
CLOUDY_CREDS = ["ec2_access_key", "ec2_secret_key",
|
49
|
+
"aws_access_key_id", "aws_secret_access_key",
|
50
|
+
"SIMPLEDB_ACCESS_KEY", "SIMPLEDB_SECRET_KEY"]
|
51
|
+
|
52
|
+
|
53
|
+
VER_NUM = "1.5"
|
54
|
+
AS_VERSION = "AppScale Tools, Version #{VER_NUM}, http://appscale.cs.ucsb.edu"
|
55
|
+
|
56
|
+
|
57
|
+
PYTHON_CONFIG = "app.yaml"
|
58
|
+
JAVA_CONFIG = "war/WEB-INF/appengine-web.xml"
|
59
|
+
|
60
|
+
|
61
|
+
# When we try to ssh to other machines, we don't want to be asked for a password
|
62
|
+
# (since we always should have the right SSH key present), and we don't want to
|
63
|
+
# be asked to confirm the host's fingerprint, so set the options for that here.
|
64
|
+
SSH_OPTIONS = "-o NumberOfPasswordPrompts=0 -o StrictHostkeyChecking=no"
|
65
|
+
|
66
|
+
|
67
|
+
# A list of the databases that AppScale nodes can run, and a list of the cloud
|
68
|
+
# infrastructures that we can run over.
|
69
|
+
VALID_TABLE_TYPES = ["hbase", "hypertable", "mysql", "cassandra", "voldemort"] +
|
70
|
+
["mongodb", "memcachedb", "scalaris", "simpledb", "redisdb"]
|
71
|
+
VALID_CLOUD_TYPES = ["ec2", "euca", "hybrid"]
|
72
|
+
|
73
|
+
|
74
|
+
# Some operations use an infinite timeout, and while -1 or 1.0/0 work in older
|
75
|
+
# versions of Ruby 1.8.7 (default on Ubuntu Lucid and before), they don't work
|
76
|
+
# in newer versions of Ruby 1.8.7 and Ruby 1.9. Instead, just use a large
|
77
|
+
# number, which will work on both.
|
78
|
+
INFINITY = 1000000
|
79
|
+
|
80
|
+
|
81
|
+
module CommonFunctions
|
82
|
+
|
83
|
+
|
84
|
+
# A convenience function that can be used to write a string to a file.
|
85
|
+
def self.write_file(location, contents)
|
86
|
+
File.open(location, "w+") { |file| file.write(contents) }
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
# A convenience function that returns a file's contents as a string.
|
91
|
+
def self.read_file(location, chomp=true)
|
92
|
+
file = File.open(location) { |f| f.read }
|
93
|
+
if chomp
|
94
|
+
return file.chomp
|
95
|
+
else
|
96
|
+
return file
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
# cgb: added in shell function for backticks so that we can unit test it
|
102
|
+
# Flexmock doesn't like backticks since its name is non-alphanumeric
|
103
|
+
# e.g., its name is Kernel:`
|
104
|
+
def self.shell(command)
|
105
|
+
`#{command}`
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
# Uses rsync to copy over a copy of the AppScale main codebase (e.g., not the
|
110
|
+
# AppScale Tools) from this machine to a remote machine.
|
111
|
+
# TODO(cgb): This function doesn't copy files in the main directory, like the
|
112
|
+
# firewall rules. It should be changed accordingly.
|
113
|
+
def self.rsync_files(dest_ip, ssh_key, local)
|
114
|
+
local = File.expand_path(local)
|
115
|
+
controller = "#{local}/AppController"
|
116
|
+
server = "#{local}/AppServer"
|
117
|
+
loadbalancer = "#{local}/AppLoadBalancer"
|
118
|
+
monitoring = "#{local}/AppMonitoring"
|
119
|
+
appdb = "#{local}/AppDB"
|
120
|
+
neptune = "#{local}/Neptune"
|
121
|
+
loki = "#{local}/Loki"
|
122
|
+
iaas_manager = "#{local}/InfrastructureManager"
|
123
|
+
|
124
|
+
if !File.exists?(controller)
|
125
|
+
raise BadConfigurationException.new("The location you " +
|
126
|
+
"specified to rsync from, #{local}, doesn't exist or contain " +
|
127
|
+
"AppScale data.")
|
128
|
+
end
|
129
|
+
|
130
|
+
self.shell("rsync -e 'ssh -i #{ssh_key}' -arv #{controller}/* root@#{dest_ip}:/root/appscale/AppController")
|
131
|
+
self.shell("rsync -e 'ssh -i #{ssh_key}' -arv #{server}/* root@#{dest_ip}:/root/appscale/AppServer")
|
132
|
+
self.shell("rsync -e 'ssh -i #{ssh_key}' -arv #{loadbalancer}/* root@#{dest_ip}:/root/appscale/AppLoadBalancer")
|
133
|
+
self.shell("rsync -e 'ssh -i #{ssh_key}' -arv #{monitoring}/* root@#{dest_ip}:/root/appscale/AppMonitoring")
|
134
|
+
self.shell("rsync -e 'ssh -i #{ssh_key}' -arv --exclude='logs/*' --exclude='hadoop-*' --exclude='hbase/hbase-*' --exclude='voldemort/voldemort/*' --exclude='cassandra/cassandra/*' #{appdb}/* root@#{dest_ip}:/root/appscale/AppDB")
|
135
|
+
self.shell("rsync -e 'ssh -i #{ssh_key}' -arv #{neptune}/* root@#{dest_ip}:/root/appscale/Neptune")
|
136
|
+
#self.shell("rsync -e 'ssh -i #{ssh_key}' -arv #{loki}/* root@#{dest_ip}:/root/appscale/Loki")
|
137
|
+
self.shell("rsync -e 'ssh -i #{ssh_key}' -arv #{iaas_manager}/* root@#{dest_ip}:/root/appscale/InfrastructureManager")
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
# This function tries to contact a node in the AppScale deployment to
|
142
|
+
# get the most up-to-date information on the current state of the
|
143
|
+
# deployment (as the number of nodes could change, or the roles that
|
144
|
+
# nodes have taken on).
|
145
|
+
def self.update_locations_file(keyname, ips=nil)
|
146
|
+
secret = self.get_secret_key(keyname)
|
147
|
+
|
148
|
+
# Don't worry about prioritizing the Shadow over any other nodes,
|
149
|
+
# as all nodes should be checking ZooKeeper and keeping themselves
|
150
|
+
# up-to-date already.
|
151
|
+
new_role_info = nil
|
152
|
+
|
153
|
+
if ips
|
154
|
+
all_ips = ips
|
155
|
+
else
|
156
|
+
all_ips = self.get_all_public_ips(keyname)
|
157
|
+
end
|
158
|
+
|
159
|
+
all_ips.each { |ip|
|
160
|
+
begin
|
161
|
+
acc = AppControllerClient.new(ip, secret)
|
162
|
+
new_role_info = acc.get_role_info()
|
163
|
+
break
|
164
|
+
rescue Exception
|
165
|
+
Kernel.puts("Couldn't contact AppController at #{ip}, skipping...")
|
166
|
+
end
|
167
|
+
}
|
168
|
+
|
169
|
+
if new_role_info.nil?
|
170
|
+
abort("Couldn't contact any AppControllers - is AppScale running?")
|
171
|
+
end
|
172
|
+
|
173
|
+
CommonFunctions.write_nodes_json(new_role_info, keyname)
|
174
|
+
end
|
175
|
+
|
176
|
+
|
177
|
+
def self.get_login_ip(head_node_ip, secret_key)
|
178
|
+
acc = AppControllerClient.new(head_node_ip, secret_key)
|
179
|
+
all_nodes = acc.get_all_public_ips()
|
180
|
+
|
181
|
+
all_nodes.each { |node|
|
182
|
+
acc_new = AppControllerClient.new(node, secret_key)
|
183
|
+
roles = acc_new.status()
|
184
|
+
return node if roles.match(/Is currently:(.*)login/)
|
185
|
+
}
|
186
|
+
|
187
|
+
raise AppScaleException.new("Unable to find login ip address!")
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
def self.clear_app(app_path, force=false)
|
192
|
+
return if !File.exists?(app_path)
|
193
|
+
return if app_path !~ /\A\/tmp/ and !force
|
194
|
+
path_to_remove = File.dirname(app_path)
|
195
|
+
FileUtils.rm_f(path_to_remove)
|
196
|
+
end
|
197
|
+
|
198
|
+
|
199
|
+
def self.validate_app_name(app_name, database)
|
200
|
+
disallowed = ["none", "auth", "login", "new_user", "load_balancer"]
|
201
|
+
if disallowed.include?(app_name)
|
202
|
+
raise AppEngineConfigException.new("App cannot be called " +
|
203
|
+
"'#{not_allowed}' - this is a reserved name.")
|
204
|
+
end
|
205
|
+
|
206
|
+
if app_name =~ /[^[a-z]-]/
|
207
|
+
raise AppEngineConfigException.new("App name can only contain " +
|
208
|
+
"numeric and lowercase alphabetical characters.")
|
209
|
+
end
|
210
|
+
|
211
|
+
if app_name.include?("-") and database == "hypertable"
|
212
|
+
raise AppEngineConfigException.new("App name cannot contain " +
|
213
|
+
"dashes when Hypertable is the underlying database.")
|
214
|
+
end
|
215
|
+
|
216
|
+
return app_name
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
def self.get_ips_from_yaml(ips)
|
221
|
+
return "using_tools" if ips.nil?
|
222
|
+
|
223
|
+
ips_to_use = []
|
224
|
+
if !ips[:servers].nil?
|
225
|
+
ips[:servers].each { |ip|
|
226
|
+
if ip =~ IP_REGEX
|
227
|
+
ips_to_use << ip
|
228
|
+
else
|
229
|
+
ips_to_use << CommonFunctions.convert_fqdn_to_ip(ip)
|
230
|
+
end
|
231
|
+
}
|
232
|
+
ips_to_use = ips_to_use.join(":")
|
233
|
+
end
|
234
|
+
|
235
|
+
return ips_to_use
|
236
|
+
end
|
237
|
+
|
238
|
+
|
239
|
+
def self.get_credentials(testing)
|
240
|
+
if testing
|
241
|
+
return DEFAULT_USERNAME, DEFAULT_PASSWORD
|
242
|
+
else
|
243
|
+
return CommonFunctions.get_email, CommonFunctions.get_password
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
|
248
|
+
def self.create_user(user, test, head_node_ip, secret_key, uac, pass=nil)
|
249
|
+
if pass
|
250
|
+
pass = pass # TODO - can we just remove this?
|
251
|
+
elsif test
|
252
|
+
pass = "aaaaaa"
|
253
|
+
else
|
254
|
+
pass = CommonFunctions.get_password
|
255
|
+
end
|
256
|
+
|
257
|
+
encrypted_pass = CommonFunctions.encrypt_password(user, pass)
|
258
|
+
uac.commit_new_user(user, encrypted_pass)
|
259
|
+
|
260
|
+
login_ip = CommonFunctions.get_login_ip(head_node_ip, secret_key)
|
261
|
+
|
262
|
+
# create xmpp account
|
263
|
+
# for user a@a.a, this translates to a@login_ip
|
264
|
+
|
265
|
+
pre = user.scan(/\A(.*)@/).flatten.to_s
|
266
|
+
xmpp_user = "#{pre}@#{login_ip}"
|
267
|
+
xmpp_pass = CommonFunctions.encrypt_password(xmpp_user, pass)
|
268
|
+
uac.commit_new_user(xmpp_user, xmpp_pass)
|
269
|
+
Kernel.puts "Your XMPP username is #{xmpp_user}"
|
270
|
+
end
|
271
|
+
|
272
|
+
|
273
|
+
def self.scp_app_to_ip(app_name, user, language, keyname, head_node_ip,
|
274
|
+
file_location, uac)
|
275
|
+
|
276
|
+
Kernel.puts "Uploading #{app_name}..."
|
277
|
+
uac.commit_new_app_name(user, app_name, language)
|
278
|
+
|
279
|
+
local_file_path = File.expand_path(file_location)
|
280
|
+
|
281
|
+
app_dir = "/var/apps/#{app_name}/app"
|
282
|
+
remote_file_path = "#{app_dir}/#{app_name}.tar.gz"
|
283
|
+
make_app_dir = "mkdir -p #{app_dir}"
|
284
|
+
true_key = File.expand_path("~/.appscale/#{keyname}.key")
|
285
|
+
|
286
|
+
Kernel.puts "Creating remote directory to copy app into"
|
287
|
+
CommonFunctions.run_remote_command(head_node_ip,
|
288
|
+
make_app_dir, true_key, false)
|
289
|
+
Kernel.sleep(1)
|
290
|
+
Kernel.puts "Copying over app"
|
291
|
+
CommonFunctions.scp_file(local_file_path, remote_file_path,
|
292
|
+
head_node_ip, true_key)
|
293
|
+
|
294
|
+
return remote_file_path
|
295
|
+
end
|
296
|
+
|
297
|
+
|
298
|
+
def self.update_appcontroller(head_node_ip, secret, app_name,
|
299
|
+
remote_file_path)
|
300
|
+
|
301
|
+
acc = AppControllerClient.new(head_node_ip, secret)
|
302
|
+
acc.done_uploading(app_name, remote_file_path)
|
303
|
+
Kernel.puts "Updating AppController"
|
304
|
+
acc.update([app_name])
|
305
|
+
end
|
306
|
+
|
307
|
+
def self.wait_for_nodes_to_load(head_node_ip, secret)
|
308
|
+
head_acc = AppControllerClient.new(head_node_ip, secret)
|
309
|
+
|
310
|
+
Kernel.puts "Please wait for AppScale to finish starting up."
|
311
|
+
all_ips = head_acc.get_all_public_ips()
|
312
|
+
loop {
|
313
|
+
done_starting = true
|
314
|
+
all_ips.each { |ip|
|
315
|
+
acc = AppControllerClient.new(ip, secret)
|
316
|
+
if !acc.is_done_initializing?
|
317
|
+
done_starting = false
|
318
|
+
end
|
319
|
+
}
|
320
|
+
break if done_starting
|
321
|
+
Kernel.sleep(5)
|
322
|
+
}
|
323
|
+
end
|
324
|
+
|
325
|
+
def self.wait_for_app_to_start(head_node_ip, secret, app_name)
|
326
|
+
acc = AppControllerClient.new(head_node_ip, secret)
|
327
|
+
|
328
|
+
Kernel.puts "Please wait for your app to start up."
|
329
|
+
loop {
|
330
|
+
break if acc.app_is_running?(app_name)
|
331
|
+
Kernel.sleep(5)
|
332
|
+
}
|
333
|
+
|
334
|
+
url_suffix = "/apps/#{app_name}"
|
335
|
+
url = "http://#{head_node_ip}#{url_suffix}"
|
336
|
+
Kernel.puts "\nYour app can be reached at the following URL: #{url}"
|
337
|
+
end
|
338
|
+
|
339
|
+
|
340
|
+
def self.wait_until_redirect(host, url_suffix)
|
341
|
+
uri = "http://#{host}#{url_suffix}"
|
342
|
+
loop {
|
343
|
+
response = ""
|
344
|
+
begin
|
345
|
+
response = Net::HTTP.get_response(URI.parse(uri))
|
346
|
+
rescue Errno::ECONNREFUSED, EOFError
|
347
|
+
Kernel.sleep(1)
|
348
|
+
next
|
349
|
+
rescue Exception => e
|
350
|
+
raise e
|
351
|
+
end
|
352
|
+
|
353
|
+
return if response['location'] != "http://#{host}/status"
|
354
|
+
Kernel.sleep(1)
|
355
|
+
}
|
356
|
+
end
|
357
|
+
|
358
|
+
|
359
|
+
def self.user_has_cmd?(command)
|
360
|
+
output = CommonFunctions.shell("which #{command}")
|
361
|
+
if output.empty?
|
362
|
+
return false
|
363
|
+
else
|
364
|
+
return true
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
|
369
|
+
def self.require_commands(commands)
|
370
|
+
commands.each { |cmd|
|
371
|
+
if !CommonFunctions.user_has_cmd?(cmd)
|
372
|
+
raise BadConfigurationException.new("You do not have the '#{cmd}' " +
|
373
|
+
"command in your PATH. Please ensure that it is in your path and " +
|
374
|
+
"try again.")
|
375
|
+
end
|
376
|
+
}
|
377
|
+
end
|
378
|
+
|
379
|
+
|
380
|
+
def self.convert_fqdn_to_ip(host)
|
381
|
+
nslookup = CommonFunctions.shell("nslookup #{host}")
|
382
|
+
ip = nslookup.scan(/#{host}\nAddress:\s+(#{IP_REGEX})/).flatten.to_s
|
383
|
+
if ip.nil? or ip.empty?
|
384
|
+
raise AppScaleException.new("Couldn't convert #{host} to an IP " +
|
385
|
+
"address. Result of nslookup was \n#{nslookup}")
|
386
|
+
end
|
387
|
+
return ip
|
388
|
+
end
|
389
|
+
|
390
|
+
|
391
|
+
def self.encrypt_password(user, pass)
|
392
|
+
Digest::SHA1.hexdigest(user + pass)
|
393
|
+
end
|
394
|
+
|
395
|
+
|
396
|
+
def self.sleep_until_port_is_open(ip, port, use_ssl=false)
|
397
|
+
loop {
|
398
|
+
return if CommonFunctions.is_port_open?(ip, port, use_ssl)
|
399
|
+
Kernel.sleep(1)
|
400
|
+
}
|
401
|
+
end
|
402
|
+
|
403
|
+
|
404
|
+
def self.sleep_until_port_is_closed(ip, port, use_ssl=false)
|
405
|
+
loop {
|
406
|
+
return unless CommonFunctions.is_port_open?(ip, port, use_ssl)
|
407
|
+
Kernel.sleep(1)
|
408
|
+
}
|
409
|
+
end
|
410
|
+
|
411
|
+
|
412
|
+
def self.is_port_open?(ip, port, use_ssl=false)
|
413
|
+
begin
|
414
|
+
Timeout::timeout(1) do
|
415
|
+
begin
|
416
|
+
sock = TCPSocket.new(ip, port)
|
417
|
+
if use_ssl
|
418
|
+
ssl_context = OpenSSL::SSL::SSLContext.new()
|
419
|
+
unless ssl_context.verify_mode
|
420
|
+
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
421
|
+
end
|
422
|
+
sslsocket = OpenSSL::SSL::SSLSocket.new(sock, ssl_context)
|
423
|
+
sslsocket.sync_close = true
|
424
|
+
sslsocket.connect
|
425
|
+
end
|
426
|
+
sock.close
|
427
|
+
return true
|
428
|
+
rescue Exception
|
429
|
+
return false
|
430
|
+
end
|
431
|
+
end
|
432
|
+
rescue Timeout::Error
|
433
|
+
end
|
434
|
+
|
435
|
+
return false
|
436
|
+
end
|
437
|
+
|
438
|
+
|
439
|
+
def self.run_remote_command(ip, command, public_key_loc, want_output)
|
440
|
+
if public_key_loc.class == Array
|
441
|
+
public_key_loc.each { |key|
|
442
|
+
key = File.expand_path(key)
|
443
|
+
}
|
444
|
+
|
445
|
+
remote_cmd = "ssh -i #{public_key_loc.join(' -i ')} #{SSH_OPTIONS} 2>&1 root@#{ip} '#{command}"
|
446
|
+
else
|
447
|
+
public_key_loc = File.expand_path(public_key_loc)
|
448
|
+
remote_cmd = "ssh -i #{public_key_loc} #{SSH_OPTIONS} root@#{ip} '#{command} "
|
449
|
+
end
|
450
|
+
|
451
|
+
if want_output
|
452
|
+
remote_cmd << "> /tmp/#{ip}.log 2>&1 &' &"
|
453
|
+
else
|
454
|
+
remote_cmd << "> /dev/null 2>&1 &' &"
|
455
|
+
end
|
456
|
+
|
457
|
+
Kernel.system remote_cmd
|
458
|
+
return remote_cmd
|
459
|
+
end
|
460
|
+
|
461
|
+
|
462
|
+
def self.get_remote_appscale_home(ip, key)
|
463
|
+
cat = "cat /etc/appscale/home"
|
464
|
+
remote_cmd = "ssh -i #{key} #{SSH_OPTIONS} 2>&1 root@#{ip} '#{cat}'"
|
465
|
+
possible_home = CommonFunctions.shell("#{remote_cmd}").chomp
|
466
|
+
if possible_home.nil? or possible_home.empty?
|
467
|
+
return "/root/appscale/"
|
468
|
+
else
|
469
|
+
return possible_home
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
|
474
|
+
def self.start_head_node(options, node_layout, apps_to_start)
|
475
|
+
# since we changed the key's name sometimes it works with storing the key
|
476
|
+
# in ssh.key and sometimes it needs to be in keyname.key{.private}
|
477
|
+
# always do both just in case
|
478
|
+
|
479
|
+
# TODO: check here to make sure that if hybrid, the keyname is free
|
480
|
+
# in all clouds specified
|
481
|
+
|
482
|
+
keyname = options['keyname']
|
483
|
+
named_key_loc = "~/.appscale/#{keyname}.key"
|
484
|
+
named_backup_key_loc = "~/.appscale/#{keyname}.private"
|
485
|
+
ssh_key_location = named_key_loc
|
486
|
+
ssh_keys = [ssh_key_location, named_key_loc, named_backup_key_loc]
|
487
|
+
secret_key, secret_key_location = EncryptionHelper.generate_secret_key(keyname)
|
488
|
+
self.verbose("New secret key is #{CommonFunctions.obscure_string(secret_key)}", options['verbose'])
|
489
|
+
|
490
|
+
# TODO: serialize via json instead of this hacky way
|
491
|
+
ips_hash = node_layout.to_hash
|
492
|
+
ips_to_use = ips_hash.map { |node,roles| "#{node}--#{roles}" }.join("..")
|
493
|
+
|
494
|
+
head_node = node_layout.head_node
|
495
|
+
infrastructure = options['infrastructure']
|
496
|
+
if infrastructure == "hybrid"
|
497
|
+
head_node_infra = VMTools.lookup_cloud_env(head_node.cloud)
|
498
|
+
VMTools.set_hybrid_creds(node_layout)
|
499
|
+
machine = VMTools.get_hybrid_machine(head_node_infra, "1")
|
500
|
+
else
|
501
|
+
head_node_infra = infrastructure
|
502
|
+
machine = options['machine']
|
503
|
+
end
|
504
|
+
|
505
|
+
locations = VMTools.spawn_head_node(head_node, head_node_infra, keyname,
|
506
|
+
ssh_key_location, ssh_keys, options['force'], machine,
|
507
|
+
options['instance_type'], options['group'], options['verbose'])
|
508
|
+
|
509
|
+
head_node_ip = locations.split(":")[0]
|
510
|
+
instance_id = locations.scan(/i-\w+/).flatten.to_s
|
511
|
+
locations = [locations]
|
512
|
+
|
513
|
+
true_key = CommonFunctions.find_real_ssh_key(ssh_keys, head_node_ip)
|
514
|
+
|
515
|
+
self.verbose("Log in to your head node: ssh -i #{true_key} " +
|
516
|
+
"root@#{head_node_ip}", options['verbose'])
|
517
|
+
|
518
|
+
CommonFunctions.ensure_image_is_appscale(head_node_ip, true_key)
|
519
|
+
CommonFunctions.ensure_db_is_supported(head_node_ip, options['table'],
|
520
|
+
true_key)
|
521
|
+
|
522
|
+
scp = options['scp']
|
523
|
+
if scp
|
524
|
+
Kernel.puts "Copying over local copy of AppScale from #{scp}"
|
525
|
+
CommonFunctions.rsync_files(head_node_ip, true_key, scp)
|
526
|
+
end
|
527
|
+
|
528
|
+
keypath = true_key.scan(/([\d|\w|\.]+)\Z/).flatten.to_s
|
529
|
+
remote_key_location = "/root/.appscale/#{keyname}.key"
|
530
|
+
CommonFunctions.scp_file(true_key, remote_key_location, head_node_ip, true_key)
|
531
|
+
|
532
|
+
creds = CommonFunctions.generate_appscale_credentials(options, node_layout,
|
533
|
+
head_node_ip, ips_to_use, true_key)
|
534
|
+
self.verbose(CommonFunctions.obscure_creds(creds).inspect, options['verbose'])
|
535
|
+
|
536
|
+
Kernel.puts "Head node successfully created at #{head_node_ip}. It is now " +
|
537
|
+
"starting up #{options['table']} via the command line arguments given."
|
538
|
+
|
539
|
+
RemoteLogging.remote_post(options['max_images'], options['table'],
|
540
|
+
infrastructure, "started headnode", "success")
|
541
|
+
|
542
|
+
Kernel.sleep(10) # sometimes this helps out with ec2 / euca deployments
|
543
|
+
# gives them an extra moment to come up and accept scp requests
|
544
|
+
|
545
|
+
CommonFunctions.copy_keys(secret_key_location, head_node_ip, true_key,
|
546
|
+
options)
|
547
|
+
|
548
|
+
CommonFunctions.start_appcontroller(head_node_ip, true_key,
|
549
|
+
options['verbose'])
|
550
|
+
|
551
|
+
acc = AppControllerClient.new(head_node_ip, secret_key)
|
552
|
+
creds = creds.to_a.flatten
|
553
|
+
acc.set_parameters(locations, creds, apps_to_start)
|
554
|
+
|
555
|
+
return {:acc => acc, :head_node_ip => head_node_ip,
|
556
|
+
:instance_id => instance_id, :true_key => true_key,
|
557
|
+
:secret_key => secret_key}
|
558
|
+
end
|
559
|
+
|
560
|
+
|
561
|
+
def self.find_real_ssh_key(ssh_keys, host)
|
562
|
+
ssh_keys.each { |key|
|
563
|
+
key = File.expand_path(key)
|
564
|
+
return_value = CommonFunctions.shell("ssh -i #{key} #{SSH_OPTIONS} 2>&1 root@#{host} 'touch /tmp/foo'; echo $? ").chomp
|
565
|
+
if return_value == "0"
|
566
|
+
return key
|
567
|
+
end
|
568
|
+
}
|
569
|
+
|
570
|
+
raise AppScaleException.new(NO_SSH_KEY_FOUND)
|
571
|
+
end
|
572
|
+
|
573
|
+
|
574
|
+
def self.scp_file(local_file_loc, remote_file_loc, target_ip, public_key_loc)
|
575
|
+
cmd = ""
|
576
|
+
local_file_loc = File.expand_path(local_file_loc)
|
577
|
+
retval_file = File.expand_path("~/.appscale/retval-#{rand()}")
|
578
|
+
|
579
|
+
if public_key_loc.class == Array
|
580
|
+
public_key_loc.each { |key|
|
581
|
+
key = File.expand_path(key)
|
582
|
+
}
|
583
|
+
|
584
|
+
cmd = "scp -i #{public_key_loc.join(' -i ')} #{SSH_OPTIONS} 2>&1 #{local_file_loc} root@#{target_ip}:#{remote_file_loc}"
|
585
|
+
else
|
586
|
+
public_key_loc = File.expand_path(public_key_loc)
|
587
|
+
cmd = "scp -i #{public_key_loc} #{SSH_OPTIONS} 2>&1 #{local_file_loc} root@#{target_ip}:#{remote_file_loc}"
|
588
|
+
end
|
589
|
+
|
590
|
+
cmd << "; echo $? > #{retval_file}"
|
591
|
+
|
592
|
+
FileUtils.rm_f(retval_file)
|
593
|
+
|
594
|
+
begin
|
595
|
+
Timeout::timeout(INFINITY) { CommonFunctions.shell("#{cmd}") }
|
596
|
+
rescue Timeout::Error
|
597
|
+
raise AppScaleException.new("Remotely copying over files failed. Is " +
|
598
|
+
"the destination machine on and reachable from this computer? We " +
|
599
|
+
"tried the following command:\n\n#{cmd}")
|
600
|
+
end
|
601
|
+
|
602
|
+
loop {
|
603
|
+
break if File.exists?(retval_file)
|
604
|
+
Kernel.sleep(5)
|
605
|
+
}
|
606
|
+
|
607
|
+
retval = (File.open(retval_file) { |f| f.read }).chomp
|
608
|
+
|
609
|
+
fails = 0
|
610
|
+
loop {
|
611
|
+
break if retval == "0"
|
612
|
+
Kernel.puts "\n\n[#{cmd}] returned #{retval} instead of 0 as expected. Will try to copy again momentarily..."
|
613
|
+
fails += 1
|
614
|
+
if fails >= 5
|
615
|
+
raise AppScaleException.new("SCP failed")
|
616
|
+
end
|
617
|
+
Kernel.sleep(1)
|
618
|
+
CommonFunctions.shell("#{cmd}")
|
619
|
+
retval = (File.open(retval_file) { |f| f.read }).chomp
|
620
|
+
}
|
621
|
+
|
622
|
+
FileUtils.rm_f(retval_file)
|
623
|
+
return cmd
|
624
|
+
end
|
625
|
+
|
626
|
+
|
627
|
+
def self.get_username_from_options(options)
|
628
|
+
if options['test']
|
629
|
+
return DEFAULT_USERNAME
|
630
|
+
elsif options['email']
|
631
|
+
return options['email']
|
632
|
+
else
|
633
|
+
return CommonFunctions.get_email
|
634
|
+
end
|
635
|
+
end
|
636
|
+
|
637
|
+
|
638
|
+
def self.get_email
|
639
|
+
email = nil
|
640
|
+
Kernel.puts "\nThis AppScale instance is linked to an e-mail address giving it administrator privileges."
|
641
|
+
|
642
|
+
loop {
|
643
|
+
Kernel.print "Enter your desired administrator e-mail address: "
|
644
|
+
STDOUT.flush
|
645
|
+
email = STDIN.gets.chomp
|
646
|
+
|
647
|
+
if email =~ EMAIL_REGEX
|
648
|
+
break
|
649
|
+
else
|
650
|
+
Kernel.puts "The response you typed in was not an e-mail address. Please try again.\n\n"
|
651
|
+
end
|
652
|
+
}
|
653
|
+
|
654
|
+
return email
|
655
|
+
end
|
656
|
+
|
657
|
+
|
658
|
+
def self.get_password()
|
659
|
+
pass = nil
|
660
|
+
Kernel.puts "\nThe new administrator password must be at least six characters long and can include non-alphanumeric characters."
|
661
|
+
|
662
|
+
loop {
|
663
|
+
Kernel.print "Enter your new password: "
|
664
|
+
new_pass = self.get_line_from_stdin_no_echo()
|
665
|
+
Kernel.print "\nEnter again to verify: "
|
666
|
+
verify_pass = self.get_line_from_stdin_no_echo()
|
667
|
+
Kernel.print "\n"
|
668
|
+
|
669
|
+
if new_pass == verify_pass
|
670
|
+
pass = new_pass
|
671
|
+
|
672
|
+
if pass =~ PASSWORD_REGEX
|
673
|
+
break
|
674
|
+
else
|
675
|
+
Kernel.puts "\n\nThe password you typed in was not at least six characters long. Please try again.\n\n"
|
676
|
+
end
|
677
|
+
else
|
678
|
+
Kernel.puts "\n\nPasswords entered do not match. Please try again.\n\n"
|
679
|
+
end
|
680
|
+
}
|
681
|
+
|
682
|
+
return pass
|
683
|
+
end
|
684
|
+
|
685
|
+
|
686
|
+
def self.get_line_from_stdin_no_echo
|
687
|
+
state = CommonFunctions.shell("stty -g")
|
688
|
+
system "stty -echo" # Turn off character echoing
|
689
|
+
STDOUT.flush
|
690
|
+
line = STDIN.gets.chomp
|
691
|
+
system "stty #{state}"
|
692
|
+
return line
|
693
|
+
end
|
694
|
+
|
695
|
+
|
696
|
+
def self.get_from_yaml(keyname, tag, required=true)
|
697
|
+
location_file = File.expand_path("~/.appscale/locations-#{keyname}.yaml")
|
698
|
+
|
699
|
+
if !File.exists?(location_file)
|
700
|
+
raise AppScaleException.new("An AppScale instance is not currently " +
|
701
|
+
"running with the provided keyname, \"#{keyname}\".")
|
702
|
+
end
|
703
|
+
|
704
|
+
begin
|
705
|
+
tree = YAML.load_file(location_file)
|
706
|
+
rescue ArgumentError
|
707
|
+
if required
|
708
|
+
raise AppScaleException.new(MALFORMED_YAML)
|
709
|
+
else
|
710
|
+
return nil
|
711
|
+
end
|
712
|
+
end
|
713
|
+
|
714
|
+
if !tree
|
715
|
+
raise AppScaleException.new("The location file is in the wrong format.")
|
716
|
+
end
|
717
|
+
|
718
|
+
value = tree[tag]
|
719
|
+
|
720
|
+
if value.nil? and required
|
721
|
+
raise AppScaleException.new("The location file did not contain a #{tag} tag.")
|
722
|
+
end
|
723
|
+
|
724
|
+
return value
|
725
|
+
end
|
726
|
+
|
727
|
+
|
728
|
+
def self.get_role_from_nodes(keyname, role)
|
729
|
+
nodes = self.read_nodes_json(keyname)
|
730
|
+
nodes.each { |node|
|
731
|
+
if node['jobs'].include?(role)
|
732
|
+
return node['public_ip']
|
733
|
+
end
|
734
|
+
}
|
735
|
+
|
736
|
+
return ""
|
737
|
+
end
|
738
|
+
|
739
|
+
|
740
|
+
def self.get_load_balancer_ip(keyname, required=true)
|
741
|
+
return self.get_role_from_nodes(keyname, 'load_balancer')
|
742
|
+
end
|
743
|
+
|
744
|
+
|
745
|
+
def self.get_load_balancer_id(keyname, required=true)
|
746
|
+
return CommonFunctions.get_from_yaml(keyname, :instance_id)
|
747
|
+
end
|
748
|
+
|
749
|
+
|
750
|
+
def self.get_table(keyname, required=true)
|
751
|
+
return CommonFunctions.get_from_yaml(keyname, :table, required)
|
752
|
+
end
|
753
|
+
|
754
|
+
|
755
|
+
def self.get_all_public_ips(keyname, required=true)
|
756
|
+
ips = []
|
757
|
+
nodes = self.read_nodes_json(keyname)
|
758
|
+
nodes.each { |node|
|
759
|
+
ips << node['public_ip']
|
760
|
+
}
|
761
|
+
return ips
|
762
|
+
end
|
763
|
+
|
764
|
+
|
765
|
+
def self.get_db_master_ip(keyname, required=true)
|
766
|
+
return self.get_role_from_nodes(keyname, 'db_master')
|
767
|
+
end
|
768
|
+
|
769
|
+
|
770
|
+
def self.get_head_node_ip(keyname, required=true)
|
771
|
+
return self.get_role_from_nodes(keyname, 'shadow')
|
772
|
+
end
|
773
|
+
|
774
|
+
|
775
|
+
def self.get_secret_key(keyname, required=true)
|
776
|
+
CommonFunctions.get_from_yaml(keyname, :secret)
|
777
|
+
end
|
778
|
+
|
779
|
+
|
780
|
+
def self.get_infrastructure(keyname, required=true)
|
781
|
+
CommonFunctions.get_from_yaml(keyname, :infrastructure)
|
782
|
+
end
|
783
|
+
|
784
|
+
|
785
|
+
def self.make_appscale_directory()
|
786
|
+
# AppScale generates private keys for each cloud that machines are
|
787
|
+
# running in. It stores this data (and a YAML file detailing IP address
|
788
|
+
# mappings) in ~/.appscale - create this location if it doesn't exist.
|
789
|
+
appscale_path = File.expand_path("~/.appscale")
|
790
|
+
if !File.exists?(appscale_path)
|
791
|
+
FileUtils.mkdir(appscale_path)
|
792
|
+
end
|
793
|
+
end
|
794
|
+
|
795
|
+
|
796
|
+
# Reads the JSON file that stores information about which roles run on
|
797
|
+
# which nodes.
|
798
|
+
def self.read_nodes_json(keyname)
|
799
|
+
filename = File.expand_path("~/.appscale/locations-#{keyname}.json")
|
800
|
+
return JSON.load(self.read_file(filename))
|
801
|
+
end
|
802
|
+
|
803
|
+
|
804
|
+
# Writes the given JSON to the ~/.appscale directory so that we can read
|
805
|
+
# it later and determine what nodes run what services.
|
806
|
+
def self.write_nodes_json(new_role_info, keyname)
|
807
|
+
filename = File.expand_path("~/.appscale/locations-#{keyname}.json")
|
808
|
+
self.write_file(filename, JSON.dump(new_role_info))
|
809
|
+
end
|
810
|
+
|
811
|
+
|
812
|
+
def self.write_and_copy_node_file(options, node_layout, head_node_result)
|
813
|
+
keyname = options['keyname']
|
814
|
+
head_node_ip = head_node_result[:head_node_ip]
|
815
|
+
|
816
|
+
all_ips = head_node_result[:acc].get_all_public_ips()
|
817
|
+
db_master_ip = node_layout.db_master.id
|
818
|
+
|
819
|
+
locations_yaml = File.expand_path("~/.appscale/locations-#{keyname}.yaml")
|
820
|
+
CommonFunctions.write_node_file(head_node_ip,
|
821
|
+
head_node_result[:instance_id], options['table'],
|
822
|
+
head_node_result[:secret_key], db_master_ip, all_ips,
|
823
|
+
options['infrastructure'], locations_yaml)
|
824
|
+
remote_locations_file = "/root/.appscale/locations-#{keyname}.yaml"
|
825
|
+
CommonFunctions.scp_file(locations_yaml, remote_locations_file,
|
826
|
+
head_node_ip, head_node_result[:true_key])
|
827
|
+
end
|
828
|
+
|
829
|
+
|
830
|
+
def self.write_node_file(head_node_ip, instance_id, table, secret, db_master,
|
831
|
+
ips, infrastructure, locations_yaml)
|
832
|
+
|
833
|
+
infrastructure ||= "xen"
|
834
|
+
tree = { :load_balancer => head_node_ip, :instance_id => instance_id ,
|
835
|
+
:table => table, :shadow => head_node_ip,
|
836
|
+
:secret => secret , :db_master => db_master,
|
837
|
+
:ips => ips , :infrastructure => infrastructure }
|
838
|
+
loc_path = File.expand_path(locations_yaml)
|
839
|
+
File.open(loc_path, "w") {|file| YAML.dump(tree, file)}
|
840
|
+
end
|
841
|
+
|
842
|
+
|
843
|
+
def self.get_random_alphanumeric(length=10)
|
844
|
+
random = ""
|
845
|
+
possible = "0123456789abcdefghijklmnopqrstuvxwyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
846
|
+
possibleLength = possible.length
|
847
|
+
|
848
|
+
length.times { |index|
|
849
|
+
random << possible[rand(possibleLength)]
|
850
|
+
}
|
851
|
+
|
852
|
+
return random
|
853
|
+
end
|
854
|
+
|
855
|
+
|
856
|
+
def self.get_app_info_from_options(options)
|
857
|
+
file_location = options['file_location']
|
858
|
+
table = options['table']
|
859
|
+
|
860
|
+
if file_location.nil?
|
861
|
+
apps_to_start = ["none"]
|
862
|
+
return apps_to_start, {}
|
863
|
+
else
|
864
|
+
app_info = CommonFunctions.get_app_name_from_tar(file_location)
|
865
|
+
apps_to_start = [CommonFunctions.validate_app_name(app_info[:app_name], table)]
|
866
|
+
return apps_to_start, app_info
|
867
|
+
end
|
868
|
+
end
|
869
|
+
|
870
|
+
|
871
|
+
def self.get_app_name_from_tar(fullpath)
|
872
|
+
app_name, file, language = CommonFunctions.get_app_info(fullpath, PYTHON_CONFIG)
|
873
|
+
|
874
|
+
if app_name.nil? or file.nil? or language.nil?
|
875
|
+
app_name, file, language = CommonFunctions.get_app_info(fullpath, JAVA_CONFIG)
|
876
|
+
end
|
877
|
+
|
878
|
+
if app_name.nil? or file.nil? or language.nil?
|
879
|
+
raise AppScaleException.new(NO_CONFIG_FILE)
|
880
|
+
end
|
881
|
+
|
882
|
+
return {:app_name => app_name, :file => file, :language => language}
|
883
|
+
end
|
884
|
+
|
885
|
+
|
886
|
+
def self.move_app(temp_dir, filename, app_file, fullpath)
|
887
|
+
if File.directory?(fullpath)
|
888
|
+
CommonFunctions.shell("cp -r #{fullpath}/* /tmp/#{temp_dir}/")
|
889
|
+
return
|
890
|
+
else
|
891
|
+
FileUtils.cp(fullpath, "/tmp/#{temp_dir}/#{filename}")
|
892
|
+
FileUtils.rm_f("/tmp/#{temp_dir}/#{app_file}")
|
893
|
+
tar_file = CommonFunctions.shell("cd /tmp/#{temp_dir}; tar zxvfm #{filename} 2>&1; echo $?").chomp
|
894
|
+
tar_ret_val = tar_file.scan(/\d+\Z/).to_s
|
895
|
+
if tar_ret_val != "0"
|
896
|
+
raise AppScaleException.new("Untar'ing the given tar file in /tmp failed")
|
897
|
+
end
|
898
|
+
end
|
899
|
+
return
|
900
|
+
end
|
901
|
+
|
902
|
+
|
903
|
+
def self.warn_on_large_app_size(fullpath)
|
904
|
+
size = File.size(fullpath)
|
905
|
+
if size > MAX_FILE_SIZE
|
906
|
+
Kernel.puts "Warning: Your application is large enough that it may take a while to upload."
|
907
|
+
end
|
908
|
+
end
|
909
|
+
|
910
|
+
|
911
|
+
def self.get_app_info(fullpath, app_file)
|
912
|
+
if !File.exists?(fullpath)
|
913
|
+
raise AppEngineConfigException.new("AppEngine file not found")
|
914
|
+
end
|
915
|
+
filename = fullpath.scan(/\/?([\w\.]+\Z)/).flatten.to_s
|
916
|
+
|
917
|
+
temp_dir = CommonFunctions.get_random_alphanumeric
|
918
|
+
FileUtils.rm_rf("/tmp/#{temp_dir}", :secure => true)
|
919
|
+
FileUtils.mkdir_p("/tmp/#{temp_dir}")
|
920
|
+
|
921
|
+
CommonFunctions.move_app(temp_dir, filename, app_file, fullpath)
|
922
|
+
app_yaml_loc = app_file
|
923
|
+
if !File.exists?("/tmp/#{temp_dir}/#{app_file}")
|
924
|
+
FileUtils.rm_rf("/tmp/#{temp_dir}", :secure => true)
|
925
|
+
return nil, nil, nil
|
926
|
+
end
|
927
|
+
|
928
|
+
if app_file == PYTHON_CONFIG
|
929
|
+
app_name = CommonFunctions.get_app_name_via_yaml(temp_dir, app_yaml_loc)
|
930
|
+
language = "python"
|
931
|
+
if File.directory?(fullpath)
|
932
|
+
temp_dir2 = CommonFunctions.get_random_alphanumeric
|
933
|
+
FileUtils.rm_rf("/tmp/#{temp_dir2}", :secure => true)
|
934
|
+
FileUtils.mkdir_p("/tmp/#{temp_dir2}")
|
935
|
+
CommonFunctions.shell("cd /tmp/#{temp_dir}; tar -czf ../#{temp_dir2}/#{app_name}.tar.gz .")
|
936
|
+
file = "/tmp/#{temp_dir2}/#{app_name}.tar.gz"
|
937
|
+
else
|
938
|
+
file = fullpath
|
939
|
+
end
|
940
|
+
elsif app_file == JAVA_CONFIG
|
941
|
+
app_name = CommonFunctions.get_app_name_via_xml(temp_dir, app_yaml_loc)
|
942
|
+
language = "java"
|
943
|
+
# don't remove user's jar files, they may have their own jars in it
|
944
|
+
#FileUtils.rm_rf("/tmp/#{temp_dir}/war/WEB-INF/lib/", :secure => true)
|
945
|
+
FileUtils.mkdir_p("/tmp/#{temp_dir}/war/WEB-INF/lib")
|
946
|
+
temp_dir2 = CommonFunctions.get_random_alphanumeric
|
947
|
+
FileUtils.rm_rf("/tmp/#{temp_dir2}", :secure => true)
|
948
|
+
FileUtils.mkdir_p("/tmp/#{temp_dir2}")
|
949
|
+
FileUtils.rm_f("/tmp/#{temp_dir}/#{filename}")
|
950
|
+
CommonFunctions.shell("cd /tmp/#{temp_dir}; tar -czf ../#{temp_dir2}/#{app_name}.tar.gz .")
|
951
|
+
file = "/tmp/#{temp_dir2}/#{app_name}.tar.gz"
|
952
|
+
else
|
953
|
+
FileUtils.rm_rf("/tmp/#{temp_dir}", :secure => true)
|
954
|
+
raise AppEngineConfigException.new("app_name was #{app_file}, " +
|
955
|
+
"which was not a recognized value.")
|
956
|
+
end
|
957
|
+
|
958
|
+
if app_name.nil?
|
959
|
+
FileUtils.rm_rf("/tmp/#{temp_dir}", :secure => true)
|
960
|
+
raise AppEngineConfigException.new("AppEngine tar file is invalid - " +
|
961
|
+
"Doesn't have an app name in #{app_file}")
|
962
|
+
end
|
963
|
+
|
964
|
+
FileUtils.rm_rf("/tmp/#{temp_dir}", :secure => true)
|
965
|
+
CommonFunctions.warn_on_large_app_size(file)
|
966
|
+
return app_name, file, language
|
967
|
+
end
|
968
|
+
|
969
|
+
|
970
|
+
def self.get_app_name_via_yaml(temp_dir, app_yaml_loc)
|
971
|
+
app_yaml_loc = "/tmp/" + temp_dir + "/" + app_yaml_loc
|
972
|
+
|
973
|
+
begin
|
974
|
+
tree = YAML.load_file(app_yaml_loc.chomp)
|
975
|
+
rescue ArgumentError
|
976
|
+
raise AppScaleException.new(MALFORMED_YAML)
|
977
|
+
end
|
978
|
+
|
979
|
+
app_name = String(tree["application"])
|
980
|
+
return app_name
|
981
|
+
end
|
982
|
+
|
983
|
+
|
984
|
+
def self.get_app_name_via_xml(temp_dir, xml_loc)
|
985
|
+
xml_loc = "/tmp/" + temp_dir + "/" + xml_loc
|
986
|
+
web_xml_contents = (File.open(xml_loc) { |f| f.read }).chomp
|
987
|
+
app_name = web_xml_contents.scan(/<application>([\w\d-]+)<\/application>/).flatten.to_s
|
988
|
+
app_name = nil if app_name == ""
|
989
|
+
return app_name
|
990
|
+
end
|
991
|
+
|
992
|
+
|
993
|
+
private
|
994
|
+
|
995
|
+
|
996
|
+
def self.grab_file filename
|
997
|
+
filename = File.expand_path(filename)
|
998
|
+
content = nil
|
999
|
+
begin
|
1000
|
+
content = File.open(filename) { |f| f.read.chomp! }
|
1001
|
+
rescue Errno::ENOENT
|
1002
|
+
end
|
1003
|
+
content
|
1004
|
+
end
|
1005
|
+
|
1006
|
+
|
1007
|
+
def self.obscure_string(string)
|
1008
|
+
return string if string.length < 4
|
1009
|
+
last_four = string[string.length-4, string.length]
|
1010
|
+
obscured = "*" * (string.length-4)
|
1011
|
+
return obscured + last_four
|
1012
|
+
end
|
1013
|
+
|
1014
|
+
|
1015
|
+
def self.obscure_array(array)
|
1016
|
+
return array.map {|string| obscure_string(string)}
|
1017
|
+
end
|
1018
|
+
|
1019
|
+
|
1020
|
+
def self.obscure_creds(creds)
|
1021
|
+
obscured = {}
|
1022
|
+
creds.each { |k, v|
|
1023
|
+
if CLOUDY_CREDS.include?(k)
|
1024
|
+
obscured[k] = self.obscure_string(v)
|
1025
|
+
else
|
1026
|
+
obscured[k] = v
|
1027
|
+
end
|
1028
|
+
}
|
1029
|
+
|
1030
|
+
return obscured
|
1031
|
+
end
|
1032
|
+
|
1033
|
+
|
1034
|
+
def self.does_image_have_location?(ip, location, key)
|
1035
|
+
ret_val = CommonFunctions.shell("ssh -i #{key} #{SSH_OPTIONS} 2>&1 root@#{ip} 'ls #{location}'; echo $?").chomp[-1]
|
1036
|
+
return ret_val.chr == "0"
|
1037
|
+
end
|
1038
|
+
|
1039
|
+
|
1040
|
+
def self.ensure_image_is_appscale(ip, key)
|
1041
|
+
return if self.does_image_have_location?(ip, "/etc/appscale", key)
|
1042
|
+
raise AppScaleException.new("The image at #{ip} is not an AppScale image." +
|
1043
|
+
" Please install AppScale on it and try again.")
|
1044
|
+
end
|
1045
|
+
|
1046
|
+
|
1047
|
+
def self.ensure_db_is_supported(ip, db, key)
|
1048
|
+
return if self.does_image_have_location?(ip, "/etc/appscale/#{VER_NUM}/#{db}", key)
|
1049
|
+
raise AppScaleException.new("The image at #{ip} does not have support for #{db}." +
|
1050
|
+
" Please install support for this database and try again.")
|
1051
|
+
end
|
1052
|
+
|
1053
|
+
|
1054
|
+
def self.confirm_app_removal(confirm, app_name)
|
1055
|
+
if confirm
|
1056
|
+
return "YES"
|
1057
|
+
end
|
1058
|
+
|
1059
|
+
Kernel.print "We are about to attempt to remove your application, #{app_name}." +
|
1060
|
+
"\nAre you sure you want to remove this application (Y/N)? "
|
1061
|
+
STDOUT.flush
|
1062
|
+
|
1063
|
+
loop {
|
1064
|
+
result = STDIN.gets.chomp.upcase
|
1065
|
+
if result == "Y" or result == "YES"
|
1066
|
+
return "YES"
|
1067
|
+
end
|
1068
|
+
if result == "N" or result == "NO"
|
1069
|
+
return "NO"
|
1070
|
+
end
|
1071
|
+
Kernel.print "Please type in 'yes' or 'no'.\nAre you sure you want to remove this application (Y/N)? "
|
1072
|
+
}
|
1073
|
+
end
|
1074
|
+
|
1075
|
+
|
1076
|
+
def self.remove_app(app_name, keyname)
|
1077
|
+
secret_key = CommonFunctions.get_secret_key(keyname)
|
1078
|
+
head_node_ip = CommonFunctions.get_head_node_ip(keyname)
|
1079
|
+
acc = AppControllerClient.new(head_node_ip, secret_key)
|
1080
|
+
userappserver_ip = acc.get_userappserver_ip()
|
1081
|
+
|
1082
|
+
uac = UserAppClient.new(userappserver_ip, secret_key)
|
1083
|
+
app_exists = uac.does_app_exist?(app_name, retry_on_except=true)
|
1084
|
+
|
1085
|
+
if !app_exists
|
1086
|
+
raise AppEngineConfigException.new(AppScaleTools::APP_NOT_RUNNING)
|
1087
|
+
end
|
1088
|
+
|
1089
|
+
load_balancer_ip = CommonFunctions.get_load_balancer_ip(keyname)
|
1090
|
+
acc.stop_app(app_name)
|
1091
|
+
|
1092
|
+
Kernel.puts "Please wait for your app to shut down."
|
1093
|
+
loop {
|
1094
|
+
if !acc.app_is_running?(app_name)
|
1095
|
+
break
|
1096
|
+
end
|
1097
|
+
Kernel.sleep(5)
|
1098
|
+
}
|
1099
|
+
end
|
1100
|
+
|
1101
|
+
|
1102
|
+
def self.scp_ssh_key_to_ip(ip, ssh_key, pub_key)
|
1103
|
+
Kernel.puts CommonFunctions.shell("scp -i #{ssh_key} #{ssh_key} root@#{ip}:.ssh/id_rsa")
|
1104
|
+
# this is needed for EC2 integration.
|
1105
|
+
Kernel.puts CommonFunctions.shell("scp -i #{ssh_key} #{ssh_key} root@#{ip}:.ssh/id_dsa")
|
1106
|
+
Kernel.puts CommonFunctions.shell("scp -i #{ssh_key} #{pub_key} root@#{ip}:.ssh/id_rsa.pub")
|
1107
|
+
end
|
1108
|
+
|
1109
|
+
|
1110
|
+
def self.generate_rsa_key(keyname)
|
1111
|
+
path = File.expand_path("~/.appscale/#{keyname}")
|
1112
|
+
backup_key = File.expand_path("~/.appscale/#{keyname}.key")
|
1113
|
+
pub_key = File.expand_path("~/.appscale/#{keyname}.pub")
|
1114
|
+
|
1115
|
+
#FileUtils.rm_f([path, backup_key, pub_key])
|
1116
|
+
unless File.exists?(path) and File.exists?(pub_key)
|
1117
|
+
FileUtils.rm_f([path, backup_key, pub_key])
|
1118
|
+
Kernel.puts CommonFunctions.shell("ssh-keygen -t rsa -N '' -f #{path}")
|
1119
|
+
end
|
1120
|
+
FileUtils.chmod(0600, [path, pub_key])
|
1121
|
+
return pub_key, backup_key
|
1122
|
+
end
|
1123
|
+
|
1124
|
+
|
1125
|
+
def self.ssh_copy_id(ip, path, auto, expect_script, password)
|
1126
|
+
Kernel.puts "\n\n"
|
1127
|
+
Kernel.puts "Executing ssh-copy-id for host : " + ip
|
1128
|
+
Kernel.puts "------------------------------"
|
1129
|
+
|
1130
|
+
if auto
|
1131
|
+
Kernel.puts CommonFunctions.shell("#{expect_script} root@#{ip} #{path} #{password}")
|
1132
|
+
else
|
1133
|
+
Kernel.puts CommonFunctions.shell("ssh-copy-id -i #{path} root@#{ip}")
|
1134
|
+
end
|
1135
|
+
|
1136
|
+
# Check the exit status of the above shell command
|
1137
|
+
if $?.to_i != 0
|
1138
|
+
raise AppScaleException.new("ERROR ! Unable to ssh-copy-id to host : #{ip}")
|
1139
|
+
end
|
1140
|
+
end
|
1141
|
+
|
1142
|
+
|
1143
|
+
def self.validate_run_instances_options(options)
|
1144
|
+
infrastructure = options['infrastructure']
|
1145
|
+
machine = options['machine']
|
1146
|
+
# In non-hybrid cloud deployments, the user must specify the machine image
|
1147
|
+
# (emi or ami) to use. Abort if they didn't.
|
1148
|
+
if infrastructure && infrastructure != "hybrid" && machine.nil?
|
1149
|
+
raise BadConfigurationException.new(AppScaleTools::NO_MACHINE_SET)
|
1150
|
+
end
|
1151
|
+
|
1152
|
+
if infrastructure && infrastructure != "hybrid"
|
1153
|
+
VMTools.verify_credentials_are_set_correctly(infrastructure)
|
1154
|
+
end
|
1155
|
+
|
1156
|
+
# If the user hasn't given us an ips.yaml file, then they're running in a cloud
|
1157
|
+
# deployment. They need to give us a machine image id to spawn up - if they
|
1158
|
+
# haven't, fail.
|
1159
|
+
if options['ips'].nil? and machine.nil?
|
1160
|
+
raise BadConfigurationException.new(EC2_USAGE_MSG)
|
1161
|
+
end
|
1162
|
+
|
1163
|
+
keyname = options['keyname']
|
1164
|
+
locations_yaml = File.expand_path("~/.appscale/locations-#{keyname}.yaml")
|
1165
|
+
if File.exists?(locations_yaml) and !options['force']
|
1166
|
+
error_msg = "An AppScale instance is already running with the given" +
|
1167
|
+
" keyname, #{keyname}. Please terminate that instance first with the" +
|
1168
|
+
" following command:\n\nappscale-terminate-instances --keyname " +
|
1169
|
+
"#{keyname} <--ips path-to-ips.yaml if using non-cloud deployment>"
|
1170
|
+
raise BadConfigurationException.new(error_msg)
|
1171
|
+
end
|
1172
|
+
end
|
1173
|
+
|
1174
|
+
|
1175
|
+
def self.print_starting_message(infrastructure, instance_type)
|
1176
|
+
if infrastructure and infrastructure != "hybrid"
|
1177
|
+
deployment = "cloud environment with the #{infrastructure} tools" +
|
1178
|
+
" with instance type #{instance_type}"
|
1179
|
+
elsif infrastructure and infrastructure == "hybrid"
|
1180
|
+
# TODO - say which environments
|
1181
|
+
deployment = "hybrid environment"
|
1182
|
+
else
|
1183
|
+
deployment = "non-cloud environment"
|
1184
|
+
end
|
1185
|
+
|
1186
|
+
Kernel.puts "About to start AppScale over a #{deployment}."
|
1187
|
+
end
|
1188
|
+
|
1189
|
+
def self.generate_node_layout(options)
|
1190
|
+
remote_options = {
|
1191
|
+
:infrastructure => options['infrastructure'],
|
1192
|
+
:database => options['table'],
|
1193
|
+
:min_images => options['min_images'],
|
1194
|
+
:max_images => options['max_images'],
|
1195
|
+
:replication => options['replication'],
|
1196
|
+
:read_factor => options['voldemort_r'],
|
1197
|
+
:write_factor => options['voldemort_w'],
|
1198
|
+
}
|
1199
|
+
|
1200
|
+
node_layout = NodeLayout.new(options['ips'], remote_options)
|
1201
|
+
|
1202
|
+
if !node_layout.valid?
|
1203
|
+
raise BadConfigurationException.new("There were errors with the yaml " +
|
1204
|
+
"file: \n#{node_layout.errors}")
|
1205
|
+
end
|
1206
|
+
|
1207
|
+
return node_layout
|
1208
|
+
end
|
1209
|
+
|
1210
|
+
|
1211
|
+
def self.generate_appscale_credentials(options, node_layout, head_node_ip,
|
1212
|
+
ips_to_use, ssh_key)
|
1213
|
+
|
1214
|
+
table = options['table']
|
1215
|
+
keypath = ssh_key.scan(/([\d|\w|\.]+)\Z/).flatten.to_s
|
1216
|
+
|
1217
|
+
creds = {
|
1218
|
+
"table" => "#{table}",
|
1219
|
+
"hostname" => "#{head_node_ip}",
|
1220
|
+
"ips" => "#{ips_to_use}",
|
1221
|
+
"keyname" => "#{options['keyname']}",
|
1222
|
+
"keypath" => "#{keypath}",
|
1223
|
+
"replication" => "#{node_layout.replication_factor}",
|
1224
|
+
"appengine" => "#{options['appengine']}",
|
1225
|
+
"autoscale" => "#{options['autoscale']}",
|
1226
|
+
"group" => "#{options['group']}"
|
1227
|
+
}
|
1228
|
+
|
1229
|
+
if table == "voldemort"
|
1230
|
+
voldemort_creds = {
|
1231
|
+
"voldemortr" => "#{node_layout.read_factor}",
|
1232
|
+
"voldemortw" => "#{node_layout.write_factor}"
|
1233
|
+
}
|
1234
|
+
creds.merge!(voldemort_creds)
|
1235
|
+
end
|
1236
|
+
|
1237
|
+
if table == "simpledb"
|
1238
|
+
simpledb_creds = {
|
1239
|
+
"SIMPLEDB_ACCESS_KEY" => ENV['SIMPLEDB_ACCESS_KEY'],
|
1240
|
+
"SIMPLEDB_SECRET_KEY" => ENV['SIMPLEDB_SECRET_KEY']
|
1241
|
+
}
|
1242
|
+
creds.merge!(simpledb_creds)
|
1243
|
+
end
|
1244
|
+
|
1245
|
+
infrastructure = options['infrastructure']
|
1246
|
+
if VALID_CLOUD_TYPES.include?(infrastructure)
|
1247
|
+
if infrastructure == "hybrid"
|
1248
|
+
creds.merge!(VMTools.get_hybrid_creds(node_layout))
|
1249
|
+
else
|
1250
|
+
creds.merge!(VMTools.get_cloud_creds(node_layout, options))
|
1251
|
+
end
|
1252
|
+
end
|
1253
|
+
|
1254
|
+
if options['restore_from_tar']
|
1255
|
+
db_backup_location = "/root/db-backup.tar.gz"
|
1256
|
+
tar_creds = {"restore_from_tar" => db_backup_location}
|
1257
|
+
creds.merge!(tar_creds)
|
1258
|
+
|
1259
|
+
CommonFunctions.scp_file(options['restore_from_tar'], db_backup_location, head_node_ip, true_key)
|
1260
|
+
end
|
1261
|
+
|
1262
|
+
if options['restore_from_ebs']
|
1263
|
+
ebs_creds = {"restore_from_tar" => options['restore_from_ebs']}
|
1264
|
+
creds.merge!(tar_creds)
|
1265
|
+
end
|
1266
|
+
|
1267
|
+
if options['restore_neptune_info']
|
1268
|
+
remote_neptune_info_location = "/etc/appscale/neptune_info.txt"
|
1269
|
+
CommonFunctions.scp_file(options['restore_neptune_info'],
|
1270
|
+
remote_neptune_info_location, head_node_ip, ssh_key)
|
1271
|
+
Kernel.puts "Neptune data restored!"
|
1272
|
+
end
|
1273
|
+
|
1274
|
+
return creds
|
1275
|
+
end
|
1276
|
+
|
1277
|
+
|
1278
|
+
def self.copy_keys(secret_key_location, ip, ssh_key, options)
|
1279
|
+
remote_secret_key_location = "/etc/appscale/secret.key"
|
1280
|
+
CommonFunctions.scp_file(secret_key_location, remote_secret_key_location,
|
1281
|
+
ip, ssh_key)
|
1282
|
+
|
1283
|
+
remote_ssh_key_location = "/etc/appscale/ssh.key"
|
1284
|
+
CommonFunctions.scp_file(ssh_key, remote_ssh_key_location, ip, ssh_key)
|
1285
|
+
|
1286
|
+
cert_loc, key_loc = VMTools.get_vmm_keys(options)
|
1287
|
+
|
1288
|
+
remote_cert_loc = "/etc/appscale/certs/mycert.pem"
|
1289
|
+
CommonFunctions.scp_file(cert_loc, remote_cert_loc, ip, ssh_key)
|
1290
|
+
|
1291
|
+
remote_key_loc = "/etc/appscale/certs/mykey.pem"
|
1292
|
+
CommonFunctions.scp_file(key_loc, remote_key_loc, ip, ssh_key)
|
1293
|
+
|
1294
|
+
infrastructure = options["infrastructure"]
|
1295
|
+
if VALID_CLOUD_TYPES.include?(infrastructure) and infrastructure == "hybrid"
|
1296
|
+
cloud_num = 1
|
1297
|
+
|
1298
|
+
loop {
|
1299
|
+
cloud_type = ENV["CLOUD#{cloud_num}_TYPE"]
|
1300
|
+
break if cloud_type.nil?
|
1301
|
+
|
1302
|
+
Kernel.puts "Copying over credentials for cloud #{cloud_num}"
|
1303
|
+
cert = ENV["CLOUD#{cloud_num}_EC2_CERT"]
|
1304
|
+
private_key = ENV["CLOUD#{cloud_num}_EC2_PRIVATE_KEY"]
|
1305
|
+
CommonFunctions.copy_cloud_keys(cloud_num, ip, ssh_key,
|
1306
|
+
options['verbose'], cert, private_key)
|
1307
|
+
cloud_num += 1
|
1308
|
+
}
|
1309
|
+
elsif VALID_CLOUD_TYPES.include?(infrastructure)
|
1310
|
+
Kernel.puts "Copying over credentials for cloud"
|
1311
|
+
cloud_num = 1
|
1312
|
+
cert = ENV["EC2_CERT"]
|
1313
|
+
private_key = ENV["EC2_PRIVATE_KEY"]
|
1314
|
+
CommonFunctions.copy_cloud_keys(cloud_num, ip, ssh_key,
|
1315
|
+
options['verbose'], cert, private_key)
|
1316
|
+
else
|
1317
|
+
cloud_num = 1
|
1318
|
+
CommonFunctions.copy_cloud_keys(cloud_num, ip, ssh_key,
|
1319
|
+
options['verbose'], cert_loc, key_loc)
|
1320
|
+
end
|
1321
|
+
end
|
1322
|
+
|
1323
|
+
|
1324
|
+
def self.copy_cloud_keys(cloud_num, ip, ssh_key, verbose, cert, private_key)
|
1325
|
+
cloud_key_dir = "/etc/appscale/keys/cloud#{cloud_num}"
|
1326
|
+
make_cloud_key_dir = "mkdir -p #{cloud_key_dir}"
|
1327
|
+
CommonFunctions.run_remote_command(ip, make_cloud_key_dir, ssh_key, verbose)
|
1328
|
+
CommonFunctions.scp_file(cert, "#{cloud_key_dir}/mycert.pem", ip, ssh_key)
|
1329
|
+
CommonFunctions.scp_file(private_key, "#{cloud_key_dir}/mykey.pem", ip, ssh_key)
|
1330
|
+
end
|
1331
|
+
|
1332
|
+
|
1333
|
+
def self.start_appcontroller(ip, ssh_key, verbose)
|
1334
|
+
# TODO(cgb) - check the return value of the following command. a user could
|
1335
|
+
# accidentally remove that file and cause this to return a bad value
|
1336
|
+
remote_home = CommonFunctions.get_remote_appscale_home(ip, ssh_key)
|
1337
|
+
start = "ruby #{remote_home}/AppController/djinnServer.rb"
|
1338
|
+
stop = "ruby #{remote_home}/AppController/terminate.rb"
|
1339
|
+
|
1340
|
+
Kernel.puts "Starting server at #{ip}"
|
1341
|
+
|
1342
|
+
# remove any possible appcontroller state that may not have been
|
1343
|
+
# properly removed in non-cloud runs
|
1344
|
+
remove_state = "rm -rf /etc/appscale/appcontroller-state.json"
|
1345
|
+
CommonFunctions.run_remote_command(ip, remove_state, ssh_key, verbose)
|
1346
|
+
|
1347
|
+
GodInterface.start_god(ip, ssh_key)
|
1348
|
+
Kernel.sleep(1)
|
1349
|
+
|
1350
|
+
begin
|
1351
|
+
Timeout::timeout(60) {
|
1352
|
+
GodInterface.start(:controller, start, stop,
|
1353
|
+
AppScaleTools::DJINN_SERVER_PORT,
|
1354
|
+
{'APPSCALE_HOME' => remote_home}, ip, ssh_key)
|
1355
|
+
|
1356
|
+
Kernel.puts "Please wait for the controller to finish " +
|
1357
|
+
"pre-processing tasks."
|
1358
|
+
|
1359
|
+
CommonFunctions.sleep_until_port_is_open(ip,
|
1360
|
+
AppScaleTools::DJINN_SERVER_PORT, AppScaleTools::USE_SSL)
|
1361
|
+
}
|
1362
|
+
rescue Timeout::Error
|
1363
|
+
retry
|
1364
|
+
end
|
1365
|
+
end
|
1366
|
+
|
1367
|
+
|
1368
|
+
def self.backup_neptune_info(keyname, shadow_ip, backup_neptune_info)
|
1369
|
+
remote_file = "/etc/appscale/neptune_info.txt"
|
1370
|
+
command = "scp -i ~/.appscale/#{keyname}.key " +
|
1371
|
+
"root@#{shadow_ip}:#{remote_file} #{backup_neptune_info}"
|
1372
|
+
CommonFunctions.shell("#{command}")
|
1373
|
+
Kernel.puts "Your Neptune information has been backed up to #{backup_neptune_info}\n"
|
1374
|
+
end
|
1375
|
+
|
1376
|
+
|
1377
|
+
def self.terminate_via_infrastructure(infrastructure, keyname, shadow_ip, secret)
|
1378
|
+
Kernel.puts "About to terminate instances spawned via #{infrastructure} " +
|
1379
|
+
"with keyname '#{keyname}'..."
|
1380
|
+
Kernel.sleep(2)
|
1381
|
+
|
1382
|
+
acc = AppControllerClient.new(shadow_ip, secret)
|
1383
|
+
if acc.is_live?
|
1384
|
+
acc.kill()
|
1385
|
+
# TODO(cgb): read the group from the locations.yaml file and delete that
|
1386
|
+
cmd = "#{infrastructure}-delete-group appscale"
|
1387
|
+
Kernel.puts CommonFunctions.shell("#{cmd}")
|
1388
|
+
Kernel.puts "Terminated AppScale in cloud deployment."
|
1389
|
+
else
|
1390
|
+
VMTools.terminate_infrastructure_machines(infrastructure, keyname)
|
1391
|
+
end
|
1392
|
+
end
|
1393
|
+
|
1394
|
+
|
1395
|
+
def self.terminate_via_vmm(keyname, verbose)
|
1396
|
+
Kernel.puts "About to terminate instances spawned via Xen/KVM with " +
|
1397
|
+
"keyname '#{keyname}'..."
|
1398
|
+
Kernel.sleep(2)
|
1399
|
+
|
1400
|
+
ssh_key_location = "~/.appscale/#{keyname}"
|
1401
|
+
live_ips = CommonFunctions.stop_appcontrollers(keyname, ssh_key_location,
|
1402
|
+
verbose)
|
1403
|
+
CommonFunctions.wait_for_appcontrollers_to_stop(live_ips, ssh_key_location)
|
1404
|
+
end
|
1405
|
+
|
1406
|
+
|
1407
|
+
def self.stop_appcontrollers(keyname, ssh_key, verbose)
|
1408
|
+
command = "service appscale-controller stop"
|
1409
|
+
|
1410
|
+
ips = CommonFunctions.get_all_public_ips(keyname)
|
1411
|
+
live_ips = []
|
1412
|
+
|
1413
|
+
threads = []
|
1414
|
+
ips.each { |ip|
|
1415
|
+
threads << Thread.new {
|
1416
|
+
CommonFunctions.run_remote_command(ip, command, ssh_key, verbose)
|
1417
|
+
Kernel.sleep(5)
|
1418
|
+
CommonFunctions.run_remote_command(ip, command, ssh_key, verbose)
|
1419
|
+
live_ips << ip
|
1420
|
+
}
|
1421
|
+
}
|
1422
|
+
|
1423
|
+
threads.each { |t| t.join }
|
1424
|
+
return live_ips
|
1425
|
+
end
|
1426
|
+
|
1427
|
+
|
1428
|
+
def self.wait_for_appcontrollers_to_stop(ips, ssh_key)
|
1429
|
+
boxes_shut_down = 0
|
1430
|
+
ips.each { |ip|
|
1431
|
+
Kernel.print "Shutting down AppScale components at #{ip}"
|
1432
|
+
STDOUT.flush
|
1433
|
+
loop {
|
1434
|
+
remote_cmd = "ssh root@#{ip} #{SSH_OPTIONS} -i #{ssh_key} 'ps x'"
|
1435
|
+
ps = CommonFunctions.shell(remote_cmd)
|
1436
|
+
processes_left = ps.scan(/appscale-controller stop/).length
|
1437
|
+
break if processes_left.zero?
|
1438
|
+
Kernel.print '.'
|
1439
|
+
STDOUT.flush
|
1440
|
+
Kernel.sleep(0.3)
|
1441
|
+
}
|
1442
|
+
boxes_shut_down += 1
|
1443
|
+
Kernel.print "\n"
|
1444
|
+
}
|
1445
|
+
|
1446
|
+
if boxes_shut_down.zero?
|
1447
|
+
raise AppScaleException.new(
|
1448
|
+
AppScaleTools::UNABLE_TO_TERMINATE_ANY_MACHINES)
|
1449
|
+
end
|
1450
|
+
|
1451
|
+
Kernel.puts "Terminated AppScale across #{boxes_shut_down} boxes."
|
1452
|
+
end
|
1453
|
+
|
1454
|
+
|
1455
|
+
def self.delete_appscale_files(keyname)
|
1456
|
+
locations_yaml = File.expand_path("~/.appscale/locations-#{keyname}.yaml")
|
1457
|
+
FileUtils.rm_f(locations_yaml) if File.exists?(locations_yaml)
|
1458
|
+
|
1459
|
+
retval_file = File.expand_path("~/.appscale/retval")
|
1460
|
+
FileUtils.rm_f(retval_file) if File.exists?(retval_file)
|
1461
|
+
end
|
1462
|
+
|
1463
|
+
|
1464
|
+
def self.verbose(msg, verbose)
|
1465
|
+
Kernel.puts msg if verbose
|
1466
|
+
end
|
1467
|
+
end
|