Class: Cosmos::System

Inherits:
Object show all
Defined in:
lib/cosmos/system/system.rb,
ext/cosmos/ext/telemetry/telemetry.c

Overview

System is the primary entry point into the COSMOS framework. It captures system wide configuration items such as the available ports and paths to various files used by the system. The #commands, #telemetry, and #limits class variables are the primary access points for applications. The #targets variable provides access to all the targets defined by the system. Its primary responsibily is to load the system configuration file and create all the Target instances. It also saves and restores configurations using a MD5 checksum over the entire configuration to detect changes.

Constant Summary collapse

KNOWN_PORTS =

Known COSMOS ports

['CTS_API', 'TLMVIEWER_API', 'CTS_PREIDENTIFIED']
KNOWN_PATHS =

Known COSMOS paths

['LOGS', 'TMP', 'SAVED_CONFIG', 'TABLES', 'HANDBOOKS', 'PROCEDURES']
@@instance =
nil
@@instance_mutex =
Mutex.new

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(filename = nil) ⇒ System

Create a new System object. Note, this should not be called directly but you should instead use System.instance and treat this class as a singleton.

Parameters:

  • filename (String) (defaults to: nil)

    Full path to the system configuration file to read. Be default this is <Cosmos::USERPATH>/config/system/system.txt



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/cosmos/system/system.rb', line 73

def initialize(filename = nil)
  raise "Cosmos::System created twice" unless @@instance.nil?
  @targets = {}
  @targets['UNKNOWN'] = Target.new('UNKNOWN')
  @config = nil
  @commands = nil
  @telemetry = nil
  @limits = nil
  @cmd_tlm_version = nil
  @default_packet_log_writer = PacketLogWriter
  @default_packet_log_reader = PacketLogReader
  @use_dns = true
  @acl = nil
  @staleness_seconds = 30
  @limits_set = :DEFAULT

  @ports = {}
  @ports['CTS_API'] = 7777
  @ports['TLMVIEWER_API'] = 7778
  @ports['CTS_PREIDENTIFIED'] = 7779

  @paths = {}
  @paths['LOGS'] = File.join(USERPATH, 'outputs', 'logs')
  @paths['TMP'] = File.join(USERPATH, 'outputs', 'tmp')
  @paths['SAVED_CONFIG'] = File.join(USERPATH, 'outputs', 'saved_config')
  @paths['TABLES'] = File.join(USERPATH, 'outputs', 'tables')
  @paths['HANDBOOKS'] = File.join(USERPATH, 'outputs', 'handbooks')
  @paths['PROCEDURES'] = [File.join(USERPATH, 'procedures')]

  unless filename
    system_arg = false
    ARGV.each do |arg|
      if system_arg
        filename = File.join(USERPATH, 'config', 'system', arg)
        break
      end
      system_arg = true if arg == '--system'
    end
    filename = File.join(USERPATH, 'config', 'system', 'system.txt') unless filename
  end
  process_file(filename)
  ENV['COSMOS_LOGS_DIR'] = @paths['LOGS']

  @initial_filename = filename
  @initial_config = nil
  @@instance = self
end

Class Method Details

.clear_countersObject

Clear the command and telemetry counters for all the targets as well as the counters associated with the Commands and Telemetry instances



131
132
133
134
135
136
137
138
# File 'lib/cosmos/system/system.rb', line 131

def self.clear_counters
  self.instance.targets.each do |target_name, target|
    target.cmd_cnt = 0
    target.tlm_cnt = 0
  end
  self.instance.telemetry.clear_counters
  self.instance.commands.clear_counters
end

.commandsCommands

Returns Access to the command definiton.

Returns:

  • (Commands)

    Access to the command definiton



147
148
149
# File 'lib/cosmos/system/system.rb', line 147

def self.commands
  return self.instance.commands
end

.configuration_nameString

Returns Configuration name.

Returns:

  • (String)

    Configuration name



122
123
124
125
126
127
# File 'lib/cosmos/system/system.rb', line 122

def self.configuration_name
  # In order not to make the @config instance variable accessable to the
  # outside (using a regular accessor method) we use the ability of the
  # object to grab its own instance variable
  self.instance.instance_variable_get(:@config).name
end

.instance(filename = nil) ⇒ System

Returns The System singleton.

Returns:

  • (System)

    The System singleton



197
198
199
200
201
202
203
# File 'lib/cosmos/system/system.rb', line 197

def self.instance(filename = nil)
  return @@instance if @@instance
  @@instance_mutex.synchronize do
    @@instance ||= self.new(filename)
    return @@instance
  end
end

.limitsLimits

Returns Access to the limits definition.

Returns:

  • (Limits)

    Access to the limits definition



169
170
171
# File 'lib/cosmos/system/system.rb', line 169

