Consumiendo servicios del API Red Hat Cloudforms con PHP

redhat-logo

Para ser sincero no he visto hasta la fecha ejemplos de como podemos consumir con PHP los servicios que ofrece la API de Red Hat Cloudforms en Internet (ni en inglés), por lo que me anime a postear esta entrada detallando mi reciente experiencia con el servicio de Cloudforms de Red Hat.

La versión que estoy trabajando será la de la API de Red Hat Cloudforms 3.0, sobre una plataforma de Red Hat Enterprise Virtualization 3.2. Lo primero por donde empezaré será por comentarles algunos conceptos iniciales y el objetivo de esta entrada.

¿Qué es Red Hat Enterprise Virtualization?

 

La empresa Red Hat, tiene como parte de su portafolio de productos a Red Hat Enterprise Virtualization (RHEV), software que se ha convertido líder en el rubro de la virtualización segura de servidores y escritorio empresarial. RHEV forma parte de una Suite Empresarial llamada Red Hat Cloud Infrastructure (RHCI), un servicio que le permite crear clouds IaaS privadas a su medida. La Suite Empresarial de productos de Red Hat están orientados a poder ofrecer servicios de Cloud Computing convirtiendonos como Proveedores de Infraestructura como Servicio (IaaS).

RHCI2

 

¿Qué es Red Hat Cloudforms?

 

Red Hat Cloudforms (RHC) es el software en plataforma web que proporciona una interface de administración, donde los usuarios finales se provisionan sus propios recursos de cómputo (Procesador, Memoria RAM, Almacenamiento, Máquinas Virtuales) mediante este “portal de autoservicio”, ya sea dentro del Centro de Datos con RHEV,  o desde un servicio de Nube Pública como Amazon (Amazon Web Services AWS) y aquellos basado en VMWare vSphere. También proporciona herramientas para configurar  las instancias de OpenStack.

Este software de autoservicio de recursos de cómputo en la Nube, tiene una API de la cual podemos hacer uso para conectarnos a la plataforma de Virtualización y ejecutar múltiples operaciones, como por ejemplo: listar todas las máquinas virtuales, hosts, datastores, encender, apagar, suspender, crear y eliminar máquinas virtuales.

CloudForms-2.0

 

Creando nuestro Cliente PHP.

Primero, vamos a crear un script llamado ClientAPICloudforms.php, el cual se encargará de realizar la conexión con el API de Cloudforms, con los parámetros de conexión que le enviemos al instanciarlo.

La API de Cloudforms trabaja con SOAP, por lo que utilizaré la librería NuSOAP PHP para consumir los servicios mediante SOAP. La librería la puedes bajar aquí.

También se crearán los métodos de conexión y algunos para la validación del protocolo y WSDL.

<?php 
/**
 * Client : API Cloudforms.
 * @author Gonzalo Chacaltana Buleje <gchacaltanab@outlook.com>;
 */
require_once __DIR__ . '/nusoap/nusoap.php';
 
class ClientAPICloudforms {
    private $username;
    private $password;
    private $urlWsdl;
    private $connect;
    public $error;
    public $protocol;
 
    public function getUsername() {
        return $this->username;
    }
 
    public function setUsername($username) {
        $this->username = $username;
    }
 
    public function getPassword() {
        return $this->password;
    }
 
    public function setPassword($password) {
        $this->password = $password;
    }
 
    public function getUrlWsdl() {
        return $this->urlWsdl;
    }
 
    public function setUrlWsdl($urlWsdl) {
        $this->urlWsdl = $urlWsdl;
    }
 
    public function getConnect() {
        return $this->connect;
    }
 
    public function setConnect($connect) {
        $this->connect = $connect;
    }
 
    /**
     * constructor method
     * @param string $urlWsdl, URL of WSDL.
     * @param string $username, user to login wsdl.
     * @param string $password, password to login wsdl.
     * @return boolean
     */
    public function __construct($urlWsdl, $username = "", $password = "") {
        if (!$this->checkUrl($urlWsdl)) {
            return false;
        }
        if (!$this->getProtocolWsdl($urlWsdl)) {
            return false;
        }
        if ($this->protocol == "https" && (empty($username) || empty($password))) {
            $this->error = "You must enter the authentication data.";
            return false;
        }
        $this->setUrlWsdl($urlWsdl);
        $this->setUsername($username);
        $this->setPassword($password);
        return true;
    }
 
