-------------------------------------------------------------------------
-- $Author: ururahy $
-- $Revision: 132629 $  - $Date: 2012-08-23 14:27:49 -0300 (Thu, 23 Aug 2012) $
-------------------------------------------------------------------------

require"scheduler"
require"oil"
local verbose = require "sga.verbose"
local ext = require "shellExtension" 

YES = true
NO = false

-------------------------------------------------------------------------
-- Construtores para as descrições dos SGAs
-------------------------------------------------------------------------

SGA_DAEMON = {}
SGAD_CONF = {}
CSFS = {}
IPERF = {}

BIG_ENDIAN = "BIG_ENDIAN"
LITTLE_ENDIAN = "LITTLE_ENDIAN"
NO_CAPACITY = -1

SGA_DAEMON.CType = {
  CPU = "CPU", 
  DISK_READ = "DISK_READ", 
  DISK_WRITE = "DISK_WRITE",
  NET = "NET"
}

-- Configuração padrão para as máquinas
SGAD_CONF.node_defaults = {
  platform_id = "ERROR",
  num_processors = 1,
  memory_ram_info_mb = 0,
  memory_swap_info_mb = 0,
  clock_speed_mhz = 0,
  file_separator = "/",
  byte_order = BIG_ENDIAN,
  disabled = NO,
  benchmarks = NO,
  net_benchmark = NO,
  cpu_capacity = NO_CAPACITY,
  reading_disk_capacity = NO_CAPACITY,
  writing_disk_capacity = NO_CAPACITY,
  enable_historic = NO,
  partition_name = "sda",
  net_interface_name = "eth0",
  requirements = {},
  capacities = {},
}

SGAD_CONF.network = {}
SGAD_CONF.grid = {}

-- Mapeamento de nomes de plataformas
SGAD_CONF.platform_mapping = {}


-------------------------------------------------------------------------
----------------------------<:::::::::::::::>----------------------------
-------------------------------------------------------------------------


-------------------------------------------------------------------------
-- Declara a configuração de uma máquina
-------------------------------------------------------------------------
function Node(tab)
  if not tab.name then 
     return nil 
  end
  SGAD_CONF.network[string.upper(tab.name)] = { tab }

  return tab 
end

-------------------------------------------------------------------------
-- Declara a configuração de um cluster
-------------------------------------------------------------------------
function Grid(tab)
  if not tab.name then 
     return nil 
  end
  tab.grid = true
  SGAD_CONF.grid[string.upper(tab.name)] = tab

  return tab 
end

-------------------------------------------------------------------------
-- Preenche a configuração de uma máquina com a configuração padrão.
-------------------------------------------------------------------------
function fillDefaults(tab)
  local def = SGAD_CONF.node_defaults
  local map = SGAD_CONF.platform_mapping

  for i, v in pairs(def) do
     -- Quando tab[i] é nil, significa que não há um valor definido
     -- nesse campo. Quando tab[i] é false, significa que o valor foi
     -- definido!
     if tab[i] == nil then
       tab[i] = def[i]
     end
  end

  if tab.file_separator ~= def.file_separator then
	 if tab.algorithm_root_directory then
	 	tab.algorithm_root_directory = string.gsub(tab.algorithm_root_directory,
          def.file_separator, tab.file_separator)
     end

	 if tab.project_root_directory then
	 	tab.project_root_directory = string.gsub(tab.project_root_directory,
          def.file_separator, tab.file_separator)
	 end

	 if tab.sandbox_root_directory then
	 	tab.sandbox_root_directory = string.gsub(tab.sandbox_root_directory,
          def.file_separator, tab.file_separator)
	 end
	
     if tab.ssi_bin_directory then
        tab.ssi_bin_directory = string.gsub(tab.ssi_bin_directory,
         def.file_separator, tab.file_separator)
     end

     if tab.sga_lib_directory then
        tab.sga_lib_directory = string.gsub(tab.sga_lib_directory,
         def.file_separator, tab.file_separator)
     end
  end
  tab.platform_os = tab.platform_os or map[tab.platform_id] or tab.platform_id
end

-------------------------------------------------------------------------
-- Define a configurção de um cluster
-------------------------------------------------------------------------
function getNodes(server)
	local nodes
	local err
  if servermanager and servermanager.getnodes then
    nodes, err = servermanager.getnodes(server)
	if err then
		verbose.error(err)
	end
	return nodes
  end
