Automatizando tareas con la REST API de HPE Nimble

En la entrada de hoy vamos a seguir indagando sobre automatización, utilizando Python para crear scripts que se comuniquen directamente con las REST APIs de HPE; concretamente con la de HPE Nimble Storage.

No vamos a crear nada excesivamente complejo, ya que este artículo está pensando para personas que no tengan mucha experiencia con APIs ni con el lenguaje Python, pero si os parece interesante haremos scripts mucho más potentes en artículos posteriores.

Por ahora vamos a ver cómo podemos empezar a automatizar nuestra infraestructura.

Entendiendo la REST API de Nimble

La forma en que nos vamos a comunicar con nuestra cabina Nimble para pedirle que ejecute ciertas tareas es a través de su API, que es una interfaz a través de la cual puedes lanzar peticiones al software de la cabina, y que además es REST, lo que significa que sigue una serie de reglas de manera que esas peticiones se hacen mediante HTTP(s) de forma análoga a cómo nuestro navegador se comunica con las páginas webs.

La mayoría de soluciones de Datacenter de la industria ofrecen algún tipo de REST API (y este es el caso de las soluciones de HPE), lo que significa que una vez aprendamos a crear scripts para, por ejemplo, HPE Nimble, con muy poco esfuerzo podremos hacer otros scripts semejantes para automatizar el provisionamiento de otras cabinas o incluso servidores o redes, porque todos siguen las mismas reglas básicas. Lo único que necesitamos saber es cómo funcionan esas peticiones HTTP específicamente en el caso de la cabina HPE Nimble.

Así pues vamos a echar un vistazo a la documentación, que podéis encontrar en la pestaña de Documentación de InfoSight o haciendo click en el siguiente enlace:

https://infosight.hpe.com/InfoSight/media/cms/active/public/pubs_REST_API_Reference_NOS_51x.whz/htr1449782645093.html

Nótese que esta documentación es para la versión 5.1.X, que es la de nuestra cabina. Si tienes otra versión en tu cabina visita la web de InfoSight.

Las acciones que se pueden llevar a cabo en la REST API de Nimble, que se suelen conocer como verbos o métodos, son las siguientes:

En el menú desplegable de la izquierda podemos navegar y explorar todo lo que se puede hacer con esta API. En el menú Object Sets se encuentran todos los “objetos” que gestiona la API, entendiendo como objeto cualquier elemento que gestiona la cabina como volúmenes, políticas de protección, exportaciones a hosts, etc. Sobre estos objetos llevamos a cabo estas acciones que acabamos de ver.

Es en esta sección donde vamos a pasar la mayor parte del tiempo.

Imaginemos que queremos automatizar la creación y borrado de volúmenes. Crear un volumen en Nimble es muy fácil, pero ¿qué pasa si queremos crear 100 volúmenes y luego borrarlos? Este podría ser el caso de alguna aplicación de desarrollo o, en mi caso, lo que hago para los laboratorios que utilizo en los cursos que impartimos en Pleiades. Resulta muy cómodo automatizar la creación y posterior destrucción de esos laboratorios de forma que haciendo un click todo el entorno se regenere.

¡Estos scripts nos ahorran decenas de horas de limpieza!

Este es sólo un ejemplo de los muchos que se nos podrían ocurrir.

Si seleccionamos el objeto volumes nos muestra las diferentes acciones que se pueden ejecutar con los mismos. En nuestro caso nos interesan create y delete. Lógicamente primero vamos a crear los volúmenes, así que hacemos click en create.

Lo primero que nos dice la documentación es que esta acción se corresponde con:

POST v1/volumes

Esto quiere decir que debemos enviar un verbo POST a la dirección v1/volumes, lo cual tiene sentido porque ya hemos visto que POST sirve para “crear”. La API de nimble escucha por el puerto 5392, así que si por ejemplo la dirección IP de nuestra Nimble es 10.1.0.10, debemos enviar un POST a:

https://10.1.0.10:5392/v1/volumes

Y crearemos un volumen. Pero sólo enviar un POST a esa dirección no le da suficiente información a la cabina para crear un volumen.

¿Qué más necesitamos? Pues sólo tenemos que seguir leyendo esta página para llegar a la sección:

Request

Que nos dice qué información debemos enviar junto a esa petición POST para que la cabina sepa qué características tiene el volumen a crear.

De toda la lista, las únicas dos que son obligatorias son las dos primeras: name y size. Size sólo si no estamos clonando un volumen. En nuestro caso vamos a clonar el volumen que contiene las VMs de los laboratorios, así que realmente sólo tenemos que darle el nombre. Si seguimos leyendo, vemos que en caso de ser un volumen creado a partir de clon se necesita indicar el id del snapshot a partir del cual generar el volumen, lo cual tiene sentido, porque la cabina debe saber exactamente en qué snapshot basarse para generar los clones.

Además, la documentación nos dice cómo se debe pasar esta información, es decir, en qué formato. En este caso, y como suele ser común en muchas APIs, será en formato JSON, del cual tenéis un ejemplo un poco más abajo en esta misma página:

En nuestro caso sólo vamos a necesitar el nombre, así que nuestro JSON para la petición de creación del volumen será:

request_data = {
    "data": {
        "name": name,
        "base_snap_id": snap_clone,
        "online": online
    }
}

Donde snap_clone es la variable que contiene el id del snapshot que queremos clonar. Esto lo veremos en detalle en unos instantes. “name” es una variable string que equivale al nombre que queramos darle a los clones y “online” un bool cuyo valor será True o False según queramos que estos clones se pongan online automáticamente a medida que se crean.

Ya tenemos todos los elementos necesarios. Ahora sólo hace falta crear un script que haga todo esto por nosotros. Aquí es donde entra en juego Python.

Creando un Script en Python

A partir de aquí voy a asumir que el lector tiene unas nociones básicas y algo de experiencia programando con Python.

Obviamente, necesitaremos descargar e instalar Python en el PC o servidor desde el cual vayamos a lanzar los scripts.

Una vez lo tengamos, usaremos nuestro IDE o editor de texto preferido para empezar a crear nuestro script.

Lo primero que necesitamos hacer en Python es definir los módulos, que son las librerías de Python, es decir, código externo que descargamos o viene incluido en la instalación y que nos permite realizar tareas sin tener que escribir nosotros mismos todo el código.

import json
import requests
from datetime import datetime

Estamos importando tres módulos: el primero para trabajar con el formato JSON más cómodamente, el segundo para realizar esa comunicación HTTP con la API y el tercero para poder obtener la fecha y hora actuales (este último no es necesario, pero veréis que lo uso en unos momentos).

Lo primero que necesitamos es autenticarnos en la API de la cabina como un usuario válido. Este proceso suele consistir en enviar unas credenciales a la API y recibir un token que nos identifica y que utilizaremos para cada una de las peticiones que hagamos a la API.

Vamos a definir una función que hace exactamente eso:

def authenticate(user, password, url):
    credentials = {
        'data': {
            'username': user,
            'password': password
        }
    }
    request_json = requests.post(url + 'tokens', data=json.dumps(credentials), verify=False).json()
    token = request_json['data']['session_token']
    return {'X-Auth-Token': token}

La función toma tres variables: el usuario, la contraseña y la dirección donde se encuentra la API, que en el caso de Nimble es su IP de gestión por el puerto 5392.

Además vamos a necesitar un JSON para la petición, exactamente igual que como hemos visto en los volúmenes. Así que creamos un JSON llamado credentials (puede tener el nombre que queramos) que contiene la key ‘data’ que a su vez contiene dos keys llamadas username y password, cada una conteniendo como valores el usuario y la contraseña que hemos definido como inputs de la función. La estructura de los datos que acompañan a la petición depende de cada API y se puede consultar en su documentación.

Ahora que tenemos el JSON construido, llegamos a la parte de iniciar la petición HTTP, para lo cual utilizamos el módulo requests que hemos importado al principio:

request_json = requests.post(url + 'tokens', data=json.dumps(credentials), verify=False).json()

El modulo requests requiere que pasemos la dirección de la API, los datos que necesite en formato JSON -de ahí que usemos json.dumps()- y finalmente recomiendo usar siempre verify=False si no tenéis certificados válidos en la cabina, o la función devolverá error de certificado y abortará.