    /**
     * validate a URL
     * @param string $url, URL
     * @return boolean
     */
    private function checkUrl($url) {
        $pattern = "#((http|https)://(\S*?\.\S*?))(\s|\;|\)|\]|\[|\{|\}|,|\"|'|:|\&lt;|$|\.\s)#ie";         if (!preg_match($pattern, $url)) {             $this->error = "URL is not valid.";
            return false;
        }
        return true;
    }
 
    /**
     * Returns the protocol of the URL of the WSDL.
     * @param string $urlWsdl, URL of WSDL
     * @return boolean
     */
    private function getProtocolWsdl($urlWsdl) {
        $urlStructure = parse_url($urlWsdl);
        if (is_array($urlStructure) && isset($urlStructure['scheme'])) {
            $this->protocol = $urlStructure['scheme'];
            return true;
        } else {
            $this->error = "Failed to parse URL";
            return false;
        }
    }
 
    /**
     * Connect to the API CloudForms.
     * @return boolean
     */
    public function connectAPI() {
        if (!class_exists('nusoap_client')) {
            $this->error = 'nusoap_client Class not exist!';
        }
        $connect = new nusoap_client($this->getUrlWsdl(), true);
        $connect->soap_defencoding = 'UTF-8';
        $connect->debug_flag = false;
        if ($this->protocol == "https") {
            $connect->setCredentials($this->getUsername(), $this->getPassword());
        }
 
        if ($connect->getError()) {
            (string) $message = "Connection Error: " . $connect->getError();
            $this->error = $message;
            return false;
        }
        $this->setConnect($connect);
        return true;
    }
 
    /**
     * Display error messages.
     * @return string.
     */
    public function getError() {
        return $this->error;
    }
 
    /**
     * Check the connection to the API.
     * @return boolean
     */
    public function checkConnectApi() {
        if (is_null($this->getConnect())) {
            if ($this->connectAPI()) {
                return true;
            } else {
                $this->error = "No connection to the API";
                return false;
            }
        }
    }
 
}

 

Luego ahora creamos algunos métodos para obtener el listado e información de detalle de las máquinas virtuales, hosts, clusters y datastores que pueda tener nuestra plataforma de virtualización.

Estos métodos deben ir dentro de la clase.

<?php
/**
     * Returns a list and / or information of virtual machines
     * @param string $guid, GUID of a virtual machine (optional)
     * @return array, array components or boolean value.
     */
    public function getVirtualMachine($guid = "*") {
        if ($this->checkConnectApi()) {
            $result = array();
 
            if ($guid == "*") {
 
                $result = $this->getConnect()->call('GetVmList', array("hostGuid" => "*"));
            } else {
                $result = $this->getConnect()->call('FindVmByGuid', array("vmGuid" => $guid));
            }
 
            if (!$result) {
                $this->error = "No records were found.";
                return false;
            }
            return $result;
        } else {
            return false;
        }
    }
 
    /**
     * Returns a list and / or information of Hosts
     * @param string $guid, GUID of a host (optional)
     * @return array, array components or boolean value.
     */
    public function getHost($guid = "*") {
        if ($this->checkConnectApi()) {
            $result = array();
 
            if ($guid == "*") {
                $result = $this->getConnect()->call('GetHostList', array("emsGuid" => "*"));
            } else {
                $result = $this->getConnect()->call('FindHostByGuid', array("hostGuid" => $guid));
            }
 
            if (!$result) {
                $this->error = "No records were found.";
                return false;
            }
            return $result;
        } else {
            return false;
        }
    }
 
    /**
     * Returns a list and / or information of clusters.
     * @param string $guid, GUID of a clusters (optional)
     * @return array, array components or boolean value.
     */
    public function getCluster($guid = "*") {
        if ($this->checkConnectApi()) {
            $result = array();
 
            if ($guid == "*") {
                $result = $this->getConnect()->call('GetClusterList', array("emsGuid" => "*"));
            } else {
                $result = $this->getConnect()->call('FindClusterById', array("clusterId" => $guid));
            }
 
            if (!$result) {
                $this->error = "No records were found.";
                return false;
            }
            return $result;
        } else {
            return false;
        }
    }
 
    /**
     * Returns a list and / or information of datastores.
     * @param string $guid, GUID of a datastore (optional)
     * @return array, array components or boolean value.
     */
    public function getDataStore($guid = "*") {
        if ($this->checkConnectApi()) {
            $result = array();
 
            if ($guid == "*") {
                $result = $this->getConnect()->call('GetDatastoreList', array("emsGuid" => "*"));
            } else {
                $result = $this->getConnect()->call('FindDatastoreById', array("datastoreId" => $guid));
            }
 
            if (!$result) {
                $this->error = "No records were found.";
                return false;
            }
            return $result;
        } else {
            return false;
        }
    }
