10 Motor Webservice

El motor Webservice obtiene los datos llamando a servicios web por HTTP. Admite dos tipos de servicio, REST y SOAP, que pueden convivir en un mismo fichero de consultas y un mismo monitor.

10.1 REST y SOAP#

Los dos tipos comparten la conexión (URL base, TLS, autenticación) y el modo de extraer los valores: en ambos casos la respuesta se convierte en un documento JSON y cada ítem indica una ruta dentro de él. Se diferencian en cómo se construye la petición y en el formato original de la respuesta:

REST (type="REST") SOAP (type="SOAP")
Petición GET o POST a una URL. POST de un sobre XML SOAP con un método y sus argumentos.
Parámetros de la clave Se pueden enviar en la URL (urlparameter) o usar para elegir un elemento de la respuesta (path con <base>). No se usan: los argumentos del método son fijos en el XML.
Respuesta JSON. XML SOAP, que Databbix convierte en JSON (sección 10.4.1).
status_code en <transform> Código HTTP de la respuesta. Siempre 0; los errores llegan como un Fault en el propio JSON.
Descubrimientos Sí. No.
Autenticación HTTPAuth, RESTToken, JDE. Oasis (WS-Security) o HTTPAuth.

10.2 Configuración: sección [Webservice]#

Parámetro Descripción
BaseURL Obligatorio. URL base. Las URL de las consultas son relativas a ella.
UserName, Password Credenciales.
Credentials Credencial adicional con la forma clave=valor (entorno, rol, método…), según el motor de autenticación. Se puede repetir.
Authenticator Nombre de un método de autenticación definido en los XML o, si no existe, de un motor de autenticación: HTTPAuth, JDE u Oasis (sección 10.6).
Timeout Tiempo máximo de espera en milisegundos (defecto: 20000).
AuthPersistFile Fichero donde se guarda el estado de la autenticación (por ejemplo, el token) entre reinicios.
VerifyCertificate Verificar el certificado del servidor (defecto: yes).
CertCAFile Fichero de CA que se usa en lugar del almacén del sistema.
ClientCertificate Certificado de cliente (PEM) para autenticación TLS.
ClientPrivKey Clave privada del certificado de cliente. Obligatoria si se usa ClientCertificate.
ClientPrivKeyPass Contraseña de la clave privada, si está cifrada.
DisableContent Encoding Desactiva Content-Encoding, para servidores antiguos (defecto: no).
[General]
Name = JDEPROD
Engine = Webservice
QueryFile = /etc/databbix/query-jde.xml

[Zabbix]
Server = zabbix.empresa.com
Port = 10051

[Data]
Module = JDE

[Webservice]
BaseURL = http://10.1.1.45:9888/restservice
UserName = monitor
Password = secreto
Authenticator = JDE

10.3 Consultas REST#

<Queries engine="Webservice">

<query name="JDEEntStatus" module="JDE" type="REST" transport="GET" encode="Json">
    <url>entserverinstanceinfo</url>
    <parameter urlparameter="instanceName" order="1" type="STRING"/>
    <item name="ts.jde.enterprise.kerneljobs"
          path="entServerInstanceInfoDetail:[0]:kernelJobs" type="INT"/>
</query>

</Queries>
Elemento / atributo Descripción
type="REST" Atributo de <query>. Obligatorio.
transport Obligatorio. GET o POST.
encode Formato de la respuesta. Solo se admite Json.
<url> Obligatorio. Ruta relativa a BaseURL.
<parameter urlparameter> Envía el parámetro de la clave en la URL, como ?nombre=valor (sección 10.3.2).
<base path> Array de la respuesta que se recorre para buscar el elemento que indica la clave (sección 10.3.3).
<parameter path> Con <base>: campo de cada elemento que se compara con el parámetro de la clave.
<item path type> Ruta del valor dentro del JSON (sección 10.3.1) y tipo: INT o STRING. Ambos obligatorios, salvo en ítems built_in.
<transform> Función que procesa la respuesta antes de extraer los valores (sección 12.6).

Sin <transform>, una respuesta con código HTTP distinto de 200 es un error y todos los ítems de la consulta pasan a no soportados.

10.3.1 Rutas JSON#

Una ruta indica cómo llegar a un valor desde la raíz del documento JSON. Se compone de pasos separados por dos puntos (:):

