├── .gitignore ├── LICENSE-2.0.txt ├── README.rdoc ├── Rakefile ├── assembly.xml ├── bin ├── galaxy ├── galaxy-agent └── galaxy-console ├── build ├── rpm │ └── galaxy.spec ├── start-scripts │ ├── galaxy-agent │ └── galaxy-console └── sun │ ├── checkinstall │ ├── pkginfo │ ├── postinstall │ ├── postremove │ ├── preinstall │ ├── preremove │ ├── prototype │ └── root │ └── var │ └── svc │ └── manifest │ └── application │ └── management │ ├── galaxy-agent.xml │ └── galaxy-console.xml ├── lib └── galaxy │ ├── agent.rb │ ├── agent_remote_api.rb │ ├── agent_utils.rb │ ├── announcements.rb │ ├── client.rb │ ├── command.rb │ ├── commands │ ├── assign.rb │ ├── cleanup.rb │ ├── clear.rb │ ├── perform.rb │ ├── reap.rb │ ├── restart.rb │ ├── rollback.rb │ ├── show.rb │ ├── show_agent.rb │ ├── show_console.rb │ ├── show_core.rb │ ├── ssh.rb │ ├── start.rb │ ├── stop.rb │ ├── update.rb │ └── update_config.rb │ ├── config.rb │ ├── console.rb │ ├── controller.rb │ ├── daemon.rb │ ├── db.rb │ ├── deployer.rb │ ├── events.rb │ ├── fetcher.rb │ ├── filter.rb │ ├── host.rb │ ├── log.rb │ ├── parallelize.rb │ ├── properties.rb │ ├── proxy_console.rb │ ├── report.rb │ ├── repository.rb │ ├── software.rb │ ├── starter.rb │ ├── temp.rb │ ├── transport.rb │ ├── version.rb │ └── versioning.rb ├── pom.xml └── test ├── bad_core_package ├── bin │ ├── control │ ├── launcher │ └── xndeploy └── stuff ├── core_package ├── bin │ ├── control │ ├── launcher │ └── xndeploy └── stuff ├── helper.rb ├── performance ├── build.xml ├── lib │ ├── commons-logging-1.1.1.jar │ ├── httpclient-4.0.1.jar │ ├── httpcore-4.0.1.jar │ ├── httpmime-4.0.1.jar │ └── one-jar-ant-task-0.96.jar └── src │ └── LoadTest.java ├── property_data ├── a │ ├── b │ │ ├── c │ │ │ ├── d │ │ │ │ └── test_override.properties │ │ │ └── test_simple.properties │ │ ├── test_override.properties │ │ └── xncore.properties │ ├── build.properties │ └── test_comments_ignored.properties └── foo-bar.properties ├── test_agent.rb ├── test_announcements.rb ├── test_client.rb ├── test_commands.rb ├── test_config.rb ├── test_console.rb ├── test_controller.rb ├── test_db.rb ├── test_deployer.rb ├── test_event.rb ├── test_fetcher.rb ├── test_filter.rb ├── test_host.rb ├── test_logger.rb ├── test_logger_collector.rb ├── test_parallelize.rb ├── test_propbuilder.rb ├── test_repository.rb ├── test_temp.rb └── test_transport.rb /.gitignore: -------------------------------------------------------------------------------- 1 | pkg/ 2 | *.o 3 | *.lo 4 | *.pc 5 | *.log 6 | *.status 7 | .deps/ 8 | *.iml 9 | *.ipr 10 | *.iws 11 | coverage/ 12 | target/ 13 | .rakeTasks 14 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'fileutils' 3 | require 'tmpdir' 4 | require 'rake' 5 | require 'rake/testtask' 6 | require 'rake/clean' 7 | require 'rake/gempackagetask' 8 | require 'lib/galaxy/version' 9 | begin 10 | require 'rcov/rcovtask' 11 | $RCOV_LOADED = true 12 | rescue LoadError 13 | $RCOV_LOADED = false 14 | puts "Unable to load rcov" 15 | end 16 | 17 | THIS_FILE = File.expand_path(__FILE__) 18 | PWD = File.dirname(THIS_FILE) 19 | RUBY = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) 20 | 21 | PACKAGE_NAME = 'galaxy' 22 | PACKAGE_VERSION = Galaxy::Version 23 | GEM_VERSION = PACKAGE_VERSION.split('-')[0] 24 | 25 | task :default => [:test] 26 | 27 | task :install do 28 | sitelibdir = Config::CONFIG["sitelibdir"] 29 | cd 'lib' do 30 | for file in Dir["galaxy/*.rb", "galaxy/commands/*.rb" ] 31 | d = File.join(sitelibdir, file) 32 | mkdir_p File.dirname(d) 33 | install(file, d) 34 | end 35 | end 36 | 37 | bindir = Config::CONFIG["bindir"] 38 | cd 'bin' do 39 | for file in ["galaxy", "galaxy-agent", "galaxy-console" ] 40 | d = File.join(bindir, file) 41 | mkdir_p File.dirname(d) 42 | install(file, d) 43 | end 44 | end 45 | end 46 | 47 | 48 | Rake::TestTask.new("test") do |t| 49 | t.pattern = 'test/test*.rb' 50 | t.libs << 'test' 51 | t.warning = true 52 | end 53 | 54 | if $RCOV_LOADED 55 | Rcov::RcovTask.new do |t| 56 | t.pattern = 'test/test*.rb' 57 | t.libs << 'test' 58 | t.rcov_opts = ['--exclude', 'gems/*', '--text-report'] 59 | end 60 | end 61 | 62 | Rake::PackageTask.new(PACKAGE_NAME, PACKAGE_VERSION) do |p| 63 | p.tar_command = 'gtar' if RUBY_PLATFORM =~ /solaris/ 64 | p.need_tar = true 65 | p.package_files.include(["lib/galaxy/**/*.rb", "bin/*"]) 66 | end 67 | 68 | spec = Gem::Specification.new do |s| 69 | s.name = PACKAGE_NAME 70 | s.version = GEM_VERSION 71 | s.author = "Ning, Inc." 72 | s.email = "pierre@ning.com" 73 | s.homepage = "http://github.com/ning/galaxy" 74 | s.platform = Gem::Platform::RUBY 75 | s.summary = "Galaxy is a lightweight software deployment and management tool." 76 | s.files = FileList["lib/galaxy/**/*.rb", "bin/*"] 77 | s.executables = FileList["galaxy-agent", "galaxy-console", "galaxy"] 78 | s.require_path = "lib" 79 | s.add_dependency("fileutils", ">= 0.7") 80 | s.add_dependency("json", ">= 1.5.1") 81 | s.add_dependency("mongrel", ">= 1.1.5") 82 | s.add_dependency("rcov", ">= 0.9.9") 83 | end 84 | 85 | Rake::GemPackageTask.new(spec) do |pkg| 86 | pkg.need_zip = false 87 | pkg.tar_command = 'gtar' if RUBY_PLATFORM =~ /solaris/ 88 | pkg.need_tar = true 89 | end 90 | 91 | namespace :run do 92 | desc "Run a Gonsole locally" 93 | task :gonsole do 94 | # Note that -i localhost is needed. Otherwise the DRb server will bind to the 95 | # hostname, which can be as ugly as "Pierre-Alexandre-Meyers-MacBook-Pro.local" 96 | system(RUBY, "-I", File.join(PWD, "lib"), 97 | File.join(PWD, "bin", "galaxy-console"), "--start", 98 | "-i", "localhost", 99 | "--ping-interval", "10", "-f", "-l", "STDOUT", "-L", "DEBUG", "-v") 100 | end 101 | 102 | desc "Run a Gagent locally" 103 | task :gagent do 104 | system(RUBY, "-I", File.join(PWD, "lib"), 105 | File.join(PWD, "bin", "galaxy-agent"), "--start", 106 | "-i", "localhost", "-c", "localhost", 107 | "-r", "http://localhost/config/trunk/qa", 108 | "-b", "http://localhost/binaries", 109 | "-d", "/tmp/deploy", "-x", "/tmp/extract", 110 | "--announce-interval", "10", "-f", "-l", "STDOUT", "-L", "DEBUG", "-v") 111 | end 112 | end 113 | 114 | desc "Build a Gem with the full version number" 115 | task :versioned_gem => :gem do 116 | gem_version = PACKAGE_VERSION.split('-')[0] 117 | if gem_version != PACKAGE_VERSION 118 | FileUtils.mv("pkg/#{PACKAGE_NAME}-#{gem_version}.gem", "pkg/#{PACKAGE_NAME}-#{PACKAGE_VERSION}.gem") 119 | end 120 | end 121 | 122 | namespace :package do 123 | desc "Build an RPM package" 124 | task :rpm => :versioned_gem do 125 | build_dir = "/tmp/galaxy-package" 126 | rpm_dir = "/tmp/galaxy-rpm" 127 | rpm_version = PACKAGE_VERSION 128 | rpm_version += "-final" unless rpm_version.include?('-') 129 | 130 | FileUtils.rm_rf(build_dir) 131 | FileUtils.mkdir_p(build_dir) 132 | FileUtils.rm_rf(rpm_dir) 133 | FileUtils.mkdir_p(rpm_dir) 134 | 135 | `rpmbuild --target=noarch -v --define "_builddir ." --define "_rpmdir #{rpm_dir}" -bb build/rpm/galaxy.spec` || raise("Failed to create package") 136 | # You can tweak the rpm as follow: 137 | #`rpmbuild --target=noarch -v --define "_gonsole_url gonsole.company.com" --define "_gepo_url http://gepo.company.com/config/trunk/prod" --define "_builddir ." --define "_rpmdir #{rpm_dir}" -bb build/rpm/galaxy.spec` || raise("Failed to create package") 138 | 139 | FileUtils.cp("#{rpm_dir}/noarch/#{PACKAGE_NAME}-#{rpm_version}.noarch.rpm", "pkg/#{PACKAGE_NAME}-#{rpm_version}.noarch.rpm") 140 | FileUtils.rm_rf(build_dir) 141 | FileUtils.rm_rf(rpm_dir) 142 | end 143 | 144 | desc "Build a Sun package" 145 | task :sunpkg => :versioned_gem do 146 | build_dir = "#{Dir.tmpdir}/galaxy-package" 147 | source_dir = File.dirname(__FILE__) 148 | 149 | FileUtils.rm_rf(build_dir) 150 | FileUtils.mkdir_p(build_dir) 151 | FileUtils.cp_r("#{source_dir}/build/sun/.", build_dir) 152 | FileUtils.cp("#{source_dir}/pkg/#{PACKAGE_NAME}-#{PACKAGE_VERSION}.gem", "#{build_dir}/#{PACKAGE_NAME}.gem") 153 | FileUtils.mkdir_p("#{build_dir}/root/lib/svc/method") 154 | 155 | # Expand version tokens 156 | `ruby -pi -e "gsub('\#{PACKAGE_VERSION}', '#{PACKAGE_VERSION}'); gsub('\#{GEM_VERSION}', '#{GEM_VERSION}')" #{build_dir}/*` 157 | 158 | # Build the package 159 | `cd #{build_dir} && pkgmk -r root -d .` || raise("Failed to create package") 160 | `cd #{build_dir} && pkgtrans -s . #{PACKAGE_NAME}.pkg galaxy` || raise("Failed to translate package") 161 | 162 | FileUtils.cp("#{build_dir}/#{PACKAGE_NAME}.pkg", "#{source_dir}/pkg/#{PACKAGE_NAME}-#{PACKAGE_VERSION}.pkg") 163 | FileUtils.rm_rf(build_dir) 164 | end 165 | end 166 | -------------------------------------------------------------------------------- /assembly.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | tar.gz 4 | 5 | false 6 | 7 | 8 | 9 | lib/** 10 | 11 | / 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /bin/galaxy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'rubygems' 4 | require 'optparse' 5 | require 'resolv' 6 | require 'timeout' 7 | 8 | require 'galaxy/client' 9 | require 'galaxy/command' 10 | require 'galaxy/config' 11 | require 'galaxy/host' 12 | require 'galaxy/version' 13 | require 'galaxy/transport' 14 | require 'galaxy/versioning' 15 | 16 | @filter = {} 17 | @options = { 18 | :console_url => ENV['GALAXY_CONSOLE'], 19 | :thread_count => 25, 20 | :versioning_policy => Galaxy::Versioning::StrictVersioningPolicy, 21 | } 22 | 23 | @opts = OptionParser.new do |opts| 24 | opts.banner = "#{$0} [options] [args]" 25 | 26 | opts.separator "" 27 | opts.separator "Options:" 28 | opts.on("-h", "--help", "Display a help message and exit") { @options[:help_requested] = true } 29 | opts.on("-c", "--console CONSOLE", "Galaxy console host (overrides GALAXY_CONSOLE)") { |arg| @options[:console_url] = arg } 30 | opts.on("-C", "--config FILE", "Configuration file (overrides GALAXY_CONFIG)") { |arg| @options[:config_file] = arg } 31 | opts.on("-p", "--parallel-count THREADS", "Maximum number of threads to use, default #{@options[:thread_count]}") { |arg| @options[:thread_count] = arg.to_i } 32 | opts.on("-r", "--relaxed-versioning", "Allow updates to the currently assigned version") { @options[:versioning_policy] = Galaxy::Versioning::RelaxedVersioningPolicy } 33 | opts.on("-V", "Display the Galaxy version number and exit") do |x| 34 | puts "Galaxy version #{Galaxy::Version}" 35 | @options[:version_requested] = true 36 | end 37 | opts.on("-y", "--yes", "Avoid confirmation prompts by automatically confirming all actions") { @options[:implicit_confirmation] = true } 38 | 39 | opts.separator "" 40 | opts.separator "Filters:" 41 | 42 | opts.on("-i", "--host HOST", "Select a specific agent by hostname") do |arg| 43 | @filter[:host] = arg 44 | end 45 | 46 | opts.on("-I", "--ip IP", "Select a specific agent by IP address") do |arg| 47 | @filter[:ip] = arg 48 | end 49 | 50 | opts.on("-m", "--machine MACHINE", "Select agents by physical machine") do |arg| 51 | @filter[:machine] = arg 52 | end 53 | 54 | opts.on("-M", "--cohabitants HOST", "Select agents that share a physical machine with the specified host") do |arg| 55 | @options[:cohabitant_host] = arg 56 | end 57 | 58 | opts.on("-s", "--set SET", "Select 'e{mpty}', 't{aken}' or 'a{ll}' hosts", [:empty, :all, :taken, :e, :a, :t]) do |arg| 59 | case arg 60 | when :all, :a then 61 | @filter[:set] = :all 62 | when :empty, :e then 63 | @filter[:set] = :empty 64 | when :taken, :t then 65 | @filter[:set] = :taken 66 | end 67 | end 68 | 69 | opts.on("-S", "--state STATE", "Select 'r{unning}' or 's{topped}' hosts", [:running, :stopped, :r, :s]) do |arg| 70 | case arg 71 | when :running, :r then 72 | @filter[:state] = 'running' 73 | when :stopped, :s then 74 | @filter[:state] = 'stopped' 75 | end 76 | end 77 | 78 | opts.on("-A", "--agent-state STATE", "Select 'online' or 'offline' agents", [:online, :offline]) do |arg| 79 | case arg 80 | when :online then 81 | @filter[:agent_state] = 'online' 82 | when :offline then 83 | @filter[:agent_state] = 'offline' 84 | end 85 | end 86 | 87 | opts.on("-e", "--env ENV", "Select agents in the given environment") { |arg| @filter[:env] = arg } 88 | opts.on("-t", "--type TYPE", "Select agents with a given software type") { |arg| @filter[:type] = arg } 89 | opts.on("-v", "--version VERSION", "Select agents with a given software version") { |arg| @filter[:version] = arg } 90 | 91 | opts.separator "" 92 | opts.separator "Notes:" 93 | opts.separator " - Filters are evaluated as: set | host | (env & version & type)" 94 | opts.separator " - The HOST, MACHINE, and TYPE arguments are regular expressions (not globs)" 95 | opts.separator " - The default filter selects all hosts" 96 | 97 | begin 98 | @original_args = ARGV.join(" ") 99 | @args = opts.parse! ARGV 100 | @filter[:command] = @original_args 101 | rescue Exception => msg 102 | puts opts 103 | puts msg 104 | exit 1 105 | end 106 | end 107 | 108 | def parse_command_line 109 | begin 110 | @options[:config_from_file] = Galaxy::Config::read_config_file(@options[:config_file]) 111 | get_command 112 | abort(usage_message) if @options[:help_requested] 113 | validate_options 114 | rescue CommandLineError => e 115 | puts usage_message if @command_class 116 | $stderr.puts "Error: #{e}" unless e.message.empty? 117 | exit(1) 118 | end 119 | end 120 | 121 | def get_command 122 | command_name = @args.shift 123 | unless @options[:help_requested] 124 | raise CommandLineError.new("Missing command") if command_name.nil? 125 | end 126 | 127 | unless command_name.nil? 128 | @command_class = Galaxy::Commands[command_name] 129 | if @command_class.nil? 130 | raise CommandLineError.new("Unrecognized command: #{command_name}") 131 | end 132 | @command = @command_class.new(@args, @options) 133 | end 134 | 135 | # If a host is removed from dns, it should then be possible to reap it from the gonsole, without 136 | # having to restart the gonsole. Don't bail out if the DNS does not exist anymore. 137 | # See GAL-290. 138 | begin 139 | @filter[:host] = canonical_hostname(@filter[:host]) if @filter[:host] 140 | rescue Exception => e 141 | raise CommandLineError.new("DNS error: #{e}") unless command_name == "reap" 142 | @filter[:host] = @filter[:host] 143 | end 144 | begin 145 | @filter[:machine] = canonical_hostname(@filter[:machine]) if @filter[:machine] 146 | rescue Exception => e 147 | raise CommandLineError.new("DNS error: #{e}") unless command_name == "reap" 148 | @filter[:machine] = @filter[:machine] 149 | end 150 | begin 151 | @options[:cohabitant_host] = canonical_hostname(@options[:cohabitant_host]) if @options[:cohabitant_host] 152 | rescue Exception => e 153 | raise CommandLineError.new("DNS error: #{e}") unless command_name == "reap" 154 | @options[:cohabitant_host] = @options[:cohabitant_host] 155 | end 156 | end 157 | 158 | def validate_options 159 | console_url = @options[:console_url] || @options[:config_from_file]['galaxy.client.console'] 160 | if console_url.nil? 161 | raise CommandLineError.new("Cannot determine console host; consider passing -c or setting GALAXY_CONSOLE") 162 | end 163 | @options[:console_url] = normalize_console_url(console_url) 164 | @options[:console] = Galaxy::Transport.locate(@options[:console_url]) 165 | 166 | if @options[:cohabitant_host] 167 | begin 168 | agents = @options[:console].agents({:host => @options[:cohabitant_host], :command => @original_args}) 169 | if agents.length != 1 170 | raise "Found #{agents.length} agents matching #{@options[:cohabitant_host]}" 171 | end 172 | @filter[:machine] = agents[0].machine 173 | rescue Exception => e 174 | raise "Unable to determine machine for host #{@options[:cohabitant_host]}: #{e}" 175 | end 176 | end 177 | end 178 | 179 | def usage_message 180 | @opts.separator "" 181 | 182 | if @command_class.nil? 183 | @opts.separator "Commands:" 184 | Galaxy::Commands.each do |command_name| 185 | @opts.separator " #{command_name}" 186 | end 187 | else 188 | @opts.separator "Usage for '#{@command_class.name}':" 189 | 190 | help = @command_class.help 191 | indent = help.scan(/^\s+/).first 192 | 193 | help.split("\n").each do |line| 194 | @opts.separator line.gsub(/^#{indent}/, " ") 195 | end 196 | end 197 | @opts.to_s 198 | end 199 | 200 | def get_agents 201 | @agents = @command.select_agents(@filter) 202 | rescue Exception => e 203 | abort("Error: #{e}") 204 | end 205 | 206 | def validate_agents 207 | # If command is show-console, it's okay not to have found any agent 208 | if @agents.length == 0 and @command.class.name != "show-console" 209 | abort("No agents matching the provided filter(s) were available for #{@command.class.name}") 210 | elsif @agents.length > 1 and (@command.changes_agent_state or @command.changes_console_state) and not@options[:implicit_confirmation] 211 | abort unless prompt_and_wait_for_user_confirmation("#{@agents.length} agents will be affected; continue? (y/n) ") 212 | end 213 | locate_agent_proxies # AgentProxy should provide this instead of having to instantiate it here 214 | end 215 | 216 | def locate_agent_proxies 217 | @agents.each { |agent| agent.proxy = Galaxy::Transport.locate(agent.url) if agent.url } 218 | end 219 | 220 | def run_command 221 | user = Galaxy::HostUtils::shell_user || 'unknown' 222 | message = "#{user} ran: galaxy " + @original_args 223 | stdout, stderr = @command.execute(@agents) 224 | puts stdout unless stdout.nil? 225 | $stderr.puts stderr unless stderr.nil? 226 | end 227 | 228 | exit(0) if @options[:version_requested] 229 | parse_command_line 230 | get_agents 231 | validate_agents 232 | run_command 233 | -------------------------------------------------------------------------------- /bin/galaxy-agent: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | tried = false 4 | begin 5 | require 'galaxy/agent' 6 | require 'galaxy/daemon' 7 | require 'galaxy/config' 8 | require 'galaxy/version' 9 | rescue LoadError 10 | tried = true 11 | $:.unshift File.join(File.dirname(__FILE__), "..", "lib") 12 | tried ? raise : retry 13 | end 14 | require 'optparse' 15 | require 'ostruct' 16 | 17 | action = "help" 18 | command_line_options = OpenStruct.new 19 | opts = OptionParser.new do |opts| 20 | opts.banner = "Usage: #{$0} [options]" 21 | 22 | opts.separator " Commands, use just one of these" 23 | opts.on("-s", "--start", "Start the agent") { action = "start" } 24 | opts.on("-k", "--stop", "Stop the agent") { action = "stop" } 25 | 26 | opts.separator " Options for Start" 27 | opts.on("-C", "--config FILE", "Configuration file (overrides GALAXY_CONFIG)") do |arg| 28 | command_line_options.config_file = arg 29 | end 30 | opts.on("-i", "--host HOST[:PORT]", "Hostname this agent manages (default localhost)") do |host| 31 | command_line_options.host = host 32 | end 33 | opts.on("-m", "--machine MACHINE", "Physical machine where the agent lives (overrides -M)") do |machine| 34 | command_line_options.machine = machine 35 | end 36 | opts.on("-M", "--machine-file FILE", "Filename containing the physical machine name") do |machine_file| 37 | command_line_options.machine_file = machine_file 38 | end 39 | opts.on("-c", "--console ['http://'|'druby://']HOST[:PORT]", "Hostname where the console is listening") do |console| 40 | command_line_options.console = console 41 | end 42 | opts.on("-r", "--repository URL", "Base URL for the repository") do |repo| 43 | command_line_options.repository = repo 44 | end 45 | opts.on("-b", "--binaries URL", "Base URL for the binary archive") do |bin| 46 | command_line_options.binaries = bin 47 | end 48 | opts.on("-d", "--deploy-to DIR", "Directory where to make deployments") do |path| 49 | command_line_options.deploy_dir = path 50 | end 51 | opts.on("-x", "--data DIR", "Directory for the agent's database") do |path| 52 | command_line_options.data_dir = path 53 | end 54 | opts.on("-f", "--fore", "--foreground", "Run agent in the foreground") do 55 | command_line_options.foreground = true 56 | end 57 | opts.on("-a", "--announce-interval INTERVAL", "How frequently (in seconds) the agent should announce") do |interval| 58 | command_line_options.announce_interval = interval 59 | end 60 | 61 | opts.separator " General Options" 62 | opts.on_tail("-l", "--log LOG", "STDOUT | STDERR | SYSLOG | /path/to/file.log") do |log| 63 | command_line_options.log = log 64 | end 65 | opts.on_tail("-L", "--log-level LEVEL", "DEBUG | INFO | WARN | ERROR. Default=INFO") do |level| 66 | command_line_options.log_level = level 67 | end 68 | opts.on_tail("-u", "--user USER", "User to run as") do |arg| 69 | command_line_options.user = arg 70 | end 71 | opts.on("-z", "--event_listener URL", "Which listener to use") do |event_listener| 72 | command_line_options.event_listener = event_listener 73 | end 74 | opts.on("-H", "--http-user USER", "HTTP User for authentication.") do |http_user| 75 | command_line_options.http_user = http_user 76 | end 77 | opts.on("-P", "--http-password PASSWORD", "HTTP Password for authentication.") do |http_password| 78 | command_line_options.http_password = http_password 79 | end 80 | opts.on_tail("-g", "--agent-log FILE", "File agent should rediect stdout and stderr to") do |log| 81 | command_line_options.agent_log = log 82 | end 83 | 84 | opts.on_tail("-t", "--test", "Test, displays as -v without doing anything") do 85 | command_line_options.verbose = true 86 | command_line_options.test = true 87 | end 88 | opts.on_tail("-v", "--verbose", "Verbose output") { command_line_options.verbose = true } 89 | opts.on_tail("-V", "--version", "Print the galaxy version and exit") { action = "version" } 90 | opts.on_tail("-h", "--help", "Show this help") { action = "help" } 91 | 92 | 93 | begin 94 | opts.parse! ARGV 95 | rescue Exception => msg 96 | puts opts 97 | puts msg 98 | exit 1 99 | end 100 | end 101 | 102 | case action 103 | when "help" 104 | puts opts 105 | 106 | when "version" 107 | puts "Galaxy version #{Galaxy::Version}" 108 | 109 | when "start" 110 | config = Galaxy::AgentConfigurator.new(command_line_options).configure 111 | exit if command_line_options.test 112 | if command_line_options.foreground 113 | agent = Galaxy::Agent.start config 114 | agent.join 115 | else 116 | Galaxy::Daemon.start('galaxy-agent', config[:pid_file], config[:user]) do 117 | agent = Galaxy::Agent.start config 118 | agent.join 119 | end 120 | end 121 | 122 | when "stop" 123 | config = Galaxy::AgentConfigurator.new(command_line_options).configure 124 | begin 125 | Galaxy::Daemon.kill_daemon(config[:pid_file]) 126 | rescue Exception => e 127 | abort("Error: #{e}") 128 | end 129 | 130 | end 131 | -------------------------------------------------------------------------------- /bin/galaxy-console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'optparse' 3 | require 'ostruct' 4 | 5 | tried = false 6 | begin 7 | require 'galaxy/console' 8 | require 'galaxy/proxy_console' 9 | require 'galaxy/config' 10 | require 'galaxy/daemon' 11 | require 'galaxy/host' 12 | require 'galaxy/version' 13 | rescue LoadError 14 | tried = true 15 | $:.unshift File.join(File.dirname(__FILE__), "..", "lib") 16 | tried ? raise : retry 17 | end 18 | 19 | action = "help" 20 | command_line_options = OpenStruct.new 21 | opts = OptionParser.new do |opts| 22 | opts.banner = "Usage: #{$0} [options]" 23 | 24 | opts.separator " Commands, use just one of these" 25 | opts.on("-s", "--start", "Start the console") { action = "start" } 26 | opts.on("-P", "--start-proxy", "Start the proxy console") { action = "start-proxy" } 27 | opts.on("-k", "--stop", "Stop the console") { action = "stop" } 28 | 29 | opts.separator " Options for Start" 30 | opts.on("-C", "--config FILE", "Configuration file (overrides GALAXY_CONFIG)") do |arg| 31 | command_line_options.config_file = arg 32 | end 33 | opts.on("-i", "--host HOST", "Hostname this console runs on") do |host| 34 | command_line_options.host = host 35 | end 36 | opts.on("-a", "--announcement-url HOST[:PORT]", "Port for Http post announcements") do |ann_host| 37 | command_line_options.announcement_url = ann_host 38 | end 39 | opts.on("-p", "--ping-interval INTERVAL", "How many seconds an agent can be silent before being marked dead") do |interval| 40 | command_line_options.ping_interval = interval 41 | end 42 | opts.on("-f", "--fore", "--foreground", "Run console in the foreground") do 43 | command_line_options.foreground = true 44 | end 45 | opts.on("-Q", "--console-proxied-url URL", "Gonsole to proxy") do |host| 46 | command_line_options.console_proxyied_url = host 47 | end 48 | 49 | 50 | opts.separator " General Options" 51 | opts.on_tail("-l", "--log LOG", "STDOUT | STDERR | SYSLOG | /path/to/file.log") do |log| 52 | command_line_options.log = log 53 | end 54 | opts.on_tail("-L", "--log-level LEVEL", "DEBUG | INFO | WARN | ERROR. Default=INFO") do |level| 55 | command_line_options.log_level = level 56 | end 57 | opts.on_tail("-g", "--console-log FILE", "File agent should rediect stdout and stderr to") do |log| 58 | command_line_options.agent_log = log 59 | end 60 | opts.on_tail("-u", "--user USER", "User to run as") do |arg| 61 | command_line_options.user = arg 62 | end 63 | opts.on("-z", "--event_listener URL", "Which listener to use") do |event_listener| 64 | command_line_options.event_listener = event_listener 65 | end 66 | opts.on_tail("-t", "--test", "Test, displays as -v without doing anything") do 67 | command_line_options.verbose = true 68 | command_line_options.test = true 69 | end 70 | opts.on_tail("-v", "--verbose", "Verbose output") { command_line_options.verbose = true } 71 | opts.on_tail("-V", "--version", "Print the galaxy version and exit") { action = "version" } 72 | opts.on_tail("-h", "--help", "Show this help") { action = "help" } 73 | 74 | begin 75 | opts.parse! ARGV 76 | rescue Exception => msg 77 | puts opts 78 | puts msg 79 | exit 1 80 | end 81 | end 82 | 83 | case action 84 | when "help" 85 | puts opts 86 | exit 87 | 88 | when "version" 89 | puts "Galaxy version #{Galaxy::Version}" 90 | 91 | when "start" 92 | config = Galaxy::ConsoleConfigurator.new(command_line_options).configure 93 | exit if command_line_options.test 94 | if command_line_options.foreground 95 | console = Galaxy::Console.start config 96 | console.join 97 | else 98 | Galaxy::Daemon.start('galaxy-console', config[:pid_file], config[:user]) do 99 | console = Galaxy::Console.start(config) 100 | console.join 101 | end 102 | end 103 | when "start-proxy" 104 | config = Galaxy::ConsoleConfigurator.new(command_line_options).configure 105 | exit if command_line_options.test 106 | if command_line_options.foreground 107 | console = Galaxy::ProxyConsole.start config 108 | console.join 109 | else 110 | Galaxy::Daemon.start('galaxy-proxy-console', config[:pid_file], config[:user]) do 111 | console = Galaxy::ProxyConsole.start(config) 112 | console.join 113 | end 114 | end 115 | when "stop" 116 | config = Galaxy::ConsoleConfigurator.new(command_line_options).configure 117 | begin 118 | Galaxy::Daemon.kill_daemon(config[:pid_file]) 119 | rescue Exception => e 120 | abort("Error: #{e}") 121 | end 122 | 123 | end 124 | -------------------------------------------------------------------------------- /build/rpm/galaxy.spec: -------------------------------------------------------------------------------- 1 | %define gemversion %(ruby -rlib/galaxy/version -e 'puts Galaxy::Version.split("-", 1)[0]' 2>/dev/null) 2 | %define gemname galaxy 3 | 4 | Name: %{gemname} 5 | Summary: Software deployment tool 6 | Version: %{gemversion} 7 | Release: %(ruby -rlib/galaxy/version -e 'puts Galaxy::Version.split("-", 2)[1] || "final"' 2>/dev/null) 8 | License: Apache License, version 2.0 9 | Group: Development/Tools/Other 10 | URL: http://github.com/ning/galaxy 11 | BuildArch: noarch 12 | Requires: ruby 13 | BuildRoot: /tmp/galaxy-package 14 | Provides: rubygem(%{gemname}) = %{gemversion} 15 | 16 | %define gem %(ruby -rlib/galaxy/version -e 'puts "galaxy-#{Galaxy::Version}.gem"') 17 | 18 | # Use rpmbuild --define "_gonsole_url gonsole.prod.company.com" to customize 19 | # galaxy.{client,agent}.console in galaxy.conf 20 | %{?!_gonsole_url: %define _gonsole_url GONSOLE_URL} 21 | 22 | # Use rpmbuild --define "_gepo_url http://gepo.company.com/config/trunk/prod" to customize 23 | # galaxy.agent.config-root in galaxy.conf 24 | %{?!_gepo_url: %define _gepo_url GEPO_URL} 25 | 26 | # Use rpmbuild --define "_gepobin_url http://gepo.company.com/binaries" to customize 27 | # galaxy.agent.config-root in galaxy.conf 28 | %{?!_gepobin_url: %define _gepobin_url GEPOBIN_URL} 29 | 30 | %description 31 | Galaxy is a lightweight software deployment and management tool used to manage the Java cores and Apache httpd instances that make up the Ning platform. 32 | 33 | %prep 34 | 35 | %build 36 | 37 | %install 38 | mkdir -p %{buildroot}/var/cache/gem 39 | cp pkg/%{gem} %{buildroot}/var/cache/gem 40 | mkdir -p %{buildroot}/etc/rc.d/init.d 41 | cp -r build/start-scripts/* %{buildroot}/etc/rc.d/init.d 42 | find %{buildroot}/etc/rc.d/init.d -type f | xargs chmod a+x 43 | 44 | %clean 45 | rm -rf %{buildroot} 46 | 47 | %files 48 | %defattr(-, root, root, -) 49 | /var/cache/gem/* 50 | /etc/rc.d/init.d/* 51 | 52 | %post 53 | # Stop and disable the agent (/etc/galaxy.conf required) 54 | [ -f /etc/galaxy.conf ] && service galaxy-agent stop 55 | chkconfig galaxy-agent off 56 | 57 | # Stop and disable the gonsole (/etc/galaxy.conf required) 58 | [ -f /etc/galaxy.conf ] && service galaxy-console stop 59 | chkconfig galaxy-console off 60 | 61 | # Don't kill... On Linux jails, this will affect other zones. 62 | # We have to trust the pid... 63 | ## Kill rogue Galaxy processes 64 | #killed_some= 65 | #for pid in `ps -ef | grep galaxy | grep -v grep | grep -v rpm | awk '{print $2}'` 66 | #do 67 | # kill $pid 68 | # killed_some=true 69 | #done 70 | #[ ! -z $killed_some ] && sleep 5 71 | #for pid in `ps -ef | grep galaxy | grep -v grep | grep -v rpm | awk '{print $2}'` 72 | #do 73 | # kill -9 $pid 74 | #done 75 | 76 | # Install the Galaxy gem 77 | gem install /var/cache/gem/%{gem} 78 | 79 | # Write Galaxy configuration 80 | # We assume that it is an agent by default. You'll need to update this template 81 | # on the gonsole 82 | if [ ! -f "/etc/galaxy.conf" ]; then 83 | cat < /etc/galaxy.conf 84 | # 85 | # Galaxy client properties 86 | # 87 | galaxy.client.console: %_gonsole_url 88 | 89 | ## 90 | ## Galaxy console properties 91 | ## 92 | #galaxy.console.log: SYSLOG 93 | #galaxy.console.log-level: INFO 94 | #galaxy.console.ping-interval: 90 95 | #galaxy.console.user: xncore 96 | #galaxy.console.pid-file: /home/xncore/galaxy-console.pid 97 | 98 | # 99 | # Galaxy agent properties 100 | # 101 | galaxy.agent.console: %_gonsole_url 102 | galaxy.agent.config-root: %_gepo_url 103 | galaxy.agent.binaries-root: %_gepobin_url 104 | galaxy.agent.deploy-dir: /home/xncore/deploy 105 | galaxy.agent.data-dir: /home/xncore/data 106 | galaxy.agent.log: SYSLOG 107 | galaxy.agent.log-level: INFO 108 | galaxy.agent.announce-interval: 60 109 | galaxy.agent.user: xncore 110 | galaxy.agent.pid-file: /home/xncore/galaxy-agent.pid 111 | EOF 112 | 113 | # Create /etc/rc.d files for Galaxy agent 114 | /sbin/chkconfig --add galaxy-agent 115 | 116 | # Turn on the agent (/etc/galaxy.conf required) 117 | [ -f /etc/galaxy.conf ] && /sbin/service galaxy-agent start 118 | /sbin/chkconfig galaxy-agent on 119 | 120 | # The console is not turned on by default 121 | # /sbin/chkconfig --add galaxy-console 122 | 123 | %preun 124 | # Stop Galaxy services 125 | [ -f /etc/galaxy.conf ] && service galaxy-agent stop 126 | [ -f /etc/galaxy.conf ] && service galaxy-console stop 127 | 128 | # Remove Galaxy services 129 | /sbin/chkconfig --del galaxy-agent 130 | /sbin/chkconfig --del galaxy-console 131 | 132 | %postun 133 | # Uninstall the Galaxy gem 134 | gem uninstall -v=%{version} %{name} 135 | # rpm -Uvh will install first and uninstall the old version afterwards 136 | #/bin/rm -f /etc/galaxy.conf 137 | -------------------------------------------------------------------------------- /build/start-scripts/galaxy-agent: -------------------------------------------------------------------------------- 1 | #!/bin/bash -l 2 | # chkconfig: 2345 80 90 3 | # description: Activates/Deactivates Galaxy agent 4 | 5 | GALAXY_CONFIG="/etc/galaxy.conf" 6 | GALAXY_USER=`ruby -ryaml -e "puts YAML.load(File.open('$GALAXY_CONFIG'))['galaxy.agent.user'] || ''"` 7 | 8 | if [ -z "$GALAXY_USER" ] 9 | then 10 | echo "Error: Unable to determine galaxy agent user. Please set the galaxy.agent.user property in $GALAXY_CONFIG." 11 | exit 2 12 | fi 13 | 14 | case "$1" in 15 | 'start') 16 | logger -p daemon.notice "Starting galaxy-agent" 17 | su - $GALAXY_USER -c 'galaxy-agent --start' 18 | ;; 19 | 'stop') 20 | logger -p daemon.notice "Stopping galaxy-agent" 21 | su - $GALAXY_USER -c 'galaxy-agent --stop' 22 | ;; 23 | 'restart') 24 | logger -p daemon.notice "Restarting galaxy-agent" 25 | su - $GALAXY_USER -c 'galaxy-agent --stop' 26 | su - $GALAXY_USER -c 'galaxy-agent --start' 27 | ;; 28 | 'status') 29 | galaxy-agent --status 30 | ;; 31 | *) 32 | echo "Usage: $0 " 33 | exit 1 34 | ;; 35 | esac 36 | 37 | -------------------------------------------------------------------------------- /build/start-scripts/galaxy-console: -------------------------------------------------------------------------------- 1 | #!/bin/bash -l 2 | # chkconfig: 2345 80 90 3 | # description: Activates/Deactivates Galaxy console 4 | 5 | GALAXY_CONFIG="/etc/galaxy.conf" 6 | GALAXY_USER=`ruby -ryaml -e "puts YAML.load(File.open('$GALAXY_CONFIG'))['galaxy.console.user'] || ''"` 7 | 8 | if [ -z "$GALAXY_USER" ] 9 | then 10 | echo "Error: Unable to determine galaxy console user. Please set the galaxy.console.user property in $GALAXY_CONFIG." 11 | exit 2 12 | fi 13 | 14 | case "$1" in 15 | 'start') 16 | logger -p daemon.notice "Starting galaxy-console" 17 | su - $GALAXY_USER -c 'galaxy-console --start' 18 | ;; 19 | 'stop') 20 | logger -p daemon.notice "Stopping galaxy-console" 21 | su - $GALAXY_USER -c 'galaxy-console --stop' 22 | ;; 23 | 'restart') 24 | logger -p daemon.notice "Restarting galaxy-console" 25 | su - $GALAXY_USER -c 'galaxy-console --stop' 26 | su - $GALAXY_USER -c 'galaxy-console --start' 27 | ;; 28 | 'status') 29 | galaxy-console --status 30 | ;; 31 | *) 32 | echo "Usage: $0 " 33 | exit 1 34 | ;; 35 | esac 36 | -------------------------------------------------------------------------------- /build/sun/checkinstall: -------------------------------------------------------------------------------- 1 | #!/bin/bash -l 2 | # 3 | # Determines whether Galaxy can be installed on this host 4 | # 5 | 6 | # 7 | # Reinvoke under bash to configure PATH 8 | # 9 | if [ -z "$BASH" ] 10 | then 11 | exec /bin/bash -l $0 $* 12 | fi 13 | 14 | # This script is run by nobody, who may not have ruby on the PATH 15 | PATH="/usr/local/bin:$PATH" 16 | 17 | RUBY_VERSION=`ruby --version` 18 | 19 | if [ -z "$RUBY_VERSION" ] 20 | then 21 | echo "Error: This package requires Ruby" 22 | exit 1 23 | fi 24 | 25 | GEM_VERSION=`gem --version` 26 | 27 | if [ -z "$GEM_VERSION" ] 28 | then 29 | echo "Error: This package requires RubyGems" 30 | exit 1 31 | fi 32 | -------------------------------------------------------------------------------- /build/sun/pkginfo: -------------------------------------------------------------------------------- 1 | PKG="galaxy" 2 | NAME="Galaxy :: Lightweight Code Deployment" 3 | DESC="Galaxy is a lightweight software deployment and management tool." 4 | SUNW_PKG_ALLZONES=true 5 | BASEDIR=/ 6 | VERSION="3.0.0" 7 | ARCH="all" 8 | CLASSES="none" 9 | CATEGORY="application" 10 | VENDOR="Ning, Inc." 11 | EMAIL="pierre@ning.com" 12 | -------------------------------------------------------------------------------- /build/sun/postinstall: -------------------------------------------------------------------------------- 1 | #!/bin/bash -l 2 | # 3 | # Installs the bundled Galaxy gem and imports the Galaxy SMF services 4 | # 5 | 6 | # 7 | # Reinvoke under bash to configure PATH 8 | # 9 | if [ -z "$BASH" ] 10 | then 11 | exec /bin/bash -l $0 $* 12 | fi 13 | 14 | PACKAGE="galaxy" 15 | PACKAGE_VERSION="#{PACKAGE_VERSION}" 16 | GEM_VERSION="#{GEM_VERSION}" 17 | 18 | # The filesystem containing the gem directory may be mounted read-only on 19 | # zones with shared filesystems. Do not install the gem in these cases, 20 | # but warn the user so they know that the gem was not installed. 21 | gemdir=`ruby -rubygems -e 'puts Gem::dir'` 22 | 23 | # We can't just use '[ -w $gemdir ]' here because -w only checks the mode 24 | # bits on the file and cannot determine whether the underlying filesystem 25 | # is mounted read-only. 26 | WRITABLE_TEST="$gemdir/galaxy-write-test" 27 | touch $WRITABLE_TEST 2>/dev/null 28 | 29 | if [ $? -eq 0 ] 30 | then 31 | rm $WRITABLE_TEST 32 | gem install /var/sadm/pkg/galaxy/install/$PACKAGE.gem 33 | if [ $? -ne 0 ] 34 | then 35 | echo "Error: Unable to install the $PACKAGE-$GEM_VERSION gem" 36 | exit 1 37 | fi 38 | else 39 | echo "WARNING: Skipping Gem installation for unwritable directory $gemdir (FINE ON ZONES)" 40 | fi 41 | 42 | # Configure galaxy.conf 43 | file=/etc/galaxy.conf 44 | CONSOLE="gonsole.company.com" 45 | GEPO="gepo.company.com" 46 | ENVIRONMENT="prod" 47 | if [ ! -e "$file" ]; then 48 | cat < $file 49 | # 50 | # Galaxy client properties 51 | # 52 | galaxy.client.console: $CONSOLE 53 | 54 | # 55 | # Galaxy agent properties 56 | # 57 | galaxy.agent.console: $CONSOLE 58 | galaxy.agent.config-root: http://$GEPO/config/trunk/$ENVIRONMENT 59 | galaxy.agent.binaries-root: http://$GEPO/binaries 60 | galaxy.agent.deploy-dir: /home/xncore/deploy 61 | galaxy.agent.data-dir: /home/xncore/data 62 | galaxy.agent.log: SYSLOG 63 | galaxy.agent.log-level: INFO 64 | galaxy.agent.announce-interval: 60 65 | galaxy.agent.user: xncore 66 | galaxy.agent.pid-file: /home/xncore/galaxy-agent.pid 67 | EOF 68 | fi 69 | 70 | svccfg import /var/svc/manifest/application/management/galaxy-agent.xml 71 | svccfg import /var/svc/manifest/application/management/galaxy-console.xml 72 | 73 | svccfg -s galaxy-agent setprop start/user = astring: xncore 74 | svccfg -s galaxy-agent setprop start/group = astring: xncore 75 | 76 | # Start Galaxy agent 77 | svcadm enable "galaxy-agent" 78 | -------------------------------------------------------------------------------- /build/sun/postremove: -------------------------------------------------------------------------------- 1 | #!/bin/bash -l 2 | # 3 | # Removes the previously installed Galaxy gem 4 | # 5 | 6 | # 7 | # Reinvoke under bash to configure PATH 8 | # 9 | if [ -z "$BASH" ] 10 | then 11 | exec /bin/bash -l $0 $* 12 | fi 13 | 14 | PACKAGE="galaxy" 15 | PACKAGE_VERSION="#{PACKAGE_VERSION}" 16 | GEM_VERSION="#{GEM_VERSION}" 17 | 18 | # The filesystem containing the gem directory may be mounted read-only on 19 | # zones with shared filesystems. Do not install the gem in these cases, 20 | # but warn the user so they know that the gem was not installed. 21 | gemdir=`ruby -rubygems -e 'puts Gem::dir'` 22 | 23 | # We can't just use '[ -w $gemdir ]' here because -w only checks the mode 24 | # bits on the file and cannot determine whether the underlying filesystem 25 | # is mounted read-only. 26 | WRITABLE_TEST="$gemdir/galaxy-write-test" 27 | touch $WRITABLE_TEST 2>/dev/null 28 | 29 | if [ $? -eq 0 ] 30 | then 31 | rm $WRITABLE_TEST 32 | gem uninstall -v=$GEM_VERSION $PACKAGE 33 | 34 | if [ $? -ne 0 ] 35 | then 36 | echo "Error: Unable to remove $PACKAGE-$GEM_VERSION gem" 37 | exit 1 38 | fi 39 | rm -f /etc/galaxy.conf 40 | else 41 | echo "WARNING: Skipping Gem removal for unwritable directory $gemdir" 42 | fi 43 | -------------------------------------------------------------------------------- /build/sun/preinstall: -------------------------------------------------------------------------------- 1 | #!/bin/bash -l 2 | # 3 | # Prepare for installation of Galaxy Agent. 4 | # 5 | # Solaris boxes don't run gonsoles. 6 | # 7 | 8 | # 9 | # Reinvoke under bash to configure PATH 10 | # 11 | if [ -z "$BASH" ] 12 | then 13 | exec /bin/bash -l $0 $* 14 | fi 15 | 16 | PACKAGE="galaxy" 17 | PACKAGE_VERSION="#{PACKAGE_VERSION}" 18 | GEM_VERSION="#{GEM_VERSION}" 19 | 20 | # Stop and disable Galaxy Agent 21 | 22 | ZONENAME=`zonename` 23 | if [ $ZONENAME = global ] 24 | then 25 | for zone in `zoneadm list -cv | awk '$3 == "running" && $2 != "global" { print $2 }'` # Non-global zones 26 | do 27 | zlogin $zone svcadm disable "galaxy-agent" 28 | done 29 | else 30 | svcadm disable "galaxy-agent" # Global zone 31 | fi 32 | 33 | # Kill rogue Galaxy Agent processes 34 | killed_some= 35 | for pid in `ps -ef | grep galaxy-agent | grep -v grep | awk '{print $2}'` 36 | do 37 | kill $pid 38 | killed_some=true 39 | done 40 | [ ! -z $killed_some ] && sleep 5 41 | for pid in `ps -ef | grep galaxy-agent | grep -v grep | awk '{print $2}'` 42 | do 43 | kill -9 $pid 44 | done 45 | -------------------------------------------------------------------------------- /build/sun/preremove: -------------------------------------------------------------------------------- 1 | #!/bin/bash -l 2 | # 3 | # Disables and removes the previously installed Galaxy SMF services 4 | # 5 | 6 | # 7 | # Reinvoke under bash to configure PATH 8 | # 9 | if [ -z "$BASH" ] 10 | then 11 | exec /bin/bash -l $0 $* 12 | fi 13 | 14 | svcadm disable galaxy-agent 15 | 16 | # Give the daemons time to stop 17 | sleep 3 18 | 19 | svccfg delete -f galaxy-agent 20 | 21 | # Don't fail if the daemon wasn't enabled for some reason 22 | exit 0 23 | -------------------------------------------------------------------------------- /build/sun/prototype: -------------------------------------------------------------------------------- 1 | i pkginfo 2 | i checkinstall 3 | i preinstall 4 | i postinstall 5 | i preremove 6 | i postremove 7 | i galaxy.gem 8 | f none var/svc/manifest/application/management/galaxy-agent.xml 0644 root root 9 | f none var/svc/manifest/application/management/galaxy-console.xml 0644 root root 10 | -------------------------------------------------------------------------------- /build/sun/root/var/svc/manifest/application/management/galaxy-agent.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | 30 | 32 | 33 | 34 | 38 | 39 | 40 | 41 | 45 | 46 | 47 | 48 | 49 | 50 | 55 | 56 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /build/sun/root/var/svc/manifest/application/management/galaxy-console.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | 30 | 32 | 33 | 34 | 38 | 39 | 40 | 41 | 45 | 46 | 47 | 48 | 49 | 50 | 55 | 56 | 61 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /lib/galaxy/agent.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'logger' 3 | require 'ostruct' 4 | require 'resolv' 5 | require 'socket' 6 | require 'stringio' 7 | require 'yaml' 8 | 9 | require 'galaxy/agent_remote_api' 10 | require 'galaxy/config' 11 | require 'galaxy/controller' 12 | require 'galaxy/db' 13 | require 'galaxy/deployer' 14 | require 'galaxy/events' 15 | require 'galaxy/fetcher' 16 | require 'galaxy/log' 17 | require 'galaxy/properties' 18 | require 'galaxy/repository' 19 | require 'galaxy/software' 20 | require 'galaxy/starter' 21 | require 'galaxy/transport' 22 | require 'galaxy/version' 23 | require 'galaxy/versioning' 24 | 25 | module Galaxy 26 | class Agent 27 | attr_reader :host, :machine, :config, :locked, :logger, :gonsole_url 28 | attr_accessor :starter, :fetcher, :deployer, :db 29 | 30 | include Galaxy::AgentRemoteApi 31 | 32 | def initialize host, url, machine, announcements_url, repository_base, deploy_dir, 33 | data_dir, binaries_base, http_user, http_password, log, log_level, announce_interval, event_listener 34 | @drb_url = url 35 | @host = host 36 | @machine = machine 37 | @http_user = http_user 38 | @http_password = http_password 39 | @ip = Resolv.getaddress(@host) 40 | 41 | # Setup the logger and the event dispatcher (HDFS) if needed 42 | @logger = Galaxy::Log::Glogger.new log, event_listener, announcements_url, @ip 43 | @logger.log.level = log_level 44 | 45 | @lock = OpenStruct.new(:owner => nil, :count => 0, :mutex => Mutex.new) 46 | 47 | # set up announcements 48 | @gonsole_url = announcements_url 49 | @announcer = Galaxy::Transport.locate announcements_url, @logger 50 | 51 | # Setup event listener 52 | @event_dispatcher = Galaxy::GalaxyEventSender.new(event_listener, @gonsole_url, @ip, @logger) 53 | 54 | @announce_interval = announce_interval 55 | @prop_builder = Galaxy::Properties::Builder.new repository_base, @http_user, @http_password, @logger 56 | @repository = Galaxy::Repository.new repository_base, @logger 57 | @deployer = Galaxy::Deployer.new deploy_dir, @logger 58 | @fetcher = Galaxy::Fetcher.new binaries_base, @http_user, @http_password, @logger 59 | @starter = Galaxy::Starter.new @logger 60 | @db = Galaxy::DB.new data_dir 61 | @repository_base = repository_base 62 | @binaries_base = binaries_base 63 | 64 | if RUBY_PLATFORM =~ /\w+-(\D+)/ 65 | @os = $1 66 | @logger.debug "Detected OS: #{@os}" 67 | end 68 | 69 | @logger.debug "Detected machine: #{@machine}" 70 | 71 | @config = read_config current_deployment_number 72 | 73 | Galaxy::Transport.publish url, self 74 | announce 75 | sync_state! 76 | 77 | @thread = Thread.start do 78 | loop do 79 | sleep @announce_interval 80 | announce 81 | end 82 | end 83 | end 84 | 85 | def lock 86 | @lock.mutex.synchronize do 87 | raise "Agent is locked performing another operation" unless @lock.owner.nil? || @lock.owner == Thread.current 88 | 89 | @lock.owner = Thread.current if @lock.owner.nil? 90 | 91 | @logger.debug "Locking from #{caller[2]}" if @lock.count == 0 92 | @lock.count += 1 93 | end 94 | end 95 | 96 | def unlock 97 | @lock.mutex.synchronize do 98 | raise "Lock not owned by current thread" unless @lock.owner.nil? || @lock.owner == Thread.current 99 | @lock.count -= 1 100 | @lock.owner = nil if @lock.count == 0 101 | 102 | @logger.debug "Unlocking from #{caller[2]}" if @lock.count == 0 103 | end 104 | end 105 | 106 | def status 107 | OpenStruct.new( 108 | :host => @host, 109 | :ip => @ip, 110 | :url => @drb_url, 111 | :os => @os, 112 | :machine => @machine, 113 | :core_type => config.core_type, 114 | :config_path => config.config_path, 115 | :build => config.build, 116 | :status => @starter.status(config.core_base), 117 | :last_start_time => config.last_start_time, 118 | :agent_status => 'online', 119 | :galaxy_version => Galaxy::Version 120 | ) 121 | end 122 | 123 | def announce 124 | begin 125 | res = @announcer.announce status 126 | @event_dispatcher.dispatch_announce_success_event status 127 | return res 128 | rescue Exception => e 129 | error_reason = "Unable to communicate with console, #{e.message}" 130 | @logger.warn "Unable to communicate with console, #{e.message}" 131 | @logger.warn e 132 | @event_dispatcher.dispatch_announce_error_event error_reason 133 | end 134 | end 135 | 136 | def read_config deployment_number 137 | config = nil 138 | deployment_number = deployment_number.to_s 139 | data = @db[deployment_number] 140 | unless data.nil? 141 | begin 142 | config = YAML.load data 143 | unless config.is_a? OpenStruct 144 | config = nil 145 | raise "Expecting serialized OpenStruct" 146 | end 147 | rescue Exception => e 148 | @logger.warn "Error reading deployment descriptor: #{@db.file_for(deployment_number)}: #{e}" 149 | end 150 | end 151 | config ||= OpenStruct.new 152 | # Ensure autostart=true for pre-2.5 deployments 153 | if config.auto_start.nil? 154 | config.auto_start = true 155 | end 156 | config 157 | end 158 | 159 | def write_config deployment_number, config 160 | deployment_number = deployment_number.to_s 161 | @db[deployment_number] = YAML.dump config 162 | end 163 | 164 | def current_deployment_number 165 | @db['deployment'] ||= "0" 166 | @db['deployment'].to_i 167 | end 168 | 169 | def current_deployment_number= deployment_number 170 | deployment_number = deployment_number.to_s 171 | @db['deployment'] = deployment_number 172 | @config = read_config deployment_number 173 | end 174 | 175 | # private 176 | def sync_state! 177 | lock 178 | 179 | begin 180 | if @config 181 | # Get the status from the core 182 | status = @starter.status @config.core_base 183 | @config.state = status 184 | write_config current_deployment_number, @config 185 | end 186 | ensure 187 | unlock 188 | end 189 | end 190 | 191 | # Stop the agent 192 | def shutdown 193 | @starter.stop! config.core_base if config 194 | @thread.kill 195 | Galaxy::Transport.unpublish @drb_url 196 | end 197 | 198 | # Wait for the agent to finish 199 | def join 200 | @thread.join 201 | end 202 | 203 | # args: host => IP/Name to uniquely identify this agent 204 | # console => hostname of the console 205 | # repository => base of url to repository 206 | # binaries => base of url=l to binary repository 207 | # deploy_dir => /path/to/deployment 208 | # data_dir => /path/to/agent/data/storage 209 | # log => /path/to/log || STDOUT || STDERR || SYSLOG 210 | # url => url to listen on 211 | # event_listener => url of the event listener 212 | def Agent.start args 213 | host_url = args[:host] || "localhost" 214 | host_url = "druby://#{host_url}" unless host_url.match("^http://") || host_url.match("^druby://") # defaults to drb 215 | host_url = "#{host_url}:4441" unless host_url.match ":[0-9]+$" 216 | 217 | # default console to http/4442 unless specified 218 | console_url = args[:console] || "localhost" 219 | console_url = "http://" + console_url unless console_url.match("^http://") || console_url.match("^druby://") 220 | console_url += ":4442" unless console_url.match ":[0-9]+$" 221 | 222 | # need host as simple name without protocol or port 223 | host = args[:host] || "localhost" 224 | host = host.sub(/^http:\/\//, "") 225 | host = host.sub(/^druby:\/\//, "") 226 | host = host.sub(/:[0-9]+$/, "") 227 | 228 | if args[:machine] 229 | machine = args[:machine] 230 | else 231 | machine_file = args[:machine_file] || Galaxy::Config::DEFAULT_MACHINE_FILE 232 | if File.exists? machine_file 233 | File.open machine_file, "r" do |f| 234 | machine = f.read.chomp 235 | end 236 | else 237 | machine = Socket.gethostname 238 | end 239 | end 240 | 241 | agent = Agent.new host, 242 | host_url, 243 | machine, 244 | console_url, 245 | args[:repository] || "/tmp/galaxy-agent-properties", 246 | args[:deploy_dir] || "/tmp/galaxy-agent-deploy", 247 | args[:data_dir] || "/tmp/galaxy-agent-data", 248 | args[:binaries] || "http://localhost:8000", 249 | args[:http_user], 250 | args[:http_password], 251 | args[:log] || "STDOUT", 252 | args[:log_level] || Logger::INFO, 253 | args[:announce_interval] || 60, 254 | args[:event_listener] 255 | 256 | agent 257 | end 258 | 259 | private :initialize, :sync_state!, :config 260 | end 261 | 262 | end 263 | -------------------------------------------------------------------------------- /lib/galaxy/agent_utils.rb: -------------------------------------------------------------------------------- 1 | require 'timeout' 2 | 3 | module Galaxy 4 | module AgentUtils 5 | def ping_agent agent 6 | Timeout::timeout(5) do 7 | agent.proxy.status 8 | end 9 | end 10 | 11 | module_function :ping_agent 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/galaxy/announcements.rb: -------------------------------------------------------------------------------- 1 | require 'net/http' 2 | require 'uri' 3 | require 'yaml' 4 | require 'ostruct' 5 | require 'logger' 6 | require 'rubygems' 7 | 8 | begin 9 | # mongrel is installed only on the gonsole, not on agent machines 10 | require 'mongrel' 11 | $MONGREL_LOADED = true 12 | rescue LoadError 13 | $MONGREL_LOADED = false 14 | end 15 | 16 | begin 17 | # We don't install the json gem by default on our machines. 18 | # Not critical, since it is for the HTTP API that will be rolled in the future 19 | require 'json' 20 | $JSON_LOADED = true 21 | rescue LoadError 22 | $JSON_LOADED = false 23 | end 24 | 25 | module Galaxy 26 | module HTTPUtils 27 | def url_escape(string) 28 | string.gsub(/([^ a-zA-Z0-9_.-]+)/n) do 29 | '%' + $1.unpack('H2' * $1.size).join('%').upcase 30 | end.tr(' ', '+') 31 | end 32 | 33 | def url_unescape(string) 34 | string.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n) do 35 | [$1.delete('%')].pack('H*') 36 | end 37 | end 38 | end 39 | 40 | if $MONGREL_LOADED 41 | class HTTPServer 42 | def initialize(url, console, callbacks=nil, log=nil) 43 | @log = log || Logger.new(STDOUT) 44 | 45 | # Create server 46 | begin 47 | @server = Mongrel::HttpServer.new("0.0.0.0", get_port(url)) 48 | rescue Exception => err 49 | msg = "HTTP server initialization error: #{err}" 50 | @log.error msg 51 | raise IOError, msg 52 | end 53 | 54 | @server.register("/", ReceiveAnnouncement.new(console), true) 55 | @server.register("/status", AnnouncementStatus.new, true) 56 | 57 | # Actually start the server 58 | @thread = Thread.new do 59 | begin 60 | @server.run.join 61 | rescue Exception => err 62 | msg = "HTTP server start error: #{err}" 63 | @log.error msg 64 | raise msg 65 | end 66 | end 67 | end 68 | 69 | # parse the port from the given url string 70 | def get_port(url) 71 | begin 72 | last = url.count(':') 73 | raise "malformed url: '#{url}'." if last==0 || last>2 74 | port = url.split(':')[last].to_i 75 | rescue Exception => err 76 | msg = "Problem parsing port for string '#{url}': error = #{err}" 77 | @log.error msg 78 | raise msg 79 | end 80 | port 81 | end 82 | 83 | def shutdown 84 | if @server 85 | @server.stop 86 | @server.graceful_shutdown 87 | @thread.join 88 | @server = @thread = nil 89 | end 90 | end 91 | end 92 | 93 | 94 | # POST handler that receives announcements and calls the callback function with the data payload 95 | class ReceiveAnnouncement < Mongrel::HttpHandler 96 | ANNOUNCEMENT_RESPONSE_TEXT = 'Announcement received.' 97 | 98 | def initialize(console) 99 | @console = console 100 | end 101 | 102 | def process(request, response) 103 | response.start(200) do |head, out| 104 | head['Context-Type'] = 'text/plain; charset=utf-8' 105 | head['Connection'] = 'close' 106 | if request.params['REQUEST_METHOD'] == 'POST' 107 | @console.process_post(YAML::load(request.body)) 108 | out.write ANNOUNCEMENT_RESPONSE_TEXT 109 | elsif request.params['REQUEST_METHOD'] == 'GET' 110 | out.write @console.process_get(request.params['REQUEST_PATH']) 111 | end 112 | end 113 | end 114 | end 115 | 116 | # optional GET response for querying the server status 117 | class AnnouncementStatus < Mongrel::HttpHandler 118 | def process(request, response) 119 | if request.params['REQUEST_METHOD'] == 'GET' 120 | response.start(200) do |head, out| 121 | head['Context-Type'] = 'text/plain; charset=utf-8' 122 | head['Connection'] = 'close' 123 | time = Time.now 124 | body = "" 125 | body += "

