Module: Sfp::Agent

Defined in:
lib/sfpagent/agent.rb

Defined Under Namespace

Classes: Handler, Maintenance

Constant Summary collapse

NetHelper =
Object.new.extend(Sfp::Helper::Net)
CacheDir =
(Process.euid == 0 ? '/var/sfpagent' : File.expand_path(Dir.home + '/.sfpagent'))
DefaultPort =
1314
PIDFile =
"#{CacheDir}/sfpagent.pid"
LogFile =
"#{CacheDir}/sfpagent.log"
ModelFile =
"#{CacheDir}/sfpagent.model"
AgentsDataFile =
"#{CacheDir}/sfpagent.agents"
CacheModelFile =
"#{CacheDir}/cache.model"
BSigFile =
"#{CacheDir}/bsig.model"
BSigPIDFile =
"#{CacheDir}/bsig.pid"
BSigThreadsLockFile =
"#{CacheDir}/bsig.threads.lock.#{Time.now.to_i}"
ParentEliminator =
Sfp::Visitor::ParentEliminator.new
@@logger =
WEBrick::Log.new(LogFile, WEBrick::BasicLog::INFO ||
WEBrick::BasicLog::ERROR ||
WEBrick::BasicLog::FATAL ||
WEBrick::BasicLog::WARN)
@@current_model_hash =
nil
@@bsig =
nil
@@bsig_modified_time =
nil
@@bsig_engine =

create BSig engine instance

Sfp::BSig.new
@@runtime_lock =
Mutex.new
@@agents_database =
{}
@@agents_database_modified_time =
nil

Class Method Summary collapse

Class Method Details

.bsig_engineObject



321
322
323
# File 'lib/sfpagent/agent.rb', line 321

def self.bsig_engine
  @@bsig_engine
end

.configObject



54
55
56
# File 'lib/sfpagent/agent.rb', line 54

def self.config
  @@config
end

.delete_agentsObject



568
569
570
571
572
573
574
575
576
# File 'lib/sfpagent/agent.rb', line 568

def self.delete_agents
  File.open(AgentsDataFile, File::RDWR|File::CREAT, 0644) { |f|
    f.flock(File::LOCK_EX)
    f.rewind
    f.write('{}')
    f.flush
    f.truncate(f.pos)
  }
end

.execute_action(action) ⇒ Object

 Execute an action

Parameters:

  • action

    contains the action’s schema.



403
404
405
406
407
408
409
410
411
412
413
414
415
# File 'lib/sfpagent/agent.rb', line 403

def self.execute_action(action)
  begin
    logger = (@@config[:daemon] ? Sfp::Agent.logger : Logger.new(STDOUT))
    action_string = "#{action['name']} #{JSON.generate(action['parameters'])}"
    logger.info "Executing #{action_string} [Wait]"
    result = @@runtime.execute_action(action)
    logger.info "Executing #{action_string} " + (result ? "[OK]" : "[Failed]")
    return result
  rescue Exception => e
    logger.error "Executing #{action_string} [Failed] #{e}\n#{e.backtrace.join("\n")}"
  end
  false
end

.get_agentsObject



631
632
633
634
635
636
637
638
# File 'lib/sfpagent/agent.rb', line 631

def self.get_agents
  return {} if not File.exist?(AgentsDataFile)
  modified_time = File.mtime(AgentsDataFile)
  return @@agents_database if modified_time == @@agents_database_modified_time and
                              (Time.new - modified_time) < 60
  @@agents_database_modified_time = File.mtime(AgentsDataFile)
  @@agents_database = JSON[File.read(AgentsDataFile)]
end

.get_bsigObject

Return a BSig model from cached file



306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/sfpagent/agent.rb', line 306

def self.get_bsig
  return nil if not File.exist?(BSigFile)
  return @@bsig if File.mtime(BSigFile) == @@bsig_modified_time

  begin
    data = File.read(BSigFile)
    @@bsig = (data.length > 0 ? JSON[data] : nil)
    @@bsig_modified_time = File.mtime(BSigFile)
    return @@bsig
  rescue Exception => e
    Sfp::Agent.logger.error "Get the BSig model [Failed] #{e}\n#{e.backtrace.join("\n")}"
  end
  false
