Améliorer la fiabilité de vos manifestes Jelastic grâce à une documentation vivante image

Améliorer la fiabilité de vos manifestes Jelastic grâce à une documentation vivante

Les manifestes Jelastic sont parfois si complexes qu’il est difficile de suivre tous les petits détails qui peuvent échouer lors d’une installation. La plupart du temps, un manifeste complexe est également difficile à comprendre. En effet, les manifestes Jelastic sont souvent un tas de scripts écrits en plusieurs langues et il est facile de s’y perdre lorsque l’infrastructure qu’ils définissent prend de l’ampleur. Chaque script est responsable d’un petit détail qui finira par faire tenir l’ensemble du système. En tant que fournisseur de logiciels sur la plateforme Jelastic, vous voulez certainement vous assurer que les manifestes que vous mettez sur le marché fonctionneront toujours correctement lorsque Jelastic publiera une nouvelle mise à jour de la plateforme. Au moins, vous voulez être informé si vos manifestes ne fonctionnent plus, afin de pouvoir les corriger avant que les utilisateurs ne les utilisent. J’ai trop souvent été ralenti dans mes projets juste parce qu’un manifeste du marché ne fonctionnait plus. Je l’avais installé de nombreuses fois dans le passé, mais avec la nouvelle mise à jour de Jelastic, il ne fonctionne plus. Parfois, je passe à un logiciel équivalent dont le manifeste fonctionne tout simplement. D’autres fois, comme il n’y a pas d’alternative, je dois avertir mon fournisseur Jelastic et attendre que le manifeste soit corrigé.

Je suis d’avis que les fournisseurs de manifestes Jelastic bénéficieraient grandement d’une sorte de validation de leur installation de manifeste ainsi que d’une documentation synchronisée avec ce que leurs manifestes livrent. Les tests automatisés sont l’une des pierres angulaires de tout logiciel professionnel. Une documentation vivante vous permettra de faire exactement cela : valider vos manifestes et documenter ce qu’ils font.

Laissez-moi vous montrer ce que je veux dire avec un exemple simple.

Le manifeste hasura

Hasura simplifie considérablement la création d’API web basées sur une base de données (notamment postgresql). Les applications typiques conçues avec Hasura sont les suivantes

Je suis actuellement en train de développer un manifeste pour installer hasura sur Jelastic et j’ai pensé vous fournir un exemple concret de la façon de réaliser une documentation vivante sur un cas simple. Vous pouvez trouver le code dans ce dépôt gitlab. Pour des raisons de concision, nous allons nous concentrer sur la validation d’une partie du moteur de faas. La méthode que je décris ci-dessous est généralisable au développement de tout type de manifeste.

Tout commence par une caractéristique du cornichon :

faasd.feature

    Feature: Installation of the faas engine

    The faas engine will allow to link hasura actions and events
    hasura events to features.

    Context: A Docker node is available

        Given that a jelastic environment with a docker node is available in
    group faas with the image ubuntu:latest.
    And the faas engine is installed

    Scenario: Connecting

        When a user connects to the faas engine
        She then gets a success response

    Scenario: Deploying a new function

        When a user deploys the 'hello-python' function to the faas engine
        It will get a successful response

    Scenario: Calling the function

        The hello-python function has been deployed to the faas engine.
        When a user invokes it with the payload this is me.
        It then gets the response
      
        Hello ! You said: its me

Voyez-vous cette belle description de ce que le manifeste faas veut réaliser ? La formulation claire et agréable en anglais ? Et ce fichier de fonctionnalités est minimaliste. J’aurais pu ajouter des images, des descriptions de scénarios ou des détails sur la description des fonctionnalités.

Les quelques scénarios ci-dessus permettent de s’assurer que notre manifeste Jelastic installe bien faasd et que nous pouvons effectuer les opérations de base sur le moteur faas. Avec ce simple fichier de fonctionnalités, nous décrivons le strict minimum que nous devons réaliser avec notre moteur faas après son installation réussie : nous devons

En substance, le fichier de caractéristiques ci-dessus est votre spécification. Dans un projet typique, vous aurez un grand nombre de fichiers de caractéristiques. Il est donc assez pratique de les transformer en format html dynamique. Vous pouvez y parvenir, par exemple, avec pickles, pour lequel vous disposez à la fois d’une interface utilisateur et d’un outil de console, ce qui en fait l’outil idéal pour votre pipeline gitlab ! Le site web statique généré par pickles permet de parcourir facilement vos fonctionnalités. Vous pouvez même joindre les résultats des tests à ce rapport Web, ce qui en fait un bon outil de suivi de la progression de votre équipe dans l’itération de développement en cours.

Dans le reste de cet article, nous voulons faire vivre cette spécification et nous allons nous concentrer sur la méthode du concombre. Une alternative à cucumber est gauge.

Configuration de l’exemple Python

Concentrons-nous d’abord sur la configuration du code nécessaire pour donner vie à ces scénarios de test. Il existe des frameworks pour la majorité des langages de programmation les plus populaires, comme vous pouvez le voir ici. Partons du principe que nous allons programmer les tests en python, avec behave, car c’est très facile. Tout d’abord, installez behave

pip install behave

Ensuite, lorsque j’ai démarré le projet, l’arbre source de ce projet de test ressemblait à ceci :

.
├── features
├── features
│ ├── environment.py
│ ├── faasd.feature
│ ├── fixtures.py
│ ├── steps
│ └── faasd_steps.py
├── manifest.jps
└── serverless
    └── manifest.jps

D’un côté, nous avons le dossier features, où toute la magie des tests de comportement va se produire. D’autre part, nous avons nos manifestes jps que nous voulons tester et documenter. Dans le dossier features, nous trouvons un environment.py qui définit l’environnement de test. En substance, c’est ici que nous appliquons les fixtures définies dans le fichier fixtures.py, c’est-à-dire que vous définissez ce qui va se passer avant tous les tests, avant chaque fonctionnalité, avant chaque scénario, après tous les tests, etc. Par exemple, le fichier environment.py pourrait ressembler à ceci :

environment.py

from fixtures import *
from behave import use_fixture


def before_all(context):
    # the following fixtures are applied before all tests
    use_fixture(api_clients, context)
    use_fixture(random_seed, context)
    use_fixture(worker_id, context)
    use_fixture(commit_sha, context)
    use_fixture(project_root_folder, context)
    use_fixture(serverless_manifest, context)
    use_fixture(faas_port, context)


def before_scenario(context, scenario):
    # the following fixtures are applied before each scenario
    use_fixture(clear_environment, context)
    

Dans nos tests de manifeste jps, nous avons généralement besoin de créer des environnements Jelastic, de les vider après les tests, de vérifier certaines choses sur les environnements, etc. C’est pourquoi nous avons besoin de clients API Jelastic. Afin de faciliter les tests des manifestes hasura-jps, nous avons mis en place un client Jelastic en python. Vous le verrez en action ci-dessous. De plus, comme il se peut que de nombreux tests soient exécutés simultanément (par exemple à partir de différentes branches de notre dépôt), nous devons choisir nos noms d’environnement Jelastic avec soin. Cela explique les fixtures random_seed, worker_id, et commit_sha. Les fixtures sont définis comme ceci :

# fixtures.py

import os
import random

from behave import fixture
from jelastic_client import JelasticClientFactory


@fixture
def random_seed(context):
    random.seed('hasura-jps-tests')


@fixture
def worker_id(context):
    context.worker_id = 'master'
    return context.worker_id


@fixture
def commit_sha(context):
    # this is data coming from the command-line, see .gitlab-ci.yaml below
    userdata = context.config.userdata
    context.commit_sha = userdata['commit-sha']
    return context.commit_sha


@fixture
def project_root_folder(context):
    # this is data coming from the command-line, see .gitlab-ci.yaml below
    userdata = context.config.userdata
    context.project_root_folder = userdata['project-root-folder'] if 'project-root-folder' in userdata else '.
    return context.project_root_folder


@fixture
def api_clients(context):
    # this is data coming from the command-line, see .gitlab-ci.yaml below
    userdata = context.config.userdata
    api_url = userdata['api-url']
    api_token = userdata['api-token']
    api_client_factory = JelasticClientFactory(api_url, api_token)
    # this partially wraps https://docs.jelastic.com/api/#!/api/marketplace.Jps
    context.jps_client = api_client_factory.create_jps_client()
    # this partially wraps https://docs.jelastic.com/api/#!/api/environment.Control
    context.control_client = api_client_factory.create_control_client()
    # this partially wraps https://docs.jelastic.com/api/#!/api/environment.File
    context.file_client = api_client_factory.create_file_client()


@fixture
def faas_port(context):
    context.faas_port = 8080
    return faas_port


@fixture
def new_environment(context):
    context.current_env_name = get_new_random_env_name(
        context.control_client, context.commit_sha, context.worker_id)
    yield context.current_env_name
    env_info = context.control_client.get_env_info(
        context.current_env_name)
    if env_info.exists():
        context.control_client.delete_env(context.current_env_name)


@fixture
def serverless_manifest(context):
    context.serverless_manifest = os.path.join(
        context.project_root_folder, 'serverless', 'manifest.jps')
    return context.serverless_manifest
    

En substance, behave met un contexte à la disposition de tous les scénarios de test. Les fixtures placent des éléments dans ce contexte, afin qu’ils soient disponibles dans les étapes de test que nous définirons plus tard. Par exemple, nous ne voulons pas créer nos clients API Jelastic dans nos méthodes d’étape. Nous les définissons donc une fois pour toutes dans un fixture et les rendons disponibles dans le contexte.

Le pipeline de test des fonctionnalités correspondant ressemble à ceci dans gitlab :

.gitlab-ci.yml

stages:
  - test
acceptance-test:
  stage: test
  # you need at least behave, jelastic-client, sh
  image: some-python-image-with-the-relevant-dependencies-installed
  script:
    - |
      behave --junit --junit-directory ./features/test-reports --tags ~wip \
        -D project-root-folder="${CI_PROJECT_DIR}" \
        -D api-url="${JELASTIC_API_URL}" \
        -D api-token="${JELASTIC_ACCESS_TOKEN}" \
        -D commit-sha="${CI_COMMIT_SHORT_SHA}"
  artifacts:
    reports:
      junit:
        - $CI_PROJECT_DIR/features/test-reports/*.xml
    paths:
      - $CI_PROJECT_DIR/features/test-reports/*.xml

Notez les options -D dans la ligne de commande, auxquelles on accède via les userdata dans nos fixtures.

Nous pouvons maintenant aborder le premier scénario, avec le titre Log on. La procédure pour mettre en œuvre les autres scénarios est la même. L’implémentation se déroule comme suit :

faasd_steps.py

@given(
    u'a jelastic environment with a docker node is available in group \'{node_group}\' with image \'{docker_image}\'')
def step_impl(context, node_group, docker_image):
    node_type = 'docker'
    env = EnvSettings(shortdomain=context.current_env_name)
    docker_settings = DockerSettings(image=docker_image, nodeGroup=node_group)
    node = NodeSettings(docker=docker_settings,
                        flexibleCloudlets=16, nodeType=node_type)
    created_env_info = context.control_client.create_environment(env, [node])
    assert created_env_info.is_running()
@given(u'the faas engine is installed')
def step_impl(context):
    context.jps_client.install(
        context.serverless_manifest, context.current_env_name)
    context.current_env_info = context.control_client.get_env_info(
        context.current_env_name)
    faas_node_ip = context.current_env_info.get_node_ips(
        node_type=faas_node_type, node_group=faas_node_group)[0]
    assert host_has_port_open(faas_node_ip, context.faas_port)
@when(u'a user logs on the faas engine')
def step_impl(context):
    faas_node_ip = context.current_env_info.get_node_ips(
        node_type=faas_node_type, node_group=faas_node_group)[0]
    username = context.file_client.read(
        context.current_env_name,
        '/var/lib/faasd/secrets/basic-auth-user',
        node_type=faas_node_type,
        node_group=faas_node_group)
    password = context.file_client.read(
        context.current_env_name,
        '/var/lib/faasd/secrets/basic-auth-password',
        node_type=faas_node_type,
        node_group=faas_node_group)
    faas_client = FaasClient(
        gateway_url=faas_node_ip,
        gateway_port=context.faas_port)
    context.exit_code = faas_client.login(username, password)
@then(u'she gets a success response')
def step_impl(context):
    assert context.exit_code == 0

Je ne vais pas donner la définition de tout ici car cela prendrait trop de temps de tout expliquer. J’espère que le code est auto-explicatif et que son intention est claire. Dans la première étape donnée, nous utilisons notre client API Jelastic pour créer un nouvel environnement Jelastic avec un nœud docker avec l’image docker spécifiée dans le groupe de nœuds Jelastic spécifié. La deuxième étape utilise l’API Jelastic marketplace.jps pour installer notre manifeste faasd et attendre que le nœud faasd ait un port 8080 ouvert, ce qui sera nécessaire pour toutes les opérations suivantes. L’étape when utilise notre FaasClient maison pour se connecter à faasd. Le FaasClient est essentiellement une enveloppe de shell sur l’exécutable faas-cli. Enfin, l’étape then vérifie que la connexion a réussi. Vous trouverez tous les détails sur notre dépôt public. Le code source de notre client API Jelastic est également open-source, comme vous pouvez le voir ici. Avec les définitions des étapes ci-dessus en place, nous avons lié notre spécification en anglais simple avec le code python et l’avons rendu vivant.

Conclusion

J’espère avoir réussi à éveiller votre curiosité pour les tests d’acceptation et à vous motiver à faire vos premiers pas vers une bonne documentation vivante de vos manifestes Jelastic. Bien sûr, en plus de tous les avantages de la documentation vivante, il y a des inconvénients. La rédaction de la documentation est une charge supplémentaire. L’écriture de bonnes spécifications nécessite de l’exercice et vous devez produire du code de test. De plus, l’exemple que j’ai présenté ici teste la création de l’environnement Jelastic, qui est très lente, ce qui rend vos tests très lents. Il existe cependant des moyens d’optimiser un peu. Par exemple, vous pourriez essayer d’utiliser une version du comportement supportant la concurrence, mais il n’y a rien d’officiellement supporté pour le moment (seulement une demande de pull en attente sur github). Vous pouvez également exécuter vos tests sur des environnements pré-créés. Vous pouvez définir des balises de niveau fonctionnalité qui appliqueront des fixtures de niveau fonctionnalité qui créeront les environnements Jelastic pertinents pour une fonctionnalité une fois pour toutes. Les scénarios de cette fonctionnalité s’exécuteront tous sur les environnements Jelastic préconfigurés.

Écrit par

Laurent MICHEL

03/02/2022

Product owner chez Softozor et client Hidora depuis 2017, utilisant le PaaS jelastic et le ci/cd géré gitlab pour réduire les frais généraux d’infrastructure de leur plateforme e-commerce.

Commencez votre essai gratuit

Pas de carte de crédit requise. Essai de 14 jours gratuit.

Nous utilisons vos données personnelles uniquement pour créer votre compte, promis !

Choisissez votre devise

chf
eur

Lire plus d’articles

bg

Recevoir nos actualités