end

function loadGrid(tab)
  if not tab or not tab.name then
    return nil
  end

  local ids = tab.ids or getNodes(tab.name)
  if not ids then 
     return nil 
  end

  if tab.default then 
     fillDefaults(tab.default) 
  end

  local groups = tab.groups
  local currgroup             -- Grupo atual
  local defs= tab.default
  local nodes = {}

  for i, n in pairs(ids) do
     local name
     if type(n) == "table" then
       name, currgroup = n[1], n[2]
     else
       name = n
     end

     -- Verifica se a máquina já está definida
     if SGAD_CONF.network[string.upper(name)] then 
       nodes[i] = cloneNode(SGAD_CONF.network[string.upper(name)][1])
     else
       local node = defs
       if currgroup then
         -- verifica se o grupo foi definido
         if not groups or not groups[currgroup] then 
           return nil 
         end
         -- o nó recebe as configurações do grupo
         node = groups[currgroup]
       else 
         -- nenhuma configuração definida
         if not node then 
           return nil
         end
       end
       node.name = name
       nodes[i] = cloneNode(node)
       Node(nodes[i])
     end
     fillDefaults(nodes[i])
  end

  -- Define como globais as configurações do Grid escolhido.
  for field, value in pairs(tab) do
    if field ~= "name" and field ~= "default" then
      SGAD_CONF[field] = value
    end
  end

  SGAD_CONF.network[string.upper(tab.name)] = nodes
  return nodes
end

-------------------------------------------------------------------------
-- Faz um clone da configuração
-------------------------------------------------------------------------
function cloneNode(n)
  local new_n = {}

  for i,v in pairs(n) do
     new_n[i] = v
  end

  return new_n
end


----------------------------<:::::::::::::::>----------------------------

-------------------------------------------------------------------------
-- Retorna quantos minutos faltam para restart
-------------------------------------------------------------------------
function minToGo(restart) 
  local hour, min = time()
  
  if restart == 0 then 
     restart = 24 
  end

  local w = restart - hour
  if w <= 0 then 
     w = w + 24 
  end

  w = (w - 1) * 60 + (60 - min)
  
  return w
end

-------------------------------------------------------------------------
-- Retorna hora, minuto
-------------------------------------------------------------------------
function time()
  local t = os.date("%H:%M")
  local h = string.gsub(t, "(%d%d):%d%d","%1")
  local m = string.gsub(t, "%d%d:(%d%d)","%1")

  return h, m
end


-------------------------------------------------------------------------    
-- Altera o título da janela do shell que está executando o comando
-------------------------------------------------------------------------    

function setWinTitle()
--local msg = "SGA conectado ao servidor "
--print(string.format("\027];%s%s:%d\007",
--  msg, SGAD_CONF.ssi_host, SGAD_CONF.ssi_port))
  local msg = "SGA conectado ao servidor %s:%d"
  local title = string.format(msg, SGAD_CONF.ssi_host, SGAD_CONF.ssi_port)
  if SGAD_CONF.platform_os == "Windows" then
    os.execute("title "..title)
  else
    print("\027];"..title.."\007")
  end
end

-------------------------------------------------------------------------
----------------------------<:::::::::::::::>----------------------------
-------------------------------------------------------------------------

