Añadiendo extensiones a Nagios.

En este post vamos a ver cómo añadir un comando nuevo en Nagios en los CGIs. Esta extensión no es un plugin para comprobar servicios, el comando que vamos a implementar servirá para añadir un acknowledge a todos los servicios asociados a un host.

Partiremos de la base de que ya tenemos el código fuente de Nagios descargado, en nuestro caso va a ser las versiones 3.3.1  y 3.2.3, y que ya hemos configurado los Makefile del proyecto de acuerdo a nuestro sistema mediante el comando configure, sin llegar aún a hacer un make all.

El primer paso para crear un comando en Nagios será definir un número identificativo para nuestro nuevo comando dentro del fichero nagios/include/common.h. El valor es arbitrario, pero no puede estar repetido, y en este caso he preferido que se encuentre por encima del valor máximo, CMD_CUSTOM_COMMAND

#define CMD_ACK_ALL_SERVICES_FROM_HOST 1000

Lo siguiente será poner una entrada en la página de información extendida del host desde la cual lanzar el comando. En mi caso, esta entrada estará siempre disponible, independientemente del estado del host,  justo después del comando de acknowledge de problemas en el host.

Incluiremos esta entrada en el fichero nagios/cgi/extinfo.c  dentro de la función show_host_info, justo detrás de la comprobación de si el host está caido o no se puede llegar a el, y será toda la línea siguiente:

printf(«<tr CLASS=’command’><td><img src=’%s%s’ border=0 ALT=’Acknowledge Services Problems on host’ TITLE=’Acknowledge Services Problems on host’></td><td CLASS=’command’><a href=’%s?cmd_typ=%d&host=%s’>Acknowledge Services Problems on host</a></td></tr>\n»,url_images_path,ACKNOWLEDGEMENT_ICON,COMMAND_CGI,CMD_ACK_ALL_SERVICES_FROM_HOST,url_encode(host_name));

Con esto ya tenemos lista la entrada del comando. Así es como se verá una vez que lo compilemos:

Nos queda crear la entrada de datos para este comando y añadir la parte de funcionalidad de esta entrada.

Para la entrada de datos editaremos el fichero nagios/cgi/cmd.c, que se encarga de crear la pantalla de solicitud y validación de datos para la ejecución del comando. Lo primero será localizar el punto de la función request_command_data donde se muestra la cadena que va a dar titulo a la petición de datos. Podemos buscar la cadena común para todos los comandos, «You are requesting to «, y un par de lineas después tenemos el switch que cambia la línea en función del comando. Añadiremos dentro del switch la siguente entrada:

case CMD_ACK_ALL_SERVICES_FROM_HOST:
    printf("acknowledge all non OK services from a host");
    break;

Luego, buscamos en el fichero el siguiente switch, donde completamos el apartado de solicitud de datos con el formulario correspondiente. En este caso es casi una copia de la entrada de ACKNOWLEDGE_HOST_PROBLEM, serapando cada campo y eliminando los sticky, que en el caso anterior dependía de si el ack es por servicio o por host.

case CMD_ACK_ALL_SERVICES_FROM_HOST:
 printf("<tr><td CLASS='optBoxRequiredItem'>Host Name:</td><td><b>");
 printf("<INPUT TYPE='TEXT' NAME='host' VALUE='%s'>", escape_string(host_name));
 printf("</b></td></tr>\n");
 printf("<tr><td CLASS='optBoxItem'>Sticky Acknowledgement:</td><td><b>");
 printf("<INPUT TYPE='checkbox' NAME='sticky_ack' >");
 printf("</b></td></tr>\n");
 printf("<tr><td CLASS='optBoxItem'>Send Notification:</td><td><b>");
 printf("<INPUT TYPE='checkbox' NAME='send_notification' CHECKED>");
 printf("</b></td></tr>\n");
 printf("<tr><td CLASS='optBoxItem'>Persistent:</td><td><b>");
 printf("<INPUT TYPE='checkbox' NAME='persistent' >");
 printf("</b></td></tr>\n");
 printf("<tr><td CLASS='optBoxRequiredItem'>Author (Your Name):</td><td><b>");
 printf("<INPUT TYPE='TEXT' NAME='com_author' VALUE='%s' %s>", escape_string(comment_author), (lock_author_names == TRUE) ? "READONLY DISABLED" : "");
 printf("</b></td></tr>\n");
 printf("<tr><td CLASS='optBoxRequiredItem'>Comment:</td><td><b>");
 printf("<INPUT TYPE='TEXT' NAME='com_data' VALUE='%s' SIZE=40>", escape_string(comment_data));
 printf("</b></td></tr>\n");
 break;

