ipvs_litmus 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/.rake_commit +1 -0
- data/.rvmrc +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +31 -0
- data/Rakefile +6 -0
- data/bin/litmus +6 -0
- data/bin/litmusctl +7 -0
- data/config.ru +4 -0
- data/ipvs_litmus.gemspec +25 -0
- data/lib/facts/loadaverage.rb +6 -0
- data/lib/ipvs_litmus/app.rb +48 -0
- data/lib/ipvs_litmus/cli/admin.rb +86 -0
- data/lib/ipvs_litmus/cli/server.rb +62 -0
- data/lib/ipvs_litmus/configuration.rb +20 -0
- data/lib/ipvs_litmus/dependency/http.rb +27 -0
- data/lib/ipvs_litmus/forced_health.rb +18 -0
- data/lib/ipvs_litmus/health.rb +35 -0
- data/lib/ipvs_litmus/metric/available_memory.rb +32 -0
- data/lib/ipvs_litmus/metric/cpu_load.rb +22 -0
- data/lib/ipvs_litmus/service.rb +51 -0
- data/lib/ipvs_litmus/status_file.rb +26 -0
- data/lib/ipvs_litmus/version.rb +3 -0
- data/lib/ipvs_litmus.rb +44 -0
- data/spec/ipvs_litmus/app_spec.rb +215 -0
- data/spec/ipvs_litmus/cli/admin_spec.rb +58 -0
- data/spec/ipvs_litmus/cli/server_spec.rb +16 -0
- data/spec/ipvs_litmus/configuration_spec.rb +11 -0
- data/spec/ipvs_litmus/dependency/http_spec.rb +42 -0
- data/spec/ipvs_litmus/health_spec.rb +71 -0
- data/spec/ipvs_litmus/metric/available_memory_spec.rb +33 -0
- data/spec/ipvs_litmus/metric/cpu_load_spec.rb +39 -0
- data/spec/ipvs_litmus/service_spec.rb +65 -0
- data/spec/ipvs_litmus/status_file_spec.rb +39 -0
- data/spec/ipvs_litmus_spec.rb +22 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/always_available_dependency.rb +5 -0
- data/spec/support/constant_metric.rb +9 -0
- data/spec/support/never_available_dependency.rb +5 -0
- data/spec/support/stub_facter.rb +9 -0
- data/spec/support/test.config +11 -0
- metadata +219 -0
    
        data/.gitignore
    ADDED
    
    
    
        data/.rake_commit
    ADDED
    
    | @@ -0,0 +1 @@ | |
| 1 | 
            +
            --without-prompt feature
         | 
    
        data/.rvmrc
    ADDED
    
    | @@ -0,0 +1 @@ | |
| 1 | 
            +
            rvm use ruby-1.8.7-p249@ipvs_litmus --create
         | 
    
        data/.travis.yml
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    
    
        data/LICENSE
    ADDED
    
    | @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            Copyright (c) 2012 Braintree Payment Solutions LLC
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            MIT License
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining
         | 
| 6 | 
            +
            a copy of this software and associated documentation files (the
         | 
| 7 | 
            +
            "Software"), to deal in the Software without restriction, including
         | 
| 8 | 
            +
            without limitation the rights to use, copy, modify, merge, publish,
         | 
| 9 | 
            +
            distribute, sublicense, and/or sell copies of the Software, and to
         | 
| 10 | 
            +
            permit persons to whom the Software is furnished to do so, subject to
         | 
| 11 | 
            +
            the following conditions:
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            The above copyright notice and this permission notice shall be
         | 
| 14 | 
            +
            included in all copies or substantial portions of the Software.
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         | 
| 17 | 
            +
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         | 
| 18 | 
            +
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
         | 
| 19 | 
            +
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         | 
| 20 | 
            +
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
         | 
| 21 | 
            +
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
         | 