Paso Significado
nombre Miembro nombre de un objeto.
[n] Elemento n de un array, empezando en 0.
[] Todos los elementos de un array. Solo en <base> y en el <item> de un descubrimiento.
* Todos los miembros de un objeto. Solo en <base> y en el <item> de un descubrimiento.

La ruta de un ítem tiene que terminar en un valor simple (texto, número, booleano o null). Dado este documento:

{
  "server":   { "name": "ent01", "state": "RUNNING", "up": true },
  "jobs": [
    { "id": 1, "name": "backup", "status": "OK",     "duration": 120 },
    { "id": 2, "name": "purge",  "status": "FAILED", "duration": 30 }
  ],
  "counters": { "cpu": 12.5, "memory": 2048 },
  "note": null
}

Rutas y valores obtenidos

path type Valor
server:name STRING ent01
server:up INT 1
server:up STRING true
jobs:[1]:status STRING FAILED
jobs:[0]:duration INT 120
counters:cpu STRING 12.5
note STRING (cadena vacía)
server:host STRING no soportado: el miembro no existe
jobs:[5]:status STRING no soportado: el array tiene 2 elementos
server STRING error: no es un valor simple

En los casos no soportado, si el ítem tiene atributo default, se envía ese valor. Con type="INT", los booleanos se convierten en 1 y 0.

Nota.  Los nombres de miembro no pueden contener dos puntos, porque es el separador de pasos. Si el servicio los usa, reestructure la respuesta con una transformación (sección 12.6).

10.3.2 Parámetros en la URL#

Cada <parameter> con urlparameter añade a la URL un parámetro con ese nombre y el valor de la clave. La consulta se ejecuta una vez por cada clave distinta:

<query name="JDEInstanceState" module="JDE" type="REST" transport="GET" encode="Json">
    <url>instancestate</url>
    <parameter urlparameter="instanceName" order="1" type="STRING"/>
    <item name="ts.jde.instance.state" path="instanceState" type="STRING"/>
</query>

Llamadas y valores obtenidos

Clave URL llamada Valor
ts.jde.instance.state[ENT1] …/instancestate?instanceName=ENT1 RUNNING
ts.jde.instance.state[WEB1] …/instancestate?instanceName=WEB1 STOPPED

En cada llamada el servicio responde, por ejemplo, {"instanceState":"RUNNING"}, y la ruta instanceState extrae el valor.

10.3.3 Filtro sobre la respuesta#

Cuando el servicio devuelve de una vez la lista de todos los objetos, es más eficiente hacer una sola llamada y elegir el elemento en la respuesta, igual que una consulta FILTER de base de datos. Para ello:

  • <base path="…"> indica el array que se recorre (su ruta termina en []);

  • cada <parameter path="…"> indica un campo de cada elemento que se compara con un parámetro de la clave;

  • las rutas de los ítems son relativas al elemento elegido.

<query name="LocalJobs" module="API" type="REST" transport="GET" encode="Json">
    <url>status</url>
    <base path="jobs:[]"/>
    <parameter path="name" order="1" type="STRING"/>
    <item name="local.api.job.status"   path="status"   type="STRING" default="UNKNOWN"/>
    <item name="local.api.job.duration" path="duration" type="INT"/>
</query>

Con el documento de la sección anterior como respuesta:

Valores obtenidos

Clave Elemento elegido Valor
local.api.job.status[backup] jobs[0] OK
local.api.job.duration[backup] jobs[0] 120
local.api.job.status[purge] jobs[1] FAILED
local.api.job.status[reindex] ninguno UNKNOWN (default)
local.api.job.duration[reindex] ninguno no soportado

Las cinco claves se resuelven con una sola llamada al servicio. Con type="INT" en el <parameter>, la comparación es numérica (por ejemplo, parameter path="id" y la clave …[2]).

10.3.4 Descubrimientos REST#

La ruta del <item> de un descubrimiento recorre un array ([]) o los miembros de un objeto (*). Cada elemento recorrido es una entidad descubierta, y las rutas de los <field> son relativas a él:

<query name="LocalJobDiscovery" module="API" type="REST"
       transport="GET" encode="Json" discovery="yes">
    <url>status</url>
    <item name="local.api.job.discovery" path="jobs:[]"/>
    <field name="JOB"    path="name"/>
    <field name="JOBID"  path="id"/>
