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.
- 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
- 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.
Return an Array of all callsigns from undeleted nodes.
136 def self::callsigns_hash
137 return self.naked.undeleted.select( :device_id, :callsign ).
138 as_hash( :device_id, :callsign )
139 end
Return an instance for the current host, creating it as an unsaved instance if it has not yet been registered.
157 def self::for_localhost
158 return self.local.first || new( Ravn::Tactical::Discovery.node_data ).freeze
159 end
Return the last callsign used, or nil if the table is empty.
143 def self::last_random_callsign
144 return self.with_random_callsign.ordered_by_callsign.reverse.naked.get( :callsign )
145 end
Returns true
if the local node has been registered, i.e., is either a control or a failover.
164 def self::local_is_registered?
165 return !self.local.empty?
166 end
Get a random unique callsign.
149 def self::random_callsign
150 previous_callsign = self.last_random_callsign || RANDOM_CALLSIGN_PREFIX + '000'
151 return previous_callsign.succ
152 end
Create an instance for the current host if one doesn’t already exist.
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
by_device_id( device_id )
Limit the dataset to the node with the given device_id
.
116 def by_device_id( device_id )
117 return self.where( device_id: device_id )
118 end
Ravn::Tactical::Changes that have yet to be applied to this node.
Limit the dataset to the current host’s Control. Note that this yields all non-failover nodes if the host itself is a Control.
110 def control
111 return self.exclude( :is_failover )
112 end
Return a string containing discovery information, or an empty string if the object doesn’t have any.
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
Limit the dataset to nodes other than this host.
97 def except_local
98 return self.exclude( device_id: Ravn.device_id )
99 end
Limit the dataset to just the records for registered failovers.
103 def failovers
104 return self.where( :is_failover )
105 end
Ravn::Inspection API – Return the detail part of the inspect content for this object.
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
Returns true
if this node is the control node for the current host.
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
Returns true if the current node has been marked as a failover
311 def is_failover?
312 return (self.is_registered? && self.is_failover) ? true : false
313 end
Returns true
if this node describes the current host.
295 def is_local?
296 return self.device_id == Ravn.device_id
297 end
Returns true
if this node is in the nodes database.
288 def is_registered?
289 return self.id ? true : false
290 end
Limit the dataset to just the record for this host.
91 def local
92 return self.where( device_id: Ravn.device_id ).limit( 1 )
93 end
merge_discovery_data( other_node )
Merge the given other_node
‘s discovery data with this instance.
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
Returns the next pending Change from the change queue for this node.
318 def next_change
319 return self.changes_dataset.older_first.first
320 end
Returns true
if the node has an ip
addresss.
270 def online?
271 return self.ip ? true: false
272 end
Order the dataset alphanumerically by callsign
122 def ordered_by_callsign
123 return self.order( :callsign )
124 end
Set the host’s public key to the specified new_key
. Accepts either a Z85-encoded ASCII key or a RbNaCl::PublicKey.
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
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.
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
Validate model values for purposes of discovery. Discovery doesn’t require that all the fields necessary for saving be present.
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
Sequel::Model API – validate model values before saving
341 def validate
342 super
343 self.validate_persisted_fields
344 self.validate_callsign
345 self.validates_unique( :device_id )
346 end
Ensure the node has been assigned a unique callsign
379 def validate_callsign
380 self.validates_presence( :callsign )
381 self.validates_unique( :callsign )
382 end
Ensure the node record has its device ID
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
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.
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
Ensure the public_key
is correct if it’s set.
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
Ensure the system time (via discovery) is valid.
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.
359 def validate_unpersisted_fields
360 self.validate_system_time
361 self.validate_mission_chksum
362 end
Ensure the version is valid.
393 def validate_version
394 self.validates_format( VALID_VERSION_PATTERN, :version )
395 end
Limit the dataset to records that still have a random callsign.
128 def with_random_callsign
129 return self.where( Sequel.like(:callsign, RANDOM_CALLSIGN_PREFIX + '%') )
130 end