Ravn::Tactical::

Node class

Compute pack Node records for this kit’s set.

A lot of the compute pack setup logic is controlled by this model. The presence or absence of particular models are what designates a particular kit as a controller, as registered, etc.

  • A node is “registered” if it exists as a row in the nodes table.

  • If the nodes table is empty, it hasn’t been set up.

  • If the local node is registered, the node is treated as a controller.

  • If a different node is registered (i.e., one with a different device_id), it is the control node.

In the future, we will support multiple control nodes, but that isn’t implemented yet.

Constants

RANDOM_CALLSIGN_PREFIX

String to base random callsigns on.

VALID_DEVICE_ID_PATTERN

Pattern for matching a valid device ID

VALID_MISSION_CHKSUM_PATTERN

Pattern for matching a valid mission checksum

VALID_VERSION_PATTERN

Pattern for matching a valid (partial semantic) version

VALID_Z85_KEY_PATTERN

Pattern for matching a valid Z85-encoded Curve25519X key

Z85_CHAR

Pattern for matching a valid character in Z85 encoding

Attributes

callsign RW

The callsign assigned to the node.

control_device_id RW

The device ID of this node’s Control

created_at RW

The Time the node was registered with this host

deleted_at RW

The Time the node was unregistered; if this field is null the node has not been unregistered.

device_id RW

The unique ID of the node’s hardware

ip RW

The IP address this node was last seen at (not persisted)

last_seen_at RW

The Time the node was last seen on the network via the discovery beacon

mission_chksum RW

The current mission checksum (not persisteed)

public_key RW

The RbNaCl public key associated with the node.

system_time RW

The current system time (not persisteed)

version RW

The Ravn::Tactical version the node is using.

Public Class Methods

callsigns_hash()

Return an Array of all callsigns from undeleted nodes.

    # File lib/ravn/tactical/node.rb
136 def self::callsigns_hash
137     return self.naked.undeleted.select( :device_id, :callsign ).
138         as_hash( :device_id, :callsign )
139 end
for_localhost()

Return an instance for the current host, creating it as an unsaved instance if it has not yet been registered.

    # File lib/ravn/tactical/node.rb
157 def self::for_localhost
158     return self.local.first || new( Ravn::Tactical::Discovery.node_data ).freeze
159 end
last_random_callsign()

Return the last callsign used, or nil if the table is empty.

    # File lib/ravn/tactical/node.rb
143 def self::last_random_callsign
144     return self.with_random_callsign.ordered_by_callsign.reverse.naked.get( :callsign )
145 end
local_is_registered?()

Returns true if the local node has been registered, i.e., is either a control or a failover.

    # File lib/ravn/tactical/node.rb
164 def self::local_is_registered?
165     return !self.local.empty?
166 end
random_callsign()

Get a random unique callsign.

    # File lib/ravn/tactical/node.rb
149 def self::random_callsign
150     previous_callsign = self.last_random_callsign || RANDOM_CALLSIGN_PREFIX + '000'
151     return previous_callsign.succ
152 end
register_self()

Create an instance for the current host if one doesn’t already exist.

    # File lib/ravn/tactical/node.rb
170 def self::register_self
171     fields = Ravn::Tactical::Discovery.node_data.dup
172     self.log.info "Registering local node with data: %p" % [ fields ]
173 
174     device_id = fields.delete( :device_id ) or
175         raise ScriptError, "discovery node data doesn't contain a device_id?!?"
176 
177     return self.find_or_create( device_id: device_id ) do |new_instance|
178         new_instance.set( fields )
179     end
180 end

Public Instance Methods

by_device_id( device_id )

Limit the dataset to the node with the given device_id.

    # File lib/ravn/tactical/node.rb
116 def by_device_id( device_id )
117     return self.where( device_id: device_id )
118 end
changes()

Ravn::Tactical::Changes that have yet to be applied to this node.

   # File lib/ravn/tactical/node.rb
81 one_to_many :changes
control()

Limit the dataset to the current host’s Control. Note that this yields all non-failover nodes if the host itself is a Control.

    # File lib/ravn/tactical/node.rb