def self.limits
  return self.instance.limits
end

.limits_set=(limits_set) ⇒ Object

Change the system limits set

Parameters:

  • The (Symbol)

    name of the limits set. :DEFAULT is always an option but limits sets are user defined



192
193
194
# File 'lib/cosmos/system/system.rb', line 192

def self.limits_set=(limits_set)
  self.instance.limits_set = limits_set
end

.load_configuration(name = nil) ⇒ String

Load the specified configuration by iterating through the SAVED_CONFIG directory looking for a matching MD5 sum. Updates the internal state so subsequent commands and telemetry methods return the new configuration.

Parameters:

  • name (String) (defaults to: nil)

    MD5 string which identifies the configuration. Pass nil to load the default configuration.

Returns:

  • (String)

    The actual configuration loaded



436
437
438
# File 'lib/cosmos/system/system.rb', line 436

def self.load_configuration(name = nil)
  return self.instance.load_configuration(name)
end

.telemetryTelemetry

Returns Access to the telemetry definition.

Returns:

  • (Telemetry)

    Access to the telemetry definition



158
159
160
# File 'lib/cosmos/system/system.rb', line 158

def self.telemetry
  return self.instance.telemetry
end

Instance Method Details

#aclACL

Returns Access control list showing which machines can have access.

Returns:

  • (ACL)

    Access control list showing which machines can have access



51
# File 'lib/cosmos/system/system.rb', line 51

instance_attr_reader :acl

#cmd_tlm_versionString

Returns Arbitrary string containing the version.

Returns:

  • (String)

    Arbitrary string containing the version



37
# File 'lib/cosmos/system/system.rb', line 37

instance_attr_reader :cmd_tlm_version

#commandsCommands

Returns Access to the command definiton.

Returns:

  • (Commands)

    Access to the command definiton



141
142
143
144
# File 'lib/cosmos/system/system.rb', line 141

def commands
  load_packets() unless @config
  return @commands
end

#default_packet_log_readerPacketLogReader

Returns Class used to read log files.

Returns:



41
# File 'lib/cosmos/system/system.rb', line 41

instance_attr_reader :default_packet_log_reader

#default_packet_log_writerPacketLogWriter

Returns Class used to create log files.

Returns:



39
# File 'lib/cosmos/system/system.rb', line 39

instance_attr_reader :default_packet_log_writer

#initial_configPacketDefinition

Returns Stores the initial packet list used when this System was initialized.

Returns:

  • (PacketDefinition)

    Stores the initial packet list used when this System was initialized



49
# File 'lib/cosmos/system/system.rb', line 49

instance_attr_reader :initial_config

#initial_filenameString

Returns Stores the initial configuration file used when this System was initialized.

Returns:

  • (String)

    Stores the initial configuration file used when this System was initialized



46
# File 'lib/cosmos/system/system.rb', line 46

instance_attr_reader :initial_filename

#limitsLimits

Returns Access to the limits definition.

Returns:

  • (Limits)

    Access to the limits definition



163
164
165
166
# File 'lib/cosmos/system/system.rb', line 163

def limits
  load_packets() unless @config
  return @limits
end

#limits_setSymbol

Returns The current limits set.

Returns:

  • (Symbol)

    The current limits set



57
# File 'lib/cosmos/system/system.rb', line 57

instance_attr_reader :limits_set

#limits_set=(limits_set) ⇒ Object

Change the system limits set

Parameters:

  • The (Symbol)

    name of the limits set. :DEFAULT is always an option but limits sets are user defined



177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/cosmos/system/system.rb', line 177

def limits_set=(limits_set)
  load_packets() unless @config
  new_limits_set = limits_set.to_s.upcase.intern
  if @limits_set != new_limits_set
    if @config.limits_sets.include?(new_limits_set)
      @limits_set = new_limits_set
      Logger.info("Limits Set Changed to: #{@limits_set}")
      CmdTlmServer.instance.post_limits_event(:LIMITS_SET, System.limits_set) if defined? CmdTlmServer and CmdTlmServer.instance
    else
      raise "Unknown limits set requested: #{new_limits_set}"
    end
  end
end

#load_configuration(name = nil) ⇒ String

Load the specified configuration by iterating through the SAVED_CONFIG directory looking for a matching MD5 sum. Updates the internal state so subsequent commands and telemetry methods return the new configuration.

Parameters:

  • name (String) (defaults to: nil)

    MD5 string which identifies the configuration. Pass nil to load the default configuration.

Returns:

  • (String)

    The actual configuration loaded



400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
# File 'lib/cosmos/system/system.rb', line 400