El último punto en la pantalla de recogida de datos del cgi es indicar una cadena con la descripción de qué es lo que va a ejecutarse mediante este comando. Para ello, ampliamos la función show_command_help con esta descripción de ayuda, de nuevo en un switch justo al inicio de la función.

case CMD_ACK_ALL_SERVICES_FROM_HOST:
 printf("This command is used to acknowledge all non OK services on a host.\n");
 break;

Con esto tenemos la pantalla con los datos necesarios para ejecutar el comando.

Una vez introducidos los datos correctamente en este formulario, los vuelve a recibir el CGI para  comprobar que se han introducido todos los datos necesarios y verificar que podemos ejecutar comandos sobre el host antes de proceder a ejecutarlo. Esta funcionalidad se encuentra en la función commit_command_data. La ampliaremos de nuevo en un switch agregando nuestro comando y verificando los datos marcados como necesarios.

case CMD_ACK_ALL_SERVICES_FROM_HOST:
  if(!strcmp(comment_author,"")){
    if(!error_string)
      error_string=strdup("Author was not entered");
  }
  if(!strcmp(comment_data,"")){
    if(!error_string)
      error_string=strdup("Comment was not entered");
  }
  clean_comment_data(comment_author);
  clean_comment_data(comment_data);
  temp_host=find_host(host_name);
  if(is_authorized_for_host_commands(temp_host,&current_authdata)==TRUE)
    authorized=TRUE;
  break;

El último paso en el front-end antes de la ejecución del comando es generar la cadena completa de este comando, que se escribirá en un pipe de donde lee nagios para ejecutar realmente el comando en el back-end. Esta función es commit_command y se encuentra en el mismo fichero, nagios/cgi/cmd.c. Incluiremos en esta función, una vez más en un switch, la entrada de nuestro comando.  Podemos encontrar este switch mediante la cadena «/* decide how to form the command line… */». En el caso especial de este comando, podemos copiar directamente la entrada del comando CMD_ACKNOWLEDGE_HOST_PROBLEM, ya que los parámetros que vamos a enviar y recibir son los mismos:

case CMD_ACK_ALL_SERVICES_FROM_HOST:
 result = cmd_submitf(cmd,"%s;%d;%d;%d;%s;%s",host_name,(sticky_ack==TRUE)?ACKNOWLEDGEMENT_STICKY:ACKNOWLEDGEMENT_NORMAL,send_notification,persistent_comment,comment_author,comment_data);
 break;

A partir de este punto, dejamos los CGIs para pasar a implementar la lógica real del comando, el back-end, sobre los datos recibidos y procesados en el front-end.

El primer paso de este back-end es  una zona críptica que no entiendo bien su funcionalidad, o si va en el back-end o no, pero sin la cual no nos va a funcionar la nueva entrada. Necesitamos añadir en el fichero nagios/cgi/extcmd_list.c  el nuevo comando que estamos definiendo en la estructura in_core_commands mediante esta línea

CMD_DEF(ACK_ALL_SERVICES_FROM_HOST,0,NULL),

Esta lista de comandos se va a usar posteriormente en la función process_external_command1 del fichero nagios/base/commands.c para identificar el comando solicitado en el cgi y procesarlo. En esta función añadiremos este código en el bloque de else ifs. En el caso de esta extensión he preferido ponerla en el bloque de comandos relacionados con el host, que podemos encontrar buscando la cadena «/**** HOST-RELATED COMMANDS ****/»· En este bloque añadiremos estas dos líneas de código

else if(!strcmp(command_id, "ACK_ALL_SERVICES_FROM_HOST"))
       command_type = CMD_ACK_ALL_SERVICES_FROM_HOST;