-------------------------------------------------------------------------
-- Verifica os paths configurados para o SGA
-------------------------------------------------------------------------
function checkConfiguration()

  -- verifica a configuração dos nós
  if not SGAD_CONF.nodes or type(SGAD_CONF.nodes) ~= type({})  then
     return "Falha na configuração dos nós do SGA."
  end
 
  if SGAD_CONF.csfs then
	  if not CSFS.use_local_root_directories or CSFS.launch_daemon then
	    if not CSFS.properties.HOST then
	      return "Propriedade obrigatória 'HOST' do CSFS não encontrada."
	    end
	    if not CSFS.properties.PORT then
          return "Propriedade obrigatória 'PORT' do CSFS não encontrada."
        end
	    if not CSFS.properties.ROOT_DIR then
	      return "Propriedade obrigatória 'ROOT_DIR' do CSFS não encontrada."
		elseif not ext.isDir(CSFS.properties.ROOT_DIR) then
	      return "Diretório base do CSFS não encontrado: "..CSFS.properties.ROOT_DIR
	    end
	    local path, err = ext.dirGetAbsolutePath(CSFS.properties.ROOT_DIR)
	    if err then
	      return "Caminho absoluto do diretório base do CSFS não encontrado: "..CSFS.properties.ROOT_DIR..". Erro: "..err 
	    end
		CSFS.properties.CANONICAL_ROOT_DIR = path
	  end
  end

  local validate_roots = not SGAD_CONF.csfs or CSFS.use_local_root_directories

  local projPath = ""
  if validate_roots then 
	  if not SGAD_CONF.project_root_directory then
	    return "Propriedade obrigatória 'project_root_directory' do SGA não encontrada."
	  end
	  if not ext.isDir(SGAD_CONF.project_root_directory) then
	    return "Diretório base de projetos não encontrado: "..SGAD_CONF.project_root_directory
	  end 
	  projPath, err = ext.dirGetAbsolutePath(SGAD_CONF.project_root_directory)
	  if err then 
	    return "Caminho absoluto do diretório base de projetos não encontrado: "..SGAD_CONF.project_root_directory..". Erro: "..err 
	  end
	  verbose.init("Diretório de projetos em: "..projPath)
  end  
 
  local algoPath = ""
  if validate_roots then
	  if not SGAD_CONF.algorithm_root_directory then
	      return "Propriedade obrigatória 'algorithm_root_directory' do SGA não encontrada."
	  end
	  if not ext.isDir(SGAD_CONF.algorithm_root_directory) then
	    return "Diretório base de algoritmos não encontrado: "..SGAD_CONF.algorithm_root_directory
	  end
	  algoPath, err = ext.dirGetAbsolutePath(SGAD_CONF.algorithm_root_directory)
	  if err then 
	    return "Caminho absoluto do diretório base de algoritmos não encontrado: "..SGAD_CONF.algorithm_root_directory..". Erro: "..err 
	  end
	  verbose.init("Diretório de algoritmos em: "..algoPath)      
  end

  if not SGAD_CONF.sandbox_root_directory then
    return "Propriedade obrigatória 'sandbox_root_directory' do SGA não encontrada."
  elseif not ext.isDir(SGAD_CONF.sandbox_root_directory) then
	verbose.init("Criando diretório base de sandbox em: "..SGAD_CONF.sandbox_root_directory)
	if not ext.mkdirs(SGAD_CONF.sandbox_root_directory) then
	  return "Diretório de sandbox não pode ser criado: "..SGAD_CONF.sandbox_root_directory
    end
  end
  local sandboxPath, err = ext.dirGetAbsolutePath(SGAD_CONF.sandbox_root_directory)
  if err then
    return "Caminho absoluto do diretório de sandbox não encontrado: "..SGAD_CONF.sandbox_root_directory..". Erro: "..err 
  end
  verbose.init("Diretório base de sandbox em: "..sandboxPath)
  
  for i, node in ipairs(SGAD_CONF.nodes) do
    node.project_root_directory = projPath
    node.algorithm_root_directory = algoPath
    node.sandbox_root_directory = sandboxPath
  end

  if CSFS.launch_daemon then
    verbose.init("[CSFS] Executando daemon do CSFS no host "..CSFS.properties.HOST.. " com diretório base em "..CSFS.properties.CANONICAL_ROOT_DIR)
  end

  if SGAD_CONF.csfs and not CSFS.use_local_root_directories then 
    verbose.init("[CSFS] Utilizando daemon do CSFS no host "..CSFS.properties.HOST.. " com diretório base em "..CSFS.properties.CANONICAL_ROOT_DIR)
  end

  if not validate_roots and (algorithm_root_directory or project_root_directory) then
      verbose.init("[WARN] A configuração dos diretórios base de projetos e algoritmos será ignorada." )
  end

  if SGAD_CONF.csfs and not CSFS.launch_daemon and CSFS.use_local_root_directories then
      verbose.init("[WARN] A configuração do CSFS será ignorada." )
  end

end


