10 minutes pour...comprendre OSGI

En regardant les statistiques du blog, j'ai vu que les visiteurs passaient environ 5 minutes sur le blog. Cela m'a donné l'idée de créer une rubrique qui s'appellerait '10 minutes pour...' et dans laquelle je publierai des posts un peu plus long où je traiterai d'un sujet un peu plus en profondeur. Pour inaugurer cette rubrique, je vous propose '1O minutes pour...comprendre OSGI'.

Introduction

Cela vous parait normal de ne pas redémarrer votre PC quand vous branchez une clé USB. Est-ce que redémarrer toute une application parce que vous avez ajouté un service ou une classe qui n'impacte qu'une infime partie de votre application vous choque ? Appréciez-vous les langages de script comme PHP, car leurs déploiements ne nécessitent pas le redémarrage d'Apache ? Voudriez-vous avoir cette souplesse en continuant à développer en java ? Avez-vous un conflit de versions entre deux librairies java ? Aimeriez-vous pouvoir utiliser différentes versions d'un service en même temps, au runtime, sans avoir à le coder? ou définir le cycle de vie d'un service en fonction de la présence ou non d'autres services tiers ? Si c'est le cas cet article va vous intéresser, (sinon restez quand me, LOL ;) ) !

Il y a deux ans, peu de personnes connaissaient le terme OSGI. Plus connu en électronique qu'en informatique, OSGI était utilisé sur des systèmes embarqués, dans le domaine de l'automobile (BMW, Volvo), des équipements réseaux (CISCO), de la domotique, et des alarmes.

La question qui se pose est "Pourquoi une technologie issue du monde de l'électronique se retrouve en informatique et quel est son intérêt ? ". Le but de ce post est de faire une présentation de OSGI, et d'en expliquer les concepts et les utilisations possibles.

OSGI est un ensemble de spécifications qui en est à sa version 4.0 (à l'heure de la rédaction de ce post) et qui a pour but de définir une plateforme pouvant être gérée dynamiquement avec un modèle orienté composants. Les composants appelés 'Bundle' peuvent être installés, démarrés, arrêtés, mis à jour, et désinstallés dynamiquement sans aucun redémarrage du framework OSGI. Les bundles peuvent être locaux ou distants et c'est le framework qui s'occupe de la gestion de leur cycle de vie. OSGI est basé sur un système de registres ('service registry') qui détecte les nouveaux services offerts par les bundles, et gère leur cycle de vie en fonction de l'installation, de l'arrêt et du démarrage de ces derniers.

OSGI définit une multitude de services :

  • Logs
  • Gestion de configuration
  • Préférences
  • Services HTTP (e.g :servlets)
  • Gestion des utilisateurs
  • Parseurs XML
  • Gestion de droits,
  • Politique de sécurité
  • Monitoring
  • ...

Ainsi que des services qui supportent le monde de l'électronique :

  • Gestion de périphériques
  • connecteurs d'entrés-sorties
  • Gestion du plug'n play (pnp et Upnp)
  • Gestion de l'alimentation et de l'énergie.

On devine tout de suite l'intérêt de pouvoir dynamiquement ajouter, démarrer et arrêter les services fournis par des bundles. On pourrait ainsi gérer les servlets comme le fait le manager de Tomcat pour les applications web (granularité plus fine), ajouter des services ou des périphériques à une Set Top Box (Freebox, liveBox), gérer un système de composants ou de plugins d'une manière transparente, en y ajoutant des services (du code) sans redémarrer. On ne parle alors plus d'application mais plutôt d'ensemble de services.

Eclipse et OSGI

Si vous êtes développeur, vous utilisez peut être Eclipse comme IDE. Eclipse depuis sa version 3.0 est basé sur OSGI avec un framework nommé Equinox. En effet lorsque vous ajoutez un plugin dans Eclipse : vous vous rendez dans le menu de gestion de plugins, et la vous pouvez :

  • Installer de nouvelles fonctionnalités à partir d'une URL ou d'un fichier
  • Choisir la version du plugin compatible avec votre version d'Eclipse
  • Connaître les différentes dépendances nécessaires pour que le plugin que vous installer puisse fonctionner

Une fois installé, vous pouvez le mettre à jour d'une manière automatique et l'activer ou les désactiver dynamiquement. Peut être qu'à force d'utiliser Eclipse cela vous parait normal mais cette gestion dynamique est très puissante et vous permet de rajouter aussi bien un menu, un icône, une fonctionnalité, ou une vue, d'une façon simple et centralisée. D'ailleurs avec Eclipse vous pouvez aussi bien développer du java, du C, C++, .net, ou du php, vous pouvez aussi faire une application de client lourd basé sur Eclipse RCP. bref derrière cette flexibilité, c'est de l'OSGI :)

Les frameworks