Con esto ya tenemos el identificador numérico del comando a lanzar. En este mismo fichero, tenemos ya la funcionalidad que vamos a ejecutar una vez identificado el comando. La función que va a conseguir esto es la process_external_command2. En esta función tenemos un apartado donde se recogen los dos comandos existentes de reconocimiento de alertas, a nivel de host y de servicio. Incluiremos el nuevo comando entre los dos existentes, ya que nuestra funcionalidad está relacionada con este punto. Lo podemos en este punto como podríamos crear una función específica, pero en este caso reutilizamos la función existente porque nos va a proporcionar un tokenizer que nos calcula los datos necesarios a partir de la información del comando para ejecutar la funcionalidad que necesitamos. El código queda de esta manera:

    case CMD_ACKNOWLEDGE_HOST_PROBLEM:
    case CMD_ACK_ALL_SERVICES_FROM_HOST:
    case CMD_ACKNOWLEDGE_SVC_PROBLEM:
         cmd_acknowledge_problem(cmd, args);
         break;

Esta función cmd_acknowledge_problem es la que se encarga de reconocer alarmas tanto de hosts como de servicios. Ampliaremos esta función para incorporar nuestra funcionalidad. A esta función le pasamos el comando solicitado y los parámetros con los que debe ejecutar este comando, y se encarga de recuperar en cadenas separadas los datos de estos parámetros y comprobar la validez de los datos. Una vez comprobados, ejecuta una llamada a la función específica del comando. Aquí es donde colocaremos la llamada a nuestro tratamiento del ack de servicios. El código original trata los comandos mediante un if, ya que sólo hay dos:

if(cmd==CMD_ACKNOWLEDGE_HOST_PROBLEM)
    acknowledge_host_problem(temp_host,ack_author,ack_data,type,notify,persistent);
else
    acknowledge_service_problem(temp_service,ack_author,ack_data,type,notify,persistent);

Para nuestro caso, dividimos el if y lo ampliamos para tratar todos los casos.

if (cmd == CMD_ACKNOWLEDGE_HOST_PROBLEM)
    acknowledge_host_problem(temp_host, ack_author, ack_data, type, notify, persistent);
if (cmd== CMD_ACK_ALL_SERVICES_FROM_HOST)
    acknowledge_non_ok_services_from_host(temp_host,ack_author,ack_data,type,notify,persistent);
if (cmd== CMD_ACKNOWLEDGE_SVC_PROBLEM)
    acknowledge_service_problem(temp_service, ack_author, ack_data, type, notify, persistent);

La función que hemos puesto en este fichero aún no existe, así que tenemos dos tareas pendientes en este punto. La primera es crear la firma de la función que vamos a definir en el fichero nagios/include/nagios.h. Esta firma va a ser la siguiente:

void acknowledge_non_ok_services_from_host(host *, char *, char *, int, int, int);

Se puede copiar directamente la de acknowledge_host_problem, la firma va a ser exacta, sólo habría que copiar la línea y cambiar el nombre de la función.

Una vez creada la firma de la función, pasamos a implementar esta función en el fichero nagios/base/command.c. Esta función va a recibir como parámetros los datos completos del host, el usuario que ha lanzado el comando, una cadena con el mensaje que se va a incluir en el reconocimiento de las alertas, si este reconocimiento va a ser sticky, es decir, si se va a quedar el servicio como reconocido hasta que haya un cambio de estado, sea el que sea, o sólo hasta que el servicio vuelva a estar en estado Ok, si necesitamos notificar el reconocimiento y por último, si el comentario asociado al reconocimiento se va a eliminar o no al eliminar este reconocimiento.

void acknowledge_non_ok_services_from_host(host *hst, char *ack_author, char *ack_data, int type, int notify, int persistent) {

Para recuperar todos los servicios de un host, utilizamos la variable service_list, una lista de todos los servicios definidos en Nagios. Recorreremos esta lista, comprobando para cada valor encontrado si el nombre de host coincide con el que le pasamos al comando. En caso de que sea así, llamamos a la función acknowledge_service_problem con los datos del servicio encontrado y los parámetros que ya tenemos de nuestra función. Este es el código final de la función:

  service *serv = service_list;
  while (serv != NULL) {
    if (strcmp(hst->name,serv->host_name)==0) {
      acknowledge_service_problem(serv,ack_author,ack_data,type,notify,persistent);
    }
    serv = serv->next;
  }
}

El último punto es comprobar que todo funciona. Compilamos nagios con make all, instalamos siguiendo el procedimiento normal y comprobamos que en la información extendida de un host cualquiera (preferiblemente uno con problemas en los servicios) aparece la entrada para reconocer todos los problemas de los servicios. Ejecutamos este comando y verificamos que la funcionalidad que hemos implementado es correcta.

 

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *