Stage LP DI Madjid Bouchair SERFA, CDS

VOSpace : API Web REST en Python

Encadré par : André Schaaff

Au cours du mois de février, j'ai abordé plusieurs problématiques autour de l'API VOSpace :

Analyse d'un prototype d'API VOSpace en Java

Un prototype d'API VOSpace Java avait déjà été réalisée au sein du CDS, André m'a donné accès au repository git afin que je puisse voir comment elle a été conçu.

Cette API utilise le databinding avec la librairie JaXB permettant une automatisation de la génération des classes nécessaires au serveur. J'ai pu m'inspirer de la structure générale de l'API.

Comment exploiter le fichier XML de la requête (parsing/instance Python)

La question était de savoir comment traiter le fichier XML envoyé avec la requête et quelle librairie utiliser.

J'ai d'abord chercher un moyen de "databinder" le fichier à l'aide d'une libraire inspirée par JaXB, PyXB, qui aurait permit d'avoir une option de validation des réponses XML.

http://pyxb.sourceforge.net/

Cette librairie permet un databinding depuis un schéma XML, créant un script contenant classes et méthodes directement à partir du schéma de manière "pythonic".

Elle permet aussi la génération d'instance Python à partir d'un fichier XML (createFromDocument) ou d'un DOM (createFromDOM) ainsi que la validation par schéma.

instance = VOSpace.CreateFromDocument(open(file).read())

J'ai rencontré des difficultés dans l'utilisation de cette librairie dans le cas de l'exploitation de l'instance pour obtenir les informations nécessaires au service :

Dans une premier temps, j'ai généré un fichier python à partir du schéma par databinding. Ce fichier permettait la création d'une instance manipulable à souhait et de reproduire un XML validé par schéma.

J'ai alors utiliser ce fichier pour extraire les informations d'une requête XML (pushFromVoSpace)

<vos:transfer xmlns:vos="http://www.ivoa.net/xml/VOSpace/v2.1">
  <vos:target>vos://example.com!vospace/mydata1</vos:target>
  <vos:direction>pushFromVoSpace</vos:direction>
  <vos:protocol uri="ivo://ivoa.net/vospace/core#httpput">
    <vos:endpoint>http://anotherserver1.example.com/put/the/data/here</vos:endpoint>
  </vos:protocol>
  <vos:protocol uri="ivo://ivoa.net/vospace/core#httpput">
    <vos:endpoint>http://anotherserver2.example.com/put/the/data/here</vos:endpoint>
  </vos:protocol>
</vos:transfer>

IN

import VOSpace #importe le fichier généré depuis le schéma
instance = VOSpace.CreateFromDocument(open(file).read())
print(instance)

OUT

<VOSpace.Transfer object at 0x7f99ca3bd9e8>

IN

print(instance.direction)

OUT

pushFromVoSpace

IN

print(instance.protocol)

OUT

[<VOSpace.Protocol object at 0x7f99c9add1d0>, <VOSpace.Protocol object at 0x7f99c9add4a8>]

IN

for items in instance.protocol:
    print(items.uri, ":", items.endpoint)

OUT

ivo://ivoa.net/vospace/core#httpput : http://anotherserver1.example.com/put/the/data/here
ivo://ivoa.net/vospace/core#httpput : http://anotherserver2.example.com/put/the/data/here

IN

print(instance.target)

OUT

vos://example.com!vospace/mydata1

IN

print(instance.keepBytes)

OUT

None

Création de classes et méthodes à partir d'un schéma :

Cependant :

Face à la problématique du manque de documentation et de l'unique contributeur, j'ai choisi, après une semaine de tests, de mettre de côté cette librairie et rechercher une option plus pérenne :

Après une période de recherche d'alternative directe à PyXB et de tests de librairie de parsing XML, j'ai opté pour la librairie intégrée à Python nommée ElementTree, qui permet de parser une fichier XML sous la forme d'une dictionnaire Python.

Elle a une documentation très complète, est utilisée de manière très importante par la communauté Python, est très simple à prendre en main et les forums offrent beaucoup d'aide.

https://docs.python.org/3.6/library/xml.etree.elementtree.html

La navigation dans ce dictionnaire est alors possible comme dans n'importe quel dictionnaire Python, l'identification et récupération des tags, attributs, namespaces devenant très facile et rapide.

