synapse 0.12.1 → 0.12.2
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/.travis.yml +1 -0
 - data/README.md +64 -55
 - data/lib/synapse.rb +6 -6
 - data/lib/synapse/file_output.rb +12 -1
 - data/lib/synapse/haproxy.rb +14 -4
 - data/lib/synapse/service_watcher.rb +11 -19
 - data/lib/synapse/service_watcher/README.md +84 -0
 - data/lib/synapse/service_watcher/base.rb +3 -3
 - data/lib/synapse/service_watcher/dns.rb +1 -1
 - data/lib/synapse/service_watcher/docker.rb +4 -3
 - data/lib/synapse/service_watcher/ec2tag.rb +13 -9
 - data/lib/synapse/service_watcher/marathon.rb +112 -0
 - data/lib/synapse/service_watcher/zookeeper.rb +12 -7
 - data/lib/synapse/service_watcher/zookeeper_dns.rb +3 -3
 - data/lib/synapse/version.rb +1 -1
 - data/spec/lib/synapse/file_output_spec.rb +61 -0
 - data/spec/lib/synapse/haproxy_spec.rb +14 -1
 - data/spec/lib/synapse/service_watcher_base_spec.rb +3 -3
 - data/spec/lib/synapse/service_watcher_docker_spec.rb +12 -6
 - data/spec/lib/synapse/service_watcher_ec2tags_spec.rb +36 -14
 - data/spec/lib/synapse/service_watcher_marathon_spec.rb +191 -0
 - data/spec/lib/synapse/service_watcher_spec.rb +102 -0
 - data/spec/spec_helper.rb +1 -0
 - data/spec/support/minimum.conf.yaml +6 -1
 - data/synapse.gemspec +1 -0
 - metadata +26 -2
 
| 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            require "synapse/service_watcher/base"
         
     | 
| 
       2 
2 
     | 
    
         
             
            require 'docker'
         
     | 
| 
       3 
3 
     | 
    
         | 
| 
       4 
     | 
    
         
            -
             
     | 
| 
      
 4 
     | 
    
         
            +
            class Synapse::ServiceWatcher
         
     | 
| 
       5 
5 
     | 
    
         
             
              class DockerWatcher < BaseWatcher
         
     | 
| 
       6 
6 
     | 
    
         
             
                def start
         
     | 
| 
       7 
7 
     | 
    
         
             
                  @check_interval = @discovery['check_interval'] || 15.0
         
     | 
| 
         @@ -74,10 +74,11 @@ module Synapse 
     | 
|
| 
       74 
74 
     | 
    
         
             
                    cnts.each do |cnt|
         
     | 
| 
       75 
75 
     | 
    
         
             
                      cnt['Ports'] = rewrite_container_ports cnt['Ports']
         
     | 
| 
       76 
76 
     | 
    
         
             
                    end
         
     | 
| 
       77 
     | 
    
         
            -
                    # Discover containers that match the image/port we're interested in
         
     | 
| 
      
 77 
     | 
    
         
            +
                    # Discover containers that match the image/port we're interested in and have the port mapped to the host
         
     | 
| 
       78 
78 
     | 
    
         
             
                    cnts = cnts.find_all do |cnt|
         
     | 
| 
       79 
79 
     | 
    
         
             
                      cnt["Image"].rpartition(":").first == @discovery["image_name"] \
         
     | 
| 
       80 
     | 
    
         
            -
                        and cnt["Ports"].has_key?(@discovery["container_port"].to_s())
         
     | 
| 
      
 80 
     | 
    
         
            +
                        and cnt["Ports"].has_key?(@discovery["container_port"].to_s()) \
         
     | 
| 
      
 81 
     | 
    
         
            +
                        and cnt["Ports"][@discovery["container_port"].to_s()].length > 0
         
     | 
| 
       81 
82 
     | 
    
         
             
                    end
         
     | 
| 
       82 
83 
     | 
    
         
             
                    cnts.map do |cnt|
         
     | 
| 
       83 
