--------------------------------------------------------------------------------
-- $Author: fpina $
-- $Revision: 165105 $  - $Date: 2015-06-03 08:58:52 -0300 (Qua, 03 jun 2015) $
--------------------------------------------------------------------------------

require "compat52" -- OiL assumes Lua 5.2 libraries
local oil = require "oil"
local verbose = require "sga.verbose"
local ext = require "shellExtension"
local sgaidl = require "sga.idl"

YES = true
NO = false

KEY_CONV = {}
KEY_CONV["algorithm_root_directory"] = "csbase_algorithm_root_directory"
KEY_CONV["benchmarks"] = "csbase_benchmarks"
KEY_CONV["byte_order"] = "csbase_byte_order"
KEY_CONV["clock_speed_mhz"] = "csbase_clock_speed_mhz"
KEY_CONV["command_bytes_in_kb"] = "csbase_command_bytes_in_kb"
KEY_CONV["command_bytes_out_kb"] = "csbase_command_bytes_out_kb"
KEY_CONV["command_cpu_perc"] = "csbase_command_cpu_perc"
KEY_CONV["command_disk_bytes_read_kb"] = "csbase_command_disk_bytes_read_kb"
KEY_CONV["command_disk_bytes_write_kb"] = "csbase_command_disk_bytes_write_kb"
KEY_CONV["command_error_path"] = "csbase_command_error_path"
KEY_CONV["command_exec_host"] = "csbase_command_exec_host"
KEY_CONV["command_input_path"] = "csbase_command_input_path"
KEY_CONV["command_memory_ram_size_mb"] = "csbase_command_memory_ram_size_mb"
KEY_CONV["command_memory_swap_size_mb"] = "csbase_command_memory_swap_size_mb"
KEY_CONV["command_output_path"] = "csbase_command_output_path"
KEY_CONV["command_path"] = "csbase_command_path"
KEY_CONV["command_pid"] = "csbase_command_pid"
KEY_CONV["command_ppid"] = "csbase_command_ppid"
KEY_CONV["command_processor_id"] = "csbase_command_processor_id"
KEY_CONV["command_sandbox_paths"] = "csbase_command_sandbox_paths"
KEY_CONV["command_start_path"] = "csbase_command_start_path"
KEY_CONV["command_state"] = "csbase_command_state"
KEY_CONV["command_string"] = "csbase_command_string"
KEY_CONV["command_system_time_sec"] = "csbase_command_system_time_sec"
KEY_CONV["command_time_sec"] = "csbase_command_time_sec"
KEY_CONV["command_user_time_sec"] = "csbase_command_user_time_sec"
KEY_CONV["command_virtual_memory_size_mb"] = "csbase_command_virtual_memory_size_mb"
KEY_CONV["command_wall_time_sec"] = "csbase_command_wall_time_sec"
KEY_CONV["cpu_capacity"] = "csbase_cpu_capacity"
KEY_CONV["csfs_host"] = "csbase_csfs_host"
KEY_CONV["csfs_port"] = "csbase_csfs_port"
KEY_CONV["csfs_root_dir"] = "csbase_csfs_root_dir"
KEY_CONV["disabled"] = "csbase_disabled"
KEY_CONV["enable_historic"] = "csbase_enable_historic"
KEY_CONV["error_flag"] = "csbase_error_flag"
KEY_CONV["false"] = "csbase_false"
KEY_CONV["file_separator"] = "csbase_file_separator"
KEY_CONV["has_disk_access"] = "csbase_has_disk_access"
KEY_CONV["job_control_actions"] = "csbase_job_control_actions"
KEY_CONV["jobs_info"] = "csbase_jobs_info"
KEY_CONV["load_avg_15min_perc"] = "csbase_load_avg_15min_perc"
KEY_CONV["load_avg_1min_perc"] = "csbase_load_avg_1min_perc"
KEY_CONV["load_avg_5min_perc"] = "csbase_load_avg_5min_perc"
KEY_CONV["load_perc"] = "csbase_load_perc"
KEY_CONV["memory_ram_free"] = "csbase_memory_ram_free"
KEY_CONV["memory_ram_free_perc"] = "csbase_memory_ram_free_perc"
KEY_CONV["memory_ram_info_mb"] = "csbase_memory_ram_info_mb"
KEY_CONV["memory_swap_free"] = "csbase_memory_swap_free"
KEY_CONV["memory_swap_free_perc"] = "csbase_memory_swap_free_perc"
KEY_CONV["memory_swap_info_mb"] = "csbase_memory_swap_info_mb"
KEY_CONV["name"] = "csbase_name"
KEY_CONV["net_benchmark"] = "csbase_net_benchmark"
KEY_CONV["net_interface_name"] = "csbase_net_interface_name"
KEY_CONV["num_processors"] = "csbase_num_processors"
KEY_CONV["number_of_jobs"] = "csbase_number_of_jobs"
KEY_CONV["partition_name"] = "csbase_partition_name"
KEY_CONV["platform_id"] = "csbase_platform_id"
KEY_CONV["platform_os"] = "csbase_platform_os"
KEY_CONV["project_root_directory"] = "csbase_project_root_directory"
KEY_CONV["reading_disk_capacity"] = "csbase_reading_disk_capacity"
KEY_CONV["sandbox_root_directory"] = "csbase_sandbox_root_directory"
KEY_CONV["sga_control_actions"] = "csbase_sga_control_actions"
KEY_CONV["sga_enabled"] = "csbase_sga_enabled"
KEY_CONV["sga_name"] = "csbase_sga_name"
KEY_CONV["true"] = "csbase_true"
KEY_CONV["writing_disk_capacity"] = "csbase_writing_disk_capacity"