?>

A continuación, se mostrará los métodos para poder suspender, apagar e iniciar maquinas virtuales.

<?php
    /**
     * shut down a virtual machine.
     * @param string $guid, GUID virtual machine.
     * @return boolean
     */
    public function stopServiceVirtualMachine($guid) {
        if (!$this->checkConnectApi()) {
            return false;
        }
 
        $result = $this->getConnect()->call('EVMSmartStop', array("vmGuid" => $guid));
 
        if (is_array($result) &amp;&amp; !isset($result['result'])) {
            $this->error = $this->getConnect()->getError();
            return false;
        }
        return $result;
    }
 
    /**
     * suspend a virtual machine.
     * @param string $guid, GUID virtual machine.
     * @return boolean
     */
    public function suspendServiceVirtualMachine($guid) {
        if (!$this->checkConnectApi()) {
            return false;
        }
 
        $result = $this->getConnect()->call('EVMSmartSuspend', array("vmGuid" => $guid));
 
        if (is_array($result) &amp;&amp; !isset($result['result'])) {
            $this->error = $this->getConnect()->getError();
            return false;
        }
 
        return $result;
    }
 
    /**
     * start a virtual machine.
     * @param string $guid, GUID virtual machine.
     * @return boolean
     */
    public function startServiceVirtualMachine($guid) {
        if (!$this->checkConnectApi()) {
            return false;
        }
 
        $result = $this->getConnect()->call('EVMSmartStart', array("vmGuid" => $guid));
 
        if (is_array($result) && !isset($result['result'])) {
            $this->error = $this->getConnect()->getError();
            return false;
        }
 
        return $result;
    }
?>

Creando nuestro consumidor.

El siguiente script, es para crear el archivo consumidor (demo) que llamará a la clase ClientAPICloudforms para conectarse al API de Cloudforms e invocar los métodos que hemos creado.

Para el consumidor, se uso del microframework Silex como ruteador.

<?php
/**
 * Demo : client cloudforms using silex microframework
 * @author : Gonzalo Chacaltana Buleje <gchacaltanab@outlook.com>
 */
require_once __DIR__ . '/silex/autoload.php';
require_once __DIR__ . '/ClientAPICloudforms.php';
 
$app = new Silex\Application();
 
$app->get('/{component}/{identifier}', function($component, $identifier) use ($app) {
            $components = array(
                "virtualMachine" => "getVirtualMachine",
                "host" => "getHost",
                "cluster" => "getCluster",
                "datastore" => "getDataStore"
            );
 
            // en la siguiente variable, debe ir el WSDL de tu API de Cloudforms
            (string) $wsdl = "https://cloudforms.tudominio.com/vmdbws/wsdl";
            (string) $user = "tu_usuario";
            (string) $password = "tu_clave";
 
            $cloudForms = new ClientAPICloudforms($wsdl, $user, $password);
            if ($cloudForms->getError()) {
                return $cloudForms->getError();
            }
 
            if (!array_key_exists($component, $components)) {
                return "Component not exist.";
            }
 
            (string) $identifier = (empty($identifier)) ? "*" : $identifier;
            $result = $cloudForms->$components[$component]($identifier);
 
            if (!$result) {
                return $cloudForms->getError();
            }
 
            //mostrando resultado
            print_r($result);
        })
    ->value('component', FALSE)
    ->value('identifier', FALSE);
 
$app->get('/serviceVirtualMachine/{guid}/{action}', function($guid, $action) use ($app) {
 
        $operations = array(
            "suspend" => "suspendServiceVirtualMachine",
            "start" => "startServiceVirtualMachine",
            "stop" => "stopServiceVirtualMachine"
        );
 
        if (!array_key_exists($action, $operations)) {
            return "Operation not exist.";
        }
 
        (string) $wsdl = "https://cloudforms.tudominio.com/vmdbws/wsdl";
        (string) $user = "tu_usuario";
        (string) $password = "tu_password";
 
        $cloudForms = new ClientAPICloudforms($wsdl, $user, $password);
        if ($cloudForms->getError()) {
            return $cloudForms->getError();
        }
 
        $result = $cloudForms->$operations[$action]($guid);
        if (!$result) {
            return $cloudForms->getError();
        } else {
            return $result['reason'];
        }
    });

Creando entornos de desarrollo virtuales con Vagrant

Vagrant

 

