sp-nat-monitor 1.0.9
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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +55 -0
- data/Rakefile +1 -0
- data/bin/nat-monitor +5 -0
- data/lib/nat-monitor/version.rb +5 -0
- data/lib/nat-monitor.rb +160 -0
- data/nat-monitor.gemspec +29 -0
- data/spec/lib/nat-monitor_spec.rb +197 -0
- data/spec/spec_helper.rb +4 -0
- metadata +157 -0
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA1:
         | 
| 3 | 
            +
              metadata.gz: 5ded3db6cc435026f25c6276fbe1d6a0fdff521a
         | 
| 4 | 
            +
              data.tar.gz: d57e17569e4f95473c312fa544fdff62fa9953fb
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: c823c3af135b0cd57f622bf3a3c784d701f8947cb254f32410c3ad93d11a78cd6553932c3cfb33067add7f05dea3cadf5008ea2106d275ee11800bd69046c9e3
         | 
| 7 | 
            +
              data.tar.gz: 33cbd2c344b42165bb391bbfcab2da8a943e177102c33c84367980250532ff0b51ff621220545b1a92baa4d2795c2f5996acdcedb8bd58c410fb080b05ab3d18
         | 
    
        data/.gitignore
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    
    
        data/LICENSE.txt
    ADDED
    
    | @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            Copyright (c) 2015 Eric Herot
         | 
| 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,55 @@ | |
| 1 | 
            +
            # nat-monitor
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Monitors a quorum of NAT servers for an outage and reassigns the specified EC2 route table to point to a working server.
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            ## Installation
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            Add this line to your application's Gemfile:
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            ```ruby
         | 
| 10 | 
            +
            gem 'nat-monitor'
         | 
| 11 | 
            +
            ```
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            And then execute:
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                $ bundle
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            Or install it yourself as:
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                $ gem install nat-monitor
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            ## Usage
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            Run it as a service after creating the configuration YAML file.
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            E.g.:
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                $ nat-monitor [OPTIONAL CONF_FILE]
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            By default it will check `/etc/nat_monitor.yml` for its configuration.
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            ## Example Configuration
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            ```yaml
         | 
| 34 | 
            +
            ---
         | 
| 35 | 
            +
            route_table_id: rtb-00000001
         | 
| 36 | 
            +
            nodes:
         | 
| 37 | 
            +
              i-00000001: 10.0.0.1
         | 
| 38 | 
            +
              i-00000002: 10.0.1.1
         | 
| 39 | 
            +
              i-00000003: 10.0.2.1
         | 
| 40 | 
            +
            ```
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            Optional properties include:
         | 