CONST = {}

for k,v in pairs(sgaidl.const) do
  CONST[k] = v
end
CONST.PLATFORM_OS = "csbase_platform_os"
CONST.LOAD_PERC = "csbase_load_perc"
CONST.MEMORY_RAM_FREE = "csbase_memory_ram_free"
CONST.MEMORY_SWAP_FREE = "csbase_memory_swap_free"

CONST.BYTE_ORDER = "csbase_byte_order"
CONST.DISABLED = "csbase_disabled"
CONST.BENCHMARKS = "csbase_benchmarks"
CONST.NET_BENCHMARK = "csbase_net_benchmark"
CONST.CPU_CAPACITY = "csbase_cpu_capacity"
CONST.READING_DISK_CAPACITY = "csbase_reading_disk_capacity"
CONST.WRITING_DISK_CAPACITY = "csbase_writing_disk_capacity"
CONST.ENABLE_HISTORIC = "csbase_enable_historic"
CONST.PARTITION_NAME = "csbase_partition_name"
CONST.NET_INTERFACE_NAME = "csbase_net_interface_name"

CONST.COMMAND_INPUT_PATH = "csbase_command_input_path"
CONST.COMMAND_START_PATH = "csbase_command_start_path"
CONST.COMMAND_ERROR_PATH = "csbase_command_error_path"

--------------------------------------------------------------------------------
-- Construtores para as descrições dos SGAs
--------------------------------------------------------------------------------
SGA_DAEMON = { __type = sgaidl.types.SGADaemon }
SGAD_CONF = {}
SGA_STATIC_CONF = {}
SGA_NODES = {}
SGAS = {}
GRIDS = {}
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"
}

SGA_DAEMON.loadAvg = {}
SGA_DAEMON.INF = 2 ^ 1024
SGA_DAEMON.ERROR_CODE = -1
SGA_DAEMON.NO_VALUE = ""

-- Configuração padrão
DEFAULTS = {}
DEFAULTS[CONST.SGA_NODE_PLATFORM_ID] = "ERROR"
DEFAULTS[CONST.SGA_NODE_NUM_PROCESSORS] = 1
DEFAULTS[CONST.SGA_NODE_MEMORY_RAM_INFO_MB] = 0
DEFAULTS[CONST.SGA_NODE_MEMORY_SWAP_INFO_MB] = 0
DEFAULTS[CONST.SGA_NODE_CLOCK_SPEED_MHZ] = 0
DEFAULTS[CONST.SGA_FILE_SEPARATOR] = "/"
DEFAULTS[CONST.SGA_PROJECT_ROOT_DIR] = "project"      -- Em relação ao SGA
DEFAULTS[CONST.SGA_ALGORITHM_ROOT_DIR] = "algorithms" -- Em relação ao SGA
DEFAULTS[CONST.SGA_SANDBOX_ROOT_DIR] = "/tmp/sandbox"
--
DEFAULTS[CONST.BYTE_ORDER] = BIG_ENDIAN
DEFAULTS[CONST.DISABLED] = NO
DEFAULTS[CONST.BENCHMARKS] = NO
DEFAULTS[CONST.NET_BENCHMARK] = NO
DEFAULTS[CONST.CPU_CAPACITY] = NO_CAPACITY
DEFAULTS[CONST.READING_DISK_CAPACITY] = NO_CAPACITY
DEFAULTS[CONST.WRITING_DISK_CAPACITY] = NO_CAPACITY
DEFAULTS[CONST.ENABLE_HISTORIC] = NO
DEFAULTS[CONST.PARTITION_NAME] = "sda"
DEFAULTS[CONST.NET_INTERFACE_NAME] = "eth0"

-- SGAD_CONF.nodes = {}

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

--------------------------------------------------------------------------------
-- Cria uma configuração de SGA
--------------------------------------------------------------------------------
function SGA(configTab)
  if not configTab[CONST.SGA_NAME] then
    return nil
  end

  SGAS[string.upper(configTab[CONST.SGA_NAME])] = configTab

  return configTab

end -- function SGA

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

--   return tab
-- end

function Grid(configTab)
  if not configTab[CONST.SGA_NODE_NAME] then
    return
  end

  GRIDS[configTab[CONST.SGA_NODE_NAME]] = convertConfig(configTab)
end -- function Grid
-- -------------------------------------------------------------------------
-- -- Declara a configuração de um cluster
-- -------------------------------------------------------------------------
-- -- @TODO Remover
-- 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

function convertConfig(oldConfig)
  local newConfig = {}
  for k,v in pairs(configTab) do
    newConfig(convertKey(k),v)
  end
  return newConfig
end

function convertKey(oldKey)
  if(KEY_CONV[oldKey]) then
    return KEY_CONV[oldKey]
  else
    return oldKey
  end
end

function loadGrid(gridConf)
  local ids = gridConf.ids or getNodes(gridConf.name)
  if not ids then
     return nil
  end

  -- Carrega as configurações padrões
  for k, v in pairs(DEFAULTS) do
    SGA_STATIC_CONF[k] = v
  end

  -- for k, v in pairs(gridConf.default) do
  for k, v in pairs(gridConf) do
    if k ~= "ids" and k ~= "default" and k ~= "groups" then
      SGA_STATIC_CONF[k] = v
    end
  end

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

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

    local nodeInfo
    -- Verifica se a máquina já está definida
    if SGAS[name] then
      nodeInfo = cloneConfig(SGAS[name])
    else
      nodeInfo = {}
    end

    nodeInfo[CONST.SGA_NODE_NAME] = name

    newNode(nodeInfo, groups[currgroup])
  end
