I. Introduction

Dans un premier temps, l'utilisateur doit :

  • disposer d'un réseau déjà déployé (pour les comptes CloudStack Instances c'est normalement le cas) ;
  • avoir la possibilité de créer une nouvelle instance (ne pas être à sa limite de création donc) ;
  • déterminer l'ID du réseau auquel sera connecté son instance (via l'interface web ou l'API, de même, indications en commentaires dans le script) ;
  • déterminer l'ID de l'offre de service (configuration CPU/RAM/Réseau) voulue, indications au sein du script.

II. II Déploiement de l'API

II-A. Script

 
Sélectionnez
<?php

    // Liste complète des appels possibles : http://download.cloud.com/releases/3.0.6/api_3.0.6/TOC_User.html



    ################################

    # Paramètres généraux de l'API #

    ################################

    

    // Renseignez ici votre clef API ainsi que votre clef secrète

    // Nous conseillons de déporter cette information au sein d'un autre fichier PHP dont on restreindra l'accès

    define("APIKEY","d1Ii4ON33yqUahgtlsjnzK0o7QXNzXZONZKcW_rDcOV4jln48b5ujsSvU-mo9KsoLbp631VyCI4fLb3u176JZg");

    define("SECRETKEY","fBenapO2hjOd3Vaj-NDmrekTIK85B8Jl_m8wFld4gi0XvLXy4w0iJz-2srztoVqkGVoblT25DyG4709gibo_TA");

    //On définit l'URL d'appel de l'API (ou 'EndPoint')

    define("ENDPOINT","https://cloudstack.ikoula.com/client/api");

    

    #############################################################

    # Paramètres utilisateur de configuration d(es) instance(s) #

    #############################################################

    

    //Première instance :

    

        /* UUID(s) du réseau auquel sera connectée votre instance.

        Utilisez la requête API 'listNetworks' 
		ou l'interface pour lister les réseaux existants et déterminer le réseau voulu

        */

        $vm01['conf']['networkid'] = "<ID DU RESEAU QUE VOUS SOUHAITEZ UTILISER>";

        

        /* UUID de l'offre de service (configuration matérielle) voulue.

        Utilisez la requête API 'listServiceOfferings' pour lister les offres existantes.

        Actuellement :

            - m1.small : c6b89fea-1242-4f54-b15e-9d8ec8a0b7e8

            - m1.medium : 8dae8be9-5dae-4f81-89d1-b171f25ef3fd

            - m1.large : d2b2e7b9-4ffa-419e-9ef1-6d413f08deab

            - m1.extralarge : 1412009f-0e89-4cfc-a681-1cda0631094b

        */

        $vm01['conf']['serviceofferingid'] = "<ID DE L'OFFRE DE SERVICE A UTILISER>";



        

        /* UUID du modèle de systeme d'exploitation.

        Vous pouvez aussi utiliser la requête API 'listTemplates' pour lister les modèles existants.

        Dans notre exemple nous allons nous baser sur le template 'Debian 7 - Minimal - 64bits'

        */

        $vm01['conf']['templateid'] = "6d5b069b-4268-4c7c-810f-2793c2ac44fb";

        

        /* Nom de votre choix pour l'instance (Nom de la machine et d'affichage dans l'interface)

        Caractères alphanumériques uniquement. Ce nom doit être unique au sein de votre réseau. */

        $vm01['conf']['hostname'] = "<NOM DE VOTRE INSTANCE>";

        