-------------------------------------------------------------------------
-- Inicializacao do servidor SGA daemon
-------------------------------------------------------------------------
function SGA_DAEMON:initServer()
  setWinTitle()
  
  local err = checkConfiguration()
  if err then
	return nil, err
  end

  oil.loadidlfile("../idl/sga.idl")

  if SGAD_CONF.sga_addr then
    local addr = SGAD_CONF.sga_addr
    string.gsub(addr,"^([^:]*):?(%d*)$", function(host, port)
      if host and host ~= "" then
        oil.Config.host = host
      end
      if port and port ~= "" then
        oil.Config.port = tonumber(port)
      end
    end)
  end
  -- ativa o servidor CORBA para o SGAD
  self.servant = oil.newobject(self, "IDL:sgaidl/SGAServer:1.0")

  if not self.servant then 
     return nil, "Erro ao ativar o servidor SGAD"
  end

  -- obtem a localizacao do SSI e cria o seu proxy
  if not SGAD_CONF.ssi_ior then
     return nil, "Falha na configuração do IOR do SSI."
  end

  self.ssi = oil.newproxy(SGAD_CONF.ssi_ior, "IDL:sgaidl/SGAManager:1.0")

  if not self.ssi then
     return nil, "Erro ao localizar o SSI." 
  end

  -- inicializacao do campo enabled do SGA
  if (not SGAD_CONF.disabled) then
     self.is_enabled = 1
  end

  -- Obtem dados estáticos do SGA (somente memoria e cpu por enquanto)
  -- A ideia é retirar o maximo possivel do arquivo sgad-cnf.lua
  for i, node in ipairs(SGAD_CONF.nodes) do
      local memory, err = 
        SGAD_CONF.serverManager.getmemory(node.name)
      if not err then
        node.memory_ram_info_mb = memory.ram / (1024^2)
        node.memory_swap_info_mb = memory.swap / (1024^2)
      end
      local nprocs, err = servermanager.getnumcpus(node.name)
      if not err then
        node.num_processors = nprocs
      end
  end
  
  if (SGAD_CONF.benchmarks == YES) then
      self:runBenchmarks()
  end
  
  if (SGAD_CONF.serverManager.setHistoric) then
      SGAD_CONF.serverManager.setHistoric(SGAD_CONF.enable_historic)
  end
    
  return 1
end

-------------------------------------------------------------------------
-- Executa a coleta das capacidades de processamento e escrita/leitura 
-- em disco dos SGAs
-------------------------------------------------------------------------
function SGA_DAEMON:runBenchmarks()
  local cpu_capacity
  local capacity
  -- TODO: Ainda falta tratar o caso de clusters.
  -- Por simplificação, consideramos o caso de sgas com apenas 1 nó.
  verbose.init("Calculando a capacidade de processamento da máquina...")
  local csBenchDir = ".." .. SGAD_CONF.file_separator .. "bin" ..
    SGAD_CONF.file_separator .. SGAD_CONF.platform_os ..
    SGAD_CONF.file_separator
  resFile = csBenchDir .. self:getName() .. ".cpu"
  os.execute("sh -c '"..csBenchDir.."csBench "..resFile.."'")
  fd = io.open(resFile)
  if fd then
    cpu_capacity = fd:read("*number")
    fd:close()
  end
  os.remove(resFile)
  capacity = { type = SGA_DAEMON.CType.CPU, value = cpu_capacity or NO_CAPACITY}
  table.insert(SGAD_CONF.capacities, capacity)

  -- TODO: Ainda falta tratar o caso de clusters.
  -- Por simplificação, consideramos o caso de sgas com apenas 1 nó.
  verbose.init("Calculando a capacidade de leitura em disco da máquina...")
  local iozoneDir = ".." .. SGAD_CONF.file_separator .. "bin" ..
    SGAD_CONF.file_separator .. SGAD_CONF.platform_os ..
    SGAD_CONF.file_separator
  tmpFile = iozoneDir .. self:getName()
  resFile = iozoneDir .. self:getName() .. ".io"
  memoryLen = 2*SGAD_CONF.memory_ram_info_mb
  os.execute("sh -c '"..iozoneDir.."iozone -a -s "..memoryLen..
    "M -r 4k -i 0 -i 1 -f "..tmpFile.." > "..resFile.."'")
  fd = io.open(resFile)
  if fd then
    local t = fd:read("*all")
    i,j = string.find(t, "freread")
    local results = string.sub(t, j+1)
    local capacitiesTab = {string.find(results, "%s*(%d+)%s+(%d+)%s+(%d+)%s+(%d+)%s+(%d+)")}
    capacity = { type = SGA_DAEMON.CType.DISK_READ, value= tonumber(capacitiesTab[7]) or NO_CAPACITY}
    table.insert(SGAD_CONF.capacities, capacity)
    capacity = { type = SGA_DAEMON.CType.DISK_WRITE, value= tonumber(capacitiesTab[5]) or NO_CAPACITY}
    table.insert(SGAD_CONF.capacities, capacity)
    fd:close()
  end
  os.remove(tmpFile)
  os.remove(resFile)