end

function Default(configTab)
  for k,v in pairs(configTab) do
    DEFAULTS[k] = v
  end
end -- function Default

function convertDefault()
  if SGAD_CONF.node_defaults then
    for k,v in pairs(SGAD_CONF.node_defaults) do
      DEFAULTS[convertKey(k)] = v
    end
  end
end

function newNode(nodeConfig, defaultConfig)
  if defaultConfig then
    for k, v in pairs(defaultConfig) do
      if not nodeConfig[k] then
        nodeConfig[k] = v
      end
    end
  end

  fillDefaults(nodeConfig)

  table.insert(SGA_NODES, nodeConfig)
end -- function newNode

function fillDefaults(config)
  for k, v in pairs(DEFAULTS) do
    if not config[k] then
      config[k] = v
    end
  end
end

function cloneConfig(config)
  if not config then
    return {}
  end

  local clone = {}
  for k,v in pairs(config) do
    clone[k] = v
  end

  return clone
end

function configCSFS(CSFSConfig)
  if not CSFSConfig then
    CSFS_FLAG = false
    return
  else
    CSFS_FLAG = true
  end

  CSFS.properties = CSFSConfig.properties or {}
  CSFS.launch_daemon = CSFSConfig.launch_daemon
  CSFS.use_local_root_directories = CSFSConfig.use_local_root_directories
  CSFS.retries_number = CSFSConfig.csfs_retries_number

  local logdir = os.getenv("SGALOG_DIR")
  if logdir then
    local logname = "csfs-" ..SGAD_CONF.name..".log"
    -- CSFS.properties.SERVER_LOG = "../src/"..logdir.."/"..logname
    CSFS.properties.SERVER_LOG = logname
  end

  CSFS.properties.HOST = CSFS.properties.HOST or
    (CSFS.launch_daemon and SGAD_CONF.localhost..SGAD_CONF.domain)
end -- function configCSFS

--------------------------------------------------------------------------------
-- Define SGAD_CONF com base no nó de SGAD_CONF correspondente a SGAD_CONF.name
--------------------------------------------------------------------------------
function setupSGAConfiguration()
  convertDefault()
  -- Percorrer a tabela SGAS e encontrar a configuração correspondente
  -- ao servidor. Usar essa informação para preenche o SGA_NODES
  -- SGAD_CONF.name é definido no sgad-cnfavc.lua

  local sgaName = string.upper(SGAD_CONF.name)

  if not SGAS[sgaName] and not GRIDS[sgaName] then
    verbose:error( "\n\nNo configuration found for host: "..tostring(SGAD_CONF.name).."!!!\n\n")
    os.exit(0)
  end

  if SGAS[sgaName] then
    SGAD_CONF.loadlib = SGAS[sgaName].loadlib
    if not SGA_DAEMON:openLib() then
      verbose:error("Não tem a lib definida.")
    end

    configCSFS(SGAS[sgaName].csfs)
    SGAS[sgaName].csfs = nil

    -- Carrega as configurações padrões
    for k, v in pairs(DEFAULTS) do
      SGA_STATIC_CONF[k] = v
    end

    -- Carrega as configurações específicas no SGA
    for k, v in pairs(SGAS[sgaName]) do
      SGA_STATIC_CONF[k] = v
    end

    local map = SGAD_CONF.platform_mapping
    SGA_STATIC_CONF[CONST.PLATFORM_OS] = SGA_STATIC_CONF[CONST.SGA_NODE_PLATFORM_ID] and
      (map[SGA_STATIC_CONF[CONST.SGA_NODE_PLATFORM_ID]] or SGA_STATIC_CONF[CONST.SGA_NODE_PLATFORM_ID])

    SGAD_CONF.platform_os = SGA_STATIC_CONF[CONST.PLATFORM_OS]
    SGAD_CONF.platform_id = SGA_STATIC_CONF[CONST.SGA_NODE_PLATFORM_ID]
    SGAD_CONF.file_separator = SGA_STATIC_CONF[CONST.SGA_FILE_SEPARATOR]

    SGA_STATIC_CONF[CONST.SGA_SANDBOX_ROOT_DIR] =
      SGA_STATIC_CONF[CONST.SGA_SANDBOX_ROOT_DIR] or SGAD_CONF.sandbox_root_directory

    SGA_STATIC_CONF[CONST.SGA_PROJECT_ROOT_DIR] =
      SGA_STATIC_CONF[CONST.SGA_PROJECT_ROOT_DIR] or SGAD_CONF.project_root_directory

    SGA_STATIC_CONF[CONST.SGA_ALGORITHM_ROOT_DIR] =
      SGA_STATIC_CONF[CONST.SGA_ALGORITHM_ROOT_DIR] or SGAD_CONF.algorithm_root_directory

    if SGAD_CONF.serverManager.getNodes then
      local nodes, err = SGAD_CONF.serverManager.getNodes()
      if nodes ~= nil and type(nodes) == "table" then
        for _, node in ipairs(nodes) do
          if node ~= SGAD_CONF.name then
            local configKeys = {
              CONST.SGA_NODE_NUM_PROCESSORS,
              CONST.SGA_NODE_MEMORY_RAM_INFO_MB,
              CONST.SGA_NODE_MEMORY_SWAP_INFO_MB,
              CONST.SGA_NODE_CLOCK_SPEED_MHZ
            }
            local staticInfo, err =
              SGAD_CONF.serverManager.getConfiguration(node, nil)
            if err then
              verbose:error(
                string.format("Erro ao obter a configuração estática do nó %s [%s].",
                  node, err))
            end
            staticInfo[CONST.SGA_NODE_NAME] = node
            staticInfo[CONST.SGA_NODE_PLATFORM_ID] = SGA_STATIC_CONF[CONST.SGA_NODE_PLATFORM_ID]
            staticInfo[CONST.SGA_NODE_RESOURCES_SEQ] = SGA_STATIC_CONF[CONST.SGA_NODE_RESOURCES_SEQ]
            newNode(staticInfo)
          end
        end
      else
        verbose:error("Erro ao obter os nós [" .. err .. "].")
      end
    else
      local nodeInfo = {}
      nodeInfo[CONST.SGA_NODE_NAME] = SGAD_CONF.name
      nodeInfo[CONST.SGA_NODE_PLATFORM_ID] = SGA_STATIC_CONF[CONST.SGA_NODE_PLATFORM_ID]
      nodeInfo[CONST.SGA_NODE_NUM_PROCESSORS] = SGA_STATIC_CONF[CONST.SGA_NODE_NUM_PROCESSORS]
      nodeInfo[CONST.SGA_NODE_RESOURCES_SEQ] = SGA_STATIC_CONF[CONST.SGA_NODE_RESOURCES_SEQ]
      newNode(nodeInfo)
    end
  end

  if GRIDS[sgaName] then
    SGAD_CONF.loadlib = GRIDS[sgaName].loadlib or "torquePBS"
    if not SGA_DAEMON:openLib() then
      verbose:error("Não tem a lib definida.")
    end

    configCSFS(GRIDS[sgaName].csfs)
    GRIDS[sgaName].csfs = nil

    loadGrid(GRIDS[sgaName])
  end
