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

Crear un Fail Over Trunk en Asterisk

Llevo tiempo queriendo compartir este post, el Fail Over Trunk es una de las macros  en Asterisk que más hemos utilizado y la que más nos ha servido. Aunque es muy sencillo, te puede ayudar en casos que tu troncal por algún motivo deje de funcionar y tengas que sacar las llamadas por otro lado, sin tener que hacer cambios en el Dialplan.

Supongamos que tenemos dos troncales  Trunk 1 y Trunk 2 podemos configurar hasta Trunk n, las especificaremos como variables globales ya que no van hacer modificadas y utilizaremos otra variable que nos dirá el total de troncales que tenemos definidos, en el extensions.conf lo ponemos de la siguiente manera:

[globals]

TRUNK_1=10.10.50.50
TRUNK_2=10.10.50.51
TOTAL=2

En el Dialplan escribiremos una Macro con el nombre de  “salida”  enviando como parámetros la extensión a marcar y el tiempo de marcado:

[macro-salida]
;ARG1 Numero a marcar
;ARG2 Tiempo de marcado

exten => s,1,NooP(Numero a marcar -> ${ARG1}  | tiempo de marcado -> ${ARG2} )
exten => s,n,Set(CONTADOR=1)
exten => s,n(while),GotoIf($["${CONTADOR}" > "${TOTAL}"]?fin)
exten => s,n,Dial(SIP/${ARG1}@${TRUNK_${CONTADOR}}|${ARG2}|g)
exten => s,n,NooP(DIAL STATUS -> ${DIALSTATUS})
exten => s,n,GotoIf($["${DIALSTATUS}" = "ANSWER"]?fin)
exten => s,n,GotoIf($["${DIALSTATUS}" = "BUSY"]?fin)
exten => s,n,GotoIf($["${DIALSTATUS}" = "NOANSWER"]?fin)
exten => s,n,Set(CONTADOR=$[${CONTADOR} + 1])
exten => s,n,Goto(while)
exten => s,n(fin),MacroExit

Con las variables del DIALSTATUS podemos saber si la llamada ha fallado o si ha tenido un resultado satisfactorio,  por lo que si el dialstatus retorna un CHANUNAVAIL, es posible que este caído nuestro Carrier asignado a ese troncal, con lo que intentará utilizar el segundo troncal especificado.

¿Dejarías toda tu información a una persona.?

Imagina por un momento que una persona que no conoces tenga el control de toda la información que manejas  en sus manos, como son tus correos, mensajes, ubicación exacta en todo momento, tu móvil, tu ordenador, fotos, vídeos, música, fotografías, documentos, tu dirección, datos personales,  donde estudiaste, en donde has estado, tus sitios preferidos, quienes son tu familia, tus amigos,  que pertenencias físicas tienes, hasta conoce cuales son tus sueños y deseos, conoce y maneja tus actividades diarias, las citas que tienes,  etc.  ¿ Lo harías.?

Pues muchos de nosotros lo hacemos sin darnos cuenta.  ¿Hasta dónde crees que podría llegar esto.? El control siempre se ha basado en la información,  deja todo lo que estas haciendo y  ponte a pensar, ¿cuánto crees que saben de ti?, ¿nada?, ¿poco?, ¿mucho?.  Creamos, manejamos y compartimos esta información todos los días, información personal que creemos que de alguna manera  solamente vemos y manejamos nosotros .  Pero no es real, todo esto queda almacenado en servidores ubicados en algún lugar de la tierra, lejos de nuestras manos. Ahora te pregunto ¿cuanto sabes tu sobre ellos.? Solamente lo que ellos quieren dar a conocer, nos hacen creer que desarrollan sistemas para una mejor comunicación y que somos libres de utilizarlos,  nos hacen creer que somos libres en todo lo que hacemos, libres de manejar esta información a nuestro antojo.  ¿Lo crees así.? Resumiendo  todo esto en una sola palabra CONTROL.

Todo esto puede sonar a paranoia, pero no es así, no estoy en contra de la tecnología, pero siempre y cuando esta tecnología no pueda ser usada en nuestra contra.

Quizá con este vídeo con un final oscuro :-) , pueda darles una mejor idea.

(Visto en Business Insider.)

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