Announcement Status



"; 126 | body += time.strftime("%Y%m%d-%H:%M:%S") + sprintf(".%06d", time.usec) 127 | body += "" 128 | out.write body 129 | end 130 | end 131 | end 132 | end 133 | end 134 | end 135 | 136 | # HTTP client library. 137 | # Used by the galaxy agent to send announcements to the server. 138 | # Used by the command line client to query the gonsole over HTTP. 139 | class HTTPAnnouncementSender 140 | include Galaxy::HTTPUtils 141 | 142 | def initialize(url, log = nil) 143 | # eg: 'http://encomium.company.com:4440' 144 | @uri = URI.parse(url) 145 | @log = log 146 | end 147 | 148 | # Announce an agent to a gonsole 149 | # agent is an OpenStruct defining the state of the agent. 150 | def announce(agent) 151 | begin 152 | # POST 153 | Net::HTTP.start(@uri.host, @uri.port) do |http| 154 | headers = {'Content-Type' => 'text/plain; charset=utf-8', 'Connection' => 'close'} 155 | put_data = agent.to_yaml 156 | start_time = Time.now 157 | response = http.send_request('POST', @uri.request_uri, put_data, headers) 158 | @log.debug "Announcement send response time for #{agent.host} = #{Time.now-start_time}" if @log 159 | #puts "Response = #{response.code} #{response.message}: #{response.body}" 160 | response.body 161 | end 162 | rescue Exception => e 163 | @log.warn "Client side error: #{e}" if @log 164 | end 165 | end 166 | 167 | # Retrieve a list of agents matching a giving filter 168 | # args is a hash filter (cf Galaxy::Filter). 169 | def agents(args) 170 | # Convert filter string ({:set=>:all}) to URI string (/set=all) 171 | # XXX Built-in method to do that? 172 | filter = "" 173 | args.each do |key, value| 174 | filter += url_escape(key.to_s) + "=" + url_escape(value.to_s) + "&" 175 | end 176 | filter.chomp!("&") 177 | 178 | begin 179 | Net::HTTP.start(@uri.host, @uri.port) do |http| 180 | headers = {'Content-Type' => 'text/plain; charset=utf-8', 'Connection' => 'close'} 181 | start_time = Time.now 182 | response = http.send_request('GET', @uri.request_uri + filter, headers) 183 | @log.debug "Announcement send response time for #{agent.host} = #{Time.now-start_time}" if @log 184 | return JSON.parse(response.body).collect { |x| OpenStruct.new(x) } 185 | end 186 | rescue Exception => e 187 | # If the json gem is not loaded, we will log the issue here. 188 | @log.warn "Client side error: #{e}" if @log 189 | return [] 190 | end 191 | end 192 | 193 | # :nodoc: 194 | # Compatibility with the DRb galaxy client. 195 | # XXX Should go away (overhead). 196 | def log(* args) 197 | nil 198 | end 199 | end 200 | 201 | 202 | ################################################################################################ 203 | # 204 | # sample MAIN 205 | # 206 | 207 | # example callback for action upon receiving an announcement 208 | def on_announcement(ann) 209 | puts "...received announcement: #{ann.inspect}" 210 | end 211 | 212 | # Initialize and POST to server 213 | if $0 == __FILE__ then 214 | # start server 215 | url = 'http://encomium.company.com:4440' 216 | Galaxy::HTTPAnnouncementReceiver.new(url, lambda { |a| on_announcement(a) }) 217 | announcer = HTTPAnnouncementSender.new(url) 218 | 219 | # periodically, send stuff to it 220 | loop do 221 | begin 222 | 223 | announcer.announce(OpenStruct.new(:foo=>"bar", :rand => rand(100), :item => "eggs")) 224 | 225 | puts "server running..." 226 | sleep 15 227 | rescue Exception => err 228 | STDERR.puts "* #{err}" 229 | exit(1) 230 | end 231 | end 232 | end 233 | -------------------------------------------------------------------------------- /lib/galaxy/client.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'etc' 3 | require 'resolv' 4 | 5 | class CommandLineError < Exception; 6 | end 7 | 8 | def prompt_and_wait_for_user_confirmation prompt 9 | confirmed = false 10 | loop do 11 | $stderr.print prompt 12 | $stderr.flush 13 | case $stdin.gets.chomp.downcase 14 | when "y" 15 | confirmed = true 16 | break 17 | when "n" 18 | break 19 | else 20 | $stderr.puts "Please enter 'y' or 'n'" 21 | end 22 | end 23 | confirmed 24 | end 25 | 26 | # Expand the supplied console_url (which may just consist of hostname) to a full URL, assuming DRb as the default transport 27 | def normalize_console_url console_url 28 | console_url = "druby://#{console_url}" unless console_url.match(/^\w+:\/\//) 29 | console_url ="#{console_url}:4440" unless console_url.match(/:\d+$/) 30 | console_url 31 | end 32 | 33 | # Expand short hostnames to their fully-qualified names 34 | # 35 | # This implementation depends on the client and agents sharing a common naming service (hosts files, NIS, DNS, etc). 36 | def canonical_hostname hostname 37 | Resolv.getname(Resolv.getaddress(hostname)) 38 | end 39 | -------------------------------------------------------------------------------- /lib/galaxy/command.rb: -------------------------------------------------------------------------------- 1 | require 'galaxy/agent_utils' 2 | require 'galaxy/parallelize' 3 | require 'galaxy/report' 4 | 5 | module Galaxy 6 | module Commands 7 | @@commands = {} 8 | 9 | def self.register_command command_name, command_class 10 | @@commands[command_name] = command_class 11 | end 12 | 13 | def self.[] command_name 14 | @@commands[command_name] 15 | end 16 | 17 | def self.each 18 | @@commands.keys.sort.each { |command| yield command } 19 | end 20 | 21 | class Command 22 | class << self 23 | attr_reader :name 24 | end 25 | 26 | attr_writer :report_class, :report, :error_report 27 | 28 | def self.register_command name 29 | @name = name 30 | Galaxy::Commands.register_command name, self 31 | end 32 | 33 | def self.changes_agent_state 34 | define_method("changes_agent_state") do 35 | true 36 | end 37 | end 38 | 39 | def self.changes_console_state 40 | define_method("changes_console_state") do 41 | true 42 | end 43 | end 44 | 45 | def initialize args = [], options = {} 46 | @args = args 47 | @options = options 48 | @options[:thread_count] ||= 1 49 | end 50 | 51 | def changes_agent_state 52 | false 53 | end 54 | 55 | def changes_console_state 56 | false 57 | end 58 | 59 | def select_agents filter 60 | normalized_filter = normalize_filter(filter) 61 | @options[:console].agents(normalized_filter) 62 | end 63 | 64 | def normalize_filter filter 65 | filter = default_filter if filter.empty? 66 | filter 67 | end 68 | 69 | def default_filter 70 | {:set => :all} 71 | end 72 | 73 | def execute agents 74 | report.start 75 | error_report.start 76 | agents.parallelize(@options[:thread_count]) do |agent| 77 | begin 78 | unless agent.agent_status == 'online' 79 | raise "Agent is not online" 80 | end 81 | Galaxy::AgentUtils::ping_agent(agent) 82 | result = execute_for_agent(agent) 83 | report.record_result result 84 | rescue TimeoutError 85 | error_report.record_result "Error: Timed out communicating with agent #{agent.host}" 86 | rescue Exception => e 87 | error_report.record_result "Error: #{agent.host}: #{e}" 88 | end 89 | end 90 | return report.finish, error_report.finish 91 | end 92 | 93 | def report 94 | @report ||= report_class.new 95 | end 96 | 97 | def report_class 98 | @report_class ||= Galaxy::Client::SoftwareDeploymentReport 99 | end 100 | 101 | def error_report 102 | @error_report ||= Galaxy::Client::Report.new 103 | end 104 | end 105 | end 106 | end 107 | 108 | # load and register all commands 109 | Dir.entries("#{File.join(File.dirname(__FILE__))}/commands").each do |entry| 110 | if entry =~ /\.rb$/ 111 | require "galaxy/commands/#{File.basename(entry, '.rb')}" 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /lib/galaxy/commands/assign.rb: -------------------------------------------------------------------------------- 1 | require 'galaxy/report' 2 | 3 | module Galaxy 4 | module Commands 5 | class AssignCommand < Command 6 | register_command "assign" 7 | changes_agent_state 8 | 9 | def initialize args, options 10 | super 11 | 12 | env, version, type = * args 13 | 14 | raise CommandLineError.new(" is missing") unless env 15 | raise CommandLineError.new(" is missing") unless version 16 | raise CommandLineError.new(" is missing") unless type 17 | 18 | @config_path = "/#{env}/#{version}/#{type}" 19 | @versioning_policy = options[:versioning_policy] 20 | end 21 | 22 | def default_filter 23 | {:set => :empty} 24 | end 25 | 26 | def execute_for_agent agent 27 | agent.proxy.become!(@config_path, @versioning_policy) 28 | end 29 | 30 | def self.help 31 | return <<-HELP 32 | #{name} 33 | 34 | Deploy software to the selected hosts 35 | 36 | Parameters: 37 | env The environment 38 | version The software version 39 | type The software type 40 | 41 | These three parameters together define the configuration path (relative to the repository base): 42 | 43 | /// 44 | HELP 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/galaxy/commands/cleanup.rb: -------------------------------------------------------------------------------- 1 | module Galaxy 2 | module Commands 3 | class CleanupCommand < Command 4 | register_command "cleanup" 5 | 6 | def execute_for_agent agent 7 | agent.proxy.cleanup! 8 | end 9 | 10 | def self.help 11 | return <<-HELP 12 | #{name} 13 | 14 | Remove all deployments up to the current and last one, for rollback. 15 | HELP 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/galaxy/commands/clear.rb: -------------------------------------------------------------------------------- 1 | module Galaxy 2 | module Commands 3 | class ClearCommand < Command 4 | register_command "clear" 5 | changes_agent_state 6 | 7 | def normalize_filter filter 8 | filter = super 9 | filter[:set] = :taken if filter[:set] == :all 10 | filter 11 | end 12 | 13 | def execute_for_agent agent 14 | agent.proxy.clear! 15 | end 16 | 17 | def self.help 18 | return <<-HELP 19 | #{name} 20 | 21 | Stop and clear the active software deployment on the selected hosts 22 | HELP 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/galaxy/commands/perform.rb: -------------------------------------------------------------------------------- 1 | module Galaxy 2 | module Commands 3 | class PerformCommand < Command 4 | register_command "perform" 5 | changes_agent_state 6 | 7 | def initialize args, options 8 | super 9 | 10 | @command = args.shift 11 | raise CommandLineError.new(" is missing") unless @command 12 | @args = args 13 | end 14 | 15 | def normalize_filter filter 16 | filter = super 17 | filter[:set] = :taken if filter[:set] == :all 18 | filter 19 | end 20 | 21 | def execute_for_agent agent 22 | agent.proxy.perform! @command, @args.join(' ') 23 | end 24 | 25 | def report_class 26 | Galaxy::Client::CommandOutputReport 27 | end 28 | 29 | def self.help 30 | return <<-HELP 31 | #{name} 32 | 33 | galaxy perform [args] 34 | 35 | Launch the control script (bin/control) with the indicated command on the selected hosts, optionally passing the provided arguments 36 | HELP 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/galaxy/commands/reap.rb: -------------------------------------------------------------------------------- 1 | module Galaxy 2 | module Commands 3 | class ReapCommand < Command 4 | register_command "reap" 5 | changes_console_state 6 | 7 | def execute agents 8 | report.start 9 | agents.sort_by { |agent| agent.host }.each do |agent| 10 | reaped = @options[:console].reap(agent.host) 11 | report.record_result("#{agent.host} - reap #{reaped.nil? ? 'failed' : 'succeeded'}") 12 | end 13 | [report.finish, nil] 14 | end 15 | 16 | def report_class 17 | Galaxy::Client::Report 18 | end 19 | 20 | def self.help 21 | return <<-HELP 22 | #{name} 23 | 24 | Delete stale announcements (from the console) for the selected hosts, without affecting agents 25 | HELP 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/galaxy/commands/restart.rb: -------------------------------------------------------------------------------- 1 | module Galaxy 2 | module Commands 3 | class RestartCommand < Command 4 | register_command "restart" 5 | changes_agent_state 6 | 7 | def normalize_filter filter 8 | filter = super 9 | filter[:set] = :taken if filter[:set] == :all 10 | filter 11 | end 12 | 13 | def execute_for_agent agent 14 | agent.proxy.restart! 15 | end 16 | 17 | def self.help 18 | return <<-HELP 19 | #{name} 20 | 21 | Restart the deployed software on the selected hosts 22 | HELP 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/galaxy/commands/rollback.rb: -------------------------------------------------------------------------------- 1 | module Galaxy 2 | module Commands 3 | class RollbackCommand < Command 4 | register_command "rollback" 5 | changes_agent_state 6 | 7 | def normalize_filter filter 8 | filter = super 9 | filter[:set] = :taken if filter[:set] == :all 10 | filter 11 | end 12 | 13 | def execute_for_agent agent 14 | agent.proxy.rollback! 15 | end 16 | 17 | def self.help 18 | return <<-HELP 19 | #{name} 20 | 21 | Stop and rollback software to the previously deployed version 22 | HELP 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/galaxy/commands/show.rb: -------------------------------------------------------------------------------- 1 | module Galaxy 2 | module Commands 3 | class ShowCommand < Command 4 | register_command "show" 5 | 6 | def execute agents 7 | report.start 8 | agents.sort_by { |agent| agent.host }.each do |agent| 9 | report.record_result agent 10 | end 11 | report.finish 12 | end 13 | 14 | def self.help 15 | return <<-HELP 16 | #{name} 17 | 18 | Show software deployments on the selected hosts 19 | 20 | Examples: 21 | 22 | - Show all hosts: 23 | galaxy show 24 | 25 | - Show unassigned hosts: 26 | galaxy -s empty show 27 | 28 | - Show assigned hosts: 29 | galaxy -s taken show 30 | 31 | - Show a specific host: 32 | galaxy -i foo.bar.com show 33 | 34 | - Show all widgets: 35 | galaxy -t widget show 36 | HELP 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/galaxy/commands/show_agent.rb: -------------------------------------------------------------------------------- 1 | module Galaxy 2 | module Commands 3 | class ShowAgentCommand < Command 4 | register_command "show-agent" 5 | 6 | def execute agents 7 | report.start 8 | agents.sort_by { |agent| agent.host }.each do |agent| 9 | report.record_result agent 10 | end 11 | report.finish 12 | end 13 | 14 | def report_class 15 | Galaxy::Client::AgentStatusReport 16 | end 17 | 18 | def self.help 19 | return <<-HELP 20 | #{name} 21 | 22 | Show metadata about the selected Galaxy agents 23 | HELP 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/galaxy/commands/show_console.rb: -------------------------------------------------------------------------------- 1 | module Galaxy 2 | module Commands 3 | class ShowConsoleCommand < Command 4 | register_command "show-console" 5 | 6 | def execute agents 7 | report.start 8 | report.record_result @options[:console] 9 | report.finish 10 | end 11 | 12 | def report_class 13 | Galaxy::Client::ConsoleStatusReport 14 | end 15 | 16 | def self.help 17 | return <<-HELP 18 | #{name} 19 | 20 | Show metadata about the selected Galaxy console 21 | HELP 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/galaxy/commands/show_core.rb: -------------------------------------------------------------------------------- 1 | module Galaxy 2 | module Commands 3 | class ShowCoreCommand < Command 4 | register_command "show-core" 5 | 6 | def execute agents 7 | report.start 8 | agents.sort_by { |agent| agent.host }.each do |agent| 9 | report.record_result agent 10 | end 11 | report.finish 12 | end 13 | 14 | def report_class 15 | Galaxy::Client::CoreStatusReport 16 | end 17 | 18 | def self.help 19 | return <<-HELP 20 | #{name} 21 | 22 | Show core status (last start time, ...) on the selected hosts 23 | See "galaxy show -h" for help and examples on flags usage 24 | HELP 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/galaxy/commands/ssh.rb: -------------------------------------------------------------------------------- 1 | module Galaxy 2 | module Commands 3 | class SSHCommand < Command 4 | register_command "ssh" 5 | 6 | def execute agents 7 | agent = agents.first 8 | command = ENV['GALAXY_SSH_COMMAND'] || "ssh" 9 | Kernel.system "#{command} #{agent.host}" if agent 10 | end 11 | 12 | def self.help 13 | return <<-HELP 14 | #{name} 15 | 16 | Connect via ssh to the first host matching the selection criteria 17 | 18 | The GALAXY_SSH_COMMAND environment variable can be set to specify options for ssh. 19 | 20 | For example, this instructs galaxy to login as user 'foo': 21 | 22 | export GALAXY_SSH_COMMAND="ssh -l foo" 23 | HELP 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/galaxy/commands/start.rb: -------------------------------------------------------------------------------- 1 | module Galaxy 2 | module Commands 3 | class StartCommand < Command 4 | register_command "start" 5 | changes_agent_state 6 | 7 | def normalize_filter filter 8 | filter = super 9 | filter[:set] = :taken if filter[:set] == :all 10 | filter 11 | end 12 | 13 | def execute_for_agent agent 14 | agent.proxy.start! 15 | end 16 | 17 | def self.help 18 | return <<-HELP 19 | #{name} 20 | 21 | Start the deployed software on the selected hosts 22 | HELP 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/galaxy/commands/stop.rb: -------------------------------------------------------------------------------- 1 | module Galaxy 2 | module Commands 3 | class StopCommand < Command 4 | register_command "stop" 5 | changes_agent_state 6 | 7 | def normalize_filter filter 8 | filter = super 9 | filter[:set] = :taken if filter[:set] == :all 10 | filter 11 | end 12 | 13 | def execute_for_agent agent 14 | agent.proxy.stop! 15 | end 16 | 17 | def self.help 18 | return <<-HELP 19 | #{name} 20 | 21 | Stop the deployed software on the selected hosts 22 | HELP 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/galaxy/commands/update.rb: -------------------------------------------------------------------------------- 1 | require 'galaxy/software' 2 | 3 | module Galaxy 4 | module Commands 5 | class UpdateCommand < Command 6 | register_command "update" 7 | changes_agent_state 8 | 9 | def initialize args, options 10 | super 11 | @requested_version = args.first 12 | raise CommandLineError.new("Must specify version") unless @requested_version 13 | @versioning_policy = options[:versioning_policy] 14 | end 15 | 16 | def normalize_filter filter 17 | filter = super 18 | filter[:set] = :taken if filter[:set] == :all 19 | filter 20 | end 21 | 22 | def execute_for_agent agent 23 | if agent.config_path.nil? or agent.config_path.empty? 24 | raise "Cannot update unassigned agent" 25 | end 26 | current_config = Galaxy::SoftwareConfiguration.new_from_config_path(agent.config_path) # TODO - this should already be tracked 27 | requested_config = current_config.dup 28 | requested_config.version = @requested_version 29 | agent.proxy.become!(requested_config.config_path, @versioning_policy) 30 | end 31 | 32 | def self.help 33 | return <<-HELP 34 | #{name} 35 | 36 | Stop and update the software on the selected hosts to the specified version 37 | HELP 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/galaxy/commands/update_config.rb: -------------------------------------------------------------------------------- 1 | module Galaxy 2 | module Commands 3 | class UpdateConfigCommand < Command 4 | register_command "update-config" 5 | changes_agent_state 6 | 7 | def initialize args, options 8 | super 9 | 10 | @requested_version = args.first 11 | raise CommandLineError.new("Must specify version") unless @requested_version 12 | 13 | @versioning_policy = options[:versioning_policy] 14 | end 15 | 16 | def normalize_filter filter 17 | filter = super 18 | filter[:set] = :taken if filter[:set] == :all 19 | filter 20 | end 21 | 22 | def execute_for_agent agent 23 | if agent.config_path.nil? or agent.config_path.empty? 24 | raise "Cannot update configuration of unassigned agent" 25 | end 26 | current_config = Galaxy::SoftwareConfiguration.new_from_config_path(agent.config_path) # TODO - this should already be tracked 27 | agent.proxy.update_config!(@requested_version, @versioning_policy) 28 | end 29 | 30 | def self.help 31 | return <<-HELP 32 | #{name} 33 | 34 | Update the software configuration on the selected hosts to the specified version 35 | 36 | This does NOT redeploy or restart the software. If a restart is desired to activate the new configuration, it must be done separately. 37 | HELP 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/galaxy/console.rb: -------------------------------------------------------------------------------- 1 | require 'ostruct' 2 | require 'logger' 3 | require 'rubygems' 4 | begin 5 | # We don't install the json gem by default on our machines. 6 | # Not critical, since it is for the HTTP API that will be rolled in the future 7 | require 'json' 8 | $JSON_LOADED = true 9 | rescue LoadError 10 | $JSON_LOADED = false 11 | end 12 | require 'resolv' 13 | 14 | require 'galaxy/events' 15 | require 'galaxy/filter' 16 | require 'galaxy/log' 17 | require 'galaxy/transport' 18 | require 'galaxy/announcements' 19 | 20 | module Galaxy 21 | class Console 22 | attr_reader :db, :drb_url, :http_url, :ping_interval, :host, :env, :logger 23 | 24 | def self.locate url 25 | Galaxy::Transport.locate url 26 | end 27 | 28 | def initialize drb_url, http_url, log, log_level, ping_interval, host, env, event_listener 29 | @host = host 30 | @ip = Resolv.getaddress(@host) 31 | @env = env 32 | 33 | @drb_url = drb_url 34 | @http_url = http_url 35 | 36 | # Setup the logger and the event dispatcher (HDFS) if needed 37 | @logger = Galaxy::Log::Glogger.new(log, event_listener, @http_url, @ip) 38 | @logger.log.level = log_level 39 | 40 | @ping_interval = ping_interval 41 | @db = {} 42 | @mutex = Mutex.new 43 | 44 | # set up event listener 45 | @event_dispatcher = Galaxy::GalaxyEventSender.new(event_listener, @http_url, @ip, @logger) 46 | 47 | Thread.new do 48 | loop do 49 | begin 50 | cutoff = Time.new 51 | sleep @ping_interval 52 | ping cutoff 53 | rescue Exception => e 54 | @logger.warn "Uncaught exception in agent ping thread: #{e}" 55 | @logger.warn e.backtrace 56 | end 57 | end 58 | end 59 | end 60 | 61 | # Remote API 62 | def reap host 63 | @mutex.synchronize do 64 | @db.delete host 65 | end 66 | end 67 | 68 | # Return agents matching a filter query 69 | # Used by both HTTP and DRb API. 70 | def agents filters = {} 71 | # Log command run by the client 72 | if filters[:command] 73 | @logger.info filters[:command] 74 | filters.delete :command 75 | end 76 | 77 | filters = {:set => :all} if (filters.empty? or filters.nil?) 78 | 79 | filter = Galaxy::Filter.new filters 80 | @logger.debug "Filtering agents by #{filter}" 81 | 82 | @mutex.synchronize do 83 | @db.values.select(& filter) 84 | end 85 | end 86 | 87 | # Process announcement (ping) from agent (HTTP API) 88 | # 89 | # this function is called as a callback from http post server. We could just use the announce function as the 90 | # callback, but using this function allows us to add in different stats for post announcements. 91 | def process_post announcement 92 | announce announcement 93 | end 94 | 95 | include Galaxy::HTTPUtils 96 | 97 | # Return agents matching a filter query (HTTP API). 98 | # 99 | # Note that & in the query means actually OR. 100 | def process_get query_string 101 | # Convert env=prod&host=prod-1.company.com to {:env => "prod", :host => 102 | # "prod-1.company.com"} 103 | filters = {} 104 | CGI::parse(query_string).each { |k, v| filters[k.to_sym] = v.first } 105 | if $JSON_LOADED 106 | return agents(filters).to_json 107 | else 108 | return agents(filters).inspect 109 | end 110 | end 111 | 112 | # Remote API 113 | def dispatch_event type, msg 114 | @event_dispatcher.send("dispatch_#{type}_event", msg) 115 | end 116 | 117 | def Console.start args 118 | host = args[:host] || "localhost" 119 | drb_url = args[:url] || "druby://" + host # DRB transport 120 | drb_url += ":4440" unless drb_url.match ":[0-9]+$" 121 | 122 | http_url = args[:announcement_url] || "http://localhost" # http announcements 123 | http_url = "#{http_url}:4442" unless http_url.match ":[0-9]+$" 124 | 125 | console = Console.new drb_url, http_url, 126 | args[:log] || "STDOUT", 127 | args[:log_level] || Logger::INFO, 128 | args[:ping_interval] || 5, 129 | host, args[:environment], args[:event_listener] 130 | 131 | # DRb transport (galaxy command line client) 132 | Galaxy::Transport.publish drb_url, console, console.logger 133 | 134 | # HTTP API (announcements, status, ...) 135 | Galaxy::Transport.publish http_url, console, console.logger 136 | 137 | console 138 | end 139 | 140 | def shutdown 141 | Galaxy::Transport.unpublish @http_url 142 | end 143 | 144 | def join 145 | Galaxy::Transport.join @drb_url 146 | end 147 | 148 | private 149 | 150 | # Update the agents database 151 | def announce announcement 152 | begin 153 | host = announcement.host 154 | @logger.debug "Received announcement from #{host}" 155 | @mutex.synchronize do 156 | if @db.has_key?(host) 157 | unless @db[host].agent_status != "offline" 158 | announce_message = "#{host} is now online again" 159 | @logger.info announce_message 160 | @event_dispatcher.dispatch_announce_success_event announce_message 161 | end 162 | if @db[host].status != announcement.status 163 | announce_message = "#{host} core state changed: #{@db[host].status} --> #{announcement.status}" 164 | @logger.info announce_message 165 | @event_dispatcher.dispatch_announce_success_event announce_message 166 | end 167 | else 168 | announce_message = "Discovered new agent: #{host} [#{announcement.inspect}]" 169 | @logger.info "Discovered new agent: #{host} [#{announcement.inspect}]" 170 | @event_dispatcher.dispatch_announce_success_event announce_message 171 | end 172 | 173 | @db[host] = announcement 174 | @db[host].timestamp = Time.now 175 | @db[host].agent_status = 'online' 176 | end 177 | rescue RuntimeError => e 178 | error_message = "Error receiving announcement: #{e}" 179 | @logger.warn error_message 180 | @event_dispatcher.dispatch_announce_error_event error_message 181 | end 182 | end 183 | 184 | # Iterate through the database to find agents that haven't pinged home 185 | def ping cutoff 186 | @mutex.synchronize do 187 | @db.each_pair do |host, entry| 188 | if entry.agent_status != "offline" and entry.timestamp < cutoff 189 | error_message = "#{host} failed to announce; marking as offline" 190 | @logger.warn error_message 191 | @event_dispatcher.dispatch_announce_error_event error_message 192 | 193 | entry.agent_status = "offline" 194 | entry.status = "unknown" 195 | end 196 | end 197 | end 198 | end 199 | end 200 | end 201 | -------------------------------------------------------------------------------- /lib/galaxy/controller.rb: -------------------------------------------------------------------------------- 1 | require 'logger' 2 | 3 | module Galaxy 4 | class Controller 5 | def initialize core_base, config_path, repository_base, binaries_base, log 6 | @core_base = core_base 7 | @config_path = config_path 8 | @repository_base = repository_base 9 | @binaries_base = binaries_base 10 | script = File.join(@core_base, "bin", "control") 11 | if File.exists? script 12 | @script = File.executable?(script) ? script : "/bin/sh #{script}" 13 | else 14 | raise ControllerNotFoundException.new 15 | end 16 | @log = log 17 | end 18 | 19 | def perform! command, args = '' 20 | @log.info "Invoking control script: #{@script} #{command} #{args}" 21 | 22 | begin 23 | output = `#{@script} --base #{@core_base} --binaries #{@binaries_base} --config-path #{@config_path} --repository #{@repository_base} #{command} #{args} 2>&1` 24 | rescue Exception => e 25 | raise ControllerFailureException.new(command, e) 26 | end 27 | 28 | rv = $?.exitstatus 29 | 30 | case rv 31 | when 0 32 | output 33 | when 1 34 | raise CommandFailedException.new(command, output) 35 | when 2 36 | raise UnrecognizedCommandException.new(command, output) 37 | else 38 | raise UnrecognizedResponseCodeException.new(rv, command, output) 39 | end 40 | end 41 | 42 | class ControllerException < RuntimeError; 43 | end 44 | 45 | class ControllerNotFoundException < ControllerException 46 | def initialize 47 | super "No control script available" 48 | end 49 | end 50 | 51 | class ControllerFailureException < ControllerException 52 | def initialize command, exception 53 | super "Unexpected exception executing command '#{command}': #{exception}" 54 | end 55 | end 56 | 57 | class CommandFailedException < ControllerException 58 | def initialize command, output 59 | message = "Command failed: #{command}" 60 | message += ": #{output}" unless output.empty? 61 | super message 62 | end 63 | end 64 | 65 | class UnrecognizedCommandException < ControllerException 66 | def initialize command, output 67 | message = "Unrecognized command: #{command}" 68 | message += ": #{output}" unless output.empty? 69 | super message 70 | end 71 | end 72 | 73 | class UnrecognizedResponseCodeException < ControllerException 74 | def initialize code, command, output 75 | message = "Unrecognized response code #{code} for command #{command}" 76 | message += ": #{output}" unless output.empty? 77 | super message 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/galaxy/daemon.rb: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # daemonize.rb is a slightly modified version of daemonize.rb was # 3 | # from the Daemonize Library written by Travis Whitton # 4 | # for details see http://grub.ath.cx/daemonize/ # 5 | ############################################################################### 6 | 7 | require 'galaxy/host' 8 | require 'fileutils' 9 | 10 | module Galaxy 11 | module Daemonize 12 | VERSION = "0.1.2" 13 | 14 | # Try to fork if at all possible retrying every 5 sec if the 15 | # maximum process limit for the system has been reached 16 | def safefork 17 | tryagain = true 18 | 19 | while tryagain 20 | tryagain = false 21 | begin 22 | if pid = fork 23 | return pid 24 | end 25 | rescue Errno::EWOULDBLOCK 26 | sleep 5 27 | tryagain = true 28 | end 29 | end 30 | end 31 | 32 | # This method causes the current running process to become a daemon 33 | # If closefd is true, all existing file descriptors are closed 34 | def daemonize(log = nil, oldmode=0, closefd=false) 35 | srand # Split rand streams between spawning and daemonized process 36 | safefork and exit # Fork and exit from the parent 37 | 38 | # Detach from the controlling terminal 39 | unless sess_id = Process.setsid 40 | raise 'Cannot detach from controlled terminal' 41 | end 42 | 43 | # Prevent the possibility of acquiring a controlling terminal 44 | if oldmode.zero? 45 | trap 'SIGHUP', 'IGNORE' 46 | exit if pid = safefork 47 | end 48 | 49 | Dir.chdir "/" # Release old working directory 50 | File.umask 0000 # Insure sensible umask 51 | 52 | if closefd 53 | # Make sure all file descriptors are closed 54 | ObjectSpace.each_object(IO) do |io| 55 | unless [STDIN, STDOUT, STDERR].include?(io) 56 | io.close rescue nil 57 | end 58 | end 59 | end 60 | 61 | log ||= "/dev/null" 62 | 63 | STDIN.reopen "/dev/null" # Free file descriptors and 64 | STDOUT.reopen log, "a" # point them somewhere sensible 65 | STDERR.reopen STDOUT # STDOUT/STDERR should go to a logfile 66 | return oldmode ? sess_id : 0 # Return value is mostly irrelevant 67 | end 68 | end 69 | 70 | class Daemon 71 | include Galaxy::Daemonize 72 | 73 | def self.pid_for pid_file 74 | begin 75 | File.open(pid_file) do |f| 76 | f.gets 77 | end.to_i 78 | rescue Errno::ENOENT 79 | return nil 80 | end 81 | end 82 | 83 | def self.kill_daemon pid_file 84 | pid = pid_for(pid_file) 85 | if pid.nil? 86 | raise "Cannot determine process id: Pid file #{pid_file} not found" 87 | end 88 | begin 89 | Process.kill("TERM", pid) 90 | rescue Errno::ESRCH 91 | raise "Cannot kill process id #{pid}: Not running" 92 | rescue Errno::EPERM 93 | raise "Cannot kill process id #{pid}: Permission denied" 94 | end 95 | end 96 | 97 | def self.daemon_running? pid_file 98 | pid = pid_for(pid_file) 99 | if pid.nil? 100 | return false 101 | end 102 | begin 103 | Process.kill(0, pid) 104 | rescue Errno::ESRCH 105 | return false 106 | rescue Errno::EPERM 107 | return true 108 | end 109 | return true 110 | end 111 | 112 | def initialize & block 113 | @block = block 114 | end 115 | 116 | def go pid_file, log 117 | daemonize(log) 118 | 119 | File.open(pid_file, "w", 0644) do |f| 120 | f.write Process.pid 121 | end 122 | trap "TERM" do 123 | FileUtils.rm_f pid_file 124 | exit 0 125 | end 126 | trap "KILL" do 127 | FileUtils.rm_f pid_file 128 | exit 0 129 | end 130 | @block.call 131 | end 132 | 133 | def self.start name, pid_file, user = nil, log = nil, & block 134 | Galaxy::HostUtils::switch_user(user) unless user.nil? 135 | if daemon_running?(pid_file) 136 | pid = pid_for(pid_file) 137 | abort("Error: #{name} is already running as pid #{pid}") 138 | end 139 | Daemon.new(& block).go(pid_file, log) 140 | end 141 | end 142 | end 143 | -------------------------------------------------------------------------------- /lib/galaxy/db.rb: -------------------------------------------------------------------------------- 1 | require 'thread' 2 | 3 | module Galaxy 4 | class DB 5 | def initialize path 6 | @lock = Mutex.new 7 | @path = path 8 | Dir.mkdir @path rescue nil 9 | end 10 | 11 | def delete_at key 12 | @lock.synchronize do 13 | FileUtils.rm_f file_for(key) 14 | end 15 | end 16 | 17 | def []= key, value 18 | @lock.synchronize do 19 | File.open file_for(key), "w" do |f| 20 | f.write(value) 21 | end 22 | end 23 | end 24 | 25 | def [] key 26 | @lock.synchronize do 27 | result = nil 28 | begin 29 | File.open file_for(key), "r" do |f| 30 | result = f.read 31 | end 32 | rescue Errno::ENOENT 33 | end 34 | 35 | return result 36 | end 37 | end 38 | 39 | def file_for key 40 | File.join(@path, key) 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/galaxy/deployer.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'tempfile' 3 | require 'logger' 4 | require 'galaxy/host' 5 | 6 | module Galaxy 7 | class Deployer 8 | attr_reader :log 9 | 10 | def initialize deploy_dir, log 11 | @base, @log = deploy_dir, log 12 | end 13 | 14 | # number is the deployment number for this agent 15 | # archive is the path to the binary archive to deploy 16 | # props are the properties (configuration) for the core 17 | def deploy number, archive, config_path, repository_base, binaries_base 18 | core_base = File.join(@base, number.to_s); 19 | FileUtils.mkdir_p core_base 20 | 21 | log.info "deploying #{archive} to #{core_base} with config path #{config_path}" 22 | 23 | command = "#{Galaxy::HostUtils.tar} -C #{core_base} -zxf #{archive}" 24 | begin 25 | Galaxy::HostUtils.system command 26 | rescue Galaxy::HostUtils::CommandFailedError => e 27 | raise "Unable to extract archive: #{e.message}" 28 | end 29 | 30 | xndeploy = "#{core_base}/bin/xndeploy" 31 | unless FileTest.executable? xndeploy 32 | xndeploy = "/bin/sh #{xndeploy}" 33 | end 34 | 35 | command = "#{xndeploy} --base #{core_base} --binaries #{binaries_base} --config-path #{config_path} --repository #{repository_base}" 36 | begin 37 | Galaxy::HostUtils.system command 38 | rescue Galaxy::HostUtils::CommandFailedError => e 39 | raise "Deploy script failed: #{e.message}" 40 | end 41 | return core_base 42 | end 43 | 44 | def activate number 45 | core_base = File.join(@base, number.to_s); 46 | current = File.join(@base, "current") 47 | if File.exists? current 48 | File.unlink(current) 49 | end 50 | FileUtils.ln_sf core_base, current 51 | return core_base 52 | end 53 | 54 | def deactivate number 55 | current = File.join(@base, "current") 56 | if File.exists? current 57 | File.unlink(current) 58 | end 59 | end 60 | 61 | def rollback number 62 | current = File.join(@base, "current") 63 | 64 | if File.exists? current 65 | File.unlink(current) 66 | end 67 | 68 | FileUtils.rm_rf File.join(@base, number.to_s) 69 | 70 | core_base = File.join(@base, (number - 1).to_s) 71 | FileUtils.ln_sf core_base, current 72 | 73 | return core_base 74 | end 75 | 76 | def cleanup_up_to_previous current, db 77 | # Keep the current and last one (for rollback) 78 | (1..(current - 2)).each do |number| 79 | key = number.to_s 80 | 81 | # Remove deployed bits 82 | FileUtils.rm_rf File.join(@base, key) 83 | 84 | # Cleanup the database 85 | db.delete_at key 86 | end 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /lib/galaxy/events.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'galaxy/announcements' 3 | 4 | module Galaxy 5 | # Generic Event sender class 6 | class EventSender 7 | COLLECTOR_API_VERSION = 1 8 | 9 | GALAXY_SCHEMA = 'Galaxy' 10 | GALAXY_LOG_SCHEMA = 'GalaxyLog' 11 | DUMMY_TYPE = 'dummy' 12 | 13 | attr_reader :type, :log 14 | 15 | def initialize(listener_url, gonsole_url = nil, ip_addr=nil, log=Logger.new(STDOUT)) 16 | @log = log 17 | @gonsole_url = gonsole_url 18 | @ip_addr = ip_addr 19 | @log.debug "Registered Event listener type #{self.class} at #{listener_url}, sender url #{ip_addr}" 20 | listener_url.nil? ? @uri = nil : @uri = URI.parse(listener_url) 21 | end 22 | 23 | # To override in the child class. event is an OpenStruct 24 | def send_event(event) 25 | # no-op 26 | end 27 | 28 | private 29 | 30 | def escape str 31 | # default is URI::REGEXP::UNSAFE is /[^-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]]/n 32 | # and is not enough! (& and , not escaped) 33 | return URI.escape(str, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")) 34 | end 35 | 36 | # Sanitize strings 37 | def add_field(type, value) 38 | return "#{type}#{escape(value.to_s)}," 39 | end 40 | 41 | def do_send_event(formatted_query) 42 | return if @uri.nil? 43 | http_query = @uri.merge(formatted_query).to_s 44 | @log.debug http_query 45 | begin 46 | # GET 47 | res = Net::HTTP.start(@uri.host, @uri.port) do |http| 48 | headers = {'Content-Type' => 'text/plain; charset=utf-8'} 49 | response = http.send_request('GET', http_query, nil, headers) 50 | @log.debug "Event sent to Collector #{@uri.host} = #{http_query}" if @log 51 | response # Return the response form the block 52 | end 53 | case res 54 | when Net::HTTPAccepted 55 | return true 56 | else 57 | res.error! 58 | end 59 | rescue Exception => e 60 | if @log 61 | @log.warn "Unable to contact EventListener on #{@uri}" 62 | @log.warn "Request: #{http_query}" 63 | @log.warn "Client side error: #{e}" 64 | @log.warn "Body reponse: #{res.body}" if res 65 | end 66 | return false 67 | end 68 | end 69 | end 70 | 71 | # Send logs to HDFS 72 | class GalaxyLogEventSender < EventSender 73 | EVENTS_SUPPORTED = [:debug, :error, :fatal, :info, :warn] 74 | 75 | EVENTS_SUPPORTED.each do |loglevel| 76 | define_method "dispatch_#{loglevel.to_s}_log" do |* args| 77 | message, progname, * ignored = args 78 | send_event(generate_event(loglevel.to_s, message, progname)) 79 | end 80 | end 81 | 82 | private 83 | 84 | # Pre-process logs and generate OpenStruct mapping the GalaxyLog Thrift Schema 85 | # 86 | # struct GalaxyLog { 87 | # 1:i64 date, 88 | # 2:i32 ip_addr, 89 | # 3:i16 pid, 90 | # 4:string severity, 91 | # 5:string progname, 92 | # 6:string message 93 | #} 94 | def generate_event(severity, message, progname) 95 | event = OpenStruct.new 96 | 97 | event.date = Time.now.to_i * 1000 98 | event.gonsole_url = @gonsole_url 99 | event.ip_addr = @ip_addr 100 | event.pid = $$ 101 | event.severity = severity.downcase 102 | event.progname = progname 103 | event.message = message 104 | 105 | return event 106 | end 107 | 108 | def send_event(event) 109 | do_send_event format_url(event) 110 | end 111 | 112 | private 113 | 114 | def format_url(event) 115 | url = "/#{COLLECTOR_API_VERSION}?" 116 | url += "v=#{EventSender::GALAXY_LOG_SCHEMA}," 117 | url += escape(add_field("8", event.date)) 118 | # Make sure to add a valid number (int) or the collector will choke on it (400 bad request) 119 | url += escape(add_field("4", (format_ip(event.ip_addr)))) 120 | url += escape(add_field("2", (event.pid || 0))) 121 | url += escape(add_field("s", event.severity)) 122 | url += escape(add_field("s", event.progname)) 123 | url += escape(add_field("s", event.message)) 124 | url += escape(add_field("s", event.gonsole_url)) 125 | url += "&rt=b" 126 | return url 127 | end 128 | 129 | def format_ip(ip_addr) 130 | if ip_addr.nil? or ip_addr.empty? 131 | return 0 132 | end 133 | addr = 0 134 | ip_addr.split(".").each do |x| 135 | addr = addr * 256 + x.to_i 136 | end 137 | return addr 138 | end 139 | end 140 | 141 | # Send actions related events to HDFS 142 | class GalaxyEventSender < EventSender 143 | EVENTS_SUPPORTED = [:announce, :cleanup, :command, :update_config, :become, :rollback, :start, :stop, :clear, :perform, :restart] 144 | EVENTS_RESULT_SUPPORTED = [:success, :error] 145 | 146 | EVENTS_SUPPORTED.each do |event| 147 | EVENTS_RESULT_SUPPORTED.each do |result| 148 | define_method "dispatch_#{event}_#{result}_event" do |status| 149 | if status.is_a? String 150 | status = OpenStruct.new(:message => status) 151 | elsif status.is_a? Hash 152 | status = OpenStruct.new(status) 153 | end 154 | # e.g. perform, announce 155 | status.event_type = event 156 | # e.g. error, success 157 | status.galaxy_event_type = result 158 | status.gonsole_url = @gonsole_url 159 | send_event(status) 160 | end 161 | end 162 | end 163 | 164 | def send_event(event) 165 | do_send_event format_url(event) 166 | end 167 | 168 | private 169 | 170 | def format_url(event) 171 | url = "/#{COLLECTOR_API_VERSION}?" 172 | url += "v=#{EventSender::GALAXY_SCHEMA}," 173 | #url += add_field "8", event.timestamp 174 | url += escape(add_field("x", "date")) 175 | url += escape(add_field("s", event.event_type)) 176 | url += escape(add_field("s", event.message)) 177 | url += escape(add_field("s", event.agent_status)) 178 | url += escape(add_field("s", event.os)) 179 | url += escape(add_field("s", event.host)) 180 | url += escape(add_field("s", event.galaxy_version)) 181 | url += escape(add_field("s", event.core_type)) 182 | url += escape(add_field("s", event.machine)) 183 | url += escape(add_field("s", event.status)) 184 | url += escape(add_field("s", event.galaxy_event_type)) 185 | url += escape(add_field("s", event.url)) 186 | url += escape(add_field("s", event.config_path)) 187 | url += escape(add_field("s", event.build)) 188 | url += escape(add_field("s", event.ip)) 189 | url += escape(add_field("s", event.user)) 190 | url += escape(add_field("s", event.gonsole_url)) 191 | url += "&rt=b" 192 | return url 193 | end 194 | end 195 | 196 | # When the user doesn't specify a collector URL 197 | class DummyEventSender < EventSender 198 | def initialize() 199 | end 200 | 201 | def method_missing(m, * args, & block) 202 | end 203 | end 204 | end 205 | -------------------------------------------------------------------------------- /lib/galaxy/fetcher.rb: -------------------------------------------------------------------------------- 1 | require 'galaxy/temp' 2 | require 'galaxy/host' 3 | 4 | module Galaxy 5 | class Fetcher 6 | def initialize base_url, http_user, http_password, log 7 | @base, @http_user, @http_password, @log = base_url, http_user, http_password, log 8 | end 9 | 10 | # return path on filesystem to the binary 11 | def fetch type, version, extension="tar.gz" 12 | core_url = "#{@base}/#{type}-#{version}.#{extension}" 13 | tmp = Galaxy::Temp.mk_auto_file "galaxy-download" 14 | @log.info("Fetching #{core_url} into #{tmp}") 15 | if @base =~ /^https?:/ 16 | begin 17 | curl_command = "curl -D - #{core_url} -o #{tmp} -s" 18 | if !@http_user.nil? && !@http_password.nil? 19 | curl_command << " -u #{@http_user}:#{@http_password}" 20 | end 21 | 22 | @log.debug("Running CURL command: #{curl_command}") 23 | output = Galaxy::HostUtils.system(curl_command) 24 | rescue Galaxy::HostUtils::CommandFailedError => e 25 | raise "Failed to download archive #{core_url}: #{e.message}" 26 | end 27 | status = output.first 28 | (protocol, response_code, response_message) = status.split 29 | unless response_code == '200' 30 | raise "Failed to download archive #{core_url}: #{status}" 31 | end 32 | else 33 | open(core_url) do |io| 34 | File.open(tmp, "w") { |f| f.write(io.read) } 35 | end 36 | end 37 | return tmp 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/galaxy/filter.rb: -------------------------------------------------------------------------------- 1 | module Galaxy 2 | module Filter 3 | def self.new args 4 | filters = [] 5 | 6 | case args[:set] 7 | when :all, "all" 8 | filters << lambda { true } 9 | when :empty, "empty" 10 | filters << lambda { |a| a.config_path.nil? } 11 | when :taken, "taken" 12 | filters << lambda { |a| a.config_path } 13 | end 14 | 15 | if args[:env] || args[:version] || args[:type] 16 | env = args[:env] || "[^/]+" 17 | version = args[:version] || "[^/]+" 18 | type = args[:type] || ".+" 19 | 20 | filters << lambda { |a| a.config_path =~ %r!^/#{env}/#{version}/#{type}$! } 21 | end 22 | 23 | if args[:host] 24 | filters << lambda { |a| a.host == args[:host] } 25 | end 26 | 27 | if args[:ip] 28 | filters << lambda { |a| a.ip == args[:ip] } 29 | end 30 | 31 | if args[:machine] 32 | filters << lambda { |a| a.machine == args[:machine] } 33 | end 34 | 35 | if args[:state] 36 | filters << lambda { |a| a.status == args[:state] } 37 | end 38 | 39 | if args[:agent_state] 40 | p args[:agent_state] 41 | filters << lambda { |a| p a.agent_status; a.agent_status == args[:agent_state] } 42 | end 43 | 44 | lambda do |a| 45 | filters.inject(false) { |result, filter| result || filter.call(a) } 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/galaxy/host.rb: -------------------------------------------------------------------------------- 1 | require 'tempfile' 2 | require 'syslog' 3 | require 'logger' 4 | 5 | module Galaxy 6 | module HostUtils 7 | def HostUtils.logger ident="galaxy" 8 | @logger ||= begin 9 | log = Syslog.open ident, Syslog::LOG_PID | Syslog::LOG_CONS, Syslog::LOG_LOCAL7 10 | class << log 11 | attr_reader :level 12 | # The interface is busted between Logger and Syslog. The later expects a format string. The former a string. 13 | # This was breaking logging in the event code in production (we log the url, which contains escaped characters). 14 | # Poor man's solution: assume the message is not a format string if we pass only one argument. 15 | # 16 | alias_method :unsafe_debug, :debug 17 | 18 | def debug * args 19 | args.length == 1 ? unsafe_debug(safe_format(args[0])) : unsafe_debug(* args) 20 | end 21 | 22 | alias_method :unsafe_info, :info 23 | 24 | def info * args 25 | args.length == 1 ? unsafe_info(safe_format(args[0])) : unsafe_info(* args) 26 | end 27 | 28 | def warn * args 29 | args.length == 1 ? warning(safe_format(args[0])) : warning(* args) 30 | end 31 | 32 | def error * args 33 | args.length == 1 ? err(safe_format(args[0])) : err(* args) 34 | end 35 | 36 | # set log levels from standard Logger levels 37 | def level=(val) 38 | @level = val 39 | case val # Note that there are other log levels: LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_NOTICE 40 | when Logger::ERROR 41 | Syslog.mask = Syslog::LOG_UPTO(Syslog::LOG_ERR) 42 | when Logger::WARN 43 | Syslog.mask = Syslog::LOG_UPTO(Syslog::LOG_WARNING) 44 | when Logger::DEBUG 45 | Syslog.mask = Syslog::LOG_UPTO(Syslog::LOG_DEBUG) 46 | when Logger::INFO 47 | Syslog.mask = Syslog::LOG_UPTO(Syslog::LOG_INFO) 48 | end 49 | end 50 | 51 | def safe_format(arg) 52 | return arg.gsub("%", "%%") 53 | end 54 | 55 | # The logger implementation dump msg directly, without appending any loglevel. We need one though for Syslog. 56 | # By default, logger(1) uses ``user.notice''. Do the same here. 57 | def <<(msg) 58 | notice(msg) 59 | end 60 | end 61 | log.level = Logger::INFO 62 | log 63 | end 64 | end 65 | 66 | # Returns the name of the user that invoked the command 67 | # 68 | # This implementation tries +who am i+, available on some unix platforms, to check the owner of the controlling terminal, 69 | # which preserves ownership across +su+ and +sudo+. Failing that, the environment is checked for a +USERNAME+ or +USER+ variable. 70 | # Finally, the system password database is consulted. 71 | def HostUtils.shell_user 72 | guesses = [] 73 | guesses << `who am i 2> /dev/null`.split[0] 74 | guesses << ENV['USERNAME'] 75 | guesses << ENV['USER'] 76 | guesses << Etc.getpwuid(Process.uid).name 77 | guesses.first { |guess| notguess.nil? and notguess.empty? } 78 | end 79 | 80 | def HostUtils.avail_path 81 | @avail_path ||= begin 82 | directories = %w{ /usr/local/var/galaxy /var/galaxy /var/tmp /tmp } 83 | directories.find { |dir| FileTest.writable? dir } 84 | end 85 | end 86 | 87 | def HostUtils.tar 88 | @tar ||= begin 89 | unless `which gtar` =~ /^no gtar/ || `which gtar`.length == 0 90 | "gtar" 91 | else 92 | "tar" 93 | end 94 | end 95 | end 96 | 97 | def HostUtils.switch_user user 98 | pwent = Etc::getpwnam(user) 99 | uid, gid = pwent.uid, pwent.gid 100 | if Process.gid != gid or Process.uid != uid 101 | Process::GID::change_privilege(gid) 102 | Process::initgroups(user, gid) 103 | Process::UID::change_privilege(uid) 104 | end 105 | if Process.gid != gid or Process.uid != uid 106 | abort("Error: unable to switch user to #{user}") 107 | end 108 | end 109 | 110 | class CommandFailedError < Exception 111 | attr_reader :command, :exitstatus, :output 112 | 113 | def initialize command, exitstatus, output 114 | @command = command 115 | @exitstatus = exitstatus 116 | @output = output 117 | end 118 | 119 | def message 120 | "Command '#{@command}' exited with status code #{@exitstatus} and output: #{@output}".chomp() 121 | end 122 | end 123 | 124 | # An alternative to Kernel.system that invokes a command, raising an exception containing 125 | # the command's stdout and stderr if the command returns a status code other than 0 126 | def HostUtils.system command 127 | output = IO.popen("#{command} 2>&1") { |io| io.readlines } 128 | unless $?.success? 129 | raise CommandFailedError.new(command, $?.exitstatus, output) 130 | end 131 | output 132 | end 133 | end 134 | end 135 | -------------------------------------------------------------------------------- /lib/galaxy/log.rb: -------------------------------------------------------------------------------- 1 | require 'galaxy/events' 2 | require 'galaxy/host' 3 | 4 | module Galaxy 5 | module Log 6 | class Glogger 7 | attr_reader :log, :event_dispatcher 8 | 9 | def initialize(logdev, event_listener = nil, gonsole_url = nil, ip_addr = nil, shift_age = 0, shift_size = 1048576) 10 | @gonsole_url = gonsole_url 11 | 12 | case logdev 13 | when "SYSLOG" 14 | @log = Galaxy::HostUtils.logger "galaxy" 15 | when "STDOUT" 16 | @log = Logger.new(STDOUT, shift_age, shift_size) 17 | when "STDERR" 18 | @log = Logger.new(STDERR, shift_age, shift_size) 19 | else 20 | @log = Logger.new(logdev, shift_age, shift_size) 21 | end 22 | 23 | if event_listener.nil? 24 | @event_dispatcher = Galaxy::DummyEventSender.new() 25 | else 26 | @event_dispatcher = Galaxy::GalaxyLogEventSender.new(event_listener, @gonsole_url, ip_addr, @log) 27 | end 28 | end 29 | 30 | def debug(progname = nil, & block) 31 | @event_dispatcher.dispatch_debug_log(format_message(progname, & block)) if @log.level <= Logger::DEBUG 32 | @log.debug progname, & block 33 | end 34 | 35 | def error(progname = nil, & block) 36 | @event_dispatcher.dispatch_error_log(format_message(progname, & block)) if @log.level <= Logger::ERROR 37 | @log.error progname, & block 38 | end 39 | 40 | def fatal(progname = nil, & block) 41 | @event_dispatcher.dispatch_fatal_log(format_message(progname, & block)) if @log.level <= Logger::FATAL 42 | @log.fatal progname, & block 43 | end 44 | 45 | def info(progname = nil, & block) 46 | @event_dispatcher.dispatch_info_log(format_message(progname, & block)) if @log.level <= Logger::INFO 47 | @log.info progname, & block 48 | end 49 | 50 | def warn(progname = nil, & block) 51 | @event_dispatcher.dispatch_warn_log(format_message(progname, & block)) if @log.level <= Logger::WARN 52 | @log.warn progname, & block 53 | end 54 | 55 | # Pipeline other methods to Logger 56 | # We don't want to define << for instance: when we'll end up having a dedicated Thrift 57 | # schema for logs, we do want to know beforehand the expected formatting. 58 | def method_missing(m, * args, & block) 59 | @log.send m, * args, & block 60 | end 61 | 62 | private 63 | 64 | def format_message(progname, & block) 65 | if block_given? 66 | message = yield 67 | else 68 | message = progname 69 | end 70 | message 71 | end 72 | end 73 | 74 | class LoggerIO < IO 75 | require 'strscan' 76 | 77 | def initialize log, level = :info 78 | @log = log 79 | @level = level 80 | @buffer = "" 81 | end 82 | 83 | def write str 84 | @buffer << str 85 | 86 | scanner = StringScanner.new(@buffer) 87 | 88 | while scanner.scan(/([^\n]*)\n/) 89 | line = scanner[1] 90 | case @level 91 | when :warn 92 | @log.warn line 93 | when :info 94 | @log.info line 95 | when :error 96 | @log.error line 97 | end 98 | end 99 | 100 | @buffer = scanner.rest 101 | end 102 | end 103 | end 104 | end 105 | 106 | if __FILE__ == $0 107 | def a 108 | b 109 | end 110 | 111 | def b 112 | raise "error" 113 | end 114 | 115 | require 'logger' 116 | 117 | log = Logger.new(STDERR) 118 | info = Galaxy::Log::LoggerIO.new log, :info 119 | warn = Galaxy::Log::LoggerIO.new log, :error 120 | $stdout = info 121 | $stderr = warn 122 | 123 | puts "hello world\nbye bye" 124 | 125 | a 126 | end 127 | -------------------------------------------------------------------------------- /lib/galaxy/parallelize.rb: -------------------------------------------------------------------------------- 1 | require 'thread' 2 | 3 | class CountingSemaphore 4 | 5 | def initialize(initvalue = 0) 6 | @counter = initvalue 7 | @waiting_list = [] 8 | end 9 | 10 | def wait 11 | Thread.exclusive { 12 | @counter -= 1 13 | if @counter < 0 14 | @waiting_list.push(Thread.current) 15 | Thread.stop 16 | end 17 | } 18 | self 19 | end 20 | 21 | def signal 22 | Thread.exclusive { 23 | begin 24 | @counter += 1 25 | if @counter <= 0 26 | t = @waiting_list.shift 27 | t.wakeup if t 28 | end 29 | rescue ThreadError 30 | retry 31 | end 32 | } 33 | self 34 | ensure 35 | Thread.critical = false 36 | end 37 | 38 | def exclusive 39 | wait 40 | yield 41 | ensure 42 | signal 43 | end 44 | 45 | end 46 | 47 | class ThreadGroup 48 | 49 | def join 50 | list.each { |t| t.join } 51 | end 52 | 53 | def << thread 54 | add thread 55 | end 56 | 57 | def kill 58 | list.each { |t| t.kill } 59 | end 60 | 61 | end 62 | 63 | # execute in parallel with up to thread_count threads at once 64 | class Array 65 | def parallelize(thread_count = 100) 66 | sem = CountingSemaphore.new(thread_count ? thread_count : 100) 67 | results = [] 68 | threads = ThreadGroup.new 69 | lock = Mutex.new 70 | each_with_index do |item, i| 71 | sem.wait 72 | threads << Thread.new do 73 | begin 74 | yield item 75 | ensure 76 | sem.signal 77 | end 78 | end 79 | end 80 | 81 | threads.join 82 | 83 | results 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /lib/galaxy/properties.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'open-uri' 3 | require 'logger' 4 | 5 | module Galaxy 6 | module Properties 7 | 8 | def Properties.parse_props io, props={} 9 | io.each_line do |line| 10 | if line =~ /^(\s)*#/ 11 | # comment, ignore 12 | elsif line =~ /^([^=]+)\s*=(.*)$/ 13 | props[$1.strip] = $2.strip 14 | end 15 | 16 | end 17 | props 18 | end 19 | 20 | class Builder 21 | def initialize base, http_user, http_password, log=Logger.new(STDOUT) 22 | @base = base 23 | @log = log 24 | if !http_user.nil? && !http_password.nil? 25 | @http_auth = {:http_basic_authentication =>[http_user, http_password]} 26 | end 27 | end 28 | 29 | def build hierarchy, file_name 30 | props = {} 31 | 32 | hierarchy.split(/\//).inject([]) do |history, part| 33 | history << part 34 | begin 35 | 36 | url = "#{@base}#{history.join("/")}/#{file_name}" 37 | @log.debug "Fetching #{url}" 38 | 39 | auth = {} 40 | begin 41 | fetch_done = true 42 | open(url, auth) do |io| 43 | Properties.parse_props io, props 44 | end 45 | rescue OpenURI::HTTPError => http_err 46 | if http_err.io.status[0] == "401" && auth.empty? && !@http_auth.nil? 47 | # retry with the auth information 48 | auth = @http_auth 49 | fetch_done = false 50 | else 51 | raise http_err 52 | end 53 | end while !fetch_done 54 | rescue => e 55 | @log.debug e.message 56 | end 57 | history 58 | end 59 | @log.debug props.inspect 60 | props 61 | end 62 | 63 | def replace_tokens properties, tokens 64 | # replace special tokens 65 | # syntax is #{TOKEN} 66 | # (old syntax of $TOKEN is deprecated) 67 | properties.inject({}) do |hash, pair| 68 | key, value = pair 69 | hash[key] = value 70 | tokens.each { |find, replace| hash[key] = hash[key].gsub("$#{find}", replace).gsub("\#{#{find}}", replace) } 71 | hash 72 | end 73 | end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/galaxy/proxy_console.rb: -------------------------------------------------------------------------------- 1 | require 'ostruct' 2 | require 'logger' 3 | require 'galaxy/filter' 4 | require 'galaxy/transport' 5 | 6 | module Galaxy 7 | class ProxyConsole 8 | attr_reader :db 9 | @@max_conn_failures = 3 10 | 11 | def initialize drb_url, console_url, log, log_level, ping_interval 12 | @log = 13 | case log 14 | when "SYSLOG" 15 | Galaxy::HostUtils.logger "galaxy-console" 16 | when "STDOUT" 17 | Logger.new STDOUT 18 | when "STDERR" 19 | Logger.new STDERR 20 | else 21 | Logger.new log 22 | end 23 | @log.level = log_level 24 | @drb_url = drb_url 25 | @ping_interval = ping_interval 26 | @db = {} 27 | @mutex = Mutex.new 28 | @conn_failures = 0 29 | 30 | @console_proxyied_url = console_url 31 | @console_proxied = Galaxy::Transport.locate(console_url) 32 | 33 | Thread.new do 34 | loop do 35 | begin 36 | sleep @ping_interval 37 | synchronize 38 | if @conn_failures > 0 39 | @log.warn "Communication with the master gonsole re-established" 40 | end 41 | # Reset the number of connection failures 42 | @conn_failures = 0 43 | rescue DRb::DRbConnError => e 44 | @conn_failures += 1 45 | @log.warn "Unable to communicate with the master gonsole (#{@conn_failures})" 46 | if @conn_failures >= @@max_conn_failures 47 | @log.error "Number of connection failures reached" 48 | shutdown 49 | exit("Connection Error") 50 | end 51 | retry 52 | rescue Exception => e 53 | @log.warn "Uncaught exception in agent ping thread: #{e}" 54 | @log.warn e.backtrace 55 | abort("Unkown Error") 56 | end 57 | end 58 | end 59 | end 60 | 61 | def synchronize 62 | @mutex.synchronize do 63 | @db = @console_proxied.db 64 | @log.info "Synchronized with master gonsole at #{@console_proxyied_url}" 65 | @log.debug "Got new db: #{@db}" 66 | end 67 | end 68 | 69 | # Remote API 70 | def agents filters = {:set => :all} 71 | filter = Galaxy::Filter.new filters 72 | @mutex.synchronize do 73 | @db.values.select(& filter) 74 | end 75 | end 76 | 77 | # Remote API 78 | def log msg 79 | @log.info msg 80 | end 81 | 82 | def ProxyConsole.start args 83 | host = args[:host] || "localhost" 84 | drb_url = args[:url] || "druby://" + host # DRB transport 85 | drb_url += ":4440" unless drb_url.match ":[0-9]+$" 86 | 87 | console_proxyied_url = args[:console_proxyied_url] || "druby://localhost" 88 | console_proxyied_url += ":4440" unless console_proxyied_url.match ":[0-9]+$" 89 | 90 | console = ProxyConsole.new drb_url, console_proxyied_url, 91 | args[:log] || "STDOUT", 92 | args[:log_level] || Logger::INFO, 93 | args[:ping_interval] || 5 94 | 95 | Galaxy::Transport.publish drb_url, console # DRB transport 96 | console 97 | end 98 | 99 | def shutdown 100 | Galaxy::Transport.unpublish @drb_url 101 | end 102 | 103 | def join 104 | Galaxy::Transport.join @drb_url 105 | end 106 | end 107 | end 108 | -------------------------------------------------------------------------------- /lib/galaxy/report.rb: -------------------------------------------------------------------------------- 1 | module Galaxy 2 | module Client 3 | class Report 4 | def initialize 5 | @buffer = "" 6 | end 7 | 8 | def start 9 | end 10 | 11 | def record_result result 12 | @buffer += sprintf(format_string, * format_result(result)) 13 | end 14 | 15 | def finish 16 | @buffer.length > 0 ? @buffer : nil 17 | end 18 | 19 | private 20 | 21 | def format_string 22 | "%s\n" 23 | end 24 | 25 | def format_result result 26 | [result] 27 | end 28 | end 29 | 30 | class ConsoleStatusReport < Report 31 | private 32 | 33 | def format_string 34 | "%s\t%s\t%s\t%s\t%s\n" 35 | end 36 | 37 | def format_field field 38 | field ? field : '-' 39 | end 40 | 41 | def format_result result 42 | [ 43 | format_field(result.drb_url), 44 | format_field(result.http_url), 45 | format_field(result.host), 46 | format_field(result.env), 47 | format_field(result.ping_interval), 48 | ] 49 | end 50 | end 51 | 52 | class AgentStatusReport < Report 53 | private 54 | 55 | def format_string 56 | STDOUT.tty? ? "%-20s %-8s %-10s\n" : "%s\t%s\t%s\n" 57 | end 58 | 59 | def format_field field 60 | field ? field : '-' 61 | end 62 | 63 | def format_result result 64 | [ 65 | format_field(result.host), 66 | format_field(result.agent_status), 67 | format_field(result.galaxy_version), 68 | ] 69 | end 70 | end 71 | 72 | class SoftwareDeploymentReport < Report 73 | private 74 | 75 | def format_string 76 | STDOUT.tty? ? "%-20s %-45s %-10s %-15s %-20s %-20s %-15s %-8s\n" : "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" 77 | end 78 | 79 | def format_field field 80 | field ? field : '-' 81 | end 82 | 83 | def format_result result 84 | [ 85 | format_field(result.host), 86 | format_field(result.config_path), 87 | format_field(result.status), 88 | format_field(result.build), 89 | format_field(result.core_type), 90 | format_field(result.machine), 91 | format_field(result.ip), 92 | format_field(result.agent_status), 93 | ] 94 | end 95 | end 96 | 97 | class CoreStatusReport < Report 98 | private 99 | 100 | def format_string 101 | STDOUT.tty? ? "%-20s %-45s %-10s %-15s %-20s %-14s\n" : "%s\t%s\t%s\t%s\t%s\t%s\n" 102 | end 103 | 104 | def format_field field 105 | field ? field : '-' 106 | end 107 | 108 | def format_result result 109 | [ 110 | format_field(result.host), 111 | format_field(result.config_path), 112 | format_field(result.status), 113 | format_field(result.build), 114 | format_field(result.core_type), 115 | format_field(result.last_start_time), 116 | ] 117 | end 118 | end 119 | 120 | class LocalSoftwareDeploymentReport < Report 121 | private 122 | 123 | def format_string 124 | STDOUT.tty? ? "%-45s %-10s %-15s %-20s %s\n" : "%s\t%s\t%s\t%s\t%s\n" 125 | end 126 | 127 | def format_field field 128 | field ? field : '-' 129 | end 130 | 131 | def format_result result 132 | [ 133 | format_field(result.config_path), 134 | format_field(result.status), 135 | format_field(result.build), 136 | format_field(result.core_type), 137 | "autostart=#{result.auto_start}", 138 | ] 139 | end 140 | end 141 | 142 | class CommandOutputReport < Report 143 | def initialize 144 | super 145 | @software_deployment_report = SoftwareDeploymentReport.new 146 | end 147 | 148 | def record_result result 149 | @software_deployment_report.record_result(result[0]) 150 | host, output = format_result(result) 151 | output.split("\n").each { |line| @buffer += sprintf(format_string, host, line) } 152 | end 153 | 154 | private 155 | 156 | def format_string 157 | "%-20s %s\n" 158 | end 159 | 160 | def format_result result 161 | status, output = result 162 | return "#{status.host}:", output 163 | end 164 | end 165 | end 166 | end 167 | -------------------------------------------------------------------------------- /lib/galaxy/repository.rb: -------------------------------------------------------------------------------- 1 | require 'open-uri' 2 | require 'logger' 3 | 4 | module Galaxy 5 | class Repository 6 | def initialize base, log=Logger.new(STDOUT) 7 | @base = base 8 | end 9 | 10 | def walk hierarchy, file_name 11 | result = [] 12 | hierarchy.split(/\//).inject([]) do |history, part| 13 | history << part 14 | begin 15 | path = "#{history.join("/")}/#{file_name}" 16 | url = "#{@base}#{path}" 17 | open(url) do |io| 18 | data = io.read 19 | if block_given? 20 | yield path, data 21 | end 22 | result << data 23 | end 24 | rescue 25 | end 26 | history 27 | end 28 | 29 | result 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/galaxy/software.rb: -------------------------------------------------------------------------------- 1 | module Galaxy 2 | class SoftwareExecutable 3 | attr_accessor :type, :version 4 | 5 | def initalize type, version 6 | @type = type 7 | @version = version 8 | end 9 | end 10 | 11 | class SoftwareConfiguration 12 | attr_accessor :environment, :version, :type 13 | 14 | def initialize environment, version, type 15 | @environment = environment 16 | @version = version 17 | @type = type 18 | end 19 | 20 | def config_path 21 | "/#{environment}/#{version}/#{type}" 22 | end 23 | 24 | def self.new_from_config_path config_path 25 | # Using ! as regex delimiter since the config path contains / characters 26 | unless components = %r!^/([^/]+)/([^/]+)/(.*)$!.match(config_path) 27 | raise "Illegal config path '#{config_path}'" 28 | end 29 | environment, version, type = components[1], components[2], components[3] 30 | new environment, version, type 31 | end 32 | end 33 | 34 | class SoftwareDeployment 35 | attr_accessor :executable, :config, :running_state 36 | 37 | def initialize executable, config, running_state 38 | @executable = executable 39 | @config = config 40 | @running_state = running_state 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/galaxy/starter.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'galaxy/host' 3 | require 'logger' 4 | 5 | module Galaxy 6 | class Starter 7 | def initialize log 8 | @log = log 9 | end 10 | 11 | [:start!, :restart!, :stop!, :status].each do |action| 12 | define_method action.to_s do |path| 13 | return "unknown" if path.nil? 14 | launcher_path = xnctl_path(path) 15 | 16 | command = "#{launcher_path} #{action.to_s.chomp('!')}" 17 | @log.debug "Running #{command}" 18 | begin 19 | output = Galaxy::HostUtils.system command 20 | @log.debug "#{command} returned: #{output}" 21 | # Command returned 0, return status of the app 22 | case action 23 | when :start! 24 | when :restart! 25 | return "running" 26 | when :stop! 27 | when :status 28 | return "stopped" 29 | else 30 | return "unknown" 31 | end 32 | rescue Galaxy::HostUtils::CommandFailedError => e 33 | # status is special 34 | if action == :status 35 | if e.exitstatus == 1 36 | return "running" 37 | else 38 | return "unknown" 39 | end 40 | end 41 | 42 | @log.warn "Unable to #{action}: #{e.message}" 43 | raise e 44 | end 45 | end 46 | end 47 | 48 | private 49 | 50 | def xnctl_path path 51 | xnctl = File.join(path, "bin", "launcher") 52 | xnctl = "/bin/sh #{xnctl}" unless FileTest.executable? xnctl 53 | xnctl 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/galaxy/temp.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'thread' 3 | require 'tmpdir' 4 | 5 | module Galaxy 6 | module Temp 7 | Mutex = Mutex.new 8 | 9 | def Temp.auto_delete path 10 | Kernel.at_exit do 11 | begin 12 | FileUtils.rm_r(path) if File.exist? path 13 | rescue => e 14 | puts "Failed to delete #{path}: #{e}" 15 | end 16 | end 17 | path 18 | end 19 | 20 | def Temp.mk_auto_file component="galaxy" 21 | auto_delete mk_file(component) 22 | end 23 | 24 | def Temp.mk_auto_dir component="galaxy" 25 | auto_delete mk_dir(component) 26 | end 27 | 28 | def Temp.mk_file component="galaxy" 29 | return * FileUtils.touch(next_name(component)) 30 | end 31 | 32 | def Temp.mk_dir component="galaxy" 33 | return * FileUtils.mkdir(next_name(component)) 34 | end 35 | 36 | private 37 | 38 | def Temp.next_name component 39 | Mutex.synchronize do 40 | @@id ||= 0 41 | name = ""; 42 | loop do 43 | @@id += 1 44 | name = File.join Dir::tmpdir, "#{component}.#{Process.pid}.#{@@id}" 45 | return name unless File.exists? name 46 | end 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/galaxy/transport.rb: -------------------------------------------------------------------------------- 1 | module Galaxy 2 | class Transport 3 | @@transports = [] 4 | 5 | def self.register transport 6 | @@transports << transport 7 | end 8 | 9 | def self.locate url, log=nil 10 | handler_for(url).locate url, log 11 | end 12 | 13 | def self.publish url, object, log=nil 14 | handler_for(url).publish url, object, log 15 | end 16 | 17 | def self.unpublish url 18 | handler_for(url).unpublish url 19 | end 20 | 21 | def self.handler_for url 22 | @@transports.select { |t| t.can_handle? url }.first or raise "No handler found for #{url}" 23 | end 24 | 25 | def initialize pattern 26 | @pattern = pattern 27 | end 28 | 29 | def can_handle? url 30 | @pattern =~ url 31 | end 32 | 33 | def self.join url 34 | handler_for(url).join url 35 | end 36 | end 37 | 38 | class DRbTransport < Transport 39 | require 'drb' 40 | 41 | def initialize 42 | super(/^druby:.*/) 43 | @servers = {} 44 | end 45 | 46 | def locate url, log=nil 47 | DRbObject.new_with_uri url 48 | end 49 | 50 | def publish url, object, log=nil 51 | @servers[url] = DRb.start_service url, object 52 | end 53 | 54 | def unpublish url 55 | @servers[url].stop_service 56 | @servers[url] = nil 57 | end 58 | 59 | def join url 60 | @servers[url].thread.join 61 | end 62 | end 63 | 64 | class LocalTransport < Transport 65 | def initialize 66 | super(/^local:/) 67 | @servers = {} 68 | end 69 | 70 | def locate url, log=nil 71 | @servers[url] 72 | end 73 | 74 | def publish url, object, log=nil 75 | @servers[url] = object 76 | end 77 | 78 | def unpublish url 79 | @servers[url] = nil 80 | end 81 | 82 | def join url 83 | raise "Not yet implemented" 84 | end 85 | end 86 | 87 | # This http transport isn't used in Galaxy 2.4, which uses http only for anonucements. However, this code shows 88 | # how announcements could be merged via transport. The unit test for this class shows one-direction communication 89 | # (eg, for announcements). To do two way, servers (eg, locate()) would be needed on both sides. 90 | # Note that the console code assumes that the transport initialize blocks, so the calling code (eg console) waits 91 | # for an explicit 'join'. But the Announcer class used here starts a server without blocking and returns immediately. 92 | # Therefore, explicit join is not necessary. So to use, make the console work like the agent: track the main polling 93 | # thread started in initialize() and kill/join when done. 94 | # 95 | class HttpTransport < Transport 96 | require 'galaxy/announcements' 97 | 98 | def initialize 99 | super(/^http:.*/) 100 | @servers = {} 101 | @log = nil 102 | end 103 | 104 | # get object (ie announce fn) 105 | # - install announce() callback 106 | def locate url, log=nil 107 | #DRbObject.new_with_uri url 108 | HTTPAnnouncementSender.new url, log 109 | end 110 | 111 | # make object available (ie console) 112 | def publish url, obj, log=nil 113 | if !obj.respond_to?('process_get') || !obj.respond_to?('process_post') 114 | raise TypeError.new("#{obj.class.name} doesn't contain 'process_post' and 'process_get' methods") 115 | end 116 | return @servers[url] if @servers[url] 117 | begin 118 | @servers[url] = Galaxy::HTTPServer.new(url, obj) 119 | rescue NameError 120 | raise NameError.new("Unable to create the http server. Is mongrel installed?") 121 | end 122 | return @servers[url] 123 | end 124 | 125 | def unpublish url 126 | @servers[url].shutdown 127 | @servers[url] = nil 128 | end 129 | 130 | def join url 131 | #nop 132 | end 133 | end 134 | end 135 | 136 | Galaxy::Transport.register Galaxy::DRbTransport.new 137 | Galaxy::Transport.register Galaxy::LocalTransport.new 138 | Galaxy::Transport.register Galaxy::HttpTransport.new 139 | 140 | # Disable DRb persistent connections (monkey patch) 141 | module DRb 142 | class DRbConn 143 | remove_const :POOL_SIZE 144 | POOL_SIZE = 0 145 | end 146 | end 147 | -------------------------------------------------------------------------------- /lib/galaxy/version.rb: -------------------------------------------------------------------------------- 1 | module Galaxy 2 | # Don't forget to also update the version in build/sun/pkginfo 3 | Version = "3.0.0" 4 | end 5 | -------------------------------------------------------------------------------- /lib/galaxy/versioning.rb: -------------------------------------------------------------------------------- 1 | module Galaxy 2 | module Versioning 3 | class StrictVersioningPolicy 4 | def self.assignment_allowed? current_config, requested_config 5 | if current_config.environment == requested_config.environment and current_config.type == requested_config.type 6 | return current_config.version != requested_config.version 7 | end 8 | true 9 | end 10 | end 11 | 12 | class RelaxedVersioningPolicy 13 | def self.assignment_allowed? current_config, requested_config 14 | true 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | com.ning 6 | galaxy 7 | pom 8 | 3.0.0-SNAPSHOT 9 | galaxy 10 | Galaxy, a lightweight software deployment and management tool 11 | http://github.com/ning/galaxy 12 | 13 | 14 | Apache License 2.0 15 | http://www.apache.org/licenses/LICENSE-2.0.html 16 | repo 17 | 18 | 19 | 20 | scm:git:git://github.com/ning/galaxy.git 21 | scm:git:git://github.com/ning/galaxy.git 22 | http://github.com/ning/galaxy/tree/master 23 | 24 | 25 | UTF-8 26 | 27 | 28 | 29 | 30 | org.apache.maven.plugins 31 | maven-assembly-plugin 32 | 33 | 34 | Create Galaxy distribution 35 | 36 | single 37 | 38 | package 39 | 40 | 41 | assembly.xml 42 | 43 | 44 | 45 | 46 | 47 | 48 | org.apache.maven.plugins 49 | maven-release-plugin 50 | 2.0-beta-9 51 | 52 | forked-path 53 | 54 | 55 | 56 | 57 | 58 | 59 | pierre 60 | Pierre-Alexandre Meyer 61 | pierre@mouraf.org 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /test/bad_core_package/bin/control: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'optparse' 4 | 5 | rest = OptionParser.new do |opts| 6 | opts.on("--base BASE") { |arg| @core_base = arg } 7 | opts.on("--config-path PATH") { |arg| @config_path = arg } 8 | opts.on("--repository URL") { |arg| @repository_base = arg } 9 | end.parse! ARGV 10 | 11 | command = rest.shift 12 | 13 | case command 14 | when 'test-success' 15 | exit! 0 16 | when 'test-failure' 17 | exit! 1 18 | when 'test-arguments' 19 | if (@core_base and 20 | File.join(@core_base, 'bin', 'control') == File.expand_path(__FILE__) and 21 | @config_path == '/test/config/path' and 22 | @repository_base == 'http://repository/base') 23 | exit! 0 24 | else 25 | exit! 1 26 | end 27 | else 28 | exit! 2 29 | end 30 | -------------------------------------------------------------------------------- /test/bad_core_package/bin/launcher: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'fileutils' 4 | 5 | File.open("/tmp/galaxy_test_#{Process.ppid}", "w") do |f| 6 | f.puts ARGV[0] 7 | end -------------------------------------------------------------------------------- /test/bad_core_package/bin/xndeploy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'optparse' 4 | require 'fileutils' 5 | 6 | raise "obviously a major malfunction" 7 | -------------------------------------------------------------------------------- /test/bad_core_package/stuff: -------------------------------------------------------------------------------- 1 | This is some stuff 2 | -------------------------------------------------------------------------------- /test/core_package/bin/control: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'optparse' 4 | 5 | rest = OptionParser.new do |opts| 6 | opts.on("--base BASE") { |arg| @core_base = arg } 7 | opts.on("--binaries BINARIES") { |arg| @binaries_base = arg } 8 | opts.on("--config-path PATH") { |arg| @config_path = arg } 9 | opts.on("--repository URL") { |arg| @repository_base = arg } 10 | end.parse! ARGV 11 | 12 | command = rest.shift 13 | 14 | case command 15 | when 'test-success' 16 | puts "gorple" 17 | exit 0 18 | when 'test-failure' 19 | STDERR.puts "fmep" 20 | exit 1 21 | when 'test-multiline' 22 | puts <<-EOM 23 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sit amet arcu a risus pulvinar facilisis. 24 | Proin sed sapien nec magna mattis blandit. Phasellus porta hendrerit eros. Vestibulum ante ipsum primis in 25 | faucibus orci luctus et ultrices posuere cubilia Curae; Integer consequat, ante vitae tempus consequat, nisi 26 | purus facilisis orci, et euismod ligula purus quis magna. Vestibulum diam ante, vestibulum non, adipiscing mollis, 27 | eleifend sed, neque. Cras magna. Fusce non felis et libero posuere facilisis. Cras porttitor tempor orci. 28 | Suspendisse placerat, tortor vel vehicula tempor, felis lorem tincidunt enim, eget cursus lorem tellus non mi. 29 | Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum vitae 30 | risus. Praesent rutrum lectus quis dolor. Aliquam arcu. Sed vulputate mauris. 31 | EOM 32 | exit 0 33 | when 'test-arguments' 34 | if (@core_base and 35 | File.join(@core_base, 'bin', 'control') == File.expand_path(__FILE__) and 36 | @config_path == '/config/path' and 37 | @repository_base == 'http://repository/base' and 38 | @binaries_base == 'http://binaries/base') 39 | exit 0 40 | else 41 | exit 1 42 | end 43 | else 44 | exit 2 45 | end 46 | -------------------------------------------------------------------------------- /test/core_package/bin/launcher: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'fileutils' 4 | 5 | File.open("/tmp/galaxy_test_#{Process.ppid}", "w") do |f| 6 | f.puts ARGV[0] 7 | end -------------------------------------------------------------------------------- /test/core_package/bin/xndeploy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'optparse' 4 | require 'fileutils' 5 | 6 | OptionParser.new do |opts| 7 | opts.on("--base BASE") { |arg| DeployBase = arg } 8 | opts.on("--binaries BINARIES") { |arg| BinariesBase = arg } 9 | opts.on("--config-path PATH") { |arg| ConfigPath = arg } 10 | opts.on("--repository URL") { |arg| Repository = arg } 11 | end.parse! ARGV 12 | 13 | # for test_xndeploy_invoked_on_deploy 14 | begin 15 | dump = { 16 | :deploy_base => DeployBase, 17 | :config_path => ConfigPath, 18 | :repository => Repository, 19 | :binaries_base => BinariesBase, 20 | } 21 | File.open(File.join(DeployBase, "xndeploy_touched_me"), "w") do |file| 22 | Marshal.dump(dump, file) 23 | end 24 | rescue TypeError 25 | end 26 | -------------------------------------------------------------------------------- /test/core_package/stuff: -------------------------------------------------------------------------------- 1 | This is some stuff 2 | -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | require 'tempfile' 2 | require 'fileutils' 3 | 4 | require 'galaxy/filter' 5 | require 'galaxy/temp' 6 | require 'galaxy/transport' 7 | require 'galaxy/version' 8 | require 'galaxy/versioning' 9 | 10 | module Helper 11 | 12 | def Helper.mk_tmpdir 13 | Galaxy::Temp.mk_auto_dir "testing" 14 | end 15 | 16 | class Mock 17 | 18 | def initialize listeners={} 19 | @listeners = listeners 20 | end 21 | 22 | def method_missing sym, *args 23 | f = @listeners[sym] 24 | if f 25 | f.call(*args) 26 | end 27 | end 28 | end 29 | 30 | end 31 | 32 | class MockConsole 33 | def initialize agents 34 | @agents = agents 35 | end 36 | 37 | def shutdown 38 | end 39 | 40 | def agents filters = { :set => :all } 41 | filter = Galaxy::Filter.new filters 42 | @agents.select(&filter) 43 | end 44 | end 45 | 46 | class MockAgent 47 | attr_reader :host, :config_path, :stopped, :started, :restarted 48 | attr_reader :gonsole_url, :env, :version, :type, :url, :agent_status, :proxy, :build, :core_type, :machine, :ip 49 | 50 | def initialize host, env = nil, version = nil, type = nil, gonsole_url=nil 51 | @host = host 52 | @env = env 53 | @version = version 54 | @type = type 55 | @gonsole_url = gonsole_url 56 | @stopped = @started = @restarted = false 57 | 58 | @url = "local://#{host}" 59 | Galaxy::Transport.publish @url, self 60 | 61 | @config_path = nil 62 | @config_path = "/#{env}/#{version}/#{type}" unless env.nil? || version.nil? || type.nil? 63 | @agent_status = 'online' 64 | @status = 'online' 65 | @proxy = Galaxy::Transport.locate(@url) 66 | @build = "1.2.3" 67 | @core_type = 'test' 68 | 69 | @ip = nil 70 | @drb_url = nil 71 | @os = nil 72 | @machine = nil 73 | end 74 | 75 | def shutdown 76 | Galaxy::Transport.unpublish @url 77 | end 78 | 79 | def status 80 | OpenStruct.new( 81 | :host => @host, 82 | :ip => @ip, 83 | :url => @drb_url, 84 | :os => @os, 85 | :machine => @machine, 86 | :core_type => @core_type, 87 | :config_path => @config_path, 88 | :build => @build, 89 | :status => @status, 90 | :agent_status => 'online', 91 | :galaxy_version => Galaxy::Version 92 | ) 93 | end 94 | 95 | def stop! 96 | @stopped = true 97 | status 98 | end 99 | 100 | def start! 101 | @started = true 102 | status 103 | end 104 | 105 | def restart! 106 | @restarted = true 107 | status 108 | end 109 | 110 | def become! path, versioning_policy = Galaxy::Versioning::StrictVersioningPolicy 111 | md = %r!^/([^/]+)/([^/]+)/(.*)$!.match path 112 | new_env, new_version, new_type = md[1], md[2], md[3] 113 | # XXX We don't test the versioning code - but it should go away soon 114 | #raise if @version == new_version 115 | @env = new_env 116 | @version = new_version 117 | @type = new_type 118 | @config_path = "/#{@env}/#{@version}/#{@type}" 119 | status 120 | end 121 | 122 | def update_config! new_version, versioning_policy = Galaxy::Versioning::StrictVersioningPolicy 123 | # XXX We don't test the versioning code - but it should go away soon 124 | #raise if @version == new_version 125 | @version = new_version 126 | @config_path = "/#{@env}/#{@version}/#{@type}" 127 | status 128 | end 129 | 130 | def check_credentials!(command, credentials) 131 | true 132 | end 133 | 134 | def inspect 135 | Galaxy::Client::SoftwareDeploymentReport.new.record_result(self) 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /test/performance/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
43 | 44 |
45 | 46 | 47 | 48 |
49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
64 | -------------------------------------------------------------------------------- /test/performance/lib/commons-logging-1.1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pierre/galaxy/0113bd79785b139db3565e416340e3ccbe22a3ae/test/performance/lib/commons-logging-1.1.1.jar -------------------------------------------------------------------------------- /test/performance/lib/httpclient-4.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pierre/galaxy/0113bd79785b139db3565e416340e3ccbe22a3ae/test/performance/lib/httpclient-4.0.1.jar -------------------------------------------------------------------------------- /test/performance/lib/httpcore-4.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pierre/galaxy/0113bd79785b139db3565e416340e3ccbe22a3ae/test/performance/lib/httpcore-4.0.1.jar -------------------------------------------------------------------------------- /test/performance/lib/httpmime-4.0.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pierre/galaxy/0113bd79785b139db3565e416340e3ccbe22a3ae/test/performance/lib/httpmime-4.0.1.jar -------------------------------------------------------------------------------- /test/performance/lib/one-jar-ant-task-0.96.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pierre/galaxy/0113bd79785b139db3565e416340e3ccbe22a3ae/test/performance/lib/one-jar-ant-task-0.96.jar -------------------------------------------------------------------------------- /test/performance/src/LoadTest.java: -------------------------------------------------------------------------------- 1 | import java.text.MessageFormat; 2 | import java.util.concurrent.BlockingQueue; 3 | import java.util.concurrent.LinkedBlockingQueue; 4 | 5 | import org.apache.http.HttpVersion; 6 | import org.apache.http.client.HttpClient; 7 | import org.apache.http.client.methods.HttpPost; 8 | import org.apache.http.entity.StringEntity; 9 | import org.apache.http.impl.client.DefaultHttpClient; 10 | import org.apache.http.params.BasicHttpParams; 11 | import org.apache.http.params.HttpConnectionParams; 12 | import org.apache.http.params.HttpParams; 13 | import org.apache.http.params.HttpProtocolParams; 14 | 15 | public class LoadTest { 16 | static BlockingQueue queue = new LinkedBlockingQueue(); 17 | 18 | static Thread createWorker(int workerId, final int agentId, final int totalRequests, final String url) { 19 | final MessageFormat messageFormat = new MessageFormat( 20 | "--- !ruby/object:OpenStruct\ntable:\n " 21 | + ":agent_status: online\n " + ":config_path: a/b/c\n " 22 | + ":host: z{0}.company.com\n " 23 | + ":url: druby://z{1}.company.com:4441\n " 24 | + ":core_type: benchmark\n " + ":eventType: galaxy\n " 25 | + ":machine: m{2}.company.com\n " + ":galaxy_version: 2.6.4\n " 26 | + ":os: linux\n " + ":galaxy_event_type: success"); 27 | 28 | final String name = "Worker " + workerId; 29 | Thread thread = new Thread(name) { 30 | @Override 31 | public void run() { 32 | boolean success; 33 | for (int i = 0, j; i < totalRequests; i++) { 34 | success = false; 35 | try { 36 | // System.err.println(this.getName() + " : #" + (i + 1)); 37 | System.out.print("."); 38 | HttpParams httpParams = new BasicHttpParams(); 39 | HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1); 40 | HttpConnectionParams.setSoTimeout(httpParams, new Integer(30000)); 41 | HttpConnectionParams.setConnectionTimeout(httpParams, new Integer(30000)); 42 | HttpClient httpClient = new DefaultHttpClient(httpParams); 43 | HttpPost httpPost = new HttpPost(url); 44 | j = agentId + i; 45 | Object[] input = new Object[] { j , j, j }; 46 | StringEntity entity = new StringEntity(messageFormat.format(input)); 47 | httpPost.setEntity(entity); 48 | httpClient.execute(httpPost); 49 | httpClient.getConnectionManager().shutdown(); 50 | success = true; 51 | } catch (Exception e) { 52 | // System.err.println(e.getLocalizedMessage()); 53 | } 54 | finally { 55 | if (!success) { 56 | String msg = name + " #" + (i + 1); 57 | // System.err.println("retrying worker " + msg); 58 | try { 59 | queue.put(msg); 60 | } catch (InterruptedException e) { 61 | e.printStackTrace(); 62 | } 63 | } 64 | } 65 | } 66 | } 67 | }; 68 | return thread; 69 | } 70 | 71 | public static void main(String[] args) throws Exception { 72 | int agents = 1400; 73 | int concurrentRequests = 70; 74 | String gonsoleUrl = "http://localhost:4442"; 75 | if (args.length == 1 && args[0].endsWith("-h")) { 76 | System.out.println("java -jar galaxy-loader.jar -g -a <# of agents> -c <# concurrent requests>"); 77 | System.exit(0); 78 | } 79 | for (int i = 1; i < args.length; i += 2) { 80 | if("-g".equals(args[i - 1].trim())) { 81 | gonsoleUrl = args[i].trim(); 82 | } 83 | else if("-a".equals(args[i - 1].trim())) { 84 | agents = Integer.parseInt(args[i].trim()); 85 | } 86 | else if("-c".equals(args[i - 1].trim())) { 87 | concurrentRequests = Integer.parseInt(args[i].trim()); 88 | } 89 | } 90 | 91 | System.out.println("gonsole = " + gonsoleUrl + ", agents = " + agents + ", concurrent requests = " + concurrentRequests); 92 | int totalWorkers = concurrentRequests; 93 | int totalRequests = (int) Math.round(agents / (double) concurrentRequests); 94 | Thread[] workers = new Thread[totalWorkers]; 95 | for (int i = 0, j; i < totalWorkers; i++) { 96 | j = (i + 1) * totalRequests; 97 | workers[i] = createWorker((i + 1), j, totalRequests, gonsoleUrl); 98 | } 99 | 100 | long time = System.currentTimeMillis(); 101 | for (Thread thread : workers) { 102 | thread.start(); 103 | } 104 | 105 | for (Thread thread : workers) { 106 | try { 107 | thread.join(); 108 | } catch (InterruptedException e) { 109 | } 110 | } 111 | time = System.currentTimeMillis() - time; 112 | // for (String msg : queue) { 113 | // System.out.println(msg); 114 | // } 115 | System.out.println("\ntotal failures: " + queue.size()); 116 | System.out.println("total run time: " + (time / (1000)) + " sec"); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /test/property_data/a/b/c/d/test_override.properties: -------------------------------------------------------------------------------- 1 | oscar = purple 2 | -------------------------------------------------------------------------------- /test/property_data/a/b/c/test_simple.properties: -------------------------------------------------------------------------------- 1 | chris = green 2 | -------------------------------------------------------------------------------- /test/property_data/a/b/test_override.properties: -------------------------------------------------------------------------------- 1 | oscar = blue 2 | sam = red -------------------------------------------------------------------------------- /test/property_data/a/b/xncore.properties: -------------------------------------------------------------------------------- 1 | oscar = green 2 | core = hello 3 | -------------------------------------------------------------------------------- /test/property_data/a/build.properties: -------------------------------------------------------------------------------- 1 | type=test 2 | build=1.0-12345 3 | -------------------------------------------------------------------------------- /test/property_data/a/test_comments_ignored.properties: -------------------------------------------------------------------------------- 1 | #hello = 7 2 | red = fuschia -------------------------------------------------------------------------------- /test/property_data/foo-bar.properties: -------------------------------------------------------------------------------- 1 | helo=g'bye -------------------------------------------------------------------------------- /test/test_agent.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.join(File.dirname(__FILE__), "..", "lib") 2 | $:.unshift File.join(File.dirname(__FILE__)) 3 | 4 | require 'test/unit' 5 | require 'galaxy/transport' 6 | require 'galaxy/agent' 7 | require 'galaxy/host' 8 | require 'webrick' 9 | require 'thread' 10 | require 'timeout' 11 | require 'helper' 12 | require 'fileutils' 13 | require 'logger' 14 | 15 | class TestAgent < Test::Unit::TestCase 16 | 17 | def setup 18 | @tempdir = Helper.mk_tmpdir 19 | 20 | @data_dir = File.join(@tempdir, 'data') 21 | @deploy_dir = File.join(@tempdir, 'deploy') 22 | @binaries_base = File.join(@tempdir, 'binaries') 23 | 24 | FileUtils.mkdir_p @data_dir 25 | FileUtils.mkdir_p @deploy_dir 26 | FileUtils.mkdir_p @binaries_base 27 | 28 | system "#{Galaxy::HostUtils.tar} -C #{File.join(File.dirname(__FILE__), "core_package")} -czf #{@binaries_base}/test-1.0-12345.tar.gz ." 29 | 30 | webrick_logger = Logger.new(STDOUT) 31 | webrick_logger.level = Logger::WARN 32 | @server = WEBrick::HTTPServer.new(:Port => 8000, :Logger => webrick_logger) 33 | 34 | # Replies on POST from agent 35 | @server.mount_proc("/") do |request, response| 36 | status, content_type, body = 200, "text/plain", "pong" 37 | response.status = status 38 | response['Content-Type'] = content_type 39 | response.body = body 40 | end 41 | @server.mount("/config", WEBrick::HTTPServlet::FileHandler, File.join(File.dirname(__FILE__), "property_data"), true) 42 | @server.mount("/binaries", WEBrick::HTTPServlet::FileHandler, @binaries_base, true) 43 | 44 | Thread.start do 45 | @server.start 46 | end 47 | 48 | # Note: force 127.0.0.1 not to rely on `hostname` and localhost 49 | @agent = Galaxy::Agent.start({:repository => File.dirname(__FILE__) + "/property_data", 50 | :binaries => @binaries_base, 51 | :data_dir => @data_dir, 52 | :deploy_dir => @deploy_dir, 53 | :log_level => Logger::WARN, 54 | :host => "druby://127.0.0.1:4441", 55 | :console => "http://127.0.0.1:8000" 56 | }) 57 | end 58 | 59 | def teardown 60 | @agent.shutdown 61 | @server.shutdown 62 | FileUtils.rm_rf @tempdir 63 | end 64 | 65 | def test_agent_assign 66 | @agent.become! '/a/b/c' 67 | assert File.exist?(File.join(@deploy_dir, 'current', 'bin')) 68 | end 69 | 70 | def test_agent_perform 71 | @agent.become! '/a/b/c' 72 | assert_nothing_raised do 73 | @agent.perform! 'test-success' 74 | end 75 | end 76 | 77 | def test_agent_perform_failure 78 | @agent.become! '/a/b/c' 79 | assert_raise RuntimeError do 80 | @agent.logger.log.level = Logger::FATAL 81 | # The failure will spit a stacktrace in the log (ERROR) 82 | @agent.perform! 'test-failure' 83 | @agent.logger.log.level = Logger::WARN 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /test/test_announcements.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.join(File.dirname(__FILE__), "..", "lib") 2 | $:.unshift File.join(File.dirname(__FILE__)) 3 | 4 | require 'test/unit' 5 | require 'galaxy/announcements' 6 | 7 | class TestAnnouncements < Test::Unit::TestCase 8 | 9 | # example callback for action upon receiving an announcement 10 | def on_announcement(ann) 11 | assert "bar" == ann.foo # these are not Test::Unit asserts, but $received won't be set if any are false 12 | assert ann.rand >= 0 13 | assert ann.rand < 10 14 | assert "eggs" == ann.item 15 | @@received = true 16 | end 17 | 18 | def test_server 19 | # url = "http://localhost:8000" # 4442 for announcements in production, but can be anything for test 20 | # # server 21 | # Galaxy::HTTPAnnouncementReceiver.new(url, lambda{|a| on_announcement(a)}) 22 | # 23 | # # sender 24 | # announcer = HTTPAnnouncementSender.new(url) 25 | # @@received = false 26 | # announcer.announce(OpenStruct.new(:foo=>"bar", :rand => rand(10), :item => "eggs")) 27 | # assert_equal true, @@received 28 | end 29 | end -------------------------------------------------------------------------------- /test/test_client.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.join(File.dirname(__FILE__), "..", "lib") 2 | $:.unshift File.join(File.dirname(__FILE__)) 3 | 4 | require 'test/unit' 5 | require 'galaxy/agent' 6 | require 'galaxy/command' 7 | require 'galaxy/console' 8 | require 'logger' 9 | 10 | class TestClient < Test::Unit::TestCase 11 | 12 | ENV.delete 'GALAXY_CONSOLE' 13 | GALAXY = "ruby -Ilib bin/galaxy" 14 | 15 | Galaxy::Commands.each do |command| 16 | define_method "test_#{command}_usage" do 17 | output = `#{GALAXY} -h #{command} 2>&1` 18 | assert_match("Usage for '#{command}':", output) 19 | end 20 | end 21 | 22 | def test_usage_with_no_arguments 23 | output = `#{GALAXY} 2>&1` 24 | assert_match("Error: Missing command", output) 25 | end 26 | 27 | def test_show_with_no_console 28 | output = `#{GALAXY} show 2>&1` 29 | assert_match("Error: Cannot determine console host", output) 30 | end 31 | 32 | def test_show_console 33 | console = Galaxy::Console.start({ :host => 'localhost' }) 34 | output = `#{GALAXY} show-console -c localhost 2>&1` 35 | assert_match("druby://localhost:4440\thttp://localhost:4442\tlocalhost\t-\t5\n", output) 36 | console.shutdown 37 | end 38 | 39 | def test_show_with_console_from_environment 40 | # console = Galaxy::Console.start({ :host => 'localhost' }) 41 | # output = `GALAXY_CONSOLE=localhost #{GALAXY} show 2>&1` 42 | # assert_match("No agents matching the provided filter(s) were available for show", output) 43 | # console.shutdown 44 | end 45 | 46 | def test_show_with_console_from_command_line 47 | # console = Galaxy::Console.start({ :host => 'localhost' }) 48 | # output = `#{GALAXY} -c localhost show 2>&1` 49 | # assert_match("No agents matching the provided filter(s) were available for show", output) 50 | # console.shutdown 51 | end 52 | 53 | def test_show_with_bad_console 54 | output = `#{GALAXY} -c non-existent-host show 2>&1` 55 | # On Linux, this will be: 56 | # Error: druby://non-existent-host:4440 - #" 57 | #assert_match("Error: druby://non-existent-host:4440 - # 'localhost', :log_level => Logger::WARN }) 63 | agent = Galaxy::Agent.start({ :host => 'localhost', :console => 'localhost', :log_level => Logger::WARN }) 64 | output = `#{GALAXY} -c localhost show 2>&1`.split("\n") 65 | assert_equal(1, output.length) 66 | assert_match("No agents matching the provided filter(s) were available for show", output[0]) 67 | agent.shutdown 68 | console.shutdown 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /test/test_commands.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.join(File.dirname(__FILE__), "..", "lib") 2 | $:.unshift File.join(File.dirname(__FILE__)) 3 | 4 | require 'test/unit' 5 | require 'galaxy/command' 6 | require 'galaxy/transport' 7 | require 'helper' 8 | 9 | class TestCommands < Test::Unit::TestCase 10 | def setup 11 | @agents = [ 12 | MockAgent.new("agent1", "alpha", "1.0", "sysc"), 13 | MockAgent.new("agent2", "alpha", "1.0", "idtc"), 14 | MockAgent.new("agent3", "alpha", "1.0", "appc/aclu0"), 15 | MockAgent.new("agent4"), 16 | MockAgent.new("agent5", "alpha", "2.0", "sysc"), 17 | MockAgent.new("agent6", "beta", "1.0", "sysc"), 18 | MockAgent.new("agent7") 19 | ] 20 | 21 | @console = MockConsole.new(@agents) 22 | end 23 | 24 | def teardown 25 | @agents.each { |a| a.shutdown } 26 | @console.shutdown 27 | end 28 | 29 | def test_all_registered 30 | assert Galaxy::Commands["assign"] 31 | assert Galaxy::Commands["clear"] 32 | assert Galaxy::Commands["reap"] 33 | assert Galaxy::Commands["restart"] 34 | assert Galaxy::Commands["rollback"] 35 | assert Galaxy::Commands["show"] 36 | assert Galaxy::Commands["ssh"] 37 | assert Galaxy::Commands["start"] 38 | assert Galaxy::Commands["stop"] 39 | assert Galaxy::Commands["update"] 40 | assert Galaxy::Commands["update-config"] 41 | end 42 | 43 | def internal_test_all_for cmd 44 | command = Galaxy::Commands[cmd].new [], {:console => @console} 45 | agents = command.select_agents(:set => :all) 46 | command.execute agents 47 | 48 | @agents.select { |a| a.config_path }.each { |a| assert_equal true, yield(a) } 49 | @agents.select { |a| a.config_path.nil? }.each { |a| assert_equal false, yield(a) } 50 | end 51 | 52 | def internal_test_by_host cmd 53 | command = Galaxy::Commands[cmd].new [], {:console => @console} 54 | agents = command.select_agents(:host => "agent1") 55 | command.execute agents 56 | 57 | @agents.select {|a| a.host == "agent1" }.each { |a| assert_equal true, yield(a) } 58 | @agents.select {|a| a.host != "agent1" }.each { |a| assert_equal false, yield(a) } 59 | end 60 | 61 | def internal_test_by_type cmd 62 | command = Galaxy::Commands[cmd].new [], {:console => @console} 63 | agents = command.select_agents(:type => "sysc") 64 | command.execute agents 65 | 66 | @agents.select {|a| a.type == "sysc" }.each { |a| assert_equal true, yield(a) } 67 | @agents.select {|a| a.type != "sysc" }.each { |a| assert_equal false, yield(a) } 68 | end 69 | 70 | def test_stop_all 71 | internal_test_all_for("stop") { |a| a.stopped } 72 | end 73 | 74 | def test_start_all 75 | internal_test_all_for("start") { |a| a.started } 76 | end 77 | 78 | def test_restart_all 79 | internal_test_all_for("restart") { |a| a.restarted } 80 | end 81 | 82 | def test_stop_by_host 83 | internal_test_by_host("stop") { |a| a.stopped } 84 | end 85 | 86 | def test_start_by_host 87 | internal_test_by_host("start") { |a| a.started } 88 | end 89 | 90 | def test_restart_by_host 91 | internal_test_by_host("restart") { |a| a.restarted } 92 | end 93 | 94 | def test_stop_by_type 95 | internal_test_by_type("stop") { |a| a.stopped } 96 | end 97 | 98 | def test_start_by_type 99 | internal_test_by_type("start") { |a| a.started } 100 | end 101 | 102 | def test_restart_by_type 103 | internal_test_by_type("restart") { |a| a.restarted } 104 | end 105 | 106 | def test_show_all 107 | command = Galaxy::Commands["show"].new [], {:console => @console} 108 | agents = command.select_agents(:set => :all) 109 | results = command.execute agents 110 | 111 | assert_equal format_agents, results 112 | end 113 | 114 | def test_show_by_env 115 | command = Galaxy::Commands["show"].new [], {:console => @console} 116 | agents = command.select_agents(:env => "alpha") 117 | results = command.execute agents 118 | 119 | assert_equal format_agents(@agents.select {|a| a.env == "alpha"}), results 120 | end 121 | 122 | def test_show_by_version 123 | command = Galaxy::Commands["show"].new [], {:console => @console, :version => "1.0"} 124 | agents = command.select_agents(:version => "1.0") 125 | results = command.execute agents 126 | 127 | assert_equal format_agents(@agents.select {|a| a.version == "1.0"}), results 128 | end 129 | 130 | def test_show_by_type 131 | command = Galaxy::Commands["show"].new [], {:console => @console} 132 | agents = command.select_agents(:type => :sysc) 133 | results = command.execute agents 134 | 135 | assert_equal format_agents(@agents.select {|a| a.type == "sysc"}), results 136 | end 137 | 138 | def test_show_by_type2 139 | command = Galaxy::Commands["show"].new [], {:console => @console} 140 | agents = command.select_agents(:type => "appc/aclu0") 141 | results = command.execute agents 142 | 143 | assert_equal format_agents(@agents.select {|a| a.type == "appc/aclu0"}), results 144 | end 145 | 146 | def test_show_by_env_version_type 147 | command = Galaxy::Commands["show"].new [], {:console => @console} 148 | agents = command.select_agents({:type => "sysc", :env => "alpha", :version => "1.0"}) 149 | results = command.execute agents 150 | 151 | assert_equal format_agents(@agents.select {|a| a.type == "sysc" && a.env == "alpha" && a.version == "1.0"}), results 152 | end 153 | 154 | def test_assign_empty 155 | command = Galaxy::Commands["assign"].new ["beta", "3.0", "rslv"], {:console => @console, :set => :empty} 156 | agents = command.select_agents(:set => :all) 157 | agent = @agents.select { |a| a.config_path.nil? }.first 158 | command.execute agents 159 | assert_equal "beta", agent.env 160 | assert_equal "rslv", agent.type 161 | assert_equal "3.0", agent.version 162 | end 163 | 164 | def test_assign_by_host 165 | agent = @agents.select { |a| a.host == "agent7" }.first 166 | 167 | command = Galaxy::Commands["assign"].new ["beta", "3.0", "rslv"], { :console => @console } 168 | agents = command.select_agents(:host => agent.host) 169 | command.execute agents 170 | 171 | assert_equal "beta", agent.env 172 | assert_equal "rslv", agent.type 173 | assert_equal "3.0", agent.version 174 | end 175 | 176 | def test_clear 177 | # TODO 178 | end 179 | 180 | def test_clear_by_host 181 | # TODO 182 | end 183 | 184 | def test_update_by_host 185 | agent = @agents.select { |a| !a.config_path.nil? }.first 186 | env = agent.env 187 | type = agent.type 188 | 189 | command = Galaxy::Commands["update"].new ["4.0"], { :console => @console } 190 | agents = command.select_agents(:host => agent.host) 191 | command.execute agents 192 | 193 | assert_equal env, agent.env 194 | assert_equal type, agent.type 195 | assert_equal "4.0", agent.version 196 | end 197 | 198 | def test_update_config_by_host 199 | agent = @agents.select { |a| !a.config_path.nil? }.first 200 | env = agent.env 201 | type = agent.type 202 | 203 | command = Galaxy::Commands["update-config"].new ["4.0"], { :console => @console } 204 | agents = command.select_agents(:version => "1.0") 205 | results = command.execute agents 206 | assert_equal env, agent.env 207 | assert_equal type, agent.type 208 | assert_equal "4.0", agent.version 209 | end 210 | 211 | private 212 | 213 | def format_agents(agents=@agents) 214 | res = agents.inject("") do |memo, a| 215 | memo.empty? ? a.inspect : memo.to_s + a.inspect 216 | end 217 | res 218 | end 219 | end 220 | -------------------------------------------------------------------------------- /test/test_config.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.join(File.dirname(__FILE__), "..", "lib") 2 | $:.unshift File.join(File.dirname(__FILE__)) 3 | 4 | require 'test/unit' 5 | require 'galaxy/config' 6 | require 'galaxy/log' 7 | require 'helper' 8 | require 'ostruct' 9 | require 'stringio' 10 | require 'logger' 11 | 12 | class TestConfig < Test::Unit::TestCase 13 | 14 | def setup 15 | @s = OpenStruct.new 16 | @c = Galaxy::AgentConfigurator.new @s 17 | @c2 = Galaxy::ConsoleConfigurator.new @s 18 | end 19 | 20 | def test_host_defaults_to_hostname 21 | assert_equal `hostname`.strip, @c.host 22 | end 23 | 24 | def test_logging 25 | Galaxy::HostUtils.logger("fred").info "boo!" 26 | Galaxy::HostUtils.logger("fred").info "warn!" 27 | end 28 | 29 | def test_data_dir 30 | assert_equal "#{Galaxy::HostUtils.avail_path}/galaxy-agent/data" , @c.data_dir 31 | end 32 | 33 | def test_deploy_dir 34 | assert_equal "#{Galaxy::HostUtils.avail_path}/galaxy-agent/deploy" , @c.deploy_dir 35 | end 36 | 37 | def test_deploy_dir_specced 38 | @s.deploy_dir = "/tmp/plop" 39 | assert_equal "/tmp/plop", @c.deploy_dir 40 | end 41 | 42 | def test_data_dir_specced 43 | @s.data_dir = "/tmp/plop" 44 | assert_equal "/tmp/plop", @c.data_dir 45 | end 46 | 47 | def test_verbose 48 | @s.verbose = true 49 | assert @c.configure[:verbose] 50 | end 51 | 52 | def test_log_level_debug 53 | @s.log_level = "DEBUG" 54 | assert_equal Logger::DEBUG, @c.configure[:log_level] 55 | end 56 | 57 | def test_log_level_info 58 | @s.log_level = "INFO" 59 | assert_equal Logger::INFO, @c.configure[:log_level] 60 | end 61 | 62 | def test_log_level_warn 63 | @s.log_level = "WARN" 64 | assert_equal Logger::WARN, @c.configure[:log_level] 65 | end 66 | 67 | def test_log_level_error 68 | @s.log_level = "ERROR" 69 | assert_equal Logger::ERROR, @c.configure[:log_level] 70 | end 71 | 72 | def test_log_level_other 73 | @s.log_level = "---UNK" 74 | assert_equal nil, @c.configure[:log_level] 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /test/test_console.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.join(File.dirname(__FILE__), "..", "lib") 2 | $:.unshift File.join(File.dirname(__FILE__)) 3 | 4 | require 'test/unit' 5 | require 'galaxy/transport' 6 | require 'galaxy/config' 7 | require 'galaxy/console' 8 | require 'thread' 9 | require 'timeout' 10 | require 'helper' 11 | 12 | class TestConsole < Test::Unit::TestCase 13 | 14 | def setup 15 | @foo = OpenStruct.new({ 16 | :host => 'foo', 17 | :ip => '10.0.0.1', 18 | :machine => 'foomanchu', 19 | :config_path => '/alpha/1.0/bloo', 20 | :status => 'running' 21 | }) 22 | 23 | @bar = OpenStruct.new({ 24 | :host => 'bar', 25 | :ip => '10.0.0.2', 26 | :machine => 'barmanchu', 27 | :config_path => '/beta/2.0/blar', 28 | :status => 'stopped' 29 | }) 30 | 31 | @baz = OpenStruct.new({ 32 | :host => 'baz', 33 | :ip => '10.0.0.3', 34 | :machine => 'bazmanchu', 35 | :config_path => '/gamma/3.0/blaz', 36 | :status => 'dead' 37 | }) 38 | 39 | @blee = OpenStruct.new({ 40 | :host => 'blee', 41 | :ip => '10.0.0.4', 42 | :machine => 'bleemanchu' 43 | }) 44 | 45 | @console = Galaxy::Console.start({:host => "localhost", :url => "druby://localhost:4449"}) 46 | end 47 | 48 | def teardown 49 | @console.shutdown 50 | end 51 | 52 | def test_updates_last_announced_on_announce 53 | assert_nil @console.db["foo"] 54 | 55 | @console.send("announce", @foo) 56 | first = @console.db["foo"].timestamp 57 | @console.send("announce", @foo) 58 | second = @console.db["foo"].timestamp 59 | 60 | assert second > first 61 | end 62 | 63 | def test_list_agents 64 | @console.send("announce", @foo) 65 | @console.send("announce", @bar) 66 | @console.send("announce", @baz) 67 | 68 | agents = @console.agents 69 | assert_equal 3, agents.length 70 | 71 | assert agents.include?(@foo) 72 | assert agents.include?(@bar) 73 | assert agents.include?(@baz) 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /test/test_controller.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.join(File.dirname(__FILE__), "..", "lib") 2 | $:.unshift File.join(File.dirname(__FILE__)) 3 | 4 | require 'test/unit' 5 | require 'galaxy/controller' 6 | require 'galaxy/deployer' 7 | require 'galaxy/host' 8 | require 'helper' 9 | require 'fileutils' 10 | require 'logger' 11 | 12 | class TestController < Test::Unit::TestCase 13 | 14 | def setup 15 | @core_package = Tempfile.new("package.tgz").path 16 | system %{ 17 | #{Galaxy::HostUtils.tar} -C #{File.join(File.dirname(__FILE__), "core_package")} -czf #{@core_package} . 18 | } 19 | @path = Helper.mk_tmpdir 20 | @deployer = Galaxy::Deployer.new @path, Logger.new("/dev/null") 21 | @core_base = @deployer.deploy "1", @core_package, "/config", "/repository", "/binaries" 22 | @controller = Galaxy::Controller.new @core_base, '/config/path', 'http://repository/base', 'http://binaries/base', Logger.new("/dev/null") 23 | end 24 | 25 | def test_perform_success 26 | output = @controller.perform!('test-success') 27 | assert_equal "gorple\n", output 28 | end 29 | 30 | def test_perform_failure 31 | assert_raise Galaxy::Controller::CommandFailedException do 32 | @controller.perform!('test-failure') 33 | end 34 | end 35 | 36 | def test_perform_unrecognized 37 | assert_raise Galaxy::Controller::UnrecognizedCommandException do 38 | @controller.perform!('unrecognized') 39 | end 40 | end 41 | 42 | def test_controller_arguments 43 | assert_nothing_raised do 44 | @controller.perform!('test-arguments') 45 | end 46 | end 47 | 48 | end 49 | -------------------------------------------------------------------------------- /test/test_db.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.join(File.dirname(__FILE__), "..", "lib") 2 | $:.unshift File.join(File.dirname(__FILE__)) 3 | 4 | require 'test/unit' 5 | require 'galaxy/db' 6 | require 'helper' 7 | 8 | class TestDB < Test::Unit::TestCase 9 | 10 | def setup 11 | @db = Galaxy::DB.new "#{Helper.mk_tmpdir}/galaxy.db" 12 | end 13 | 14 | def test_silly 15 | @db["k"] = "v" 16 | assert_equal "v", @db["k"] 17 | end 18 | 19 | def test_in_child 20 | @db["name"] = "Fred" 21 | pid = fork do 22 | if @db["name"] == "Fred" 23 | exit 0 24 | else 25 | exit 1 26 | end 27 | end 28 | _, status = Process.waitpid2 pid 29 | assert_equal 0, status.exitstatus 30 | end 31 | 32 | 33 | 34 | end 35 | -------------------------------------------------------------------------------- /test/test_deployer.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.join(File.dirname(__FILE__), "..", "lib") 2 | $:.unshift File.join(File.dirname(__FILE__)) 3 | 4 | require 'test/unit' 5 | require 'galaxy/deployer' 6 | require 'galaxy/host' 7 | require 'helper' 8 | require 'fileutils' 9 | require 'logger' 10 | 11 | class TestDeployer < Test::Unit::TestCase 12 | 13 | def setup 14 | @core_package = Tempfile.new("package.tgz").path 15 | @bad_core_package = Tempfile.new("bad-package.tgz").path 16 | system %{ 17 | #{Galaxy::HostUtils.tar} -C #{File.join(File.dirname(__FILE__), "core_package")} -czf #{@core_package} . 18 | } 19 | system %{ 20 | #{Galaxy::HostUtils.tar} -C #{File.join(File.dirname(__FILE__), "bad_core_package")} -czf #{@bad_core_package} . 21 | } 22 | @path = Helper.mk_tmpdir 23 | @deployer = Galaxy::Deployer.new @path, Logger.new("/dev/null") 24 | end 25 | 26 | def test_core_base_is_right 27 | core_base = @deployer.deploy "2", @core_package, "/config", "/repository", "/binaries" 28 | assert_equal File.join(@path, "2"), core_base 29 | end 30 | 31 | def test_deployment_dir_is_made 32 | core_base = @deployer.deploy "2", @core_package, "/config", "/repository", "/binaries" 33 | assert FileTest.directory?(core_base) 34 | end 35 | 36 | def test_xndeploy_exists_after_deployment 37 | core_base = @deployer.deploy "2", @core_package, "/config", "/repository", "/binaries" 38 | assert FileTest.exists?(File.join(core_base, "bin", "xndeploy")) 39 | end 40 | 41 | def test_xndeploy_invoked_on_deploy 42 | core_base = @deployer.deploy "2", @core_package, "/config", "/repository", "/binaries" 43 | assert FileTest.exists?(File.join(core_base, "xndeploy_touched_me")) 44 | end 45 | 46 | def test_xndeploy_gets_correct_values 47 | core_base = @deployer.deploy "2", @core_package, "/config", "/repository", "/binaries" 48 | dump = File.open(File.join(core_base, "xndeploy_touched_me")) do |file| 49 | Marshal.load file 50 | end 51 | assert_equal core_base, dump[:deploy_base] 52 | assert_equal "/config", dump[:config_path] 53 | assert_equal "/repository", dump[:repository] 54 | assert_equal "/binaries", dump[:binaries_base] 55 | end 56 | 57 | def test_current_symlink_created 58 | core_base = @deployer.deploy "2", @core_package, "/config", "/repository", "/binaries" 59 | link = File.join(@path, "current") 60 | assert_equal false, FileTest.symlink?(link) 61 | @deployer.activate "2" 62 | assert FileTest.symlink?(link) 63 | assert_equal File.join(@path, "2"), File.readlink(link) 64 | end 65 | 66 | def test_upgrade 67 | first = @deployer.deploy "1", @core_package, "/config", "/repository", "/binaries" 68 | @deployer.activate "1" 69 | assert_equal File.join(@path, "1"), File.readlink(File.join(@path, "current")) 70 | 71 | first = @deployer.deploy "2", @core_package, "/config", "/repository", "/binaries" 72 | @deployer.activate "2" 73 | assert_equal File.join(@path, "2"), File.readlink(File.join(@path, "current")) 74 | end 75 | 76 | def test_bad_archive 77 | assert_raise RuntimeError do 78 | @deployer.deploy "bad", "/etc/hosts", "/config", "/repository", "/binaries" 79 | end 80 | end 81 | 82 | def test_deploy_script_failure 83 | assert_raise RuntimeError do 84 | @deployer.deploy "bad", @bad_core_package, "/config", "/repository", "/binaries" 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /test/test_event.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.join(File.dirname(__FILE__), "..", "lib") 2 | $:.unshift File.join(File.dirname(__FILE__)) 3 | 4 | require 'test/unit' 5 | require 'galaxy/events' 6 | require 'logger' 7 | 8 | class TestEvent < Test::Unit::TestCase 9 | 10 | # Set your collector hostname here to run tests. 11 | # See http://github.com/ning/collector 12 | COLLECTOR_HOST = nil 13 | 14 | def test_collectors 15 | unless COLLECTOR_HOST.nil? 16 | send_log 17 | send_raw_event 18 | send_success_event 19 | send_error_event 20 | build_number_string 21 | else 22 | assert true 23 | end 24 | end 25 | 26 | def setup 27 | logger = Logger.new(STDOUT) 28 | logger.level = Logger::WARN 29 | 30 | @galaxy_sender = Galaxy::GalaxyEventSender.new(COLLECTOR_HOST, "http://gonsole.testing.company.net:1242", "127.0.0.1", logger) 31 | @log_sender = Galaxy::GalaxyLogEventSender.new(COLLECTOR_HOST, "http://gonsole.testing.company.net:1242", "127.0.0.1", logger) 32 | 33 | @event = OpenStruct.new( 34 | :host => "prod1.company.com", 35 | :ip => "192.168.12.42", 36 | :url => "drb://goofabr.company.pouet", 37 | :os => "Linux", 38 | :machine => "foobar", 39 | :core_type => "tester", 40 | :config_path => "conf/bar/baz", 41 | :build => "124212", 42 | :status => "running", 43 | :agent_status => "online", 44 | :galaxy_version => "2.5.1", 45 | :user => "John Doe", 46 | :gonsole_url => "http://gonsole.qa.company.net:4442" 47 | ) 48 | end 49 | 50 | # More tests in test_logger.rb 51 | def send_log 52 | assert @log_sender.dispatch_error_log("Hello world!", "program_test") 53 | end 54 | 55 | def send_raw_event 56 | assert @galaxy_sender.send_event(@event) 57 | end 58 | 59 | def send_success_event 60 | assert @galaxy_sender.dispatch_announce_success_event(@event) 61 | end 62 | 63 | def send_error_event 64 | assert @galaxy_sender.dispatch_perform_error_event(@event) 65 | end 66 | 67 | def build_number_string 68 | event = OpenStruct.new( 69 | :agent_status => "online", 70 | :os => "solaris", 71 | :host => "prod1.company.com", 72 | :galaxy_version => "2.6.0.5", 73 | :core_type => "apache", 74 | :machine => "localhost", 75 | :status => "stopped", 76 | :url => "drb://prod2.company.com:4441", 77 | :config_path => "alpha/DEP-1/apache", 78 | :build => "6.1.10", 79 | :ip => "0.1.9.1" 80 | ) 81 | assert @galaxy_sender.dispatch_announce_success_event(event) 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /test/test_fetcher.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.join(File.dirname(__FILE__), "..", "lib") 2 | $:.unshift File.join(File.dirname(__FILE__)) 3 | 4 | require 'test/unit' 5 | require 'galaxy/fetcher' 6 | require 'helper' 7 | require 'fileutils' 8 | require 'logger' 9 | require 'webrick' 10 | include WEBrick 11 | 12 | class TestFetcher < Test::Unit::TestCase 13 | 14 | def setup 15 | @local_fetcher = Galaxy::Fetcher.new(File.join(File.dirname(__FILE__), "property_data"), Logger.new("/dev/null")) 16 | @http_fetcher = Galaxy::Fetcher.new("http://localhost:7777", Logger.new("/dev/null")) 17 | 18 | webrick_logger = Logger.new(STDOUT) 19 | webrick_logger.level = Logger::WARN 20 | @server = HTTPServer.new(:Port => 7777, :Logger => webrick_logger) 21 | @server.mount("/", HTTPServlet::FileHandler, File.join(File.dirname(__FILE__), "property_data"), true) 22 | Thread.start do 23 | @server.start 24 | end 25 | end 26 | 27 | def teardown 28 | @server.shutdown 29 | end 30 | 31 | def test_local_fetch 32 | path = @local_fetcher.fetch "foo", "bar", "properties" 33 | assert File.exists?(path) 34 | end 35 | 36 | def test_http_fetch 37 | path = @http_fetcher.fetch "foo", "bar", "properties" 38 | assert File.exists?(path) 39 | end 40 | 41 | def test_http_fetch_not_found 42 | assert_raise RuntimeError do 43 | @server.logger.level = Logger::FATAL 44 | path = @http_fetcher.fetch "gorple", "fez", "properties" 45 | @server.logger.level = Logger::WARN 46 | end 47 | end 48 | 49 | end 50 | -------------------------------------------------------------------------------- /test/test_filter.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.join(File.dirname(__FILE__), "..", "lib") 2 | $:.unshift File.join(File.dirname(__FILE__)) 3 | 4 | require 'test/unit' 5 | require 'ostruct' 6 | require 'galaxy/filter' 7 | 8 | class TestFilter < Test::Unit::TestCase 9 | def setup 10 | @null = OpenStruct.new({ }) 11 | 12 | @foo = OpenStruct.new({ 13 | :host => 'foo', 14 | :ip => '10.0.0.1', 15 | :machine => 'foomanchu', 16 | :config_path => '/alpha/1.0/bloo', 17 | :status => 'running', 18 | }) 19 | 20 | @bar = OpenStruct.new({ 21 | :host => 'bar', 22 | :ip => '10.0.0.2', 23 | :machine => 'barmanchu', 24 | :config_path => '/beta/2.0/blar', 25 | :status => 'stopped', 26 | }) 27 | 28 | @baz = OpenStruct.new({ 29 | :host => 'baz', 30 | :ip => '10.0.0.3', 31 | :machine => 'bazmanchu', 32 | :config_path => '/gamma/3.0/blaz', 33 | :status => 'dead', 34 | }) 35 | 36 | @blee = OpenStruct.new({ 37 | :host => 'blee', 38 | :ip => '10.0.0.4', 39 | :machine => 'bleemanchu', 40 | }) 41 | 42 | @agents = [@null, @foo, @bar, @baz, @blee] 43 | end 44 | 45 | def test_filter_none 46 | filter = Galaxy::Filter.new({ }) 47 | 48 | assert_equal 0, @agents.select(&filter).size 49 | end 50 | 51 | def test_filter_by_known_host 52 | filter = Galaxy::Filter.new :host => "foo" 53 | 54 | assert_equal [@foo], @agents.select(&filter) 55 | end 56 | 57 | def test_filter_by_unknown_host 58 | filter = Galaxy::Filter.new :host => "unknown" 59 | 60 | assert_equal [ ], @agents.select(&filter) 61 | end 62 | 63 | def test_filter_by_known_machine 64 | filter = Galaxy::Filter.new :machine => "foomanchu" 65 | 66 | assert_equal [@foo], @agents.select(&filter) 67 | end 68 | 69 | def test_filter_by_unknown_machine 70 | filter = Galaxy::Filter.new :machine => "unknown" 71 | 72 | assert_equal [ ], @agents.select(&filter) 73 | end 74 | 75 | def test_filter_by_known_ip 76 | filter = Galaxy::Filter.new :ip => "10.0.0.1" 77 | 78 | assert_equal [@foo], @agents.select(&filter) 79 | end 80 | 81 | def test_filter_by_unknown_ip 82 | filter = Galaxy::Filter.new :ip => "20.0.0.1" 83 | 84 | assert_equal [ ], @agents.select(&filter) 85 | end 86 | 87 | def test_filter_by_state_running 88 | filter = Galaxy::Filter.new :state => "running" 89 | 90 | assert_equal [@foo], @agents.select(&filter) 91 | end 92 | 93 | def test_filter_by_state_stopped 94 | filter = Galaxy::Filter.new :state => "stopped" 95 | 96 | assert_equal [@bar], @agents.select(&filter) 97 | end 98 | 99 | def test_filter_by_state_dead 100 | filter = Galaxy::Filter.new :state => "dead" 101 | 102 | assert_equal [@baz], @agents.select(&filter) 103 | end 104 | 105 | def test_filter_by_unknown_state 106 | filter = Galaxy::Filter.new :state => "unknown" 107 | 108 | assert_equal [ ], @agents.select(&filter) 109 | end 110 | 111 | def test_filter_by_known_env 112 | filter = Galaxy::Filter.new :env => "beta" 113 | 114 | assert_equal [@bar], @agents.select(&filter) 115 | end 116 | 117 | def test_filter_by_known_env 118 | filter = Galaxy::Filter.new :env => "unknown" 119 | 120 | assert_equal [ ], @agents.select(&filter) 121 | end 122 | 123 | def test_filter_by_known_version 124 | filter = Galaxy::Filter.new :version => "1.0" 125 | 126 | assert_equal [@foo], @agents.select(&filter) 127 | end 128 | 129 | def test_filter_by_unknown_version 130 | filter = Galaxy::Filter.new :version => "0.0" 131 | 132 | assert_equal [ ], @agents.select(&filter) 133 | end 134 | 135 | def test_filter_by_known_type 136 | filter = Galaxy::Filter.new :type => "bloo" 137 | 138 | assert_equal [@foo], @agents.select(&filter) 139 | end 140 | 141 | def test_filter_by_unknown_type 142 | filter = Galaxy::Filter.new :type => "unknown" 143 | 144 | assert_equal [ ], @agents.select(&filter) 145 | end 146 | 147 | def test_filter_by_assigned 148 | filter = Galaxy::Filter.new :set => :taken 149 | 150 | assert_equal [@foo, @bar, @baz], @agents.select(&filter) 151 | end 152 | 153 | def test_filter_by_unassigned 154 | filter = Galaxy::Filter.new :set => :empty 155 | 156 | assert_equal [@null, @blee], @agents.select(&filter) 157 | end 158 | 159 | def test_filter_all 160 | filter = Galaxy::Filter.new :set => :all 161 | 162 | assert_equal @agents, @agents.select(&filter) 163 | end 164 | 165 | 166 | ##################################################################################### 167 | # The following are additions for GAL-151. Given the way the code is _currently_ 168 | # written, we really only need to check against host and machine, but the others are 169 | # added for increased safety and future-proofing 170 | # 171 | def test_filter_by_unknown_host_like_known_host 172 | filter = Galaxy::Filter.new :host => "fo" #don't match with "foo" 173 | 174 | assert_equal [ ], @agents.select(&filter) 175 | end 176 | 177 | def test_filter_by_unknown_machine_like_known_machine 178 | filter = Galaxy::Filter.new :machine => "fooman" # don't match with "foomanchu" 179 | 180 | assert_equal [ ], @agents.select(&filter) 181 | end 182 | 183 | def test_filter_by_unknown_ip_like_known_ip 184 | filter = Galaxy::Filter.new :ip => "10.0.0." # don't match with "10.0.0.1" 185 | 186 | assert_equal [ ], @agents.select(&filter) 187 | end 188 | 189 | def test_filter_by_unknown_env_like_known_env 190 | filter = Galaxy::Filter.new :env => "bet" #don't match with "beta" 191 | 192 | assert_equal [ ], @agents.select(&filter) 193 | end 194 | 195 | def test_filter_by_unknown_version_like_known_version 196 | filter = Galaxy::Filter.new :version => "1." #don't match with "1.0" 197 | 198 | assert_equal [ ], @agents.select(&filter) 199 | end 200 | 201 | def test_filter_by_unknown_type_like_known_type 202 | filter = Galaxy::Filter.new :type => "blo" # don't match with "bloo" 203 | 204 | assert_equal [ ], @agents.select(&filter) 205 | end 206 | 207 | end 208 | -------------------------------------------------------------------------------- /test/test_host.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.join(File.dirname(__FILE__), "..", "lib") 2 | 3 | require "fileutils" 4 | require "test/unit" 5 | require "galaxy/host" 6 | 7 | class TestHost < Test::Unit::TestCase 8 | 9 | def test_tar_executable_was_found 10 | assert_not_nil Galaxy::HostUtils.tar 11 | end 12 | 13 | def test_system_success 14 | assert_nothing_raised do 15 | Galaxy::HostUtils.system 'true' 16 | end 17 | end 18 | 19 | def test_system_failure 20 | assert_raise Galaxy::HostUtils::CommandFailedError do 21 | Galaxy::HostUtils.system 'false' 22 | end 23 | end 24 | 25 | def test_system_failure_output 26 | begin 27 | Galaxy::HostUtils.system 'ls /gorple/fez' 28 | rescue Exception => e 29 | assert_match(/No such file or directory/, e.message) 30 | end 31 | end 32 | end -------------------------------------------------------------------------------- /test/test_logger.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.join(File.dirname(__FILE__), "..", "lib") 2 | $:.unshift File.join(File.dirname(__FILE__)) 3 | 4 | require 'test/unit' 5 | require 'galaxy/events' 6 | require 'galaxy/host' 7 | require 'galaxy/log' 8 | 9 | class TestLogger < Test::Unit::TestCase 10 | def setup 11 | end 12 | 13 | def teardown 14 | #puts `cat /tmp/galaxy_unit_test.log*` 15 | FileUtils.rm Dir.glob('/tmp/galaxy_unit_test.log*') 16 | end 17 | 18 | def test_initialize_dummy_event_sender 19 | glogger = Galaxy::Log::Glogger.new "/tmp/galaxy_unit_test.log" 20 | assert_kind_of Galaxy::DummyEventSender, glogger.event_dispatcher 21 | assert_kind_of Logger, glogger.log 22 | end 23 | 24 | def test_initialize_collector 25 | glogger = Galaxy::Log::Glogger.new "/tmp/galaxy_unit_test.log", "http://collector.com" 26 | assert_kind_of Galaxy::GalaxyLogEventSender, glogger.event_dispatcher 27 | assert_kind_of Logger, glogger.log 28 | end 29 | 30 | def test_syslog 31 | # Real-life example that was breaking 2.6.pre2 32 | logger = Galaxy::HostUtils.logger 33 | assert logger.debug("http://prod1.company.com:8080/1?v=GalaxyLog,81267034768000%2C4168954152%2C29654%2Csdebug%2Cs%2CsRegistered%2520Event%2520listener%2520type%2520Galaxy%253A%253AGalaxyEventSender%2520at%2520http%253A%252F%252Fz1205a9.company.com%253A8080%252C%2520sender%2520url%25200.1.9.4%2C&rt=b") 34 | assert logger.info("http://prod1.company.com:8080/1?v=GalaxyLog,81267034768000%2C4168954152%2C29654%2Csdebug%2Cs%2CsRegistered%2520Event%2520listener%2520type%2520Galaxy%253A%253AGalaxyEventSender%2520at%2520http%253A%252F%252Fz1205a9.company.com%253A8080%252C%2520sender%2520url%25200.1.9.4%2C&rt=b") 35 | assert logger.warn("http://prod1.company.com:8080/1?v=GalaxyLog,81267034768000%2C4168954152%2C29654%2Csdebug%2Cs%2CsRegistered%2520Event%2520listener%2520type%2520Galaxy%253A%253AGalaxyEventSender%2520at%2520http%253A%252F%252Fz1205a9.company.com%253A8080%252C%2520sender%2520url%25200.1.9.4%2C&rt=b") 36 | assert logger.error("http://prod1.company.com:8080/1?v=GalaxyLog,81267034768000%2C4168954152%2C29654%2Csdebug%2Cs%2CsRegistered%2520Event%2520listener%2520type%2520Galaxy%253A%253AGalaxyEventSender%2520at%2520http%253A%252F%252Fz1205a9.company.com%253A8080%252C%2520sender%2520url%25200.1.9.4%2C&rt=b") 37 | end 38 | 39 | def test_syslog_raw 40 | logger = Galaxy::HostUtils.logger 41 | assert logger << "foo bar baz" 42 | end 43 | 44 | def test_respect_loglevel_with_event_dispatcher 45 | glogger = Galaxy::Log::Glogger.new "/tmp/galaxy_unit_test.log", "non-existent-host", "http://gonsole.test.company.com:1242", "10.15.12.14" 46 | assert_kind_of Galaxy::GalaxyLogEventSender, glogger.event_dispatcher 47 | assert_kind_of Logger, glogger.log 48 | 49 | # Make sure we don't try to send events at the wrong level 50 | 51 | glogger.log.level = Logger::INFO 52 | assert glogger.debug("debug hello from unit test") 53 | 54 | glogger.log.level = Logger::DEBUG 55 | assert_raise URI::BadURIError do 56 | glogger.debug("debug hello from unit test") 57 | end 58 | end 59 | 60 | def test_syslog_level 61 | logger = Galaxy::HostUtils.logger 62 | 63 | assert_equal Logger::INFO, logger.level 64 | 65 | logger.level = Logger::DEBUG 66 | assert_equal Logger::DEBUG, logger.level 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /test/test_logger_collector.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.join(File.dirname(__FILE__), "..", "lib") 2 | $:.unshift File.join(File.dirname(__FILE__)) 3 | 4 | require 'test/unit' 5 | require 'galaxy/events' 6 | require 'galaxy/host' 7 | require 'galaxy/log' 8 | 9 | class TestLoggerCollector < Test::Unit::TestCase 10 | 11 | # Set your collector hostname here to run tests. 12 | # See http://github.com/ning/collector 13 | COLLECTOR_HOST = nil 14 | 15 | def test_collectors 16 | unless COLLECTOR_HOST.nil? 17 | send_event_via_event_dispatcher 18 | send_encoded_event_via_event_dispatcher 19 | else 20 | assert true 21 | end 22 | end 23 | 24 | def send_event_via_event_dispatcher 25 | glogger = Galaxy::Log::Glogger.new "/tmp/galaxy_unit_test.log", COLLECTOR_HOST, "http://gonsole.test.company.com:1242", "10.15.12.14" 26 | assert_kind_of Galaxy::GalaxyLogEventSender, glogger.event_dispatcher 27 | assert_kind_of Logger, glogger.log 28 | 29 | assert glogger.event_dispatcher.dispatch_debug_log("debug hello from unit test") 30 | assert glogger.event_dispatcher.dispatch_info_log("info hello from unit test") 31 | assert glogger.event_dispatcher.dispatch_warn_log("warn hello from unit test") 32 | assert glogger.event_dispatcher.dispatch_error_log("error hello from unit test") 33 | assert glogger.event_dispatcher.dispatch_fatal_log("fatal hello from unit test") 34 | end 35 | 36 | def send_encoded_event_via_event_dispatcher 37 | glogger = Galaxy::Log::Glogger.new "/tmp/galaxy_unit_test.log", COLLECTOR_HOST, "http://gonsole.test.company.com:1242", "10.15.12.14" 38 | assert_kind_of Galaxy::GalaxyLogEventSender, glogger.event_dispatcher 39 | assert_kind_of Logger, glogger.log 40 | 41 | assert glogger.event_dispatcher.dispatch_error_log("i love spaces") 42 | assert glogger.event_dispatcher.dispatch_error_log("drb://slashespowaa.com") 43 | assert glogger.event_dispatcher.dispatch_error_log("Embedded Thrift: ,sMyThrift,412") 44 | assert glogger.event_dispatcher.dispatch_error_log("$rr0r haZ !@#\$%^&*()_+{}:<>?/.,';#][\/~-`") 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /test/test_parallelize.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'galaxy/parallelize' 3 | 4 | class TestParallelize < Test::Unit::TestCase 5 | def test_parallelize_with_thread_count_of_1 6 | array = (1..10).entries 7 | start = Time.new 8 | array.parallelize(1) { |i| sleep 1 } 9 | stop = Time.new 10 | assert stop - start >= 10 11 | assert stop - start < 11 12 | end 13 | 14 | def test_parallelize_with_thread_count_of_10 15 | array = (1..100).entries 16 | start = Time.new 17 | array.parallelize(10) { |i| sleep 1 } 18 | stop = Time.new 19 | assert stop - start >= 10 20 | assert stop - start < 11 21 | end 22 | 23 | def test_parallelize_with_thread_count_of_100 24 | array = (1..1000).entries 25 | start = Time.new 26 | array.parallelize(100) { |i| sleep 1 } 27 | stop = Time.new 28 | assert stop - start >= 10 29 | assert stop - start < 11 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /test/test_propbuilder.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.join(File.dirname(__FILE__), "..", "lib") 2 | 3 | require 'test/unit' 4 | require "galaxy/properties" 5 | require 'logger' 6 | 7 | class TestPropertyBuilder < Test::Unit::TestCase 8 | 9 | PropertyBase = File.dirname(__FILE__) + "/property_data" 10 | 11 | def setup 12 | @builder = Galaxy::Properties::Builder.new PropertyBase, Logger.new("/dev/null") 13 | end 14 | 15 | def test_simple 16 | props = @builder.build "/a/b/c/d", "test_simple.properties" 17 | assert_equal "green", props['chris'] 18 | end 19 | 20 | def test_override 21 | props = @builder.build "/a/b/c/d", "test_override.properties" 22 | assert_equal "purple", props['oscar'] 23 | assert_equal "red", props['sam'] 24 | end 25 | 26 | def test_comments_ignored 27 | props = @builder.build "/a/b/c/d", "test_comments_ignored.properties" 28 | 29 | assert_nil props['hello'] 30 | assert_nil props['#hello'] 31 | assert_equal "fuschia", props['red'] 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /test/test_repository.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.join(File.dirname(__FILE__), "..", "lib") 2 | 3 | require 'test/unit' 4 | require "galaxy/repository" 5 | 6 | class TestRepository < Test::Unit::TestCase 7 | 8 | PropertyBase = File.dirname(__FILE__) + "/property_data" 9 | 10 | def setup 11 | @builder = Galaxy::Repository.new PropertyBase 12 | end 13 | 14 | def test_simple 15 | @builder.walk "/a/b/c/d", "test_simple.properties" do |path, content| 16 | assert_equal "/a/b/c/test_simple.properties", path 17 | end 18 | end 19 | 20 | def test_multiple 21 | paths = [] 22 | @builder.walk "/a/b/c/d", "test_override.properties" do |path, content| 23 | paths << path 24 | end 25 | 26 | assert_equal ["/a/b/test_override.properties", "/a/b/c/d/test_override.properties"], paths 27 | end 28 | 29 | def test_empty 30 | paths = [] 31 | @builder.walk "/a/b/c/d", "empty.properties" do |path, content| 32 | paths << path 33 | end 34 | 35 | assert_equal [], paths 36 | end 37 | 38 | end 39 | -------------------------------------------------------------------------------- /test/test_temp.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.join(File.dirname(__FILE__), "..", "lib") 2 | 3 | require "fileutils" 4 | require "test/unit" 5 | require "galaxy/temp" 6 | 7 | class TestTemp < Test::Unit::TestCase 8 | 9 | def test_simple 10 | begin 11 | file = Galaxy::Temp.mk_file 12 | dir = Galaxy::Temp.mk_dir 13 | assert File.exists?(file) 14 | assert File.exists?(dir) 15 | ObjectSpace.garbage_collect 16 | assert File.exists?(file) 17 | assert File.exists?(dir) 18 | ensure 19 | FileUtils.rm file if File.exists? file 20 | FileUtils.rmdir dir if File.exists? dir 21 | end 22 | end 23 | 24 | def test_repeated 25 | used_files = [] 26 | used_dirs = [] 27 | begin 28 | 100.times do 29 | file = Galaxy::Temp.mk_file 30 | assert !used_files.include?(file) 31 | assert !used_dirs.include?(file) 32 | used_files.push file 33 | dir = Galaxy::Temp.mk_dir 34 | assert !used_files.include?(dir) 35 | assert !used_dirs.include?(dir) 36 | used_dirs.push dir 37 | assert File.exists?(file) 38 | assert File.exists?(dir) 39 | ObjectSpace.garbage_collect 40 | assert File.exists?(file) 41 | assert File.exists?(dir) 42 | end 43 | ensure 44 | used_files.each { |file| FileUtils.rm file if File.exists? file } 45 | end 46 | end 47 | 48 | def test_auto 49 | rd, wr = IO.pipe 50 | if fork 51 | wr.close 52 | file, dir = rd.read.split "\t" 53 | rd.close 54 | Process.wait 55 | begin 56 | assert !File.exists?(file) 57 | assert !File.exists?(dir) 58 | ensure 59 | FileUtils.rm file if File.exists? file 60 | FileUtils.rmdir dir if File.exists? dir 61 | end 62 | else 63 | rd.close 64 | file = Galaxy::Temp.mk_auto_file 65 | dir = Galaxy::Temp.mk_auto_dir 66 | assert File.exists?(file) 67 | assert File.exists?(dir) 68 | ObjectSpace.garbage_collect 69 | assert File.exists?(file) 70 | assert File.exists?(dir) 71 | wr.write "#{file}\t#{dir}" 72 | wr.close 73 | exit 0 74 | end 75 | end 76 | 77 | end 78 | -------------------------------------------------------------------------------- /test/test_transport.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.join(File.dirname(__FILE__), "..", "lib") 2 | $:.unshift File.join(File.dirname(__FILE__)) 3 | 4 | require 'test/unit' 5 | require 'galaxy/transport' 6 | require 'galaxy/console' 7 | 8 | class TestTransport < Test::Unit::TestCase 9 | def test_handler_for 10 | assert Galaxy::Transport.handler_for("druby://xxxx:444").kind_of?(Galaxy::DRbTransport) 11 | assert Galaxy::Transport.handler_for("local://xxxx:444").kind_of?(Galaxy::LocalTransport) 12 | assert Galaxy::Transport.handler_for("http://xxxx:444").kind_of?(Galaxy::HttpTransport) 13 | end 14 | 15 | def test_handler_not_found 16 | assert_raises RuntimeError do 17 | Galaxy::Transport.handler_for("invalid://xxxx:444") 18 | end 19 | end 20 | 21 | def test_drb_publish 22 | url = "druby://localhost:4444" 23 | console = Galaxy::Transport.publish url, "hello" 24 | 25 | obj = Galaxy::Transport.locate url 26 | 27 | assert_equal "hello", obj.to_s 28 | console.stop_service 29 | end 30 | 31 | def test_drb_pool_size 32 | assert_equal 0, DRb::DRbConn::POOL_SIZE 33 | end 34 | 35 | def test_http_publish 36 | console = Galaxy::Console.start({ :host => 'localhost', :log_level => Logger::WARN }) 37 | url = "http://localhost:4441" 38 | 39 | assert_raises TypeError do 40 | Galaxy::Transport.publish url, nil 41 | end 42 | 43 | console_logger = Logger.new(STDOUT) 44 | console_logger.level = Logger::WARN 45 | Galaxy::Transport.publish url, console, console_logger 46 | 47 | announcer = Galaxy::Transport.locate url 48 | o = OpenStruct.new(:host => "localhost", :url => url, :status => "running") 49 | assert_equal Galaxy::ReceiveAnnouncement::ANNOUNCEMENT_RESPONSE_TEXT, announcer.announce(o) 50 | 51 | Galaxy::Transport.unpublish url 52 | console.shutdown 53 | end 54 | 55 | def foo(a) 56 | $foo_called = true 57 | end 58 | 59 | # def test_http_publish_with_callback 60 | # url = "http://localhost:4442" 61 | # Galaxy::Transport.publish url, lambda{|a| foo(a) } 62 | # 63 | # ann = Galaxy::Transport.locate url 64 | # $foo_called = false 65 | # ann.announce("announcement") 66 | # assert $foo_called == true 67 | # 68 | # Galaxy::Transport.unpublish url 69 | # end 70 | 71 | end 72 | --------------------------------------------------------------------------------