end -- function setupSGAConfiguration

-- --------------------------------------------------------------------------------
-- -- Preenche a configuração de uma máquina com a configuração padrão.
-- --------------------------------------------------------------------------------
-- function fillDefaults(tab)
--   local def = SGAD_CONF.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 -- function fillDefaults

--------------------------------------------------------------------------------
-- Define a configuraçã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 getNodes

--------------------------------------------------------------------------------
-- 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 -- function cloneNode(n)

--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
-- 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 -- function minToGo

--------------------------------------------------------------------------------
-- 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 -- function time


--------------------------------------------------------------------------------
-- Altera o título da janela do shell que está executando o comando
--------------------------------------------------------------------------------
function setWinTitle()
  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 -- function setWinTitle

--------------------------------------------------------------------------------
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
-- Verifica os paths configurados para o SGA
--------------------------------------------------------------------------------
function checkConfiguration()
  -- verifica a configuração dos nós
  if not SGA_NODES or type(SGA_NODES) ~= type({})  then
     return "Falha na configuração dos nós do SGA."
  end

  if CSFS_FLAG 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 CSFS_FLAG or CSFS.use_local_root_directories

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

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

  if not SGA_STATIC_CONF[CONST.SGA_SANDBOX_ROOT_DIR] then
    return "Propriedade obrigatória '"..CONST.SGA_SANDBOX_ROOT_DIR.."' do SGA não encontrada."
  elseif not ext.isDir(SGA_STATIC_CONF[CONST.SGA_SANDBOX_ROOT_DIR]) then
  verbose:init("Criando diretório base de sandbox em: "..SGA_STATIC_CONF[CONST.SGA_SANDBOX_ROOT_DIR])
  if not ext.mkdirs(SGA_STATIC_CONF[CONST.SGA_SANDBOX_ROOT_DIR]) then
    return "Diretório de sandbox não pode ser criado: "..SGA_STATIC_CONF[CONST.SGA_SANDBOX_ROOT_DIR]
    end
  end
  local sandboxPath, err = ext.dirGetAbsolutePath(SGA_STATIC_CONF[CONST.SGA_SANDBOX_ROOT_DIR])
  if err then
    return "Caminho absoluto do diretório de sandbox não encontrado: "..SGA_STATIC_CONF[CONST.SGA_SANDBOX_ROOT_DIR]..". Erro: "..err
  end
  verbose:init("Diretório base de sandbox em: "..sandboxPath)

  SGA_STATIC_CONF[CONST.SGA_PROJECT_ROOT_DIR] = projPath
  SGA_STATIC_CONF[CONST.SGA_ALGORITHM_ROOT_DIR] = algoPath
  SGA_STATIC_CONF[CONST.SGA_SANDBOX_ROOT_DIR] = sandboxPath
  SGAD_CONF.project_root_directory = projPath
  SGAD_CONF.algorithm_root_directory = algoPath
  SGAD_CONF.sandbox_root_directory = sandboxPath
  -- for i, node in ipairs(SGA_NODES) do
  --   node.csbase_project_root_directory = projPath
  --   node.csbase_algorithm_root_directory = algoPath
  --   node.csbase_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 CSFS_FLAG 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 CSFS_FLAG and not CSFS.launch_daemon and CSFS.use_local_root_directories then
      verbose:init("[WARN] A configuração do CSFS será ignorada." )
  end
end -- function checkConfiguration

