tag:linuxfr.org,2005:/tags/hypervision/publicLinuxFr.org : les contenus étiquetés avec « hypervision »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: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.atom