Category: Erlang

[Erlang] Cargar un fichero config sin reiniciar el sistema

Para todos los que programamos en Erlang es posible que en algún momento necesitemos hacer un reload (cargar y/o recargar) el fichero de configuración sin tener que reiniciar el sistema. Después de buscar y ver mas aplicaciones encontré una solución que se implementa en el ejabberd.

Vamos por partes para aquellos que están empezando a programar en Erlang.

Cargar un fichero de configuración

En la documentación [Erlang: config ] podemos encontrar toda la información referente al fichero de configuración. En resumen el fichero debe de tener la siguiente estructura:

[{Application1, [{Par11, Val11}, ..]},
 ..
 {ApplicationN, [{ParN1, ValN1}, ..]}].

Para poder cargarla se debe de especificar en la línea de comando al lanzar el Erlang con el parámetro -config File tal y como se muestra a continuación:

# erl -sname MaquinaA -config /PATH/file.config

Supondremos que nuestra aplicación tiene por nombre biblioteca y se encarga de registrar los tipos de libros escribiéndolas en un fichero especifico. Nuestro ejemplo de configuración seria de la siguiente manera:

[{biblioteca, %% application name
    [{rutas, %% par
           [{ciencia_ficcion, "/tmp/ciencia_ficcion/"},
           {historia, "/tmp/historia/"},
           {novela, "/tmp/novela/"}]
    }]
}].

Para poder obtener estos datos hay que iniciar la aplicación [Erlang: application] y exportarlos con el siguiente comando :
application:get_env(Application, Par) -> Env tal y como se muestra a continuación:

(nodo1@host)1> application:start(biblioteca).
ok
(nodo1@host)2> application:get_env(biblioteca, rutas).
[{biblioteca,[{rutas,[{ciencia_ficcion,"/tmp/ciencia_ficcion/"},
   {historia,"/tmp/historia/"},
   {novela,"/tmp/novela/"}]}]}]
ok

Hasta aquí todo muy bonito, pero ¿qué pasa si quiero cambiar la ruta de “historia” desde el fichero de configuración.? Con este modelo no es posible ya que la información del config se carga en memoria una sola vez y al iniciar la aplicación. Entonces ¿cómo hacemos para que sea más dinámico y no tengamos que reiniciar cada vez que realicemos un cambio en la configuración?.

Divide y vencerás

¡Fácil.! Creando dos ficheros de configuración. El primero que se va a cargar al inicio de la aplicación, tendrá la ruta del segundo fichero donde vendrá especificada toda la configuración.
Sigamos con el ejemplo, ahora nuestro fichero file.config tendrá lo siguiente:

[{biblioteca, [
        {cfg_path,"/urs/local/etc/biblioteca/biblioteca.cfg"}
        ]
}].

Podemos observar que especificamos la ruta fija de otro fichero de configuración llamado biblioteca.cfg, la estructura que tendrá este cfg es la siguiente:

{categorias,[
        {ciencia_ficcion,"/tmp/ciencia_ficcion/"},
        {historia,"/tmp/historia/"},
        {novela,"/tmp/novela/"}
]}.

Ahora para poder cargar el segundo fichero utilizamos [ file:consult ] para rescatar los terms del fichero. Así podemos cambiar los datos y utilizarlos en el sistema sin la necesidad de reiniciar la aplicación.

Nuestro código de configuración estará de la siguiente manera:

config.erl

-module(config).
-export([get_config_file/2, load_file/1]).
 
%% @spec get_config_path(App, Par)-> {cgf, Path} | exit
%% @doc Obtenemos el fichero de configuracion 
 
get_config_file(App, Par) ->
  case application:get_env(App, Par) of
    {ok, Config} -> Config;
    undefined ->
      io:format("No existe fichero de configuracion", []),
      exit(normal);
    Reason ->
       io:format("Error fichero: ~p ~p Reason: ~p", [App, Par, Reason]),
       exit(normal)
  end.
 
%% @spec load_file(path/file) -> [terms] | []
%% @doc consultamos el fichero y obtenemos la informacion
 
 
load_file(File) ->
  case file:consult(File) of
    {ok, Data} -> 
      lists:foldl(fun search_terms/2, [], Data);
    {error, {_LineNumber, erl_parse, _ParseMessage} = Reason} ->
      ExitText = lists:flatten(File ++ " aproximadamente en la linea "
        ++ file:format_error(Reason)),
      exit(ExitText);
    {error, Reason} ->
      ExitText = lists:flatten(File ++ ": "
        ++ file:format_error(Reason)),
      exit(ExitText)
 end.
 
%% @spec search_terms(Term, List)
%% @doc Busca los términos y los añade a una lista
 
search_terms(Term, List) ->
  case Term of
    {categorias, Data} -> lists:append(List, Data);
     _ -> List
   end.

Ahora lo probamos en consola.

 
(nodo1@host)1> applicacion:start(biblioteca).
 ok
 
(nodo1@host)2> Path = config:get_config_file(biblioteca, cfg_path).
{cfg_path,"/urs/local/etc/biblioteca/biblioteca.cfg"}
 
(nodo1@host)3>  config:load_file(Path).
 [{ciencia_ficcion,"/tmp/ciencia_ficcion/"},
 {historia,"/tmp/historia/"},
 {novela,"/tmp/novela/"}]

Si cambias el fichero del cfg y ejecutas el load_file obtendrás los cambios deseados.

Conclusiones

Con estas funciones tendremos un sistema con la opción de hacer un reload de la configuración, les recomiendo ponerlo en un gen_server, de esta manera nos ahorraremos los conflictos de escritura y lectura.

Conectarse a un nodo Erlang en remoto