--------------------------------------------------------------------------------
-- Inicializacao do servidor SGA daemon
--------------------------------------------------------------------------------
function SGA_DAEMON:initServer()
  setWinTitle()

  local err = checkConfiguration()
  if err then
    return nil, err
  end

  local orbConfigs = {}
  if SGAD_CONF.sga_addr then
    local addr = SGAD_CONF.sga_addr
    string.gsub(addr,"^([^:]*):?(%d*)$",
      function(host, port)
        if host and host ~= "" then
          orbConfigs.host = host
        end
        if port and port ~= "" then
          orbConfigs.port = tonumber(port)
        end
      end
    )
  end

  if SGA_STATIC_CONF.ssl then
    orbConfigs.flavor = "cooperative;corba;corba.ssl;kernel.ssl"
    orbConfigs.options = {
      client = {
        security = "required",
        ssl = SGA_STATIC_CONF.ssl,
      },
      server = {
        security = "required",
        ssl = SGA_STATIC_CONF.ssl,
      },
    }
  end

  local orb = oil.init(orbConfigs)
  sgaidl.loadto(orb)
  self.orb = orb

  -- ativa o servidor CORBA para o SGAD
  self.servant = orb:newservant(self)

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

  if SGA_STATIC_CONF.ssl then
    self.ssi = orb:newproxy(assert(oil.readfrom(SGAD_CONF.ssi_ior_file)), "synchronous", sgaidl.types.SGAManager)
  else
      -- obtem a localizacao do SSI e cria o seu proxy
    if not SGAD_CONF.ssi_corbaloc then
       return nil, "Falha na configuração do IOR do SSI."
    end
    self.ssi = orb:newproxy(SGAD_CONF.ssi_corbaloc, "synchronous", sgaidl.types.SGAManager)
  end

  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
  for i, node in ipairs(SGA_NODES) do
    local config, err = SGAD_CONF.serverManager.getConfiguration(node[CONST.SGA_NODE_NAME])
    if not err then
      node[CONST.SGA_NODE_NUM_PROCESSORS] = config[CONST.SGA_NODE_NUM_PROCESSORS]
      node[CONST.SGA_NODE_MEMORY_RAM_INFO_MB] = config[CONST.SGA_NODE_MEMORY_RAM_INFO_MB]
      node[CONST.SGA_NODE_MEMORY_SWAP_INFO_MB] = config[CONST.SGA_NODE_MEMORY_SWAP_INFO_MB]
    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 -- function SGA_DAEMON:initServer

--------------------------------------------------------------------------------
-- 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 -- function SGA_DAEMON:runBenchmarks

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

--------------------------------------------------------------------------------
-- 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 -- function SGA_DAEMON:Retry()

--------------------------------------------------------------------------------
-- Recebe uma estrutura com as proprieades dinâmicas (opcional) e
-- completa com as propriedades estáticas do SGA e seus nós.
--------------------------------------------------------------------------------
function SGA_DAEMON:buildSGAProperties()
  local SGAProperties = {
    properties = {}, -- Propriedades do SGA (dicionário)
    nodesProperties = {}, -- Propriedades de cada nó (sequencia de dicionários)
  }

  local properties = {}
  properties[CONST.SGA_CSFS_HOST] = (CSFS.properties and CSFS.properties.HOST)
    or self.NO_VALUE
  properties[CONST.SGA_CSFS_PORT] = (CSFS.properties and tonumber(CSFS.properties.PORT))
    or self.ERROR_CODE
  properties[CONST.SGA_CSFS_ROOT_DIR] = (not CSFS.use_local_root_directories and CSFS.properties and CSFS.properties.CANONICAL_ROOT_DIR) or self.NO_VALUE
  -- properties.csbase_sga_name = SGA_STATIC_CONF.csbase_sga_name
  -- properties.csbase_sga_enabled = SGA_STATIC_CONF.csbase_sga_enabled
  -- properties.csbase_file_separator = SGA_STATIC_CONF.csbase_file_separator
  -- properties.csbase_algorithm_root_directory = SGA_STATIC_CONF.csbase_algorithm_root_directory
  -- properties.csbase_project_root_directory = SGA_STATIC_CONF.csbase_project_root_directory
  -- properties.csbase_sandbox_root_directory = SGA_STATIC_CONF.csbase_sandbox_root_directory
  -- -- Dinâmicas
  -- properties.csbase_has_disk_access = SGAD_CONF.csbase_has_disk_access
  -- properties.csbase_jobs_info = SGAD_CONF.csbase_jobs_info
  for k, v in pairs(SGA_STATIC_CONF) do
    if k == CONST.SGA_NODE_RESOURCES_SEQ then
      local resourceMap = ext.listToMap(k, v)
      for _k, _v in pairs(resourceMap) do
        properties[_k] = _v
      end
    else
      properties[k] = v
    end
  end

  SGAProperties.properties = ext.mapToDictionary(properties)

  local nodesProperties = {}
  for i, node in ipairs(SGA_NODES) do
    local nodeProp = {}
    for k, v in pairs(node) do
      if k == CONST.SGA_NODE_RESOURCES_SEQ then
        local resourceMap = ext.listToMap(k, v)
        for _k, _v in pairs(resourceMap) do
          nodeProp[_k] = _v
        end
      else
        nodeProp[k] = v
      end
    end

    local nodeName = node[CONST.SGA_NODE_NAME]
    if SGA_DAEMON.loadAvg[nodeName] then
      local avg = SGA_DAEMON.loadAvg[nodeName]:getLoad()
      nodeProp[CONST.SGA_NODE_LOAD_AVG_1MIN_PERC] = avg.loadAvg1min
      nodeProp[CONST.SGA_NODE_LOAD_AVG_5MIN_PERC] = avg.loadAvg5min
      nodeProp[CONST.SGA_NODE_LOAD_AVG_15MIN_PERC] = avg.loadAvg15min
    end

    table.insert(nodesProperties, ext.mapToDictionary(nodeProp))
  end

  SGAProperties.nodesProperties = nodesProperties

  return SGAProperties

