Robot Framework

Dernière modification le 7 décembre 2020

Il existe de nombreuses solutions pour effectuer les tests unitaires TDD (Test Driven Development) dans le cadre des mises au point NetDevOps, mais Robot Framework est mon framework d’automatisation open source générique préféré pour effectuer les tests d’acceptation ATDD (Acceptance Test Driven Development). Ses capacités de test peuvent être étendues avec des librairies implémentées en Python ou en Java. Initialement, le framework a été développé par Nokia Networks. Aujourd’hui, il est sponsorisé par la Robot Framework Foundation.

Dans la mesure où on parle de « Network as Code » ou « Infrastructure as Code », c’est que l’on considère que tout ce qui est nécessaire pour activer l’infrastructure existe sous forme logicielle. Dans le monde du logiciel, les approches Agile et DevOps sont largement adoptées. On sait que les tests sont une partie importante de l’approche Agile pour la « Definition of Done » d’une « User Story ». Pour réaliser une « User Story », il est généralement nécessaire de la découper en tâches. Il n’existe pas qu’une seule façon de concevoir les choses, mais en ce qui me concerne, je considère que les tests qui valident une tâche doivent rester au plus proche de celle-ci. Il s’agit, le plus souvent, de tests unitaires. Si j’utilise Ansible pour réaliser la tâche, alors les tests permettant de dire que la tâche est bien terminée sont intégrés à ses Playbooks.

C’est au niveau d’abstraction de la « User Story » que j’utilise Robot Framework. A ce niveau là, ce sont le plus souvent des tests d’acceptation qui sont nécessaires pour la validation. Ils peuvent se concevoir sous différentes formes, y compris avec l’approche BDD en langage Gherkin (Given/When/Then).

Pour des tests d’acceptation, il est fréquent d’executer des actions qui transitent par l’infrastructure réseau mais qui visent des serveurs ou des équipements en périphérie. Pour ce faire, j’utilise quelques librairies « standard », mais souvent des librairies « externes » comme SeleniumLibrary pour accéder à des serveurs web (pensez à activer la fonction « Autoriser l’automatisation à distance » de Safari sur MAC), SSHLibrary pour se connecter à des équipements ou NcclientLibrary pour effectuer des connexions NETCONF.

Mais je vous propose d’étudier et détailler un cas un peu plus complexe et intéressant. Supposons que nous ayons une « User Story » qui focalise sur l’implémentation ZTP (Zero Touch Provisioning). Cette « User Story » aura été découpée en différentes tâches pour monter les serveurs nécessaires et les configurer avec les informations adéquates. Ces tâches auront intégré leurs propres définitions de « Done » avec des tests unitaires. Considérons que, pour cette « User Story », le test d’acceptation consiste à accéder à chacun des équipements et à vérifier que la version d’OS corresponde à une référence attendue, ce qui prouvera que l’infrastructure Out-of-Band est fonctionnelle, que la configuration minimum a bien été injectée et que les éventuelles mises à jour automatiques d’OS se sont bien effectuées…

L’exemple proposé s’appuie sur un lab virtuel Juniper Vagrant avec deux switchs virtuels vQFX. L’objectif est d’effectuer des connexions sur les équipements pour récupérer la version de Junos. Le plus simple est de construire sa propre librairie externe en utilisant le micro framework Juniper PyEZ.

  
#!/usr/bin/env python3
from jnpr.junos import Device


class JunosDevice(object):

    def __init__(self):
        self.device = None

    def connect_device(self, host, port, user, password):
        self.device = Device(host, port=port, user=user, password=password)
        self.device.open()

    def close_device(self):
        self.device.close()

    def get_device_info(self):
        device_facts = dict(self.device.facts)
        return device_facts

    def get_device_os_version(self):
        facts = self.get_device_info()
        return facts["version"]
*** Settings ***
Documentation           This example demonstrates executing external junos library
...                     and checking the OS version of the device.

Library                 JunosDevice.py

*** Variables ***
&{HOST 1}               ip=127.0.0.1    port=2222
&{HOST 2}               ip=127.0.0.1    port=2200
@{HOSTS}                &{HOST 1}   &{HOST 2}
${USERNAME}             root
${PASSWORD}             Juniper
${JUNOS}                18.4R1.8

*** Test Cases ***
Ckeck Junos OS Version
    : FOR   ${HOST}     IN  @{HOSTS}
    \       Connect Device  host=${HOST.ip}    port=${HOST.port}    user=${USERNAME}    password=${PASSWORD}
    \       ${version}=     Get Device Os Version
    \       Should Be Equal As Strings      ${version}      ${JUNOS}
    \       Close Device

Les concepts se devinent facilement et vous pouvez imaginer qu’il est possible d’enrichir cette librairie à volonté et d’en optimiser la syntaxe… et voici la preuve que ça fonctionne.

Robot Framework Juniper Junos
Résultat Junos en ligne de commande
Log Robot Framework Juniper vQFX
Log au format HTML avec les deux switchs vQFX ciblés

Pour des raisons de lisibilité, tous les éléments ont été mis dans le même fichier robot, ce qui n’est pas le cas dans ma librairie complète. Lorsqu’on souhaite partager des ressources et des variables entre plusieurs fichiers robot, on externalise ces informations dans un fichier contenant les ressources et un autre fichier contenant les variables. Un fichier ressource a la même syntaxe qu’un fichier robot à ceci prêt qu’il ne contient pas de tests.

Pour terminer cette présentation, je vous livre un extrait d’une librairie externe que j’ai développé pour Alcatel-Lucent Enterprise OmniSwitch.

#!/usr/bin/env python3
from netmiko import ConnectHandler


class OmniSwitch(object):

    def __init__(self):
        self.device = None

    def connect_device(self, host, username, password):
        self.device = ConnectHandler(device_type="alcatel_aos", host=host,
                                     username=username, password=password)

    def close_device(self):
        self.device.disconnect()

    def get_device_os_version(self):
        return self.device.send_command("show microcode loaded")
*** Settings ***
Documentation           This example demonstrates executing external ALE library
...                     and checking the OS version of the device.

Library                 OmniSwitch.py

*** Variables ***
&{HOST 1}               ip=192.168.1.24
@{HOSTS}                &{HOST 1}
${USERNAME}             admin
${PASSWORD}             switch
${AOS}                  6.7.2.191.R04

*** Test Cases ***
Ckeck AOS OS Version
    : FOR   ${HOST}     IN  @{HOSTS}
    \       Connect Device  host=${HOST.ip}    username=${USERNAME}    password=${PASSWORD}
    \       ${version}=     Get Device Os Version
    \       Should Contain  ${version}         ${AOS}
    \       Close Device
Robot Framework ALE AOS CLI
Résultat AOS en ligne de commande
Alcatel-Lucent Enterprise SpaceWalkers
Alcatel-Lucent Enterprise Robot Fralework – ALE Web Site