A continuación explicaremos en unos sencillos pasos como podemos conectarnos a un nodo de Erlang en remoto.

HostName

Es necesario que las máquinas se puedan ver entre sí a través del DNS, por lo cual hay que incluir los hostnames en el fichero de hosts /etc/hosts, en el caso que los nodos estén en diferentes máquinas.

Name & Cookie

Para poder acceder a un nodo de Erlang en remoto es necesario iniciar Erlang con un nombre, utilizando el parámetro de -name si estamos en diferentes máquinas, pero si iniciamos los dos nodos en la misma máquina es necesario utilizar el parámetro -sname.
También debemos de asegurarnos en compartir la misma cookie en los dos nodos, ya que funciona como una clave para poder acceder al nodo.

En este ejemplo utilizaremos nodoA – MaquinaA y nodoB – MaquinaB

#erl -setcookie COOKIE1234 -sname nodoA
#erl -setcookie COOKIE1234 -sname nodoB

Es posible también cambiar la cookie una vez que estemos en la consola de erlang, utilizando el siguiente comando:

nodoA@MaquinaAl)1> erlang:set_cookie(node(), 'COOKIE1234').
true
nodoA@MaqquinaA)2> erlang:get_cookie().
COOKIE1234

Conectando Nodos

Para saber si el nodoB puede vernos, podemos hacer un ping utilizando el módulo net_adm y el nombre de la maquina B:

nodoA@MaquinaAl)1> net_adm:ping('nodoB@MaquinaB').
pong
nodoA@MaquinaAl)2> net_adm:ping('nodoCC@MaquinaCC').
pang
nodoA@MaquinaAl)3>nodes().
[nodoB@MaquinaB]

Conectarse al nodo

Una ves que podemos ver el nodoB en la MaquinaA, presionamos Control+G para entrar en el modo de control de trabajos.

(nodoA@MaquinaA)4>
User switch command
 -->

Si ponemos h, nos mostrará la ayuda de los comandos:

User switch command
 --> h
  c [nn]            - connect to job
  i [nn]            - interrupt job
  k [nn]            - kill job
  j                 - list all jobs
  s [shell]         - start local shell
  r [node [shell]]  - start remote shell
  q        - quit erlang
  ? | h             - this message
 -->

Si queremos mostrar los trabajos actuales :

 --> j
   1* {shell,start,[init]}

Para iniciar la conexión al nodoB debemos utilizar el comando r y el nombre del nodo junto con su dns como se muestra a continuación:

(nodoA@MaquinaA)1>
User switch command
 --> j
   1* {shell,start,[init]}
 --> r nodoB@MaquinaB
 --> j
   1  {shell,start,[init]}
   2* {nodoB@MaquinaB,shell,start,[]}
 -->

Como podemos observar una vez que hacemos la conexión al nodoB y mostramos los trabajos aparece el número 2 conectado al nodoB
Para entrar a la consola del nodoB solo necesitamos utilizar el comando c y el numero de trabajo:

 --> c 2
Eshell V5.6.5  (abort with ^G)
(nodoB@MaquinaB)1>

Y listo.!!!! ya estamos dentro.

Salir del nodoB

Y ¿ahora cómo salimos?. Bueno cualquiera pensará, fácil le doy q(). o Control +C y listo, pues no se te ocurra hacer esto en producción ya que pararíamos el nodoB por completo.
Para poder salir debemos regresar al nodoA, por lo que repetimos el proceso pero ahora conectándonos al trabajo número 1

(nodoA@MaquinaA)1>
User switch command
 --> j
   1* {shell,start,[init]}
 --> r nodoB@MaquinaB
 --> j
   1  {shell,start,[init]}
   2* {nodoB@MaquinaB,shell,start,[]}
 --> c 2
Eshell V5.6.5  (abort with ^G)
(nodoB@MaquinaB)1>
User switch command
 --> j
   1  {shell,start,[init]}
   2* {nodoA@MaquinaA,shell,start,[]}
 --> c 1
 
(nodoA@MaquinaA)2>

Con estos simples pasos podemos acceder a diferentes nodos de Erlang conectados entre si.

Como obtener la MacAddress en Erlang

Para que podamos obtener la MacAddress desde Erlang, utilizaremos el módulo Inet, que nos provee acceso a los protocolos de TCP/IP. Pero la parte que vamos a manejar no esta documentada ni soportada, por lo que no les puedo asegurar que funcione en todos los sistemas. Por lo menos en Linux funciona.
Para obtener las interfaces utilizamos :

1> inet:getiflist().
{ok,["lo","eth0"]}

Con ifget obtenemos la información de la interfaz que necesitamos como por ejemplo. :
[addr, broadaddr, dstaddr, mtu, netmask, flags, hwaddr]

2> inet:ifget("eth0", 
    [addr, broadaddr, dstaddr, mtu, netmask, flags, hwaddr]).
{ok,[{addr,{10,10,10,53}},
     {broadaddr,{10,10,10,255}},
     {dstaddr,{10,10,10,53}},
     {mtu,1500},
     {netmask,{255,255,255,0}},
     {flags,[up,broadcast,running,multicast]},
     {hwaddr,[0,64,203,94,69,179]}]}

Si podemos observar, el hwaddr no esta convertido en hexadecimal, por lo que tenemos que hacer la conversión utilizando el módulo de io_lib, y hacemos un lists:flatten para unirlos.

1> io_lib:format("~.16B~.16B~.16B~.16B~.16B~.16B",
    [0,64,203,94,69,179]).
["0","40","CB","5E","45","B3"]
2> lists:flatten(["0","40","CB","5E","45","B3"]).
"040CB5E45B3"

Con esto podemos obtener la MacAddres, de la máquina que estemos utilizando.

WordPress Themes