end -- function SGA_DAEMON:buildSGAProperties

-------------------------------------------------------------------------
-- 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 = 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 = pcall(self.ssi._non_existent, self.ssi)
    -- TODO Capturar a exceção de certificado inválido
  end

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

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

    local infos = SGA_DAEMON:buildSGAProperties()

    verbose:debug("Enviando info do SGA...")

    stat, updateInterval, exception = pcall(self.ssi.registerSGA, self.ssi,
                                     self.servant, myname, infos)
    if 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 ""
      if exception ~= nil and type(exception) ~= "string" then
        local exceptMsg
        if exception._repid == sgaidl.types.InvalidParameterException then
          exceptMsg = exception.message
        elseif exception._repid == sgaidl.types.SGAAlreadyRegisteredException then
          exceptMsg = exception.message
        elseif exception._repid == sgaidl.types.NoPermissionException then
          exceptMsg = exception.message
        elseif exception._repid == corbasysex.TRANSIENT then
          exceptMsg = except.completed, except.minor
        else
          -- error(except) -- rethrow
        end
        verbose:debug(exceptMsg)
      end

      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.")

  self:loadPersistentData()
end -- function SGA_DAEMON:register

-------------------------------------------------------------------------
-- 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
    verbose:debug("Renovando o registro do SGA no SSI.")
    local suc, isAlive, exception =
      pcall(self.ssi.isRegistered, self.ssi,  self.servant, myname)
    if suc and isAlive then
      self.isConnected = true
      return
    else
      if exception ~= nil and type(exception) ~= "string" then
        if exception._repid == sgaidl.types.InvalidSGAException then
          verbose:platerror(exception.message)
        elseif exception._repid == sgaidl.types.InvalidSGAException then
          verbose:platerror(exception.message)
        elseif exception._repid == corbasysex.TRANSIENT then
          verbose:platerror(exception.completed, exception.minor)
        else
          -- error(exception) -- rethrow
        end
      end
    end
  end
  -- Debug
  -- showMemUse('Failure: ')
  verbose:error("Falha na acessibilidade do SSI. Registro sera refeito.")
  self.isConnected = false
  self:register()
end -- function SGA_DAEMON:isRegistered

