arborist 0.0.1.pre20160106113421 → 0.0.1.pre20160128152542

Sign up to get free protection for your applications and to get access to all the features.
data/lib/arborist/node.rb CHANGED
@@ -29,10 +29,6 @@ class Arborist::Node
29
29
  # loaded.
30
30
  LOADED_INSTANCE_KEY = :loaded_node_instances
31
31
 
32
- ##
33
- # The glob pattern to use for searching for node
34
- NODE_FILE_PATTERN = '**/*.rb'
35
-
36
32
 
37
33
  ##
38
34
  # The struct for the 'ack' operational property
@@ -57,18 +53,25 @@ class Arborist::Node
57
53
  state :unknown,
58
54
  :up,
59
55
  :down,
60
- :acked
56
+ :acked,
57
+ :disabled
61
58
 
62
59
  event :update do
63
- transition any - [:acked] => :acked, if: :ack_set?
64
- transition any - [:up] => :up, if: :last_contact_successful?
65
- transition any - [:down, :acked] => :down, unless: :last_contact_successful?
60
+ transition [:down, :unknown, :acked] => :up, if: :last_contact_successful?
61
+ transition [:up, :unknown] => :down, unless: :last_contact_successful?
62
+ transition :down => :acked, if: :ack_set?
63
+ transition [:unknown, :up] => :disabled, if: :ack_set?
64
+ transition :disabled => :unknown, unless: :ack_set?
66
65
  end
67
66
 
68
67
  after_transition any => :acked, do: :on_ack
69
68
  after_transition :acked => :up, do: :on_ack_cleared
70
69
  after_transition :down => :up, do: :on_node_up
71
70
  after_transition [:unknown, :up] => :down, do: :on_node_down
71
+ after_transition [:unknown, :up] => :disabled, do: :on_node_disabled
72
+ after_transition :disabled => :unknown, do: :on_node_enabled
73
+
74
+ after_transition any => any, do: :log_transition
72
75
 
73
76
  after_transition do: :add_status_to_update_delta
74
77
  end
@@ -100,6 +103,8 @@ class Arborist::Node
100
103
  ### them.
101
104
  def self::add_loaded_instance( new_instance )
102
105
  instances = Thread.current[ LOADED_INSTANCE_KEY ] or return
106
+ self.log.debug "Adding new instance %p to loaded instances %p" %
107
+ [ new_instance, instances ]
103
108
  instances << new_instance
104
109
  end
105
110
 
@@ -137,24 +142,9 @@ class Arborist::Node
137
142
  end
138
143
 
139
144
 
140
- ### Return an iterator for all the node files in the specified +directory+.
141
- def self::each_in( directory )
142
- path = Pathname( directory )
143
- paths = if path.directory?
144
- Pathname.glob( directory + NODE_FILE_PATTERN )
145
- else
146
- [ path ]
147
- end
148
-
149
- return paths.flat_map do |file|
150
- file_url = "file://%s" % [ file.expand_path ]
151
- nodes = self.load( file )
152
- self.log.debug "Loaded nodes %p..." % [ nodes ]
153
- nodes.each do |node|
154
- node.source = file_url
155
- end
156
- nodes
157
- end
145
+ ### Return an iterator for all the nodes supplied by the specified +loader+.
146
+ def self::each_in( loader )
147
+ return loader.nodes
158
148
  end
159
149
 
160
150
 
@@ -323,8 +313,11 @@ class Arborist::Node
323
313
  self.log.debug "Updated: %p" % [ new_properties ]
324
314
 
325
315
  self.last_contacted = Time.now
326
- self.error = new_properties.delete( 'error' )
327
- self.ack = new_properties.delete( 'ack' ) if new_properties.key?( 'ack' )
316
+ if new_properties.key?( 'ack' )
317
+ self.ack = new_properties.delete( 'ack' )
318
+ else
319
+ self.error = new_properties.delete( 'error' )
320
+ end
328
321
 
329
322
  self.properties.merge!( new_properties, &self.method(:merge_and_record_delta) )
330
323
  compact_hash( self.properties )
