0 Abonnés · 4 Publications

Microsoft Azure est un service de cloud computing créé par Microsoft pour développer, tester, déployer et gérer des applications et des services via des centres de données gérés par Microsoft.

En savoir plus.

Article Iryna Mykhailova · Oct 9, 2024 4m read

L'accès à un stockage cloud Azure pour charger/télécharger des blobs est assez simple à l'aide des méthodes API de classe %Net.Cloud.Storage.Client désignées ou des adaptateurs entrants/sortants EnsLib.CloudStorage.*.

Notez que vous devez avoir le serveur de %JavaServer External Language opérationnel pour utiliser l'API ou les adaptateurs de stockage cloud, car ils utilisent tous deux le framework PEX à l'aide du serveur Java.

Voici un bref résumé :

L'accès à Azure Blob Storage s'effectue à l'aide d'une chaîne de connexion qui ressemble à celle-ci :

0
0 91
Article Sylvain Guilbaud · Mars 27, 2024 8m read

Si vous exécutez IRIS dans une configuration miroir pour HA dans Azure, la question de la fourniture de Mirror VIP (adresse IP virtuelle) devient pertinente. L'adresse IP virtuel permet aux systèmes en aval d'interagir avec IRIS en utilisant une seule adresse IP. Même en cas de basculement, les systèmes en aval peuvent se reconnecter à la même adresse IP et continuer à fonctionner.

Le principal problème, lors du déploiement sur Azure, est qu'un VIP IRIS doit être essentiellement un administrateur de réseau, conformément aux docs.