</query>

Con el documento de la sección 10.3.1, el valor del ítem es:

{"data":[ {"{#JOB}":"backup", "{#JOBID}":"1"},
          {"{#JOB}":"purge",  "{#JOBID}":"2"} ]}

Recorridos anidados#

Una ruta puede tener varios pasos de recorrido. Con esta respuesta:

{ "clusters": [
    { "name": "A", "nodes": [ {"host": "a1"}, {"host": "a2"} ] },
    { "name": "B", "nodes": [ {"host": "b1"} ] }
] }

Ruta del ítem y entidades descubiertas

path del item Entidades (y field path="…" disponibles)
clusters:[] 2: los clusters A y B (name, nodes:[0]:host…)
clusters:[]:nodes:[] 3: los nodos a1, a2 y b1 (host)

Los <field> solo ven el elemento recorrido: en el segundo caso no pueden obtener el nombre del cluster, y al recorrer un objeto con * no pueden obtener el nombre de cada miembro. Cuando se necesita, se reestructura la respuesta con una transformación. Por ejemplo, para descubrir los nodos junto con su cluster:

<query name="LocalNodeDiscovery" module="API" type="REST"
       transport="GET" encode="Json" discovery="yes">
    <url>clusters</url>
    <script language="Javascript"><![CDATA[
        function aplanar(status_code, result) {
            if (status_code != 200) throw "ZBX_NOTSUPPORTED";
            var res = { nodes: [] };
            for (var i = 0; i < result.clusters.length; i++) {
                var c = result.clusters[i];
                for (var j = 0; j < c.nodes.length; j++)
                    res.nodes.push({ cluster: c.name, host: c.nodes[j].host });
            }
            return res;
        }
        ]]>
    </script>
    <transform language="Javascript" function="aplanar"/>
    <item name="local.api.node.discovery" path="nodes:[]"/>
    <field name="CLUSTER" path="cluster"/>
    <field name="HOST"    path="host"/>
</query>
{"data":[ {"{#CLUSTER}":"A","{#HOST}":"a1"}, {"{#CLUSTER}":"A","{#HOST}":"a2"},
          {"{#CLUSTER}":"B","{#HOST}":"b1"} ]}

10.4 Consultas SOAP#

Una consulta SOAP llama a un método de un servicio SOAP con argumentos fijos y extrae los valores de la respuesta:

<query name="GoliveWS" module="golive" type="SOAP" transport="POST">
    <url>FoundationEnvironmentManager</url>
    <method uri="http://oracle.e1.bssv.JP000000/" name="getCurrentUserProfile"
            action="">
        <argument name="environment" type="string" value="JPD920"/>
    </method>
    <item name="golive.ws.addressnumber" path="addressNumber" type="INT" default="0"/>
    <item name="golive.ws.language"      path="userProfile:language" type="STRING"/>
    <item name="golive.ws.exectime"      built_in="exec_time"/>
</query>
Elemento / atributo Descripción
type="SOAP" Atributo de <query>. Obligatorio.
transport="POST" Obligatorio.
<url> Obligatorio. Ruta del servicio, relativa a BaseURL.
<method> Método que se invoca: uri (espacio de nombres del método), name (nombre del método) y action (valor de la cabecera HTTP SOAPAction, puede estar vacío).
<argument> Argumento del método: name, type (string o int) y value. Puede haber varios. Los valores son fijos.
<item path type> Igual que en REST, sobre el JSON obtenido de la respuesta.
<transform> Función que procesa el JSON de la respuesta (sección 10.4.2).

Con esta definición, Databbix envía a BaseURL/FoundationEnvironmentManager un POST con la cabecera SOAPAction: "" y un cuerpo similar a este:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" ...>
  <SOAP-ENV:Body>
    <ns1:getCurrentUserProfile xmlns:ns1="http://oracle.e1.bssv.JP000000/">
      <environment xsi:type="xsd:string">JPD920</environment>
    </ns1:getCurrentUserProfile>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Si el monitor usa el motor de autenticación Oasis, se añade además una cabecera SOAP WS-Security con el usuario y la contraseña del .conf.

10.4.1 Conversión de la respuesta SOAP a JSON#

