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,25 @@
|
|
|
1
|
+
# Programmer: Chris Bunch
|
|
2
|
+
|
|
3
|
+
# a generic class to represent exceptions thrown within AppScale
|
|
4
|
+
class AppScaleException < Exception
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# a class representing exceptions related to bad command line arguments
|
|
8
|
+
# (see lib/parse_args)
|
|
9
|
+
class BadCommandLineArgException < AppScaleException
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# a class representing exceptions related to incorrectly configured
|
|
13
|
+
# AppScale deployments
|
|
14
|
+
class BadConfigurationException < AppScaleException
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# a class representing exceptions related to cloud infrastructures
|
|
18
|
+
# (e.g., if euca or ec2 throw errors)
|
|
19
|
+
class InfrastructureException < AppScaleException
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# a class representing exceptions related to app engine apps
|
|
23
|
+
# to be uploaded
|
|
24
|
+
class AppEngineConfigException < AppScaleException
|
|
25
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/ruby -w
|
|
2
|
+
# Programmer: Chris Bunch
|
|
3
|
+
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require 'openssl'
|
|
6
|
+
$:.unshift File.join(File.dirname(__FILE__), ".", "lib")
|
|
7
|
+
require 'common_functions'
|
|
8
|
+
|
|
9
|
+
module EncryptionHelper
|
|
10
|
+
def self.generate_secret_key(keyname="appscale")
|
|
11
|
+
path="~/.appscale/#{keyname}.secret"
|
|
12
|
+
secret_key = ""
|
|
13
|
+
possible = "0123456789abcdefghijklmnopqrstuvxwyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
14
|
+
possibleLength = possible.length
|
|
15
|
+
|
|
16
|
+
32.times { |index|
|
|
17
|
+
secret_key << possible[rand(possibleLength)]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
full_path = File.expand_path(path)
|
|
21
|
+
File.open(full_path, "w") { |file|
|
|
22
|
+
file.puts(secret_key)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return secret_key, path
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.generate_ssh_key(verbose, outputLocation, name, infrastructure, force)
|
|
29
|
+
ec2_output = ""
|
|
30
|
+
loop {
|
|
31
|
+
sleep(10) # to avoid euca replay error message
|
|
32
|
+
ec2_output = CommonFunctions.shell("#{infrastructure}-add-keypair #{name} 2>&1")
|
|
33
|
+
break if ec2_output.include?("BEGIN RSA PRIVATE KEY")
|
|
34
|
+
if force
|
|
35
|
+
puts "Trying again. Saw this from #{infrastructure}-add-keypair: #{ec2_output}" if verbose
|
|
36
|
+
sleep(10)
|
|
37
|
+
delete_output = CommonFunctions.shell("#{infrastructure}-delete-keypair #{name} 2>&1")
|
|
38
|
+
puts "Saw this from #{infrastructure}-delete-keypair: #{delete_output}" if verbose
|
|
39
|
+
else
|
|
40
|
+
abort("The keyname you chose is already in the system. Please either run this tool again with the --force flag or run the following:\n#{infrastructure}-delete-keypair #{name}")
|
|
41
|
+
end
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# output is the ssh private key prepended with info we don't need
|
|
45
|
+
# delimited by the first \n, so rip it off first to get just the key
|
|
46
|
+
|
|
47
|
+
#first_newline = ec2_output.index("\n")
|
|
48
|
+
#ssh_private_key = ec2_output[first_newline+1, ec2_output.length-1]
|
|
49
|
+
|
|
50
|
+
if outputLocation.class == String
|
|
51
|
+
outputLocation = [outputLocation]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
outputLocation.each { |path|
|
|
55
|
+
fullPath = File.expand_path(path)
|
|
56
|
+
File.open(fullPath, "w") { |file|
|
|
57
|
+
file.puts(ec2_output)
|
|
58
|
+
}
|
|
59
|
+
FileUtils.chmod(0600, fullPath) # else ssh won't use the key
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.generate_pem_files(keyname)
|
|
66
|
+
key_loc = File.expand_path("~/.appscale/#{keyname}-key.pem")
|
|
67
|
+
cert_loc = File.expand_path("~/.appscale/#{keyname}-cert.pem")
|
|
68
|
+
|
|
69
|
+
key = OpenSSL::PKey::RSA.generate(2048)
|
|
70
|
+
pub = key.public_key
|
|
71
|
+
ca = OpenSSL::X509::Name.parse("/C=US/ST=Foo/L=Bar/O=AppScale/OU=User/CN=appscale.cs.ucsb.edu/emailAddress=test@test.com")
|
|
72
|
+
cert = OpenSSL::X509::Certificate.new
|
|
73
|
+
cert.version = 2
|
|
74
|
+
cert.serial = Time.now.to_i
|
|
75
|
+
cert.subject = ca
|
|
76
|
+
cert.issuer = ca
|
|
77
|
+
cert.public_key = pub
|
|
78
|
+
cert.not_before = Time.now
|
|
79
|
+
cert.not_after = Time.now + 3600
|
|
80
|
+
cert.sign(key, OpenSSL::Digest::SHA1.new)
|
|
81
|
+
|
|
82
|
+
File.open(key_loc, "w") { |f| f.write key.to_pem }
|
|
83
|
+
File.open(cert_loc, "w") { |f| f.write cert.to_pem }
|
|
84
|
+
return key_loc, cert_loc
|
|
85
|
+
end
|
|
86
|
+
end
|
data/lib/godinterface.rb
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
|
|
2
|
+
GOD_PORT = "17165"
|
|
3
|
+
|
|
4
|
+
module GodInterface
|
|
5
|
+
def self.start_god(remote_ip, remote_key)
|
|
6
|
+
self.run_god_command("god &", remote_ip, remote_key)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.start(watch, start_cmd, stop_cmd, ports, env_vars=nil, remote_ip=nil, remote_key=nil)
|
|
10
|
+
|
|
11
|
+
ports = [ports] unless ports.class == Array
|
|
12
|
+
|
|
13
|
+
prologue = <<BOO
|
|
14
|
+
WATCH = "#{watch}"
|
|
15
|
+
START_CMD = "#{start_cmd}"
|
|
16
|
+
STOP_CMD = "#{stop_cmd}"
|
|
17
|
+
PORTS = [#{ports.join(', ')}]
|
|
18
|
+
|
|
19
|
+
BOO
|
|
20
|
+
|
|
21
|
+
body = <<'BAZ'
|
|
22
|
+
PORTS.each do |port|
|
|
23
|
+
God.watch do |w|
|
|
24
|
+
w.name = "appscale-#{WATCH}-#{port}"
|
|
25
|
+
w.group = WATCH
|
|
26
|
+
w.interval = 30.seconds # default
|
|
27
|
+
w.start = START_CMD
|
|
28
|
+
w.stop = STOP_CMD
|
|
29
|
+
w.start_grace = 20.seconds
|
|
30
|
+
w.restart_grace = 20.seconds
|
|
31
|
+
w.log = "/var/log/appscale/#{WATCH}-#{port}.log"
|
|
32
|
+
w.pid_file = "/var/appscale/#{WATCH}-#{port}.pid"
|
|
33
|
+
|
|
34
|
+
w.behavior(:clean_pid_file)
|
|
35
|
+
|
|
36
|
+
w.start_if do |start|
|
|
37
|
+
start.condition(:process_running) do |c|
|
|
38
|
+
c.running = false
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
w.restart_if do |restart|
|
|
43
|
+
restart.condition(:memory_usage) do |c|
|
|
44
|
+
c.above = 150.megabytes
|
|
45
|
+
c.times = [3, 5] # 3 out of 5 intervals
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
restart.condition(:cpu_usage) do |c|
|
|
49
|
+
c.above = 50.percent
|
|
50
|
+
c.times = 5
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# lifecycle
|
|
55
|
+
w.lifecycle do |on|
|
|
56
|
+
on.condition(:flapping) do |c|
|
|
57
|
+
c.to_state = [:start, :restart]
|
|
58
|
+
c.times = 5
|
|
59
|
+
c.within = 5.minute
|
|
60
|
+
c.transition = :unmonitored
|
|
61
|
+
c.retry_in = 10.minutes
|
|
62
|
+
c.retry_times = 5
|
|
63
|
+
c.retry_within = 2.hours
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
BAZ
|
|
67
|
+
|
|
68
|
+
if !env_vars.nil? and !env_vars.empty?
|
|
69
|
+
env_vars_str = ""
|
|
70
|
+
|
|
71
|
+
env_vars.each { |k, v|
|
|
72
|
+
env_vars_str += " \"" + k + "\" => \"" + v + "\",\n"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
body += <<BOO
|
|
76
|
+
|
|
77
|
+
w.env = {
|
|
78
|
+
#{env_vars_str}
|
|
79
|
+
}
|
|
80
|
+
BOO
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
epilogue = <<BAZ
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
BAZ
|
|
87
|
+
|
|
88
|
+
config_file = prologue + body + epilogue
|
|
89
|
+
tempfile = "/tmp/god-#{rand(10000)}.god"
|
|
90
|
+
|
|
91
|
+
CommonFunctions.write_file(tempfile, config_file)
|
|
92
|
+
|
|
93
|
+
if remote_ip
|
|
94
|
+
CommonFunctions.scp_file(tempfile, tempfile, remote_ip, remote_key)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
if remote_ip
|
|
98
|
+
ip = remote_ip
|
|
99
|
+
else
|
|
100
|
+
ip = CommonFunctions.local_ip
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
#unless CommonFunctions.is_port_open?(ip, GOD_PORT, use_ssl=false)
|
|
104
|
+
# self.run_god_command("god", remote_ip, remote_key)
|
|
105
|
+
# sleep(5)
|
|
106
|
+
#end
|
|
107
|
+
|
|
108
|
+
self.run_god_command("god load #{tempfile}", remote_ip, remote_key)
|
|
109
|
+
|
|
110
|
+
sleep(5)
|
|
111
|
+
|
|
112
|
+
FileUtils.rm_f(tempfile)
|
|
113
|
+
if remote_ip
|
|
114
|
+
remove = "rm -rf #{tempfile}"
|
|
115
|
+
CommonFunctions.run_remote_command(ip, remove, remote_key, false)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
#god_info = "Starting #{watch} on ip #{ip}, port #{ports.join(', ')}" +
|
|
119
|
+
# " with start command [#{start_cmd}] and stop command [#{stop_cmd}]"
|
|
120
|
+
#puts god_info
|
|
121
|
+
|
|
122
|
+
self.run_god_command("god start #{watch}", remote_ip, remote_key)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def self.stop(watch, remote_ip=nil, remote_key=nil)
|
|
126
|
+
self.run_god_command("god stop #{watch}", remote_ip, remote_key)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def self.remove(watch, remote_ip=nil, remote_key=nil)
|
|
130
|
+
self.run_god_command("god remove #{watch}", remote_ip, remote_key)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def self.shutdown(remote_ip=nil, remote_key=nil)
|
|
134
|
+
%w{ uaserver pbserver memcached blobstore monitr loadbalancer }.each { |service|
|
|
135
|
+
self.run_god_command("god stop #{service}", remote_ip, remote_key)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
self.run_god_command("god terminate", remote_ip, remote_key)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
private
|
|
142
|
+
def self.run_god_command(cmd, ip, ssh_key)
|
|
143
|
+
local = ip.nil?
|
|
144
|
+
|
|
145
|
+
if local
|
|
146
|
+
puts cmd
|
|
147
|
+
else
|
|
148
|
+
CommonFunctions.run_remote_command(ip, cmd, ssh_key, true)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
data/lib/node_layout.rb
ADDED
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
#!/usr/bin/ruby
|
|
2
|
+
# Programmer: Jonathan Kupferman
|
|
3
|
+
# Updated by Chris Bunch to add hybrid cloud support
|
|
4
|
+
|
|
5
|
+
USED_SIMPLE_AND_ADVANCED_KEYS = "Used both simple and advanced layout roles." +
|
|
6
|
+
" Only simple (controller, servers) or advanced (master, appengine, etc) " +
|
|
7
|
+
"can be used"
|
|
8
|
+
NO_INPUT_YAML_REQUIRES_MIN_IMAGES = "If no input yaml is specified, " +
|
|
9
|
+
"min_images must be specified."
|
|
10
|
+
NO_INPUT_YAML_REQUIRES_MAX_IMAGES = "If no input yaml is specified, " +
|
|
11
|
+
"max_images must be specified."
|
|
12
|
+
INPUT_YAML_REQUIRED = "An input yaml file is required for Xen, KVM, and " +
|
|
13
|
+
"hybrid cloud deployments"
|
|
14
|
+
DUPLICATE_IPS = "You specified some IP addresses more than once, which is " +
|
|
15
|
+
"not allowed in simple deployments."
|
|
16
|
+
NO_CONTROLLER = "No controller was specified"
|
|
17
|
+
ONLY_ONE_CONTROLLER = "Only one controller is allowed"
|
|
18
|
+
|
|
19
|
+
NODE_ID_REGEX = /(node|cloud(\d+))-(\d+)/
|
|
20
|
+
DEFAULT_NUM_NODES = 1
|
|
21
|
+
VALID_ROLES = [:master, :appengine, :database, :shadow, :open] +
|
|
22
|
+
[:load_balancer, :login, :db_master, :db_slave, :zookeeper, :memcache] +
|
|
23
|
+
[:rabbitmq, :rabbitmq_master, :rabbitmq_slave]
|
|
24
|
+
|
|
25
|
+
class NodeLayout
|
|
26
|
+
SIMPLE_FORMAT_KEYS = [:controller, :servers]
|
|
27
|
+
ADVANCED_FORMAT_KEYS = [:master, :database, :appengine, :open, :login, :zookeeper, :memcache, :rabbitmq]
|
|
28
|
+
|
|
29
|
+
# Required options are: database_type
|
|
30
|
+
def initialize(input_yaml, options, skip_replication=false)
|
|
31
|
+
@input_yaml = (input_yaml.kind_of?(String) ? YAML.load(input_yaml) : input_yaml)
|
|
32
|
+
|
|
33
|
+
@infrastructure = options[:infrastructure]
|
|
34
|
+
@database_type = options[:database]
|
|
35
|
+
@database_type = @database_type.to_sym if !@database_type.nil?
|
|
36
|
+
@min_images = options[:min_images]
|
|
37
|
+
@max_images = options[:max_images]
|
|
38
|
+
@replication = options[:replication]
|
|
39
|
+
@read_factor = options[:read_factor]
|
|
40
|
+
@write_factor = options[:write_factor]
|
|
41
|
+
|
|
42
|
+
@nodes = []
|
|
43
|
+
@skip_replication = skip_replication
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def valid?
|
|
47
|
+
if is_simple_format?
|
|
48
|
+
valid_simple_format?[:result]
|
|
49
|
+
elsif is_advanced_format?
|
|
50
|
+
valid_advanced_format?[:result]
|
|
51
|
+
else
|
|
52
|
+
false
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def errors
|
|
57
|
+
return [] if valid?
|
|
58
|
+
|
|
59
|
+
if is_simple_format?
|
|
60
|
+
valid_simple_format?[:message]
|
|
61
|
+
elsif is_advanced_format?
|
|
62
|
+
valid_advanced_format?[:message]
|
|
63
|
+
elsif @input_yaml.nil?
|
|
64
|
+
[INPUT_YAML_REQUIRED]
|
|
65
|
+
else
|
|
66
|
+
keys = @input_yaml.keys
|
|
67
|
+
|
|
68
|
+
keys.each { |key|
|
|
69
|
+
if !(SIMPLE_FORMAT_KEYS.include?(key) || ADVANCED_FORMAT_KEYS.include?(key))
|
|
70
|
+
return ["The flag #{key} is not a supported flag"]
|
|
71
|
+
end
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return [USED_SIMPLE_AND_ADVANCED_KEYS]
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def is_simple_format?
|
|
79
|
+
if @input_yaml.nil?
|
|
80
|
+
if VALID_CLOUD_TYPES.include?(@infrastructure) and @infrastructure != "hybrid"
|
|
81
|
+
# When used with the cloud, the simple format doesn't require a yaml
|
|
82
|
+
# Note this is not so in the hybrid model - a yaml is required in
|
|
83
|
+
# that scenario.
|
|
84
|
+
return true
|
|
85
|
+
else
|
|
86
|
+
return false
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
@input_yaml.keys.each do |key|
|
|
91
|
+
return false if !SIMPLE_FORMAT_KEYS.include?(key)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
true
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def is_advanced_format?
|
|
98
|
+
return false if @input_yaml.nil?
|
|
99
|
+
|
|
100
|
+
@input_yaml.keys.each do |key|
|
|
101
|
+
return false if !ADVANCED_FORMAT_KEYS.include?(key)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
true
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def parse_ip(ip)
|
|
108
|
+
id, cloud = nil, nil
|
|
109
|
+
|
|
110
|
+
match = NODE_ID_REGEX.match(ip)
|
|
111
|
+
if match.nil?
|
|
112
|
+
id = ip
|
|
113
|
+
cloud = "not-cloud"
|
|
114
|
+
else
|
|
115
|
+
id = match[0]
|
|
116
|
+
cloud = match[1]
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
return id, cloud
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def valid_simple_format?
|
|
123
|
+
# We already computed the nodes, its valid
|
|
124
|
+
# cgb: an optimization to ensure we don't keep calling this
|
|
125
|
+
# when it always returns the same thing anyways
|
|
126
|
+
return valid if !@nodes.empty?
|
|
127
|
+
|
|
128
|
+
if @input_yaml.nil?
|
|
129
|
+
if VALID_CLOUD_TYPES.include?(@infrastructure) and @infrastructure != "hybrid"
|
|
130
|
+
if @min_images.nil?
|
|
131
|
+
return invalid(NO_INPUT_YAML_REQUIRES_MIN_IMAGES)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
if @max_images.nil?
|
|
135
|
+
return invalid(NO_INPUT_YAML_REQUIRES_MAX_IMAGES)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# No yaml was created so we will create a generic one and then allow it to be validated
|
|
139
|
+
@input_yaml = generate_cloud_layout
|
|
140
|
+
else
|
|
141
|
+
return invalid(INPUT_YAML_REQUIRED)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
nodes = []
|
|
146
|
+
@input_yaml.each_pair do |role, ips|
|
|
147
|
+
next if ips.nil?
|
|
148
|
+
|
|
149
|
+
ips.each do |ip|
|
|
150
|
+
id, cloud = parse_ip(ip)
|
|
151
|
+
node = SimpleNode.new id, cloud, [role]
|
|
152
|
+
|
|
153
|
+
# In simple deployments the db master and rabbitmq master is always on
|
|
154
|
+
# the shadow node, and db slave / rabbitmq slave is always on the other
|
|
155
|
+
# nodes
|
|
156
|
+
is_master = node.is_shadow?
|
|
157
|
+
node.add_db_role @database_type, is_master
|
|
158
|
+
node.add_rabbitmq_role is_master
|
|
159
|
+
|
|
160
|
+
return invalid(node.errors.join(",")) if !node.valid?
|
|
161
|
+
|
|
162
|
+
if VALID_CLOUD_TYPES.include?(@infrastructure)
|
|
163
|
+
error_message = "Invalid cloud node ID: #{node.id} \n" +
|
|
164
|
+
"Cloud node IDs must be in the format 'node-{IDNUMBER}'" +
|
|
165
|
+
"\nor of the form cloud{CLOUDNUMBER}-{IDNUMBER} for hybrid deployments"
|
|
166
|
+
return invalid(error_message) if NODE_ID_REGEX.match(node.id.to_s).nil?
|
|
167
|
+
else
|
|
168
|
+
# Xen/KVM should be using the ip address as the node id
|
|
169
|
+
error_message = "Invalid virtualized node ID: #{node.id} \n" +
|
|
170
|
+
"Virtualized node IDs must be a valid IP address"
|
|
171
|
+
return invalid(error_message) if IP_REGEX.match(node.id.to_s).nil?
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
nodes << node
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# make sure that the user hasn't erroneously specified the same ip
|
|
179
|
+
# address more than once
|
|
180
|
+
all_ips = @input_yaml.values.flatten
|
|
181
|
+
duplicate_ips = all_ips.length - all_ips.uniq.length
|
|
182
|
+
|
|
183
|
+
unless duplicate_ips.zero?
|
|
184
|
+
return invalid(DUPLICATE_IPS)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
if nodes.length == 1
|
|
188
|
+
# Singleton node should be master and app engine
|
|
189
|
+
nodes.first.add_role :appengine
|
|
190
|
+
nodes.first.add_role :memcache
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# controller -> shadow
|
|
194
|
+
controller_count = 0
|
|
195
|
+
nodes.each do |node|
|
|
196
|
+
if node.is_shadow?
|
|
197
|
+
controller_count += 1
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
if controller_count == 0
|
|
202
|
+
return invalid(NO_CONTROLLER)
|
|
203
|
+
elsif controller_count > 1
|
|
204
|
+
return invalid(ONLY_ONE_CONTROLLER)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
database_count = 0
|
|
208
|
+
nodes.each do |node|
|
|
209
|
+
if node.is_database?
|
|
210
|
+
database_count += 1
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
if @skip_replication
|
|
215
|
+
@nodes = nodes
|
|
216
|
+
return valid
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
rep = valid_database_replication? nodes
|
|
220
|
+
return rep unless rep[:result]
|
|
221
|
+
|
|
222
|
+
# Wait until it is validated to assign it
|
|
223
|
+
@nodes = nodes
|
|
224
|
+
valid
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def valid_advanced_format?
|
|
228
|
+
# We already computed the nodes, its valid
|
|
229
|
+
return valid if !@nodes.empty?
|
|
230
|
+
|
|
231
|
+
node_hash = {}
|
|
232
|
+
@input_yaml.each_pair do |role, ips|
|
|
233
|
+
|
|
234
|
+
ips.each_with_index do |ip, index|
|
|
235
|
+
node = nil
|
|
236
|
+
if node_hash[ip].nil?
|
|
237
|
+
id, cloud = parse_ip(ip)
|
|
238
|
+
node = AdvancedNode.new(id, cloud)
|
|
239
|
+
else
|
|
240
|
+
node = node_hash[ip]
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
if role.to_sym == :database
|
|
244
|
+
# The first database node is the master
|
|
245
|
+
is_master = index.zero?
|
|
246
|
+
node.add_db_role @database_type, is_master
|
|
247
|
+
elsif role.to_sym == :db_master
|
|
248
|
+
node.add_role :zookeeper
|
|
249
|
+
node.add_role role
|
|
250
|
+
elsif role.to_sym == :rabbitmq
|
|
251
|
+
# Like the database, the first rabbitmq node is the master
|
|
252
|
+
is_master = index.zero?
|
|
253
|
+
node.add_role :rabbitmq
|
|
254
|
+
node.add_rabbitmq_role is_master
|
|
255
|
+
else
|
|
256
|
+
node.add_role role
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
node_hash[ip] = node
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Dont need the hash any more, make a nodes list
|
|
264
|
+
nodes = node_hash.values
|
|
265
|
+
|
|
266
|
+
nodes.each do |node|
|
|
267
|
+
return invalid(node.errors.join(",")) unless node.valid?
|
|
268
|
+
|
|
269
|
+
if VALID_CLOUD_TYPES.include?(@infrastructure)
|
|
270
|
+
error_message = "Invalid cloud node ID: #{node.id} \n" +
|
|
271
|
+
"Cloud node ID must be in the format 'node-{IDNUMBER}'" +
|
|
272
|
+
"\nor of the form cloud{CLOUDNUMBER}-{IDNUMBER} for hybrid deployments"
|
|
273
|
+
return invalid(error_message) if NODE_ID_REGEX.match(node.id.to_s).nil?
|
|
274
|
+
else
|
|
275
|
+
# Xen/KVM should be using the ip address as the node id
|
|
276
|
+
error_message = "Invalid virtualized node ID: #{node.id} \n" +
|
|
277
|
+
"Virtualized node IDs must be a valid IP address"
|
|
278
|
+
return invalid(error_message) if IP_REGEX.match(node.id.to_s).nil?
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
master_nodes = nodes.select { |node| node.is_shadow? }.compact
|
|
283
|
+
|
|
284
|
+
# need exactly one master
|
|
285
|
+
if master_nodes.length == 0
|
|
286
|
+
return invalid("No master was specified")
|
|
287
|
+
elsif master_nodes.length > 1
|
|
288
|
+
return invalid("Only one master is allowed")
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
master_node = master_nodes.first
|
|
292
|
+
|
|
293
|
+
login_node = nodes.select { |node| node.is_login? }.compact
|
|
294
|
+
# If a login node was not specified, make the master into the login node
|
|
295
|
+
if login_node.empty?
|
|
296
|
+
master_node.add_role :login
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
appengine_count = 0
|
|
300
|
+
nodes.each do |node|
|
|
301
|
+
if node.is_appengine?
|
|
302
|
+
appengine_count += 1
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
if appengine_count < 1
|
|
307
|
+
return invalid("Not enough appengine nodes were provided.")
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
memcache_count = 0
|
|
311
|
+
nodes.each do |node|
|
|
312
|
+
if node.is_memcache?
|
|
313
|
+
memcache_count += 1
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# if no memcache nodes were specified, make all appengine nodes
|
|
318
|
+
# into memcache nodes
|
|
319
|
+
if memcache_count < 1
|
|
320
|
+
nodes.each { |node|
|
|
321
|
+
node.add_role :memcache if node.is_appengine?
|
|
322
|
+
}
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
if VALID_CLOUD_TYPES.include?(@infrastructure)
|
|
326
|
+
# If min and max aren't specified, they default to the number of nodes in the system
|
|
327
|
+
@min_images ||= nodes.length
|
|
328
|
+
@max_images ||= nodes.length
|
|
329
|
+
|
|
330
|
+
# TODO: look into if that first guard is really necessary with the preceding lines
|
|
331
|
+
|
|
332
|
+
if @min_images && nodes.length < @min_images
|
|
333
|
+
return invalid("Too few nodes were provided, #{nodes.length} were specified but #{@min_images} was the minimum")
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
if @max_images && nodes.length > @max_images
|
|
337
|
+
return invalid("Too many nodes were provided, #{nodes.length} were specified but #{@max_images} was the maximum")
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
zookeeper_count = 0
|
|
342
|
+
nodes.each do |node|
|
|
343
|
+
if node.is_zookeeper?
|
|
344
|
+
zookeeper_count += 1
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
master_node.add_role :zookeeper if zookeeper_count.zero?
|
|
348
|
+
|
|
349
|
+
# If no rabbitmq nodes are specified, make the shadow the rabbitmq_master
|
|
350
|
+
rabbitmq_count = 0
|
|
351
|
+
nodes.each do |node|
|
|
352
|
+
if node.is_rabbitmq?
|
|
353
|
+
rabbitmq_count += 1
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
if rabbitmq_count.zero?
|
|
357
|
+
master_node.add_role :rabbitmq
|
|
358
|
+
master_node.add_role :rabbitmq_master
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# Any node that runs appengine needs rabbitmq to dispatch task requests to
|
|
362
|
+
# It's safe to add the slave role since we ensure above that somebody
|
|
363
|
+
# already has the master role
|
|
364
|
+
nodes.each do |node|
|
|
365
|
+
if node.is_appengine? and !node.is_rabbitmq?
|
|
366
|
+
node.add_role :rabbitmq_slave
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
database_count = 0
|
|
371
|
+
nodes.each do |node|
|
|
372
|
+
if node.is_database?
|
|
373
|
+
database_count += 1
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
if @skip_replication
|
|
378
|
+
@nodes = nodes
|
|
379
|
+
return valid
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
rep = valid_database_replication? nodes
|
|
383
|
+
return rep unless rep[:result]
|
|
384
|
+
|
|
385
|
+
# Wait until it is validated to assign it
|
|
386
|
+
@nodes = nodes
|
|
387
|
+
|
|
388
|
+
return valid
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def valid_database_replication? nodes
|
|
392
|
+
database_node_count = 0
|
|
393
|
+
nodes.each do |node|
|
|
394
|
+
if node.is_database? or node.is_db_master?
|
|
395
|
+
database_node_count += 1
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
if database_node_count.zero?
|
|
400
|
+
return invalid("At least one database node must be provided.")
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
if @replication.nil?
|
|
404
|
+
if database_node_count > 3
|
|
405
|
+
# If there are a lot of database nodes, we default to 3x replication
|
|
406
|
+
@replication = 3
|
|
407
|
+
else
|
|
408
|
+
# If there are only a few nodes, replicate to each one of the nodes
|
|
409
|
+
@replication = database_node_count
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
if @replication > database_node_count
|
|
414
|
+
return invalid("The provided replication factor is too high. The replication factor (-n flag) cannot be greater than the number of database nodes.")
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
# Perform all the database specific checks here
|
|
418
|
+
if @database_type == :mysql && database_node_count % @replication != 0
|
|
419
|
+
return invalid("MySQL requires that the amount of replication be divisible by the number of nodes (e.g. with 6 nodes, 2 or 3 times replication). You specified #{database_node_count} database nodes which is not divisible by #{@replication} times replication.")
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
if @database_type == :voldemort
|
|
423
|
+
@read_factor ||= @replication
|
|
424
|
+
@write_factor ||= @replication
|
|
425
|
+
|
|
426
|
+
if @read_factor > @replication
|
|
427
|
+
return invalid("The provided read factor is too high. The read factor (-r flag) cannot be greater than the replication factor.")
|
|
428
|
+
elsif @write_factor > @replication
|
|
429
|
+
return invalid("The provided write factor is too high. The write factor (-w flag) cannot be greater than the replication factor.")
|
|
430
|
+
end
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
if @database_type == :simpledb
|
|
434
|
+
if ENV['SIMPLEDB_ACCESS_KEY'].nil?
|
|
435
|
+
return invalid("SimpleDB deployments require that the environment variable SIMPLEDB_ACCESS_KEY be set to your AWS access key.")
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
if ENV['SIMPLEDB_SECRET_KEY'].nil?
|
|
439
|
+
return invalid("SimpleDB deployments require that the environment variable SIMPLEDB_SECRET_KEY be set to your AWS secret key.")
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
valid
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
# Generates an yaml file for non-hybrid cloud layouts which don't have them
|
|
447
|
+
def generate_cloud_layout
|
|
448
|
+
layout = {:controller => "node-0"}
|
|
449
|
+
servers = []
|
|
450
|
+
num_slaves = @min_images - 1
|
|
451
|
+
num_slaves.times do |i|
|
|
452
|
+
servers << "node-#{i+1}"
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
layout[:servers] = servers
|
|
456
|
+
YAML.load(layout.to_yaml)
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
def replication_factor
|
|
460
|
+
return nil unless valid?
|
|
461
|
+
|
|
462
|
+
@replication
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
# TODO: can we just replace the if w/ unless and change ! to = ?
|
|
466
|
+
# or does that not exactly work due to the || ?
|
|
467
|
+
|
|
468
|
+
def read_factor
|
|
469
|
+
return nil if !valid? || @database_type != :voldemort
|
|
470
|
+
|
|
471
|
+
@read_factor
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
def write_factor
|
|
475
|
+
return nil if !valid? || @database_type != :voldemort
|
|
476
|
+
|
|
477
|
+
@write_factor
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
def min_images
|
|
481
|
+
return nil unless valid?
|
|
482
|
+
|
|
483
|
+
@min_images
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
def max_images
|
|
487
|
+
return nil unless valid?
|
|
488
|
+
|
|
489
|
+
@max_images
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
def nodes
|
|
493
|
+
return [] unless valid?
|
|
494
|
+
|
|
495
|
+
# Since the valid? check has succeded @nodes has been initialized
|
|
496
|
+
@nodes
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
# head node -> shadow
|
|
500
|
+
def head_node
|
|
501
|
+
return nil unless valid?
|
|
502
|
+
|
|
503
|
+
head_node = @nodes.select { |n| n.is_shadow? }.compact
|
|
504
|
+
|
|
505
|
+
# TODO: is the last guard necessary?
|
|
506
|
+
head_node.empty? ? nil : head_node[0]
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
def other_nodes
|
|
510
|
+
return [] unless valid?
|
|
511
|
+
|
|
512
|
+
other_nodes = @nodes.select { |n| !n.is_shadow? }.compact
|
|
513
|
+
|
|
514
|
+
other_nodes.empty? ? [] : other_nodes
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
def db_master
|
|
518
|
+
return nil unless valid?
|
|
519
|
+
|
|
520
|
+
db_master = @nodes.select { |n| n.is_db_master? }.compact
|
|
521
|
+
|
|
522
|
+
db_master.empty? ? nil : db_master[0]
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
def login_node
|
|
526
|
+
return nil unless valid?
|
|
527
|
+
|
|
528
|
+
login = @nodes.select { |n| n.is_login? }.compact
|
|
529
|
+
|
|
530
|
+
login.empty? ? nil : login[0]
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def to_hash
|
|
535
|
+
result = {}
|
|
536
|
+
# Put all nodes except the head node in the hash
|
|
537
|
+
other_nodes.each do |node|
|
|
538
|
+
result[node.id] = node.roles.join(":")
|
|
539
|
+
end
|
|
540
|
+
result
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
private
|
|
544
|
+
def valid message=nil
|
|
545
|
+
{ :result => true, :message => message }
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
def invalid message
|
|
549
|
+
{ :result => false, :message => message }
|
|
550
|
+
end
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
class Node
|
|
554
|
+
attr_accessor :roles, :id, :cloud
|
|
555
|
+
|
|
556
|
+
def initialize id, cloud, roles=[]
|
|
557
|
+
# For Xen/KVM id is the public ip address
|
|
558
|
+
# For clouds, id is node-X since the ip is not known
|
|
559
|
+
@id = id
|
|
560
|
+
@cloud = cloud
|
|
561
|
+
@roles = roles.map { |r| r.to_sym }
|
|
562
|
+
|
|
563
|
+
expand_roles
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
def add_db_role db_type, is_master
|
|
567
|
+
if is_master
|
|
568
|
+
add_role :db_master
|
|
569
|
+
add_role :zookeeper
|
|
570
|
+
else
|
|
571
|
+
add_role :db_slave
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
def add_rabbitmq_role is_master
|
|
576
|
+
if is_master
|
|
577
|
+
add_role :rabbitmq_master
|
|
578
|
+
else
|
|
579
|
+
add_role :rabbitmq_slave
|
|
580
|
+
end
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
def add_role role
|
|
584
|
+
@roles << role.to_sym
|
|
585
|
+
expand_roles
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
VALID_ROLES.each do |role|
|
|
589
|
+
method = "is_#{role.to_s}?"
|
|
590
|
+
send :define_method, method do
|
|
591
|
+
@roles.include?(role)
|
|
592
|
+
end
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
def valid?
|
|
596
|
+
self.errors.empty?
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
def errors
|
|
600
|
+
@roles.map { |r| "Invalid role: #{r}" if !VALID_ROLES.include?(r) }.compact
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
def expand_roles
|
|
604
|
+
error_msg = "Expand roles should never be called on a node type." + \
|
|
605
|
+
" All nodes should be either a SimpleNode or AdvancedNode"
|
|
606
|
+
raise RuntimeError error_msg
|
|
607
|
+
end
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
class SimpleNode < Node
|
|
612
|
+
private
|
|
613
|
+
def expand_roles
|
|
614
|
+
if @roles.include?(:controller)
|
|
615
|
+
@roles.delete(:controller)
|
|
616
|
+
@roles << :shadow
|
|
617
|
+
@roles << :load_balancer
|
|
618
|
+
@roles << :database
|
|
619
|
+
@roles << :memcache # the database needs memcache
|
|
620
|
+
@roles << :login
|
|
621
|
+
@roles << :zookeeper
|
|
622
|
+
@roles << :rabbitmq
|
|
623
|
+
end
|
|
624
|
+
|
|
625
|
+
# If they specify a servers role, expand it out to
|
|
626
|
+
# be database, appengine, and memcache
|
|
627
|
+
if @roles.include?(:servers)
|
|
628
|
+
@roles.delete(:servers)
|
|
629
|
+
@roles << :appengine
|
|
630
|
+
@roles << :memcache
|
|
631
|
+
@roles << :database
|
|
632
|
+
@roles << :load_balancer
|
|
633
|
+
@roles << :rabbitmq
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
@roles.uniq!
|
|
637
|
+
end
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
class AdvancedNode < Node
|
|
641
|
+
private
|
|
642
|
+
def expand_roles
|
|
643
|
+
# make sure that deleting here doesn't screw things up
|
|
644
|
+
if @roles.include?(:master)
|
|
645
|
+
@roles.delete(:master)
|
|
646
|
+
@roles << :shadow
|
|
647
|
+
@roles << :load_balancer
|
|
648
|
+
@roles << :zookeeper
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
if @roles.include?(:login)
|
|
652
|
+
@roles << :load_balancer
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
if @roles.include?(:appengine)
|
|
656
|
+
@roles << :load_balancer
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
if @roles.include?(:database)
|
|
660
|
+
@roles << :memcache
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
@roles.uniq!
|
|
664
|
+
end
|
|
665
|
+
end
|