Exemple avec le même fichier XML que pour PyXB

IN

import xml.etree.ElementTree as ET
tree = ET.parse(file)
root = tree.getroot()

print(root.tag, root.attrib)
print("---")
for childs in root:
    print(childs.tag, childs.attrib, childs.text)
    for subchilds in childs:
        print(subchilds.tag, subchilds.attrib, subchilds.text)

OUT

{http://www.ivoa.net/xml/VOSpace/v2.1}transfer {}
---
{http://www.ivoa.net/xml/VOSpace/v2.1}target {} vos://example.com!vospace/mydata1
{http://www.ivoa.net/xml/VOSpace/v2.1}direction {} pushFromVoSpace
{http://www.ivoa.net/xml/VOSpace/v2.1}protocol {'uri': 'ivo://ivoa.net/vospace/core#httpput'} 

{http://www.ivoa.net/xml/VOSpace/v2.1}endpoint {} http://anotherserver1.example.com/put/the/data/here
{http://www.ivoa.net/xml/VOSpace/v2.1}protocol {'uri': 'ivo://ivoa.net/vospace/core#httpput'} 

{http://www.ivoa.net/xml/VOSpace/v2.1}endpoint {} http://anotherserver2.example.com/put/the/data/here

Mais contrairement à l'instance générée par PyXB, l'intégralité des méthodes devaient être écrite à la main. A partir de ce dictionnaire, j'ai testé plusieurs méthodes parcourant le dictionnaire afin d'isoler les informations nécessaires à l’interprétation de la requête dans un fichier test.

Exploration du file system Ubuntu

Travaillant sur un file system Ubuntu pour la conception avant de créer un serveur web, j'ai créé une arborescence de dossiers et de fichiers afin d'effectuer mes tests. L'implémentation finale du service doit cependant pouvoir se réaliser sur tout type de serveur (file system avec arborescence, à plat, Irods...). Le dictionnaire généré avec ElementTree permet l'identification de la node cible et l'action à réaliser, j'ai d'abord utilisé une fichier XML correspondant à un pushToVoSpace puis j'ai effectué des tests avec getNode.

La première étape après l'identification de la commande envoyé était de trouver la node cible sur le disque, pour ce faire j'ai utilisé la librairie os intégrée à Python, qui permet d'obtenir des informations de la part du système d'exploitation.

Tout d'abord il fallait trouver le dossier correspondant à la node (donnant le endpoint pour les requêtes de type pushToVoSpace), ensuite lister les fichiers enfants de ce dossier et en déterminer le type, dossier, fichiers, blob (offrant les informations nécessaires à la requête getNode).

Les metadata associées à la node seront aussi récupérées.

Génération d'une réponse XML adéquate

La génération de réponse XML est primordiale car elle informe le client du déroulement de chaque étape et elle doit être conforme au protocole VOSpace afin de pouvoir être facilement interprétée par le client.

L'idée derrière l'utilisation de PyXB était de pouvoir générer cette réponse à l'aide des classes et méthodes créées par le databiding, et d'obtenir une validation par le schéma. La librairie ElementTree permet de générer des XML de façon efficace. J'ai effectué mes premiers essais sur une requête pushToVoSpace tiré de la documentation.

Le XML de la requête était parsé et vérifié comme étant bien un pushToVoSpace, une recherche sur le file system Linux se faisait afin de retourner des endpoint (ici, les path vers les fichiers). Une fois les endpoints acquis, la génération de réponse était simple.

La requête getNode entraîna plus de difficulté, déjà dans la récupération de la node et de ses enfants et ensuite dans la détection du type des enfants en suivant le protocole VOSpace. En effet, la distinction entre UnstructuredDataNode et StructuredDataNode n'était pas vraiment clair pour moi. Après quelques échanges avec Ibrahim et Pierre Fernique, la chose est devenue plus claire.

Après plusieurs essais, j'ai réussi à produire un XML de réponse "synchrone" conforme au protocole.

Exemple de retour pour pushToVoSpace

<?xml version="1.0" ?>
<vos:transfer xmlns:vos="http://www.ivoa.net/xml/VOSpace/v2.1">
    <vos:target>vos://example.com!vospace/myresult4</vos:target>
    <vos:direction>pushToVoSpace</vos:direction>
    <vos:protocol uri="ivo://ivoa.net/vospace/core#httpput">
        <vos:endpoint>/VOTest/VOSpace/nodes/myresult4</vos:endpoint>
    </vos:protocol>
</vos:transfer>

getNode

<?xml version="1.0" ?>
<vos:node uri="/VOTest/VOSpace/nodes/myresult1/" xmlns:vos="http://www.ivoa.net/xml/VOSpace/v2.1" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xs:type="vos:ContainerNode">
    <vos:properties>
        <vos:property uri="ivo://ivoa.net/vospace/core#description">My award winning images</vos:property>
        <vos:property uri="ivo://ivoa.net/vospace/core#title">The Hitchhiker's Guide to the Galaxy</vos:property>
    </vos:properties>
    <vos:accept>
        <vos:view uri="ivo://ivoa.net/vospace/core#anyview"/>
    </vos:accept>
    <vos:provide>
        <vos:view uri="ivo://ivoa/net/vospace/core#defaultview"/>
    </vos:provide>
    <vos:capabilities/>
    <vos:nodes>
        <vos:node uri="/VOTest/VOSpace/nodes/myresult1/property.txt" xs:type="UnstructuredDataNode"/>
        <vos:node uri="/VOTest/VOSpace/nodes/myresult1/Capability" xs:type="ContainerNode"/>
        <vos:node uri="/VOTest/VOSpace/nodes/myresult1/myresult1.txt" xs:type="UnstructuredDataNode"/>
        <vos:node uri="/VOTest/VOSpace/nodes/myresult1/view_accept.txt" xs:type="UnstructuredDataNode"/>
        <vos:node uri="/VOTest/VOSpace/nodes/myresult1/view_provide.txt" xs:type="UnstructuredDataNode"/>
    </vos:nodes>
</vos:node>

Retour d'Ibrahim sur les besoins côté client

J'ai prit le temps d'aller voir Ibrahim qui travail sur l'implémentation de VOSpace dans Aladin pour connaître le point de vu client afin d'avoir une meilleure vision du fonctionnement du service ainsi qu'avoir quelques scénarios d'utilisation concret.

Cet échange a permit de clarifier quelques questions telles que la différence entre UnstructuredDataNode et StructuredDataNode, les use cases les plus communs, les fonctions prioritaires pour qu'Ibrahim puisse effectuer des tests sur l'API.

Il m'a aussi présenté les VOSpace de CANFAR (Canada) et ESO/ESAC (Espagne) et m'a fait par des soucis qu'il rencontrait tels que les bugs ou encore les méthodes non implémentées qui ne lui permettaient pas d'effectuer l'ensemble des tests qu'il souhaitait. Il m'a aussi donné sa vision sur certains aspects de la conception de l'API et ce qu'il aimerait pouvoir utiliser au plus vite, m'aidant à prioriser le développement.

Il a aussi été présent lorsque des questions de conception me posaient problème afin de me guider au mieux qu'il pouvait vers une solution pertinente.

Par quel moyen assurer la persistance du service

André et moi avons eu des échanges au sujet des moyens à trouver pour rendre le service plus robuste. Il m'a expliqué schématiquement le fonctionnement du système de donnée Irods et m'a aussi donné des axes de réflexion.

L'idée est de faire en sorte qu'en cas de problème de type crash ou erreur au niveau des fichiers, l'API reste utilisable. Ceci peut-être réalisé de la manière suivante : Au lancement, le service interrogera une base de donnée, si celle-ci est vide deux possibilités sont présentes :

  1. Il s'agit du premier lancement du service
  2. Un crash est survenu

Dans les deux cas, le service demandera un mappage des fichiers et des métadonnées au serveur afin de générer les endpoints et s'assurer que les nodes sont présentes avec l'intégralité de leurs fichiers. Dans le cas contraire, la node sera signalé inaccessible et un log sera envoyé au serveur pour qu'une vérification ai lieu.

A cela se greffera des fichiers JSON représentant chaque node et leurs enfants ainsi que les métadonnées. Ce fichier JSON sera utilisé afin de limiter le nombre d'accès à la BDD et assurer des réponses rapides au client.

Scénario de lancement

apiStartup

Cette vérification sera aussi faite de façon routinière lors de l'utilisation du service.

logo_cds logo_serfa