Tout comme Equinox, il existe d'autres implémentations d'OSGI. II en existe beaucoup, mais le marché commence à se stabiliser et on distingue quelques frameworks qui semblent devenir des références

  • Equinox : Le framework d'Eclipse. Il a l'avantage d'être le seul framework (à ma connaissance) certifié par l'OSGI alliance. Il n'est toutefois pas adapté pour une utilisation dans le domaine de l'électronique. La fondation Eclipse fournit également un repository avec des bundles de tierce partie très intéressants.
  • Felix : implémentation de la fondation Apache. .
  • knoplerfish : une implémentation mature et open source avec pas mal de bundles utiles
  • Oscar : implémentation de Object Web.

Il existe aussi d'autres frameworks payants comme microService Architecture (mSA) fourni par BEA ou IBM Lotus Expeditor d'IBM, mais nous nous focalisons ici sur les frameworks open sources et gratuits

Là où OSGI est utile c'est sur la compatibilité (théorique mais qui s'avère égale ou presque en pratique) entre les bundles. En effet il est tout à fait possible d'utiliser un bundle fournit par Equinox dans knoplerfish. (il est d'ailleurs très utile d'utiliser la console graphique de Knoplerfish, Felix, ne se résumant qu'à une simple console en ligne de commande).

interface knoplerfish

Spring OSGI

Le framework Spring devient de plus en plus populaire et pour ceux qui ne seraient pas encore familier avec Spring, il faut savoir que Spring est un framework basé sur le design pattern de l'inversion de controle (aka : IOC). Le but est de développer des objets totalement indépendants appelés 'bean' (ce ne sont pas des POJOs mais on les nomme ainsi à cause de leur nomenclature qui est basée sur celle des beans) et de se servir de Spring pour faire les assembler.

L'exemple le plus courant est par exemple un service où l'on injecte un bean pour la couche d'accès aux données (DAO : Data Acess Object). On peux alors créer une couche d'accès XML et une autre en base de données. Il suffit d'injecter l'une ou l'autre dans le service selon les besoins. L'indépendance des objets entre eux offre ainsi la possibilité de changer une implémentation sans tout devoir re-coder : il suffit de changer un fichier de configuration dans Spring. Il manque cependant une chose capitale dans Spring : la dynamique.

Si un service offert par un bean est indisponible, comment doivent réagir les beans qui utilisent ce service ? Est ce que le service peux encore fonctionner alors que le DAO n'est plus opérationnel ? Si le DAO n'est pas un singleton est-ce que le service peux en prendre un autre dans un pool ?. De plus toutes modifications du fichier de contexte nécessitent le redémarrage de Spring. Il n'est donc pas possible d'ajouter/ retirer des beans en cours d'exécution. C'est pour ces différentes raisons que Spring OSGI est né : il permet de cumuler les avantages de Spring et ceux de OSGI par l'aspect Dynamique.

Un peu plus dans la technique

Comme expliqué précédemment, OSGI utilise une Registre de Services. Ce registre permet aux Fournisseurs de services de pouvoir déclarer les services qu'ils exportent et leurs propriétés, et aux utilisateurs de ces services de pouvoir en connaitre les propriétés qui permettent de les différencier (version, langage, etc...) .

En effet les services apparaissent sous la forme d'interface et un utilisateur de service peut vouloir une version spécifique de ce service (V1.0, V2.0), mais également de l'implémentation (version française par exemple). Les spécificités des services sont définies avec la syntaxe LDAP.

Des mécanismes de notifications sont également disponibles (événements), ils permettent aux utilisateurs de services de connaitre les changements apportés aux services qu'ils utilisent (arrêt, désinstallation, etc) ou qu'ils voudraient utiliser (installation, démarrage, etc).

service registry osgi

Les fournisseurs de services sont des entités appelés 'Bundle'. Ils offrent des services définis par des interfaces et implémentés par les objets instanciés dans les bundles. Les bundles sont responsables de la publication dans le registre, de la découverte et de la mise en relation des services. Ils sont également responsables de l'adaptation dynamique de leurs services en fonction des arrivées et départs des services des autres bundles. Physiquement un bundle est matérialisé par un fichier JAR qui contient du code et des ressources (images, librairies, etc) et un fichier contenant des informations sur le bundle : le fichier MANIFEST.

Déploiement et cycle de vie d'un bundle

Les frameworks OSGI offrent la possibilité de déployer des bundles d'une manière continue (Continuous deploiement). Les différents états d'un bundles, ansi que son cycle de vie sont représentés par la figure ci dessous :

cycle de vie bundle osgi

Une fois installé, un bundle, peut passer dans l'état resolved si toutes les dépendances qu'il requiert sont satisfaites, il peut alors soit être mis à jour, soit être démarrer. Il passe alors dans l'état Active.

Dans l'état resolved un service est prêt et peut exporter ses services mais ne le fait pas, tandis que dans l'état active, il exporte ses services qui sont alors disponibles pour les utilisateurs.