Pour obtenir l'HA, les membres du miroir IRIS doivent être déployés dans différentes zones de disponibilité d'un sous-réseau (ce qui est possible dans Azure car les sous-réseaux peuvent s'étendre sur plusieurs zones). L'une des solutions pourrait être les équilibreurs de charge, mais ils coûtent bien sûr plus cher et nécessitent d'être administrés.

Dans cet article, j'aimerais fournir un moyen de configurer un VIP miroir sans utiliser les équilibreurs de charge suggérés dans la plupart des autres architectures de référence Azure.

Architecture

Architecture

Nous avons un sous-réseau qui s'étend sur deux zones de disponibilité (je simplifie ici - bien sûr, vous aurez probablement des sous-réseaux publics, un arbitre dans une autre zone, et ainsi de suite, mais il s'agit d'un minimum absolu suffisant pour démontrer cette approche). La notation CIDR du sous-réseau est 10.0.0.0/24, ce qui signifie que les adresses IP 10.0.0.1 à 10.0.0.255 lui sont allouées. Puisque Azure réserve les quatre premières adresses et la dernière adresse, nous pouvons utiliser 10.0.0.4 à 10.0.0.254.

Nous mettrons en œuvre des VIP publics et privés en même temps. Si vous voulez, vous pouvez implémenter uniquement le VIP privé.

Idée

Les machines virtuelles dans Azure ont des Interfaces réseau. Ces interfaces réseau ont des Configurations IP. La configuration IP est une combinaison d'IP publiques et/ou privées, et elle est acheminée automatiquement vers la Machine virtuelle associée à l'Interface réseau. Il n'est donc pas nécessaire de mettre à jour les routes. Lors d'un basculement de miroir, nous allons supprimer la configuration IP VIP de l'ancien primaire et la créer pour un nouveau primaire. Toutes les opérations nécessaires à cette fin prennent de 5 à 20 secondes pour une IP VIP privée uniquement, de 5 secondes à une minute pour une combinaison d'IP VIP publique/privée.

Mise en œuvre de VIP

  1. Allocation de l'IP externe à utiliser en tant que VIP public. Ignorez cette étape si vous souhaitez uniquement un VIP privé. Si vous attribuez le VIP, il doit résider dans le même groupe de ressources et dans la même région et se trouver dans toutes les zones avec le primaire et le backup. Vous aurez besoin d'un nom IP externe.
  2. Choisissez une valeur VIP privée. J'utiliserai la dernière adresse IP disponible '10.0.0.254'
  3. Sur chaque machine virtuelle, attribuez l'adresse IP VIP privée sur l'interface réseau eth0:1.
cat << EOFVIP >> /etc/sysconfig/network-scripts/ifcfg-eth0:1
          DEVICE=eth0:1
          ONPARENT=on
          IPADDR=10.0.0.254
          PREFIX=32
          EOFVIP
sudo chmod -x /etc/sysconfig/network-scripts/ifcfg-eth0:1
sudo ifconfig eth0:1 up

Si vous voulez juste faire un test, exécutez-le (mais il ne survivra pas au redémarrage du système) :

sudo ifconfig eth0:1 10.0.0.254

Selon le système d'exploitation que vous devrez peut-être exécuter:

ifconfig eth0:1
systemctl restart network
  1. Pour chaque machine virtuelle, activez Identité attribuée au système ou à l'utilisateur.
  2. Pour chaque identité, attribuez les autorisations de modifier les interfaces réseau. Pour ce faire, créez un rôle personnalisé. Dans ce cas, les autorisations minimales sont les suivantes :
{
  "roleName": "custom_nic_write",
  "description": "IRIS Role to assign VIP",
  "assignableScopes": [
    "/subscriptions/{subscriptionid}/resourceGroups/{resourcegroupid}/providers/Microsoft.Network/networkInterfaces/{nicid_primary}",
    "/subscriptions/{subscriptionid}/resourceGroups/{resourcegroupid}/providers/Microsoft.Network/networkInterfaces/{nicid_backup}"
  ],
  "permissions": [
    {
      "actions": [
        "Microsoft.Network/networkInterfaces/write",
        "Microsoft.Network/networkInterfaces/read"
      ],
      "notActions": [],
      "dataActions": [],
      "notDataActions": []
    }
  ]
}

Pour les environnements non productifs, vous pouvez utiliser un rôle système Contributeur réseau sur le groupe de ressources, mais ce n'est pas une approche recommandée car Contributeur réseau est un rôle très large.

  1. Chaque interface réseau dans Azure peut avoir un ensemble de configurations IP. Lorsqu'un membre miroir actuel devient primaire, nous utilisons un callback ZMIRROR pour supprimer une configuration IP VIP sur l'interface réseau d'un autre membre miroir et créer une configuration IP VIP pointant sur lui-même :

Voici les commandes Azure CLI pour les deux nœuds en supposant le groupe de ressources rg, la configuration IP vip et l'IP externe my_vip_ip :

az login --identity
az network nic ip-config delete --resource-group rg --name vip --nic-name mirrorb280_z2
az network nic ip-config create --resource-group rg --name vip --nic-name mirrora290_z1 --private-ip-address 10.0.0.254 --public-ip-address my_vip_ip

et

az login --identity
az network nic ip-config delete --resource-group rg --name vip --nic-name mirrora290_z1
az network nic ip-config create --resource-group rg --name vip --nic-name mirrorb280_z2 --private-ip-address 10.0.0.254 --public-ip-address my_vip_ip

Et le même code qu'une routine ZMIRROR :

ROUTINE ZMIRROR

NotifyBecomePrimary() PUBLIC {
    #include %occMessages
    set rg = "rg"
    set config = "vip"
    set privateVIP = "10.0.0.254"
    set publicVIP = "my_vip_ip"

    set nic = "mirrora290_z1"
    set otherNIC = "mirrorb280_z2"
    if ##class(SYS.Mirror).DefaultSystemName() [ "MIRRORB" {
        // we are on mirrorb node, swap
        set $lb(nic, otherNIC)=$lb(otherNIC, nic)
    }

    set rc1 = $zf(-100, "/SHELL", "export", "AZURE_CONFIG_DIR=/tmp", "&&", "az", "login", "--identity")
    set rc2 = $zf(-100, "/SHELL", "export", "AZURE_CONFIG_DIR=/tmp", "&&", "az", "network", "nic", "ip-config", "delete", "--resource-group", rg, "--name", config, "--nic-name", otherNIC)
    set rc3 = $zf(-100, "/SHELL", "export", "AZURE_CONFIG_DIR=/tmp", "&&", "az", "network", "nic", "ip-config", "create", "--resource-group", rg, "--name", config, "--nic-name",      nic,  "--private-ip-address", privateVIP, "--public-ip-address", publicVIP)
    quit 1
}

La routine est la même pour les deux membres du miroir, nous échangeons simplement les noms des cartes réseau en fonction du nom du membre du miroir actuel. Vous pourriez ne pas avoir besoin du paramètre export AZURE_CONFIG_DIR=/tmp, mais parfois az cherche à écrire les informations d'identification dans le répertoire personnel de la racine, ce qui peut échouer. Au lieu de /tmp, il est préférable d'utiliser le sous-répertoire personnel de l'utilisateur d'IRIS (ou vous pouvez même ne pas avoir besoin de cette variable d'environnement, en fonction de votre configuration).

Et si vous voulez utiliser Embedded Python, voici le code Azure Python SDK:

from azure.identity import DefaultAzureCredential
from azure.mgmt.network import NetworkManagementClient
from azure.mgmt.network.models import NetworkInterface, NetworkInterfaceIPConfiguration, PublicIPAddress

sub_id = "AZURE_SUBSCRIPTION_ID"
client = NetworkManagementClient(credential=DefaultAzureCredential(), subscription_id=sub_id)

resource_group_name = "rg"
nic_name = "mirrora290_z1"
other_nic_name = "mirrorb280_z2"
public_ip_address_name = "my_vip_ip"
private_ip_address = "10.0.0.254"
vip_configuration_name = "vip"


# remove old VIP configuration
nic: NetworkInterface = client.network_interfaces.get(resource_group_name, other_nic_name)
ip_configurations_old_length = len(nic.ip_configurations)
nic.ip_configurations[:] = [ip_configuration for ip_configuration in nic.ip_configurations if
                            ip_configuration.name != vip_configuration_name]

if ip_configurations_old_length != len(nic.ip_configurations):
    poller = client.network_interfaces.begin_create_or_update(
        resource_group_name,
        other_nic_name,
        nic
    )
    nic_info = poller.result()

# add new VIP configuration
nic: NetworkInterface = client.network_interfaces.get(resource_group_name, nic_name)
ip: PublicIPAddress = client.public_ip_addresses.get(resource_group_name, public_ip_address_name)
vip = NetworkInterfaceIPConfiguration(name=vip_configuration_name,
                                      private_ip_address=private_ip_address,
                                      private_ip_allocation_method="Static",
                                      public_ip_address=ip,
                                      subnet=nic.ip_configurations[0].subnet)
nic.ip_configurations.append(vip)

poller = client.network_interfaces.begin_create_or_update(
    resource_group_name,
    nic_name,
    nic
)
nic_info = poller.result()

Lancement initial

NotifyBecomePrimary est aussi appelé automatiquement au démarrage du système (après la reconnexion de miroirs), mais si vous voulez que vos environnements non-miroirs acquièrent VIP de la même manière, utilisez la routine ZSTART:

SYSTEM() PUBLIC {
  if '$SYSTEM.Mirror.IsMember() {
    do NotifyBecomePrimary^ZMIRROR()
  }
  quit 1
}

Conclusion

Et c'est tout! Nous changeons la configuration IP qui pointe vers un miroir primaire actuel lorsque l'événement NotifyBecomePrimary se produit.

0
0 126
Article Lorenzo Scalese · Mai 31, 2023 5m read

Les systèmes de bases de données ont des exigences de sauvegarde très spécifiques qui, dans les déploiements d'entreprise, nécessitent une réflexion et une planification préalables. Pour les systèmes de bases de données, l'objectif opérationnel d'une solution de sauvegarde est de créer une copie des données dans un état équivalent à celui de l'arrêt de l'application en douceur.  Les sauvegardes cohérentes avec les applications répondent à ces exigences et Caché fournit un ensemble d'API qui facilitent l'intégration avec des solutions externes pour atteindre ce niveau de cohérence des sauvegardes.

Ces API sont ExternalFreeze et ExternalThaw. ExternalFreeze met temporairement en pause les écritures sur le disque et pendant cette période, Caché commet les changements en mémoire. Pendant cette période, l'opération de sauvegarde doit se terminer et être suivie d'un appel à ExternalThaw. Cet appel engage les démons d'écriture à écrire sur le disque la mise à jour du pool tampon global (cache de la base de données) et reprend les opérations normales des démons d'écriture de la base de données Caché. Ce processus est transparent pour les processus utilisateur de Caché.  Les méthodes spécifiques de la classe API sont les suivantes :

##Class(Backup.General).ExternalFreeze()

##Class(Backup.General).ExternalThaw()