/* ------------------ NE PLUS RIEN MODIFIER APRES CETTE LIGNE ---------------------- */

    $json_response = null;



    ################################################

    # Récupération de l'ID de zone pour ce network #

    ################################################

    // Requête API

    $args['command'] = "listNetworks";



    // On reprend l'ID de réseau renseigné plus tôt

    $args['id'] = $vm01['conf']['networkid'];



    // Execution de la requête

    sendRequest($args, $json_response);



    // Stockage de l'ID de zone pour ce réseau

    $vm01['conf']['zoneid'] = $json_response['listnetworksresponse']["network"][0]['zoneid'];



    // On réinitialise les variables utilisées pour l'envoi de requêtes API

    unset($args);

    

    ######################################################

    # Récupération de l'id de l'adresse IP de ce network #

    ######################################################

    // Requête API

    $args['command'] = "listPublicIpAddresses";

    $args['associatednetworkid'] = $vm01['conf']['networkid'];



    // Execution de la requête

    sendRequest($args, $json_response);



    // Stockage de l'ID de l'adresse IP publique du réseau

    $vm01['conf']['publicIPid'] = $json_response['listpublicipaddressesresponse']["publicipaddress"][0]['id'];



    // On réinitialise les variables utilisées pour l'envoi de requêtes API

    unset($args);

        

    ####################################

    # Création de la première instance #

    ####################################

    

    // Requête API

    $args['command'] = "deployVirtualMachine";

    $args['zoneid'] = $vm01['conf']['zoneid'];

    $args['serviceofferingid'] = $vm01['conf']['serviceofferingid'];

    $args['templateid'] = $vm01['conf']['templateid'];

    $args['networkids'] = $vm01['conf']['networkid'];

    $args['name'] = $vm01['conf']['hostname']; //Hostname

    $args['displayname'] = $args['name']; //Nom d'affichage

    

    //Type de retour : JSON ou XML (par défaut : XML)

    $args['response'] = "json";



    //On réinitialise les variables avant l'appel API

    

    // Initialisation du client API

    sendRequest($args, $json_response);

    

    //On vérifie la presence d'un Job

    if(preg_match("/^[0-9a-f\-]+$/", $json_response['deployvirtualmachineresponse']['jobid']) > 0)

    {

        $jobs[] = $json_response['deployvirtualmachineresponse']['jobid'];

    }

    else{

        echo "ID de job non trouvé.\n";

    }

    

    //On mémorise la correspondance name/id de l'instance au sein d'un tableau

    $vm01['conf']['id'] = $json_response['deployvirtualmachineresponse']['id'];

    

    //On utilise la fonction de vérification des jobs asynchrones

    if(!checkJobs($jobs))

        exit;

    