end

.get_cache_model(name) ⇒ Object



192
193
194
195
196
197
198
# File 'lib/sfpagent/agent.rb', line 192

def self.get_cache_model(name)
  if File.exist?(CacheModelFile)
    model = JSON[File.read(CacheModelFile)]
    return model[name] if model.has_key?(name)
  end
  nil
end

.get_log(n = 0) ⇒ Object



559
560
561
562
563
564
565
566
# File 'lib/sfpagent/agent.rb', line 559

def self.get_log(n=0)
  return '' if not File.exist?(LogFile)
  if n <= 0
    File.read(LogFile)
  else
    `tail -n #{n} #{LogFile}`
  end
end

.get_module_hash(name) ⇒ Object



455
456
457
458
459
460
461
462
463
464
465
466
467
# File 'lib/sfpagent/agent.rb', line 455

def self.get_module_hash(name)
  return nil if @@config[:modules_dir].to_s == ''

  module_dir = "#{@@config[:modules_dir]}/#{name}"
  if File.directory? module_dir
    if `which md5sum`.strip.length > 0
      return `find #{module_dir} -type f -exec md5sum {} + | awk '{print $1}' | sort | md5sum | awk '{print $1}'`.strip
    elsif `which md5`.strip.length > 0
      return `find #{module_dir} -type f -exec md5 {} + | awk '{print $4}' | sort | md5`.strip
    end
  end
  nil
end

.get_modulesObject



469
470
471
472
473
474
# File 'lib/sfpagent/agent.rb', line 469

def self.get_modules
  return [] if not (defined? @@modules and @@modules.is_a? Array)
  data = {}
  @@modules.each { |m| data[m] = get_module_hash(m) }
  data
end

.get_sfp(module_name) ⇒ Object



446
447
448
449
450
451
452
453
# File 'lib/sfpagent/agent.rb', line 446

def self.get_sfp(module_name)
  dir = @@config[:modules_dir]

  filepath = "#{dir}/#{module_name}/#{module_name}.sfp"
  sfp = parse(filepath).root
  sfp.accept(Sfp::Visitor::ParentEliminator.new)
  JSON.generate(sfp)
end

.get_state(as_sfp = true) ⇒ Object

Return the current state of the model.



332
333
334
335
336
337
338
339
340
341
342
# File 'lib/sfpagent/agent.rb', line 332

def self.get_state(as_sfp=true)
  @@runtime_lock.synchronize {
    return nil if !defined?(@@runtime) or @@runtime.nil?
    begin
      return @@runtime.get_state(as_sfp)
    rescue Exception => e
      Sfp::Agent.logger.error "Get state [Failed] #{e}\n#{e.backtrace.join("\n")}"
    end
  }
  false
end

.install_module(name, data, reload = true) ⇒ Object



526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
# File 'lib/sfpagent/agent.rb', line 526

def self.install_module(name, data, reload=true)
  return false if @@config[:modules_dir].to_s == '' or data.nil?

  if !File.directory? @@config[:modules_dir]
    File.delete @@config[:modules_dir] if File.exist? @@config[:modules_dir]
    Dir.mkdir(@@config[:modules_dir], 0700)
  end

  # delete old files
  module_dir = "#{@@config[:modules_dir]}/#{name}"
  system("rm -rf #{module_dir}") if File.exist? module_dir

  # save the archive
  Dir.mkdir("#{module_dir}", 0700)
  File.open("#{module_dir}/data.tgz", 'wb', 0600) { |f| f.syswrite data }

  # extract the archive and the files
  system("cd #{module_dir}; tar xvf data.tgz")
  Dir.entries(module_dir).each { |name|
    next if name == '.' or name == '..'
    if File.directory? "#{module_dir}/#{name}"
      system("cd #{module_dir}/#{name}; mv * ..; mv .* .. 2>/dev/null; cd ..; rm -rf #{name}")
    end
    system("cd #{module_dir}; rm data.tgz")
  }

  load_modules(@@config) if reload
  
  Sfp::Agent.logger.info "Installing module #{name} [OK]"

  true
