tag:linuxfr.org,2005:/tags/canopsis/publicLinuxFr.org : les contenus étiquetés avec « canopsis »2020-05-26T05:35:38+02:00/favicon.pngtag:linuxfr.org,2005:Post/410522020-04-23T12:11:43+02:002020-04-23T12:11:43+02:00[WEBINAR 📅 30 AVRIL 2020] Découvrez Canopsis, 1ère solution d'hypervision open source du marché !<p>Bonjour,</p>
<p>Rejoignez-nous le <strong>30 avril 2020 à 10H00</strong> pour découvrir Canopsis, 1ère solution d'Hypervision open source du marché ! L'outil indispensable en temps de crise pour centraliser le monitoring du SI et, ainsi, mieux piloter votre activité. <strong>Mikael MOURCIA</strong>, <em>Directeur Technique</em>, et <strong>Armand DISSAUX</strong>, <em>Responsable d'Agence Nord</em>, vous présenteront la solution et répondront à toutes vos questions.</p>
<p>Durant ce webinar, vous découvrirez comment :</p>
<ul>
<li>
<strong>Collecter</strong> vos événements provenant des différentes sources présentes au sein de votre SI</li>
<li>
<strong>Centraliser</strong> les alarmes sur un bac homogène</li>
<li>
<strong>Traiter</strong> ces événements (enrichissement, webhook, …)</li>
<li>
<strong>Afficher</strong> des vues consolidées pour mieux piloter votre activité métier</li>
</ul>
<p>Pour vous inscrire ➡️ <a href="https://www.capensis.fr/webinar-canopsis/">lien du webinar</a></p>
<p>Nous transmettrons l'enregistrement aux participants suite au webinar.</p>
<p>Nous vous attendons nombreux,<br>
<strong>L'équipe Canopsis</strong> 👋</p>
<div><a href="https://linuxfr.org/forums/general-petites-annonces/posts/webinar-30-avril-2020-decouvrez-canopsis-1ere-solution-d-hypervision-open-source-du-marche.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/120143/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/forums/general-petites-annonces/posts/webinar-30-avril-2020-decouvrez-canopsis-1ere-solution-d-hypervision-open-source-du-marche#comments">ouvrir dans le navigateur</a>
</p>
Capensishttps://linuxfr.org/nodes/120143/comments.atomtag:linuxfr.org,2005:Diary/368372016-09-13T11:18:43+02:002016-09-13T13:52:20+02:00Présentation d'un outil de migration léger en pythonLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#introduction">Introduction</a></li>
<li><a href="#la-probl%C3%A9matique">La problématique</a></li>
<li><a href="#solution-apport%C3%A9e">Solution apportée</a></li>
<li><a href="#fonctionnement">Fonctionnement</a></li>
<li><a href="#conclusion">Conclusion</a></li>
</ul><h2 id="introduction">Introduction</h2>
<p>Aujourd'hui de nombreux outils sont disponibles pour vous aider à mettre en place et à effectuer vos migrations de données. L'outil qui vous est proposé vise la simplicité et la flexibilité. En effet il ne dépend d'aucune librairie annexe et accepte tout les protocoles d'accès aux données.</p>
<h2 id="la-problématique">La problématique</h2>
<p>La problématique de la migration de données est nécessaire quand il faut procéder au changement ou à la mise à jour de systèmes informatiques. Cet outil a donc été développé pour faciliter la migration de données car cette opération est généralement longue, fastidieuse et surtout comporte des risques non négligeables.</p>
<p>Une migration de données est un processus à préparer avec précautions car on ne sait jamais immédiatement si le processus se passera bien, si les données vont être préservées, … voici une liste (non exhaustive) d'étapes clés d'une migration de données.</p>
<ol>
<li>Préparation des données à migrer, on va déterminer le type de migration a effectuer. Par exemple migrer des données d'un github vers un gitlab ou passer des données d'une version X à une version Y.</li>
<li>Choix des moyens de migration utilisés, il existe plusieurs outils comme Django, SQL direct, et maintenant celui que nous vous proposons.</li>
<li>Réservation du temps de migration, plannification de la migration avec l'équipe, cette étape nécessite du temps, on va également choisir l'heure d'execution, la mise en place éventuelle d'alertes, …</li>
<li>Sauvegarde des données, c'est une étape clé pour la réussite de la migration car elle permet de protéger les données d'une éventuelle erreur, en les sauvegardant, on peut si besoin revenir en arrière, le système ne sera donc pas impacté (ou très peu) par l'erreur. Dans cette étape on va aussi déterminer les mesures à prendre en cas d'erreur et quelles sont les informations à remonter après chaque migration.</li>
<li>Migration, cette étape consiste à lancer la migration des données.</li>
</ol><p>La migration de données d'une version X à une version Y va être prise comme exemple pour vous expliquer le fonctionnement de l'outil présenté.</p>
<h2 id="solution-apportée">Solution apportée</h2>
<p>Habituellement, toutes les personnes qui vont intervenir sur les données doivent connaître la structure de ces données et/ou les impacts de la migration sur le système.<br>
L'outil va faciliter ce travail en automatisant la préparation des données et le choix des moyens de migration car il vous suffit de ce seul outil pour gérer toute votre migration. </p>
<p>Le programme permettra donc un gain de temps et de ressources humaines, il sera utilisé directement dans Canopsis (pour plus d'information sur ce logiciel : <a href="//linuxfr.org/news/presentation-technique-de-canopsis">http://linuxfr.org/news/presentation-technique-de-canopsis</a>). Des protocoles d'accès aux storages ont été développés pour que Canopsis puisse faire de la migration de données entre storages. </p>
<p>L'outil peut également être utilisé en dehors de Canopsis car il est totalement indépendant de celui-ci. Cet outil est basé sur la notion de schéma, un schéma va décrire la structure de vos données et va ensuite servir à valider cette structure avant d'utiliser les données. </p>
<h2 id="fonctionnement">Fonctionnement</h2>
<p>Pour le moment la migration est basée sur des schémas JSON mais par la suite, il sera possible d'utiliser également d'autres formats comme XML par exemple. En effet, une factory qui va récupérer le format de vos schémas et ensuite appeler la classe traduisant les fonctions particulières au traitement de ce format a été développée, cette classe est abstraite ce qui va vous permettre de rajouter une classe définissant les fonctions particulières à un format (JSON, XML, …).</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6c75742e696d2f73613941426f3266624b2f6e324a63376a444a6d4b6d36746a39332e6a7067/n2Jc7jDJmKm6tj93.jpg" alt="Figure 1 : schéma UML des classes de base du projet" title="Source : https://lut.im/sa9ABo2fbK/n2Jc7jDJmKm6tj93.jpg"></p>
<p>Les schémas vont être traités par la classe Schema qui va utiliser les fonctions traduites par la classe correspondante au format des schémas, ici JsonSchema. </p>
<p>Les Patch de transformation vont être identifiés et traités par la classe Patch qui va appeler les fonctions traduites par la classe correspondante au format du patch, ici JSONPatch.</p>
<p>La classe transformation va instancier les classes patch et schéma, récupérer les informations nécessaires à la transformation des données et appliquer la transformation.</p>
<p>Avant de lancer votre migration de données, il vous faudra écrire les documents suivant :</p>
<ul>
<li>document de transformation : il regroupe les informations nécessaires à votre migration.
<code>
À savoir :
</code> - Input/Output : ex : file:///dossier/sous_dossier/fichier.json; Les informations d'input/output servent à donner au programme les protocoles d'accès aux données mais aussi à renseigner les chemins d'accès aux schémas de structure des données entrantes et sortantes, un filtre éventuel, le format des données, …
- patch : tableaux des opérations de transformation</li>
</ul><p>Vous pouvez également ajouter d'autres champs si vous en avez besoin comme un filtre pour sélectionner les données.</p>
<p>Pour reprendre l'exemple : </p>
<p>Une donnée en version X « Madonnee » qui se trouve dans un dossier « Mondossier »<br>
Dans l'exemple, on va migrer cette donnée d'une version X, vers une version Y, ma donnée se verra alors ajouter un champ « name ».</p>
<p>Il faut, dans un premier temps, écrire les schémas d'entrée et de sortie appelés respectivement VX et VY.</p>
<pre><code class="json"><span class="p">{</span>
<span class="nt">"$schema"</span><span class="p">:</span> <span class="s2">"http://json-schema.org/draft-04/schema#"</span><span class="p">,</span> <span class="err">#ligne</span> <span class="err">d'héritage</span> <span class="err">du</span> <span class="err">schéma</span>
<span class="nt">"id"</span><span class="p">:</span> <span class="s2">"file://Mondossier/VX.json"</span><span class="p">,</span> <span class="err">#id</span> <span class="err">du</span> <span class="err">schéma,</span> <span class="err">identifiant</span> <span class="err">unique</span>
<span class="nt">"title"</span><span class="p">:</span> <span class="s2">"VX"</span><span class="p">,</span>
<span class="nt">"description"</span><span class="p">:</span> <span class="s2">"exemple schema VX"</span><span class="p">,</span>
<span class="nt">"type"</span><span class="p">:</span> <span class="s2">"object"</span><span class="p">,</span> <span class="err">#type</span> <span class="err">de</span> <span class="err">schéma,</span> <span class="err">peut</span> <span class="err">être</span> <span class="err">array,</span> <span class="err">object,</span> <span class="err">item</span>
<span class="nt">"properties"</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">"version"</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">"type"</span><span class="p">:</span> <span class="s2">"string"</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">{</span>
<span class="nt">"$schema"</span><span class="p">:</span> <span class="s2">"http://json-schema.org/draft-04/schema#"</span><span class="p">,</span>
<span class="nt">"id"</span><span class="p">:</span> <span class="s2">"file://Mondossier/VY.json"</span><span class="p">,</span>
<span class="nt">"title"</span><span class="p">:</span> <span class="s2">"VY"</span><span class="p">,</span>
<span class="nt">"description"</span><span class="p">:</span> <span class="s2">"exemple schema VY"</span><span class="p">,</span>
<span class="nt">"type"</span><span class="p">:</span> <span class="s2">"object"</span><span class="p">,</span>
<span class="nt">"properties"</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">"version"</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">"type"</span><span class="p">:</span> <span class="s2">"string"</span>
<span class="p">},</span>
<span class="nt">"name"</span><span class="p">:</span> <span class="p">{</span>
<span class="nt">"type"</span><span class="p">:</span> <span class="s2">"string"</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre>
<p>Ensuite le document de transformation de cet exemple, pour rappelle vous pouvez le personnaliser pour vos propres migrations.</p>
<pre><code class="json"><span class="p">{</span>
<span class="nt">"id"</span><span class="p">:</span> <span class="s2">"X:Y"</span><span class="p">,</span> <span class="err">#identifiant</span> <span class="err">unique</span> <span class="err">du</span> <span class="err">document</span>
<span class="nt">"patch"</span><span class="p">:</span> <span class="p">[</span>
<span class="err">#cette</span> <span class="err">opération</span> <span class="err">va</span> <span class="err">ajouter</span> <span class="err">le</span> <span class="err">champ</span> <span class="err">« name »</span> <span class="err">qui</span> <span class="err">aura</span> <span class="err">pour</span> <span class="err">valeur</span> <span class="err">« Madonnee »</span>
<span class="s2">"op"</span><span class="err">:</span> <span class="s2">"add"</span><span class="err"> </span><span class="p">,</span>
<span class="s2">"path"</span><span class="err">:</span> <span class="s2">"/name"</span><span class="p">,</span>
<span class="s2">"value"</span><span class="err">:</span> <span class="s2">"Madonnee"</span><span class="p">,</span>
<span class="err">#cette</span> <span class="err">opération</span> <span class="err">va</span> <span class="err">remplacer</span> <span class="err">la</span> <span class="err">valeur</span> <span class="err">actuelle</span> <span class="err">du</span> <span class="err">champ</span> <span class="err">« version »</span> <span class="err">par</span> <span class="err">« </span><span class="mf">2.0</span><span class="err">.</span><span class="mi">0</span><span class="err"> »</span>
<span class="s2">"op"</span><span class="err">:</span> <span class="s2">"replace"</span><span class="p">,</span>
<span class="s2">"path"</span><span class="err">:</span> <span class="s2">"/version"</span><span class="p">,</span>
<span class="s2">"value"</span><span class="err">:</span> <span class="s2">"2.0.0"</span>
<span class="p">],</span>
<span class="nt">"output"</span><span class="p">:</span> <span class="s2">"file:///Mondossier/Madonnee"</span><span class="p">,</span>
<span class="nt">"input"</span><span class="p">:</span> <span class="s2">"file:///Mondossier/Madonnee"</span><span class="p">,</span> <span class="err">#file://</span> <span class="err">est</span> <span class="err">un</span> <span class="err">protocole</span> <span class="err">qui</span> <span class="err">va</span> <span class="err">utiliser</span> <span class="err">la</span> <span class="err">migration</span> <span class="err">pour</span> <span class="err">un</span> <span class="err">fichier,</span> <span class="err">il</span> <span class="err">est</span> <span class="err">fourni</span> <span class="err">avec</span> <span class="err">l'outil</span> <span class="err">et</span> <span class="err">va</span> <span class="err">nous</span> <span class="err">servir</span> <span class="err">d'exemple</span> <span class="err">pour</span> <span class="err">la</span> <span class="err">suite</span> <span class="err">des</span> <span class="err">explications.</span>
<span class="p">}</span></code></pre>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6c75742e696d2f50513959506255524d682f6e317232714b43344b49485a6a546e732e6a7067/n1r2qKC4KIHZjTns.jpg" alt="Figure 2 : schéma de fonctionnement de la migration" title="Source : https://lut.im/PQ9YPbURMh/n1r2qKC4KIHZjTns.jpg"></p>
<p>Pour personnaliser votre migration, vous pouvez créer vos propres protocoles d'input/output et leur associer des IOInterfaces spécifiques aux protocoles que vous voulez.</p>
<p>Le patch de transformation est lui aussi entièrement malléable à vos besoins, vous pouvez écrire autant d'opérations que vous souhaitez et celles que vous souhaitez (add, copy, move, remove, replace). Si vous ne souhaitez que déplacer vos données, tant que vous lui fournissez les schémas de structure et le document de transformation vous pouvez ne pas ajouter le patch au document de transformation.</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6c75742e696d2f614a6d557541444550542f447048546c314f516a5478415a3435542e6a7067/DpHTl1OQjTxAZ45T.jpg" alt="Figure 3 : schéma UML de la migration" title="Source : https://lut.im/aJmUuADEPT/DpHTl1OQjTxAZ45T.jpg"></p>
<p>La migration est gérée par la fonction migrate(). Elle prend en paramètre le chemin vers le document de transformation et effectue votre migration.</p>
<p>La définition d'un nouveau protocole d'accès aux données se fait simplement et dynamiquement à l'aide du design pattern factory qui fournit des objets héritant de l'interface IOInterface. Il suffit d'hériter de cette interface pour que le nouveau protocole soit automatiquement enregistré par la factory et utilisable à partir d'un document de migration.</p>
<p>Cela vous permet d'implémenter votre propre classe d'IOInterface, ainsi vos données peuvent être migrées de n'importe quel input vers n'importe quel output. Vous pouvez donc par exemple créer vos propres protocoles d'input/output et choisir le comportement de la migration par rapport à ce protocole.</p>
<p>Pour utiliser la migration il vous suffit d'importer le module de migration et de taper la commande qui suit dans Python : </p>
<pre><code class="python"><span class="kn">from</span> <span class="nn">schema.migration.core</span> <span class="kn">import</span> <span class="n">migrate</span>
<span class="n">migrate</span><span class="p">(</span><span class="n">path_transfo</span><span class="p">)</span>
<span class="c">#path_transfo : chemin d'accès vers le document de transformation</span></code></pre>
<h2 id="conclusion">Conclusion</h2>
<p>Cet outil est entièrement malléable et simple grâce à plusieurs choses :</p>
<ul>
<li>le développement des factory</li>
<li>la mise en place d'une metaclasse</li>
<li>l'implémentation d'IOInterfaces</li>
<li>acceptation de tous les protocoles d'accès aux données</li>
<li>dépendance à aucune librairie tierce</li>
</ul><p>Vous pouvez télécharger cet outil sur : <a href="https://git.canopsis.net/jvanglabeke/data_migration.git">https://git.canopsis.net/jvanglabeke/data_migration.git</a><br>
installation : pip install data_migration</p>
<p>Je remercie les gérants du projet Canopsis Mr. Mikaël Mourcia et Mr. Edouard Huault qui ont financé le développement de ce projet.</p><div><a href="https://linuxfr.org/users/lyly/journaux/presentation-d-un-outil-de-migration-leger-en-python.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/110025/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/lyly/journaux/presentation-d-un-outil-de-migration-leger-en-python#comments">ouvrir dans le navigateur</a>
</p>
lylyhttps://linuxfr.org/nodes/110025/comments.atomtag:linuxfr.org,2005:News/370852016-02-07T09:41:56+01:002016-02-07T09:41:56+01:00Présentation technique de CanopsisLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>Supervision et visualisation des données : des domaines de l'informatique qui nous permettent de surveiller, analyser une infra, des données afin de comprendre et éventuellement prédire des dysfonctionnements.</p>
<p>De nombreux outils existent et excellent dans chacune de ces tâches. Les accorder ensemble afin d'unifier l'information permet ainsi de faciliter la prise de décision.</p>
<p>C'est ce que l'on appelle <strong>l'hypervision</strong>.</p>
<p>Canopsis se veut une solution d'hypervision (on évite l'appellation <em>hyperviseur</em> qui reste dans le langage courant spécifique à la virtualisation). Solution open-source sous licence AGPL3 développée par la société française <a href="http://capensis.fr">Capensis</a>, elle se veut simple d'utilisation, et suffisamment souple pour répondre à un maximum de besoin.</p></div><ul><li>lien nᵒ 1 : <a title="https://git.canopsis.net/explore" hreflang="fr" href="https://linuxfr.org/redirect/96352">Gitlab de Canopsis</a></li><li>lien nᵒ 2 : <a title="https://capensis.fr" hreflang="fr" href="https://linuxfr.org/redirect/96353">Capensis</a></li><li>lien nᵒ 3 : <a title="http://www-igm.univ-mlv.fr/~dr/XPOSE2010/Hypervision/Hyperviseur.html" hreflang="fr" href="https://linuxfr.org/redirect/96354">Présentation d'un hyperviseur</a></li><li>lien nᵒ 4 : <a title="http://www.informatique-securite.net/hypervision-supervision/" hreflang="fr" href="https://linuxfr.org/redirect/96355">Hypervision - Piloter votre supervision</a></li><li>lien nᵒ 5 : <a title="http://wiki.monitoring-fr.org/hypervision/start" hreflang="fr" href="https://linuxfr.org/redirect/96356">Hypervision sur monitoring-fr</a></li></ul><div><h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#la-probl%C3%A9matique">La problématique</a></li>
<li>
<a href="#objectif--la-solution">Objectif : la solution</a><ul>
<li><a href="#%C3%89tape-1--les-connecteurs">Étape 1 : les connecteurs</a></li>
<li><a href="#%C3%89tape-2--le-bus-de-donn%C3%A9es-et-les-moteurs">Étape 2 : le bus de données et les moteurs</a></li>
<li><a href="#%C3%89tape-3--les-sch%C3%A9mas">Étape 3 : les schémas</a></li>
<li>
<a href="#%C3%89tape-4--g%C3%A9rer-et-servir-la-donn%C3%A9e">Étape 4 : gérer et servir la donnée</a><ul>
<li><a href="#a-les-managers">A. Les managers</a></li>
<li><a href="#b-les-webservices">B. Les webservices</a></li>
</ul>
</li>
<li>
<a href="#%C3%89tape-5--lapplication-web">Étape 5 : l'application web</a><ul>
<li><a href="#a-les-briques">A. Les briques</a></li>
<li><a href="#b-les-composants">B. Les composants</a></li>
<li><a href="#c-les-renderers%C3%A9diteurs">C. Les renderers/éditeurs</a></li>
<li><a href="#d-les-widgets">D. Les widgets</a></li>
<li><a href="#e-les-mixins">E. Les mixins</a></li>
<li><a href="#f-les-vues">F. Les vues</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#le-bac-%C3%A0-%C3%A9v%C3%A9nements">Le bac à événements</a></li>
<li><a href="#s%C3%A9lecteur-et-widget-m%C3%A9t%C3%A9o--simplifier-la-supervision">Sélecteur et widget météo : simplifier la supervision</a></li>
<li><a href="#monitoring-et-s%C3%A9ries">Monitoring et séries</a></li>
<li><a href="#les-t%C3%A2ches-ordonnanc%C3%A9es-et-les-notifications">Les tâches ordonnancées et les notifications</a></li>
<li><a href="#conclusion">Conclusion</a></li>
</ul><h2 id="la-problématique">La problématique</h2>
<p>Dans une infra hétérogène, on dispose de nombreuses sources d'information :</p>
<ul>
<li>côté supervision, on peut avoir un mélange de :
<ul>
<li>Nagios/Icinga</li>
<li>Centreon</li>
<li>Shinken</li>
<li>HPOV</li>
<li>Zabbix</li>
<li>SNMP</li>
<li>…</li>
</ul>
</li>
<li>côté récolte de données, on peut avoir :
<ul>
<li>CollectD</li>
<li>Logstash</li>
<li>Munin</li>
<li>Telegraf (de la pile <a href="https://influxdata.com/">TICK</a>)</li>
<li>…</li>
</ul>
</li>
<li>ou encore :
<ul>
<li>des logs</li>
<li>des données stockées en base de données</li>
<li>un résultat de tests unitaires et fonctionnels (jMeter, Sikuli…)</li>
</ul>
</li>
</ul><p>La mise en place d'un accès à l'ensemble de ces informations peut être fastidieuse, et dans la plupart des cas, l'utilisateur (l'administrateur technique et/ou fonctionnel) devra accéder à plusieurs interfaces et maîtriser plusieurs outils.</p>
<p>Cela empêche d'avoir une vue d'ensemble cohérente et rend difficile l'anticipation ainsi que la prise de décision.</p>
<h2 id="objectif--la-solution">Objectif : la solution</h2>
<p>C'est là que Canopsis intervient.</p>
<p><img src="//img.linuxfr.org/img/687474703a2f2f7777772e63616e6f707369732e636f6d2f77702d636f6e74656e742f7468656d65732f63616e6f707369732f696d616765732f736368656d612e706e67/schema.png" alt="Schéma fonctionnel" title="Source : http://www.canopsis.com/wp-content/themes/canopsis/images/schema.png"></p>
<h3 id="Étape-1--les-connecteurs">Étape 1 : les connecteurs</h3>
<p>Dans un premier temps, nous devons récupérer les informations produites par cet ensemble hétérogène.</p>
<p>Ainsi, pour chaque source de données, nous sommes en mesure de développer un <strong>connecteur</strong>, capable d'extraire les informations voulues, et de les transmettre à Canopsis sous la forme <strong>d'événements</strong> standardisés.</p>
<p>Un certain nombre de connecteurs sont déjà disponibles sur le <a href="https://git.canopsis.net/groups/canopsis-connectors">Gitlab</a>.</p>
<p>Le cas le plus simple est donc celui ci :</p>
<pre><code class="python"><span class="c">#!/usr/bin/env python</span>
<span class="kn">from</span> <span class="nn">time</span> <span class="kn">import</span> <span class="n">time</span>
<span class="c"># lib qui nous permet de nous connecter au bus de données (cf étape 2)</span>
<span class="kn">from</span> <span class="nn">kombu</span> <span class="kn">import</span> <span class="n">Connection</span>
<span class="c"># module qui sera utilisé pour envoyer les événements</span>
<span class="kn">from</span> <span class="nn">kombu.pools</span> <span class="kn">import</span> <span class="n">producers</span>
<span class="c"># construction de l'événement selon le standard Canopsis</span>
<span class="n">event</span> <span class="o">=</span> <span class="p">{</span>
<span class="s">"timestamp"</span><span class="p">:</span> <span class="nb">int</span><span class="p">(</span><span class="n">time</span><span class="p">()),</span>
<span class="c"># émetteur de l'événement</span>
<span class="s">"connector"</span><span class="p">:</span> <span class="s">"myconnector"</span><span class="p">,</span>
<span class="s">"connector_name"</span><span class="p">:</span> <span class="s">"myconnector-instance0"</span><span class="p">,</span>
<span class="c"># nature de l'événement</span>
<span class="s">"event_type"</span><span class="p">:</span> <span class="s">"check"</span><span class="p">,</span>
<span class="c"># source de l'événement</span>
<span class="s">"source_type"</span><span class="p">:</span> <span class="s">"resource"</span><span class="p">,</span>
<span class="s">"component"</span><span class="p">:</span> <span class="s">"<hostname>"</span><span class="p">,</span>
<span class="s">"resource"</span><span class="p">:</span> <span class="s">"<job's name>"</span><span class="p">,</span>
<span class="c"># données portées par l'événement</span>
<span class="s">"state"</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="c"># 0 = INFO, 1 = MINOR, 2 = MAJOR, 3 = CRITICAL</span>
<span class="s">"output"</span><span class="p">:</span> <span class="s">"<message>"</span>
<span class="p">}</span>
<span class="c"># construction de la routing_key, qui sert à identifier l'événement et à le router</span>
<span class="n">routing_key</span> <span class="o">=</span> <span class="s">"{0}.{1}.{2}.{3}.{4}"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span>
<span class="n">event</span><span class="p">[</span><span class="s">'connector'</span><span class="p">],</span>
<span class="n">event</span><span class="p">[</span><span class="s">'connector_name'</span><span class="p">],</span>
<span class="n">event</span><span class="p">[</span><span class="s">'event_type'</span><span class="p">],</span>
<span class="n">event</span><span class="p">[</span><span class="s">'source_type'</span><span class="p">],</span>
<span class="n">event</span><span class="p">[</span><span class="s">'component'</span><span class="p">]</span>
<span class="p">)</span>
<span class="k">if</span> <span class="n">event</span><span class="p">[</span><span class="s">'source_type'</span><span class="p">]</span> <span class="o">==</span> <span class="s">"resource"</span><span class="p">:</span>
<span class="n">routing_key</span> <span class="o">=</span> <span class="s">"{0}.{1}"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span>
<span class="n">routing_key</span><span class="p">,</span>
<span class="n">event</span><span class="p">[</span><span class="s">'resource'</span><span class="p">]</span>
<span class="p">)</span>
<span class="c"># Connexion</span>
<span class="n">uri</span> <span class="o">=</span> <span class="s">'amqp://cpsrabbit:canopsis@localhost:5672/canopsis'</span>
<span class="k">with</span> <span class="n">Connection</span><span class="p">(</span><span class="n">uri</span><span class="p">)</span> <span class="k">as</span> <span class="n">conn</span><span class="p">:</span>
<span class="c"># Création de l'émetteur</span>
<span class="k">with</span> <span class="n">producers</span><span class="p">[</span><span class="n">conn</span><span class="p">]</span><span class="o">.</span><span class="n">acquire</span><span class="p">(</span><span class="n">block</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span> <span class="k">as</span> <span class="n">producer</span><span class="p">:</span>
<span class="c"># Publication</span>
<span class="n">producer</span><span class="o">.</span><span class="n">publish</span><span class="p">(</span>
<span class="n">event</span><span class="p">,</span>
<span class="n">serializer</span><span class="o">=</span><span class="s">'json'</span><span class="p">,</span>
<span class="n">exchange</span><span class="o">=</span><span class="s">'canopsis.events'</span><span class="p">,</span>
<span class="n">routing_key</span><span class="o">=</span><span class="n">routing_key</span>
<span class="p">)</span></code></pre>
<p>Bien évidemment, du moment qu'un connecteur envoie ses événements, il peut être développé dans n'importe quel langage, c'est le cas du connecteur Nagios qui se présente comme un Nagios Event Broker, et est donc développé en C.</p>
<h3 id="Étape-2--le-bus-de-données-et-les-moteurs">Étape 2 : le bus de données et les moteurs</h3>
<p>Les événements produits par les connecteurs sont transmis au bus de données de Canopsis, basé sur <a href="https://www.rabbitmq.com/">RabbitMQ</a>.</p>
<p><img src="//img.linuxfr.org/img/687474703a2f2f69676d2e756e69762d6d6c762e66722f7e64722f58504f5345323031312f7261626269746d712f6c6f61645f62616c616e63696e672e706e67/load_balancing.png" alt="Schema RabbitMQ" title="Source : http://igm.univ-mlv.fr/~dr/XPOSE2011/rabbitmq/load_balancing.png"></p>
<p>Source : <a href="http://igm.univ-mlv.fr/%7Edr/XPOSE2011/rabbitmq/usages.html">http://igm.univ-mlv.fr/~dr/XPOSE2011/rabbitmq/usages.html</a></p>
<p>Ces événements vont être consommés par des <em>daemons</em> que nous appelons <strong>moteurs</strong>.</p>
<p>Leur but est simple :</p>
<ul>
<li>traiter la donnée</li>
<li>enregistrer les informations pertinentes en base de données</li>
<li>transmettre, si nécessaire, l'événement à un ou plusieurs autres moteurs</li>
</ul><p>Nous avons, par exemple :</p>
<ul>
<li>le moteur <code>event_filter</code> qui se charge de filtrer/modifier les événements entrants</li>
<li>le moteur <code>context</code> qui se charge d'enregistrer les informations sur l'entité cible de l'événement (connecteur source, composant/ressource qui a produit l'événement…)</li>
<li>le moteur <code>perfdata</code> qui s'occupe d'historiser les données de performance (comme remontées par Nagios ou CollectD)</li>
<li>…</li>
</ul><p>Comme pour les <em>connecteurs</em>, les <em>moteurs</em> peuvent être développés dans n'importe quel langage, bien que pour le moment nous les avons tous faits en Python.</p>
<p>Étant un <em>daemon</em>, un <em>moteur</em> dispose de 2 fonctions :</p>
<ul>
<li>une pour consommer les événements (dans un <em>thread</em> à part)</li>
<li>une pour exécuter une tâche régulièrement (recharger la configuration, envoyer des stats…)</li>
</ul><p>Ainsi que la configuration suivante :</p>
<pre><code class="ini"><span class="k">[engine:myengine]</span>
<span class="c1"># chemin Python de la méthode de consommation</span>
<span class="na">event_processing</span><span class="o">=</span><span class="s">canopsis.myfeature.process.event_processing</span>
<span class="c1"># chemin Python de la méthode exécutée régulièrement</span>
<span class="na">beat_processing</span><span class="o">=</span><span class="s">canopsis.myfeature.process.beat_processing</span>
<span class="c1"># nom de l'exchange AMQP sur lequel écouter (par défaut: amq.direct)</span>
<span class="na">exchange_name</span><span class="o">=</span><span class="s">canopsis.event</span>
<span class="c1"># RK à consommer</span>
<span class="na">routing_keys</span><span class="o">=</span><span class="s">nagios.#,shinken.#</span>
<span class="c1"># intervalle entre 2 exécutions du beat_processing (en secondes)</span>
<span class="na">beat_interval</span><span class="o">=</span><span class="s">60</span>
<span class="c1"># liste des moteurs sur lesquels retransmettre l'événement reçu, possiblement modifié (par défaut: aucun)</span>
<span class="na">next</span><span class="o">=</span><span class="s">myengine2,myengine3</span></code></pre>
<p>Et donc l'implémentation se résume à :</p>
<pre><code class="python"><span class="k">def</span> <span class="nf">event_processing</span><span class="p">(</span><span class="n">engine</span><span class="p">,</span> <span class="n">event</span><span class="p">,</span> <span class="o">**</span><span class="n">_</span><span class="p">):</span>
<span class="c"># traiter l'événement</span>
<span class="k">return</span> <span class="n">event</span>
<span class="k">def</span> <span class="nf">beat_processing</span><span class="p">(</span><span class="n">engine</span><span class="p">,</span> <span class="o">**</span><span class="n">_</span><span class="p">):</span>
<span class="c"># faire des choses</span></code></pre>
<h3 id="Étape-3--les-schémas">Étape 3 : les schémas</h3>
<p>Toutes les données qui véhiculent dans le bus et qui sont sauvegardées en base sont munies de schémas les décrivant.</p>
<p>Ces schémas servent à plusieurs choses :</p>
<ul>
<li>valider que la donnée est bien formatée</li>
<li>décrire comment la donnée sera représentée</li>
<li>décrire comment la donnée sera éditée</li>
</ul><p>Ces deux derniers points permettent de générer une partie du code de l'UI (cf étape 5).</p>
<p>À terme, ils serviront également à :</p>
<ul>
<li>décrire comment la donnée sera utilisée</li>
<li>décrire comment la donnée pourra être transformée</li>
</ul><p>Ce qui permettra de générer une partie du code backend (cf étape 4).</p>
<p>Le formalisme qui permet d'écrire un schéma est actuellement inspiré du standard <a href="http://json-schema.org/">JSON Schema</a> :</p>
<pre><code class="javascript"><span class="p">{</span>
<span class="s2">"title"</span><span class="o">:</span> <span class="s2">"MyData"</span><span class="p">,</span>
<span class="s2">"description"</span><span class="o">:</span> <span class="s2">"Schéma décrivant la donnée, comment l'afficher et l'éditer"</span>
<span class="c1">// description de la donnée</span>
<span class="s2">"type"</span><span class="o">:</span> <span class="s2">"object"</span><span class="p">,</span>
<span class="s2">"properties"</span><span class="o">:</span> <span class="p">{</span>
<span class="s2">"color"</span><span class="o">:</span> <span class="p">{</span>
<span class="s2">"type"</span><span class="o">:</span> <span class="s2">"string"</span><span class="p">,</span>
<span class="s2">"required"</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="cm">/* comment la donnée sera affichée/éditée</span>
<span class="cm"> * le rôle 'color' :</span>
<span class="cm"> * - affichera un carré de couleur lorsqu'on l'affichera</span>
<span class="cm"> * - affichera un colorpicker lorsqu'on l'éditera</span>
<span class="cm"> */</span>
<span class="s2">"role"</span><span class="o">:</span> <span class="s2">"color"</span><span class="p">,</span>
<span class="c1">// les champs suivants servent pour le formulaire</span>
<span class="s2">"title"</span><span class="o">:</span> <span class="s2">"Couleur de la donnée"</span><span class="p">,</span>
<span class="s2">"description"</span><span class="o">:</span> <span class="s2">"Tooltip d'aide"</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="c1">// les champs suivants aident à générer le formulaire d'édition</span>
<span class="s2">"categories"</span><span class="o">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="s2">"title"</span><span class="o">:</span> <span class="s2">"General"</span><span class="p">,</span>
<span class="s2">"keys"</span><span class="o">:</span> <span class="p">[</span><span class="s2">"color"</span><span class="p">]</span>
<span class="p">}</span>
<span class="p">]</span>
<span class="p">}</span></code></pre>
<p>L'<code>id</code> d'un schéma est construit de la manière suivante :</p>
<ul>
<li><code>mydata</code></li>
<li>
<code>mydata.mydata2</code> : ici <code>mydata2</code> hérite de <code>mydata</code>
</li>
</ul><p>On obtient donc en base de données :</p>
<pre><code class="javascript"><span class="p">{</span>
<span class="s2">"_id"</span><span class="o">:</span> <span class="s2">"<id du schema>"</span><span class="p">,</span>
<span class="s2">"schema"</span><span class="o">:</span> <span class="c1">// le schéma à proprement parler</span>
<span class="p">}</span></code></pre>
<h3 id="Étape-4--gérer-et-servir-la-donnée">Étape 4 : gérer et servir la donnée</h3>
<p>Maintenant que nous avons la structure pour récupérer la donnée, et que nous sommes en mesure de la schématiser, il faut mettre en place les mécanismes permettant d'interagir avec et de la servir à l'UI.</p>
<h4 id="a-les-managers">A. Les managers</h4>
<p>La gestion de la donnée est prise en charge par ce que l'on appelle les <strong>managers</strong>. Pour bien comprendre son rôle, il faut plonger un peu dans le code.</p>
<p>Un <strong>configurable</strong> est un objet Python dont les propriétés sont définies par son fichier de configuration :</p>
<pre><code class="python"><span class="kn">from</span> <span class="nn">canopsis.configuration.configurable</span> <span class="kn">import</span> <span class="n">Configurable</span>
<span class="kn">from</span> <span class="nn">canopsis.configuration.configurable.decorator</span> <span class="kn">import</span> <span class="n">conf_paths</span>
<span class="kn">from</span> <span class="nn">canopsis.configuration.configurable.decorator</span> <span class="kn">import</span> <span class="n">add_category</span>
<span class="kn">from</span> <span class="nn">canopsis.configuration.model</span> <span class="kn">import</span> <span class="n">Parameter</span>
<span class="n">CONF_PATH</span> <span class="o">=</span> <span class="s">'myfeature/manager.conf'</span> <span class="c"># {sys.prefix}/etc/{CONF_PATH}</span>
<span class="n">CATEGORY</span> <span class="o">=</span> <span class="s">'MYFEATURE'</span>
<span class="c"># Définition du contenu de la catégorie</span>
<span class="n">CONTENT</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">Parameter</span><span class="p">(</span><span class="s">'foo'</span><span class="p">),</span>
<span class="n">Parameter</span><span class="p">(</span><span class="s">'bar'</span><span class="p">,</span> <span class="n">parser</span><span class="o">=</span><span class="nb">int</span><span class="p">)</span>
<span class="p">]</span>
<span class="c"># ajoute un fichier de configuration à lire par le configurable</span>
<span class="nd">@conf_paths</span><span class="p">(</span><span class="n">CONF_PATH</span><span class="p">)</span>
<span class="c"># permet de spécifier la catégorie de configuration depuis laquelle on peut lire les paramètres</span>
<span class="nd">@add_category</span><span class="p">(</span><span class="n">CATEGORY</span><span class="p">,</span> <span class="n">content</span><span class="o">=</span><span class="n">CONTENT</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">MyFeatureManager</span><span class="p">(</span><span class="n">Configurable</span><span class="p">):</span>
<span class="k">pass</span></code></pre>
<p>Ainsi, avec le fichier de configuration suivant :</p>
<pre><code class="ini"><span class="k">[MYFEATURE]</span>
<span class="na">foo</span><span class="o">=</span><span class="s">bar</span>
<span class="na">bar</span><span class="o">=</span><span class="s">42</span></code></pre>
<p>Ou :</p>
<pre><code class="javascript"><span class="p">{</span>
<span class="s2">"MYFEATURE"</span><span class="o">:</span> <span class="p">{</span>
<span class="s2">"foo"</span><span class="o">:</span> <span class="s2">"bar"</span><span class="p">,</span>
<span class="s2">"bar"</span><span class="o">:</span> <span class="mi">42</span>
<span class="p">}</span>
<span class="p">}</span></code></pre>
<p>En effet, le configurable va tester différents drivers, actuellement on dispose de INI et JSON mais il est envisageable d'avoir un driver MongoDB ou autre</p>
<p>Lorsque l'on instanciera la classe, on obtiendra :</p>
<pre><code class="python"><span class="n">obj</span> <span class="o">=</span> <span class="n">MyFeatureManager</span><span class="p">()</span>
<span class="k">assert</span> <span class="n">obj</span><span class="o">.</span><span class="n">foo</span> <span class="o">==</span> <span class="s">"bar"</span>
<span class="k">assert</span> <span class="n">obj</span><span class="o">.</span><span class="n">bar</span> <span class="o">==</span> <span class="mi">42</span></code></pre>
<p>Et à partir de cette classe <code>Configurable</code> on va définir l'arbre d'héritage suivant :</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6c75742e696d2f684147434b6b50544c6e2f496a4e43675541764369486f7233696c/IjNCgUAvCiHor3il" alt="Diagramme Configurable" title="Source : https://lut.im/hAGCKkPTLn/IjNCgUAvCiHor3il"></p>
<p>Un <code>ConfigurableRegistry</code> permet de spécifier, dans un paramètre de configuration, un autre <code>Configurable</code> à instancier :</p>
<pre><code class="ini"><span class="na">otherconfigurable_value</span> <span class="o">=</span> <span class="s">canopsis.myotherfeature.manager.MyOtherFeatureManager</span></code></pre>
<p>Et on y accèdera, dans l'instance, de la manière suivante :</p>
<pre><code class="python"><span class="k">assert</span> <span class="nb">isinstance</span><span class="p">(</span><span class="bp">self</span><span class="p">[</span><span class="s">'otherconfigurable'</span><span class="p">],</span> <span class="n">MyOtherFeatureManager</span><span class="p">)</span></code></pre>
<p>Le <code>MiddlewareRegistry</code> fait de même pour les <code>Middleware</code> (qui identifient un <strong>protocole</strong> ainsi qu'un <strong>type de données</strong>) :</p>
<pre><code class="ini"><span class="na">mymiddleware_uri</span> <span class="o">=</span> <span class="s">protocol-datatype-datascope://</span></code></pre>
<p>De même que pour le <code>ConfigurableRegistry</code>, on y accède de la manière suivante :</p>
<pre><code class="python"><span class="k">assert</span> <span class="bp">self</span><span class="p">[</span><span class="s">'mymiddleware'</span><span class="p">]</span><span class="o">.</span><span class="n">connected</span><span class="p">()</span></code></pre>
<p>En général, un <em>manager</em> sera un <code>MiddlewareRegistry</code>, ce qui permettra de changer de techno utilisée, sans modifier le code :</p>
<pre><code class="ini"><span class="k">[MYFEATURE]</span>
<span class="na">mystorage_uri</span> <span class="o">=</span> <span class="s">mongodb-timed-mydata://</span>
<span class="c1"># mystorage_uri = influxdb-timed-mydata://</span>
<span class="na">mymanager_value</span> <span class="o">=</span> <span class="s">canopsis.myotherfeature.manager.MyOtherFeatureManager</span>
<span class="c1"># mymanager_value = canopsis.myotherfeature.manager2.MyOtherFeatureManager2</span></code></pre>
<p>Et ce manager sera utilisé par le <strong>moteur</strong> et le <strong>webservice</strong>.</p>
<p>En reprenant l'exemple du moteur :</p>
<pre><code class="python"><span class="kn">from</span> <span class="nn">canopsis.common.utils</span> <span class="kn">import</span> <span class="n">singleton_per_scope</span>
<span class="kn">from</span> <span class="nn">canopsis.myfeature.manager</span> <span class="kn">import</span> <span class="n">MyFeatureManager</span>
<span class="k">def</span> <span class="nf">event_processing</span><span class="p">(</span><span class="n">engine</span><span class="p">,</span> <span class="n">event</span><span class="p">,</span> <span class="n">manager</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="o">**</span><span class="n">_</span><span class="p">):</span>
<span class="k">if</span> <span class="n">manager</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
<span class="c"># instancie la classe une seule fois par module</span>
<span class="n">manager</span> <span class="o">=</span> <span class="n">singleton_per_scope</span><span class="p">(</span><span class="n">MyFeatureManager</span><span class="p">)</span>
<span class="c"># faire des choses avec l'événement et le manager</span>
<span class="k">return</span> <span class="n">event</span>
<span class="k">def</span> <span class="nf">beat_processing</span><span class="p">(</span><span class="n">engine</span><span class="p">,</span> <span class="n">event</span><span class="p">,</span> <span class="n">manager</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="o">**</span><span class="n">_</span><span class="p">):</span>
<span class="k">if</span> <span class="n">manager</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
<span class="n">manager</span> <span class="o">=</span> <span class="n">singleton_per_scope</span><span class="p">(</span><span class="n">MyFeatureManager</span><span class="p">)</span>
<span class="c"># faire des choses avec le manager</span></code></pre>
<h4 id="b-les-webservices">B. Les webservices</h4>
<p>Afin de servir la donnée à l'UI, on dispose d'une application WSGI découpée en modules, que l'on appelle <strong>webservice</strong>.</p>
<p>Ces derniers se trouvent dans le paquet Python <code>canopsis.webcore.services</code>.</p>
<p>Et grâce au code suivant (à placer dans le <code>__init__.py</code>), on peut avoir plusieurs paquets Python fournissant du code à cet emplacement :</p>
<pre><code class="python"><span class="kn">from</span> <span class="nn">pkgutil</span> <span class="kn">import</span> <span class="n">extend_path</span>
<span class="n">__path__</span> <span class="o">=</span> <span class="n">extend_path</span><span class="p">(</span><span class="n">__path__</span><span class="p">,</span> <span class="n">__name__</span><span class="p">)</span></code></pre>
<p>Bref, un <em>webservice</em> s'écrit très simplement :</p>
<pre><code class="python"><span class="c"># couche d'abstraction qui nous permettra de passer de Bottle à Flask</span>
<span class="kn">from</span> <span class="nn">canopsis.common.ws</span> <span class="kn">import</span> <span class="n">route</span>
<span class="kn">from</span> <span class="nn">canopsis.common.utils</span> <span class="kn">import</span> <span class="n">singleton_per_scope</span>
<span class="kn">from</span> <span class="nn">canopsis.myfeature.manager</span> <span class="kn">import</span> <span class="n">MyFeatureManager</span>
<span class="c"># Sera appelé par l'application WSGI pour charger les routes HTTP</span>
<span class="k">def</span> <span class="nf">exports</span><span class="p">(</span><span class="n">ws</span><span class="p">):</span>
<span class="c"># ici `ws` désigne donc le webserver</span>
<span class="n">manager</span> <span class="o">=</span> <span class="n">singleton_per_scope</span><span class="p">(</span><span class="n">MyFeatureManager</span><span class="p">)</span>
<span class="c"># ici on créé la route /foo qui accepte la méthode GET</span>
<span class="nd">@route</span><span class="p">(</span><span class="n">ws</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">get</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">foo</span><span class="p">():</span>
<span class="k">return</span> <span class="n">manager</span><span class="o">.</span><span class="n">foo</span>
<span class="c"># l'API retournera :</span>
<span class="c"># {</span>
<span class="c"># "total": 1,</span>
<span class="c"># "data": ["bar"],</span>
<span class="c"># "success": true</span>
<span class="c"># }</span>
<span class="c"># cette fois ci, on créé la route /bar/:baz</span>
<span class="nd">@route</span><span class="p">(</span><span class="n">ws</span><span class="o">.</span><span class="n">application</span><span class="o">.</span><span class="n">get</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">bar</span><span class="p">(</span><span class="n">baz</span><span class="p">):</span>
<span class="k">return</span> <span class="p">(</span><span class="n">manager</span><span class="o">.</span><span class="n">bar</span> <span class="o">==</span> <span class="n">baz</span><span class="p">)</span></code></pre>
<h3 id="Étape-5--lapplication-web">Étape 5 : l'application web</h3>
<p>Côté <em>backend</em>, on dispose désormais du nécessaire pour fournir à l'UI tout ce dont elle a besoin.<br>
Ainsi, nous avons également travaillé la modularité de cette UI, basée sur <a href="http://emberjs.com">Ember</a>, au maximum.</p>
<h4 id="a-les-briques">A. Les briques</h4>
<p>Afin de répondre à cette problématique de modularité, nous avons mis en place un système de briques, permettant de sélectionner les fonctionnalités effectivement chargées.</p>
<p>Concrètement, on peut voir une <strong>brique</strong> comme étant un <em>addon</em> apportant plusieurs fonctionnalités telles que :</p>
<ul>
<li>nouveaux éléments graphiques (<em>composants</em>)</li>
<li>nouveaux outils de rendus (<em>widgets</em>, <em>renderers</em>)</li>
<li>nouveaux outils d'éditions (<em>éditors</em>)</li>
<li>…</li>
</ul><p>Pour construire une brique, il suffit de créer un paquet <a href="https://www.npmjs.com/">NPM</a> avec le <code>package.json</code> suivant :</p>
<pre><code class="javascript"><span class="p">{</span>
<span class="s2">"name"</span><span class="o">:</span> <span class="s2">"<nom de la brique>"</span><span class="p">,</span>
<span class="s2">"description"</span><span class="o">:</span> <span class="s2">"<description de la brique>"</span><span class="p">,</span>
<span class="s2">"version"</span><span class="o">:</span> <span class="s2">"0.1.0"</span><span class="p">,</span>
<span class="c1">// il s'agit du fichier principal de la brique, il pointera vers la version de dev ou minifiée</span>
<span class="s2">"main"</span><span class="o">:</span> <span class="s2">"init.js"</span><span class="p">,</span>
<span class="s2">"scripts"</span><span class="o">:</span> <span class="p">{</span>
<span class="s2">"test"</span><span class="o">:</span> <span class="s2">"echo \"Error: no test specified\" && exit 1"</span><span class="p">,</span>
<span class="s2">"compile"</span><span class="o">:</span> <span class="s2">"rm -Rf tmp/build && broccoli build tmp/build && cp tmp/build . -RT"</span><span class="p">,</span>
<span class="s2">"lint"</span><span class="o">:</span> <span class="s2">"eslint src"</span><span class="p">,</span>
<span class="s2">"doc"</span><span class="o">:</span> <span class="s2">"./node_modules/canopsis-ui-toolbelt/scripts/buildDoc"</span><span class="p">,</span>
<span class="s2">"minify"</span><span class="o">:</span> <span class="s2">"node node_modules/canopsis-ui-toolbelt/scripts/minify.js"</span>
<span class="p">},</span>
<span class="s2">"repository"</span><span class="o">:</span> <span class="p">{</span>
<span class="s2">"type"</span><span class="o">:</span> <span class="s2">"git"</span><span class="p">,</span>
<span class="s2">"url"</span><span class="o">:</span> <span class="s2">"<url du dépôt>"</span>
<span class="p">},</span>
<span class="s2">"author"</span><span class="o">:</span> <span class="s2">"<auteur>"</span><span class="p">,</span>
<span class="s2">"license"</span><span class="o">:</span> <span class="s2">"AGPL-3.0"</span><span class="p">,</span>
<span class="s2">"devDependencies"</span><span class="o">:</span> <span class="p">{</span>
<span class="s2">"broccoli"</span><span class="o">:</span> <span class="s2">"^0.16.9"</span><span class="p">,</span>
<span class="s2">"broccoli-funnel"</span><span class="o">:</span> <span class="s2">"^1.0.1"</span><span class="p">,</span>
<span class="s2">"broccoli-merge-trees"</span><span class="o">:</span> <span class="s2">"^1.0.0"</span><span class="p">,</span>
<span class="s2">"broccoli-sourcemap-concat"</span><span class="o">:</span> <span class="s2">"^1.1.6"</span><span class="p">,</span>
<span class="s2">"broccoli-template"</span><span class="o">:</span> <span class="s2">"^0.1.1"</span><span class="p">,</span>
<span class="s2">"broccoli-uglify-js"</span><span class="o">:</span> <span class="s2">"^0.1.3"</span><span class="p">,</span>
<span class="c1">// outil de gestion des briques Canopsis</span>
<span class="s2">"canopsis-ui-toolbelt"</span><span class="o">:</span> <span class="s2">"https://git.canopsis.net/canopsis/canopsis-ui-toolbelt/repository/archive.tar.gz?ref=<branche git de canopsis-ui-toolbelt>"</span><span class="p">,</span>
<span class="s2">"jsdoc"</span><span class="o">:</span> <span class="s2">"^3.3.0"</span><span class="p">,</span>
<span class="s2">"pre-commit"</span><span class="o">:</span> <span class="s2">"^1.1.1"</span>
<span class="p">},</span>
<span class="s2">"pre-commit"</span><span class="o">:</span> <span class="p">[</span>
<span class="s2">"lint"</span><span class="p">,</span>
<span class="s2">"doc"</span>
<span class="p">]</span>
<span class="p">}</span></code></pre>
<p>Puis, dans un dossier <code>src</code> on placera le code source de la brique :</p>
<ul>
<li><code>components/mycomponent/component.js</code></li>
<li><code>components/mycomponent/template.hbs</code></li>
<li><code>renderers/renderer-myrole.hbs</code></li>
<li><code>editors/editor-myrole.hbs</code></li>
<li><code>widgets/mywidget/controller.js</code></li>
<li><code>widgets/mywidget/mywidget.hbs</code></li>
<li><code>mixins/mymixin.js</code></li>
</ul><p>L'outil <code>canopsis-ui-toolbelt</code> se charge de :</p>
<ul>
<li>récupérer récursivement tout le code JS dans le dossier <code>src</code>
</li>
<li>référencer le code JS dans le fichier <code>init.js</code> qui représente la brique</li>
<li>récupérer récursivement tout les templates (<code>*.hbs</code>) dans le dossier <code>src</code>
</li>
<li>référencer les templates également dans le fichier <code>init.js</code>
</li>
<li>référencer les templates dans <code>Ember.TEMPLATES</code> :
<ul>
<li>pour un composant, il est requis d'avoir le dossier <code>components/<mycomponent></code>, afin que le template soit reconnu comme étant un template de composant</li>
<li>pour le reste, le nom du template dans <em>Ember</em> sera le nom du fichier sans extension</li>
</ul>
</li>
</ul><p><em>NB: Le découpage n'est donc obligatoire que pour les composants, le reste peut être mis en vrac dans <code>src</code>.</em></p>
<p>Une fois que l'on a créé tout cela (ou récupéré le dépôt Git), on peut finaliser la brique :</p>
<pre><code>$ npm install
$ npm run compile
</code></pre>
<p>Chaque fichier source contiendra au moins un <strong>Ember Initializer</strong> :</p>
<pre><code class="javascript"><span class="nx">Ember</span><span class="p">.</span><span class="nx">Application</span><span class="p">.</span><span class="nx">initializer</span><span class="p">({</span>
<span class="nx">name</span><span class="o">:</span> <span class="s1">'MyModule'</span><span class="p">,</span>
<span class="nx">after</span><span class="o">:</span> <span class="p">[</span><span class="s1">'Dependency1'</span><span class="p">,</span> <span class="s1">'Dependency2'</span><span class="p">],</span>
<span class="nx">initialize</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">container</span><span class="p">,</span> <span class="nx">application</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">Dependency1</span> <span class="o">=</span> <span class="nx">container</span><span class="p">.</span><span class="nx">lookupFactory</span><span class="p">(</span><span class="s1">'deptype:dependency1'</span><span class="p">),</span>
<span class="nx">Dependency2</span> <span class="o">=</span> <span class="nx">container</span><span class="p">.</span><span class="nx">lookupFactory</span><span class="p">(</span><span class="s1">'deptype:dependency2'</span><span class="p">);</span>
<span class="c1">// do stuff</span>
<span class="nx">application</span><span class="p">.</span><span class="nx">register</span><span class="p">(</span><span class="s1">'modtype:mymodule'</span><span class="p">,</span> <span class="cm">/* stuff */</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span></code></pre>
<p>Cela permet ainsi de s'assurer du bon chargement de chaque module.</p>
<p>Au final, pour installer une brique, il suffit de :</p>
<ul>
<li>cloner le dépôt finalisé dans : <code>/opt/canopsis/var/www/canopsis</code>
</li>
<li>lancer la commande <code>webmodulemanager enable mybrick</code> afin de l'activer au chargement de l'UI</li>
</ul><p>L'ensemble des briques existantes (hormis celle par défaut livrées avec Canopsis) sont disponible <a href="https://git.canopsis.net/groups/canopsis-ui-bricks">ici</a>.</p>
<h4 id="b-les-composants">B. Les composants</h4>
<p>Le composant, l'élément le plus basique de l'UI, sera utilisé par tout les autres éléments.<br>
Il s'agit simplement d'un composant <em>Ember</em> avec un template à fournir.</p>
<p>On définira notre composant dans <code>src/components/mycomponent/component.js</code> :</p>
<pre><code class="javascript"><span class="nx">Ember</span><span class="p">.</span><span class="nx">Application</span><span class="p">.</span><span class="nx">initializer</span><span class="p">({</span>
<span class="nx">name</span><span class="o">:</span> <span class="s1">'MyComponent'</span><span class="p">,</span>
<span class="nx">after</span><span class="o">:</span> <span class="p">[],</span>
<span class="nx">initialize</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">container</span><span class="p">,</span> <span class="nx">application</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">MyComponent</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Component</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">init</span><span class="o">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">_super</span><span class="p">.</span><span class="nx">apply</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="nx">arguments</span><span class="p">);</span>
<span class="c1">// faire des choses</span>
<span class="p">},</span>
<span class="nx">actions</span><span class="o">:</span> <span class="p">{</span>
<span class="c1">// on implémente les actions, déclenchable depuis le template</span>
<span class="nx">foo</span><span class="o">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">alert</span><span class="p">(</span><span class="s1">'bar'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="nx">application</span><span class="p">.</span><span class="nx">register</span><span class="p">(</span><span class="s1">'component:component-mycomponent'</span><span class="p">,</span> <span class="nx">MyComponent</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span></code></pre>
<p>Et le template dans <code>src/components/mycomponent/template.hbs</code> :</p>
<pre><code class="html"><span class="nt"><h1></span>My awesome component<span class="nt"></h1></span>
<span class="nt"><a</span> <span class="err">{{</span><span class="na">action</span> <span class="na">foo</span><span class="err">}}</span><span class="nt">></span>Launch action<span class="nt"></a></span></code></pre>
<h4 id="c-les-rendererséditeurs">C. Les renderers/éditeurs</h4>
<p>Une fois nos composants définis, on est en mesure de les utiliser dans des <strong>renderers</strong> ou des <strong>éditeurs</strong>.</p>
<p>Le but de ces éléments est simple :</p>
<ul>
<li>lorsqu'on demande l'affichage d'une donnée avec le composant <code>renderer</code> :
<ul>
<li>le champ <code>role</code> du schéma JSON est également lu</li>
<li>si un <em>renderer</em> du même nom est trouvé, il est utilisé dans le template</li>
<li>sinon, on affiche la donnée telle quelle</li>
</ul>
</li>
<li>lorsqu'un formulaire d'édition est généré à partir du schéma JSON, en utilisant le composant <code>editor</code> :
<ul>
<li>le champ <code>role</code> est lu</li>
<li>si un éditeur du même nom est trouvé, il est utilisé dans le formulaire</li>
<li>sinon, on utilise l'éditeur par défaut</li>
</ul>
</li>
</ul><p>Les <em>renderers</em>/<em>éditeurs</em> ne sont donc que de simple templates <a href="https://github.com/tildeio/htmlbars">HTMLBars</a>.</p>
<p>On aura le <em>renderer</em> dans <code>src/renderers/renderer-myrole.hbs</code> :</p>
<pre><code class="html"><span class="nt"><p></span>My rendered data: {{value}}<span class="nt"></p></span>
{{component-mycomponent}}</code></pre>
<p>Et l'<em>éditeur</em> dans <code>src/editors/editor-myrole.hbs</code> :</p>
<pre><code class="html"><span class="nt"><p></span>My data is being edited :<span class="nt"></p></span>
{{input type="password" value=attr.value}}
{{component-mycomponent}}</code></pre>
<h4 id="d-les-widgets">D. Les widgets</h4>
<p>Enfin, on aura les <strong>widgets</strong>, qui seront en mesure d'utiliser <em>composants</em> et<br><em>renderers</em> afin d'implémenter des outils de visualisation de données plus complexes.</p>
<p>Un <em>widget</em> est un <strong>MVC</strong> complet :</p>
<ul>
<li>on implémente un contrôleur</li>
<li>on implémente des <em>mixins</em> qui viendront s'appliquer à la vue</li>
<li>on écrit un template</li>
</ul><p>Ce qui donne :</p>
<pre><code class="javascript"><span class="nx">Ember</span><span class="p">.</span><span class="nx">Application</span><span class="p">.</span><span class="nx">initializer</span><span class="p">({</span>
<span class="nx">name</span><span class="o">:</span> <span class="s1">'MyWidget'</span><span class="p">,</span>
<span class="nx">after</span><span class="o">:</span> <span class="p">[</span><span class="s1">'WidgetFactory'</span><span class="p">],</span>
<span class="nx">initialize</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">container</span><span class="p">,</span> <span class="nx">application</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">WidgetFactory</span> <span class="o">=</span> <span class="nx">container</span><span class="p">.</span><span class="nx">lookupFactory</span><span class="p">(</span><span class="s1">'factory:widget'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">widgetOptions</span> <span class="o">=</span> <span class="p">{};</span>
<span class="kd">var</span> <span class="nx">MyWidgetViewMixin</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Mixin</span><span class="p">.</span><span class="nx">create</span><span class="p">({</span>
<span class="nx">didInsertElement</span><span class="o">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">_super</span><span class="p">.</span><span class="nx">apply</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="nx">arguments</span><span class="p">);</span>
<span class="c1">// faire des choses</span>
<span class="p">},</span>
<span class="nx">willDestroyElement</span><span class="o">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">_super</span><span class="p">.</span><span class="nx">apply</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="nx">arguments</span><span class="p">);</span>
<span class="c1">// faire des choses</span>
<span class="p">},</span>
<span class="nx">actions</span><span class="o">:</span> <span class="p">{</span>
<span class="c1">// actions de la vue</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="kd">var</span> <span class="nx">widget</span> <span class="o">=</span> <span class="nx">WidgetFactory</span><span class="p">(</span><span class="s1">'mywidget'</span><span class="p">,</span> <span class="p">{</span>
<span class="nx">viewMixins</span><span class="o">:</span> <span class="p">[</span>
<span class="nx">MyWidgetViewMixin</span>
<span class="p">],</span>
<span class="nx">actions</span><span class="o">:</span> <span class="p">{</span>
<span class="c1">// actions du contrôleur</span>
<span class="p">},</span>
<span class="nx">init</span><span class="o">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// faire des choses</span>
<span class="p">},</span>
<span class="nx">findItems</span><span class="o">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// méthode appelée lors de la récupération des données pour le template</span>
<span class="p">}</span>
<span class="p">},</span> <span class="nx">widgetOptions</span><span class="p">);</span>
<span class="nx">application</span><span class="p">.</span><span class="nx">register</span><span class="p">(</span><span class="s1">'widget:mywidget'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span></code></pre>
<p>Ensuite, il est nécessaire de créer un schéma <code>widget.mywidget.json</code> qui sera utilisé pour configurer le widget lors de son ajout dans une vue :</p>
<pre><code class="javascript"><span class="p">{</span>
<span class="s2">"title"</span><span class="o">:</span> <span class="s2">"MyWidget"</span><span class="p">,</span>
<span class="s2">"description"</span><span class="o">:</span> <span class="s2">"Schéma de configuration de MyWidget"</span><span class="p">,</span>
<span class="s2">"metadata"</span><span class="o">:</span> <span class="p">{</span>
<span class="c1">// icône dans le formulaire d'ajout de widget</span>
<span class="s2">"icon"</span><span class="o">:</span> <span class="s2">"fa fa-cog"</span>
<span class="p">},</span>
<span class="s2">"categories"</span><span class="o">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="s2">"title"</span><span class="o">:</span> <span class="s2">"General"</span><span class="p">,</span>
<span class="s2">"keys"</span><span class="o">:</span> <span class="p">[</span><span class="s2">"foo"</span><span class="p">]</span>
<span class="p">}</span>
<span class="p">],</span>
<span class="s2">"type"</span><span class="o">:</span> <span class="s2">"object"</span><span class="p">,</span>
<span class="s2">"properties"</span><span class="o">:</span> <span class="p">{</span>
<span class="s2">"foo"</span><span class="o">:</span> <span class="p">{</span>
<span class="s2">"title"</span><span class="o">:</span> <span class="s2">"Foo"</span><span class="p">,</span> <span class="c1">// nom du champ dans le formulaire</span>
<span class="s2">"description"</span><span class="o">:</span> <span class="s2">"Foo field"</span><span class="p">,</span> <span class="c1">// tooltip dans le formulaire</span>
<span class="s2">"type"</span><span class="o">:</span> <span class="s2">"string"</span><span class="p">,</span>
<span class="s2">"role"</span><span class="o">:</span> <span class="s2">"myrole"</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre>
<h4 id="e-les-mixins">E. Les mixins</h4>
<p>Lorsque l'on a des fonctionnalités communes à plusieurs widgets (comme le <code>PeriodicRefresh</code> qui actualise le widget régulièrement), il convient de factoriser le code en un <strong>mixin</strong>.</p>
<p>Ce dernier pourra être ajouté et configuré au <em>widget</em> via l'UI, et ensuite appliquer la-dite configuration au contrôleur du widget :</p>
<pre><code class="javascript"><span class="nx">Ember</span><span class="p">.</span><span class="nx">Application</span><span class="p">.</span><span class="nx">initializer</span><span class="p">({</span>
<span class="nx">name</span><span class="o">:</span><span class="s1">'MyMixin'</span><span class="p">,</span>
<span class="nx">after</span><span class="o">:</span> <span class="p">[</span><span class="s1">'MixinFactory'</span><span class="p">],</span>
<span class="nx">initialize</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">container</span><span class="p">,</span> <span class="nx">application</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">Mixin</span> <span class="o">=</span> <span class="nx">container</span><span class="p">.</span><span class="nx">lookupFactory</span><span class="p">(</span><span class="s1">'factory:mixin'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">get</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">get</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">MyMixin</span> <span class="o">=</span> <span class="nx">Mixin</span><span class="p">(</span><span class="s1">'mymixin'</span><span class="p">,</span> <span class="p">{</span>
<span class="nx">mixinsOptionsReady</span><span class="o">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">_super</span><span class="p">.</span><span class="nx">apply</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="nx">arguments</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">mixinOptions</span> <span class="o">=</span> <span class="nx">get</span><span class="p">(</span><span class="s1">'mixinOptions.mymixin'</span><span class="p">);</span>
<span class="c1">// faire des choses</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="nx">application</span><span class="p">.</span><span class="nx">register</span><span class="p">(</span><span class="s1">'mixin:mymixin'</span><span class="p">,</span> <span class="nx">MyMixin</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span></code></pre>
<p>Et il ne manque plus que le schéma <code>mixin.mymixin.json</code> décrivant la configuration du <em>mixin</em> :</p>
<pre><code class="javascript"><span class="p">{</span>
<span class="s2">"title"</span><span class="o">:</span> <span class="s2">"MyMixin"</span><span class="p">,</span>
<span class="s2">"description"</span><span class="o">:</span> <span class="s2">"Schéma de configuration de MyMixin"</span><span class="p">,</span>
<span class="s2">"metadata"</span><span class="o">:</span> <span class="p">{</span>
<span class="c1">// description affiché dans l'UI</span>
<span class="s2">"description"</span><span class="o">:</span> <span class="s2">"Add stuff to widget"</span>
<span class="p">},</span>
<span class="s2">"categories"</span><span class="o">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="s2">"title"</span><span class="o">:</span> <span class="s2">"General"</span><span class="p">,</span>
<span class="s2">"keys"</span><span class="o">:</span> <span class="p">[</span><span class="s2">"bar"</span><span class="p">]</span>
<span class="p">}</span>
<span class="p">],</span>
<span class="s2">"type"</span><span class="o">:</span> <span class="s2">"object"</span><span class="p">,</span>
<span class="s2">"properties"</span><span class="o">:</span> <span class="p">{</span>
<span class="s2">"bar"</span><span class="o">:</span> <span class="p">{</span>
<span class="s2">"title"</span><span class="o">:</span> <span class="s2">"Bar"</span><span class="p">,</span>
<span class="s2">"description"</span><span class="o">:</span> <span class="s2">"Bar field"</span><span class="p">,</span>
<span class="s2">"type"</span><span class="o">:</span> <span class="s2">"string"</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre>
<h4 id="f-les-vues">F. Les vues</h4>
<p>Tout les outils sont désormais à notre disposition pour construire nos <em>dashboards</em>.</p>
<p>Donc si on résume :</p>
<ul>
<li>une <strong>vue</strong> est composée de <em>widgets</em>
</li>
<li>un <em>widget</em> est composé de <em>composants</em> et de <em>mixins</em>
</li>
</ul><p>L'UI fournit les outils qui permettent de construire ces vues, le résultat final est un document JSON stocké en base :</p>
<pre><code class="javascript"><span class="p">{</span>
<span class="s2">"_id"</span><span class="o">:</span> <span class="s2">"id de ma vue"</span><span class="p">,</span>
<span class="s2">"description"</span><span class="o">:</span> <span class="s2">"description de ma vue"</span>
<span class="s2">"crecord_name"</span><span class="o">:</span> <span class="s2">"nom de ma vue"</span><span class="p">,</span>
<span class="s2">"crecord_type"</span><span class="o">:</span> <span class="s2">"view"</span><span class="p">,</span>
<span class="s2">"author"</span><span class="o">:</span> <span class="s2">"<user qui a créé la vue>"</span><span class="p">,</span>
<span class="s2">"enable"</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="s2">"internal"</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="s2">"tags"</span><span class="o">:</span> <span class="p">[],</span>
<span class="c1">// le widget initial de la vue</span>
<span class="s2">"containerwidget"</span><span class="o">:</span> <span class="p">{</span>
<span class="c1">// identifiant du widget</span>
<span class="s2">"xtype"</span><span class="o">:</span> <span class="s2">"widgetcontainer"</span><span class="p">,</span>
<span class="s2">"title"</span><span class="o">:</span> <span class="s2">"container title vbox"</span><span class="p">,</span>
<span class="c1">// le widget "widgetcontainer" s'attend à avoir un champ "items"</span>
<span class="s2">"items"</span><span class="o">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="c1">// le widget "widgetwrapper" sert à inclure des widgets dans un container pour les placer correctement</span>
<span class="s2">"xtype"</span><span class="o">:</span> <span class="s2">"widgetwrapper"</span><span class="p">,</span>
<span class="s2">"title"</span><span class="o">:</span> <span class="s2">"wrapper"</span><span class="p">,</span>
<span class="c1">// le widget encapsulé :</span>
<span class="s2">"widget"</span><span class="o">:</span> <span class="p">{</span>
<span class="s2">"xtype"</span><span class="o">:</span> <span class="s2">"mywidget"</span><span class="p">,</span>
<span class="s2">"title"</span><span class="o">:</span> <span class="s2">"My awesome widget"</span><span class="p">,</span>
<span class="c1">// configuration spécifique au widget</span>
<span class="s2">"foo"</span><span class="o">:</span> <span class="s2">"bar"</span><span class="p">,</span>
<span class="c1">// mixins appliqués via l'UI :</span>
<span class="s2">"mixins"</span><span class="o">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="s2">"name"</span><span class="o">:</span> <span class="s2">"periodicrefresh"</span><span class="p">,</span>
<span class="c1">// paramètres du mixin</span>
<span class="s2">"refreshInterval"</span><span class="o">:</span> <span class="mi">60</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="s2">"name"</span><span class="o">:</span> <span class="s2">"mymixin"</span><span class="p">,</span>
<span class="c1">// paramètres du mixin</span>
<span class="s2">"bar"</span><span class="o">:</span> <span class="s2">"baz"</span>
<span class="p">}</span>
<span class="p">]</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">],</span>
<span class="c1">// le container de base de la vue à un widget de layout par défaut</span>
<span class="s2">"mixins"</span><span class="o">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="s2">"name"</span><span class="o">:</span> <span class="s2">"lightlayout"</span>
<span class="p">}</span>
<span class="p">]</span>
<span class="p">}</span>
<span class="p">}</span></code></pre>
<p>La totalité de l'UI est générée à partir de ces vues JSON, et est donc complètement personnalisable.</p>
<h2 id="le-bac-à-événements">Le bac à événements</h2>
<p>Parmi les vues par défaut qui sont livrées avec Canopsis, on trouve le <strong>Bac à événements</strong>.</p>
<p>Cette vue fournit un <em>dashboard</em> de supervision commun, unifiant ainsi la totalité des superviseurs remontant des informations à Canopsis.</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6c75742e696d2f4e4f39435252754476492f325757494b674355726b364574593175/2WWIKgCUrk6EtY1u" alt="screenshot du bac à événements" title="Source : https://lut.im/NO9CRRuDvI/2WWIKgCUrk6EtY1u"></p>
<p>Depuis ce bac, on voit apparaître les différentes alarmes avec comme informations :</p>
<ul>
<li>leurs sources d'émission (le connecteur)</li>
<li>leurs sources cible (composant/ressource, qui dans la plupart des cas correspond au couple <em>host</em>/<em>service</em>)</li>
<li>le dernier message associé à un <em>check</em> (Nagios, Shinken, Centreon, …)</li>
<li>le statut de l'alarme associé au <em>check</em> :
<ul>
<li>
<strong>Off</strong> : aucune alarme n'est présente</li>
<li>
<strong>On Going</strong> : un problème a été remonté et n'est toujours pas résolu</li>
<li>
<strong>Stealthy</strong> : une alarme a été remontée et est immédiatement repassée OK (durée paramétrable)</li>
<li>
<strong>Flapping</strong> : il y a eu X changements d'état en 1h sur l'alarme (durée et fréquence paramétrable)</li>
<li>
<strong>Cancelled</strong> : l'alarme a été annulée par un utilisateur (pour éviter les faux-positifs)</li>
</ul>
</li>
<li>l'état du <em>check</em> :
<ul>
<li>
<strong>INFO</strong> : tout va bien</li>
<li>
<strong>MINOR</strong> : équivalent au <em>Warning</em> de Nagios</li>
<li>
<strong>MAJOR</strong> : équivalent au <em>Critical</em> de Nagios</li>
<li>
<strong>CRITICAL</strong> : les <em>Unknown</em> de Nagios sont remontés en tant que tel, mais cela ne se limite pas à cette notion</li>
<li>
<strong>UNKNOWN</strong> : état non pris en charge à l'heure actuelle, c'est tout ce qui est supérieur à <em>CRITICAL</em> (<code>3</code>)</li>
</ul>
</li>
<li>la présence d'un acquittement et/ou d'un ticket :
<ul>
<li>les connecteurs pour Nagios, Shinken, etc… peuvent remonter les acquittements posés</li>
<li>depuis Canopsis, on peut en poser manuellement</li>
</ul>
</li>
<li>la date du dernier changement d'état</li>
</ul><p>Sur chaque alarme, on peut réaliser différentes actions :</p>
<ul>
<li>l'acquittement (permet de déverrouiller les autres actions), cela émettra le même événement qui serait remonté par un superviseur, soit un événement de type <code>ack</code>
</li>
<li>une suppression de l'acquittement, cela émettra un événement de type <code>ackremove</code>
</li>
<li>une annulation de l'alarme, cela émettra un événement de type <code>cancel</code>
</li>
<li>une fois l'alarme annulée, on peut annuler cette action, cela émettra un événement de type <code>uncancel</code>
</li>
<li>une déclaration de ticket :
<ul>
<li>cela émettra un événement de type <code>declareticket</code>
</li>
<li>cet événement pourra être capturé par le moteur <code>event_filter</code> pour déclencher un job (voir plus bas) qui communiquera le ticket à un outil tiers (par exemple <a href="http://www.combodo.com/itop">iTop</a>)</li>
</ul>
</li>
<li>une association de ticket existant :
<ul>
<li>cela émettra un événement de type <code>assocticket</code>
</li>
<li>on peut imaginer que le job qui communique la déclaration du ticket à l'outil tiers récupère le numéro du ticket nouvellement créé, et l'associe automatiquement</li>
</ul>
</li>
<li>une requalification de l'événement :
<ul>
<li>cela changera l'état du <em>check</em> manuellement, et ce dernier gardera cet état jusqu'à la résolution de l'alarme</li>
<li>cela émettra un événement de type <code>check</code>, comme le superviseur</li>
<li>la seule différence est la présence d'un champ <code>keep_state</code> qui vaut <code>true</code> dans l'événement</li>
</ul>
</li>
</ul><p>Toutes ces actions permettent ainsi d'unifier une supervision hétérogène, et l'administrateur (technique) ne devra utiliser/maîtriser qu'un seul outil.</p>
<h2 id="sélecteur-et-widget-météo--simplifier-la-supervision">Sélecteur et widget météo : simplifier la supervision</h2>
<p>Lorsque l'on supervise une grosse infrastructure, les informations remontées via les <em>checks</em> deviennent tout de suite beaucoup plus conséquentes. C'est pourquoi nous avons mis en place la possibilité d'agréger ces <em>checks</em> afin d'avoir une visibilité plus simple sur l'infra.</p>
<p>Cette agrégation se fait à l'aide des <strong>sélecteurs</strong> :</p>
<ul>
<li>on créé un filtre d'événements</li>
<li>on applique un algorithme à l'état de chaque <em>check</em> qui matche le filtre (actuellement seul l'algo <strong>Worst State</strong> est disponible)</li>
<li>on produit un événement de type <code>selector</code> qui contient :
<ul>
<li>l'état agrégé</li>
<li>le champ <code>output</code> qui est le rendu du template spécifié dans la configuration du sélecteur</li>
</ul>
</li>
<li>si le sélecteur est configuré pour, on déclenche le calcul des <strong>SLA</strong> :
<ul>
<li>sur une période de temps (spécifiée dans la configuration du sélecteur)</li>
<li>on calcule le pourcentage de temps passé sur chaque état possible</li>
<li>on produit une <em>métrique</em> pour chacune de ces métriques, ainsi qu'un événement de type <code>sla</code>
</li>
<li>l'état remonté par l'événement correspond aux seuils de SLA configurés dans le sélecteur</li>
</ul>
</li>
</ul><p>Le résultat est finalement affichable avec un widget <strong>weather</strong> :</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6c75742e696d2f72466a387241725044672f383866507a4643305931354b35423030/88fPzFC0Y15K5B00" alt="screenshot widget weather" title="Source : https://lut.im/rFj8rArPDg/88fPzFC0Y15K5B00"></p>
<p><em>NB: Le sélecteur peut afficher également des checks unitairement</em></p>
<p>On peut ainsi noter les couleurs suivantes :</p>
<ul>
<li>
<em>vert</em> : l'état du sélecteur est <code>INFO</code>
</li>
<li>
<em>jaune</em> : l'état du sélecteur est <code>MINOR</code>
</li>
<li>
<em>orange</em> : l'état du sélecteur est <code>MAJOR</code>
</li>
<li>
<em>rouge</em> : l'état du sélecteur est <code>CRITICAL</code>
</li>
<li>
<em>violet</em> : toutes les alarmes du sélecteur ont été acquittée</li>
<li>la couleur du widget est celle du pire état des sélecteurs positionnés dans celui ci</li>
</ul><p>Un clic sur le sélecteur dans le widget nous redirigera sur le <em>Bac à événements</em>, filtré avec le filtre du sélecteur.</p>
<h2 id="monitoring-et-séries">Monitoring et séries</h2>
<p>Chaque connecteur, moteur, et sélecteur produisent des données de performances :</p>
<ul>
<li>temps d'exécution d'un <em>check</em>
</li>
<li>usage CPU/RAM/Disque</li>
<li>temps moyen passé sur un événement</li>
<li>nombre moyen d'événements par seconde</li>
<li>donnée de SLA</li>
<li>…</li>
</ul><p>Tout cela est remonté dans Canopsis dans un événement via le champ <code>perf_data_array</code> :</p>
<pre><code class="javascript"><span class="p">{</span>
<span class="c1">// info de l'événement classique</span>
<span class="s2">"perf_data_array"</span><span class="o">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="s2">"metric"</span><span class="o">:</span> <span class="s2">"nom_de_ma_metrique"</span><span class="p">,</span>
<span class="s2">"value"</span><span class="o">:</span> <span class="mf">42.1337</span><span class="p">,</span>
<span class="s2">"type"</span><span class="o">:</span> <span class="s2">"GAUGE"</span><span class="p">,</span> <span class="c1">// GAUGE, COUNTER, ABSOLUTE ou DERIVE</span>
<span class="c1">// champs optionnels</span>
<span class="s2">"unit"</span><span class="o">:</span> <span class="s2">"..."</span><span class="p">,</span>
<span class="s2">"min"</span><span class="o">:</span> <span class="mi">0</span><span class="p">,</span>
<span class="s2">"max"</span><span class="o">:</span> <span class="mf">1337.42</span><span class="p">,</span>
<span class="s2">"warn"</span><span class="o">:</span> <span class="mi">1000</span><span class="p">,</span>
<span class="s2">"crit"</span><span class="o">:</span> <span class="mi">1300</span>
<span class="p">}</span>
<span class="p">]</span>
<span class="p">}</span></code></pre>
<p>Ces données vont être historisée dans Canopsis. On peut donc noter 4 types de métriques :</p>
<ul>
<li>
<code>GAUGE</code> : on historise la valeur telle quelle</li>
<li>
<code>COUNTER</code> : lorsque l'on récupère la valeur, on fait l'addition des valeurs historisées</li>
<li>
<code>ABSOLUTE</code> : on historise la valeur absolue</li>
<li>
<code>DERIVE</code> : il s'agit de la valeur dérivée par rapport au temps</li>
</ul><p>Une métrique est ensuite identifiée par :</p>
<ul>
<li>le composant de l'événement</li>
<li>la ressource de l'événement</li>
<li>le nom de la métrique dans le tableau de <em>perfdata</em>
</li>
</ul><p>Le tout peut être affiché dans un chart :</p>
<p><img src="//img.linuxfr.org/img/68747470733a2f2f6c75742e696d2f7435554f6566485170612f385a48595364427878624f444f4a5377/8ZHYSdBxxbODOJSw" alt="screenshot de timechart" title="Source : https://lut.im/t5UOefHQpa/8ZHYSdBxxbODOJSw"></p>
<p>On est ainsi en mesure de sélectionner un ensemble de métrique avec un filtre basé sur des expressions régulières :</p>
<ul>
<li><code>co:.*\.myhost re:cpu-.* me:system me:user me:wait</code></li>
</ul><p>Qui se traduit en filtre MongoDB :</p>
<pre><code class="javascript"><span class="p">{</span>
<span class="s1">'$and'</span><span class="o">:</span> <span class="p">[</span>
<span class="p">{</span><span class="s1">'component'</span><span class="o">:</span> <span class="p">{</span><span class="s1">'$regex'</span><span class="o">:</span> <span class="s1">'.*\.myhost'</span><span class="p">}},</span>
<span class="p">{</span><span class="s1">'resource'</span><span class="o">:</span> <span class="p">{</span><span class="s1">'$regex'</span><span class="o">:</span> <span class="s1">'cpu.*'</span><span class="p">}},</span>
<span class="p">{</span>
<span class="s1">'$or'</span><span class="o">:</span> <span class="p">[</span>
<span class="p">{</span><span class="s1">'name'</span><span class="o">:</span> <span class="p">{</span><span class="s1">'$regex'</span><span class="o">:</span> <span class="s1">'system'</span><span class="p">}},</span>
<span class="p">{</span><span class="s1">'name'</span><span class="o">:</span> <span class="p">{</span><span class="s1">'$regex'</span><span class="o">:</span> <span class="s1">'user'</span><span class="p">}},</span>
<span class="p">{</span><span class="s1">'name'</span><span class="o">:</span> <span class="p">{</span><span class="s1">'$regex'</span><span class="o">:</span> <span class="s1">'wait'</span><span class="p">}}</span>
<span class="p">]</span>
<span class="p">}</span>
<span class="p">]</span>
<span class="p">}</span></code></pre>
<p>Une fois les identifiants de métriques récupérés, on peut aller demander les points stockés en base, dans une fenêtre de temps bien définie.</p>
<p>Une <strong>série</strong> est donc munie :</p>
<ul>
<li>d'un filtre de métrique</li>
<li>d'une période d'agrégation avec un opérateur d'agrégation (le manager de <em>perfdata</em> nous retournera les données agrégées)</li>
<li>d'une période de consolidation</li>
<li>d'une formule de consolidation</li>
</ul><p>Ici la partie consolidation sert à consolider les différents points agrégés en un seul, afin de produire une nouvelle métrique.</p>
<p>La formule se construit de la manière suivante :</p>
<ul>
<li>on a des opérateurs qui prennent en paramètre un filtre de métrique qui sera appliqué sur l'ensemble de métriques déjà sélectionnées</li>
<li>ces opérateurs retournent un point consolidé</li>
<li>on peut les utiliser dans une expression mathématique classique</li>
</ul><p>Par exemple, <code>SUM("me:.*") / COUNT("me:.*")</code>, permet de réaliser une moyenne.</p>
<h2 id="les-tâches-ordonnancées-et-les-notifications">Les tâches ordonnancées et les notifications</h2>
<p>Parmi les moteurs de Canopsis, certains sont dédiés à une fonction précise : exécuter une tâche.</p>
<p>Il y a donc un moteur <strong>scheduler</strong> qui, régulièrement, va chercher à exécuter des jobs configurés selon une <a href="http://www.kanzaki.com/docs/ical/rrule.html">règle de récurrence</a>.<br>
En fonction du type de job, ce dernier sera redirigé au moteur correspondant, que l'on appellera un <strong>taskhandler</strong>.</p>
<p>Cela permet de construire un équivalent de <code>crontab</code> au sein de Canopsis.</p>
<p>Ces <em>taskhandlers</em> ne servent pas uniquement à l'exécution de tâches ordonnancées, ils peuvent être utilisés en tant que notification :</p>
<ul>
<li>une règle du moteur <em>event_filter</em> peut déclencher l'exécution d'un job si l'événement reçu matche le filtre de la règle</li>
<li>par exemple, à la réception d'un événement <code>declareticket</code>, on peut lancer l'exécution d'un job réalisant une requête d'insertion de ticket à un outil tiers</li>
</ul><h2 id="conclusion">Conclusion</h2>
<p>Grâce à tout ces éléments, Canopsis est en mesure de répondre à de nombreux besoins, allant de la supervision simple, à l'analyse poussée de données afin de générer des rapports sur une infrastructure (ou autre).</p>
<p>Notre objectif premier est la modularité du projet, afin de pouvoir fournir une solution sur mesure et de ne pas transformer l'outil en énorme usine à gaz. Pour résumer, on a répondu à cette problématique avec :</p>
<ul>
<li>le découpage du <em>backend</em> en projets Python embarquant : un <em>manager</em>, éventuellement un moteur et un <em>webservice</em>
</li>
<li>le découpage du <em>frontend</em> en briques embarquant : des composants, des <em>renderers</em>, des <em>éditeurs</em>, des <em>mixins</em>, des <em>widgets</em>
</li>
<li>la schématisation des données et à l'avenir des actions possibles sur cette dernière (transformation, schématisation de l'API des <em>managers</em>, …)</li>
<li>le développement d'API générique permettant le changement de technologies sans modification du code</li>
</ul><p>Beaucoup de choses ont été faites, et beaucoup de travail reste à faire, notamment :</p>
<ul>
<li>la finalisation des rôles <strong>Ansible</strong>
</li>
<li>l'intégration d'une notion de graphe pour les entités qui sont le centre de toutes les données stockées par Canopsis, afin de rendre le système complètement réflexif</li>
<li>une séparation totale du <em>backend</em> et du <em>frontend</em>, permettant d'utiliser l'un sans l'autre</li>
<li>génération de code à partir des schémas</li>
<li>…</li>
</ul><p>Bref, Canopsis est en constante évolution, et touche à de nombreuses problématiques toutes plus intéressantes les unes que les autres.</p></div><div><a href="https://linuxfr.org/news/presentation-technique-de-canopsis.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/108104/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/presentation-technique-de-canopsis#comments">ouvrir dans le navigateur</a>
</p>
David DelassusBenoît SibaudNÿcopalm123claudexbubar🦥https://linuxfr.org/nodes/108104/comments.atomtag:linuxfr.org,2005:Diary/363312016-01-27T17:07:40+01:002016-01-27T17:07:40+01:00Code Python en BDD avec CanopsisLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<h2 id="1-introduction">1. Introduction</h2>
<p>Avant de rentrer dans le vif du sujet, une petite présentation de Canopsis s'impose.</p>
<p><img src="//img.linuxfr.org/img/687474703a2f2f7777772e63616e6f707369732e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031352f30312f63616e6f707369732d333030783131332e706e67/canopsis-300x113.png" alt="Canopsis Logo" title="Source : http://www.canopsis.com/wp-content/uploads/2015/01/canopsis-300x113.png"></p>
<p>Il s'agit d'une solution d'hypervision sous licence AGPL3, capable d'agréger de nombreuses sources de données afin de les présenter à l'utilisateur, et de lui permettre d'interagir avec de manière standardisée et cohérente.</p>
<p>L'architecture du projet, grossièrement simplifiée, se compose des éléments suivants :</p>
<ul>
<li>des connecteurs récupèrent les données depuis différentes sources (Nagios/Shinken/Icinga/…, jMeter, Sikuli, BDD type SQL, API REST, …), et les envoient sur un bus de données sous forme d'événement standardisé</li>
<li>des moteurs consomment les événements afin de les traiter et de les stocker de manière cohérente en base de données</li>
<li>une API REST fournit un accès à ces données</li>
<li>un application web complètement personnalisable permet à l'utilisateur de construire ses vues et de décider comment afficher, valoriser et interagir avec ces données</li>
</ul><p>Les parties moteurs et API REST sont développées en Python, et c'est ce qui va nous intéresser dans cet article.</p>
<h2 id="2-charger-le-code-dynamiquement">2. Charger le code dynamiquement</h2>
<p>Du travail est en cours sur ce projet afin de généraliser la modularité du projet.</p>
<p>Actuellement, un moteur se présente comme un daemon, et s'implémente en surclassant une classe de base abstraite afin d'implémenter certaines méthodes.</p>
<p>C'est certes simple, mais pas suffisamment souple car cela ne permet pas de bien découper le projet quand les fonctionnalités s'additionnent, on se retrouve ainsi avec un paquet python <code>engines</code> qui fait un peu fourre tout.</p>
<p>L'idée était donc de rentre tout cela plus modulaire. Et la solution adoptée fut donc de spécifier dans le fichier de configuration l'implémentation des méthodes. La classe de base (que l'on n'a plus besoin de toucher) va charger le code tel qu'il est spécifié dans la configuration.</p>
<p>Un exemple vaut mieux qu'un long discours.</p>
<p>Avec ce fichier de configuration :</p>
<pre><code class="ini"><span class="k">[engine:myengine]</span>
<span class="na">event_processing</span><span class="o">=</span><span class="s">canopsis.myfeature.process.event_processing</span></code></pre>
<p>Le moteur saura aller chercher la fonction <code>event_processing</code> dans le paquet python <code>canopsis.myfeature.process</code>, et l'implémentation se résume à ceci :</p>
<pre><code class="python"><span class="k">def</span> <span class="nf">event_processing</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="o">**</span><span class="n">_</span><span class="p">):</span>
<span class="c"># do something with event</span></code></pre>
<p>Ce système permet donc de mieux découper le code, chaque fonctionnalité apporte ses propres implémentations pour chaque aspect du projet.</p>
<p>Cela repose notamment sur un petit utilitaire :</p>
<pre><code class="python"><span class="kn">from</span> <span class="nn">canopsis.common.utils</span> <span class="kn">import</span> <span class="n">lookup</span>
<span class="n">event_processing</span> <span class="o">=</span> <span class="n">lookup</span><span class="p">(</span><span class="s">'canopsis.myfeature.process.event_processing'</span><span class="p">)</span></code></pre>
<p>Nous visons a démocratiser dans le projet ce genre d'usage, toujours pour une meilleure découpe, et une meilleure maintenabilité.</p>
<h2 id="3-le-vif-du-sujet-enfin">3. Le vif du sujet (enfin)</h2>
<p>En parcourant un peu le web, je tombe sur un <a href="https://nvbn.github.io/2016/01/04/import-from-github/">article</a> en particulier.</p>
<p>TL;DR: cela présente le mécanisme qui permet d'agrémenter le système d'import de Python avec de nouvelles fonctionnalités (ici un import depuis un dépôt Github).</p>
<p>Bon, fonctionnalité un peu risquée dans le cas où le code dépend du bon vouloir d'un inconnu à ne pas supprimer/casser son code.</p>
<p>MAIS, le mécanisme présenté peut avoir d'autres usages, c'est donc notre cas.</p>
<p>Ici, il est utilisé afin de stocker du code dans une base de données :</p>
<ul>
<li>document MongoDB</li>
<li>entrée dans une table SQL</li>
<li>… (avec le système de <code>storage</code>, nous sommes capable de développer un driver pour n'importe quelle base de données)</li>
</ul><p>Ce qui permet ainsi de stocker le code des moteurs (et autres à l'avenir) dans MongoDB actuellement :</p>
<pre><code class="json"><span class="p">{</span>
<span class="nt">"_id"</span><span class="p">:</span> <span class="s2">"myfeature"</span><span class="p">,</span>
<span class="nt">"src"</span><span class="p">:</span> <span class="s2">"def event_processing(event, **_):\n # do something with event\n"</span>
<span class="p">}</span></code></pre>
<p>Et de le référencer dans la configuration :</p>
<pre><code class="ini"><span class="k">[engine:myengine]</span>
<span class="na">event_processing</span><span class="o">=</span><span class="s">canopsis.pyloader.myfeature.event_processing</span></code></pre>
<p>Après cela, nous serons en mesure d'écrire un schéma JSON décrivant le document en base, et ce dernier sera utilisé par l'application web pour générer automatiquement un modèle, utilisable pour faire un listing des morceaux de code présent en base.</p>
<p>L'édition du code pourra ainsi se faire depuis l'application web de Canopsis, même si ce n'est pas l'objectif premier, cela permettra de modifier à l'exécution le traitement des données, par le biais de l'API REST.</p>
<p>On peut même imaginer une sorte de <em>pastebin</em> contenant diverses implémentations, qu'il suffira d'injecter pour tester avant de considérer un merge.</p>
<h2 id="4-conclusion">4. Conclusion</h2>
<p>Cette fonctionnalité, simple mais efficace, est intégrée à un plus gros chantier (de migration du code utilisant l'ancienne API pour les moteurs, à la nouvelle API plus dynamique), et ne sera donc pas disponible tout de suite.</p>
<p>En attendant, on peut toujours suivre l'avancée du projet sur notre <a href="https://git.canopsis.net/explore">Gitlab</a>.</p><div><a href="https://linuxfr.org/users/linkdd/journaux/code-python-en-bdd-avec-canopsis.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/108009/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/linkdd/journaux/code-python-en-bdd-avec-canopsis#comments">ouvrir dans le navigateur</a>
</p>
David Delassushttps://linuxfr.org/nodes/108009/comments.atom