Ces API, associées à la nouvelle capacité d'Azure Backup à exécuter un script avant et après l'exécution d'une opération d'instantané, fournissent une solution de sauvegarde complète pour les déploiements de Caché sur Azure. La [capacité de script pré/post d'Azure Backup] (https://azure.microsoft.com/en-us/blog/announcing-application-consistent-backup-for-linux-vms-using-azure-backup) est actuellement disponible uniquement sur les VM Linux.

Conditions préalables

Au niveau le plus élevé, vous devez effectuer trois étapes avant de pouvoir sauvegarder une VM à l'aide d'Azure Backup:

  1. Create a Recovery Services vault
  2. Install has the latest version of the VM Agent.
  3. Check network access to the Azure services from your VM. 

L'espace de stockage Recovery Services gère les objectifs de sauvegarde, les politiques et les éléments à protéger. Vous pouvez créer une voûte de Recovery Services via le portail Azure ou via un script utilisant PowerShell.  Azure Backup nécessite une extension qui s'exécute dans votre VM, est contrôlée par l'agent Linux VM et la dernière version de l'agent est également nécessaire.  L'extension interagit avec les points d'extrémité HTTPS externes d'Azure Storage et de la voûte Recovery Services.  L'accès sécurisé à ces services depuis la VM peut être configuré à l'aide d'un proxy et de règles réseau dans un groupe de sécurité Azure Network Security Group. 

Vous trouverez plus d'informations sur ces étapes dans la section Préparez votre environnement pour sauvegarder les machines virtuelles déployées par Resource Manager.

La Configuration du pré-scripting et post-scripting

La possibilité d'appeler un script avant et après l'opération de sauvegarde est incluse dans la dernière version de l'extension Azure Backup (Microsoft.Azure.RecoveryServices.VMSnapshotLinux). Pour plus d'informations sur l'installation de l'extension, veuillez consulter [la documentation détaillée des fonctionnalités] (https://docs.microsoft.com/en-us/azure/backup/backup-azure-linux-app-consistent).

Par défaut, l'extension inclut des exemples de pré-scripts et post-scripts situés dans votre VM Linux à l'adresse suivante :

/var/lib/waagent/Microsoft.Azure.RecoveryServices.VMSnapshotLinux-1.0.9110.0/main/tempPlugin

Et doit être copié aux emplacements suivants respectivement.

/etc/azure/prescript.sh
/etc/azure/postScript.sh

Vous pouvez également télécharger le modèle de script à partir de GitHub.

Pour Caché, le script prescript.sh où un appel à l'API ExternalFreeze peut être implémenté et le postScript.sh doivent contenir l'implémentation qui exécute ExternalThaw.

Voici un exemple d'implémentation de prescript.sh pour Caché.

#!/bin/bash
# les variables utilisées pour retourner l'état du script
success=0
error=1
warning=2
status=$success
log_path="/etc/preScript.log"#path of log file
printf  "Logs:\n" > $log_path# TODO: Replace &lt;CACHE INSTANCE> with the name of the running instance
csession &lt;CACHE INSTANCE> -U%SYS "##Class(Backup.General).ExternalFreeze()" >> $log_path
status=$?if [ $status -eq 5 ]; then
echo "SYSTEM IS FROZEN"
printf  "SYSTEM IS FROZEN\n" >> $log_pathelif [ $status -eq 3 ]; then
echo "SYSTEM FREEZE FAILED"
printf  "SYSTEM FREEZE FAILED\n" >> $log_path
status=$error
csession &lt;CACHE INSTANCE> -U%SYS "##Class(Backup.General).ExternalThaw()"
fi

exit $status

Voici un exemple d'implémentation de postScript.sh pour Caché.

#!/bin/bash
# les variables utilisées pour retourner l'état du script
success=0
error=1
warning=2
status=$success
log_path="/etc/postScript.log"#path of log file
printf  "Logs:\n" > $log_path# TODO: Replace &lt;CACHE INSTANCE> with the name of the running instance
csession &lt;CACHE INSTANCE> -U%SYS "##class(Backup.General).ExternalThaw()"
status=$?
if [ $status req 5]; then
echo "SYSTEM IS UNFROZEN"
printf  "SYSTEM IS UNFROZEN\n" >> $log_pathelif [ $status -eq 3 ]; then
echo "SYSTEM UNFREEZE FAILED"
printf  "SYSTEM UNFREEZE FAILED\n" >> $log_path
status=$error
fi
exit $status

Exécution d'une sauvegarde

Dans le portail Azure, vous pouvez déclencher la première sauvegarde en naviguant vers le service de restauration. Veuillez considérer que le temps d'instantané de la VM devrait être de quelques secondes indépendamment de la première sauvegarde ou des sauvegardes suivantes. Le transfert de données de la première sauvegarde prendra plus de temps mais le transfert de données commencera après l'exécution du post-script pour dégeler la base de données et ne devrait pas avoir d'impact sur le temps entre le pré-script et le post-script.

Il est fortement recommandé de restaurer régulièrement votre sauvegarde dans un environnement de non-production et [d'effectuer des contrôles d'intégrité de la base de données] (http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GCDI_integrity#GCDI_integrity_verify_utility) pour garantir l'efficacité de vos opérations de protection des données.

Pour plus d'informations sur le déclenchement de la sauvegarde et d'autres sujets tels que la planification de la sauvegarde, veuillez consulter Sauvegarde des machines virtuelles Azure dans une voûte Recovery Services.  

0
0 60
Article Lorenzo Scalese · Oct 14, 2022 6m read

Présentation générale

Il y a trois ans, nous avons commencé à utiliser Azure Service Bus (ASB) comme solution de messagerie d'entreprise. Elle est utilisée pour publier et consommer des données entre de nombreuses applications de l'entreprise. Comme le flux de données est complexe et les données d'une application sont généralement nécessaires à plusieurs applications, le modèle "éditeur" ---> "abonnés multiples" était parfaitement adapté. L'utilisation d'ASB dans l'organisation donne des dizaines de millions de messages par jour, tandis que la plate-forme IRIS a environ 2-3 millions de messages par jour.

Le problème d'ASB

Lorsque nous avons commencé l'intégration d'ASB, nous avons constaté que le protocole AMQP n'est pas " prêt à l'emploi " pour le déploiement d'IRIS. Nous avons donc cherché une solution alternative pour pouvoir communiquer avec l'ASB.

La solution

Nous avons développé un service Windows local (en .NET et Swagger) qui effectuait la communication réelle avec l'ASB. Il était installé sur la même machine qu'IRIS. Nous avons envoyé les messages à ASB à ce service local (en utilisant : localhost et en faisant une API REST), et ce service a fait le reste. Cette solution a bien fonctionné pendant quelques années, mais il était très difficile de surveiller le trafic, de déboguer les messages "perdus" et de mesurer la performance globale. Le fait que nous avions ce service Windows local comme "un homme au milieu" n'est pas toujours la meilleure architecture.

Programme d'accès anticipé à IRIS Embedded Python (EAP)

On m'a demandé (ou on m'a proposé, je ne sais plus) de participer au programme de diffusion anticipée (ERP) pour le programme Embedded Python. C'était très excitant pour moi, de pouvoir tester de nouvelles fonctionnalités, de donner du feedback à l'équipe de développement. C'était également très agréable de participer activement au développement de ce produit et de l'influencer. À ce stade, nous avons commencé à tester le programme Embedded Python pour l'ERP et nous avons décidé de vérifier si nous pouvions élargir le programme Embedded Python pour résoudre des problèmes "réels". Prendre la "connectivité directe entre IRIS et ASB" dans ce but était tout à fait logique.

Le POC (preuve de concept**)**

Nous avons commencé à coder la solution et avons découvert que l'utilisation de la bibliothèque ASB de Microsoft pour Python nous faciliterait la vie (et le codage). L'étape initiale était de développer une fonction qui peut se connecter à un sujet spécifique dans ASB et recevoir des messages. Cela a été fait assez rapidement (1 jour de développement) et ensuite nous sommes passés à la phase de "publication" (envoi à ASB).

Quelques jours supplémentaires nous ont permis de développer une " enveloppe " complète pour ces fonctions d'envoi et de réception : nous avons construit ce qui suit :

  • Une "base de transit" adéquate pour contrôler le flux des messages entrants et sortants
  • Un mécanisme central pour stocker le trafic d'entrée et de sortie de l'ASB afin de pouvoir disposer de statistiques et de mesures de performance appropriées
  • Une page de surveillance du CSP pour activer/désactiver les services, montrer les statistiques et donner des alertes en cas de problème

La mise en œuvre

Configuration préalable

Avant d'utiliser les bibliothèques ASB de Microsoft pour Python, il est nécessaire de les installer dans un dossier dédié ..\python.
Nous avons choisi d'utiliser ../mge/python/ mais vous pouvez utiliser n'importe quel autre dossier (initialement c'était un dossier vide).
Ces commandes doivent être exécutées à partir d'un CMD élevé (sous Windows) ou en utilisant un utilisateur élevé (sous Linux) :

..\bin\irispip install --target ..\mgr\python asyncio

..\bin\irispip install --target ..\mgr\python azure.servicebus

Réception de messages à l'ASB (consommation)

Nous avons une méthode ClassMethod avec quelques paramètres :

  • Le topiId - L'ASB est divisée en thèmes à partir desquels vous consommez des messages
  • subscriptionName  - Nom de l'abonnement à Azure
  •  connectionString  - Pour pouvoir vous connecter à la rubrique à laquelle vous êtes abonné(e)

Notez que nous utilisons [ Language = python ] pour indiquer que cette ClassMethod est écrite en Python (!)

ClassMethod retrieveASB(topicId As %Integer = 1, topicName As %String = "", subscriptionName As %String = "", connectionString As %String = "", debug As %Integer = 1, deadLetter As %Integer = 0) As %Integer [ Language = python ]

Pour utiliser la bibliothèque ASB, nous devons d'abord importer les bibliothèques ServiceBusClient et ServiceBusSubQueue :

 d' azure.servicebus importer ServiceBusClient,ServiceBusSubQueue

pour pouvoir interagir avec IRIS (par exemple pour exécuter du code), nous devons également :

importer iris  

À ce stade, nous pouvons utiliser les bibliothèques ASB :

avec ServiceBusClient.from_connection_string(connectionString) en tant que client:

    avec client.get_subscription_receiver(topicName, subscriptionName, max_wait_time=1, sub_queue=subQueue) en tant que récepteur :

        pour msg dans récepteur:

À ce stade, nous avons un objet Python "msg" (flux) que nous pouvons transmettre (en tant que flux bien sûr) à IRIS et stocker directement dans la base de données :

result = iris.cls("anyIRIS.Class").StoreFrom Python(stream)

Envoi de messages à l'ASB (publication)

Nous avons une ClassMethod avec quelques paramètres :

  • topicName  - La rubrique (Topic) sur laquelle nous voulons publier (ici, nous devons passer le nom et non l'identifiant)
  • connectionString  - Pour pouvoir vous connecter à la rubrique à laquelle vous êtes abonné(e)
  • JSONmessage  - le message que nous voulons envoyer (publier)

Notez que nous utilisons [ Language = python ] pour indiquer que cette ClassMethod est écrite en Python (!)

ClassMethod publishASB(topicName As %String = "", connectionString As %String = "", JSONmessage As %String = "") As %Status [ Language = python ]

Pour utiliser la bibliothèque ASB, nous devons d'abord importer les bibliothèques ServiceBusClient et ServiceBusMessage :

from azure.servicebus import ServiceBusClient, ServiceBusMessage

L'utilisation des bibliothèques de l'ASB est alors très facile :

try:

        résultat=1

        avec ServiceBusClient.from_connection_string(connectionString) en tant que client:

            avec client.get_queue_sender(topicName) en tant qu'expéditeur:

                single_message = ServiceBusMessage(JSONmessage)

                sender.send_messages(single_message)        

    sauf Exception comme e:

        publier(e)

        résultat=0

    retourner le résultat

Avantages de l'utilisation de la connectivité directe de l'ASB

  • Beaucoup plus rapide que l'utilisation de l'"ancienne alternative" du "service local de Windows"
  • Facile à surveiller, à collecter des statistiques, à déboguer les problèmes
  • La mise hors service de l'"homme du milieu" (service local de Windows) réduit un "point de défaillance" potentiel
  • La possibilité de gérer automatiquement les "lettres mortes" pour toute rubrique et de faire en sorte que l'ASB renvoie ces messages à la rubrique de l'abonné

Références

Je tiens à remercier @David.Satorres6134 (notre développeur principal pour IRIS) pour son énorme contribution à la conception, au codage et aux tests. Sans son aide, ce projet n'aurait pas été possible.

0
0 99