--------------------------------------------------------------------------------
-- 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

  -- servermanager é uma tabela global exportada pelo biblioteca
  local libname
  if SGAD_CONF.loadlib then
    servermanager = nil
    -- Retirar todas as '/' de loadlib
    libname = string.gsub(SGAD_CONF.loadlib,"(/)","")
    local fname = string.format("%s.lua", libname)

    local dirs = {}
    dirs[#dirs+1] = SGAD_CONF.usrlibsdir -- diretório de bibliotecas do usuário
    dirs[#dirs+1] = SGAD_CONF.sgalibsdir -- diretório de bibliotecas do sga
    dirs[#dirs+1] = SGAD_CONF.path -- diretório dos fontes do sga
    for index, dir in ipairs(dirs) do
      dirs[index] = dir.."/?.lua"
    end
    local backup = package.path
    package.path = table.concat(dirs, ";")
    local ok, errmsg = pcall(require, libname) -- @TODO:[maia] avoid CPath loaders
    package.path = backup
    if ok then
      verbose:init("Biblioteca do SGA '",libname,"' carregada")
    else
      verbose:error("Falha ao carregar a biblioteca do SGA '",libname,"': ",errmsg)
      os.exit(0)
    end

    if not servermanager then
      verbose:error("Biblioteca do SGA ",libname," não definiu o 'servermanager'") -- @TODO: find a better message
      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 -- function SGA_DAEMON:openLib()

-------------------------------------------------------------------------
-- 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")

  setupSGAConfiguration()
end -- function SGA_DAEMON:loadBasicConfiguration

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

-------------------------------------------------------------------------
-- Obtém os dados estáticos do SGA
-------------------------------------------------------------------------
function SGA_DAEMON:getNodes()
  return SGA_NODES
end -- function SGA_DAEMON:getNodes

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

-------------------------------------------------------------------------
-- 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 -- function SGA_DAEMON:createCSFSProcess

------------------------------------------------------------------------
-- 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 -- function SGA_DAEMON:createIPerfProcess

---------------------------------------------------------------------
-- 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 -- function SGA_DAEMON:checkDiskAccess

-------------------------------------------------------------------------
-- Loop principal do programa
-------------------------------------------------------------------------
function SGA_DAEMON:startServer()
  -- @TODO:[maia] use 'cothread.Timer' instead.
  local add = function (tb)
    oil.newthread(function()
      while true do
        tb.wakeme = oil.time() + tb.freq
        tb:func()
        -- Garante o yield
        oil.sleep(math.max(tb.wakeme - oil.time(), 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 -- function SGA_DAEMON:startServer()

-------------------------------------------------------------------------
-- Apresentação do programa
-------------------------------------------------------------------------
function SGA_DAEMON:doPresentation()
  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 = " ("..SGA_STATIC_CONF[CONST.SGA_NODE_PLATFORM_ID]..")"
  if SGA_STATIC_CONF[CONST.SGA_NODE_PLATFORM_ID] ~= SGA_STATIC_CONF[CONST.PLATFORM_OS] then
    platmsg = platmsg .. " ("..SGA_STATIC_CONF[CONST.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(SGA_STATIC_CONF[CONST.SGA_NODE_NUM_PROCESSORS]))
  verbose:init("Memória total do nó principal: "..
    tostring(SGA_STATIC_CONF[CONST.SGA_NODE_MEMORY_RAM_INFO_MB] +
             SGA_STATIC_CONF[CONST.SGA_NODE_MEMORY_SWAP_INFO_MB]))
  verbose:init("Frequência de clock do nó principal: "..
    tostring( SGA_STATIC_CONF[CONST.SGA_NODE_CLOCK_SPEED_MHZ]))
  verbose:init("Ordenação de bytes no processador do nó principal: "..
    tostring( SGA_STATIC_CONF[CONST.BYTE_ORDER]))
  if SGA_STATIC_CONF.deny_node_selection then
    verbose:init("Não permite a seleção de nós.")
  end
  if SGA_STATIC_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 -- function SGA_DAEMON:doPresentation

-------------------------------------------------------------------------
-- 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 -- function showMemUse

--------------------------------------------------------------------------------
--
--------------------------------------------------------------------------------
function SGA_DAEMON:exit(status)
  verbose:kill("Encerrando...")

  SGAD_CONF.serverManager.close()

  if CSFS.launch_daemon == YES and CSFS.process ~= nil then
    CSFS.process:control("TERMINATE")
  end

  if SGAD_CONF.net_benchmark == YES and IPERF.process ~= nil then
    IPERF.process:control("TERMINATE")
  end

  os.exit(status) -- function SGA_DAEMON:exit
end -- function SGA_DAEMON:exit

--------------------------------------------------------------------------------
--
--------------------------------------------------------------------------------
function SGA_DAEMON:getNode(nodeName)
  for i, node in ipairs(SGA_NODES) do
    if string.upper(node[CONST.SGA_NODE_NAME]) == string.upper(nodeName) then
      return SGA_NODES[i]
    end
  end

  return nil
end -- function SGA_DAEMON:getNode

--------------------------------------------------------------------------------
--
--------------------------------------------------------------------------------
function SGA_DAEMON:getNodeNames()
  local tab = {}
  local nodes = SGA_NODES

  for i, node in ipairs(nodes) do
    table.insert(tab, node[CONST.SGA_NODE_NAME])
  end

  return tab
end -- function SGA_DAEMON:getNodeNames

--------------------------------------------------------------------------------
--
--------------------------------------------------------------------------------
function SGA_DAEMON:getHostName()
  return SGAD_CONF.name or self:getNodeNames()[1]
end -- function SGA_DAEMON:getHostName

--------------------------------------------------------------------------------
--
--------------------------------------------------------------------------------
function SGA_DAEMON:isCluster()
  return #self:getNodeNames() > 1
end -- function SGA_DAEMON:isCluster


--------------------------------------------------------------------------------
-- A biblioteca do sga retorna a carga da CPU no ultimo minuto.
-- Cabe a este objeto calcular a carga nos últimos 5 e 15 minutos...
--------------------------------------------------------------------------------

-- Função para criar o objeto que calcula a média de carga na CPU
function SGA_DAEMON.new_loadAvg()
  obj = {
    update = 0,
    load5 = {},
    load15 = {},
    loadAvg1min = 0,
    loadAvg5min = 0,
    loadAvg15min = 0,
    acc1 = function (self, load1, maxold)
      local NOW, err = SGAD_CONF.serverManager.now()
      if err then
        verbose:error(string.format("ERRO: %s", err))
        NOW = SGA_DAEMON.INF
      end
      if maxold and (NOW - self.update) < maxold then
        return
      end
      self.update = NOW
      local load5 = self:acc("load5", load1, 5 * 60)
      local load15= self:acc("load15",load1, 15 * 60)
      self.loadAvg1min = load1
      self.loadAvg5min = load5
      self.loadAvg15min = load15
    end, -- function acc1
    acc = function (self, loadtb, currload, expire)
      local total = 0
      local nelem = 0
      local load = 0
      self[loadtb][self.update] = currload
      for tstamp, load in pairs(self[loadtb]) do
        if (self.update - tstamp) > expire then
          self[loadtb][tstamp] = nil
        else
          total = total + load
          nelem = nelem + 1
        end
      end
      if nelem > 0 then
        load = total / nelem
      end
      return load
    end, -- function acc
    getLoad = function (self)
    return {
      loadAvg1min = self.loadAvg1min,
      loadAvg5min = self.loadAvg5min,
      loadAvg15min = self.loadAvg15min,
    }
    end, -- function getLoad
  }
  return obj
end -- function SGA_DAEMON.new_loadAvg

--------------------------------------------------------------------------------
--
--------------------------------------------------------------------------------
function SGA_DAEMON:readMachineStateInformation(freqTb)
  local update = function (nodeName, lastInfo, maxOld)
    local server = SGAD_CONF.serverManager
    local anyError

    local info, err = server.getInfo(nodeName, nil, maxold)

    if err then
      verbose:error(string.format("ERRO (%s): %s", nodeName, err))
      anyError = true
    end

    -- Verifica se as informações padrões foram obtidas
    if not info[CONST.LOAD_PERC] then
      local msg = "Erro ao obter o percentual de carga do processador."
      verbose:error(string.format("ERRO (%s): %s",nodeName, msg))
    end
    if not info[CONST.MEMORY_RAM_FREE] then
      local msg = "Erro ao obter a quantidade de memória livre."
      verbose:error(string.format("ERRO (%s): %s",nodeName, msg))
    end
    if not info[CONST.MEMORY_SWAP_FREE] then
      local msg = "Erro ao obter a quantidade de memória virtual livre."
      verbose:error(string.format("ERRO (%s): %s",nodeName, msg))
    end
    if not info[CONST.SGA_NODE_NUMBER_OF_JOBS] then
      local msg = "Erro ao obter o número de jobs."
      verbose:error(string.format("ERRO (%s): %s",nodeName, msg))
    end

    local node = SGA_DAEMON:getNode(nodeName)

    for k, v in pairs(info) do
      node[k] = v
    end

    local freeRam   = node[CONST.MEMORY_RAM_FREE]
    local totalRam  = node[CONST.SGA_NODE_MEMORY_RAM_INFO_MB]
    local freeSwap  = node[CONST.MEMORY_SWAP_FREE]
    local totalSwap = node[CONST.SGA_NODE_MEMORY_SWAP_INFO_MB]

    if (freeRam == nil) or (totalRam == nil or totalRam == 0) then
      node[CONST.SGA_NODE_MEMORY_RAM_FREE_PERC] = SGA_DAEMON.ERROR_CODE
    else
      node[CONST.SGA_NODE_MEMORY_RAM_FREE_PERC] = freeRam/totalRam
    end

    if (freeSwap == nil) or (totalSwap == nil or totalSwap == 0) then
      node[CONST.SGA_NODE_MEMORY_SWAP_FREE_PERC] = SGA_DAEMON.ERROR_CODE
    else
      node[CONST.SGA_NODE_MEMORY_SWAP_FREE_PERC] = freeSwap/totalSwap
    end

    if not SGA_DAEMON.loadAvg[nodeName] then
      SGA_DAEMON.loadAvg[nodeName] = SGA_DAEMON:new_loadAvg()
    end

    -- @TODO Usar uma constante para definir a flag de erro
    if (info[CONST.LOAD_PERC] ~= "csbase_error_flag") then
      SGA_DAEMON.loadAvg[nodeName]:acc1(info[CONST.LOAD_PERC], maxold)
    end
    local avg = SGA_DAEMON.loadAvg[nodeName]:getLoad()

    local logMsg = "EXECUTANDO COLETA (ram:%d/swap:%d)(load:%d/%d/%d)."
    verbose:top(
      string.format(logMsg,
        node[CONST.SGA_NODE_MEMORY_RAM_FREE_PERC],
        node[CONST.SGA_NODE_MEMORY_SWAP_FREE_PERC],
        avg.loadAvg1min,
        avg.loadAvg5min,
        avg.loadAvg15min))
    return anyError
  end -- function update

  local nodes = self:getNodes()
  local maxold = freqTb.freq

  if not self.currNodeIdx then
    self.currNodeIdx = 1
    -- self.dynamic_data = {}
    self.disregard_times = {}
  end

  local err
  local currNode = nodes[self.currNodeIdx]
  local nodeName = currNode[CONST.SGA_NODE_NAME]
  local dtimes = self.disregard_times[self.currNodeIdx]
    or SGAD_CONF.disregard_times
    or 0

  if currNode and dtimes > 0 then
     err = update(nodeName, currNode, maxold)
  else
     err = update(nodeName, {}, maxold)
  end

  if err then
    if dtimes == 0 then
      self.disregard_times[self.currNodeIdx] = 0
    else
      self.disregard_times[self.currNodeIdx] = dtimes - 1
    end
  else
    self.disregard_times[self.currNodeIdx] = nil
  end

  if #nodes > 1 then
     if self.currNodeIdx == #nodes then
        freqTb.wakeme = freqTb.wakemeOriginal
     else
        if self.currNodeIdx == 1 then
           freqTb.wakemeOriginal = freqTb.wakeme
        end

        local NOW, err = SGAD_CONF.serverManager.now()
        if err then
          NOW = SGA_DAEMON.INF
        end
        freqTb.wakeme = NOW
     end
     self.currNodeIdx = (self.currNodeIdx % #nodes) + 1
  end

  if self.currNodeIdx == 1 then
    -- Verifica se há falha no acesso ao disco.
    SGA_STATIC_CONF[CONST.SGA_HAS_DISK_ACCESS] = self:checkDiskAccess()

    -- Obtém a infomação dos jobs em execução no SGA
    SGA_STATIC_CONF[CONST.SGA_JOBS_INFO] = SGAD_CONF.serverManager.getJobsInfo() or ""

    -- Envia os dados atualizados para o servidor
    local ssi = SGA_DAEMON.ssi
    local myname = self:getName()
    local sgaProperties = SGA_DAEMON:buildSGAProperties()

    verbose:debug("Atualizando informações do SGA.")

    local suc, wasUpdated, exception =
      pcall(self.ssi.updateSGAInfo, self.ssi, self.servant, myname, sgaProperties)
    if not suc or not wasUpdated then
      if exception ~= nil and type(exception) ~= "string" then
        local exceptMsg
        if exception._repid == sgaidl.types.InvalidParameterException then
          exceptMsg = exception.message
        elseif exception._repid == sgaidl.types.SGANotRegisteredException then
          exceptMsg = exception.message
        elseif exception._repid == sgaidl.types.NoPermissionException then
          exceptMsg = exception.message
        elseif exception._repid == corbasysex.TRANSIENT then
          exceptMsg = exception.completed .. exception.minor
        else
          -- error(exception) -- rethrow
        end
        verbose:debug(exceptMsg)
      end
     verbose:error("Falha no envio de dados do SGA.")
     return nil
    end
  end
end -- function SGA_DAEMON:readMachineStateInformation