end

-------------------------------------------------------------------------
-- Retorna o nome de um node ou de um grid
-------------------------------------------------------------------------
function SGA_DAEMON:getName()
  return SGAD_CONF.name
end

-------------------------------------------------------------------------
-- Retorna uma função iteradora que, cada vez que é chamada, indica se o
-- SGA deve fazer uma nova tentativa de se registrar junto ao SSI.
-------------------------------------------------------------------------
-- Se a variável SGAD_CONF.retries_number não estiver definida, a
-- função iteradora retorna sempre true, i.e., o SGA deve ficar em
-- loop tentando fazer o registro e nunca vai desistir.
-- Caso contrário, a variável SGAD_CONF.retries_number deve indicar o
-- número de tentativas que o SGA deve fazer antes de desistir de
-- registrar-se e terminar.
-------------------------------------------------------------------------
function SGA_DAEMON:Retry()
  if SGAD_CONF.retries_number then
    self.retries_number = SGAD_CONF.retries_number
    return function()
      self.retries_number = self.retries_number - 1
      return self.retries_number >= 0
    end
  else
    return function() return true end
  end
end

-------------------------------------------------------------------------
-- Registro do SGA junto ao SSI
-------------------------------------------------------------------------
function SGA_DAEMON:register()
  -- XXX Assume-se que o IOR do SSI nao muda, senão vou ter
  -- que criar um outro proxy

  -- registra o SGA junto ao Servidor
  local doRetry = SGA_DAEMON:Retry()
  local suc, non_existent = scheduler.pcall(self.ssi._non_existent, self.ssi)
  while not suc or non_existent do 
     if not doRetry() then
       verbose.kill("Não conseguiu encontrar referência CORBA para o SSI.")
       SGA_DAEMON:exit(0)
     end
     verbose.init("Buscando referência CORBA para o SSI "..SGAD_CONF.ssi_printablename.."...")
     SGAD_CONF.serverManager.sleep(tonumber(SGAD_CONF.ssiref_time_seconds) 
       or 5)
     suc, non_existent = scheduler.pcall(self.ssi._non_existent, self.ssi)
  end

  verbose.init("Referência CORBA para o SSI "..SGAD_CONF.ssi_printablename.." encontrada.")

  local myname = self:getName()
  local static_data = SGAD_CONF.nodes
  local doRetry = SGA_DAEMON:Retry()
  local suc, stat
  while doRetry() do
     verbose.init("Tentando registro do SGA...")

     -- A versão atual do luaorb não está tratando as exceções de
     -- comunicação com o servidor. Utilizar pcall para evitar
     -- a parada do SGA.
     -- local stat = self.ssi:registerSGA(self.servant, myname,
     -- static_data)
     suc, stat, updateInterval = scheduler.pcall(self.ssi.registerSGA, self.ssi, 
                                       self.servant, myname, static_data)

     if suc and stat then
       SGA_DAEMON.update_time_seconds = updateInterval
       if SGA_DAEMON.heartBeat then 
         SGA_DAEMON.heartBeat.freq = updateInterval
       end
       break 
     else
       local msg = (stat and tostring(stat).."\n\n" ) or "" 
       verbose.init("Falha de registro no SGA: "..myname.."\n"..msg)
     end
     SGAD_CONF.serverManager.sleep(tonumber(SGAD_CONF.sgaregistry_time_seconds) 
                                   or 30)
  end

  if not stat then
    verbose.kill("Não conseguiu registrar-se no SSI.")
    SGA_DAEMON:exit(0)
  end

  self.isConnected = true
  verbose.init("SGA ["..myname.."] registrado no SSI.")

  if not self.is_enabled then
     -- A versão atual do luaorb não está tratando as exceções de
     -- comunicação com o servidor. Utilizar pcall para evitar
     -- a parada do SGA.
     -- self.ssi:setSGADisabled(self.servant,myname)
     scheduler.pcall(self.ssi.setSGADisabled, self.ssi, self.servant, myname)
     verbose.init("SGA ["..myname.."] desabilitado junto ao SSI.")
  end
  self:loadPersistentData()
