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