@@ -504,6 +497,8 @@ class Arborist::Node
504
497
  return "%s as of %s" % [ self.status.upcase, self.last_contacted ]
505
498
  when 'acked'
506
499
  return "ACKed by %s %s" % [ self.ack.sender, self.ack.time.as_delta ]
500
+ when 'disabled'
501
+ return "disabled by %s %s" % [ self.ack.sender, self.ack.time.as_delta ]
507
502
  else
508
503
  return "in an unknown state"
509
504
  end
@@ -607,17 +602,21 @@ class Arborist::Node
607
602
 
608
603
  ### Ack the node with the specified +ack_data+, which should contain
609
604
  def ack=( ack_data )
610
- self.log.debug "ACKed with data: %p" % [ ack_data ]
611
-
612
- ack_data['time'] ||= Time.now
613
- ack_values = ack_data.values_at( *Arborist::Node::ACK.members.map(&:to_s) )
614
- new_ack = Arborist::Node::ACK.new( *ack_values )
605
+ if ack_data
606
+ self.log.info "Node %s ACKed with data: %p" % [ self.identifier, ack_data ]
607
+ ack_data['time'] ||= Time.now
608
+ ack_values = ack_data.values_at( *Arborist::Node::ACK.members.map(&:to_s) )
609
+ new_ack = Arborist::Node::ACK.new( *ack_values )
610
+
611
+ if missing = ACK_REQUIRED_PROPERTIES.find {|prop| new_ack[prop].nil? }
612
+ raise "Missing required ACK attribute %s" % [ missing ]
613
+ end
615
614
 
616
- if missing = ACK_REQUIRED_PROPERTIES.find {|prop| new_ack[prop].nil? }
617
- raise "Missing required ACK attribute %s" % [ missing ]
615
+ @ack = new_ack
616
+ else
617
+ self.log.info "Node %s ACK cleared explicitly" % [ self.identifier ]
618
+ @ack = nil
618
619
  end
619
-
620
- @ack = new_ack
621
620
  end
622
621
 
623
622
 
@@ -642,6 +641,13 @@ class Arborist::Node
642
641
  # :section: State Callbacks
643
642
  #
644
643
 
644
+ ### Log every status transition
645
+ def log_transition( transition )
646
+ self.log.debug "Transitioned %s from %s to %s" %
647
+ [ self.identifier, transition.from, transition.to ]
648
+ end
649
+
650
+
645
651
  ### Callback for when an acknowledgement is set.
646
652
  def on_ack( transition )
647
653
  self.log.warn "ACKed: %s" % [ self.status_description ]
@@ -652,12 +658,14 @@ class Arborist::Node
652
658
 
653
659
  ### Callback for when an acknowledgement is cleared.
654
660
  def on_ack_cleared( transition )
661
+ self.error = nil
655
662
  self.log.warn "ACK cleared for %s" % [ self.identifier ]
656
663
  end
657
664
 
658
665
 
659
666
  ### Callback for when a node goes from down to up
660
667
  def on_node_up( transition )
668
+ self.error = nil
661
669
  self.log.warn "%s is %s" % [ self.identifier, self.status_description ]
662
670
  end
663
671
 
@@ -669,6 +677,18 @@ class Arborist::Node
669
677
  end
670
678
 
671
679
 
680
+ ### Callback for when a node goes from up to disabled
681
+ def on_node_disabled( transition )
682
+ self.log.warn "%s is %s" % [ self.identifier, self.status_description ]
683
+ end
684
+
685
+
686
+ ### Callback for when a node goes from disabled to unknown
687
+ def on_node_enabled( transition )
688
+ self.log.warn "%s is %s" % [ self.identifier, self.status_description ]
689
+ end
690
+
691
+
672
692
  ### Add the transition from one state to another to the data used to build
673
693
  ### deltas for the #update event.
674
694
  def add_status_to_update_delta( transition )
@@ -61,27 +61,13 @@ class Arborist::Observer
61
61
  end
62
62
 
63
63
 