110 def control
111     return self.exclude( :is_failover )
112 end
control?()
Alias for: is_control?
discovery_info()

Return a string containing discovery information, or an empty string if the object doesn’t have any.

    # File lib/ravn/tactical/node.rb
431 def discovery_info
432     parts = []
433 
434     parts << "@%s" % [ self.ip ] if self.ip
435 
436     return '' if parts.empty?
437     return ' ' + parts.join( ' ' )
438 end
except_local()

Limit the dataset to nodes other than this host.

   # File lib/ravn/tactical/node.rb
97 def except_local
98     return self.exclude( device_id: Ravn.device_id )
99 end
failover?()
Alias for: is_failover?
failovers()

Limit the dataset to just the records for registered failovers.

    # File lib/ravn/tactical/node.rb
103 def failovers
104     return self.where( :is_failover )
105 end
inspect_details()

Ravn::Inspection API – Return the detail part of the inspect content for this object.

    # File lib/ravn/tactical/node.rb
418 def inspect_details
419     return %|[%s] #%s "%s" %s v%s| % [
420         self.device_id || '0',
421         self.id ? self.id : '(unregistered)',
422         self.callsign,
423         self.discovery_info,
424         self.version,
425     ]
426 end
is_control?()

Returns true if this node is the control node for the current host.

    # File lib/ravn/tactical/node.rb
302 def is_control?
303     return false unless self.is_registered?
304     return false if !self.local? && self.class.local_is_registered?
305     return !self.is_failover?
306 end
Also aliased as: control?
is_failover?()

Returns true if the current node has been marked as a failover

    # File lib/ravn/tactical/node.rb
311 def is_failover?
312     return (self.is_registered? && self.is_failover) ? true : false
313 end
Also aliased as: failover?
is_local?()

Returns true if this node describes the current host.

    # File lib/ravn/tactical/node.rb
295 def is_local?
296     return self.device_id == Ravn.device_id
297 end
Also aliased as: local?
is_registered?()

Returns true if this node is in the nodes database.

    # File lib/ravn/tactical/node.rb
288 def is_registered?
289     return self.id ? true : false
290 end
Also aliased as: registered?
local()

Limit the dataset to just the record for this host.

   # File lib/ravn/tactical/node.rb
91 def local
92     return self.where( device_id: Ravn.device_id ).limit( 1 )
93 end
local?()
Alias for: is_local?
merge_discovery_data( other_node )

Merge the given other_node‘s discovery data with this instance.

    # File lib/ravn/tactical/node.rb
276 def merge_discovery_data( other_node )
277     self.log.debug "Merging discovery data from %p" % [ other_node ]
278 
279     self.ip ||= other_node.ip
280     self.system_time ||= other_node.system_time
281     self.last_seen_at ||= other_node.last_seen_at
282     self.mission_chksum ||= other_node.mission_chksum
283     self.control_device_id ||= other_node.control_device_id
284 end
next_change()

Returns the next pending Change from the change queue for this node.

    # File lib/ravn/tactical/node.rb
318 def next_change
319     return self.changes_dataset.older_first.first
320 end
online?()

Returns true if the node has an ip addresss.

    # File lib/ravn/tactical/node.rb
270 def online?
271     return self.ip ? true: false
272 end
ordered_by_callsign()

Order the dataset alphanumerically by callsign

    # File lib/ravn/tactical/node.rb
122 def ordered_by_callsign
123     return self.order( :callsign )
124 end
public_key=( new_key )

Set the host’s public key to the specified new_key. Accepts either a Z85-encoded ASCII key or a RbNaCl::PublicKey.

    # File lib/ravn/tactical/node.rb
246 def public_key=( new_key )
247     new_key = new_key.to_bytes if new_key.respond_to?( :to_bytes )
248     new_key = Zyre.z85_encode( new_key ) if new_key.encoding == Encoding::ASCII_8BIT
249 
250     self[:public_key] = new_key
251 end
registered?()
Alias for: is_registered?
update_discovery_info()

