auser-poolparty 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. data/CHANGELOG +12 -0
  2. data/Manifest +115 -0
  3. data/README.txt +140 -0
  4. data/Rakefile +27 -0
  5. data/bin/instance +61 -0
  6. data/bin/pool +62 -0
  7. data/config/cloud_master_takeover +17 -0
  8. data/config/create_proxy_ami.sh +582 -0
  9. data/config/haproxy.conf +29 -0
  10. data/config/heartbeat.conf +8 -0
  11. data/config/heartbeat_authkeys.conf +2 -0
  12. data/config/installers/ubuntu_install.sh +77 -0
  13. data/config/monit/haproxy.monit.conf +7 -0
  14. data/config/monit/nginx.monit.conf +0 -0
  15. data/config/monit.conf +9 -0
  16. data/config/nginx.conf +24 -0
  17. data/config/reconfigure_instances_script.sh +18 -0
  18. data/config/sample-config.yml +23 -0
  19. data/config/scp_instances_script.sh +12 -0
  20. data/lib/core/array.rb +13 -0
  21. data/lib/core/exception.rb +9 -0
  22. data/lib/core/float.rb +13 -0
  23. data/lib/core/hash.rb +11 -0
  24. data/lib/core/kernel.rb +12 -0
  25. data/lib/core/module.rb +22 -0
  26. data/lib/core/object.rb +18 -0
  27. data/lib/core/proc.rb +15 -0
  28. data/lib/core/string.rb +49 -0
  29. data/lib/core/time.rb +41 -0
  30. data/lib/modules/callback.rb +133 -0
  31. data/lib/modules/ec2_wrapper.rb +82 -0
  32. data/lib/modules/safe_instance.rb +31 -0
  33. data/lib/modules/vlad_override.rb +82 -0
  34. data/lib/poolparty/application.rb +170 -0
  35. data/lib/poolparty/init.rb +6 -0
  36. data/lib/poolparty/master.rb +329 -0
  37. data/lib/poolparty/monitors/cpu.rb +19 -0
  38. data/lib/poolparty/monitors/memory.rb +26 -0
  39. data/lib/poolparty/monitors/web.rb +23 -0
  40. data/lib/poolparty/monitors.rb +13 -0
  41. data/lib/poolparty/optioner.rb +16 -0
  42. data/lib/poolparty/plugin.rb +43 -0
  43. data/lib/poolparty/plugin_manager.rb +67 -0
  44. data/lib/poolparty/provider/packages/essential.rb +6 -0
  45. data/lib/poolparty/provider/packages/git.rb +4 -0
  46. data/lib/poolparty/provider/packages/haproxy.rb +20 -0
  47. data/lib/poolparty/provider/packages/heartbeat.rb +4 -0
  48. data/lib/poolparty/provider/packages/monit.rb +6 -0
  49. data/lib/poolparty/provider/packages/rsync.rb +4 -0
  50. data/lib/poolparty/provider/packages/ruby.rb +37 -0
  51. data/lib/poolparty/provider/packages/s3fuse.rb +11 -0
  52. data/lib/poolparty/provider/provider.rb +60 -0
  53. data/lib/poolparty/provider.rb +2 -0
  54. data/lib/poolparty/remote_instance.rb +216 -0
  55. data/lib/poolparty/remoter.rb +106 -0
  56. data/lib/poolparty/remoting.rb +112 -0
  57. data/lib/poolparty/scheduler.rb +103 -0
  58. data/lib/poolparty/tasks/cloud.rake +57 -0
  59. data/lib/poolparty/tasks/development.rake +38 -0
  60. data/lib/poolparty/tasks/ec2.rake +20 -0
  61. data/lib/poolparty/tasks/instance.rake +63 -0
  62. data/lib/poolparty/tasks/plugins.rake +30 -0
  63. data/lib/poolparty/tasks/server.rake +42 -0
  64. data/lib/poolparty/tasks.rb +29 -0
  65. data/lib/poolparty/tmp.rb +46 -0
  66. data/lib/poolparty.rb +105 -0
  67. data/lib/s3/s3_object_store_folders.rb +44 -0
  68. data/misc/basics_tutorial.txt +142 -0
  69. data/poolparty.gemspec +72 -0
  70. data/spec/application_spec.rb +39 -0
  71. data/spec/callback_spec.rb +194 -0
  72. data/spec/core_spec.rb +15 -0
  73. data/spec/helpers/ec2_mock.rb +44 -0
  74. data/spec/kernel_spec.rb +11 -0
  75. data/spec/master_spec.rb +203 -0
  76. data/spec/monitors/cpu_monitor_spec.rb +38 -0
  77. data/spec/monitors/memory_spec.rb +50 -0
  78. data/spec/monitors/misc_monitor_spec.rb +50 -0
  79. data/spec/monitors/web_spec.rb +39 -0
  80. data/spec/optioner_spec.rb +22 -0
  81. data/spec/plugin_manager_spec.rb +31 -0
  82. data/spec/plugin_spec.rb +101 -0
  83. data/spec/pool_binary_spec.rb +10 -0
  84. data/spec/poolparty_spec.rb +15 -0
  85. data/spec/provider_spec.rb +17 -0
  86. data/spec/remote_instance_spec.rb +149 -0
  87. data/spec/remoter_spec.rb +65 -0
  88. data/spec/remoting_spec.rb +84 -0
  89. data/spec/scheduler_spec.rb +75 -0
  90. data/spec/spec_helper.rb +39 -0
  91. data/spec/string_spec.rb +28 -0
  92. data/web/static/conf/nginx.conf +22 -0
  93. data/web/static/site/images/balloon.png +0 -0
  94. data/web/static/site/images/cb.png +0 -0
  95. data/web/static/site/images/clouds.png +0 -0
  96. data/web/static/site/images/railsconf_preso_img.png +0 -0
  97. data/web/static/site/index.html +71 -0
  98. data/web/static/site/javascripts/application.js +3 -0
  99. data/web/static/site/javascripts/corner.js +178 -0
  100. data/web/static/site/javascripts/jquery-1.2.6.pack.js +11 -0
  101. data/web/static/site/misc.html +42 -0
  102. data/web/static/site/storage/pool_party_presentation.pdf +0 -0
  103. data/web/static/site/stylesheets/application.css +100 -0
  104. data/web/static/site/stylesheets/reset.css +17 -0
  105. data/web/static/src/layouts/application.haml +25 -0
  106. data/web/static/src/pages/index.haml +25 -0
  107. data/web/static/src/pages/misc.haml +5 -0
  108. data/web/static/src/stylesheets/application.sass +100 -0
  109. metadata +260 -0
@@ -0,0 +1,82 @@
1
+ require "vlad"
2
+ class Rake::RemoteTask < Rake::Task
3
+ def run command
4
+ cmd = [ssh_cmd, ssh_flags, target_host, command].compact
5
+ result = []
6
+
7
+ warn cmd.join(' ') if $TRACE
8
+
9
+ pid, inn, out, err = popen4(*cmd.join(" "))
10
+
11
+ inn.sync = true
12
+ streams = [out, err]
13
+ out_stream = {
14
+ out => $stdout,
15
+ err => $stderr,
16
+ }
17
+
18
+ # Handle process termination ourselves
19
+ status = nil
20
+ Thread.start do
21
+ status = Process.waitpid2(pid).last
22
+ end
23
+
24
+ until streams.empty? do
25
+ # don't busy loop
26
+ selected, = select streams, nil, nil, 0.1
27
+
28
+ next if selected.nil? or selected.empty?
29
+
30
+ selected.each do |stream|
31
+ if stream.eof? then
32
+ streams.delete stream if status # we've quit, so no more writing
33
+ next
34
+ end
35
+
36
+ data = stream.readpartial(1024)
37
+ out_stream[stream].write data
38
+
39
+ if stream == err and data =~ /^Password:/ then
40
+ inn.puts sudo_password
41
+ data << "\n"
42
+ $stderr.write "\n"
43
+ end
44
+
45
+ result << data
46
+ end
47
+ end
48
+
49
+ PoolParty.message "execution failed with status #{status.exitstatus}: #{cmd.join ' '}" unless status.success?
50
+
51
+ result.join
52
+ end
53
+
54
+ def rsync local, remote
55
+ cmd = [rsync_cmd, rsync_flags, local, "#{@target_host}:#{remote}"].flatten.compact
56
+
57
+ success = system(*cmd.join(" "))
58
+
59
+ unless success then
60
+ raise Vlad::CommandFailedError, "execution failed: #{cmd.join ' '}"
61
+ end
62
+ end
63
+ def set name, val = nil, &b
64
+ rt.set name, val, &b
65
+ end
66
+ def rt
67
+ @rt ||= Rake::RemoteTask
68
+ end
69
+
70
+ def target_hosts
71
+ if hosts = ENV["HOSTS"] then
72
+ hosts.strip.gsub(/\s+/, '').split(",")
73
+ elsif options[:single]
74
+ @roles = {}; @roles[:app] = {}
75
+ @roles[:app][options[:single]] = options[:single]
76
+ roles = Rake::RemoteTask.hosts_for(@roles)
77
+ else
78
+ roles = options[:roles]
79
+ roles ? Rake::RemoteTask.hosts_for(roles) : Rake::RemoteTask.all_hosts
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,170 @@
1
+ =begin rdoc
2
+ Application
3
+ This handles user interaction
4
+ =end
5
+ module PoolParty
6
+ class Application
7
+ class << self
8
+ attr_accessor :verbose, :options
9
+
10
+ # The application options
11
+ def options(opts={})
12
+ @options ||= make_options(opts)
13
+ end
14
+ # Make the options with the config_file overrides included
15
+ # Default config file assumed to be at config/config.yml
16
+ def make_options(opts={})
17
+ loading_options = opts.delete(:optsparse) || {}
18
+ loading_options.merge!( {:argv => opts.delete(:argv)} )
19
+
20
+ load_options!(loading_options)
21
+ # default_options.merge!(opts)
22
+ # If the config_file options are specified and not empty
23
+ unless default_options[:config_file].nil? || default_options[:config_file].empty?
24
+ require "yaml"
25
+ # Try loading the file if it exists
26
+ filedata = open(default_options[:config_file]).read if File.file?(default_options[:config_file])
27
+ default_options.merge!( YAML.load(filedata) ) if filedata # We want the command-line to overwrite the config file
28
+ end
29
+
30
+ OpenStruct.new(default_options.merge(opts))
31
+ end
32
+
33
+ # Load options via commandline
34
+ def load_options!(opts={})
35
+ require 'optparse'
36
+ OptionParser.new do |op|
37
+ op.banner = opts[:banner] if opts[:banner]
38
+ op.on('-A key', '--access-key key', "Ec2 access key (ENV['ACCESS_KEY'])") { |key| default_options[:access_key] = key }
39
+ op.on('-S key', '--secret-access-key key', "Ec2 secret access key (ENV['SECRET_ACCESS_KEY'])") { |key| default_options[:secret_access_key] = key }
40
+ op.on('-I ami', '--image-id id', "AMI instance (default: 'ami-4a46a323')") {|id| default_options[:ami] = id }
41
+ op.on('-k keypair', '--keypair name', "Keypair name (ENV['KEYPAIR_NAME'])") { |key| default_options[:keypair] = key }
42
+ op.on('-b bucket', '--bucket bucket', "Application bucket") { |bucket| default_options[:shared_bucket] = bucket }
43
+ op.on('-D ec2 directory', '--ec2-dir dir', "Directory with ec2 data (default: '~/.ec2')") {|id| default_options[:ec2_dir] = id }
44
+ op.on('-r names', '--services names', "Monitored services (default: '')") {|id| default_options[:services] = id }
45
+ op.on('-c file', '--config-file file', "Config file (default: '')") {|file| default_options[:config_file] = file }
46
+ op.on('-l plugin_dir', '--plugin-dir dir', "Plugin directory (default: '')") {|file| default_options[:plugin_dir] = file }
47
+ op.on('-p port', '--host_port port', "Run on specific host_port (default: 7788)") { |host_port| default_options[:host_port] = host_port }
48
+ op.on('-m monitors', '--monitors names', "Monitor instances using (default: 'web,memory,cpu')") {|s| default_options[:monitor_load_on] = s }
49
+ op.on('-o port', '--client_port port', "Run on specific client_port (default: 7788)") { |client_port| default_options[:client_port] = client_port }
50
+ op.on('-O os', '--os os', "Configure for os (default: ubuntu)") { |os| default_options[:os] = os }
51
+ op.on('-e env', '--environment env', "Run on the specific environment (default: development)") { |env| default_options[:env] = env }
52
+ op.on('-a address', '--public-ip address', "Associate this public address with the master node") {|s| default_options[:public_ip] = s}
53
+ op.on('-s size', '--size size', "Run specific sized instance") {|s| default_options[:size] = s}
54
+ op.on('-n name', '--name name', "Application name") {|n| default_options[:app_name] = n}
55
+ op.on('-u username', '--username name', "Login with the user (default: root)") {|s| default_options[:user] = s}
56
+ op.on('-d user-data','--user-data data', "Extra data to send each of the instances (default: "")") { |data| default_options[:user_data] = data }
57
+ op.on('-t seconds', '--polling-time', "Time between polling in seconds (default 50)") {|t| default_options[:polling_time] = t }
58
+ op.on('-v', '--[no-]verbose', 'Run verbosely (default: false)') {|v| default_options[:verbose] = true}
59
+ op.on('-i number', '--minimum-instances', "The minimum number of instances to run at all times (default 1)") {|i| default_options[:minimum_instances] = i.to_i}
60
+ op.on('-x number', '--maximum-instances', "The maximum number of instances to run (default 3)") {|x| default_options[:maximum_instances] = x.to_i}
61
+
62
+ op.on_tail("-V", "Show version") do
63
+ puts Application.version
64
+ exit
65
+ end
66
+ op.on_tail("-h", "-?", "Show this message") do
67
+ puts op
68
+ exit
69
+ end
70
+ end.parse!(opts[:argv] ? opts.delete(:argv) : ARGV.dup)
71
+ end
72
+
73
+ # Basic default options
74
+ # All can be overridden by the command line
75
+ # or in a config.yml file
76
+ def default_options
77
+ @default_options ||= {
78
+ :app_name => "application_name",
79
+ :host_port => 80,
80
+ :client_port => 8001,
81
+ :environment => 'development',
82
+ :verbose => false,
83
+ :logging => true,
84
+ :size => "m1.small",
85
+ :polling_time => "30.seconds",
86
+ :user_data => "",
87
+ :heavy_load => 0.80,
88
+ :light_load => 0.15,
89
+ :minimum_instances => 2,
90
+ :maximum_instances => 4,
91
+ :public_ip => "",
92
+ :access_key => ENV["AWS_ACCESS_KEY_ID"],
93
+ :secret_access_key => ENV["AWS_SECRET_ACCESS_ID"],
94
+ :config_file => if ENV["CONFIG_FILE"] && !ENV["CONFIG_FILE"].empty?
95
+ ENV["CONFIG_FILE"]
96
+ elsif File.file?("config/config.yml")
97
+ "config/config.yml"
98
+ else
99
+ nil
100
+ end,
101
+ :username => "root",
102
+ :ec2_dir => ENV["EC2_HOME"],
103
+ :keypair => ENV["KEYPAIR_NAME"],
104
+ :ami => 'ami-4a46a323',
105
+ :shared_bucket => "",
106
+ :services => "",
107
+ :expand_when => "web_usage < 1.5\n memory > 0.85",
108
+ :contract_when => "cpu < 0.20\n memory < 0.10",
109
+ :os => "ubuntu",
110
+ :plugin_dir => "vendor",
111
+ :install_on_load => true
112
+ }
113
+ end
114
+ # Services monitored by Heartbeat
115
+ # Always at least monitors haproxy
116
+ def managed_services
117
+ "#{services}"
118
+ end
119
+ def master_managed_services
120
+ "cloud_master_takeover #{services}"
121
+ end
122
+ def launching_user_data
123
+ {:polling_time => polling_time}.to_yaml
124
+ end
125
+ # Keypair path
126
+ # Idiom:
127
+ # /Users/username/.ec2/id_rsa-name
128
+ def keypair_path
129
+ "#{ec2_dir}/id_rsa#{keypair ? "-#{keypair}" : "" }"
130
+ end
131
+ # Are we in development or test mode
132
+ %w(development production test).each do |env|
133
+ eval <<-EOE
134
+ def #{env}?
135
+ environment == '#{env}'
136
+ end
137
+ EOE
138
+ end
139
+ def environment=(env)
140
+ environment = env
141
+ end
142
+ def maintain_pid_path
143
+ "/var/run/pool_maintain.pid"
144
+ end
145
+ %w(scp_instances_script reconfigure_instances_script).each do |file|
146
+ define_method "sh_#{file}" do
147
+ File.join(File.dirname(__FILE__), "../..", "config", "#{file}.sh")
148
+ end
149
+ end
150
+ # Standard configuration files
151
+ %w(haproxy monit heartbeat heartbeat_authkeys).each do |file|
152
+ define_method "#{file}_config_file" do
153
+ File.join(File.dirname(__FILE__), "../..", "config", "#{file}.conf")
154
+ end
155
+ end
156
+ def version
157
+ PoolParty::Version::STRING
158
+ end
159
+ def install_on_load?
160
+ options.install_on_load == true
161
+ end
162
+ # Call the options from the Application
163
+ def method_missing(m,*args)
164
+ options.methods.include?("#{m}") ? options.send(m,args) : super
165
+ end
166
+ end
167
+
168
+ end
169
+
170
+ end
@@ -0,0 +1,6 @@
1
+ =begin rdoc
2
+ Load the files in order
3
+ =end
4
+ %w(optioner application monitors scheduler provider remoter remoting remote_instance master tasks plugin plugin_manager).each do |f|
5
+ require File.join(File.dirname(__FILE__), f)
6
+ end
@@ -0,0 +1,329 @@
1
+ =begin rdoc
2
+ The basic master for PoolParty
3
+ =end
4
+ module PoolParty
5
+ class Master < Remoting
6
+ include Aska
7
+ include Callbacks
8
+ include Monitors
9
+ include Remoter
10
+
11
+ def initialize
12
+ super
13
+
14
+ self.class.send :rules, :contract_when, Application.options.contract_when
15
+ self.class.send :rules, :expand_when, Application.options.expand_when
16
+ end
17
+ # Start the cloud
18
+ def start_cloud!
19
+ start!
20
+ end
21
+ alias_method :start_cloud, :start_cloud!
22
+ # Start the cloud, which launches the minimum_instances
23
+ def start!
24
+ message "Launching minimum_instances"
25
+ launch_minimum_instances
26
+ message "Waiting for master to boot up"
27
+ reset!
28
+ while !number_of_pending_instances.zero?
29
+ wait "2.seconds" unless Application.test?
30
+ waited = true
31
+ reset!
32
+ end
33
+ unless Application.test? || waited.nil?
34
+ message "Give some time for the instance ssh to start up"
35
+ wait "15.seconds"
36
+ end
37
+ install_cloud if Application.install_on_load?
38
+ configure_cloud
39
+ end
40
+ alias_method :start, :start!
41
+ def configure_cloud
42
+ message "Configuring master"
43
+ master = get_node 0
44
+ master.configure
45
+ end
46
+ def install_cloud
47
+ Master.with_nodes do |node|
48
+ node.login_once
49
+ end
50
+ Provider.install_poolparty(nodes.collect {|a| a.ip })
51
+ end
52
+ # Launch the minimum number of instances.
53
+ def launch_minimum_instances
54
+ request_launch_new_instances(Application.minimum_instances - number_of_pending_and_running_instances)
55
+ nodes
56
+ end
57
+ # Start monitoring the cloud with the threaded loop
58
+ def start_monitor!
59
+ begin
60
+ trap("INT") do
61
+ on_exit
62
+ exit
63
+ end
64
+ # Daemonize only if we are not in the test environment
65
+ run_thread_loop(:daemonize => !Application.test?) do
66
+ add_task {launch_minimum_instances} # If the base instances go down...
67
+ add_task {reconfigure_cloud_when_necessary}
68
+ add_task {scale_cloud!}
69
+ add_task {check_stats}
70
+ end
71
+ rescue Exception => e
72
+ puts "There was an error: #{e.nice_message}"
73
+ end
74
+ end
75
+ alias_method :start_monitor, :start_monitor!
76
+ def user_tasks
77
+ puts "in user_tasks"
78
+ end
79
+ # Sole purpose to check the stats, mainly in a plugin
80
+ def check_stats
81
+ end
82
+ # Add an instance if the cloud needs one ore terminate one if necessary
83
+ def scale_cloud!
84
+ add_instance_if_load_is_high
85
+ terminate_instance_if_load_is_low
86
+ end
87
+ alias_method :scale_cloud, :scale_cloud!
88
+ # Tough method:
89
+ # We need to make sure that all the instances have the required software installed
90
+ # This is a basic check against the local store of the instances that have the
91
+ # stack installed.
92
+ def reconfigure_cloud_when_necessary
93
+ reconfigure_running_instances if number_of_unconfigured_nodes > 0
94
+ end
95
+ alias_method :reconfiguration, :reconfigure_cloud_when_necessary
96
+ def number_of_unconfigured_nodes
97
+ nodes.reject {|a| a.stack_installed? }.size
98
+ end
99
+ def grow_by_one
100
+ request_launch_new_instance
101
+ self.class.get_master.configure
102
+ end
103
+ def shrink_by_one
104
+ node = nodes.reject {|a| a.master? }[-1]
105
+ request_termination_of_instance(node.instance_id) if node
106
+ end
107
+ # Add an instance if the load is high
108
+ def add_instance_if_load_is_high
109
+ request_launch_new_instance if expand?
110
+ end
111
+ alias_method :add_instance, :add_instance_if_load_is_high
112
+ # Teardown an instance if the load is pretty low
113
+ def terminate_instance_if_load_is_low
114
+ if contract?
115
+ shrink_by_one
116
+ end
117
+ end
118
+ alias_method :terminate_instance, :terminate_instance_if_load_is_low
119
+ # FOR MONITORING
120
+ def contract?
121
+ valid_rules?(:contract_when)
122
+ end
123
+ def expand?
124
+ valid_rules?(:expand_when)
125
+ end
126
+ # Restart the running instances services with monit on all the nodes
127
+ def restart_running_instances_services
128
+ nodes.each do |node|
129
+ node.restart_with_monit
130
+ end
131
+ end
132
+ # Reconfigure the running instances
133
+ # Since we are using vlad, running configure on one of the instances
134
+ # should configure all of the instances. We set the hosts in this file
135
+ def reconfigure_running_instances
136
+ # nodes.each do |node|
137
+ # node.configure if node.status =~ /running/
138
+ # end
139
+ master = get_node(0)
140
+ master.configure
141
+ end
142
+ # Build the basic haproxy config file from the config file in the config directory and return a tempfile
143
+ def build_haproxy_file
144
+ servers=<<-EOS
145
+ #{nodes.collect {|node| node.haproxy_entry}.join("\n")}
146
+ EOS
147
+ open(Application.haproxy_config_file).read.strip ^ {:servers => servers, :host_port => Application.host_port}
148
+ end
149
+ # Build the hosts file and return a tempfile
150
+ def build_hosts_file
151
+ write_to_temp_file(nodes.collect {|a| a.hosts_entry }.join("\n"))
152
+ end
153
+ # Build host file for a specific node
154
+ def build_hosts_file_for(n)
155
+ servers=<<-EOS
156
+ #{nodes.collect {|node| node.ip == n.ip ? node.local_hosts_entry : node.hosts_entry}.join("\n")}
157
+ EOS
158
+ servers
159
+ end
160
+ # Build the basic auth file for the heartbeat
161
+ def build_heartbeat_authkeys_file
162
+ write_to_temp_file(open(Application.heartbeat_authkeys_config_file).read)
163
+ end
164
+ # Build heartbeat config file
165
+ def build_heartbeat_config_file_for(node)
166
+ servers = "#{node.node_entry}\n#{get_next_node(node).node_entry}"
167
+ open(Application.heartbeat_config_file).read.strip ^ {:nodes => servers}
168
+ end
169
+ # Return a list of the nodes and cache them
170
+ def nodes
171
+ @nodes ||= list_of_nonterminated_instances.collect_with_index do |inst, i|
172
+ RemoteInstance.new(inst.merge({:number => i}))
173
+ end
174
+ end
175
+ # Get the node at the specific index from the cached nodes
176
+ def get_node(i=0)
177
+ nodes.select {|a| a.number == i.to_i}.first
178
+ end
179
+ # Get the next node in sequence, so we can configure heartbeat to monitor the next node
180
+ def get_next_node(node)
181
+ i = node.number + 1
182
+ i = 0 if i >= (nodes.size - 1)
183
+ get_node(i)
184
+ end
185
+ # On exit command
186
+ def on_exit
187
+ end
188
+ # List the clouds
189
+ def list
190
+ if number_of_pending_and_running_instances > 0
191
+ out = "-- CLOUD (#{number_of_pending_and_running_instances})--\n"
192
+ out << nodes.collect {|node| node.description }.join("\n")
193
+ else
194
+ out = "Cloud is not running"
195
+ end
196
+ out
197
+ end
198
+ # Reset and clear the caches
199
+ def reset!
200
+ @cached_descriptions = nil
201
+ @nodes = nil
202
+ end
203
+
204
+ class << self
205
+ include PoolParty
206
+
207
+ def with_nodes(&block)
208
+ new.nodes.each &block
209
+ end
210
+
211
+ def collect_nodes(&block)
212
+ new.nodes.collect &block
213
+ end
214
+
215
+ def requires_heartbeat?
216
+ new.nodes.size > 1
217
+ end
218
+ def is_master_responding?
219
+ `ping -c1 -t5 #{get_master.ip}`
220
+ end
221
+ def get_master
222
+ new.nodes[0]
223
+ end
224
+ def get_next_node(node)
225
+ new.get_next_node(node)
226
+ end
227
+ # Build a heartbeat_config_file from the config file in the config directory and return a tempfile
228
+ def build_heartbeat_config_file_for(node)
229
+ return nil unless node
230
+ new.build_heartbeat_config_file_for(node)
231
+ end
232
+ # Build a heartbeat resources file from the config directory and return a tempfile
233
+ def build_heartbeat_resources_file_for(node)
234
+ return nil unless node
235
+ "#{node.haproxy_resources_entry}\n#{get_next_node(node).haproxy_resources_entry}"
236
+ end
237
+ # Build hosts files for a specific node
238
+ def build_hosts_file_for(node)
239
+ new.build_hosts_file_for(node)
240
+ end
241
+ # Build the scp script for the specific node
242
+ def build_scp_instances_script_for(node)
243
+ authkeys_file = write_to_temp_file(open(Application.heartbeat_authkeys_config_file).read.strip)
244
+ if Master.requires_heartbeat?
245
+ ha_d_file = Master.build_heartbeat_config_file_for(node)
246
+ haresources_file = Master.build_heartbeat_resources_file_for(node)
247
+ end
248
+ haproxy_file = Master.build_haproxy_file
249
+ hosts_file = Master.build_hosts_file_for(node)
250
+
251
+ str = open(Application.sh_scp_instances_script).read.strip ^ {
252
+ :cloud_master_takeover => "#{node.scp_string("#{root_dir}/config/cloud_master_takeover", "/etc/ha.d/resource.d/", :dir => "/etc/ha.d/resource.d")}",
253
+ :config_file => "#{node.scp_string(Application.config_file, "~/.config")}",
254
+ :authkeys => "#{node.scp_string(authkeys_file.path, "/etc/ha.d/authkeys", :dir => "/etc/ha.d/")}",
255
+ :resources => "#{node.scp_string("#{root_dir}/config/resource.d/*", "/etc/ha.d/resource.d/", {:switches => "-r"})}",
256
+ :monitrc => "#{node.scp_string(Application.monit_config_file, "/etc/monit/monitrc", :dir => "/etc/monit")}",
257
+ :monit_d => "#{node.scp_string("#{File.dirname(Application.monit_config_file)}/monit/*", "/etc/monit.d/", {:switches => "-r", :dir => "/etc/monit.d/"})}",
258
+ :haproxy => "#{node.scp_string(haproxy_file, "/etc/haproxy.cfg")}",
259
+
260
+ :ha_d => Master.requires_heartbeat? ? "#{node.scp_string(ha_d_file, "/etc/ha.d/ha.cf")}" : "",
261
+ :haresources => Master.requires_heartbeat? ? "#{node.scp_string(haresources_file, "/etc/ha.d/ha.cf")}" : "",
262
+
263
+ :hosts => "#{node.scp_string(hosts_file, "/etc/hosts")}"
264
+ }
265
+ write_to_temp_file(str)
266
+ end
267
+ # Build basic configuration script for the node
268
+ def build_reconfigure_instances_script_for(node)
269
+ str = open(Application.sh_reconfigure_instances_script).read.strip ^ {
270
+ :config_master => "#{node.update_plugin_string}",
271
+ :start_pool_maintain => "pool maintain -c ~/.config -l ~/plugins",
272
+ :set_hostname => "hostname -v #{node.name}",
273
+ :start_s3fs => "/usr/bin/s3fs #{Application.shared_bucket} -o accessKeyId=#{Application.access_key} -o secretAccessKey=#{Application.secret_access_key} -o nonempty /data"
274
+ }
275
+ write_to_temp_file(str)
276
+ end
277
+
278
+ def set_hosts(c, remotetask=nil)
279
+ unless remotetask.nil?
280
+ rt = remotetask
281
+ end
282
+
283
+ ssh_location = `which ssh`.gsub(/\n/, '')
284
+ rsync_location = `which rsync`.gsub(/\n/, '')
285
+ rt.set :user, Application.username
286
+ # rt.set :domain, "#{Application.user}@#{ip}"
287
+ rt.set :application, Application.app_name
288
+ rt.set :ssh_flags, "-i #{Application.keypair_path} -o StrictHostKeyChecking=no"
289
+ rt.set :rsync_flags , ['-azP', '--delete', "-e '#{ssh_location} -l #{Application.username} -i #{Application.keypair_path} -o StrictHostKeyChecking=no'"]
290
+
291
+ master = get_master
292
+ rt.set :domain, "#{master.ip}" if master
293
+ Master.with_nodes { |node|
294
+ rt.host "#{Application.username}@#{node.ip}",:app if node.status =~ /running/
295
+ }
296
+ end
297
+
298
+ def ssh_configure_string_for(node)
299
+ cmd=<<-EOC
300
+ #{node.update_plugin_string(node)}
301
+ pool maintain -c ~/.config -l #{PoolParty.plugin_dir}
302
+ hostname -v #{node.name}
303
+ /usr/bin/s3fs #{Application.shared_bucket} -o accessKeyId=#{Application.access_key} -o secretAccessKey=#{Application.secret_access_key} -o nonempty /data
304
+ EOC
305
+ end
306
+ def build_haproxy_file
307
+ servers=<<-EOS
308
+ #{collect_nodes {|node| node.haproxy_entry}.join("\n")}
309
+ EOS
310
+ open(Application.haproxy_config_file).read.strip ^ {:servers => servers, :host_port => Application.host_port}
311
+ end
312
+ # Write a temp file with the content str
313
+ def write_to_temp_file(str="")
314
+ tempfile = Tempfile.new("pool-party-#{rand(1000)}-#{rand(1000)}")
315
+ tempfile.print(str)
316
+ tempfile.flush
317
+ tempfile
318
+ end
319
+ def with_temp_file(str="", &block)
320
+ Tempfile.open "pool-party-#{rand(10000)}" do |fp|
321
+ fp.puts str
322
+ fp.flush
323
+ block.call(fp)
324
+ end
325
+ end
326
+ end
327
+
328
+ end
329
+ end
@@ -0,0 +1,19 @@
1
+ =begin rdoc
2
+ Basic monitor on the cpu stats
3
+ =end
4
+ module Cpu
5
+ module Master
6
+ def cpu
7
+ nodes.size > 0 ? nodes.inject(0) {|i,a| i+=a.cpu } / nodes.size : 0.0
8
+ end
9
+ end
10
+
11
+ module Remote
12
+ def cpu
13
+ ssh("uptime").split(/\s+/)[-3].to_f rescue 0.0
14
+ end
15
+ end
16
+
17
+ end
18
+
19
+ PoolParty.register_monitor Cpu
@@ -0,0 +1,26 @@
1
+ =begin rdoc
2
+ Basic monitor on the cpu stats
3
+ =end
4
+ module Memory
5
+ module Master
6
+ # Get the average memory usage over the cloud
7
+ def memory
8
+ nodes.size > 0 ? nodes.inject(0) {|i,a| i += a.memory } / nodes.size : 0.0
9
+ end
10
+ end
11
+
12
+ module Remote
13
+ def memory
14
+ str = ssh("free -m | grep -i mem")
15
+ total_memory = str.split[1].to_f
16
+ used_memory = str.split[2].to_f
17
+
18
+ used_memory / total_memory
19
+ rescue
20
+ 0.0
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ PoolParty.register_monitor Memory
@@ -0,0 +1,23 @@
1
+ =begin rdoc
2
+ Basic monitor on the cpu stats
3
+ =end
4
+ module Web
5
+ module Master
6
+ # Get the average web request capabilities over the cloud
7
+ def web
8
+ nodes.size > 0 ? nodes.inject(0) {|i,a| i += a.web } / nodes.size : 0.0
9
+ end
10
+ end
11
+
12
+ module Remote
13
+ def web
14
+ str = ssh("httperf --server localhost --port #{Application.client_port} --num-conn 3 --timeout 5 | grep 'Request rate'")
15
+ str[/[.]* ([\d]*\.[\d]*) [.]*/, 0].chomp.to_f
16
+ rescue
17
+ 0.0
18
+ end
19
+ end
20
+
21
+ end
22
+
23
+ PoolParty.register_monitor Web
@@ -0,0 +1,13 @@
1
+ =begin rdoc
2
+ Basic monitors for the master
3
+ =end
4
+ module PoolParty
5
+ module Monitors
6
+ module Master
7
+ end
8
+ module Remote
9
+ end
10
+ end
11
+ end
12
+
13
+ Dir["monitors/*"].each {|f| require f}
@@ -0,0 +1,16 @@
1
+ module PoolParty
2
+ class Optioner
3
+ # Parse the command line options for options without a switch
4
+ def self.parse(argv, safe=[])
5
+ args = []
6
+ argv.each_with_index do |arg,i|
7
+ unless arg.index("-")
8
+ args << arg
9
+ else
10
+ argv.delete_at(i+1) unless safe.include?(arg)
11
+ end
12
+ end
13
+ args
14
+ end
15
+ end
16
+ end