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

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



72
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
# File 'lib/cosmos/system/system.rb', line 72

def initialize(filename = nil)
  raise "Cosmos::System created twice" unless @@instance.nil?
  @@instance = self
  @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
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



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

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



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

def self.commands
  return self.instance.commands
end

.configuration_nameString

Returns Configuration name.

Returns:

  • (String)

    Configuration name



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

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



196
197
198
199
# File 'lib/cosmos/system/system.rb', line 196

def self.instance(filename = nil)
  @@instance ||= self.new(filename)
  return @@instance
end

.limitsLimits

Returns Access to the limits definition.

Returns:

  • (Limits)

    Access to the limits definition



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

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



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

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



432
433
434
# File 'lib/cosmos/system/system.rb', line 432

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



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

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



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

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



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

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



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

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



396
397
398
399
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
# File 'lib/cosmos/system/system.rb', line 396

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.



207
208
209
210
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
# File 'lib/cosmos/system/system.rb', line 207

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.



335
336
337
338
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
# File 'lib/cosmos/system/system.rb', line 335

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



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

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