Class: Nerve::ServiceWatcher

Inherits:
Object
  • Object
show all
Includes:
Logging, Utils
Defined in:
lib/nerve/service_watcher.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Logging

configure_logger_for, #log, logger_for

Methods included from Utils

#responsive_sleep, #safe_run

Constructor Details

#initialize(service = {}) ⇒ ServiceWatcher

Returns a new instance of ServiceWatcher.



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/nerve/service_watcher.rb', line 12

def initialize(service={})
  log.debug "nerve: creating service watcher object"

  # check that we have all of the required arguments
  %w{name instance_id host port}.each do |required|
    raise ArgumentError, "missing required argument #{required} for new service watcher" unless service[required]
  end

  @name = service['name']

  # configure the reporter, which we use for reporting status to the registry 
  @reporter = Reporter.new_from_service(service)

  # instantiate the checks for this service
  @service_checks = []
  service['checks'] ||= []
  service['checks'].each do |check|
    # checks inherit attributes from the service overall
    check['host'] ||= service['host']
    check['port'] ||= service['port']

    # generate a nice readable name for each check
    check['name'] ||= "#{@name} #{check['type']}-#{check['host']}:#{check['port']}"

    # make sure a type is set
    check['type'] ||= "undefined"

    # require a 3rd-party module if necessary for external checkers
    unless ServiceCheck::CHECKS[check['type']]
      m = check['module'] ? check['module'] : "nerve-watcher-#{check['type']}"
      require m
    end

    # instantiate the check object
    service_check_class = ServiceCheck::CHECKS[check['type']]
    if service_check_class.nil?
      raise ArgumentError,
        "invalid service check type #{check['type']}; valid types: #{ServiceCheck::CHECKS.keys.join(',')}"
    end

    # save the check object
    @service_checks << service_check_class.new(check)
  end

  # how often do we initiate service checks?
  @check_interval = service['check_interval'] || 0.5

  # force an initial report on startup
  @was_up = nil

  # when this watcher is started it will store the
  # thread here
  @run_thread = nil
  @should_finish = false

  log.debug "nerve: created service watcher for #{@name} with #{@service_checks.size} checks"
end

Instance Attribute Details

#was_upObject (readonly)

Returns the value of attribute was_up.



10
11
12
# File 'lib/nerve/service_watcher.rb', line 10

def was_up
  @was_up
end

Instance Method Details

#alive?Boolean

Returns:

  • (Boolean)


92
93
94
# File 'lib/nerve/service_watcher.rb', line 92

def alive?()
  !@run_thread.nil? && @run_thread.alive?
end

#check?Boolean

Returns:

  • (Boolean)


139
140
141
142
143
144
# File 'lib/nerve/service_watcher.rb', line 139

def check?
  @service_checks.each do |check|
    return false unless check.up?
  end
  return true
end

#check_and_reportObject



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/nerve/service_watcher.rb', line 116

def check_and_report
  if !@reporter.ping?
    # If the reporter can't ping, then we do not know the status
    # and must force a new report.
    @was_up = nil
  end

  # what is the status of the service?
  is_up = check?
  log.debug "nerve: current service status for #{@name} is #{is_up.inspect}"

  if is_up != @was_up
    if is_up
      @reporter.report_up
      log.info "nerve: service #{@name} is now up"
    else
      @reporter.report_down
      log.warn "nerve: service #{@name} is now down"
    end
    @was_up = is_up
  end
end

#runObject



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/nerve/service_watcher.rb', line 96

def run()
  log.info "nerve: starting service watch #{@name}"
  @reporter.start()

  until watcher_should_exit?
    check_and_report

    # wait to run more checks but make sure to exit if $EXIT
    # we avoid sleeping for the entire check interval at once
    # so that nerve can exit promptly if required
    responsive_sleep (@check_interval) { watcher_should_exit? }
  end
rescue StandardError => e
  log.error "nerve: error in service watcher #{@name}: #{e.inspect}"
  raise e
ensure
  log.info "nerve: stopping reporter for #{@name}"
  @reporter.stop
end

#startObject



70
71
72
73
74
75
76
# File 'lib/nerve/service_watcher.rb', line 70

def start()
  unless @run_thread
    @run_thread = Thread.new { self.run() }
  else
    log.error "nerve: tried to double start a watcher"
  end
end

#stopObject



78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/nerve/service_watcher.rb', line 78

def stop()
  log.info "nerve: stopping service watch #{@name}"
  @should_finish = true
  return true if @run_thread.nil?

  unclean_shutdown = @run_thread.join(10).nil?
  if unclean_shutdown
    log.error "nerve: unclean shutdown of #{@name}, killing thread"
    Thread.kill(@run_thread)
  end
  @run_thread = nil
  !unclean_shutdown
end