def load_configuration(name = nil)
  if name and @config
    # Make sure they're requesting something other than the current
    # configuration.
    if name != @config.name
      # If they want the initial configuration we can just swap out the
      # current configuration without doing any file processing
      if name == @initial_config.name
        update_config(@initial_config)
      else
        # Look for the requested configuration in the saved configurations
        configuration_directory = find_configuration(name)
        if configuration_directory
          # We found the configuration requested. Reprocess the system.txt
          # and reload the packets
          process_file(File.join(configuration_directory, 'system.txt'), configuration_directory)
          load_packets()
        else
          # We couldn't find the configuration request. Reload the
          # initial configuration
          update_config(@initial_config)
        end
      end
      @telemetry.reset
    end
  else
    # Ensure packets have been lazy loaded
    System.commands()
    current_config = @config
    @config = @initial_config
    @telemetry.reset if current_config != @initial_config
  end
  return @config.name
end

#pathsHash<String,String>

Returns Hash of all the known paths and their values.

Returns:



35
# File 'lib/cosmos/system/system.rb', line 35

instance_attr_reader :paths

#portsHash<String,Fixnum>

Returns Hash of all the known ports and their values.

Returns:

  • (Hash<String,Fixnum>)

    Hash of all the known ports and their values



33
# File 'lib/cosmos/system/system.rb', line 33

instance_attr_reader :ports

#process_file(filename, configuration_directory = nil) ⇒ Object

Process the system.txt configuration file

Parameters:

  • filename (String)

    The configuration file

  • configuration_directory (String) (defaults to: nil)

    The configuration directory to search for the target command and telemetry files. Pass nil to look in the default location of <USERPATH>/config/targets.



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/cosmos/system/system.rb', line 211

def process_file(filename, configuration_directory = nil)
  @targets = {}
  # Set config to nil so things will lazy load later
  @config = nil
  acl_list = []
  all_allowed = false
  first_procedures_path = true

  Cosmos.set_working_dir do
    parser = ConfigParser.new

    # First pass - Everything except targets
    parser.parse_file(filename) do |keyword, parameters|
      case keyword
      when 'AUTO_DECLARE_TARGETS', 'DECLARE_TARGET'
        # Will be handled by second pass

      when 'PORT'
        usage = "#{keyword} <PORT NAME> <PORT VALUE>"
        parser.verify_num_parameters(2, 2, usage)
        port_name = parameters[0].to_s.upcase
        @ports[port_name] = Integer(parameters[1])
        Logger.warn("Unknown port name given: #{port_name}") unless KNOWN_PORTS.include?(port_name)

      when 'PATH'
        usage = "#{keyword} <PATH NAME> <PATH>"
        parser.verify_num_parameters(2, 2, usage)
        path_name = parameters[0].to_s.upcase
        path = File.expand_path(parameters[1])
        if path_name == 'PROCEDURES'
          if first_procedures_path
            @paths[path_name] = []
            first_procedures_path = false
          end
          @paths[path_name] << path
        else
          @paths[path_name] = path
        end
        unless Dir.exist?(path)
          begin
            FileUtils.mkdir_p(path)
            raise "Path creation failed: #{path}" unless File.exist?(path)
            Logger.info "Created PATH #{path_name} #{path}"
          rescue Exception => err
            Logger.error "Problem creating PATH #{path_name} #{path}\n#{err.formatted}"
          end
        end
        Logger.warn("Unknown path name given: #{path_name}") unless KNOWN_PATHS.include?(path_name)

      when 'DEFAULT_PACKET_LOG_WRITER'
        usage = "#{keyword} <FILENAME>"
        parser.verify_num_parameters(1, 1, usage)
        @default_packet_log_writer = Cosmos.require_class(parameters[0])

      when 'DEFAULT_PACKET_LOG_READER'
        usage = "#{keyword} <FILENAME>"
        parser.verify_num_parameters(1, 1, usage)
        @default_packet_log_reader = Cosmos.require_class(parameters[0])

      when 'DISABLE_DNS'
        usage = "#{keyword}"
        parser.verify_num_parameters(0, 0, usage)
        @use_dns = false

      when 'ALLOW_ACCESS'
        parser.verify_num_parameters(1, 1, "#{keyword} <IP Address or Hostname>")
        begin
          addr = parameters[0].upcase
          if addr == 'ALL'
            all_allowed = true
            acl_list = []
          end

          unless all_allowed
            first_char = addr[0..0]
            if !((first_char =~ /[1234567890]/) or (first_char == '*') or (addr.upcase == 'ALL'))
              # Try to lookup IP Address
              info = Socket.gethostbyname(addr)
              addr = "#{info[3].getbyte(0)}.#{info[3].getbyte(1)}.#{info[3].getbyte(2)}.#{info[3].getbyte(3)}"
              if (acl_list.empty?)
                acl_list << 'allow'
                acl_list << '127.0.0.1'
              end
              acl_list << 'allow'
              acl_list << addr
            else
              raise "badly formatted address #{addr}" unless /\b(?:\d{1,3}\.){3}\d{1,3}\b/.match(addr)
              if (acl_list.empty?)
                acl_list << 'allow'
                acl_list << '127.0.0.1'
              end
              acl_list << 'allow'
              acl_list << addr
            end
          end
        rescue => err
          raise parser.error("Problem with ALLOW_ACCESS due to #{err.message.strip}")
        end

      when 'STALENESS_SECONDS'
        parser.verify_num_parameters(1, 1, "#{keyword} <Value in Seconds>")
        @staleness_seconds = Integer(parameters[0])

      when 'CMD_TLM_VERSION'
        usage = "#{keyword} <VERSION>"
        parser.verify_num_parameters(1, 1, usage)
        @cmd_tlm_version = parameters[0]

      else
        # blank lines will have a nil keyword and should not raise an exception
        raise parser.error("Unknown keyword '#{keyword}'") if keyword
      end # case keyword
    end # parser.parse_file

    @acl = ACL.new(acl_list, ACL::ALLOW_DENY) unless acl_list.empty?

    # Second pass - Process targets
    process_targets(parser, filename, configuration_directory)

  end # Cosmos.set_working_dir
