Monitorizar datos usando ElasticSearch

Solapas principales

API REST, HealthShare

¡Hola a tod@s!

El objetivo de este artículo es describir cómo emplear una BBDD como ElasticSearch, para permitirnos monitorizar nuestros proyectos.

 

Como paso inicial nos instalaremos una máquina virtual con un servidor ubuntu, de forma que luego incorporaremos ElasticSearch en él.

1.1    Instalar Ubuntu Server

Para ello vamos a la web: https://ubuntu.com/download/server#download

Y nos descargamos Ubuntu Server:

En VirtualBox asociamos el Sistema Operativo que nos acabamos de descargar:

Reservar la mitad de la memoria:

Reservar 20 GigaBytes para el disco duro virtual:

Al iniciar la máquina virtual, se nos pedirá que busquemos dónde nos hemos descargado Ubuntu:

 

En el resto de pasos dejar las opciones por defecto. Cuando nos pida los datos del servidor, elegiremos el nombre, usuario y contraseña que queramos:

 

Instalamos SSH

 

1.2    Instalar ElasticSearch

 

A continuación ponemos nuestro usuario y contraseña en la terminal. En nuestro caso, usuario: student, contraseña: student

Añadimos el repositorio desde el cual nos descargaremos elastic search:

wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -

 

Instalamos https:

sudo apt-get install apt-transport-https

 

Obtenemos elastic search:

echo "deb https://artifacts.elastic.co/packages/7.x/apt stable main" |

sudo tee -a /etc/apt/sources.list.d/elastic-7.x.list

 

Lo instalamos:

sudo apt-get update && sudo apt-get install elasticsearch

 

Lo configuramos editando elasticsearch.yml

sudo vi /etc/elasticsearch/elasticsearch.yml

 

Descomentamos la línea node.name:

 

 

Cambiamos network.host: 0.0.0.0

Escribimos en discovery.seed_hosts: [“127.0.0.1”]

Ponemos cluster.initial_master_nodes:[“node-1”]

 

Guardamos los cambios, pulsando Escape y :wq

Para iniciar el servicio y asegurar que se ejecuta automáticamente cuando encendemos el servidor:

sudo /bin/systemctl daemon-reload

sudo /bin/systemctl enable elasticsearch.service

sudo /bin/systemctl start elasticsearch.service

 

Para verificar que nos conectamos a elastic search, escribimos:

curl –XGET 127.0.0.1:9200

 

Obtenemos una respuesta como la siguiente:

2 Conectarnos al servidor mediante SSH

La idea es enviar peticiones desde nuestro ordenador local con Windows, al servidor en la máquina virtual Ubuntu.

1.1    Abrir puertos en la máquina virtual

En VirtualBox seleccionamos la máquina virtual con Ubuntu, Configuración, Red:

 

Pulsamos en Reenvío de puertos:

 

Pulsamos en el más a la derecha y añadimos la apertura de puertos para Elastic Search, Kibana y SSH:

 

1.2    Descargarnos Putty

Con esta aplicación logramos conectarnos mediante SSH

https://www.putty.org/

La conexión será a 127.0.0.1 en el puerto 22 por ssh

 

Con ello estaremos conectados:

 

 3 Usar POSTMAN

Una herramienta que nos ahorra tiempo es POSTMAN, ya que en vez de escribir una y otra vez las consultas a la BBDD, las podemos guardar. Por ejemplo si queremos saber el estado de nuestro servidor, pondremos en la petición: 

http://localhost:9200

Ponemos como método el GET y enviamos:

Y nos responderá el servidor con su información:

4 Enviando peticiones desde HealthShare al servidor local.

4.1 Operación

Para el ejemplo, vamos a contar con una Operación, la cual gestiona el añadirDispositivos (lo cual significa que un usuario ha aceptado que la aplicación le envíe notificaciones), y el crearNotificacion (lo que implica generar un mensaje, el cual enviar al dispositivo del usuario).

Class Operaciones.HTTP.Monitorizacion.ElasticSearch Extends Ens.BusinessOperation [ ProcedureBlock ]