L'activation ou la désactivation d'un bundle résulte dans la création ou la destruction d'une unité logique, matérialisée par l'instance d'une classe dans le bundle, appelée 'bundle Activator'. Quand un bubdle est activé, le framework OSGI appelle une méthode qui signale que le bundle est démarré. De même lorsqu'un bundle est arrêté, le bundle appelle une méthode de désactivation. Quand un bundle est actif il peut alors publier ses services, découvrir les services disponibles, ainsi que de se tenir informer des changements (évènements) qui surviennent.

Gestion des Dépendances

il existe deux sortes de dépendances pour un bundle :

  • Les dépendances physiques 'Deployment dependencies' : elles correspondent aux classes nécessaires au classes contenus dans le bundle. Dans ce cas le bundle doit les déclarer explicitement, et si d'autres bundles exportent ces classes, les dépendances peuvent alors être satisfaites. Si les dépendances ne sont pas satisfaites, le bundle ne peut être démarré et ne peut donc pas être actif. Ces dépendances sont déclarées dans le fichier MANIFEST du jar du bundle, et sont gérées par le framework qui répertorie les packages exportés par les bunbles.
  • Les dépendances logiques 'services dépendances' : elle correspondent aux services nécessaires aux fonctionnement des services du bundle. A la différence des dépendances physiques, les dépendances logiques ne sont pas garanties ou gérées par le framework, ce qui signifie qu'un bundle qui satisfait les dépendances physiques peux être activé même si les dépendances logiques ne sont pas satisfaites. les dépendances logiques sont déclarées dans le MANIFEST, mais la spécification OSGI indique qu'ils sont juste là à titre d'information, c'est au bundle qu'incombe la responsabilité de découvrir les services logiques qu'il a besoin, et c'est aussi lui qui publie les services qu'il exporte dans le Service registry.

Déclaratives Services OU Services Component Runtime

Les Déclarative Service sont des services qui sont déclarés avec leur dépendances logiques et dont le cycle de vie est géré par le services Component Runtime. Les Services sont décrits par une syntaxe XML et définit par une classe d'implémentation, et ses dépendances. Les dépendances sont caractérisées entre autres par

  • Un nom
  • Une interface
  • Une variable définissant si le service peux être dynamiquement changé pendant l'exécution
  • Une cardinalité
  • Une méthode qui sera appelée lorsque le service requis sera ajouté aux registres
  • Une méthode lorsque le service requis sera retiré du registre.

Exemple d'un service de servlet qui renvoi 'Hello World', requiert un et un seul service HTTP obligatoire, un service de log optionnel (c'est un cas d'école), et un ou plusieurs services 'hello world':

<component name="Hello.Servlet" immediate="true">
        <!-- for immediate="true", see 112.5.4 Delayed Component -->
        <implementation class="com.helloworld.impl.HelloWorldImpl">

        <!-- No provided services -->
        
        <!-- Referenced services -->
             <reference
                name="HELLO"
                interface="com.helloworld.HelloService"
                filter="(language=*)"
                cardinality="1..n"
                policy="dynamic"
                bind="bindHelloService" 
                unbind="unbindHelloService"
        />
        
        <reference
                name="HTTP"
                interface="org.osgi.service.http.HttpService"
                cardinality="1..1"
                policy="static"
                bind="bindHttpService" 
                unbind="unbindHttpService"
        >

        <reference
                name="LOG"
                interface="org.osgi.service.log.LogService"
                cardinality="0..1"
                policy="dynamic"
                bind="bindLogService" 
                unbind="unbindLogService"
        />
        
</component>

Peut être vous posez-vous la question "Pourquoi une gestion dynamique ? personne n'est mort à redémarrer une jvm ?!". Je répondrais "tout à fait", mais OSGI va plus loin que ça. Si vous avez des mises en production qui impliquent une continuité de service et que le redémarrage de l'application provoque une coupure de service, que vous deviez mettre à jour une partie de votre application sans tout redéployer, que vous désirez définir le comportement de vos services en fonctions des indisponibilités des autres, que vous voulez faire tourner différentes versions d'un même service, alors OSGI peut vous aider. et puis nombreux sont ceux qui ont un jour eu le cas de deux librairies qui ont une librairie en commun mais que la première utilise utilise une version X et l'autre utilise une version Y (par exemple : deux librairies : une qui utiliserait Spring V1 et l'autre Spring v2.5, cela est possible devient possible avec OSGI mais pas dans avec une JVM et un class loader standard. Sinon il faut abandonné le Java, mais c'est un autre débat ;)

Pour ceux qui veulent allez un peu plus loin, je vous conseille site de didier donsez (extra ! c'est là que j'ai tout appris sur OSGI) et ce tutorial

La discussion continue ailleurs

URL de rétrolien : https://davidmasclet.gisgraphy.com/index.php?trackback/41

Fil des commentaires de ce billet