| 22 | 
            +
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         | 
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,31 @@ | |
| 1 | 
            +
            # IpvsLitmus
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Backend health tester for HA Services
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            [](http://travis-ci.org/braintree/ipvs_litmus)
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            ## Installation
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            Add this line to your application's Gemfile:
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                gem 'ipvs_litmus'
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            And then execute:
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                $ bundle
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            Or install it yourself as:
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                $ gem install ipvs_litmus
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            ## Usage
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            TODO: Write usage instructions here
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            ## Contributing
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            1. Fork it
         | 
| 28 | 
            +
            2. Create your feature branch (`git checkout -b my-new-feature`)
         | 
| 29 | 
            +
            3. Commit your changes (`git commit -am 'Added some feature'`)
         | 
| 30 | 
            +
            4. Push to the branch (`git push origin my-new-feature`)
         | 
| 31 | 
            +
            5. Create new Pull Request
         | 
    
        data/Rakefile
    ADDED
    
    
    
        data/bin/litmus
    ADDED
    
    
    
        data/bin/litmusctl
    ADDED
    
    
    
        data/config.ru
    ADDED
    
    
    
        data/ipvs_litmus.gemspec
    ADDED
    
    | @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            # -*- encoding: utf-8 -*-
         | 
| 2 | 
            +
            require File.expand_path('../lib/ipvs_litmus/version', __FILE__)
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            Gem::Specification.new do |gem|
         | 
| 5 | 
            +
              gem.authors       = ["Braintreeps"]
         | 
| 6 | 
            +
              gem.email         = ["code@getbraintree.com"]
         | 
| 7 | 
            +
              gem.description   = %q{Backend health tester for HA Services}
         | 
| 8 | 
            +
              gem.summary       = %q{Backend health tester for HA Services}
         | 
| 9 | 
            +
              gem.homepage      = ""
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              gem.files         = `git ls-files`.split($\)
         | 
| 12 | 
            +
              gem.executables   = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
         | 
| 13 | 
            +
              gem.test_files    = gem.files.grep(%r{^(test|spec|features)/})
         | 
| 14 | 
            +
              gem.name          = "ipvs_litmus"
         | 
| 15 | 
            +
              gem.require_paths = ["lib"]
         | 
| 16 | 
            +
              gem.version       = IPVSLitmus::VERSION
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              gem.add_dependency "sinatra", "~> 1.3.2"
         | 
| 19 | 
            +
              gem.add_dependency "facter", "~> 1.6.7"
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              gem.add_development_dependency "rspec", "2.9.0"
         | 
| 22 | 
            +
              gem.add_development_dependency "rack-test", "0.6.1"
         | 
| 23 | 
            +
              gem.add_development_dependency "rake"
         | 
| 24 | 
            +
              gem.add_development_dependency "rake_commit", "0.13"
         | 
| 25 | 
            +
            end
         | 
| @@ -0,0 +1,48 @@ | |
| 1 | 
            +
            module IPVSLitmus
         | 
| 2 | 
            +
              class App < Sinatra::Base
         | 
| 3 | 
            +
                post "/force/*" do
         | 
| 4 | 
            +
                  path = *status_file_path(params[:splat])
         | 
| 5 | 
            +
                  statusfile = StatusFile.new(*path)
         | 
| 6 | 
            +
                  statusfile.create(params[:reason])
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  text 201, "File created"
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                delete "/force/*" do
         | 
| 12 | 
            +
                  path = *status_file_path(params[:splat])
         | 
| 13 | 
            +
                  statusfile = StatusFile.new(*path)
         | 
| 14 | 
            +
                  if statusfile.exists?
         | 
| 15 | 
            +
                    statusfile.delete
         | 
| 16 | 
            +
                    text 200, "File deleted"
         | 
| 17 | 
            +
                  else
         | 
| 18 | 
            +
                    text 404, "NOT FOUND"
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                get "/:service/status" do
         | 
| 23 | 
            +
                  service = IPVSLitmus.services[params[:service]]
         | 
| 24 | 
            +
                  if service.nil?
         | 
| 25 | 
            +
                    text 404, "NOT FOUND"
         | 
| 26 | 
            +
                  else
         | 
| 27 | 
            +
                    health = service.current_health
         | 
| 28 | 
            +
                    response_code = health.ok? ? 200 : 503
         | 
| 29 | 
            +
                    body = "Health: #{health.value}\n"
         | 
| 30 | 
            +
                    body << health.summary
         | 
| 31 | 
            +
                    text response_code, body
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def text(response_code, body)
         | 
| 36 | 
            +
                  [response_code, { "Content-Type" => "text/plain" }, body]
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def status_file_path(splat)
         | 
| 40 | 
            +
                  path = splat.first.split("/")
         | 
| 41 | 
            +
                  if path.size == 1
         | 
| 42 | 
            +
                    ["global_#{path.first}"]
         | 
| 43 | 
            +
                  else
         | 
| 44 | 
            +
                    path
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
            end
         | 
| @@ -0,0 +1,86 @@ | |
| 1 | 
            +
            module IPVSLitmus
         | 
| 2 | 
            +
              module CLI
         | 
| 3 | 
            +
                class Admin
         | 
| 4 | 
            +
                  def run(argv = ARGV)
         | 
| 5 | 
            +
                    command = argv.shift
         | 
| 6 | 
            +
                    send(command, argv)
         | 
| 7 | 
            +
                  end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def force(args)
         | 
| 10 | 
            +
                    options = _default_options
         | 
| 11 | 
            +
                    opt_parser = _extend_default_parser(options) do |opts|
         | 
| 12 | 
            +
                      opts.banner = "Usage: litmusctl force <up|down> [service] [options]"
         | 
| 13 | 
            +
                      opts.on("-d", "--delete", "Remove status file") do
         | 
| 14 | 
            +
                        options[:delete] = true
         | 
| 15 | 
            +
                      end
         | 
| 16 | 
            +
                      opts.on("-r", "--reason=reason", String, "Reason for status file") do |reason|
         | 
| 17 | 
            +
                        options[:reason] = reason
         | 
| 18 | 
            +
                      end
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    opt_parser.parse! args
         | 
| 22 | 
            +
                    direction, service = args
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    if options[:delete]
         | 
| 25 | 
            +
                      request = Net::HTTP::Delete.new("/force/#{direction}/#{service}")
         | 
| 26 | 
            +
                    else
         | 
| 27 | 
            +
                      if !options.has_key?(:reason)
         | 
| 28 | 
            +
                        print "Reason? "
         | 
| 29 | 
            +
                        options[:reason] = gets.chomp
         | 
| 30 | 
            +
                      end
         | 
| 31 | 
            +
                      request = Net::HTTP::Post.new("/force/#{direction}/#{service}")
         | 
| 32 | 
            +
                      request.set_form_data('reason' => options[:reason])
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    _litmus_request(options[:host], options[:port], request)
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def status(args)
         | 
| 39 | 
            +
                    options = _default_options
         | 
| 40 | 
            +
                    opt_parser = _extend_default_parser(options) do |opts|
         | 
| 41 | 
            +
                      opts.banner = "Usage: litmusctl status <service> [options]"
         | 
| 42 | 
            +
                    end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                    opt_parser.parse! args
         | 
| 45 | 
            +
                    service = args.shift
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                    _litmus_request(options[:host], options[:port], Net::HTTP::Get.new("/#{service}/status"))
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  def _default_options
         | 
| 51 | 
            +
                    options = { :port => 9292, :host => 'localhost' }
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  def _extend_default_parser(options, &block)
         | 
| 55 | 
            +
                    OptionParser.new do |opts|
         | 
| 56 | 
            +
                      block.call(opts)
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                      opts.on("-p", "--port=port", Integer, "Port litmus is running on", "Default: 9292") do |port|
         | 
| 59 | 
            +
                        options[:port] = port
         | 
| 60 | 
            +
                      end
         | 
| 61 | 
            +
                      opts.on("-h", "--host=ip", String, ":Host litmus is running on", "Default: localhost") do |host|
         | 
| 62 | 
            +
                        options[:host] = host
         | 
| 63 | 
            +
                      end
         | 
| 64 | 
            +
                      opts.on("--help", "Show this help message.") { puts opts; exit }
         | 
| 65 | 
            +
                    end
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  def _litmus_request(host, port, request)
         | 
| 69 | 
            +
                    begin
         | 
| 70 | 
            +
                      http = Net::HTTP.start(host, port)
         | 
| 71 | 
            +
                      response = http.request(request)
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                      puts response.body
         | 
| 74 | 
            +
                      case response
         | 
| 75 | 
            +
                      when Net::HTTPSuccess then exit 0
         | 
| 76 | 
            +
                      when Net::HTTPClientError then exit 2
         | 
| 77 | 
            +
                      else exit 1
         | 
| 78 | 
            +
                      end
         | 
| 79 | 
            +
                    rescue Errno::ECONNREFUSED => e
         | 
| 80 | 
            +
                      puts "Unable to connect to litmus on #{host}:#{port}: #{e.message}"
         | 
| 81 | 
            +
                      exit 1
         | 
| 82 | 
            +
                    end
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
              end
         | 
| 86 | 
            +
            end
         | 
| @@ -0,0 +1,62 @@ | |
| 1 | 
            +
            module IPVSLitmus
         | 
| 2 | 
            +
              module CLI
         | 
| 3 | 
            +
                class Server < Rack::Server
         | 
| 4 | 
            +
                  class Options
         | 
| 5 | 
            +
                    def parse!(args)
         | 
| 6 | 
            +
                      args, options = args.dup, {}
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                      opt_parser = OptionParser.new do |opts|
         | 
| 9 | 
            +
                        opts.banner = "Usage: litmus [mongrel, thin, etc] [options]"
         | 
| 10 | 
            +
                        opts.on("-c", "--config=file", String,
         | 
| 11 | 
            +
                                "Litmus configuration file", "Default: /etc/litmus.conf") { |v| options[:litmus_config] = v }
         | 
| 12 | 
            +
                        opts.on("-D", "--data-dir=path", String,
         | 
| 13 | 
            +
                                "Litmus data directory", "Default: /etc/litmus") { |v| options[:config_dir] = v }
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                        opts.separator ""
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                        opts.on("-p", "--port=port", Integer,
         | 
| 18 | 
            +
                                "Runs Litmus on the specified port.", "Default: 9292") { |v| options[:Port] = v }
         | 
| 19 | 
            +
                        opts.on("-b", "--binding=ip", String,
         | 
| 20 | 
            +
                                "Binds Litmus to the specified ip.", "Default: 0.0.0.0") { |v| options[:Host] = v }
         | 
| 21 | 
            +
                        opts.on("-d", "--daemon", "Make server run as a Daemon.") { options[:daemonize] = true }
         | 
| 22 | 
            +
                        opts.on("-P","--pid=pid",String,
         | 
| 23 | 
            +
                                "Specifies the PID file.",
         | 
| 24 | 
            +
                                "Default: rack.pid") { |v| options[:pid] = v }
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                        opts.separator ""
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                        opts.on("-h", "--help", "Show this help message.") { puts opts; exit }
         | 
| 29 | 
            +
                      end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                      opt_parser.parse! args
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                      options[:config] = File.expand_path("../../../config.ru", File.dirname(__FILE__))
         | 
| 34 | 
            +
                      options[:server] = args.shift
         | 
| 35 | 
            +
                      options
         | 
| 36 | 
            +
                    end
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  def opt_parser
         | 
| 40 | 
            +
                    Options.new
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  def start
         | 
| 44 | 
            +
                    if !File.exists?(options[:litmus_config])
         | 
| 45 | 
            +
                      puts "Could not find #{options[:litmus_config]}. Specify correct location with -c file"
         | 
| 46 | 
            +
                      exit 1
         | 
| 47 | 
            +
                    end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    IPVSLitmus.configure(options[:litmus_config])
         | 
| 50 | 
            +
                    IPVSLitmus.config_dir = options[:config_dir]
         | 
| 51 | 
            +
                    super
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  def default_options
         | 
| 55 | 
            +
                    super.merge(
         | 
| 56 | 
            +
                      :litmus_config => '/etc/litmus.conf',
         | 
| 57 | 
            +
                      :config_dir => '/etc/litmus'
         | 
| 58 | 
            +
                    )
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
              end
         | 
| 62 | 
            +
            end
         | 
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            module IPVSLitmus
         | 
| 2 | 
            +
              class Configuration
         | 
| 3 | 
            +
                def initialize(config_file_path)
         | 
| 4 | 
            +
                  @config_file_path = config_file_path
         | 
| 5 | 
            +
                end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def evaluate
         | 
| 8 | 
            +
                  config_contents = File.read(@config_file_path)
         | 
| 9 | 
            +
                  @services = {}
         | 
| 10 | 
            +
                  instance_eval(config_contents)
         | 
| 11 | 
            +
                  @services
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def service(name, &block)
         | 
| 15 | 
            +
                  service = Service.new(name.to_s)
         | 
| 16 | 
            +
                  block.call(service)
         | 
| 17 | 
            +
                  @services[name.to_s] = service
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
            end
         | 
| @@ -0,0 +1,27 @@ | |
| 1 | 
            +
            module IPVSLitmus
         | 
| 2 | 
            +
              module Dependency
         | 
| 3 | 
            +
                class HTTP
         | 
| 4 | 
            +
                  def initialize(uri, options = {})
         | 
| 5 | 
            +
                    @uri = uri
         | 
| 6 | 
            +
                    @expected_content = Regexp.new(options.fetch(:contnet, '.*'))
         | 
| 7 | 
            +
                  end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def available?
         | 
| 10 | 
            +
                    begin
         | 
| 11 | 
            +
                      response = Net::HTTP.get_response(URI.parse(@uri))
         | 
| 12 | 
            +
                      _successful_response?(response) && _body_matches?(response)
         | 
| 13 | 
            +
                    rescue Exception => e
         | 
| 14 | 
            +
                      false
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def _successful_response?(response)
         | 
| 19 | 
            +
                    response.is_a? Net::HTTPSuccess
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def _body_matches?(response)
         | 
| 23 | 
            +
                    response.body =~ @expected_content
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
            end
         | 
| @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            module IPVSLitmus
         | 
| 2 | 
            +
              class Health
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                attr_reader :summary
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                def initialize
         | 
| 7 | 
            +
                  @value = 0
         | 
| 8 | 
            +
                  @dependencies_available = true
         | 
| 9 | 
            +
                  @summary = ""
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def ok?
         | 
| 13 | 
            +
                  value > 0
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def value
         | 
| 17 | 
            +
                  return 0 unless @dependencies_available
         | 
| 18 | 
            +
                  @value
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def perform(metric)
         | 
| 22 | 
            +
                  health = metric.current_health
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  @value += health
         | 
| 25 | 
            +
                  @summary << "#{metric.class}: #{health}\n"
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def ensure(dependency)
         | 
| 29 | 
            +
                  available = dependency.available?
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  @dependencies_available &&= available
         | 
| 32 | 
            +
                  @summary << "#{dependency.class}: #{available ? 'OK' : 'FAIL'}\n"
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
            end
         | 
| @@ -0,0 +1,32 @@ | |
| 1 | 
            +
            module IPVSLitmus
         | 
| 2 | 
            +
              module Metric
         | 
| 3 | 
            +
                class AvailableMemory
         | 
| 4 | 
            +
                  MULTIPLIER = {
         | 
| 5 | 
            +
                    "GB" => 1024*1024*1024,
         | 
| 6 | 
            +
                    "MB" => 1024*1024,
         | 
| 7 | 
            +
                    "KB" => 1024
         | 
| 8 | 
            +
                  }
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def initialize(weight, facter = Facter)
         | 
| 11 | 
            +
                    @weight = weight
         | 
| 12 | 
            +
                    @facter = facter
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def current_health
         | 
| 16 | 
            +
                    @weight * memory_free / memory_total
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  def memory_total
         | 
| 20 | 
            +
                    return @memory_total unless @memory_total.nil?
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                    size, scale = @facter.value('memorytotal').split(' ')
         | 
| 23 | 
            +
                    @memory_total = size.to_i * MULTIPLIER[scale]
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def memory_free
         | 
| 27 | 
            +
                    size, scale = @facter.value('memoryfree').split(' ')
         | 
| 28 | 
            +
                    size.to_i * MULTIPLIER[scale]
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
            end
         | 
| @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            module IPVSLitmus
         | 
| 2 | 
            +
              module Metric
         | 
| 3 | 
            +
                class CPULoad
         | 
| 4 | 
            +
                  def initialize(weight, facter = Facter)
         | 
| 5 | 
            +
                    @weight = weight
         | 
| 6 | 
            +
                    @facter = facter
         | 
| 7 | 
            +
                  end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def current_health
         | 
| 10 | 
            +
                    [@weight - (@weight * load_average / processor_count), 0].max
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def processor_count
         | 
| 14 | 
            +
                    @processor_count ||= @facter.value('processorcount').to_i
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def load_average
         | 
| 18 | 
            +
                    @facter.value('loadaverage').split(' ').first.to_f
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         | 
| @@ -0,0 +1,51 @@ | |
| 1 | 
            +
            module IPVSLitmus
         | 
| 2 | 
            +
              class Service
         | 
| 3 | 
            +
                def initialize(name, dependencies = [], checks = [])
         | 
| 4 | 
            +
                  @name = name
         | 
| 5 | 
            +
                  @dependencies = dependencies
         | 
| 6 | 
            +
                  @checks = checks
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def success?
         | 
| 10 | 
            +
                  health > 0
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def current_health
         | 
| 14 | 
            +
                  forced_health = _determine_forced_health
         | 
| 15 | 
            +
                  return forced_health unless forced_health.nil?
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  health = IPVSLitmus::Health.new
         | 
| 18 | 
            +
                  @dependencies.each do |dependency|
         | 
| 19 | 
            +
                    health.ensure(dependency)
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  @checks.each do |check|
         | 
| 23 | 
            +
                    health.perform(check)
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                  health
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def measure_health(metric_class, options)
         | 
| 29 | 
            +
                  @checks << metric_class.new(options[:weight])
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def depends(dependency_class, *args)
         | 
| 33 | 
            +
                  @dependencies << dependency_class.new(*args)
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                def _health_files
         | 
| 37 | 
            +
                  @health_files ||= [
         | 
| 38 | 
            +
                    [0, IPVSLitmus::StatusFile.new('down', @name)],
         | 
| 39 | 
            +
                    [100, IPVSLitmus::StatusFile.new('up', @name)],
         | 
| 40 | 
            +
                    [0, IPVSLitmus::StatusFile.new('global_down')],
         | 
| 41 | 
            +
                    [100, IPVSLitmus::StatusFile.new('global_up')]
         | 
| 42 | 
            +
                  ]
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                def _determine_forced_health
         | 
| 46 | 
            +
                  _health_files.map do |health, status_file|
         | 
| 47 | 
            +
                    ForcedHealth.new(health, status_file.content) if status_file.exists?
         | 
| 48 | 
            +
                  end.compact.first
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
            end
         | 
| @@ -0,0 +1,26 @@ | |
| 1 | 
            +
            module IPVSLitmus
         | 
| 2 | 
            +
              class StatusFile
         | 
| 3 | 
            +
                def initialize(*filenames)
         | 
| 4 | 
            +
                  @path = File.join(IPVSLitmus.config_dir, *filenames)
         | 
| 5 | 
            +
                end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def content
         | 
| 8 | 
            +
                  File.read(@path).chomp
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def create(reason)
         | 
| 12 | 
            +
                  FileUtils.mkdir_p(File.dirname(@path))
         | 
| 13 | 
            +
                  File.open(@path, 'w') do |file|
         | 
| 14 | 
            +
                    file.puts(reason)
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def delete
         | 
| 19 | 
            +
                  FileUtils.rm(@path)
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def exists?
         | 
| 23 | 
            +
                  File.exists?(@path)
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
            end
         | 
    
        data/lib/ipvs_litmus.rb
    ADDED
    
    | @@ -0,0 +1,44 @@ | |
| 1 | 
            +
            require 'pathname'
         | 
| 2 | 
            +
            require 'net/http'
         | 
| 3 | 
            +
            require 'uri'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            require 'bundler/setup'
         | 
| 6 | 
            +
            require 'sinatra/base'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            require 'facter'
         | 
| 9 | 
            +
            require 'facts/loadaverage'
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            require 'ipvs_litmus/app'
         | 
| 12 | 
            +
            require 'ipvs_litmus/configuration'
         | 
| 13 | 
            +
            require 'ipvs_litmus/dependency/http'
         | 
| 14 | 
            +
            require 'ipvs_litmus/health'
         | 
| 15 | 
            +
            require 'ipvs_litmus/forced_health'
         | 
| 16 | 
            +
            require 'ipvs_litmus/metric/available_memory'
         | 
| 17 | 
            +
            require 'ipvs_litmus/metric/cpu_load'
         | 
| 18 | 
            +
            require 'ipvs_litmus/service'
         | 
| 19 | 
            +
            require 'ipvs_litmus/status_file'
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            module IPVSLitmus
         | 
| 22 | 
            +
              class << self
         | 
| 23 | 
            +
                attr_reader :services, :config_dir
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              def self.configure(filename)
         | 
| 27 | 
            +
                @config_file = filename
         | 
| 28 | 
            +
                @services = IPVSLitmus::Configuration.new(filename).evaluate
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
              def self.config_dir=(path)
         | 
| 32 | 
            +
                @config_dir = Pathname.new(path)
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
              def self.reload
         | 
| 36 | 
            +
                configure(@config_file)
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              def self.reset
         | 
| 40 | 
            +
                @services = {}
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
            end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            Signal.trap("HUP") { IPVSLitmus.reload }
         |