{

Parameter ADAPTER = "EnsLib.HTTP.OutboundAdapter";

Parameter INVOCATION = "Queue";

//Metodo para el GET

...

Method POST(request As EsquemasDatos.Monitorizacion.ElasticSearch.BaseRequest) As EsquemasDatos.Monitorizacion.ElasticSearch.BaseResponse

{

        if (request.%ClassName() = "añadirDispositivoRequest") {

                set body = ##class(EsquemasDatos.Monitorizacion.ElasticSearch.añadirDispositivoBaseRequest).%New()

               

        set body.idapp = request.idapp

        set body.usuario = request.usuario

        set body.iddispositivo = request.iddispositivo

        set body.fecha = request.fecha

        set body.hora = request.hora

        set body.resultado = request.resultado

        set body.descripcionresultado = request.descripcionresultado

    }

    if (request.%ClassName() = "crearNotificacionRequest") {

        set body = ##class(EsquemasDatos.Monitorizacion.ElasticSearch.crearNotificacionBaseRequest).%New()

        set body.idapp = request.idapp

        set body.numexpediente = request.numexpediente

        set body.idioma = request.idioma

        set body.tiponotificacion = request.tiponotificacion

        set body.fecha = request.fecha

        set body.hora = request.hora

        set body.descripcionresultado = request.descripcionresultado

        set body.resultado = request.resultado

       

       

    }

   

    set httpResponse = ##class(%Net.HttpResponse).%New()

    set httpRequest = ##class(%Net.HttpRequest).%New()

   

    set httpRequest.Server = ##class(Util.TablasMaestras).getValorMaestra("ElasticSearch","servidor")

    $$$LOGINFO("Servidor: "_httpRequest.Server)

    set httpRequest.Port = ##class(Util.TablasMaestras).getValorMaestra("ElasticSearch","puerto")

    set httpRequest.ContentType = "application/json"

    set status = ##class(%ZEN.Auxiliary.jsonProvider).%WriteJSONStreamFromObject(httpRequest.EntityBody, body,,,,"aeloqtuw")

    set url = request.indice_"/_doc/"_request.opciones

    set tSC = httpRequest.Post(url)

    set httpResponse = httpRequest.HttpResponse

    Quit ..ProcesarRespuesta(httpResponse)

}

//Metodo para el PUT

//Metodo para el DELETE

Method SeleccionarMetodo(request As EsquemasDatos.Monitorizacion.ElasticSearch.BaseRequest, Output response As EsquemasDatos.Monitorizacion.ElasticSearch.BaseResponse) As %Library.Status

{

    $$$LOGINFO("La accion es: "_request.accion)

    try {

        if (request.accion = "GET") {

            set response = ..GET(request)   

        } elseif (request.accion = "POST") {

            set response = ..POST(request)   

        } elseif (request.accion = "PUT") {

            set response = ..PUT(request)

        } elseif (request.accion = "DELETE") {

            set response = ..DELETE(request)

        }

    }catch {

        set response = ##class(EsquemasDatos.Monitorizacion.ElasticSearch.BaseResponse).%New()

        set response.error = "-1"

        set response.descripcion = "Error interno"

    }

    Quit $$$OK

}

Method ProcesarRespuesta(data As %Net.HttpResponse) As EsquemasDatos.Monitorizacion.ElasticSearch.BaseResponse

{

    set error = "{""error"""

    set huboError = ""

    set ok = """result"":""created"""

    set successful = """successful"":1"

    While (data.Data.AtEnd = 0) {

            set linea = data.Data.Read()

            if ($FIND(linea,error) > 0){

                set huboError = 1

            } elseif ($FIND(linea, ok) > 0) {

                set huboError = 0   

            } elseif ($FIND(linea, successful) > 0){

                set huboError = 0

            }

        }

   

    set response = ##class(EsquemasDatos.Monitorizacion.ElasticSearch.BaseResponse).%New()

    if (huboError = 1){

        set response.error = 1

        set response.descripcion = "No se pudo realizar la operación"

    } elseif (huboError = 0) {

        set response.error = 0

        set response.descripcion = "Se realizó correctamente la operación"   

    }

    Quit response

}

XData MessageMap

{

<MapItems>

    <MapItem MessageType="EsquemasDatos.Monitorizacion.ElasticSearch.BaseRequest">

        <Method>SeleccionarMetodo</Method>

    </MapItem>

</MapItems>

}

}

Como vemos, el código anterior explica que: cuando recibimos un mensaje del tipo baseRequest, se selecciona que método ejecutar, según la acción en la equest. Además en el caso concreto del POST, el que vamos a ver en este artículo, tenemos que diferenciar los campos usados cuando nos viene un añadirDispositivoRequest o un crearNotificacionRequest,

Una vez rellenado el body con los campos apropiados, cogemos el servidor y puerto, en nuestro caso 127.0.0.1 9200, añadimos la cabecera ContentType: “application/json” (porque es imprescindible comunicarle a la BBDD el tipo de contenido). Construimos la URL, de forma que el índice será “la tabla” donde guardaremos la info. Por ejemplo en nuestro caso serían los índices: notificacionespush_anadirdispositivo y notificacionespush_crearnotificacion.

Luego hacemos el POST, obtenemos la respuesta, y la procesamos, definiendo si hubo error o fue correcta.

Una pregunta se nos viene a la mente ¿por qué usamos un Mensaje llamado EsquemasDatos ... BaseRequest? El motivo es que necesitamos de los siguientes campos:

Class EsquemasDatos.Monitorizacion.ElasticSearch.BaseRequest Extends Ens.Request

{

Property indice As %String(MAXLEN = "");

Property url As %String(MAXLEN = "");

Property puerto As %String(MAXLEN = "");

Property accion As %String(MAXLEN = "");

Property opciones As %String(MAXLEN = "");

Donde el índice es la “tabla” donde se guarda. La url y puerto del servidor. La acción, el verbo REST a emplear. Las opciones, son parámetros que irían en la URL. Por ejemplo, la URL para enviar un GET mediante POSTMAN sería: http://localhost:9200/notificacionespush_anadirdispositivo/_search?size=100

De forma que le estamos pidiendo al servidor que nos de los 100 primeros registros del índice notificacionespush_anadirdispositivo. De esta forma “_search?size=100” iría en opciones.

Además es muy importante que entendamos cómo y por qué funciona el if (request.%ClassName()==...) donde ... es anadirDispositivoRequest o crearNotificacionRequest. Funciona ya que hemos creado dos clases de mensajes:

Class EsquemasDatos.Monitorizacion.ElasticSearch.añadirDispositivoRequest Extends EsquemasDatos.Monitorizacion.ElasticSearch.BaseRequest

{

Property usuario As %String(MAXLEN = "");

Property iddispositivo As %String(MAXLEN = "");

Property idapp As %String(MAXLEN = "");

Property fecha As %String(MAXLEN = "");

Property hora As %String(MAXLEN = "");

Property resultado As %String(MAXLEN = "");

Property descripcionresultado As %String(MAXLEN = "");

}

---

Class EsquemasDatos.Monitorizacion.ElasticSearch.crearNotificacionRequest Extends EsquemasDatos.Monitorizacion.ElasticSearch.BaseRequest

{

Property idapp As %String(MAXLEN = "");

Property numexpediente As %String(MAXLEN = "");

Property idioma As %String(MAXLEN = "");

Property tiponotificacion As %String(MAXLEN = "");

Property fecha As %String(MAXLEN = "");

Property hora As %String(MAXLEN = "");

Property resultado As %String(MAXLEN = "");

Property descripcionresultado As %String(MAXLEN = "");

}

 

Mucha atención porque ambas heredan de BaseRequest. Repito, atención, que hereden de BaseRequest implican que tienen su índice, operación, url, puerto... con lo cual ElasticSearch entienda qué queremos hacer.

 

Además un detalle muy interesante y curioso es el siguiente. Si enviáramos de forma directa un anadirDispositivoRequest o un crearNotificacionRequest, veríamos que se almacena en ElasticSearch dentro del mismo índice “notificacionespush_añadirdispositivo” unos JSON con campos rarísimos como indice, url, puerto... Esto es como si en una tabla, guardamos una película, que contenga el mismo nombre de la tabla. O con otro ejemplo, es como guardar un vehículo, en un garaje, y que el vehículo tenga el nombre del garaje.

 

Para lograr que los campos heredados no se almacenen, nos creamos dos clases réplicas, que heredan de Ens.Request:

 

Class EsquemasDatos.Monitorizacion.ElasticSearch.añadirDispositivoBaseRequest Extends Ens.Request

{

Property usuario As %String(MAXLEN = "");

Property iddispositivo As %String(MAXLEN = "");

Property idapp As %String(MAXLEN = "");

Property fecha As %String(MAXLEN = "");

Property hora As %String(MAXLEN = "");

Property resultado As %String(MAXLEN = "");

Property descripcionresultado As %String(MAXLEN = "");

---

Class EsquemasDatos.Monitorizacion.ElasticSearch.crearNotificacionBaseRequest Extends Ens.Request

{

Property idapp As %String(MAXLEN = "");

Property numexpediente As %String(MAXLEN = "");

Property idioma As %String(MAXLEN = "");

Property tiponotificacion As %String(MAXLEN = "");

Property fecha As %String(MAXLEN = "");

Property hora As %String(MAXLEN = "");

Property resultado As %String(MAXLEN = "");

Property descripcionresultado As %String(MAXLEN = "");

4.3 Proceso

Para que el Proceso envíe nuestros mensajes a la Operación comentada, añadimos algo de código. Por ejemplo para añadirDispositivo:

Donde escribiriamos los datos relativos a la request y la response que necesitamos guardar en la monitorización, por ejemplo:

 

  set context.infoDispositivoElastic = ##class(EsquemasDatos.Monitorizacion.ElasticSearch.añadirDispositivoRequest).%New()

 

  set context.infoDispositivoElastic.accion = "POST"

  set context.infoDispositivoElastic.indice = "notificacionespush_anadirdispositivo"

 

  set context.infoDispositivoElastic.usuario = request.usuario

  set context.infoDispositivoElastic.iddispositivo = request.idDispositivo

  set context.infoDispositivoElastic.idapp = request.idApp

  set context.infoDispositivoElastic.fecha = $ZDATE($HOROLOG,4)

  set context.infoDispositivoElastic.hora = $ZTIME($PIECE($HOROLOG,",",2),1)

 

  if (response.resultado=""){

   set context.infoDispositivoElastic.resultado=response.error.codigo

   set context.infoDispositivoElastic.descripcionresultado=response.error.descripcion                

  }else{

   set context.infoDispositivoElastic.resultado=response.resultado

   set context.infoDispositivoElastic.descripcionresultado=response.informacion

  }

Y luego llamaríamos a la operación:

De esta forma veríamos en el Message Viewer, que el proceso envía a la Operación que lidia con ElasticSearch:

Y ElasticSearch nos responde en función de si pudo guardar la información:

Además, mediante POSTMAN hacemos un GET del índice notificacionespush_anadirdispositivo:

Y así nos daría el listado:

En resumen, hemos visto como implementar la interacción entre HealthShare y ElasticSearch.