Databbix convierte en JSON el elemento de respuesta del método, es decir, el primer hijo de <Body>. Ese elemento es la raíz del JSON: su nombre no forma parte de las rutas. La conversión sigue estas reglas:

  1. Un elemento con elementos hijos se convierte en un objeto. Cada hijo es un miembro con su nombre local, sin el prefijo del espacio de nombres.

  2. Si un elemento tiene varios hijos con el mismo nombre, ese miembro es un array con todos sus valores, en orden.

  3. Un elemento sin hijos se convierte en un texto, aunque su contenido sea un número o un booleano, y aunque lleve un atributo xsi:type.

  4. Los atributos XML se ignoran.

  5. El orden de los miembros del objeto puede no coincidir con el del XML. Esto no afecta a las rutas.

Como todos los valores simples son texto, en las funciones de transformación hay que convertirlos para operar con ellos (Number(r.sessions) en JavaScript, int(r[’sessions’]) en Python). En los ítems no hace falta: type="INT" envía el texto tal cual y convierte true/false en 1/0.

Ejemplo. Respuesta del servicio:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <soap:Body>
    <ns2:getCurrentUserProfileResponse xmlns:ns2="http://oracle.e1.bssv.JP000000/">
      <addressNumber>4242</addressNumber>
      <active xsi:type="xsd:boolean">true</active>
      <sessions xsi:type="xsd:int">3</sessions>
      <userProfile>
        <language>S</language>
        <dateFormat>DMY</dateFormat>
      </userProfile>
      <role>ADMIN</role>
      <role>USER</role>
    </ns2:getCurrentUserProfileResponse>
  </soap:Body>
</soap:Envelope>

JSON que se obtiene (y que recibe la función de <transform>, si la hay):

{
  "addressNumber": "4242",
  "active": "true",
  "sessions": "3",
  "userProfile": { "language": "S", "dateFormat": "DMY" },
  "role": [ "ADMIN", "USER" ]
}

Rutas y valores obtenidos

path type Valor
addressNumber INT 4242
active INT 1
sessions INT 3
userProfile:language STRING S
role:[0] STRING ADMIN
role:[1] STRING USER

Elementos repetidos.  Un elemento solo se convierte en array si aparece más de una vez. Si el usuario del ejemplo tuviera un único <role>, el JSON sería "role": "ADMIN" y la ruta role:[0] fallaría. Cuando un elemento puede aparecer una o varias veces, normalice la respuesta con una transformación (sección 10.4.2).

Errores SOAP#

Si el servicio responde con un Fault, el elemento convertido es el propio <Fault>:

<soap:Body>
  <soap:Fault>
    <faultcode>soap:Server</faultcode>
    <faultstring>User not authorized</faultstring>
  </soap:Fault>
</soap:Body>
{ "faultcode": "soap:Server", "faultstring": "User not authorized" }

Sin transformación, las rutas de los ítems no existen en este JSON y los ítems pasan a no soportados (o toman su valor default). Con una transformación se puede detectar el error y decidir qué hacer.

10.4.2 Transformación de respuestas SOAP#

La función de <transform> recibe (status_code, result), igual que en REST, pero status_code vale siempre 0 y result es el JSON obtenido con las reglas anteriores. Este ejemplo detecta los Fault, normaliza role para que sea siempre un array y calcula valores derivados:

<query name="LocalUserProfile" module="golive" type="SOAP" transport="POST">
    <url>FoundationEnvironmentManager</url>
    <method uri="http://oracle.e1.bssv.JP000000/" name="getCurrentUserProfile" action=""/>
    <script language="Javascript"><![CDATA[
        function perfil(status_code, r) {
            if (r.faultcode !== undefined) {
                log_info("SOAP Fault:", r.faultstring);
                throw "ZBX_NOTSUPPORTED";
            }
            var roles = r.role;
            if (roles === undefined)
                roles = [];
            else if (!(roles instanceof Array))
                roles = [ roles ];                 // un solo <role>
            return {
                address:  Number(r.addressNumber),
                nroles:   roles.length,
                admin:    roles.indexOf("ADMIN") >= 0,
                language: r.userProfile ? r.userProfile.language : ""
            };
        }
        ]]>
    </script>
    <transform language="Javascript" function="perfil"/>
    <item name="local.golive.address"  path="address"  type="INT"/>
    <item name="local.golive.nroles"   path="nroles"   type="INT"/>
    <item name="local.golive.admin"    path="admin"    type="INT"/>
    <item name="local.golive.language" path="language" type="STRING"/>