end

#process_targets(parser, filename, configuration_directory) ⇒ Object

Parse the system.txt configuration file looking for keywords associated with targets and create all the Target instances in the system.

Parameters:

  • parser (ConfigParser)

    Parser created by process_file

  • filename (String)

    The configuration file

  • configuration_directory (String)

    The configuration directory to search for the target command and telemetry files. Pass nil to look in the default location of <USERPATH>/config/targets.



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
# File 'lib/cosmos/system/system.rb', line 339

def process_targets(parser, filename, configuration_directory)
  parser.parse_file(filename) do |keyword, parameters|
    case keyword
    when 'AUTO_DECLARE_TARGETS'
      usage = "#{keyword}"
      parser.verify_num_parameters(0, 0, usage)
      path = File.join(USERPATH, 'config', 'targets')
      unless File.exist? path
        raise parser.error("#{path} must exist", usage)
      end
      system_found = false
      dirs = []
      Dir.foreach(File.join(USERPATH, 'config', 'targets')) { |dir_filename| dirs << dir_filename }
      dirs.sort!
      dirs.each do |dir_filename|
        if dir_filename[0] != '.'
          if dir_filename == dir_filename.upcase
            if dir_filename == 'SYSTEM'
              system_found = true
              next
            end
            target = Target.new(dir_filename)
            @targets[target.name] = target
          else
            raise parser.error("Target folder must be uppercase: '#{dir_filename}'")
          end
        end
      end
      if system_found
        target = Target.new('SYSTEM')
        @targets[target.name] = target
      end

    when 'DECLARE_TARGET'
      usage = "#{keyword} <TARGET NAME> <SUBSTITUTE TARGET NAME (Optional)> <TARGET FILENAME (Optional - defaults to target.txt)>"
      parser.verify_num_parameters(1, 3, usage)
      target_name = parameters[0].to_s.upcase
      substitute_name = nil
      substitute_name = ConfigParser.handle_nil(parameters[1])
      substitute_name.to_s.upcase if substitute_name
      if configuration_directory
        folder_name = File.join(configuration_directory, target_name)
      else
        folder_name = File.join(USERPATH, 'config', 'targets', target_name)
      end
      unless Dir.exist?(folder_name)
        raise parser.error("Target folder must exist '#{folder_name}'.")
      end
      target = Target.new(target_name, substitute_name, configuration_directory, ConfigParser.handle_nil(parameters[2]))
      @targets[target.name] = target
    end # case keyword
  end # parser.parse_file
end

#staleness_secondsInteger

Returns The number of seconds before a telemetry packet is considered stale.

Returns:

  • (Integer)

    The number of seconds before a telemetry packet is considered stale



55
# File 'lib/cosmos/system/system.rb', line 55

instance_attr_reader :staleness_seconds

#targetsHash<String,Target>

Returns Hash of all the known targets.

Returns:



53
# File 'lib/cosmos/system/system.rb', line 53

instance_attr_reader :targets

#telemetryTelemetry

Returns Access to the telemetry definition.

Returns:

  • (Telemetry)

    Access to the telemetry definition



152
153
154
155
# File 'lib/cosmos/system/system.rb', line 152

def telemetry
  load_packets() unless @config
  return @telemetry
end

#use_dnsBoolean

Returns Whether to use DNS to lookup IP addresses or not.

Returns:

  • (Boolean)

    Whether to use DNS to lookup IP addresses or not



43
# File 'lib/cosmos/system/system.rb', line 43

instance_attr_reader :use_dns