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