64
- ### Return an iterator for all the observer files in the specified +directory+.
65
- def self::each_in( directory )
66
- path = Pathname( directory )
67
- paths = if path.directory?
68
- Pathname.glob( directory + OBSERVER_FILE_PATTERN )
69
- else
70
- [ path ]
71
- end
72
-
73
- return paths.flat_map do |file|
74
- file_url = "file://%s" % [ file.expand_path ]
75
- observers = self.load( file )
76
- self.log.debug "Loaded observers %p..." % [ observers ]
77
- observers.each do |observer|
78
- observer.source = file_url
79
- end
80
- observers
81
- end
64
+ ### Return an iterator for all the observers supplied by the specified +loader+.
65
+ def self::each_in( loader )
66
+ return loader.observers
82
67
  end
83
68
 
84
69
 
70
+
85
71
  ### Create a new Observer with the specified +description+.
86
72
  def initialize( description, &block )
87
73
  @description = description
@@ -109,7 +109,7 @@ describe Arborist::Client do
109
109
  end
110
110
 
111
111
 
112
- it "can update the properties of managed nodes" do
112
+ it "can update the properties of managed nodes", :no_ci do
113
113
  client.update( duir: { ping: {rtt: 24} } )
114
114
 
115
115
  expect( manager.nodes['duir'].properties ).to include( 'ping' )
@@ -424,6 +424,7 @@ describe Arborist::Manager::TreeAPI, :testing_manager do
424
424
  manager.create_subscription( nil, 'node.delta', {type: 'host'} )
425
425
  end
426
426
 
427
+
427
428
  it "removes the subscription with the specified ID" do
428
429
  msg = pack_message( :unsubscribe, {subscription_id: subscription.id}, nil )
429
430
 
@@ -239,21 +239,32 @@ describe Arborist::Manager do
239
239
  expect( manager.nodes[ 'host_a' ] ).to be_down
240
240
  manager.nodes[ 'host_c' ].update( error: "gamma rays" )
241
241
  expect( manager.nodes[ 'host_c' ] ).to be_down
242
+ manager.nodes[ 'host_b_nfs' ].
243
+ update( ack: {sender: 'nancy_kerrigan', message: 'bad case of disk rot'} )
244
+ expect( manager.nodes[ 'host_b_nfs' ] ).to be_disabled
245
+ expect( manager.nodes[ 'host_b_nfs' ] ).to_not be_down
242
246
 
243
247
  iter = manager.reachable_nodes
244
248
 
245
249
  expect( iter ).to be_a( Enumerator )
246
250
 