Como en todo proyecto web, muchas veces sucede que los miembros de un equipo de desarrollo utilizan una plataforma distinta (WindowsLinuxMac OS X) para construir software, no solo entre ellos, ademas distinta con la plataforma del software en el ambiente de producción,  ocasionando en la mayoría de los casos inconsistencia y problemas a la hora de subir sus avances al entorno de pruebas y producción.
La solución a este problema es utilizar máquinas virtuales, para centralizar y aislar un entorno de pruebas y compartirlo entre todos los miembros del equipo.
Las soluciones de virtualización mas comunes son : VirtualboxVM WareXen, entre otros, pero requieren instalar el Sistema Operativo y configurarlos.

¿QUÉ ES VAGRANT?

 

Vagrant es una herramienta, escrita en Ruby, que facilita  la creación de entornos virtuales de desarrollo completos, para ello nos abstrae del sistema de virtualización subyacente (Virtualbox, VM Ware u otro) y nos permite instalar y configurar el software de la máquina virtual con herramientas de automatización como ChefPuppet o incluso los clásicos scripts de shell.
El proyecto Vagrant se inició en enero del 2010 por Mitchell Hashimoto.

INSTALACIÓN

 

Vagrant lo puedes utilizar con varios sistemas de virtualización, para el presente ejemplo voy a utilizar VirtualBox porque es gratuito, está disponible para varias plataformas y funciona con Vagrant sin configuraciones adicionales.
En el ejemplo, lo instalaré sobre plataforma windows 7.
1. Primero descargamos la ultima versión de Vagrant y la instalamos en nuestro Sistema Operativo Windows 7.

Iniciando nuestro entorno de desarrollo con Vagrant

 

Inicializamos la configuración partiendo de una imagen de base.
$ vagrant init precise32 http://files.vagrantup.com/precise32.box

Levantamos la maquina virtual

$ vagrant up
Con estos dos simples comandos ya tenemos funcionando nuestra máquina virtual de Ubuntu 12.04 LTS de 32 bits. Podemos conectarnos a ella por SSH con vagrant ssh y, una vez hemos terminado de usarla, eliminarla con vagrant destroy.
El comando vagrant init crea un fichero Vagrantfile en el directorio donde lo hemos ejecutado que contiene la configuración de la máquina virtual. Estando en ese directorio, con vagrant uppodemos volver a levantar la máquina sin necesidad de hacer de nuevo el init.

BOXES EN VAGRANT

 

En Vagrant una box es un fichero que contiene la base a partir de la cual se crean las máquinas virtuales. Este fichero puede ser compartido fácilmente para que otros repliquen nuestro mismo entorno de desarrollo.
En el ejemplo anterior creamos una máquina virtual con Ubuntu, pero no estamos limitados a este sistema. En la página vagrantbox.es disponemos de un listado enorme de Sistemas Operativos, tanto de 32 como de 64 bits, que podemos utilizar como base para nuestras máquinas de Vagrant. Podríamos incluso crear nuestras propias boxes utilizando Veewee.

COMANDOS BÁSICOS

 

vagrant init [box-name] [box-url]

Crea el fichero de configuración Vagrantfile en el directorio actual. Si se pasan el segundo y el tercer parámetro, se establecen el nombre y la box a utilizar en la configuración.

vagrant up

Levanta la máquina virtual de acuerdo a la configuración del Vagrantfile del directorio actual.

vagrant halt

Detiene la máquina virtual, pero mantiene los cambios que hayamos hecho en ella para la siguiente vez que ejecutemos vagrant up.

vagrant reload

Es equivalente a hacer un vagrant halt seguido de un vagrant up. Tras hacer cambios en un Vagrantfile es necesario hacer reload.

vagrant suspend

Suspende la máquina virtual, salvando el estado de ejecución actual de forma que pueda ser resumido más tarde y continuar desde ese punto exacto.

vagrant resume

Resume una máquina que ha sido anteriormente suspendida con vagrant suspend.

vagrant destroy

Detiene la máquina virtual y destruye todos los cambios que podamos haber hecho. La siguiente vez que hagamos un vagrant up partirá de una imagen totalmente limpia.

vagrant ssh

Nos conecta por SSH a la máquina virtual que hemos levantado.

vagrant status

Muestra el estado de las máquinas levantadas con Vagrant.

vagrant box

Comando para gestionar las boxes que tenemos en nuestro sistema.

vagrant package

Exporta la máquina virtual en ejecución en un fichero box que puede ser reutilizado.

QUE ES EL VAGRANTFILE

 

El archivo Vagrantfile contiene el tipo de máquina virtual y su configuración. Por defecto, tras hacer vagrant init, la configuración solo incluye el nombre de la box que va a usar y su URL. Para que puedas realizar una configuración según las necesidades de tu proyecto puedes revisar la documentación de vagrantfile.