end

-------------------------------------------------------------------------
-- Verifica se SSI está acessível e indica que o SGA está vivo.
-- Se o SSI estiver inacessível, inicia processo de novo registro
-------------------------------------------------------------------------
function SGA_DAEMON:isRegistered()
  local myname = self:getName()
  for i = 1, 2, 1 do
     -- A versão atual do luaorb não está tratando as exceções de
     -- comunicação com o servidor. Utilizar pcall para evitar
     -- a parada do SGA.
     -- local isAlive = self.ssi:isRegistered(self.servant, myname)
     local suc, isAlive = scheduler.pcall(self.ssi.isRegistered, self.ssi,
       self.servant, myname)
     if suc and isAlive then
       self.isConnected = true
       return
     end
  end
  -- Debug
  -- showMemUse('Failure: ')
  verbose.error("Falha na acessibilidade do SSI. Registro sera refeito.")
  self.isConnected = false
  self:register()
end

function dofile(file)
  local func = loadfile(file)
  if not func then
    verbose.error("Erro ao carregar o arquivo do SGA ("..
      file.."). Abortando...")
    os.exit(0)
  end
  func()
  verbose.init( "Arquivo do SGA ("..file..") carregado." )
end

-------------------------------------------------------------------------
-- Carrega as funções que definem a implementação da api servermanager.
--
-- Retorna falso se não conseguiu carregar as funções ou verdadeiro,
-- caso contrário.
-------------------------------------------------------------------------
function SGA_DAEMON:openLib()
  local sganame = string.upper( SGAD_CONF.name )
  local tab = SGAD_CONF.network[ sganame ] or SGAD_CONF.grid[ sganame ]
  if not tab then
    return false
  end
  -- Se não for um grid, obtém a configuração do node[1]
  if not tab.grid then
    tab = tab[1]
  end

  local libname
  if tab.loadlib then
    servermanager = nil
    -- Retirar todas as '/' de loadlib
    libname = string.gsub(tab.loadlib,"(/)","")
    local fname = string.format("%s.lua", libname)

    local chooser = ext.createDirectoryChooser()
    chooser:add(SGAD_CONF.usrlibsdir) -- diretório de bibliotecas do usuário
    chooser:add(SGAD_CONF.sgalibsdir) -- diretório de bibliotecas do sga
    chooser:add(SGAD_CONF.path) -- diretório dos fontes do sga
    local libdir = chooser:choose(fname)
    if libdir then
      verbose.init("Carregando a biblioteca do SGA ".. libdir..fname)
      dofile(libdir..fname)
    end

    if not servermanager then
      verbose.error("Erro ao carregar a biblioteca do SGA "..  libname)
      os.exit(0)
    end
  end

  SGAD_CONF.serverManager = servermanager
  local ok, err = servermanager.open()
  if err then
    verbose.error("Erro ao abrir a biblioteca do SGA "..
                  (libname or SGAD_CONF.platform_os) .. err)
    os.exit(0)
  end
  return true
end
 
-------------------------------------------------------------------------
-- Define SGAD_CONF.nodes com base em SGAD_CONF.name.
-------------------------------------------------------------------------
function SGA_DAEMON:setNodes()
  local sganame = string.upper( SGAD_CONF.name )

  local nodes = SGAD_CONF.network[ sganame ] or SGAD_CONF.grid[ sganame ]
  if not nodes then
    return false
  end
  if nodes.grid then
    SGAD_CONF.nodes = loadGrid(nodes)
  else
    fillDefaults(nodes[1])
    SGAD_CONF.nodes = nodes
  end
  if not SGAD_CONF.nodes then
     verbose.error( "\n\nNo configuration found for host: "..tostring(SGAD_CONF.name).."!!!\n\n")
     os.exit(0)
  end

  -- Define o mecanismo de herança com o campo 'parent' 
  SGAD_CONF.meta = {
    __index = function ( tab, index )
      if type(tab) ~= type({}) then
         error( "indexed expression is not a table.")
      end
  
      local parent = rawget(tab, "parent")
      if parent then
         return parent[ index ]
      else
         return nil
      end
    end
  }
  setmetatable( SGAD_CONF, SGAD_CONF.meta )
  SGAD_CONF.parent = SGAD_CONF.nodes[1]
end