| 43 | 
            +
            ```yaml
         | 
| 44 | 
            +
            pings: 3
         | 
| 45 | 
            +
            ping_timeout: 1
         | 
| 46 | 
            +
            heartbeat_interval: 10
         | 
| 47 | 
            +
            ```
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            ## Contributing
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            1. Fork it ( https://github.com/[my-github-username]/nat-monitor/fork )
         | 
| 52 | 
            +
            2. Create your feature branch (`git checkout -b my-new-feature`)
         | 
| 53 | 
            +
            3. Commit your changes (`git commit -am 'Add some feature'`)
         | 
| 54 | 
            +
            4. Push to the branch (`git push origin my-new-feature`)
         | 
| 55 | 
            +
            5. Create a new Pull Request
         | 
    
        data/Rakefile
    ADDED
    
    | @@ -0,0 +1 @@ | |
| 1 | 
            +
            require 'bundler/gem_tasks'
         | 
    
        data/bin/nat-monitor
    ADDED
    
    
    
        data/lib/nat-monitor.rb
    ADDED
    
    | @@ -0,0 +1,160 @@ | |
| 1 | 
            +
            module EtTools
         | 
| 2 | 
            +
              class NatMonitor
         | 
| 3 | 
            +
                require 'net/http'
         | 
| 4 | 
            +
                require 'net/ping'
         | 
| 5 | 
            +
                require 'fog'
         | 
| 6 | 
            +
                require 'yaml'
         | 
| 7 | 
            +
                require 'syslog'
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def initialize(conf_file = nil)
         | 
| 10 | 
            +
                  @conf = defaults.merge load_conf(conf_file)
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def load_conf(conf_file = nil)
         | 
| 14 | 
            +
                  YAML.load_file(conf_file || '/etc/nat_monitor.yml')
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def run
         | 
| 18 | 
            +
                  validate!
         | 
| 19 | 
            +
                  output 'Starting NAT Monitor'
         | 
| 20 | 
            +
                  main_loop
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def validate!
         | 
| 24 | 
            +
                  case
         | 
| 25 | 
            +
                  when !@conf['route_table_id']
         | 
| 26 | 
            +
                    output 'route_table_id not specified'
         | 
| 27 | 
            +
                    exit 1
         | 
| 28 | 
            +
                  when !route_exists?(@conf['route_table_id'])
         | 
| 29 | 
            +
                    output "Route #{@conf['route_table_id']} not found"
         | 
| 30 | 
            +
                    exit 2
         | 
| 31 | 
            +
                  when @conf['nodes'].count < 2
         | 
| 32 | 
            +
                    output '2 or more nodes are required to create a quorum'
         | 
| 33 | 
            +
                    exit 3
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                def defaults
         | 
| 38 | 
            +
                  { 'pings' => 3,
         | 
| 39 | 
            +
                    'ping_timeout' => 1,
         | 
| 40 | 
            +
                    'heartbeat_interval' => 10 }
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def main_loop
         | 
| 44 | 
            +
                  loop do
         | 
| 45 | 
            +
                    begin
         | 
| 46 | 
            +
                      heartbeat
         | 
| 47 | 
            +
                    rescue => e
         | 
| 48 | 
            +
                      output "Caught #{e.class} exception: #{e.message}"
         | 
| 49 | 
            +
                      output e.backtrace
         | 
| 50 | 
            +
                    end
         | 
| 51 | 
            +
                    sleep @conf['heartbeat_interval']
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                def heartbeat
         | 
| 56 | 
            +
                  if am_i_master?
         | 
| 57 | 
            +
                    output "Looks like I'm the master"
         | 
| 58 | 
            +
                    return
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
                  un = unreachable_nodes
         | 
| 61 | 
            +
                  return if un.empty?
         | 
| 62 | 
            +
                  if un.count == other_nodes.keys.count # return if I'm unreachable...
         | 
| 63 | 
            +
                    output "No nodes are reachable. Seems I'm the unreachable one."
         | 
| 64 | 
            +
                    return
         | 
| 65 | 
            +
                  end
         | 
| 66 | 
            +
                  cm = current_master
         | 
| 67 | 
            +
                  unless un.include?(cm) # ...unless master is unreachable
         | 
| 68 | 
            +
                    output "Unreachable nodes: #{un.inspect}"
         | 
| 69 | 
            +
                    output "Current master (#{cm}) is still reachable"
         | 
| 70 | 
            +
                    return
         | 
| 71 | 
            +
                  end
         | 
| 72 | 
            +
                  steal_route
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                def steal_route
         | 
| 76 | 
            +
                  output 'Stealing route 0.0.0.0/0 on route table ' \
         | 
| 77 | 
            +
                         "#{@conf['route_table_id']}"
         | 
| 78 | 
            +
                  return if @conf['mocking']
         | 
| 79 | 
            +
                  connection.replace_route(
         | 
| 80 | 
            +
                    @conf['route_table_id'],
         | 
| 81 | 
            +
                    '0.0.0.0/0',
         | 
| 82 | 
            +
                    'InstanceId' => my_instance_id
         | 
| 83 | 
            +
                  )
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                def unreachable_nodes
         | 
| 87 | 
            +
                  other_nodes.select { |_node, ip| !pingable?(ip) }
         | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                def other_nodes
         | 
| 91 | 
            +
                  @other_nodes ||= begin
         | 
| 92 | 
            +
                    nodes = @conf['nodes'].dup
         | 
| 93 | 
            +
                    nodes.delete my_instance_id
         | 
| 94 | 
            +
                    nodes
         | 
| 95 | 
            +
                  end
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                def pingable?(ip)
         | 
| 99 | 
            +
                  p = Net::Ping::External.new(ip)
         | 
| 100 | 
            +
                  p.timeout = @conf['ping_timeout']
         | 
| 101 | 
            +
                  p.ping?
         | 
| 102 | 
            +
                end
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                def route_exists?(route_id)
         | 
| 105 | 
            +
                  connection.route_tables.map(&:id).include? route_id
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                def connection
         | 
| 109 | 
            +
                  @connection ||= begin
         | 
| 110 | 
            +
                    if @conf['aws_access_key_id']
         | 
| 111 | 
            +
                      options = { aws_access_key_id: @conf['aws_access_key_id'],
         | 
| 112 | 
            +
                                  aws_secret_access_key: @conf['aws_secret_access_key'] }
         | 
| 113 | 
            +
                    else
         | 
| 114 | 
            +
                      options = { use_iam_profile: true }
         | 
| 115 | 
            +
                    end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                    options[:endpoint] = @conf['aws_url'] if @conf['aws_url']
         | 
| 118 | 
            +
                    Fog::Compute::AWS.new(options)
         | 
| 119 | 
            +
                  end
         | 
| 120 | 
            +
                end
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                def current_master
         | 
| 123 | 
            +
                  default_r =
         | 
| 124 | 
            +
                    connection.route_tables.get(@conf['route_table_id']).routes.find do |r|
         | 
| 125 | 
            +
                      r['destinationCidrBlock'] == '0.0.0.0/0'
         | 
| 126 | 
            +
                    end
         | 
| 127 | 
            +
                  default_r['instanceId']
         | 
| 128 | 
            +
                end
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                def my_instance_id
         | 
| 131 | 
            +
                  @my_instance_id ||= begin
         | 
| 132 | 
            +
                    Net::HTTP.get(
         | 
| 133 | 
            +
                      '169.254.169.254',
         | 
| 134 | 
            +
                      '/latest/meta-data/instance-id'
         | 
| 135 | 
            +
                    )
         | 
| 136 | 
            +
                  end
         | 
| 137 | 
            +
                end
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                def am_i_master?
         | 
| 140 | 
            +
                  master_node? my_instance_id
         | 
| 141 | 
            +
                end
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                def master_node?(node_id)
         | 
| 144 | 
            +
                  current_master == node_id
         | 
| 145 | 
            +
                end
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                private
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                def output(message)
         | 
| 150 | 
            +
                  puts message
         | 
| 151 | 
            +
                  log message
         | 
| 152 | 
            +
                end
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                def log(message, level = 'info')
         | 
| 155 | 
            +
                  Syslog.open('nat-monitor', Syslog::LOG_PID | Syslog::LOG_CONS) do |s|
         | 
| 156 | 
            +
                    s.send(level, message)
         | 
| 157 | 
            +
                  end
         | 
| 158 | 
            +
                end
         | 
| 159 | 
            +
              end
         | 
| 160 | 
            +
            end
         | 
    
        data/nat-monitor.gemspec
    ADDED
    
    | @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            # coding: utf-8
         | 
| 2 | 
            +
            lib = File.expand_path('../lib', __FILE__)
         | 
| 3 | 
            +
            $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
         | 
| 4 | 
            +
            require 'nat-monitor/version'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            Gem::Specification.new do |spec|
         | 
| 7 | 
            +
              spec.name          = 'sp-nat-monitor'
         | 
| 8 | 
            +
              spec.version       = EtTools::NatMonitor::VERSION
         | 
| 9 | 
            +
              spec.authors       = ['Eric Herot']
         | 
| 10 | 
            +
              spec.email         = ['eric.github@herot.com']
         | 
| 11 | 
            +
              spec.summary       = 'A service for providing an HA NAT in EC2'
         | 
| 12 | 
            +
              spec.description   = spec.summary
         | 
| 13 | 
            +
              spec.homepage      = ''
         | 
| 14 | 
            +
              spec.license       = 'Apache 2.0'
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              spec.files         = `git ls-files -z`.split("\x0")
         | 
| 17 | 
            +
              spec.executables   = spec.files.grep(/^bin\//) { |f| File.basename(f) }
         | 
| 18 | 
            +
              spec.test_files    = spec.files.grep(/^(test|spec|features)\//)
         | 
| 19 | 
            +
              spec.require_paths = ['lib']
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              spec.add_development_dependency 'rspec'
         | 
| 22 | 
            +
              spec.add_development_dependency 'simplecov'
         | 
| 23 | 
            +
              spec.add_development_dependency 'byebug'
         | 
| 24 | 
            +
              spec.add_development_dependency 'bundler', '~> 1.7'
         | 
| 25 | 
            +
              spec.add_development_dependency 'rake', '~> 10.0'
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              spec.add_runtime_dependency 'net-ping'
         | 
| 28 | 
            +
              spec.add_runtime_dependency 'fog', '~> 1.23'
         | 
| 29 | 
            +
            end
         | 
| @@ -0,0 +1,197 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
            require 'byebug'
         | 
| 3 | 
            +
            require 'nat-monitor'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            describe EtTools::NatMonitor do
         | 
| 6 | 
            +
              before(:each) do
         | 
| 7 | 
            +
                @route_table_id = 'rtb-00000000'
         | 
| 8 | 
            +
                @my_instance_id = 'i-00000001'
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                @other_nodes = { 'i-00000002' => '1.1.1.2',
         | 
| 11 | 
            +
                                 'i-00000003' => '1.1.1.3' }
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                @yaml_conf = { 'route_table_id' => @route_table_id,
         | 
| 14 | 
            +
                               'aws_access_key_id' => 'AWS_ACCESS_KEY_ID',
         | 
| 15 | 
            +
                               'aws_secret_access_key' => 'AWS_SECRET_ACCESS_KEY',
         | 
| 16 | 
            +
                               'nodes' => (
         | 
| 17 | 
            +
                                 { @my_instance_id => '1.1.1.1' }
         | 
| 18 | 
            +
                               ).merge(@other_nodes) }
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                filepath = 'bogus_filename.yml'
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                allow_any_instance_of(EtTools::NatMonitor).to receive(:load_conf)
         | 
| 23 | 
            +
                  .with(filepath).and_return(@yaml_conf)
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                @nat_monitor = EtTools::NatMonitor.new(filepath)
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                @defaults = { 'pings' => 3,
         | 
| 28 | 
            +
                              'ping_timeout' => 1,
         | 
| 29 | 
            +
                              'heartbeat_interval' => 10 }
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                allow(@nat_monitor).to receive(:my_instance_id).and_return(@my_instance_id)
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              context 'fewer than 3 nodes are specified' do
         | 
| 35 | 
            +
                before do
         | 
| 36 | 
            +
                  allow(YAML).to receive(:load_file).with(any_args)
         | 
| 37 | 
            +
                  @nat_monitor.instance_variable_set(
         | 
| 38 | 
            +
                    :@conf,
         | 
| 39 | 
            +
                    ({ 'route_table_id' => @route_table_id,
         | 
| 40 | 
            +
                       'aws_access_key_id' => 'AWS_ACCESS_KEY_ID',
         | 
| 41 | 
            +
                       'aws_secret_access_key' => 'AWS_SECRET_ACCESS_KEY',
         | 
| 42 | 
            +
                       'nodes' => @other_nodes })
         | 
| 43 | 
            +
                  )
         | 
| 44 | 
            +
                  allow(@nat_monitor).to receive(:route_exists?).with(any_args)
         | 
| 45 | 
            +
                    .and_return true
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                it 'exits with status 3' do
         | 
| 49 | 
            +
                  expect(@nat_monitor).to receive(:exit).with(3)
         | 
| 50 | 
            +
                  @nat_monitor.validate!
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
              context 'invalid route is specified' do
         | 
| 55 | 
            +
                before do
         | 
| 56 | 
            +
                  expect(@nat_monitor.connection).to receive(:route_tables)
         | 
| 57 | 
            +
                    .and_return [
         | 
| 58 | 
            +
                      double('route_tables',
         | 
| 59 | 
            +
                             id: 'rtb-99999999')
         | 
| 60 | 
            +
                    ]
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                it 'exits with status 2' do
         | 
| 64 | 
            +
                  expect(@nat_monitor).to receive(:exit).with(2)
         | 
| 65 | 
            +
                  @nat_monitor.validate!
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
              end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
              context 'no route is specified' do
         | 
| 70 | 
            +
                before do
         | 
| 71 | 
            +
                  @nat_monitor.instance_variable_set(
         | 
| 72 | 
            +
                    :@conf,
         | 
| 73 | 
            +
                    'nodes' => ({ @my_instance_id => '1.1.1.1' }).merge(@other_nodes)
         | 
| 74 | 
            +
                  )
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                it 'exits with status 1' do
         | 
| 78 | 
            +
                  expect(@nat_monitor).to receive(:exit).with(1)
         | 
| 79 | 
            +
                  @nat_monitor.validate!
         | 
| 80 | 
            +
                end
         | 
| 81 | 
            +
              end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
              context 'local node is the master' do
         | 
| 84 | 
            +
                before do
         | 
| 85 | 
            +
                  allow(@nat_monitor).to(
         | 
| 86 | 
            +
                    receive(:current_master).and_return(@my_instance_id)
         | 
| 87 | 
            +
                  )
         | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                it 'sets @conf correctly' do
         | 
| 91 | 
            +
                  expect(@nat_monitor.instance_variable_get(:@conf)).to eq(
         | 
| 92 | 
            +
                    @yaml_conf.merge(@defaults)
         | 
| 93 | 
            +
                  )
         | 
| 94 | 
            +
                end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                it 'and knows that it is master' do
         | 
| 97 | 
            +
                  expect(@nat_monitor).to receive(:am_i_master?).and_return(true)
         | 
| 98 | 
            +
                  @nat_monitor.heartbeat
         | 
| 99 | 
            +
                end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                it 'and does not check for unreachable nodes' do
         | 
| 102 | 
            +
                  expect(@nat_monitor).to_not receive(:unreachable_nodes)
         | 
| 103 | 
            +
                  @nat_monitor.heartbeat
         | 
| 104 | 
            +
                end
         | 
| 105 | 
            +
              end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
              context 'local node is not master' do
         | 
| 108 | 
            +
                before do
         | 
| 109 | 
            +
                  allow(@nat_monitor).to(
         | 
| 110 | 
            +
                    receive(:current_master).and_return('i-00000002')
         | 
| 111 | 
            +
                  )
         | 
| 112 | 
            +
                end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                context 'and can ping everything' do
         | 
| 115 | 
            +
                  before do
         | 
| 116 | 
            +
                    allow(@nat_monitor).to receive(:pingable?).with(any_args)
         | 
| 117 | 
            +
                      .and_return true
         | 
| 118 | 
            +
                  end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                  it 'does not try to steal the route' do
         | 
| 121 | 
            +
                    expect(@nat_monitor).to_not receive(:steal_route)
         | 
| 122 | 
            +
                    @nat_monitor.heartbeat
         | 
| 123 | 
            +
                  end
         | 
| 124 | 
            +
                end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                context 'and can\'t ping anything' do
         | 
| 127 | 
            +
                  before do
         | 
| 128 | 
            +
                    allow(@nat_monitor).to receive(:pingable?).with(any_args)
         | 
| 129 | 
            +
                      .and_return false
         | 
| 130 | 
            +
                  end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                  it 'counts unreachable nodes correctly' do
         | 
| 133 | 
            +
                    expect(@nat_monitor).to receive(:unreachable_nodes)
         | 
| 134 | 
            +
                      .and_return(@other_nodes)
         | 
| 135 | 
            +
                    @nat_monitor.heartbeat
         | 
| 136 | 
            +
                  end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                  it 'does not try to steal the route' do
         | 
| 139 | 
            +
                    expect(@nat_monitor).to receive(:unreachable_nodes)
         | 
| 140 | 
            +
                      .and_return(@other_nodes)
         | 
| 141 | 
            +
                    expect(@nat_monitor).to_not receive(:steal_route)
         | 
| 142 | 
            +
                    @nat_monitor.heartbeat
         | 
| 143 | 
            +
                  end
         | 
| 144 | 
            +
                end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                context 'and only can\'t ping the master' do
         | 
| 147 | 
            +
                  before do
         | 
| 148 | 
            +
                    allow(@nat_monitor).to receive(:pingable?).with('1.1.1.2')
         | 
| 149 | 
            +
                      .and_return false
         | 
| 150 | 
            +
                    allow(@nat_monitor).to receive(:pingable?).with('1.1.1.3')
         | 
| 151 | 
            +
                      .and_return true
         | 
| 152 | 
            +
                  end
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                  it 'computes the list of other nodes correctly' do
         | 
| 155 | 
            +
                    allow(@nat_monitor).to receive(:steal_route).and_return true
         | 
| 156 | 
            +
                    expect(@nat_monitor).to receive(:other_nodes)
         | 
| 157 | 
            +
                      .exactly(2).times.and_return(@other_nodes)
         | 
| 158 | 
            +
                    @nat_monitor.heartbeat
         | 
| 159 | 
            +
                  end
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                  it 'finds i-00000002 unreachable' do
         | 
| 162 | 
            +
                    allow(@nat_monitor).to receive(:steal_route).and_return true
         | 
| 163 | 
            +
                    expect(@nat_monitor).to receive(:unreachable_nodes)
         | 
| 164 | 
            +
                      .and_return('i-00000002' => '1.1.1.2')
         | 
| 165 | 
            +
                    @nat_monitor.heartbeat
         | 
| 166 | 
            +
                  end
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                  it 'tries to steal the route' do
         | 
| 169 | 
            +
                    expect(@nat_monitor).to receive(:steal_route)
         | 
| 170 | 
            +
                    @nat_monitor.heartbeat
         | 
| 171 | 
            +
                  end
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                  it 'sends correct replace route command' do
         | 
| 174 | 
            +
                    expect(@nat_monitor.connection).to receive(:replace_route)
         | 
| 175 | 
            +
                      .with(@route_table_id,
         | 
| 176 | 
            +
                            '0.0.0.0/0',
         | 
| 177 | 
            +
                            'InstanceId' => @my_instance_id)
         | 
| 178 | 
            +
                      .and_return true
         | 
| 179 | 
            +
                    @nat_monitor.heartbeat
         | 
| 180 | 
            +
                  end
         | 
| 181 | 
            +
                end
         | 
| 182 | 
            +
             | 
| 183 | 
            +
                context 'mocking and can\'t ping the master' do
         | 
| 184 | 
            +
                  before do
         | 
| 185 | 
            +
                    @nat_monitor.instance_variable_set(
         | 
| 186 | 
            +
                      :@conf,
         | 
| 187 | 
            +
                      @yaml_conf.merge('mocking' => true).merge(@defaults)
         | 
| 188 | 
            +
                    )
         | 
| 189 | 
            +
                  end
         | 
| 190 | 
            +
             | 
| 191 | 
            +
                  it 'does not steal the route when mocking is enabled' do
         | 
| 192 | 
            +
                    expect(@nat_monitor.connection).to_not receive(:replace_route)
         | 
| 193 | 
            +
                    @nat_monitor.heartbeat
         | 
| 194 | 
            +
                  end
         | 
| 195 | 
            +
                end
         | 
| 196 | 
            +
              end
         | 
| 197 | 
            +
            end
         | 
    
        data/spec/spec_helper.rb
    ADDED
    
    
    
        metadata
    ADDED
    
    | @@ -0,0 +1,157 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: sp-nat-monitor
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 1.0.9
         | 
| 5 | 
            +
            platform: ruby
         | 
| 6 | 
            +
            authors:
         | 
| 7 | 
            +
            - Eric Herot
         | 
| 8 | 
            +
            autorequire: 
         | 
| 9 | 
            +
            bindir: bin
         | 
| 10 | 
            +
            cert_chain: []
         | 
| 11 | 
            +
            date: 2015-05-27 00:00:00.000000000 Z
         | 
| 12 | 
            +
            dependencies:
         | 
| 13 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            +
              name: rspec
         | 
| 15 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            +
                requirements:
         | 
| 17 | 
            +
                - - ">="
         | 
| 18 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            +
                    version: '0'
         | 
| 20 | 
            +
              type: :development
         | 
| 21 | 
            +
              prerelease: false
         | 
| 22 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 | 
            +
                requirements:
         | 
| 24 | 
            +
                - - ">="
         | 
| 25 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            +
                    version: '0'
         | 
| 27 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 28 | 
            +
              name: simplecov
         | 
| 29 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 30 | 
            +
                requirements:
         | 
| 31 | 
            +
                - - ">="
         | 
| 32 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            +
                    version: '0'
         | 
| 34 | 
            +
              type: :development
         | 
| 35 | 
            +
              prerelease: false
         | 
| 36 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 | 
            +
                requirements:
         | 
| 38 | 
            +
                - - ">="
         | 
| 39 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            +
                    version: '0'
         | 
| 41 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 42 | 
            +
              name: byebug
         | 
| 43 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 | 
            +
                requirements:
         | 
| 45 | 
            +
                - - ">="
         | 
| 46 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 47 | 
            +
                    version: '0'
         | 
| 48 | 
            +
              type: :development
         | 
| 49 | 
            +
              prerelease: false
         | 
| 50 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 51 | 
            +
                requirements:
         | 
| 52 | 
            +
                - - ">="
         | 
| 53 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 54 | 
            +
                    version: '0'
         | 
| 55 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 56 | 
            +
              name: bundler
         | 
| 57 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 58 | 
            +
                requirements:
         | 
| 59 | 
            +
                - - "~>"
         | 
| 60 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 61 | 
            +
                    version: '1.7'
         | 
| 62 | 
            +
              type: :development
         | 
| 63 | 
            +
              prerelease: false
         | 
| 64 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 65 | 
            +
                requirements:
         | 
| 66 | 
            +
                - - "~>"
         | 
| 67 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 68 | 
            +
                    version: '1.7'
         | 
| 69 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 70 | 
            +
              name: rake
         | 
| 71 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 72 | 
            +
                requirements:
         | 
| 73 | 
            +
                - - "~>"
         | 
| 74 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 75 | 
            +
                    version: '10.0'
         | 
| 76 | 
            +
              type: :development
         | 
| 77 | 
            +
              prerelease: false
         | 
| 78 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 79 | 
            +
                requirements:
         | 
| 80 | 
            +
                - - "~>"
         | 
| 81 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 82 | 
            +
                    version: '10.0'
         | 
| 83 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 84 | 
            +
              name: net-ping
         | 
| 85 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 86 | 
            +
                requirements:
         | 
| 87 | 
            +
                - - ">="
         | 
| 88 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 89 | 
            +
                    version: '0'
         | 
| 90 | 
            +
              type: :runtime
         | 
| 91 | 
            +
              prerelease: false
         | 
| 92 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 93 | 
            +
                requirements:
         | 
| 94 | 
            +
                - - ">="
         | 
| 95 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 96 | 
            +
                    version: '0'
         | 
| 97 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 98 | 
            +
              name: fog
         | 
| 99 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 100 | 
            +
                requirements:
         | 
| 101 | 
            +
                - - "~>"
         | 
| 102 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 103 | 
            +
                    version: '1.23'
         | 
| 104 | 
            +
              type: :runtime
         | 
| 105 | 
            +
              prerelease: false
         | 
| 106 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 107 | 
            +
                requirements:
         | 
| 108 | 
            +
                - - "~>"
         | 
| 109 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 110 | 
            +
                    version: '1.23'
         | 
| 111 | 
            +
            description: A service for providing an HA NAT in EC2
         | 
| 112 | 
            +
            email:
         | 
| 113 | 
            +
            - eric.github@herot.com
         | 
| 114 | 
            +
            executables:
         | 
| 115 | 
            +
            - nat-monitor
         | 
| 116 | 
            +
            extensions: []
         | 
| 117 | 
            +
            extra_rdoc_files: []
         | 
| 118 | 
            +
            files:
         | 
| 119 | 
            +
            - ".gitignore"
         | 
| 120 | 
            +
            - Gemfile
         | 
| 121 | 
            +
            - LICENSE.txt
         | 
| 122 | 
            +
            - README.md
         | 
| 123 | 
            +
            - Rakefile
         | 
| 124 | 
            +
            - bin/nat-monitor
         | 
| 125 | 
            +
            - lib/nat-monitor.rb
         | 
| 126 | 
            +
            - lib/nat-monitor/version.rb
         | 
| 127 | 
            +
            - nat-monitor.gemspec
         | 
| 128 | 
            +
            - spec/lib/nat-monitor_spec.rb
         | 
| 129 | 
            +
            - spec/spec_helper.rb
         | 
| 130 | 
            +
            homepage: ''
         | 
| 131 | 
            +
            licenses:
         | 
| 132 | 
            +
            - Apache 2.0
         | 
| 133 | 
            +
            metadata: {}
         | 
| 134 | 
            +
            post_install_message: 
         | 
| 135 | 
            +
            rdoc_options: []
         | 
| 136 | 
            +
            require_paths:
         | 
| 137 | 
            +
            - lib
         | 
| 138 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 139 | 
            +
              requirements:
         | 
| 140 | 
            +
              - - ">="
         | 
| 141 | 
            +
                - !ruby/object:Gem::Version
         | 
| 142 | 
            +
                  version: '0'
         | 
| 143 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 144 | 
            +
              requirements:
         | 
| 145 | 
            +
              - - ">="
         | 
| 146 | 
            +
                - !ruby/object:Gem::Version
         | 
| 147 | 
            +
                  version: '0'
         | 
| 148 | 
            +
            requirements: []
         | 
| 149 | 
            +
            rubyforge_project: 
         | 
| 150 | 
            +
            rubygems_version: 2.2.2
         | 
| 151 | 
            +
            signing_key: 
         | 
| 152 | 
            +
            specification_version: 4
         | 
| 153 | 
            +
            summary: A service for providing an HA NAT in EC2
         | 
| 154 | 
            +
            test_files:
         | 
| 155 | 
            +
            - spec/lib/nat-monitor_spec.rb
         | 
| 156 | 
            +
            - spec/spec_helper.rb
         | 
| 157 | 
            +
            has_rdoc: 
         |