247
- nodes = iter.to_a
248
- expect( nodes.size ).to eq( 6 )
249
- expect( nodes.map(&:identifier) ).to include(
251
+ nodes = iter.map( &:identifier )
252
+ expect( nodes ).to include(
250
253
  "_",
251
254
  "router",
252
255
  "host_b",
253
256
  "host_b_www",
254
- "host_b_nfs",
255
257
  "host_b_ssh"
256
258
  )
259
+ expect( nodes ).to_not include(
260
+ "host_b_nfs",
261
+ "host_c",
262
+ "host_c_www",
263
+ "host_a",
264
+ 'host_a_www',
265
+ 'host_a_smtp',
266
+ 'host_a_imap'
267
+ )
257
268
  end
258
269
 
259
270
 
@@ -25,7 +25,11 @@ describe Arborist::Monitor do
25
25
  end
26
26
 
27
27
 
28
- let( :testing_nodes ) {[ trunk_node, branch_node, leaf_node ]}
28
+ let( :testing_nodes ) {{
29
+ 'trunk' => trunk_node.to_hash,
30
+ 'branch' => branch_node.to_hash,
31
+ 'leaf' => leaf_node.to_hash
32
+ }}
29
33
 
30
34
 
31
35
  it "can be created with just a description" do
@@ -112,7 +116,7 @@ describe Arborist::Monitor do
112
116
 
113
117
  output = mon.run( testing_nodes )
114
118
  expect( output ).to be_a( Hash )
115
- expect( output ).to include( *(testing_nodes.map(&:identifier)) )
119
+ expect( output ).to include( *(testing_nodes.keys) )
116
120
  end
117
121
 
118
122
 
@@ -159,7 +163,7 @@ describe Arborist::Monitor do
159
163
  exec_input {|*| }
160
164
  exec_arguments do |nodes|
161
165
  Loggability[ Arborist ].debug "In the argument-builder."
162
- nodes.map {|n| n.identifier }
166
+ nodes.keys
163
167
  end
164
168
  end
165
169
 
@@ -183,7 +187,7 @@ describe Arborist::Monitor do
183
187
  exec 'cat'
184
188
 
185
189
  exec_input do |nodes, writer|
186
- writer.puts( nodes.map(&:identifier) )
190
+ writer.puts( nodes.keys )
187
191
  end
188
192
  handle_results do |pid, out, err|
189
193
  return out.readlines.map( &:chomp )
@@ -192,7 +196,7 @@ describe Arborist::Monitor do
192
196
 
193
197
  results = mon.run( testing_nodes )
194
198
 
195
- expect( results ).to eq( testing_nodes.map(&:identifier) )
199
+ expect( results ).to eq( testing_nodes.keys )
196
200
  end
197
201
 
198
202
 
@@ -203,7 +207,7 @@ describe Arborist::Monitor do
203
207
 
204
208
  exec_arguments {|*| }
205
209
  exec_input do |nodes, writer|
206
- writer.puts( nodes.map(&:identifier) )
210
+ writer.puts( nodes.keys )
207
211
  end
208
212
  handle_results do |pid, out, err|
209
213
  out.readlines.map( &:chomp ).map( &:upcase )
@@ -212,7 +216,7 @@ describe Arborist::Monitor do
212
216
 
213
217
  results = mon.run( testing_nodes )
214
218
 
215
- expect( results ).to eq( testing_nodes.map(&:identifier).map(&:upcase) )
219
+ expect( results ).to eq( testing_nodes.keys.map(&:upcase) )
216
220
  end
217
221
 
218
222
 
@@ -220,7 +224,7 @@ describe Arborist::Monitor do
220
224
  the_module = Module.new do
221
225
 
222
226
  def exec_input( nodes, writer )
223
- writer.puts( nodes.map {|n| n.identifier } )
227
+ writer.puts( nodes.keys )
224
228
  end
225
229
 
226
230
  def handle_results( pid, out, err )
@@ -241,7 +245,7 @@ describe Arborist::Monitor do
241
245
 
242
246
  expect( results ).to be_a( Hash )
243
247
  expect( results.size ).to eq( 3 )
244
- expect( results ).to include( *testing_nodes.map(&:identifier) )
248
+ expect( results ).to include( *testing_nodes.keys )
245
249
  expect( results['trunk'] ).to eq({ echoed: 'yep' })
246
250
  expect( results['branch'] ).to eq({ echoed: 'yep' })
247
251
  expect( results['leaf'] ).to eq({ echoed: 'yep' })
@@ -145,11 +145,39 @@ describe Arborist::Node do
145
145
  expect( node ).to be_acked
146
146
  end
147
147
 
148
- it "transitions to `up` from `acked` status if it's updated with an `ack` property" do
149
- node.update( ack: {message: "Maintenance", sender: 'mahlon'}, error: "Offlined" )
150
- node.update( ping_time: 0.02 )
148
+ it "transitions to `disabled` from `up` status if it's updated with an `ack` property" do
149
+ node.status = 'up'
150
+ node.update( ack: {message: "Maintenance", sender: 'mahlon'} )
151
151
 
152
- expect( node ).to be_up
152
+ expect( node ).to be_disabled
153
+ end
154
+
155
+ it "stays `disabled` if it gets an error" do
156
+ node.status = 'up'
157
+ node.update( ack: {message: "Maintenance", sender: 'mahlon'} )
158
+ node.update( error: "take me to the virus hospital" )
159
+
160
+ expect( node ).to be_disabled
161
+ expect( node.ack ).to_not be_nil
162
+ end
163
+
164
+ it "stays `disabled` if it gets a successful update" do
165
+ node.status = 'up'
166
+ node.update( ack: {message: "Maintenance", sender: 'mahlon'} )
167
+ node.update( ping: {time: 0.02} )
168
+
169
+ expect( node ).to be_disabled
170
+ expect( node.ack ).to_not be_nil
171
+ end
172
+
173
+ it "transitions to `unknown` from `disabled` status if its ack is cleared" do
174
+ node.status = 'up'
175
+ node.update( ack: {message: "Maintenance", sender: 'mahlon'} )
176
+ node.update( ack: nil )
177
+
178
+ expect( node ).to_not be_disabled
179
+ expect( node ).to be_unknown
180
+ expect( node.ack ).to be_nil
153
181
  end
154
182
 
155
183
  end
@@ -307,8 +335,9 @@ describe Arborist::Node do
307
335
 
308
336
 
309
337
  it "an ACKed node stays ACKed when reconstituted" do
310
- node.update(ack: {
311
- message: 'We know about the fire. It rages on.',
338
+ node.update( error: "there's a fire" )
339
+ node.update( ack: {
340
+ message: 'We know about the fire. It rages on.',
312
341
  sender: '1986 Labyrinth David Bowie'
313
342
  })
314
343
  cloned_node = concrete_class.from_hash( node.to_hash )
@@ -409,6 +438,7 @@ describe Arborist::Node do
409
438
 
410
439
 
411
440
  it "generates a node.acked event when a node is acked" do
441
+ node.update( error: 'ping failed ')
412
442
  events = node.update(ack: {
413
443
  message: "I have a poisonous friend. She's living in the house.",
414
444
  sender: 'Seabound'
@@ -10,14 +10,16 @@ describe Arborist do
10
10
 
11
11
  before( :all ) do
12
12
  @original_config_env = ENV[Arborist::CONFIG_ENV]
13
- @data_dir = Pathname( __FILE__ ).dirname + 'data'
14
- @nodes_dir = @data_dir + 'nodes'
15
13
  end
16
14
 
17
15
  before( :each ) do
18
16
  ENV.delete(Arborist::CONFIG_ENV)
19
17
  end
20
18
 
19
+ after( :each ) do
20
+ Arborist::Node::Root.reset
21
+ end
22
+
21
23
  after( :all ) do
22
24
  ENV[Arborist::CONFIG_ENV] = @original_config_env
23
25
  end
@@ -28,7 +30,7 @@ describe Arborist do
28
30
  end
29
31
 
30
32
 
31
- describe "configurability" do
33
+ describe "configurability", log: :fatal do
32
34
 
33
35
  before( :each ) do
34
36
  Configurability.configure_objects( Configurability.default_config )
@@ -133,8 +135,37 @@ describe Arborist do
133
135
  end
134
136
 
135
137
 
136
- it "can load all nodes in a directory and return a manager for them" do
137
- expect( described_class.manager_for(@nodes_dir) ).to be_a( Arborist::Manager )
138
+ it "can construct a Manager for all nodes provided by a Loader" do
139
+ loader = instance_double( Arborist::Loader )
140
+ expect( loader ).to receive( :nodes ).and_return([
141
+ testing_node('trunk'),
142
+ testing_node('branch', 'trunk'),
143
+ testing_node('leaf', 'branch')
144
+ ])
145
+
146
+ expect( described_class.manager_for(loader) ).to be_a( Arborist::Manager )
147
+ end
148
+
149
+
150
+ it "can construct a MonitorRunner for all monitors provided by a Loader" do
151
+ loader = instance_double( Arborist::Loader )
152
+ expect( loader ).to receive( :monitors ).and_return([
153
+ :a_monitor,
154
+ :another_monitor
155
+ ])
156
+
157
+ expect( described_class.monitor_runner_for(loader) ).to be_a( Arborist::MonitorRunner )
158
+ end
159
+
160
+
161
+ it "can construct an ObserverRunner for all observers provided by a Loader" do
162
+ loader = instance_double( Arborist::Loader )
163
+ expect( loader ).to receive( :observers ).and_return([
164
+ :an_observer,
165
+ :another_observer
166
+ ])
167
+
168
+ expect( described_class.observer_runner_for(loader) ).to be_a( Arborist::ObserverRunner )
138
169
  end
139
170
 
140
171