Todo eso lo pasamos a formato JSON con .json() y lo guardamos en la variable request_json, que contiene la respuesta de la API en formato JSON. De esta respuesta extraemos únicamente el token que se encuentra en la key ‘session_token’.

Este token lo debemos enviar junto a cada petición que hagamos a la API, en forma de un header como el siguiente:

Así que para terminar devolvemos el header ya “construido” al finalizar la función.

token = request_json['data']['session_token']
return {'X-Auth-Token': token}

Ahora que ya estamos identificados podemos crear los volúmenes. Pero acabamos de ver en la documentación, debemos pasar la variable que se corresponde con el id que identifica al snapshot a partir del cual queremos crear los clones. Es otras palabras, necesitamos que el volumen que queremos clonar tenga al menos un snapshot creado y debemos usar la API para leer el id del mismo.

Una forma sencilla de conseguir esto es directamente usar este script para crear un snapshot justo antes de clonar, de forma que nos aseguramos al 100% de que el volumen tiene algún snap y además ya sabemos el id, porque lo estamos creando nosotros.

Si vamos a la documentación y en lugar de en volumes hacemos click en snapshots, veremos que la forma de crear snapshots es muy parecida a la de crear volúmenes, pero cambiando algún dato en el JSON de la petición. Realmente acabaréis viendo que casi todas las acciones de la API son casi idénticas, por lo que cuando hayáis aprendido a hacer 3 o 4 cosas ya prácticamente podéis hacer todo.

Os dejo que consultéis la documentación y crees una función igual que la que acabáis de ver para conseguir el token, pero en este caso para generar un snapshot en un volumen.

Queda tal que así:

def take_snap(vol_id, url, header):
    request_data = {
        "data": {
            "name": "Snap-clone"+str(datetime.now()).replace(' ', '-'),
            "vol_id": vol_id
        }
    }
    request_json = requests.post(URL + 'snapshots', headers=header, data=json.dumps(request_data), verify=False).json()
    return request_json['data']['id']

Para el nombre he decidido usar Snap-clone + la fecha actual, pero como Nimble no admite espacios y el formato de la fecha de datetime tiene un espacio entre el día y la hora, uso el método replace() para cambiar los espacios por guiones. Si esto os parece complicado utilizad cualquier otra forma de numerar los clones.

El único dato que no conoceréis es el vol_id, que es justamente el id que identifica al volumen sobre el cual queremos crear el snapshot.

Podemos ver todos los ids de todos los volúmenes de la cabina simplemente lanzando un GET a la dirección /v1/volumes:

request = requests.get(URL + 'volumes', headers=headers, verify=False)
print(request.json())

Ese print nos saca un output como el siguiente:

He marcado en amarillo el volumen que nos interesa clonar. Como este script siempre va a clonar el mismo volumen, simplemente copiamos y pegamos ese id en el JSON de la función clone_vol.

Podríamos crear un script más versátil haciendo que éste pregunte al usuario el nombre del volumen a clonar y que automáticamente busque su ID, de forma que podría servir para clonar cualquier volumen, no sólo el que acabamos de buscar “a mano”. Pero esto es un poco más complicado de lo que parece y requiere saber algo más de Python, así que de momento lo vamos a dejar así.

Resumiendo:

  • Hacemos un GET request para ver el id del volumen que queremos clonar.
  • Lanzamos un POST para “obligar” a la cabina a crear un snapshot de ese volumen
  • Guardamos el id de ese snapshot en alguna variable y la usamos para enviar otro POST a la cabina pidiéndole que genere un volumen a partir de ese snap.

Si lanzamos esta función y vamos a la interfaz de la cabina, sólo tenemos que seleccionar el volumen para ver sus snapshots creados:

Y vemos que efectivamente se ha creado un snapshot llamado snap-clone, del cual ya sabemos el ID porque lo estamos devolviendo como resultado de la propia función.

Por tanto ya tenemos todo lo necesario para, finalmente, clonar nuestros volúmenes:

def clone_vol(name, snap_id, url, header, online=False):
    request_data = {
        "data": {
            "name": name,
            "clone": True,
            "base_snap_id": snap_id,
            "online": online,
        }
    }
    request_json = requests.post(URL + 'volumes', headers=header, data=json.dumps(request_data), verify=False).json()
    return request_json

Con todas las funciones creadas, el script queda tal que así:

#VARIABLES
URL = 'https://nimble-td1.plds.es:5392/v1/'
vol_id= "06402f4678e96223d00000000000000000000000c8"
headers = authenticate("admin", "password", URL)
snap_id = take_snap(vol_id, URL, header)
clone_vol('Lab-Clone', snap_id, URL, True, header)

Nótese que he usado “admin” y “password” como ejemplos de credenciales. La URL de la cabina y el vol_id también serán diferentes en tu caso.

Así de sencillo. Guardamos al principio la dirección de la API de nuestra cabina y el id del volumen que queremos clonar. Luego nos identificamos y guardamos el header que utilizamos para identificarnos y que pasaremos a las siguientes funciones.

Finalmente sólo tenemos que lanzar la función que toma un snap, guardar su id en la variable snap_id y pasársela a la última función de todas, que es la que realmente realiza los clones. Llamamos al clon “Lab-Clone” o cualquier otro nombre que prefiráis.

Si por ejemplo quisiéramos hacer 5 clones, sólo tendríamos que hacer:

#VARIABLES
URL = 'https://nimble-td1.plds.es:5392/v1/'
vol_id= "06402f4678e96223d00000000000000000000000c8"
n_vol = 5
header = authenticate("admin", "password", URL)
snap_id = take_snap(vol_id, URL, header)
for i in range(0,n_vol):
    r = clone_vol('Lab-Clone-'+str(i), snap_id, URL, header, True)

Creamos un bucle for que llame a la función 5 veces y añadimos ese mismo número al final del nombre que pasamos a la función, de forma que los volúmenes se vayan creando con una numeración coherente:

¡Y ya está! Cuando queramos otra cantidad de volúmenes simplemente modificamos la variable n_vol, o si queremos un script interactivo hacemos que el script pida por consola el número de volúmenes a clonar. De momento no he querido complicar más el script, pero se puede complicar todo lo que queramos.

El script final queda así:

import json
import requests
from datetime import datetime
def authenticate(user, password, url):
    credentials = {
        'data': {
            'username': user,
            'password': password
        }
    }
    request_json = requests.post(url + 'tokens', data=json.dumps(credentials), verify=False).json()
    token = request_json['data']['session_token']
    return {'X-Auth-Token': token}
def take_snap(vol_id, url, header):
    request_data = {
        "data": {
            "name": "Snap-clone"+str(datetime.now()).replace(' ', '-'),
            "vol_id": vol_id
        }
    }
    request_json = requests.post(URL + 'snapshots', headers=header, data=json.dumps(request_data), verify=False).json()
    return request_json['data']['id']
def clone_vol(name, snap_id, url, header, online=False):
    request_data = {
        "data": {
            "name": name,
            "clone": True,
            "base_snap_id": snap_id,
            "online": online,
        }
    }
    request_json = requests.post(URL + 'volumes', headers=header, data=json.dumps(request_data), verify=False).json()
    return request_json
#VARIABLES
URL = 'https://nimble-td1.plds.es:5392/v1/'
vol_id= "06402f4678e96223d00000000000000000000000c8"
n_vol = 5
header = authenticate("admin", "password", URL)
snap_id = take_snap(vol_id, URL, header)
for i in range(0,n_vol):
    r = clone_vol('Lab-Clone-'+str(i), snap_id, URL, header, True)
    print(r)

He añadido un print de la respuesta que da la API al clonar, simplemente para tener algo que ver en el output de la consola y comprobar que las tareas se lanzan correctamente (r contendrá un código de respuesta cuyo significado puede leerse en la propia documentación de la API de Nimble).

Ahora faltaría crear otro script que borre todos estos volúmenes. De forma que tengamos un script para crear el entorno y otro para borrarlo. Esto os lo voy a dejar a vosotros y si os atascáis no tenéis más que preguntarme.

Comparte este artículo

Deja un comentario

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