end

.install_modules(modules) ⇒ Object



518
519
520
521
522
523
524
# File 'lib/sfpagent/agent.rb', line 518

def self.install_modules(modules)
  modules.each { |name,data| return false if not install_module(name, data, false) }

  load_modules(@@config)

  true
end

.load_modules(p = {}) ⇒ Object

 Load all modules in given directory.

options: :dir => directory that holds all modules



422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
# File 'lib/sfpagent/agent.rb', line 422

def self.load_modules(p={})
  dir = p[:modules_dir]

  @@modules = []
  counter = 0
  if dir != '' and File.exist?(dir)
    Sfp::Agent.logger.info "Modules directory: #{dir}"
    Dir.entries(dir).each { |name|
      next if name == '.' or name == '..' or File.file?("#{dir}/#{name}")
      module_file = "#{dir}/#{name}/#{name}.rb"
      next if not File.exist?(module_file)
      begin
        load module_file # use 'load' than 'require'
        Sfp::Agent.logger.info "Loading module #{dir}/#{name} [OK]"
        counter += 1
        @@modules << name
      rescue Exception => e
        Sfp::Agent.logger.warn "Loading module #{dir}/#{name} [Failed]\n#{e}"
      end
    }
  end
  Sfp::Agent.logger.info "Successfully loading #{counter} modules."
end

.loggerObject



50
51
52
# File 'lib/sfpagent/agent.rb', line 50

def self.logger
  @@logger
end

.pidObject



173
174
175
176
177
178
179
180
# File 'lib/sfpagent/agent.rb', line 173

def self.pid
  begin
    pid = File.read(PIDFile).to_i
    return pid if Process.kill 0, pid
  rescue
  end
  nil
end

.push_modules(p = {}) ⇒ Object

Push a list of modules to an agent using a script in $SFPAGENT_HOME/bin/install_module.

parameters:

:address => address of target agent
:port    => port of target agent
:modules => an array of modules' name that will be pushed


483
484
485
486
487
488
489
490
491
# File 'lib/sfpagent/agent.rb', line 483

def self.push_modules(p={})
  fail "Incomplete parameters." if !p[:modules] or !p[:address] or !p[:port]

  install_module = File.expand_path('../../../bin/install_module', __FILE__)
  modules = p[:modules].join(' ')
  cmd = "cd #{@@config[:modules_dir]}; #{install_module} #{p[:address]} #{p[:port]} #{modules}"
  result = `#{cmd}`
  (result =~ /status: ok/)
end

.resolve(path, as_sfp = true) ⇒ Object



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
392
393
394
395
396
397
# File 'lib/sfpagent/agent.rb', line 364

def self.resolve(path, as_sfp=true)
  return Sfp::Undefined.new if !defined?(@@runtime) or @@runtime.nil? or @@runtime.root.nil?
  begin
    path = path.simplify
    _, node, _ = path.split('.', 3)
    if @@runtime.root.has_key?(node)
      # local resolve
      parent, attribute = path.pop_ref
      mod = @@runtime.root.at?(parent)
      if mod.is_a?(Hash)
        mod[:_self].update_state
        state = mod[:_self].state
        return state[attribute] if state.has_key?(attribute)
      end
      return Sfp::Undefined.new
    end

    agents = get_agents
    if agents[node].is_a?(Hash)
      # remote resolve
      agent = agents[node]
      path = path[1, path.length-1].gsub /\./, '/'
      code, data = NetHelper.get_data(agent['sfpAddress'], agent['sfpPort'], "/state#{path}")
      if code.to_i == 200
        state = JSON[data]['state']
        return Sfp::Unknown.new if state == '<sfp::unknown>'
        return state if !state.is_a?(String) or state[0,15] != '<sfp::undefined'
      end
    end
  rescue Exception => e
    Sfp::Agent.logger.error "Resolve #{path} [Failed] #{e}\n#{e.backtrace.join("\n")}"
  end
  Sfp::Undefined.new
end

.resolve_model(path) ⇒ Object



344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
# File 'lib/sfpagent/agent.rb', line 344

def self.resolve_model(path)
  return Sfp::Undefined.new if !defined?(@@runtime) or @@runtime.nil? or @@runtime.root.nil?
  begin
    path = path.simplify
    value = @@runtime.model.at?(path)
    if value.is_a?(Sfp::Unknown)
      _, name, rest = path.split('.', 3)
      model = get_cache_model(name)
      if !model.nil? and model.has_key?('model')
        value = (rest.to_s.length <= 0 ? model['model'] : model['model'].at?("$.#{rest}"))
        value.accept(ParentEliminator) if value.is_a?(Hash)
      end
    end
    return value
  rescue Exception => e
    Sfp::Agent.logger.error "Resolve model #{path} [Failed] #{e}\n#{e.backtrace.join("\n")}"
  end
  Sfp::Undefined.new
end

.runtimeObject



58
59
60
# File 'lib/sfpagent/agent.rb', line 58

def self.runtime
  @@runtime
end

.set_agents(data) ⇒ Object

parameter:

:data => To delete an agent: { "agent_name" => nil }
         To add/modify an agent: { "agent_name" => { "sfpAddress" => "10.0.0.1", "sfpPort" => 1314 } }


582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
# File 'lib/sfpagent/agent.rb', line 582

def self.set_agents(data)
  data.each { |name,agent|
    return false if agent.is_a?(Hash) and (not agent['sfpAddress'].is_a?(String) or
                    agent['sfpAddress'].strip == '' or agent['sfpPort'].to_i <= 0)
  }

  updated = false
  agents = nil
  File.open(AgentsDataFile, File::RDWR|File::CREAT, 0644) { |f|
    f.flock(File::LOCK_EX)
    json = f.read
    agents = (json == '' ? {} : JSON[json])
    current_hash = agents.hash

    data.each { |k,v|
      if !agents.has_key?(k) or v.nil? or agents[k].hash != v.hash
        agents[k] = v
      end
    }
    agents.keys.each { |k| agents.delete(k) if agents[k].nil? }

    if current_hash != agents.hash
      updated = true
      f.rewind
      f.write(JSON.generate(agents))
      f.flush
      f.truncate(f.pos)
    end
  }

  if updated
    @@agents_database = agents
    Thread.new {
      # if updated then broadcast to other agents
      http_data = {'agents' => JSON.generate(data)}
      agents.each { |name,agent|
        begin
          code, _ = NetHelper.put_data(agent['sfpAddress'], agent['sfpPort'], '/agents', http_data, 5, 20)
          raise Exception if code != '200'
        rescue Exception => e
          Sfp::Agent.logger.warn "Push agents list to #{agent['sfpAddress']}:#{agent['sfpPort']} [Failed]"
        end
      }
    }
  end

  true
end

.set_bsig(bsig) ⇒ Object

Setting a new BSig model: set @@bsig variable, and save in cached file



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/sfpagent/agent.rb', line 281

def self.set_bsig(bsig)
  begin
    File.open(BSigFile, File::RDWR|File::CREAT, 0600) { |f|
      f.flock(File::LOCK_EX)
      Sfp::Agent.logger.info "Setting the BSig model [Wait]"
      f.rewind
      data = ''
      if !bsig.nil?
        bsig['operators'].each_index { |i| bsig['operators'][i]['id'] = i }
        data = JSON.generate(bsig)
      end
      f.write(data)
      f.flush
      f.truncate(f.pos)
    }
    Sfp::Agent.logger.info "Setting the BSig model [OK]"
    return true
  rescue Exception => e
    Sfp::Agent.logger.error "Setting the BSig model [Failed] #{e}\n#{e.backtrace.join("\n")}"
  end
  false
end

.set_cache_model(p = {}) ⇒ Object



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/sfpagent/agent.rb', line 200

def self.set_cache_model(p={})
  File.open(CacheModelFile, File::RDWR|File::CREAT, 0600) do |f|
    f.flock(File::LOCK_EX)
    json = f.read
    model = (json.length >= 2 ? JSON[json] : {})

    if p[:name]
      if p[:model]
        model[p[:name]] = p[:model]
        Sfp::Agent.logger.info "Saving cache model for #{p[:name]}..."
      else
        model.delete(p[:name]) if model.has_key?(p[:name])
        Sfp::Agent.logger.info "Deleting cache model for #{p[:name]}..."
      end
    else
      model = {}
      Sfp::Agent.logger.info "Deleting all cache model..."
    end

    f.rewind
    f.write(JSON.generate(model))
    f.flush
    f.truncate(f.pos)
  end

  true
end

.set_model(model) ⇒ Object

Save given model to cached file, and then reload the model.



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
# File 'lib/sfpagent/agent.rb', line 230

def self.set_model(model)
  begin
    # generate MD5 hash for the new model
    data = JSON.generate(model)
    new_model_hash = Digest::MD5.hexdigest(data)

    # save the new model if it's not same with the existing one
    if Digest::MD5.hexdigest(data) != @@current_model_hash
      Sfp::Agent.logger.info "Setting new model [Wait]"
      File.open(ModelFile, File::RDWR|File::CREAT, 0600) { |f|
        f.flock(File::LOCK_EX)
        f.rewind
        f.write(data)
        f.flush
        f.truncate(f.pos)
      }
      update_model
      Sfp::Agent.logger.info "Setting the model [OK]"
    end
    return true
  rescue Exception => e
    Sfp::Agent.logger.error "Setting the model [Failed] #{e}\n#{e.backtrace.join("\n")}"
  end
  false
end

.start(opts = {}) ⇒ Object

Start the agent.

options: :daemon => true if running as a daemon, false if as a console application :port => port of web server will listen to :ssl => set true to enable HTTPS :certfile => certificate file path for HTTPS :keyfile => key file path for HTTPS



71
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/sfpagent/agent.rb', line 71

def self.start(opts={})
  Sfp::Agent.logger.info "Starting agent..."
  puts "Starting agent..."

  @@config = opts

  Process.daemon if opts[:daemon] and not opts[:mock]

  begin
    # check modules directory, and create it if it's not exist
    opts[:modules_dir] = File.expand_path(opts[:modules_dir].to_s.strip != '' ? opts[:modules_dir].to_s : "#{CacheDir}/modules")
    Dir.mkdir(opts[:modules_dir], 0700) if not File.exist?(opts[:modules_dir])

    # load modules from cached directory
    load_modules(opts)

    # reload model
    update_model({:rebuild => true})

    # create web server
    server_type = WEBrick::SimpleServer
    port = (opts[:port] ? opts[:port] : DefaultPort)
    config = { :Host => '0.0.0.0',
               :Port => port,
               :ServerType => server_type,
               :pid => '/tmp/webrick.pid',
               :Logger => Sfp::Agent.logger }
    if opts[:ssl]
      config[:SSLEnable] = true
      config[:SSLVerifyClient] = OpenSSL::SSL::VERIFY_NONE
      config[:SSLCertificate] = OpenSSL::X509::Certificate.new(File.open(opts[:certfile]).read)
      config[:SSLPrivateKey] = OpenSSL::PKey::RSA.new(File.open(opts[:keyfile]).read)
      config[:SSLCertName] = [["CN", WEBrick::Utils::getservername]]
    end
    server = WEBrick::HTTPServer.new(config)
    server.mount("/", Sfp::Agent::Handler, Sfp::Agent.logger)

    # create maintenance object
    maintenance = Maintenance.new(opts)

    # trap signal
    ['INT', 'KILL', 'HUP'].each { |signal|
      trap(signal) {
        maintenance.stop

        Sfp::Agent.logger.info "Shutting down web server and BSig engine..."
        bsig_engine.stop
        loop do
          break if bsig_engine.status == :stopped
          sleep 1
        end
        server.shutdown
      }
    }

    File.open(PIDFile, 'w', 0644) { |f| f.write($$.to_s) }

    bsig_engine.start

    maintenance.start

    server.start if not opts[:mock]

  rescue Exception => e
    Sfp::Agent.logger.error "Starting the agent [Failed] #{e}\n#{e.backtrace.join("\n")}"

    raise e
  end
end

.statusObject

Return agent’s PID if it is running, otherwise nil.



184
185
186
187
188
189
190
# File 'lib/sfpagent/agent.rb', line 184

def self.status
  if pid.nil?
    puts "Agent is not running."
  else
    puts "Agent is running with PID #{pid}"
  end
end

.stop(opts = {}) ⇒ Object

Stop the agent’s daemon.



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/sfpagent/agent.rb', line 143

def self.stop(opts={})
  begin
    pid = File.read(PIDFile).to_i
    puts "Stopping agent with PID #{pid}..."
    Process.kill 'HUP', pid

    if not opts[:mock]
      begin
        sleep (Sfp::BSig::SleepTime + 0.25)

        Process.kill 0, pid
        Sfp::Agent.logger.info "Agent is still running."
        puts "Agent is still running."

        Sfp::Agent.logger.info "Killing agent."
        puts "Killing agent."
        Process.kill 9, pid
      rescue
        Sfp::Agent.logger.info "Agent has stopped."
        puts "Agent has stopped."
        File.delete(PIDFile) if File.exist?(PIDFile)
      end
    end

  rescue
    puts "Agent is not running."
    File.delete(PIDFile) if File.exist?(PIDFile)
  end
end

.uninstall_all_modules(p = {}) ⇒ Object



493
494
495
496
497
498
499
500
501
502
# File 'lib/sfpagent/agent.rb', line 493

def self.uninstall_all_modules(p={})
  return true if @@config[:modules_dir] == ''
  if system("rm -rf #{@@config[:modules_dir]}/*")
    load_modules(@@config)
    Sfp::Agent.logger.info "Deleting all modules [OK]"
    return true
  end
  Sfp::Agent.logger.info "Deleting all modules [Failed]"
  false
end

.uninstall_module(name) ⇒ Object



504
505
506
507
508
509
510
511
512
513
514
515
516
# File 'lib/sfpagent/agent.rb', line 504

def self.uninstall_module(name)
  return false if @@config[:modules_dir] == ''
  
  module_dir = "#{@@config[:modules_dir]}/#{name}"
  if File.directory?(module_dir)
    result = !!system("rm -rf #{module_dir}")
  else
    result = true
  end
  load_modules(@@config)
  Sfp::Agent.logger.info "Deleting module #{name} " + (result ? "[OK]" : "[Failed]")
  result
end

.update_model(p = {}) ⇒ Object

Reload the model from cached file.



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/sfpagent/agent.rb', line 258

def self.update_model(p={})
  if not File.exist?(ModelFile)
    Sfp::Agent.logger.info "There is no model in cache."
  else
    begin
      @@runtime_lock.synchronize {
        data = File.read(ModelFile)
        @@current_model_hash = Digest::MD5.hexdigest(data)
        if !defined?(@@runtime) or @@runtime.nil? or p[:rebuild]
          @@runtime = Sfp::Runtime.new(JSON[data])
        else
          @@runtime.set_model(JSON[data])
        end
      }
      Sfp::Agent.logger.info "Reloading the model in cache [OK]"
    rescue Exception => e
      Sfp::Agent.logger.error "Reloading the model in cache [Failed] #{e}\n#{e.backtrace.join("\n")}"
    end
  end
end

.whoami?Boolean

Returns:

  • (Boolean)


325
326
327
328
# File 'lib/sfpagent/agent.rb', line 325

def self.whoami?
  return nil if !defined?(@@runtime) or @@runtime.nil?
  @@runtime.whoami?
end