/*-----------------------------------------------------------------------------------------------------------*/

    

    #############

    # Fonctions #

    #############

    

    //Fonction de gestion d'erreur(s) API

    function apiErrorCheck($json_response)

    {

        if(is_array($json_response))

        {

            $key = array_keys($json_response);

            if(isset($json_response['errorcode']))

            {

                echo "ERREUR : ".$json_response['errorcode']." - ".$json_response['errortext']."\n";

                exit;

            }

            if(isset($json_response['errorcode']) || (isset($key[0]) && isset($json_response[$key[0]]['errorcode'])))

            {

                echo "ERREUR : ".$json_response[$key[0]]['errorcode']." - ".$json_response[$key[0]]['errortext']."\n";

                exit;

            }

        }

        else

        {

            echo "ERREUR : PARAMETRE INVALIDE";

                exit;

        }

    }

    

    //Fonction d'envoi de requête à l'API

    function sendRequest($args, &$json_response)

    {

        $json_response = null;

        // Clef API

        $args['apikey'] = APIKEY;

        $args['response'] = "json";

        //On classe les paramètres

        ksort($args);

        // On construit la requête HTTP basée sur les paramètres contenus dans $args

        $query = http_build_query($args);

        // On s'assure de bien remplacer toutes les occurences de '+' par des '%20'

        $query = str_replace("+", "%20", $query);

        //On utilise la clef secrète et un algorithme HMAC SHA-1 sur la requête pour encoder la signature

        $hash = hash_hmac("SHA1",  strtolower($query), SECRETKEY, true);

        $base64encoded = base64_encode($hash);

        $signature = urlencode($base64encoded);



        // Construction de la requête finale sous la forme 'URL API + Requête API et paramètres + Signature'

        $query .= "&signature=" . $signature;



        // $jobs = null;

        

        // Initialisation du client API

        try

        {

            //Construction de la requête

            $httpRequest = new HttpRequest();

            $httpRequest->setMethod(HTTP_METH_POST);

            $httpRequest->setUrl(ENDPOINT . "?" . $query);

            

            // Envoi de la requête au serveur :

            $httpRequest->send();

            // Récupération du retour de l'API

            $response = $httpRequest->getResponseData();

            // retour de la réponse

            $json_response = json_decode($response['body'], true);

            

            apiErrorCheck($json_response);

        }

        catch (Exception $e)

        {

            echo "Probleme lors de l'envoi de la requête. ERREUR=".$e->getMessage();

            exit;

        }

    }

    

    //Fonction de vérification des jobs asynchrones

    function checkJobs($jobs)

    {

        $json_response = null;

        $error_msg = "";

        if(is_array($jobs) && count($jobs) > 0)

        {

            // La tâche est asynchrone, on doit donc régulièrement vérifier les tâches avec une sécurité

            $secu = 0;

            // On indexe les tâches

            $ij = 0;

            // Tant qu'il y a des tâches asynchrones non-terminées dans la pile de vérification, on boucle et on vérifie le statut

            while(count($jobs) > 0 && $secu < 100)

            {

                try

                {

                    //On interroge le statut de la tâche asynchrone

                    // http://download.cloud.com/releases/3.0.6/api_3.0.6/root_admin/queryAsyncJobResult.html

                    $args['apikey'] = APIKEY;

                    $args['command'] = "queryAsyncJobResult";

                    $args['jobid'] = $jobs[$ij];

                    $args['response'] = "json";

                    

                    $json_response = null;

                    sendRequest($args, $json_response);

                    

                    if(is_array($json_response['queryasyncjobresultresponse']))

                    {

                        // Si OK...

                        if($json_response['queryasyncjobresultresponse']['jobstatus'] == 1)

                        {

                            // ...On retire simplement la tâche du tableau a surveiller

                            //return("JOB OK\n");

                            array_splice($jobs, $ij, 1);

                        }

                        // Sinon...

                        elseif($json_response['queryasyncjobresultresponse']['jobstatus'] == 2)

                        {

                            //...On mémorise l'erreur et on retire la tâche du tableau à surveiller

                            //return("JOB ERREUR\n");

                            array_splice($jobs, $ij, 1);

                            $error_msg .= "ERREUR ! RESULT_CODE=".$json_response['queryasyncjobresultresponse']['jobresultcode'];

                        }

                        // Cette tâche est encore en cours, on passe à la suivante et on temporise

                        elseif($json_response['queryasyncjobresultresponse']['jobstatus'] == 0)

                        {

                            // Tâche suivante

                            $ij++;

                            // Temporisation entre chaque interrogation pour ne pas charger inutilement l'API

                            sleep(5);

                        }

                    }

                }

                catch(Exception $e)

                {

                    $error_msg .= "EXCEPTION Lors de la verification la tache asynchrone. JOB_UUID:".$jobs[$ij]." ERREUR=".$e->getMessage()." \n";

                }



                // Si l'index arrive en bout de tableau, on le réinitialise

                if($ij == count($jobs))

                {

                    $ij = 0;

                    $secu++;

                }

            }



            if($error_msg)

            {

                echo "ERRORS:".$error_msg."\n";

                return false;

            }

            return true;

        }

        echo "No job\n";

        return false;

    }

?>

II-B. Conclusion

Le principal intérêt des opérations via l'API est la rapidité d'exécution et la flexibilité des procédés.

L'interface CloudStack a comme avantages d'être ergonomique et à la portée de la majorité, mais l'API va encore plus loin et permet des déploiements rapides et automatisés d'une ou plusieurs instances prêtes à l'emploi.

Ce script vous permettra donc de déployer de manière programmatique une instance Debian « Wheezy » prête à l'emploi. La version fournie ici en exemple est très simplifiée mais il est tout à fait possible via quelques modifications mineures de procéder à la création d'instances d'autres configurations « matérielles » (Mémoire/Processeur/Réseau en changeant d'offre de service) ou logicielles (via la modification du modèle choisi pour un autre système d'exploitation).

De même, cet aspect programmatique permet la création multiple d'instances sans intervention de l'utilisateur (en faisant en sorte de changer automatiquement le nom pour chaque instance).

Via la répétition de son exécution on peut donc imaginer déployer des clusters complets d'instances identiques en un temps très réduit.

De plus, l'exemple ci-dessus est fourni sur la base du langage PHP5 mais il est tout à fait possible d'utiliser d'autres langages de programmation selon vos préférences ou contraintes.

III. Remerciements

Merci à Wiztricks pour sa relecture technique.

Merci à Ikoula pour ce tutoriel.