84 
     | 
    
         
             
                      {
         
     | 
| 
         @@ -1,8 +1,8 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            require 'synapse/service_watcher/base'
         
     | 
| 
       2 
2 
     | 
    
         
             
            require 'aws-sdk'
         
     | 
| 
       3 
3 
     | 
    
         | 
| 
       4 
     | 
    
         
            -
             
     | 
| 
       5 
     | 
    
         
            -
              class  
     | 
| 
      
 4 
     | 
    
         
            +
            class Synapse::ServiceWatcher
         
     | 
| 
      
 5 
     | 
    
         
            +
              class Ec2tagWatcher < BaseWatcher
         
     | 
| 
       6 
6 
     | 
    
         | 
| 
       7 
7 
     | 
    
         
             
                attr_reader :check_interval
         
     | 
| 
       8 
8 
     | 
    
         | 
| 
         @@ -41,15 +41,18 @@ module Synapse 
     | 
|
| 
       41 
41 
     | 
    
         
             
                      "Missing server_port_override for service #{@name} - which port are backends listening on?"
         
     | 
| 
       42 
42 
     | 
    
         
             
                  end
         
     | 
| 
       43 
43 
     | 
    
         | 
| 
       44 
     | 
    
         
            -
                  unless @haproxy['server_port_override'].match(/^\d+$/)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  unless @haproxy['server_port_override'].to_s.match(/^\d+$/)
         
     | 
| 
       45 
45 
     | 
    
         
             
                    raise ArgumentError, "Invalid server_port_override value"
         
     | 
| 
       46 
46 
     | 
    
         
             
                  end
         
     | 
| 
       47 
47 
     | 
    
         | 
| 
       48 
     | 
    
         
            -
                  #  
     | 
| 
       49 
     | 
    
         
            -
                   
     | 
| 
       50 
     | 
    
         
            -
                     
     | 
| 
       51 
     | 
    
         
            -
             
     | 
| 
       52 
     | 
    
         
            -
             
     | 
| 
      
 48 
     | 
    
         
            +
                  # aws region is optional in the SDK, aws will use a default value if not provided
         
     | 
| 
      
 49 
     | 
    
         
            +
                  unless @discovery['aws_region'] || ENV['AWS_REGION']
         
     | 
| 
      
 50 
     | 
    
         
            +
                    log.info "aws region is missing, will use default"
         
     | 
| 
      
 51 
     | 
    
         
            +
                  end
         
     | 
| 
      
 52 
     | 
    
         
            +
                  # access key id & secret are optional, might be using IAM instance profile for credentials
         
     | 
| 
      
 53 
     | 
    
         
            +
                  unless ((@discovery['aws_access_key_id'] || ENV['aws_access_key_id']) \
         
     | 
| 
      
 54 
     | 
    
         
            +
                          && (@discovery['aws_secret_access_key'] || ENV['aws_secret_access_key'] ))
         
     | 
| 
      
 55 
     | 
    
         
            +
                    log.info "aws access key id & secret not set in config or env variables for service #{name}, will attempt to use IAM instance profile"
         
     | 
| 
       53 
56 
     | 
    
         
             
                  end
         
     | 
| 
       54 
57 
     | 
    
         
             
                end
         
     | 
| 
       55 
58 
     | 
    
         | 
| 
         @@ -60,10 +63,11 @@ module Synapse 
     | 
|
| 
       60 
63 
     | 
    
         
             
                      if set_backends(discover_instances)
         
     | 
| 
       61 
64 
     | 
    
         
             
                        log.info "synapse: ec2tag watcher backends have changed."
         
     | 
| 
       62 
65 
     | 
    
         
             
                      end
         
     | 
| 
       63 
     | 
    
         
            -
                      sleep_until_next_check(start)
         
     | 
| 
       64 
66 
     | 
    
         
             
                    rescue Exception => e
         
     | 
| 
       65 
67 
     | 
    
         
             
                      log.warn "synapse: error in ec2tag watcher thread: #{e.inspect}"
         
     | 
| 
       66 
68 
     | 
    
         
             
                      log.warn e.backtrace
         
     | 
| 
      
 69 
     | 
    
         
            +
                    ensure
         
     | 
| 
      
 70 
     | 
    
         
            +
                      sleep_until_next_check(start)
         
     | 
| 
       67 
71 
     | 
    
         
             
                    end
         
     | 
| 
       68 
72 
     | 
    
         
             
                  end
         
     | 
| 
       69 
73 
     | 
    
         | 
| 
         @@ -0,0 +1,112 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'synapse/service_watcher/base'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'json'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'net/http'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'resolv'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            class Synapse::ServiceWatcher
         
     | 
| 
      
 7 
     | 
    
         
            +
              class MarathonWatcher < BaseWatcher
         
     | 
| 
      
 8 
     | 
    
         
            +
                def start
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @check_interval = @discovery['check_interval'] || 10.0
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @connection = nil
         
     | 
| 
      
 11 
     | 
    
         
            +
                  @watcher = Thread.new { sleep splay; watch }
         
     | 
| 
      
 12 
     | 
    
         
            +
                end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                def stop
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @connection.finish
         
     | 
| 
      
 16 
     | 
    
         
            +
                rescue
         
     | 
| 
      
 17 
     | 
    
         
            +
                  # pass
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
              private
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                def validate_discovery_opts
         
     | 
| 
      
 23 
     | 
    
         
            +
                  required_opts = %w[marathon_api_url application_name]
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  required_opts.each do |opt|
         
     | 
| 
      
 26 
     | 
    
         
            +
                    if @discovery.fetch(opt, '').empty?
         
     | 
| 
      
 27 
     | 
    
         
            +
                      raise ArgumentError,
         
     | 
| 
      
 28 
     | 
    
         
            +
                        "a value for services.#{@name}.discovery.#{opt} must be specified"
         
     | 
| 
      
 29 
     | 
    
         
            +
                    end
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                def attempt_marathon_connection
         
     | 
| 
      
 34 
     | 
    
         
            +
                  marathon_api_path = @discovery.fetch('marathon_api_path', '/v2/apps/%{app}/tasks')
         
     | 
| 
      
 35 
     | 
    
         
            +
                  marathon_api_path = marathon_api_path % { app: @discovery['application_name'] }
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  @marathon_api = URI.join(@discovery['marathon_api_url'], marathon_api_path)
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 40 
     | 
    
         
            +
                    @connection = Net::HTTP.new(@marathon_api.host, @marathon_api.port)
         
     | 
| 
      
 41 
     | 
    
         
            +
                    @connection.open_timeout = 5
         
     | 
| 
      
 42 
     | 
    
         
            +
                    @connection.start
         
     | 
| 
      
 43 
     | 
    
         
            +
                  rescue => ex
         
     | 
| 
      
 44 
     | 
    
         
            +
                    @connection = nil
         
     | 
| 
      
 45 
     | 
    
         
            +
                    log.error "synapse: could not connect to marathon at #{@marathon_api}: #{ex}"
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                    raise ex
         
     | 
| 
      
 48 
     | 
    
         
            +
                  end
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                def watch
         
     | 
| 
      
 52 
     | 
    
         
            +
                  until @should_exit
         
     | 
| 
      
 53 
     | 
    
         
            +
                    retry_count = 0
         
     | 
| 
      
 54 
     | 
    
         
            +
                    start = Time.now
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 57 
     | 
    
         
            +
                      if @connection.nil?
         
     | 
| 
      
 58 
     | 
    
         
            +
                        attempt_marathon_connection
         
     | 
| 
      
 59 
     | 
    
         
            +
                      end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                      req = Net::HTTP::Get.new(@marathon_api.request_uri)
         
     | 
| 
      
 62 
     | 
    
         
            +
                      req['Accept'] = 'application/json'
         
     | 
| 
      
 63 
     | 
    
         
            +
                      response = @connection.request(req)
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                      tasks = JSON.parse(response.body).fetch('tasks', [])
         
     | 
| 
      
 66 
     | 
    
         
            +
                      port_index = @discovery['port_index'] || 0
         
     | 
| 
      
 67 
     | 
    
         
            +
                      backends = tasks.keep_if { |task| task['startedAt'] }.map do |task|
         
     | 
| 
      
 68 
     | 
    
         
            +
                        {
         
     | 
| 
      
 69 
     | 
    
         
            +
                          'name' => task['host'],
         
     | 
| 
      
 70 
     | 
    
         
            +
                          'host' => task['host'],
         
     | 
| 
      
 71 
     | 
    
         
            +
                          'port' => task['ports'][port_index],
         
     | 
| 
      
 72 
     | 
    
         
            +
                        }
         
     | 
| 
      
 73 
     | 
    
         
            +
                      end.sort_by { |task| task['name'] }
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                      invalid_backends = backends.find_all { |b| b['port'].nil? }
         
     | 
| 
      
 76 
     | 
    
         
            +
                      if invalid_backends.any?
         
     | 
| 
      
 77 
     | 
    
         
            +
                        backends = backends - invalid_backends
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                        invalid_backends.each do |backend|
         
     | 
| 
      
 80 
     | 
    
         
            +
                          log.error "synapse: port index #{port_index} not found in task's port array!"
         
     | 
| 
      
 81 
     | 
    
         
            +
                        end
         
     | 
| 
      
 82 
     | 
    
         
            +
                      end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                      set_backends(backends)
         
     | 
| 
      
 85 
     | 
    
         
            +
                    rescue EOFError
         
     | 
| 
      
 86 
     | 
    
         
            +
                      # If the persistent HTTP connection is severed, we can automatically
         
     | 
| 
      
 87 
     | 
    
         
            +
                      # retry
         
     | 
| 
      
 88 
     | 
    
         
            +
                      log.info "synapse: marathon HTTP API disappeared, reconnecting..."
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                      retry if (retry_count += 1) == 1
         
     | 
| 
      
 91 
     | 
    
         
            +
                    rescue => e
         
     | 
| 
      
 92 
     | 
    
         
            +
                      log.warn "synapse: error in watcher thread: #{e.inspect}"
         
     | 
| 
      
 93 
     | 
    
         
            +
                      log.warn e.backtrace.join("\n")
         
     | 
| 
      
 94 
     | 
    
         
            +
                      @connection = nil
         
     | 
| 
      
 95 
     | 
    
         
            +
                    ensure
         
     | 
| 
      
 96 
     | 
    
         
            +
                      elapsed_time = Time.now - start
         
     | 
| 
      
 97 
     | 
    
         
            +
                      sleep (@check_interval - elapsed_time) if elapsed_time < @check_interval
         
     | 
| 
      
 98 
     | 
    
         
            +
                    end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                    @should_exit = true if only_run_once? # for testability
         
     | 
| 
      
 101 
     | 
    
         
            +
                  end
         
     | 
| 
      
 102 
     | 
    
         
            +
                end
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                def splay
         
     | 
| 
      
 105 
     | 
    
         
            +
                  Random.rand(@check_interval)
         
     | 
| 
      
 106 
     | 
    
         
            +
                end
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                def only_run_once?
         
     | 
| 
      
 109 
     | 
    
         
            +
                  false
         
     | 
| 
      
 110 
     | 
    
         
            +
                end
         
     | 
| 
      
 111 
     | 
    
         
            +
              end
         
     | 
| 
      
 112 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -3,7 +3,7 @@ require "synapse/service_watcher/base" 
     | 
|
| 
       3 
3 
     | 
    
         
             
            require 'thread'
         
     | 
| 
       4 
4 
     | 
    
         
             
            require 'zk'
         
     | 
| 
       5 
5 
     | 
    
         | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
      
 6 
     | 
    
         
            +
            class Synapse::ServiceWatcher
         
     | 
| 
       7 
7 
     | 
    
         
             
              class ZookeeperWatcher < BaseWatcher
         
     | 
| 
       8 
8 
     | 
    
         
             
                NUMBERS_RE = /^\d+$/
         
     | 
| 
       9 
9 
     | 
    
         | 
| 
         @@ -61,7 +61,8 @@ module Synapse 
     | 
|
| 
       61 
61 
     | 
    
         
             
                    node = @zk.get("#{@discovery['path']}/#{id}")
         
     | 
| 
       62 
62 
     | 
    
         | 
| 
       63 
63 
     | 
    
         
             
                    begin
         
     | 
| 
       64 
     | 
    
         
            -
                       
     | 
| 
      
 64 
     | 
    
         
            +
                      # TODO: Do less munging, or refactor out this processing
         
     | 
| 
      
 65 
     | 
    
         
            +
                      host, port, name, weight, haproxy_server_options = deserialize_service_instance(node.first)
         
     | 
| 
       65 
66 
     | 
    
         
             
                    rescue StandardError => e
         
     | 
| 
       66 
67 
     | 
    
         
             
                      log.error "synapse: invalid data in ZK node #{id} at #{@discovery['path']}: #{e}"
         
     | 
| 
       67 
68 
     | 
    
         
             
                    else
         
     | 
| 
         @@ -72,7 +73,11 @@ module Synapse 
     | 
|
| 
       72 
73 
     | 
    
         
             
                      numeric_id = NUMBERS_RE =~ numeric_id ? numeric_id.to_i : nil
         
     | 
| 
       73 
74 
     | 
    
         | 
| 
       74 
75 
     | 
    
         
             
                      log.debug "synapse: discovered backend #{name} at #{host}:#{server_port} for service #{@name}"
         
     | 
| 
       75 
     | 
    
         
            -
                      new_backends << { 
     | 
| 
      
 76 
     | 
    
         
            +
                      new_backends << {
         
     | 
| 
      
 77 
     | 
    
         
            +
                        'name' => name, 'host' => host, 'port' => server_port,
         
     | 
| 
      
 78 
     | 
    
         
            +
                        'id' => numeric_id, 'weight' => weight,
         
     | 
| 
      
 79 
     | 
    
         
            +
                        'haproxy_server_options' => haproxy_server_options
         
     | 
| 
      
 80 
     | 
    
         
            +
                      }
         
     | 
| 
       76 
81 
     | 
    
         
             
                    end
         
     | 
| 
       77 
82 
     | 
    
         
             
                  end
         
     | 
| 
       78 
83 
     | 
    
         | 
| 
         @@ -84,13 +89,12 @@ module Synapse 
     | 
|
| 
       84 
89 
     | 
    
         
             
                  return if @zk.nil?
         
     | 
| 
       85 
90 
     | 
    
         
             
                  log.debug "synapse: setting watch at #{@discovery['path']}"
         
     | 
| 
       86 
91 
     | 
    
         | 
| 
       87 
     | 
    
         
            -
                  @watcher. 
     | 
| 
       88 
     | 
    
         
            -
                  @watcher = @zk.register(@discovery['path'], &watcher_callback)
         
     | 
| 
      
 92 
     | 
    
         
            +
                  @watcher = @zk.register(@discovery['path'], &watcher_callback) unless @watcher
         
     | 
| 
       89 
93 
     | 
    
         | 
| 
       90 
94 
     | 
    
         
             
                  # Verify that we actually set up the watcher.
         
     | 
| 
       91 
95 
     | 
    
         
             
                  unless @zk.exists?(@discovery['path'], :watch => true)
         
     | 
| 
       92 
96 
     | 
    
         
             
                    log.error "synapse: zookeeper watcher path #{@discovery['path']} does not exist!"
         
     | 
| 
       93 
     | 
    
         
            -
                     
     | 
| 
      
 97 
     | 
    
         
            +
                    zk_cleanup
         
     | 
| 
       94 
98 
     | 
    
         
             
                  end
         
     | 
| 
       95 
99 
     | 
    
         
             
                  log.debug "synapse: set watch at #{@discovery['path']}"
         
     | 
| 
       96 
100 
     | 
    
         
             
                end
         
     | 
| 
         @@ -175,8 +179,9 @@ module Synapse 
     | 
|
| 
       175 
179 
     | 
    
         
             
                  port = decoded['port'] || (raise ValueError, 'instance json data does not have port key')
         
     | 
| 
       176 
180 
     | 
    
         
             
                  name = decoded['name'] || nil
         
     | 
| 
       177 
181 
     | 
    
         
             
                  weight = decoded['weight'] || nil
         
     | 
| 
      
 182 
     | 
    
         
            +
                  haproxy_server_options = decoded['haproxy_server_options'] || nil
         
     | 
| 
       178 
183 
     | 
    
         | 
| 
       179 
     | 
    
         
            -
                  return host, port, name, weight
         
     | 
| 
      
 184 
     | 
    
         
            +
                  return host, port, name, weight, haproxy_server_options
         
     | 
| 
       180 
185 
     | 
    
         
             
                end
         
     | 
| 
       181 
186 
     | 
    
         
             
              end
         
     | 
| 
       182 
187 
     | 
    
         
             
            end
         
     | 
| 
         @@ -19,7 +19,7 @@ require 'thread' 
     | 
|
| 
       19 
19 
     | 
    
         
             
            # for messages indicating that new servers are available, the check interval
         
     | 
| 
       20 
20 
     | 
    
         
             
            # has passed (triggering a re-resolve), or that the watcher should shut down.
         
     | 
| 
       21 
21 
     | 
    
         
             
            # The DNS watcher is responsible for the actual reconfiguring of backends.
         
     | 
| 
       22 
     | 
    
         
            -
             
     | 
| 
      
 22 
     | 
    
         
            +
            class Synapse::ServiceWatcher
         
     | 
| 
       23 
23 
     | 
    
         
             
              class ZookeeperDnsWatcher < BaseWatcher
         
     | 
| 
       24 
24 
     | 
    
         | 
| 
       25 
25 
     | 
    
         
             
                # Valid messages that can be passed through the internal message queue
         
     | 
| 
         @@ -46,7 +46,7 @@ module Synapse 
     | 
|
| 
       46 
46 
     | 
    
         
             
                  CHECK_INTERVAL_MESSAGE = CheckInterval.new
         
     | 
| 
       47 
47 
     | 
    
         
             
                end
         
     | 
| 
       48 
48 
     | 
    
         | 
| 
       49 
     | 
    
         
            -
                class Dns < Synapse::DnsWatcher
         
     | 
| 
      
 49 
     | 
    
         
            +
                class Dns < Synapse::ServiceWatcher::DnsWatcher
         
     | 
| 
       50 
50 
     | 
    
         | 
| 
       51 
51 
     | 
    
         
             
                  # Overrides the discovery_servers method on the parent class
         
     | 
| 
       52 
52 
     | 
    
         
             
                  attr_accessor :discovery_servers
         
     | 
| 
         @@ -106,7 +106,7 @@ module Synapse 
     | 
|
| 
       106 
106 
     | 
    
         
             
                  end
         
     | 
| 
       107 
107 
     | 
    
         
             
                end
         
     | 
| 
       108 
108 
     | 
    
         | 
| 
       109 
     | 
    
         
            -
                class Zookeeper < Synapse::ZookeeperWatcher
         
     | 
| 
      
 109 
     | 
    
         
            +
                class Zookeeper < Synapse::ServiceWatcher::ZookeeperWatcher
         
     | 
| 
       110 
110 
     | 
    
         
             
                  def initialize(opts={}, synapse, message_queue)
         
     | 
| 
       111 
111 
     | 
    
         
             
                    super(opts, synapse)
         
     | 
| 
       112 
112 
     | 
    
         | 
    
        data/lib/synapse/version.rb
    CHANGED
    
    
| 
         @@ -0,0 +1,61 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'spec_helper'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'fileutils'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            describe Synapse::FileOutput do
         
     | 
| 
      
 5 
     | 
    
         
            +
              subject { Synapse::FileOutput.new(config['file_output']) }
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              before(:example) do
         
     | 
| 
      
 8 
     | 
    
         
            +
                FileUtils.mkdir_p(config['file_output']['output_directory'])
         
     | 
| 
      
 9 
     | 
    
         
            +
              end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              after(:example) do
         
     | 
| 
      
 12 
     | 
    
         
            +
                FileUtils.rm_r(config['file_output']['output_directory'])
         
     | 
| 
      
 13 
     | 
    
         
            +
              end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              let(:mockwatcher_1) do
         
     | 
| 
      
 16 
     | 
    
         
            +
                mockWatcher = double(Synapse::ServiceWatcher)
         
     | 
| 
      
 17 
     | 
    
         
            +
                allow(mockWatcher).to receive(:name).and_return('example_service')
         
     | 
| 
      
 18 
     | 
    
         
            +
                backends = [{ 'host' => 'somehost', 'port' => 5555}]
         
     | 
| 
      
 19 
     | 
    
         
            +
                allow(mockWatcher).to receive(:backends).and_return(backends)
         
     | 
| 
      
 20 
     | 
    
         
            +
                mockWatcher
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
              let(:mockwatcher_2) do
         
     | 
| 
      
 23 
     | 
    
         
            +
                mockWatcher = double(Synapse::ServiceWatcher)
         
     | 
| 
      
 24 
     | 
    
         
            +
                allow(mockWatcher).to receive(:name).and_return('foobar_service')
         
     | 
| 
      
 25 
     | 
    
         
            +
                backends = [{ 'host' => 'somehost', 'port' => 1234}]
         
     | 
| 
      
 26 
     | 
    
         
            +
                allow(mockWatcher).to receive(:backends).and_return(backends)
         
     | 
| 
      
 27 
     | 
    
         
            +
                mockWatcher
         
     | 
| 
      
 28 
     | 
    
         
            +
              end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
              it 'updates the config' do
         
     | 
| 
      
 31 
     | 
    
         
            +
                expect(subject).to receive(:write_backends_to_file)
         
     | 
| 
      
 32 
     | 
    
         
            +
                subject.update_config([mockwatcher_1])
         
     | 
| 
      
 33 
     | 
    
         
            +
              end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
              it 'manages correct files' do
         
     | 
| 
      
 36 
     | 
    
         
            +
                subject.update_config([mockwatcher_1, mockwatcher_2])
         
     | 
| 
      
 37 
     | 
    
         
            +
                FileUtils.cd(config['file_output']['output_directory']) do
         
     | 
| 
      
 38 
     | 
    
         
            +
                  expect(Dir.glob('*.json')).to eql(['example_service.json', 'foobar_service.json'])
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
                # Should clean up after itself
         
     | 
| 
      
 41 
     | 
    
         
            +
                subject.update_config([mockwatcher_1])
         
     | 
| 
      
 42 
     | 
    
         
            +
                FileUtils.cd(config['file_output']['output_directory']) do
         
     | 
| 
      
 43 
     | 
    
         
            +
                  expect(Dir.glob('*.json')).to eql(['example_service.json'])
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
                # Should clean up after itself
         
     | 
| 
      
 46 
     | 
    
         
            +
                subject.update_config([])
         
     | 
| 
      
 47 
     | 
    
         
            +
                FileUtils.cd(config['file_output']['output_directory']) do
         
     | 
| 
      
 48 
     | 
    
         
            +
                  expect(Dir.glob('*.json')).to eql([])
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
              end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
              it 'writes correct content' do
         
     | 
| 
      
 53 
     | 
    
         
            +
                subject.update_config([mockwatcher_1])
         
     | 
| 
      
 54 
     | 
    
         
            +
                data_path = File.join(config['file_output']['output_directory'],
         
     | 
| 
      
 55 
     | 
    
         
            +
                                      "example_service.json")
         
     | 
| 
      
 56 
     | 
    
         
            +
                old_backends = JSON.load(File.read(data_path))
         
     | 
| 
      
 57 
     | 
    
         
            +
                expect(old_backends.length).to eql(1)
         
     | 
| 
      
 58 
     | 
    
         
            +
                expect(old_backends.first['host']).to eql('somehost')
         
     | 
| 
      
 59 
     | 
    
         
            +
                expect(old_backends.first['port']).to eql(5555)
         
     | 
| 
      
 60 
     | 
    
         
            +
              end
         
     | 
| 
      
 61 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -8,7 +8,16 @@ describe Synapse::Haproxy do 
     | 
|
| 
       8 
8 
     | 
    
         
             
              let(:mockwatcher) do
         
     | 
| 
       9 
9 
     | 
    
         
             
                mockWatcher = double(Synapse::ServiceWatcher)
         
     | 
| 
       10 
10 
     | 
    
         
             
                allow(mockWatcher).to receive(:name).and_return('example_service')
         
     | 
| 
       11 
     | 
    
         
            -
                backends = [{ 'host' => 'somehost', 'port' =>  
     | 
| 
      
 11 
     | 
    
         
            +
                backends = [{ 'host' => 'somehost', 'port' => 5555}]
         
     | 
| 
      
 12 
     | 
    
         
            +
                allow(mockWatcher).to receive(:backends).and_return(backends)
         
     | 
| 
      
 13 
     | 
    
         
            +
                allow(mockWatcher).to receive(:haproxy).and_return({'server_options' => "check inter 2000 rise 3 fall 2"})
         
     | 
| 
      
 14 
     | 
    
         
            +
                mockWatcher
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
              let(:mockwatcher_with_server_options) do
         
     | 
| 
      
 18 
     | 
    
         
            +
                mockWatcher = double(Synapse::ServiceWatcher)
         
     | 
| 
      
 19 
     | 
    
         
            +
                allow(mockWatcher).to receive(:name).and_return('example_service')
         
     | 
| 
      
 20 
     | 
    
         
            +
                backends = [{ 'host' => 'somehost', 'port' => 5555, 'haproxy_server_options' => 'backup'}]
         
     | 
| 
       12 
21 
     | 
    
         
             
                allow(mockWatcher).to receive(:backends).and_return(backends)
         
     | 
| 
       13 
22 
     | 
    
         
             
                allow(mockWatcher).to receive(:haproxy).and_return({'server_options' => "check inter 2000 rise 3 fall 2"})
         
     | 
| 
       14 
23 
     | 
    
         
             
                mockWatcher
         
     | 
| 
         @@ -29,4 +38,8 @@ describe Synapse::Haproxy do 
     | 
|
| 
       29 
38 
     | 
    
         
             
                expect(subject.generate_backend_stanza(mockwatcher, mockConfig)).to eql(["\nbackend example_service", ["\tmode tcp"], ["\tserver somehost:5555 somehost:5555 check inter 2000 rise 3 fall 2"]])
         
     | 
| 
       30 
39 
     | 
    
         
             
              end
         
     | 
| 
       31 
40 
     | 
    
         | 
| 
      
 41 
     | 
    
         
            +
              it 'respects haproxy_server_options' do
         
     | 
| 
      
 42 
     | 
    
         
            +
                mockConfig = []
         
     | 
| 
      
 43 
     | 
    
         
            +
                expect(subject.generate_backend_stanza(mockwatcher_with_server_options, mockConfig)).to eql(["\nbackend example_service", [], ["\tserver somehost:5555 somehost:5555 cookie somehost:5555 check inter 2000 rise 3 fall 2 backup"]])
         
     | 
| 
      
 44 
     | 
    
         
            +
              end
         
     | 
| 
       32 
45 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1,12 +1,12 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            require 'spec_helper'
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
            class Synapse::BaseWatcher
         
     | 
| 
      
 3 
     | 
    
         
            +
            class Synapse::ServiceWatcher::BaseWatcher
         
     | 
| 
       4 
4 
     | 
    
         
             
              attr_reader :should_exit, :default_servers
         
     | 
| 
       5 
5 
     | 
    
         
             
            end
         
     | 
| 
       6 
6 
     | 
    
         | 
| 
       7 
     | 
    
         
            -
            describe Synapse::BaseWatcher do
         
     | 
| 
      
 7 
     | 
    
         
            +
            describe Synapse::ServiceWatcher::BaseWatcher do
         
     | 
| 
       8 
8 
     | 
    
         
             
              let(:mocksynapse) { double() }
         
     | 
| 
       9 
     | 
    
         
            -
              subject { Synapse::BaseWatcher.new(args, mocksynapse) }
         
     | 
| 
      
 9 
     | 
    
         
            +
              subject { Synapse::ServiceWatcher::BaseWatcher.new(args, mocksynapse) }
         
     | 
| 
       10 
10 
     | 
    
         
             
              let(:testargs) { { 'name' => 'foo', 'discovery' => { 'method' => 'base' }, 'haproxy' => {} }}
         
     | 
| 
       11 
11 
     | 
    
         | 
| 
       12 
12 
     | 
    
         
             
              def remove_arg(name)
         
     | 
| 
         @@ -1,13 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            require 'spec_helper'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'synapse/service_watcher/docker'
         
     | 
| 
       2 
3 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
            class Synapse::DockerWatcher
         
     | 
| 
      
 4 
     | 
    
         
            +
            class Synapse::ServiceWatcher::DockerWatcher
         
     | 
| 
       4 
5 
     | 
    
         
             
              attr_reader :check_interval, :watcher, :synapse
         
     | 
| 
       5 
6 
     | 
    
         
             
              attr_accessor :default_servers
         
     | 
| 
       6 
7 
     | 
    
         
             
            end
         
     | 
| 
       7 
8 
     | 
    
         | 
| 
       8 
     | 
    
         
            -
            describe Synapse::DockerWatcher do
         
     | 
| 
      
 9 
     | 
    
         
            +
            describe Synapse::ServiceWatcher::DockerWatcher do
         
     | 
| 
       9 
10 
     | 
    
         
             
              let(:mocksynapse) { double() }
         
     | 
| 
       10 
     | 
    
         
            -
              subject { Synapse::DockerWatcher.new(testargs, mocksynapse) }
         
     | 
| 
      
 11 
     | 
    
         
            +
              subject { Synapse::ServiceWatcher::DockerWatcher.new(testargs, mocksynapse) }
         
     | 
| 
       11 
12 
     | 
    
         
             
              let(:testargs) { { 'name' => 'foo', 'discovery' => { 'method' => 'docker', 'servers' => [{'host' => 'server1.local', 'name' => 'mainserver'}], 'image_name' => 'mycool/image', 'container_port' => 6379 }, 'haproxy' => {} }}
         
     | 
| 
       12 
13 
     | 
    
         
             
              before(:each) do
         
     | 
| 
       13 
14 
     | 
    
         
             
                allow(subject.log).to receive(:warn)
         
     | 
| 
         @@ -84,9 +85,9 @@ describe Synapse::DockerWatcher do 
     | 
|
| 
       84 
85 
     | 
    
         
             
                it('has a sane uri') { subject.send(:containers); expect(Docker.url).to eql('http://server1.local:4243') }
         
     | 
| 
       85 
86 
     | 
    
         | 
| 
       86 
87 
     | 
    
         
             
                context 'old style port mappings' do
         
     | 
| 
      
 88 
     | 
    
         
            +
                  let(:docker_data) { [{"Ports" => "0.0.0.0:49153->6379/tcp, 0.0.0.0:49154->6390/tcp", "Image" => "mycool/image:tagname"}] }
         
     | 
| 
       87 
89 
     | 
    
         
             
                  context 'works for one container' do
         
     | 
| 
       88 
     | 
    
         
            -
                     
     | 
| 
       89 
     | 
    
         
            -
                    it do 
         
     | 
| 
      
 90 
     | 
    
         
            +
                    it do
         
     | 
| 
       90 
91 
     | 
    
         
             
                      expect(Docker::Util).to receive(:parse_json).and_return(docker_data)
         
     | 
| 
       91 
92 
     | 
    
         
             
                      expect(subject.send(:containers)).to eql([{"name"=>"mainserver", "host"=>"server1.local", "port"=>"49153"}])
         
     | 
| 
       92 
93 
     | 
    
         
             
                     end
         
     | 
| 
         @@ -106,6 +107,12 @@ describe Synapse::DockerWatcher do 
     | 
|
| 
       106 
107 
     | 
    
         
             
                    expect(Docker::Util).to receive(:parse_json).and_return(docker_data)
         
     | 
| 
       107 
108 
     | 
    
         
             
                    expect(subject.send(:containers)).to eql([{"name"=>"mainserver", "host"=>"server1.local", "port"=>"49153"}])
         
     | 
| 
       108 
109 
     | 
    
         
             
                  end
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
                  it 'filters out containers with unmapped ports' do
         
     | 
| 
      
 112 
     | 
    
         
            +
                    test_docker_data = docker_data + [{"Ports" => [{'PrivatePort' => 6379}], "Image" => "mycool/image:unmapped"}]
         
     | 
| 
      
 113 
     | 
    
         
            +
                    expect(Docker::Util).to receive(:parse_json).and_return(test_docker_data)
         
     | 
| 
      
 114 
     | 
    
         
            +
                    expect(subject.send(:containers)).to eql([{"name"=>"mainserver", "host"=>"server1.local", "port"=>"49153"}])
         
     | 
| 
      
 115 
     | 
    
         
            +
                  end
         
     | 
| 
       109 
116 
     | 
    
         
             
                end
         
     | 
| 
       110 
117 
     | 
    
         | 
| 
       111 
118 
     | 
    
         
             
                context 'filters out wrong images' do
         
     | 
| 
         @@ -117,4 +124,3 @@ describe Synapse::DockerWatcher do 
     | 
|
| 
       117 
124 
     | 
    
         
             
                end
         
     | 
| 
       118 
125 
     | 
    
         
             
              end
         
     | 
| 
       119 
126 
     | 
    
         
             
            end
         
     | 
| 
       120 
     | 
    
         
            -
             
     | 
| 
         @@ -1,7 +1,8 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            require 'spec_helper'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'synapse/service_watcher/ec2tag'
         
     | 
| 
       2 
3 
     | 
    
         
             
            require 'logging'
         
     | 
| 
       3 
4 
     | 
    
         | 
| 
       4 
     | 
    
         
            -
            class Synapse:: 
     | 
| 
      
 5 
     | 
    
         
            +
            class Synapse::ServiceWatcher::Ec2tagWatcher
         
     | 
| 
       5 
6 
     | 
    
         
             
              attr_reader   :synapse
         
     | 
| 
       6 
7 
     | 
    
         
             
              attr_accessor :default_servers, :ec2
         
     | 
| 
       7 
8 
     | 
    
         
             
            end
         
     | 
| 
         @@ -28,9 +29,9 @@ class FakeAWSInstance 
     | 
|
| 
       28 
29 
     | 
    
         
             
              end
         
     | 
| 
       29 
30 
     | 
    
         
             
            end
         
     | 
| 
       30 
31 
     | 
    
         | 
| 
       31 
     | 
    
         
            -
            describe Synapse:: 
     | 
| 
      
 32 
     | 
    
         
            +
            describe Synapse::ServiceWatcher::Ec2tagWatcher do
         
     | 
| 
       32 
33 
     | 
    
         
             
              let(:mock_synapse) { double }
         
     | 
| 
       33 
     | 
    
         
            -
              subject { Synapse:: 
     | 
| 
      
 34 
     | 
    
         
            +
              subject { Synapse::ServiceWatcher::Ec2tagWatcher.new(basic_config, mock_synapse) }
         
     | 
| 
       34 
35 
     | 
    
         | 
| 
       35 
36 
     | 
    
         
             
              let(:basic_config) do
         
     | 
| 
       36 
37 
     | 
    
         
             
                { 'name' => 'ec2tagtest',
         
     | 
| 
         @@ -86,24 +87,24 @@ describe Synapse::EC2Watcher do 
     | 
|
| 
       86 
87 
     | 
    
         
             
                end
         
     | 
| 
       87 
88 
     | 
    
         | 
| 
       88 
89 
     | 
    
         
             
                context 'when missing arguments' do
         
     | 
| 
       89 
     | 
    
         
            -
                  it ' 
     | 
| 
      
 90 
     | 
    
         
            +
                  it 'does not break if aws_region is missing' do
         
     | 
| 
       90 
91 
     | 
    
         
             
                    expect {
         
     | 
| 
       91 
     | 
    
         
            -
                      Synapse:: 
     | 
| 
       92 
     | 
    
         
            -
                    }. 
     | 
| 
      
 92 
     | 
    
         
            +
                      Synapse::ServiceWatcher::Ec2tagWatcher.new(remove_discovery_arg('aws_region'), mock_synapse)
         
     | 
| 
      
 93 
     | 
    
         
            +
                    }.not_to raise_error
         
     | 
| 
       93 
94 
     | 
    
         
             
                  end
         
     | 
| 
       94 
     | 
    
         
            -
                  it ' 
     | 
| 
      
 95 
     | 
    
         
            +
                  it 'does not break if aws_access_key_id is missing' do
         
     | 
| 
       95 
96 
     | 
    
         
             
                    expect {
         
     | 
| 
       96 
     | 
    
         
            -
                      Synapse:: 
     | 
| 
       97 
     | 
    
         
            -
                    }. 
     | 
| 
      
 97 
     | 
    
         
            +
                      Synapse::ServiceWatcher::Ec2tagWatcher.new(remove_discovery_arg('aws_access_key_id'), mock_synapse)
         
     | 
| 
      
 98 
     | 
    
         
            +
                    }.not_to raise_error
         
     | 
| 
       98 
99 
     | 
    
         
             
                  end
         
     | 
| 
       99 
     | 
    
         
            -
                  it ' 
     | 
| 
      
 100 
     | 
    
         
            +
                  it 'does not break if aws_secret_access_key is missing' do
         
     | 
| 
       100 
101 
     | 
    
         
             
                    expect {
         
     | 
| 
       101 
     | 
    
         
            -
                      Synapse:: 
     | 
| 
       102 
     | 
    
         
            -
                    }. 
     | 
| 
      
 102 
     | 
    
         
            +
                      Synapse::ServiceWatcher::Ec2tagWatcher.new(remove_discovery_arg('aws_secret_access_key'), mock_synapse)
         
     | 
| 
      
 103 
     | 
    
         
            +
                    }.not_to raise_error
         
     | 
| 
       103 
104 
     | 
    
         
             
                  end
         
     | 
| 
       104 
105 
     | 
    
         
             
                  it 'complains if server_port_override is missing' do
         
     | 
| 
       105 
106 
     | 
    
         
             
                    expect {
         
     | 
| 
       106 
     | 
    
         
            -
                      Synapse:: 
     | 
| 
      
 107 
     | 
    
         
            +
                      Synapse::ServiceWatcher::Ec2tagWatcher.new(remove_haproxy_arg('server_port_override'), mock_synapse)
         
     | 
| 
       107 
108 
     | 
    
         
             
                    }.to raise_error(ArgumentError, /Missing server_port_override/)
         
     | 
| 
       108 
109 
     | 
    
         
             
                  end
         
     | 
| 
       109 
110 
     | 
    
         
             
                end
         
     | 
| 
         @@ -111,7 +112,7 @@ describe Synapse::EC2Watcher do 
     | 
|
| 
       111 
112 
     | 
    
         
             
                context 'invalid data' do
         
     | 
| 
       112 
113 
     | 
    
         
             
                  it 'complains if the haproxy server_port_override is not a number' do
         
     | 
| 
       113 
114 
     | 
    
         
             
                      expect {
         
     | 
| 
       114 
     | 
    
         
            -
                        Synapse:: 
     | 
| 
      
 115 
     | 
    
         
            +
                        Synapse::ServiceWatcher::Ec2tagWatcher.new(munge_haproxy_arg('server_port_override', '80deadbeef'), mock_synapse)
         
     | 
| 
       115 
116 
     | 
    
         
             
                      }.to raise_error(ArgumentError, /Invalid server_port_override/)
         
     | 
| 
       116 
117 
     | 
    
         
             
                  end
         
     | 
| 
       117 
118 
     | 
    
         
             
                end
         
     | 
| 
         @@ -121,6 +122,27 @@ describe Synapse::EC2Watcher do 
     | 
|
| 
       121 
122 
     | 
    
         
             
                let(:instance1) { FakeAWSInstance.new }
         
     | 
| 
       122 
123 
     | 
    
         
             
                let(:instance2) { FakeAWSInstance.new }
         
     | 
| 
       123 
124 
     | 
    
         | 
| 
      
 125 
     | 
    
         
            +
                context 'watch' do
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
                  it 'discovers instances, configures backends, then sleeps' do
         
     | 
| 
      
 128 
     | 
    
         
            +
                    fake_backends = [1,2,3]
         
     | 
| 
      
 129 
     | 
    
         
            +
                    expect(subject).to receive(:discover_instances).and_return(fake_backends)
         
     | 
| 
      
 130 
     | 
    
         
            +
                    expect(subject).to receive(:set_backends).with(fake_backends) { subject.stop }
         
     | 
| 
      
 131 
     | 
    
         
            +
                    expect(subject).to receive(:sleep_until_next_check)
         
     | 
| 
      
 132 
     | 
    
         
            +
                    subject.send(:watch)
         
     | 
| 
      
 133 
     | 
    
         
            +
                  end
         
     | 
| 
      
 134 
     | 
    
         
            +
             
     | 
| 
      
 135 
     | 
    
         
            +
                  it 'sleeps until next check if discover_instances fails' do
         
     | 
| 
      
 136 
     | 
    
         
            +
                    expect(subject).to receive(:discover_instances) do
         
     | 
| 
      
 137 
     | 
    
         
            +
                      subject.stop
         
     | 
| 
      
 138 
     | 
    
         
            +
                      raise "discover failed"
         
     | 
| 
      
 139 
     | 
    
         
            +
                    end
         
     | 
| 
      
 140 
     | 
    
         
            +
                    expect(subject).to receive(:sleep_until_next_check)
         
     | 
| 
      
 141 
     | 
    
         
            +
                    subject.send(:watch)
         
     | 
| 
      
 142 
     | 
    
         
            +
                  end
         
     | 
| 
      
 143 
     | 
    
         
            +
             
     | 
| 
      
 144 
     | 
    
         
            +
                end
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
       124 
146 
     | 
    
         
             
                context 'using the AWS API' do
         
     | 
| 
       125 
147 
     | 
    
         
             
                  let(:ec2_client) { double('AWS::EC2') }
         
     | 
| 
       126 
148 
     | 
    
         
             
                  let(:instance_collection) { double('AWS::EC2::InstanceCollection') }
         
     |