</query>

Valores obtenidos con la respuesta del ejemplo anterior

Clave Valor
local.golive.address 4242
local.golive.nroles 2
local.golive.admin 1
local.golive.language S

La misma función en Python:

def perfil(status_code, r):
    if 'faultcode' in r:
        log_info("SOAP Fault:", r.get('faultstring'))
        raise Exception("ZBX_NOTSUPPORTED")
    roles = r.get('role', [])
    if not isinstance(roles, list):
        roles = [roles]
    return {
        'address':  r.get('addressNumber'),
        'nroles':   len(roles),
        'admin':    'ADMIN' in roles,
        'language': r.get('userProfile', {}).get('language', '')
    }

10.5 Datos de ejecución#

El motor Webservice ofrece estos datos de ejecución:

Nombre Valor
DATABBIX.PROGRAM_NAME databbix, o queryexplorer en esa utilidad.
CONN.TYPE Webservice.

Como las consultas de este motor no tienen condiciones <when>, estos datos solo se consultan con getRuntimeValue en queryexplorer.

10.6 Autenticación#

10.6.1 Motores y métodos de autenticación#

Hay que distinguir dos cosas:

Motor de autenticación.

Está programado en C++ dentro de Databbix e implementa un mecanismo completo: cómo se envían las credenciales, cómo se obtiene y renueva un token, etc. Hay cuatro: HTTPAuth, RESTToken, JDE y Oasis.

Método de autenticación.

Se define en un fichero de consultas con <authentication>. Toma un motor (atributo type) y lo personaliza para un servicio concreto con funciones en JavaScript o Python y otros elementos (URL de login, cabeceras, dónde va el token…). Se identifica por su atributo name.

Motor Servicios Uso directo Personalización con un método
HTTPAuth REST y SOAP Sí: Credentials = method=BASIC… Opcional: <auth-generate>, <header>, <verify-call>.
RESTToken REST No (error RESTToken cannot be directly used) Obligatoria: <login>, <token>, <verify-call>.
JDE REST Sí No admite: un método de este tipo solo le da otro nombre.
Oasis SOAP Sí No admite: un método de este tipo solo le da otro nombre.

10.6.2 Cómo se elige la autenticación#

El parámetro Webservice.Authenticator del .conf contiene un nombre. Databbix lo busca en este orden:

  1. Un método de autenticación en los ficheros de consultas del monitor: un <authentication> cuyo name coincida. Si lo encuentra, usa el motor que indica su type, personalizado con las funciones del método.

  2. Un motor de autenticación con ese nombre. Si lo encuentra, lo usa directamente, sin personalizar, con las credenciales del .conf. (Se admite también el nombre con el sufijo Authenticator, como HTTPAuthAuthenticator.)

  3. Si no encuentra ninguno de los dos, el monitor no puede conectar y lo registra en su log.

Nota.  Como los métodos se buscan primero, un método cuyo name coincida con el de un motor (por ejemplo, name="HTTPAuth") oculta ese motor. Use nombres propios para los métodos (AISAuth, ApiKeyAuth…).

10.6.3 Ejemplos#

Motor usado directamente: no hay ningún método llamado JDE en los XML, así que se usa el motor JDE tal cual.

[Webservice]
BaseURL = http://jdesm.empresa.com:8999/manage/mgmtrestservice/
Authenticator = JDE
UserName = jde_admin
Password = secreto

Método de autenticación: el XML define AISAuth, que personaliza el motor RESTToken, y el .conf lo usa por su nombre.

<Queries engine="Webservice">
    <authentication type="RESTToken" name="AISAuth">
        <script language="Javascript"> ... </script>
        <login type="JsonPost"> ... </login>
        <token type="Header" name="jde-AIS-Auth"/>
        <verify-call language="Javascript" function="verifyCall"/>
    </authentication>
    <query ...> ... </query>
</Queries>
[Webservice]
Authenticator = AISAuth

La programación de las funciones de autenticación se describe en la sección 12.7.