-------------------------------------------------------------------------
-- Carregamento do arquivo básico de configuração
-------------------------------------------------------------------------    
function SGA_DAEMON:loadBasicConfiguration()
  verbose.init( "Buscando o arquivo de configuração do SGA..." )
  local cnf_dir = os.getenv("SGA_CONFIG_DIR") or ""
  dofile(cnf_dir .. "sgad-cnf.lua")

  verbose.init( "Buscando o arquivo de configuração avancada do SGA..." )
  dofile("sgad-cnfavc.lua")
end

-------------------------------------------------------------------------
-- Carrega os dados dos comandos
-------------------------------------------------------------------------    
function SGA_DAEMON:loadPersistentData()
  return COMMAND:loadPersistentData()
end

-------------------------------------------------------------------------
-- Obtém os dados estáticos do SGA
-------------------------------------------------------------------------    
function SGA_DAEMON:getNodesStaticInfo()
  return SGAD_CONF.nodes
end

-------------------------------------------------------------------------
-- Obtém os dados dinâmicos do SGA
-------------------------------------------------------------------------    
function SGA_DAEMON:getNodesDynamicInfo()
  return self.dynamic_data or {}
end

-------------------------------------------------------------------------
-- Verifica a referencia e cria um novo processo Servidor de Arquivos
-------------------------------------------------------------------------    
function SGA_DAEMON:createCSFSProcess()
  verbose.csfs("Inicia processo CSFS.")
  -- Olha as propriedades do CSFS.
  local props = CSFS.properties

  if CSFS.process == nil then
    local cmd = CSFS.command
    for name, val in pairs(props) do
      cmd = cmd .. " --"..name.."="..val
    end
    ---------------------
    -- XXX: Rever :XXX --
    ---------------------
    -- No caso particular do PBS, não queremos que o CSFS seja executado
    -- por um qsub, nem monitorado pelo qstat. Perdemos a possibilidade
    -- de monitorar o CSFS, mas garantimos a coerência dos parâmetros 
    -- usados na sua configuração.
    ---------------------
    if SGA_DAEMON:isCluster() then
      CSFS.process = os.execute (cmd .. " &")
      CSFS.launch_daemon = NO
    else
      CSFS.process = self:executeCommand( cmd, CSFS.process_id )
    end
  else
    verbose.csfs("ATENÇÃO: Processo CSFS já está em execução.")
  end
end

------------------------------------------------------------------------
-- Verifica a referencia e cria um novo processo Servidor para a 
-- medição de taxas de transferência na rede.
-------------------------------------------------------------------------    
function SGA_DAEMON:createIPerfProcess()
  verbose.iperf("Inicia processo IPerf.")
  
  if IPERF.process == nil then
    local cmd = IPERF.command
    if SGA_DAEMON:isCluster() then
      IPERF.process = os.execute (cmd .. " &")
      SGAD_CONF.benchmarks = NO
    else
      IPERF.process = self:executeCommand( cmd, IPERF.process_id )
    end
  else
    verbose.iperf("ATENÇÃO: Processo IPerf já está em execução.")
  end
end

---------------------------------------------------------------------
-- Verifica se há falha no acesso ao disco
--
-- O SGAD_CONF.dump_file, usado na COMMAND:writePersistentData, pode
-- conter um diretório que não foi criado. Por isso, vamos testar
-- outro arquivo para dizer se há falha no acesso ao disco.
---------------------------------------------------------------------
function SGA_DAEMON:checkDiskAccess()
   --local tmpname = os.tmpname() -- Pode cair em outro disco.
   local tmpname = ".tmpFile."..self:getName()
   local fd, err = io.open(tmpname, "w")
   if not fd then
      verbose.error("ERRO: Falha no acesso ao disco.\n")
      return false
   end
   fd:close()
   os.remove(tmpname)
   return true
end

