arborist 0.0.1.pre20160106113421 → 0.0.1.pre20160128152542

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/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