Check the discovery service for updated info for this node and merge it if it exists. If it isn’t in the discovery table, clear discovery info from the node.

    # File lib/ravn/tactical/node.rb
257 def update_discovery_info
258     if ( discovery_info = Ravn::Tactical::Discovery.nodes[ self.device_id ] )
259         self.merge_discovery_data( discovery_info )
260     else
261         self.log.info "Node is not online; clearing discovery info"
262         self.ip = nil
263         self.system_time = nil
264         self.mission_chksum = nil
265     end
266 end
valid_for_discovery?()

Validate model values for purposes of discovery. Discovery doesn’t require that all the fields necessary for saving be present.

    # File lib/ravn/tactical/node.rb
325 def valid_for_discovery?
326     return self.errors.empty? if self.frozen?
327     self.errors.clear
328 
329     self.around_validation do
330         self.before_validation
331         self.validate_persisted_fields
332         self.validate_unpersisted_fields
333         self.after_validation
334     end
335 
336     return self.errors.empty?
337 end
validate()

Sequel::Model API – validate model values before saving

    # File lib/ravn/tactical/node.rb
341 def validate
342     super
343     self.validate_persisted_fields
344     self.validate_callsign
345     self.validates_unique( :device_id )
346 end
validate_callsign()

Ensure the node has been assigned a unique callsign

    # File lib/ravn/tactical/node.rb
379 def validate_callsign
380     self.validates_presence( :callsign )
381     self.validates_unique( :callsign )
382 end
validate_device_id()

Ensure the node record has its device ID

    # File lib/ravn/tactical/node.rb
386 def validate_device_id
387     self.validates_format( VALID_DEVICE_ID_PATTERN, :device_id )
388     self.validates_presence( :device_id )
389 end
validate_mission_chksum()

Ensure the mission checksum is valid if it’s set

    # File lib/ravn/tactical/node.rb
373 def validate_mission_chksum
374     self.validates_format( VALID_MISSION_CHKSUM_PATTERN, :mission_chksum, allow_nil: true )
375 end
validate_persisted_fields()

Run validations that apply to fields that are persisted in the database.

    # File lib/ravn/tactical/node.rb
350 def validate_persisted_fields
351     self.validates_schema_types( [:created_at, :last_seen_at, :callsign, :version] )
352     self.validate_device_id
353     self.validate_version
354     self.validate_public_key
355 end
validate_public_key()

Ensure the public_key is correct if it’s set.

    # File lib/ravn/tactical/node.rb
399 def validate_public_key
400     raw_key = self[:public_key] or return self.errors.add( :public_key, 'is missing' )
401     key = raw_key[ VALID_Z85_KEY_PATTERN, 1 ] or
402         return self.errors.add( :public_key, 'is malformed' )
403     decoded_key = Zyre.z85_decode( key ) or
404         return self.errors.add( :public_key, 'invalid Z85-encoded value' )
405 
406     begin
407         RbNaCl::PublicKey.new( decoded_key )
408     rescue ArgumentError => err
409         self.log.error "%p validating the public key (%d bytes): %s" %
410             [ err.class, decoded_key.bytesize, err.message ]
411         self.errors.add( :public_key, err.message )
412     end
413 end
validate_system_time()

Ensure the system time (via discovery) is valid.

    # File lib/ravn/tactical/node.rb
366 def validate_system_time
367     self.validates_presence( :system_time )
368     self.validates_numeric( :system_time )
369 end
validate_unpersisted_fields()

Run validateions that apply to fields that are NOT persisted in the database.

    # File lib/ravn/tactical/node.rb
359 def validate_unpersisted_fields
360     self.validate_system_time
361     self.validate_mission_chksum
362 end
validate_version()

Ensure the version is valid.

    # File lib/ravn/tactical/node.rb
393 def validate_version
394     self.validates_format( VALID_VERSION_PATTERN, :version )
395 end
with_random_callsign()

Limit the dataset to records that still have a random callsign.

    # File lib/ravn/tactical/node.rb
128 def with_random_callsign
129     return self.where( Sequel.like(:callsign, RANDOM_CALLSIGN_PREFIX + '%') )
130 end