-------------------------------------------------------------------------
-- Loop principal do programa
-------------------------------------------------------------------------    
function SGA_DAEMON:startServer()
  local add = function (tb)
    scheduler.new(function()
      while true do
        tb.wakeme = socket.gettime() + tb.freq
        tb:func()
        -- Garante o yield
        scheduler.sleep(math.max(tb.wakeme - socket.gettime(), 0))
      end
    end)
  end

  if SGAD_CONF.restarthour then
     add({freq = minToGo(SGAD_CONF.restarthour)*60,
          title = "restartSGA", -- DEBUG
          func = function(this)
            if not this.second then
              verbose.init("Próximo restart em " ..
                minToGo(SGAD_CONF.restarthour) .. " minutos.")
              this.second = 1
              return
            end
            verbose.init("Restart: Terminando execução do SGA.")
            os.exit(1)
          end })
  end
  
  add({freq = tonumber(SGAD_CONF.machine_time_seconds),
       title = "MachineState", -- DEBUG
       func = function(self)
         if SGA_DAEMON.isConnected then
           SGA_DAEMON:readMachineStateInformation(self)
         end
       end})

  add({freq = tonumber(SGAD_CONF.dump_time_seconds),
       title = "PersistentData",  -- DEBUG
       func = function()
         COMMAND:writePersistentData()
       end})

  self.heartBeat = {
       freq = SGA_DAEMON.update_time_seconds or 
         tonumber(SGAD_CONF.update_time_seconds),
       title = "Heart Beat", -- DEBUG
       func = function(self)
         SGA_DAEMON:isRegistered()
       end}
  add(self.heartBeat)

  add({freq = tonumber(SGAD_CONF.completed_time_seconds),
       title = "CompletedItems", -- DEBUG
       func = function() 
         if SGA_DAEMON.isConnected then
           COMMAND:searchCompletedItems()
         end
       end})
end

-------------------------------------------------------------------------
-- Apresentação do programa
-------------------------------------------------------------------------   
function SGA_DAEMON:doPresentation()
  local np = #SGAD_CONF.nodes
  if np < 1 then
     verbose.init( "SGA daemon - Não há nós configurados!!!" )
     os.exit(0)
  end

  verbose.init("\n")
  verbose.init(string.rep("=", 72) )
  verbose.init("SGA daemon - Servidor de execução remota do CSBase" )
  verbose.init(string.rep("=", 72) )
  local platmsg = " ("..SGAD_CONF.platform_id..")"
  if SGAD_CONF.platform_id ~= SGAD_CONF.platform_os then
    platmsg = platmsg .. " ("..SGAD_CONF.platform_os..")"
  end
  verbose.init(self:getName()..platmsg)
  verbose.init("Endereço do SSI: "..SGAD_CONF.ssi_printablename)
  verbose.init("# nós: "..tostring(np) )
  verbose.init("# processadores no nó principal: "..
    tostring( SGAD_CONF.num_processors) )
  verbose.init("Memória total do nó principal: "..
    tostring(SGAD_CONF.memory_ram_info_mb + 
             SGAD_CONF.memory_swap_info_mb) )
  verbose.init("Frequência de clock do nó principal: "..
    tostring( SGAD_CONF.clock_speed_mhz) )
  verbose.init("Ordenação de bytes no processador do nó principal: "..
    tostring( SGAD_CONF.byte_order) )
  if SGAD_CONF.deny_node_selection then
    verbose.init("Não permite a seleção de nós.")
  end
  if SGAD_CONF.queue_name then
    verbose.init("Utiliza a fila " ..
      tostring( SGAD_CONF.queue_name) )
  end
  verbose.init("Intervalo, em segundos, para leitura de uso de cpu/memória: "..
    tostring(tonumber(SGAD_CONF.machine_time_seconds) or "não ajustado"))
  verbose.init("Intervalo, em segundos, para envio de uso de cpu/memória: "..
    tostring(tonumber(SGAD_CONF.update_time_seconds) or "não ajustado"))
  verbose.init("Intervalo, em segundos, para consulta de processos: "..
    tostring(tonumber(SGAD_CONF.process_time_seconds) or "não ajustado"))

  verbose.init("Intervalo, em segundos, para busca de comandos terminados: "..
    tostring(tonumber(SGAD_CONF.completed_time_seconds) or "não ajustado"))
  verbose.init("Hora definida para restart do SGA: "..
    tostring(tonumber(SGAD_CONF.restarthour) or "não ajustado"))

  verbose.init(string.rep("=", 72) )
  verbose.init("\n\n\n")
end


-------------------------------------------------------------------------
-- DEBUG
-- Exibe a quantidade de memória consumida
------------------------------------------------------------------------- 
function showMemUse(label)
  print(label)
  if label then
     execute("echo '"..label.."' `date`;ps -eo 'pid comm vsz'|grep sgad")
  else
     execute("date;ps -eo 'pid comm vsz'|grep sgad")
  end
end

