<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[null]]></title><description><![CDATA[null]]></description><link>https://techblog.wifirst.net/</link><image><url>https://techblog.wifirst.net/favicon.png</url><title>null</title><link>https://techblog.wifirst.net/</link></image><generator>Ghost 5.79</generator><lastBuildDate>Thu, 08 Jan 2026 05:31:45 GMT</lastBuildDate><atom:link href="https://techblog.wifirst.net/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Flash Talk du Sysadmin Day : Molecule, ou comment tester ses rôles Ansible]]></title><description><![CDATA[<p>En novembre dernier, <em><a href="https://www.linkedin.com/in/grosjeancyril/?ref=techblog.wifirst.net">Cyril Grosjean</a> </em>est intervenu lors de la conf&#xE9;rence Sysadmin Day #9 dans un flash talk consacr&#xE9; &#xE0; Molecule, un outil de gestion des tests pour les r&#xF4;les Ansible.</p><p>Retour sur son Flash Talk en vid&#xE9;o : </p><!--kg-card-begin: html--><iframe width="560" height="315" src="https://www.youtube.com/embed/k1KkTJ331aQ" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><!--kg-card-end: html--><p></p><p>Si vous &#xEA;tes passionn&</p>]]></description><link>https://techblog.wifirst.net/flash-talk-ansible-molecule/</link><guid isPermaLink="false">5e68bbd38d46e900012804c2</guid><category><![CDATA[molecule]]></category><category><![CDATA[sysadmin]]></category><category><![CDATA[devops]]></category><category><![CDATA[cloud]]></category><category><![CDATA[container]]></category><category><![CDATA[ansible]]></category><category><![CDATA[openstack]]></category><dc:creator><![CDATA[Olivier Buisson]]></dc:creator><pubDate>Wed, 11 Mar 2020 16:07:36 GMT</pubDate><content:encoded><![CDATA[<p>En novembre dernier, <em><a href="https://www.linkedin.com/in/grosjeancyril/?ref=techblog.wifirst.net">Cyril Grosjean</a> </em>est intervenu lors de la conf&#xE9;rence Sysadmin Day #9 dans un flash talk consacr&#xE9; &#xE0; Molecule, un outil de gestion des tests pour les r&#xF4;les Ansible.</p><p>Retour sur son Flash Talk en vid&#xE9;o : </p><!--kg-card-begin: html--><iframe width="560" height="315" src="https://www.youtube.com/embed/k1KkTJ331aQ" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><!--kg-card-end: html--><p></p><p>Si vous &#xEA;tes passionn&#xE9; par Ansible, les containers et les infrastructures Cloud, vous pouvez rejoindre Cyril dans son quotidien, postulez sur notre offre &quot;<a href="https://www.welcometothejungle.com/fr/companies/wifirst/jobs/ingenieur-systeme-et-reseau-dans-l-equipe-infrastructure-du-systeme-d-information-h-f_paris?ref=techblog.wifirst.net">Ing&#xE9;nieur Syst&#xE8;me et R&#xE9;seaux (Orient&#xE9; Linux) H/F</a>&quot;</p>]]></content:encoded></item><item><title><![CDATA[Wifirst participera aux conférences PyCon FR et Sysadmin Days]]></title><description><![CDATA[<p>Du 31 octobre au 3 novembre se tiendra la conf&#xE9;rence <a href="https://www.pycon.fr/2019/?ref=techblog.wifirst.net">Pycon FR 2019</a>. <strong>Wifirst</strong> est fi&#xE8;re de sponsoriser l&apos;&#xE9;v&#xE8;nement permettant ainsi &#xE0; la communaut&#xE9; des utilisateurs de Python de se r&#xE9;unir et d&apos;&#xE9;changer. L&apos;</p>]]></description><link>https://techblog.wifirst.net/wifirst-conference/</link><guid isPermaLink="false">5db2ae478d46e90001280454</guid><dc:creator><![CDATA[Olivier Buisson]]></dc:creator><pubDate>Fri, 25 Oct 2019 10:35:58 GMT</pubDate><content:encoded><![CDATA[<p>Du 31 octobre au 3 novembre se tiendra la conf&#xE9;rence <a href="https://www.pycon.fr/2019/?ref=techblog.wifirst.net">Pycon FR 2019</a>. <strong>Wifirst</strong> est fi&#xE8;re de sponsoriser l&apos;&#xE9;v&#xE8;nement permettant ainsi &#xE0; la communaut&#xE9; des utilisateurs de Python de se r&#xE9;unir et d&apos;&#xE9;changer. L&apos;&#xE9;v&#xE8;nement aura lieu &#xE0; l&apos;universit&#xE9; de Bordeaux. </p><figure class="kg-card kg-image-card"><img src="https://techblog.wifirst.net/content/images/2019/10/pyconfr2019.png" class="kg-image" alt loading="lazy"></figure><p>En plus du sponsoring, <strong>Wifirst</strong> sera pr&#xE9;sent par l&apos;interm&#xE9;diaire de nos conf&#xE9;renciers qui ont &#xE9;t&#xE9; s&#xE9;lectionn&#xE9;s par l&apos;organisation.</p><p>Samedi 2 Novembre &#xE0; 14h30, <em>Romain Bellan</em> animera une session de &quot;live coding&quot; intitul&#xE9;e &quot;<a href="https://www.pycon.fr/2019/fr/talks/conference.html?ref=techblog.wifirst.net#je%20construis%20mon%20premier%20moteur%20%28asynchrone%29%20de%20python%E2%80%AF2%20%C3%A0%20python%E2%80%AF3.7">JE CONSTRUIS MON PREMIER MOTEUR (ASYNCHRONE) DE PYTHON&#x202F;2 &#xC0; PYTHON&#x202F;3.7</a>&quot;</p><p>Et &#xE0; 16h30 le m&#xEA;me jour, <em>Florent Fourcot</em> ainsi que <em>Charles Daymand</em> et <em>Victorien Molle</em> animeront la conf&#xE9;rence &quot;<a href="https://www.pycon.fr/2019/fr/talks/conference.html?ref=techblog.wifirst.net#configurez%C2%A0%2F%20tracez%C2%A0%2F%20d%C3%A9buguez%20votre%20noyau%20linux%20avec%20python">CONFIGUREZ / TRACEZ / D&#xC9;BUGUEZ VOTRE NOYAU LINUX AVEC PYTHON</a>&quot;</p><p></p><p>Ensuite, le 18 et 19 Novembre aura lieu la conf&#xE9;rence <a href="https://sysadmindays.fr/?ref=techblog.wifirst.net">Sysadmin Days</a> #9 &#xE0; Paris </p><figure class="kg-card kg-image-card"><img src="https://techblog.wifirst.net/content/images/2019/10/Screenshot-2019-10-25-at-11.08.35.png" class="kg-image" alt loading="lazy"></figure><p>A cette occasion, mardi 19 Novembre &#xE0; 11h40, <em>Cyril Grosjean</em> fera un flask talk sur &quot;<a href="https://sysadmindays.fr/?ref=techblog.wifirst.net#programme">MOLECULE, OU COMMENT TESTER SES ROLES ANSIBLE&quot;</a></p><p></p><p>N&apos;h&#xE9;sitez pas &#xE0; venir voir nos intervenants pour en savoir plus sur notre savoir-faire. RDV &#xE0; la Pycon ou aux Sysadmin Days</p>]]></content:encoded></item><item><title><![CDATA[Sécurité des points d'accès WiFi]]></title><description><![CDATA[<p>D&apos;ici quelques jours, nous interviendrons &#xE0; l&apos;&#xE9;dition 2019 du Symposium sur la S&#xE9;curit&#xE9; des Technologies de l&apos;Information et des Communications.</p><p>Lors de cette conf&#xE9;rence, nous allons parler de la s&#xE9;curit&#xE9; des points d&apos;acc&</p>]]></description><link>https://techblog.wifirst.net/securite-des-points-dacces-wifi/</link><guid isPermaLink="false">5cf664eb8d46e9000128043c</guid><dc:creator><![CDATA[Victorien Molle]]></dc:creator><pubDate>Tue, 04 Jun 2019 12:47:23 GMT</pubDate><content:encoded><![CDATA[<p>D&apos;ici quelques jours, nous interviendrons &#xE0; l&apos;&#xE9;dition 2019 du Symposium sur la S&#xE9;curit&#xE9; des Technologies de l&apos;Information et des Communications.</p><p>Lors de cette conf&#xE9;rence, nous allons parler de la s&#xE9;curit&#xE9; des points d&apos;acc&#xE8;s WiFi Ruckus et Aerohive. En effet, ceux-ci &#xE9;taient vuln&#xE9;rables &#xE0; des &#xE9;l&#xE9;vations de privil&#xE8;ges permettant de prendre la main en mode super-utilisateur et nous octroyant ainsi tous les droits sur le syst&#xE8;me (plus de d&#xE9;tails <a href="https://www.sstic.org/2019/presentation/analyse_de_firmwares_de_points_dacces_retro_ingenierie_et_elevation_de_privileges?ref=techblog.wifirst.net">ici</a>).</p><p>Vous pouvez retrouver le planning des conf&#xE9;rences &#xE0; cette adresse : <a href="https://www.sstic.org/2019/programme/?ref=techblog.wifirst.net">https://www.sstic.org/2019/programme/</a>. A vendredi !</p>]]></content:encoded></item><item><title><![CDATA[Passer à l'échelle avec Dnsmasq (pour le DHCP)]]></title><description><![CDATA[<p><a href="http://www.thekelleys.org.uk/dnsmasq/doc.html?ref=techblog.wifirst.net">Dnsmasq</a> est un serveur DHCP et DNS (et m&#xEA;me TFTP) qu&apos;on ne pr&#xE9;sente probablement plus. L&#xE9;ger, facile &#xE0; configurer (il lit par d&#xE9;faut les configurations habituelles du syst&#xE8;me, comme /etc/resolv.conf, /etc/hosts, /etc/ethers, etc. Et</p>]]></description><link>https://techblog.wifirst.net/passer-a-lechelle-avec-dnsmasq/</link><guid isPermaLink="false">5c6fd6138d46e900012803b1</guid><dc:creator><![CDATA[Florent Fourcot]]></dc:creator><pubDate>Fri, 19 Apr 2019 08:30:53 GMT</pubDate><content:encoded><![CDATA[<p><a href="http://www.thekelleys.org.uk/dnsmasq/doc.html?ref=techblog.wifirst.net">Dnsmasq</a> est un serveur DHCP et DNS (et m&#xEA;me TFTP) qu&apos;on ne pr&#xE9;sente probablement plus. L&#xE9;ger, facile &#xE0; configurer (il lit par d&#xE9;faut les configurations habituelles du syst&#xE8;me, comme /etc/resolv.conf, /etc/hosts, /etc/ethers, etc. Et en plus utilise <a href="https://linuxfr.org/news/exploiter-inotify-c-est-simple?ref=techblog.wifirst.net">inotify</a> pour les recharger si n&#xE9;cessaire) et combinant deux services essentiels en un seul programme pour avoir un r&#xE9;seau fonctionnel.</p><p>Du c&#xF4;t&#xE9; du probablement moins connu, Dnsmasq contient &#xE9;galement aussi beaucoup d&apos;options, qui permettent de l&apos;adapter &#xE0; beaucoup de situations. Un exemple est sa capacit&#xE9; &#xE0; ajouter les adresses IP des noms qu&apos;il r&#xE9;sout en DNS dans des ipset. Loin d&apos;&#xEA;tre anodine, cette fonctionnalit&#xE9; permet par exemple de mettre en place des autorisations/interdictions en fonction de noms de domaines (ce qui a forc&#xE9;ment des effets de bords, notamment du fait des CDN de plus en plus nombreux, mais c&apos;est un autre sujet).</p><p>Pour ces raisons, nous utilisons Dnsmasq sur beaucoup de nos &#xE9;quipements. Reste une question : ce petit d&#xE9;mon, l&#xE9;ger, embarqu&#xE9; habituellement pour sa simplicit&#xE9;, peut-il passer &#xE0; l&apos;&#xE9;chelle ? Son site officiel le d&#xE9;finit comme &#xAB; Dnsmasq provides network infrastructure for small networks &#xBB;. Quand on lit le mot <em>small</em>, un premier r&#xE9;flexe pourrait &#xEA;tre de basculer vers des solutions comme isc-dhcp-server quand le r&#xE9;seau grossit. Mais si l&apos;on souhaite avoir un parc homog&#xE8;ne, peut-on imaginer garder Dnsmasq pour des r&#xE9;seaux plus volumineux ? (spoiler : la r&#xE9;ponse est oui, voici un retour d&apos;exp&#xE9;rience).</p><h1 id="le-premier-palier-qui-n-existe-pas-vraiment">Le premier palier, qui n&apos;existe pas vraiment</h1><p>La premi&#xE8;re configuration est relativement &#xE9;vidente. Par d&#xE9;faut, la valeur pour <em>dhcp-lease-max </em>est configur&#xE9;e &#xE0; 1000. C&apos;est rapidement insuffisant, et l&apos;augmenter va de soit. &#xC0; noter que la page de manuel parle d&apos;un risque sur la consommation m&#xE9;moire, mais il est &#xE0; relativiser. Un dnsmasq avec plus de 10 000 baux en m&#xE9;moire consomme moins de 5Mo de RAM. On peut l&apos;augmenter sans crainte sur la plupart des &#xE9;quipements.</p><p>Second param&#xE8;tre &#xE0; prendre en compte, l&apos;option <em>dhcp-ignore-names. </em>Par d&#xE9;faut Dnsmasq va ins&#xE9;rer les noms annonc&#xE9;s par les clients en DHCP dans ses entr&#xE9;es DNS. C&apos;est sympa/pratique sur un petit r&#xE9;seau local personnel. C&apos;est par contre &#xE0; bannir rapidement sur un r&#xE9;seau professionnel. Si malgr&#xE9; les risques de s&#xE9;curit&#xE9; quelqu&apos;un souhaite cependant la conserver sur un grand r&#xE9;seau, on voit rapidement appara&#xEE;tre des probl&#xE8;mes de performances d&#xE8;s quelques milliers de baux en m&#xE9;moire. Notamment car Dnsmasq va v&#xE9;rifier &#xE0; chaque requ&#xEA;te DHCP que ce nom n&apos;existe pas d&#xE9;j&#xE0; sans sa liste, ce qui est tr&#xE8;s lent.</p><p>Avec ces deux param&#xE8;tres, vous pouvez d&#xE9;j&#xE0; avoir un r&#xE9;seau de grande taille (plusieurs milliers de baux) sans d&#xE9;tecter la moindre consommation CPU de Dnsmasq.</p><h1 id="le-bug-qui-co-te-une-seconde-par-bail">Le bug qui co&#xFB;te une seconde par bail </h1><p>Le palier suivant a &#xE9;t&#xE9; d&#xE9;tect&#xE9; chez nous sur une configuration qui d&#xE9;passait les 15 000 baux allou&#xE9;s dans la journ&#xE9;e. Sur ce site sp&#xE9;cifique, ces baux &#xE9;taient lib&#xE9;r&#xE9;s durant la nuit (dur&#xE9;e de quelques heures, et aucune activit&#xE9; r&#xE9;seau apr&#xE8;s minuit. Atypique pour nous, qui avons plut&#xF4;t des utilisateurs nocturnes et de longue dur&#xE9;e). Nous avons remarqu&#xE9; une consommation CPU anormale, Dnsmasq consommant bien plus de CPU la nuit (alors qu&apos;il ne faisait que supprimer ses baux) qu&apos;en journ&#xE9;e.</p><p>Apr&#xE8;s analyse, il s&apos;agit d&apos;un bug de Dnsmasq qui d&#xE9;clenche un nettoyage de ses sessions &#xE0; l&apos;heure exacte d&apos;expiration du prochain bail, mais ne le nettoie effectivement qu&apos;une seconde apr&#xE8;s cette expiration. R&#xE9;sultat, durant une seconde Dnsmasq boucle sur ce nettoyage qu&apos;il relance en permanence sans accomplir la moindre action. On perd ainsi une seconde de temps processeur pour chaque bail supprim&#xE9;.</p><p>Cet effet est compl&#xE8;tement invisible sur un petit r&#xE9;seau. Dans notre cas, en lib&#xE9;rant 15 000 baux en quelques heures, notre processeur &#xE9;tait occup&#xE9; pendant plus de quatre heures pour rien. Pour la petite histoire, on voyait m&#xEA;me appara&#xEE;tre la fonction <em>difftime</em> avec <em>perf</em> tant Dnsmasq passait son temps &#xE0; v&#xE9;rifier si un bail de sa liste &#xE9;tait expir&#xE9; et la date de prochaine expiration.</p><p>Nous avons donc <a href="https://www.mail-archive.com/dnsmasq-discuss@lists.thekelleys.org.uk/msg12773.html?ref=techblog.wifirst.net">propos&#xE9; un patch</a>, rapidement accept&#xE9; (et d&#xE9;ploy&#xE9; imm&#xE9;diatement sur nos &#xE9;quipements), qui permet de revenir sur une situation beaucoup plus calme (on ne voit plus Dnsmasq consommer du CPU durant la nuit, ce qui est tout de m&#xEA;me plus logique).</p><h1 id="encore-plus-loin">Encore plus loin</h1><p>Arriv&#xE9; l&#xE0;, on a d&#xE9;j&#xE0; un Dnsmasq qui consomme de fa&#xE7;on tr&#xE8;s raisonnable des ressources pour un r&#xE9;seau voyant passer plus de 20 000 &#xE9;quipements diff&#xE9;rents dans la journ&#xE9;e. Cela devrait suffire pour la plupart des r&#xE9;seaux.</p><p>Pour aller plus loin, le prochain probl&#xE8;me est l&apos;&#xE9;criture du fichier <em>dnsmasq.leases</em> par Dnsmasq &#xE0; chaque changement d&apos;&#xE9;tat. Sur ce type de r&#xE9;seau, on se retrouve &#xE0; r&#xE9;-&#xE9;crire un fichier de milliers de lignes &#xE0; chaque seconde ou presque (voir plus souvent). De plus, comme effet de bord, Dnsmasq r&#xE9;-&#xE9;crit le fichier plut&#xF4;t que d&apos;effectuer une op&#xE9;ration atomique avec un fichier temporaire, provoquant des effets de bords dans d&apos;&#xE9;ventuels programmes externes tentant de le lire (le fichier est souvent invalide, partiellement &#xE9;crit). Cette r&#xE9;-&#xE9;criture est suffisamment intense pour qu&apos;on voit appara&#xEE;tre la fonction <em>vsnprintf </em>avec<em> perf, </em>ce qui n&apos;est pas tr&#xE8;s bon signe.</p><p>Heureusement Dnsmasq permet une nouvelle fois par ses nombreuses options d&apos;&#xE9;viter &#xE7;a. Avec l&apos;option <em>dhcp-script</em>, un script est appel&#xE9; &#xE0; chaque changement d&apos;&#xE9;tat DHCP (ajout, suppression, modification). On peut donc maintenir une base des baux nous m&#xEA;me, et permet d&#xE9;j&#xE0; &#xE0; des outils de supervision d&apos;&#xE9;viter les probl&#xE8;mes de r&#xE9;-&#xE9;criture en permanence du fichier <em>dnsmasq.leases</em>. Cependant, on ne diminue pas encore la consommation de ressource.</p><p>La solution arrive avec <em>leasefile-ro</em>, qui permet cette fois de s&apos;affranchir enti&#xE8;rement de ce fichier. L&apos;astuce &#xE0; ajouter est de permettre &#xE0; Dnsmasq de retrouver les baux DHCP actifs lors d&apos;un red&#xE9;marrage. C&apos;est assez simple, dans ce cas Dnsmasq appelle le script habituel avec en param&#xE8;tre <em>init</em>. Il suffit de lui afficher sur <em>stdin</em> les baux actuels dans le m&#xEA;me format que son fichier habituel et tout roule. C&apos;est assez simple &#xE0; faire si l&apos;&#xE9;tape pr&#xE9;c&#xE9;dente de maintien d&apos;une base externe fonctionne d&#xE9;j&#xE0;.</p><p>&#xC0; noter que Dnsmasq permet d&apos;utiliser des scripts en lua plut&#xF4;t que des scripts externes (et qu&apos;on pourrait donc s&apos;attendre &#xE0; des gains en ressource), mais qu&apos;&#xE0; ce jour les scripts lua ne permettent pas de charger les baux au d&#xE9;marrage avec <em>init</em>. Ils ne sont donc pas utilisables pour ce cas d&apos;usage.</p><h1 id="prochain-palier">Prochain palier ?</h1><p>En conclusion, Wifirst utilise donc Dnsmasq sur des r&#xE9;seaux qui ne voient que quelques &#xE9;quipements dans la journ&#xE9;e &#xE0; des r&#xE9;seaux de bien plus grande envergure. Et &#xE7;a se passe aujourd&apos;hui tr&#xE8;s bien, la consommation de ressource de Dnsmasq &#xE9;tant n&#xE9;gligeable avec ces configurations par rapport aux autres fonctionnalit&#xE9;s.</p><p>Nous avions commenc&#xE9; &#xE0; stocker notre propre base de baux DHCP pour des raisons de supervision (une autre solution serait d&apos;am&#xE9;liorer Dnsmasq pour faire des changements atomique sur le fichier). L&apos;utiliser pour am&#xE9;liorer les performances est un petit bonus, pas indispensable d&apos;apr&#xE8;s notre exp&#xE9;rience sur des r&#xE9;seaux ayant jusqu&apos;&#xE0; 20 000 baux allou&#xE9;s et lib&#xE9;r&#xE9;s dans la journ&#xE9;e.</p><p>Et nous attendons donc avec impatience de voir ce que pourrait &#xEA;tre le prochain point de blocage. Un suspect probable est &#xE0; regarder du c&#xF4;t&#xE9; des appels &#xE0; <em>dhcp-script</em>, mais nous n&apos;imaginons pas pour le moment voir de r&#xE9;seaux d&apos;une taille suffisante pour que ce soit d&#xE9;tectable.</p>]]></content:encoded></item><item><title><![CDATA[IPSet protocol version 7]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>La <a href="https://lwn.net/Articles/769839/?ref=techblog.wifirst.net">version 7 d&apos;ipset</a> est sortie r&#xE9;cemment (mais pas encore dans la branche principale du noyau, elle devrait arriver en 4.20 ou 5.0). Et Wifirst y est un peu pour quelques chose.</p>
<p>Nous utilisons massivement les ipset sur nos &#xE9;quipements, notamment car ils</p>]]></description><link>https://techblog.wifirst.net/ipset-protocol-version-7/</link><guid isPermaLink="false">5c26359fd8d84a0001d4a478</guid><dc:creator><![CDATA[Florent Fourcot]]></dc:creator><pubDate>Fri, 30 Nov 2018 15:49:42 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>La <a href="https://lwn.net/Articles/769839/?ref=techblog.wifirst.net">version 7 d&apos;ipset</a> est sortie r&#xE9;cemment (mais pas encore dans la branche principale du noyau, elle devrait arriver en 4.20 ou 5.0). Et Wifirst y est un peu pour quelques chose.</p>
<p>Nous utilisons massivement les ipset sur nos &#xE9;quipements, notamment car ils permettent de configurer un pare-feu bas&#xE9; sur iptables enti&#xE8;rement vers netlink (et aussi car il permet de rendre dynamique un pare-feu iptables classique, sans ajouter/modifier de r&#xE8;gles). C&apos;est donc assez naturellement que nous avons &#xE9;galement commenc&#xE9; &#xE0; les utiliser pour configurer une partie de notre QoS (commande <code>tc</code>). Nous avons ainsi ajout&#xE9; le support des ipset par tc-ematch dans <a href="https://github.com/svinota/pyroute2?ref=techblog.wifirst.net">pyroute2</a>.</p>
<p>Il restait cependant un probl&#xE8;me pour nous qui n&apos;utilisons que du netlink. Pour convertir le nom d&apos;un ipset en un index utilisable par le noyau pour ajouter la r&#xE8;gle, le module tc-ematch fait ceci :</p>
<pre><code class="language-c">static int
get_set_byname(const char *setname, struct xt_set_info *info)
{
    struct ip_set_req_get_set req;
    int res;

    req.op = IP_SET_OP_GET_BYNAME;
    strlcpy(req.set.name, setname, IPSET_MAXNAMELEN);
    res = do_getsockopt(&amp;req);
    if (res != 0)
        return -1;
    if (req.set.index == IPSET_INVALID_ID)
        return -1;
    info-&gt;index = req.set.index;
    return 0;
}   
</code></pre>
<p>Ils utilisent donc getsockopt plut&#xF4;t que netlink. C&apos;est un peu dommage pour un module qui autrement fonctionne enti&#xE8;rement en netlink. Nous avons donc <a href="https://patchwork.ozlabs.org/patch/939918/?ref=techblog.wifirst.net">propos&#xE9; un patch</a> permettant d&apos;exporter ces index, en ajoutant un flag pour contr&#xF4;ler son affichage afin de ne pas casser les outils existants en espace utilisateur. Pour les curieux, cette interface via getsockopt a <a href="https://elixir.bootlin.com/linux/v4.9.12/source/include/uapi/linux/netfilter/ipset/ip_set.h?ref=techblog.wifirst.net#L273">quatre options d&#xE9;finies dans ip_set.h</a>.</p>
<p>Ce patch a &#xE9;t&#xE9; refus&#xE9;, le mainteneur ne souhaitant pas introduire ce type de changement sans augmenter le num&#xE9;ro de version du protocol. IPSet version sept &#xE9;tait donc n&#xE9; ! Quelques mois apr&#xE8;s notre demande initiale, <a href="https://marc.info/?l=netfilter-devel&amp;m=154070094331469&amp;w=2&amp;ref=techblog.wifirst.net">le mainteneur a propos&#xE9; sa version</a>, ajoutant deux commandes <code>ip_set_byname</code> et <code>ip_set_byindex</code>.</p>
<p>Peu apr&#xE8;s sa mise &#xE0; disposition, nous avons pu <a href="https://patchwork.ozlabs.org/patch/1003914/?ref=techblog.wifirst.net">tester</a> et travailler sur la <a href="https://github.com/svinota/pyroute2/pull/569?ref=techblog.wifirst.net">gestion par pyroute2</a> de ces commandes avec pour but de supprimer nos patchs maintenus en interne.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Conntrack, namespaces et supervision]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Sous Linux, chaque namespace r&#xE9;seau a sa propre table Conntrack, compl&#xE8;tement isol&#xE9;es entre elles. Elles se rejoignent en revanche sur la taille maximale, partout la m&#xEA;me, et configurable uniquement en sysctl (voir le fichier <code>/proc/sys/net/netfilter/nf_conntrack_max</code>). Ainsi,</p>]]></description><link>https://techblog.wifirst.net/conntrack-supervision/</link><guid isPermaLink="false">5c26359fd8d84a0001d4a477</guid><dc:creator><![CDATA[Florent Fourcot]]></dc:creator><pubDate>Mon, 09 Jul 2018 13:58:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Sous Linux, chaque namespace r&#xE9;seau a sa propre table Conntrack, compl&#xE8;tement isol&#xE9;es entre elles. Elles se rejoignent en revanche sur la taille maximale, partout la m&#xEA;me, et configurable uniquement en sysctl (voir le fichier <code>/proc/sys/net/netfilter/nf_conntrack_max</code>). Ainsi, une modification de taille effectu&#xE9; depuis n&apos;importe quel namespace aura des cons&#xE9;quences partout.</p>
<p>Apparemment pour des raisons de compatibilit&#xE9; avec de  noyaux, notre supervision lisait un alias de ce fichier, sous le chemin <code>/proc/sys/net/nf_conntrack_max</code> (les chemins pr&#xE9;c&#xE9;dents en <code>ip_conntrack</code> ayant &#xE9;t&#xE9; <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=adf0516845bcd0e626323c858ece28ee58c74455&amp;ref=techblog.wifirst.net">supprim&#xE9;s en 2016</a>). Contrairement au &#xAB; v&#xE9;ritable &#xBB; fichier dans le dossier netfilter, celui-ci n&apos;est export&#xE9; que sur le namespace principal :</p>
<pre><code>$ cat /proc/sys/net/netfilter/nf_conntrack_max
262144
$ ip netns exec namespace1 cat /proc/sys/net/nf_conntrack_max
cat: /proc/sys/net/nf_conntrack_max: No such file or directory
$ ip netns exec namespace1 cat /proc/sys/net/netfilter/nf_conntrack_max
262144
</code></pre>
<p>Quitte &#xE0; regarder cette partie de notre supervision, nous avons d&#xE9;cid&#xE9; d&apos;harmoniser la r&#xE9;cup&#xE9;ration du nombre d&apos;entr&#xE9;es (que nous r&#xE9;cup&#xE9;rions en netlink) avec la r&#xE9;cup&#xE9;ration du nombre maximum. C&apos;est l&apos;id&#xE9;e derri&#xE8;re <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=538c5672be6d67b7b10c15701588e20617374973&amp;ref=techblog.wifirst.net">ce patch</a>.</p>
<p>Signe que nous n&apos;&#xE9;tions probablement pas les seuls &#xE0; avoir ce besoin, un contributeur externe a ajout&#xE9; la <a href="https://github.com/svinota/pyroute2/commit/fca08fc7cd7f56263cfc6bde500ef869dc0e33a5?ref=techblog.wifirst.net">gestion par pyroute2</a> avant que nous ayons int&#xE9;gr&#xE9; cette partie de notre c&#xF4;t&#xE9;.</p>
<p>En probl&#xE8;me reli&#xE9; &#xE0; tout &#xE7;a, on peut &#xE9;galement noter qu&apos;openvswitch permet depuis peu de <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=11efd5cb04a184eea4f57b68ea63dddd463158d1&amp;ref=techblog.wifirst.net">limiter le nombre d&apos;entr&#xE9;es</a> conntrack par zone.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Python 2 à 3: une méthode portée, deux bugs offerts]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>M&#xEA;me si Python 3 n&apos;est pas fondamentalement diff&#xE9;rent de Python 2 et qu&apos;il existe de nombreux outils pour aider &#xE0; porter son code, le portage d&apos;un projet entier ne se fait jamais enti&#xE8;rement sans difficult&#xE9; et est</p>]]></description><link>https://techblog.wifirst.net/1-methode-2-bugs/</link><guid isPermaLink="false">5c26359fd8d84a0001d4a476</guid><dc:creator><![CDATA[Étienne Noss]]></dc:creator><pubDate>Tue, 19 Dec 2017 10:00:47 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>M&#xEA;me si Python 3 n&apos;est pas fondamentalement diff&#xE9;rent de Python 2 et qu&apos;il existe de nombreux outils pour aider &#xE0; porter son code, le portage d&apos;un projet entier ne se fait jamais enti&#xE8;rement sans difficult&#xE9; et est souvent l&apos;occasion d&apos;en apprendre plus sur notre langage pr&#xE9;f&#xE9;r&#xE9;.<br>
Comme on va le voir, c&apos;est aussi une bonne mani&#xE8;re de d&#xE9;couvrir des probl&#xE8;mes insoup&#xE7;onn&#xE9;s dans son code !</p>
<h3 id="unpeudecontextesgestiondurseauchezwifirst">Un peu de contexte(s): gestion du r&#xE9;seau chez Wifirst</h3>
<p>J&apos;ai r&#xE9;cemment &#xE9;t&#xE9; amen&#xE9; &#xE0; travailler sur la compatibilit&#xE9; Python 3 de notre code de gestion du r&#xE9;seau (adressage, routage, etc). Nous utilisons le projet <a href="https://github.com/svinota/pyroute2?ref=techblog.wifirst.net">pyroute2</a>, auquel nous contribuons r&#xE9;guli&#xE8;rement, pour s&apos;interfacer avec le noyau via l&apos;API Netlink.</p>
<p>La base de pyroute2 est une classe d&apos;assez bas niveau (<code>IPRoute</code>), dont l&apos;instanciation provoque l&apos;ouverture d&apos;une socket Netlink. Pour faire simple, nous appellons l&apos;ensemble &quot;contexte IPRoute&quot;.</p>
<p>Nous avons impl&#xE9;ment&#xE9; notre propre module d&apos;abstraction utilisant massivement ces contextes. Il nous permet de g&#xE9;rer par exemple les adresses et les routes sans avoir &#xE0; rentrer dans les d&#xE9;tails des structures Netlink expos&#xE9;es par les m&#xE9;thodes d&apos;<code>IPRoute</code>.<br>
Afin de ne pas avoir &#xE0; g&#xE9;rer le cycle de vie de ces contextes &#xE0; chaque appel &#xE0; nos fonctions d&apos;abstraction, un motif r&#xE9;current que l&apos;on y retrouve est le suivant:</p>
<pre><code class="language-python">@needs_iproute_context
def do_something_network_related(arg1, arg2, ipr=None):
    # do stuff
    other_network_related_things(&quot;yeah&quot;, arg2, ipr=ipr)
</code></pre>
<p>Le d&#xE9;corateur <code>needs_iproute_context</code> inspecte les arguments de la fonction d&#xE9;cor&#xE9;e et, s&apos;il y trouve un argument nomm&#xE9; <code>&quot;ipr&quot;</code> dont la valeur est <code>None</code>, le remplace par un contexte IPRoute qu&apos;il ferme apr&#xE8;s l&apos;appel. Cela nous permet de pouvoir appeler toutes nos fonctions de routage sans avoir &#xE0; instancier de contexte IPRoute par nous-m&#xEA;me, et tout en &#xE9;vitant d&apos;en instancier un pour chaque appel de fonction.</p>
<p>En voici une impl&#xE9;mentation simplifi&#xE9;e:</p>
<pre><code class="language-python">from pyroute2 import IPRoute
def needs_iproute_context(fun):

    def wrapped(*args, **kwargs):
        if kwargs.get(&quot;ipr&quot;):
            return fun(*args, **kwargs)
        else:
            with IPRoute() as ipr:
                kwargs[&quot;ipr&quot;] = ipr
                return fun(*args, **kwargs)
    return wrapped
</code></pre>
<h3 id="unsoucideportabilitpascommun">Un souci de portabilit&#xE9; pas commun</h3>
<p>Dans la majeure partie des cas, pas de probl&#xE8;me; <code>needs_iproute_context</code> fonctionne aussi bien en Python 2 qu&apos;en Python 3. Mais dans un cas particulier, ce d&#xE9;corateur &#xE9;tait utilis&#xE9;e d&apos;une mani&#xE8;re un peu plus exotique (code &#xE9;galement simplifi&#xE9;):</p>
<pre><code class="language-python">@needs_iproute_context
@contextmanager
def temporary_route(dst, gateway, ipr=None):
    ipr.route(&quot;add&quot;, dst=dst, gateway=gateway)
    yield
    ipr.route(&quot;del&quot;, dst=dst, gateway=gateway)
</code></pre>
<p>Ce gestionnaire de contexte nous permet de mettre en place une route de mani&#xE8;re temporaire, qui est supprim&#xE9;e d&#xE8;s que l&apos;on sort du bloc <code>with</code>. Par exemple, ce bloc rajoute une route temporaire, l&apos;affiche et la supprime:</p>
<pre><code class="language-python">with temporary_route(dst=&quot;66.96.149.1/32&quot;, gateway=&quot;10.4.1.2&quot;):
    with IPRoute() as ipr:
        for i in ipr.get_routes(dst=&quot;66.96.149.1/32&quot;):
            print(i.get_attr(&quot;RTA_GATEWAY&quot;))
</code></pre>
<p>Et c&apos;est l&#xE0; que les soucis commencent. <code>temporary_route</code> fonctionne parfaitement en Python 2, mais &#xE9;choue invariablement en Python 3:</p>
<pre><code>$ sudo python3 temporary_route.py 
Traceback (most recent call last):
  File &quot;temporary_route.py&quot;, line 39, in &lt;module&gt;
    with temporary_route(dst=&quot;66.96.149.1/32&quot;, gateway=&quot;10.4.1.2&quot;):
  File &quot;/usr/lib/python3.6/contextlib.py&quot;, line 81, in __enter__
    return next(self.gen)
  File &quot;temporary_route.py&quot;, line 19, in temporary_route
    ipr.route(&quot;add&quot;, dst=dst, gateway=gateway)
  File &quot;/usr/lib/python3/dist-packages/pyroute2/iproute.py&quot;, line 1750, in route
    callback=callback)
  File &quot;/usr/lib/python3/dist-packages/pyroute2/netlink/nlsocket.py&quot;, line 804, in nlm_request
    return do_try()
  File &quot;/usr/lib/python3/dist-packages/pyroute2/netlink/nlsocket.py&quot;, line 780, in do_try
    self.put(msg, msg_type, msg_flags, msg_seq=msg_seq)
  File &quot;/usr/lib/python3/dist-packages/pyroute2/netlink/nlsocket.py&quot;, line 561, in put
    self.sendto_gate(msg, addr)
  File &quot;/usr/lib/python3/dist-packages/pyroute2/netlink/rtnl/iprsocket.py&quot;, line 74, in _gate
    return self._sendto(msg.data, addr)
OSError: [Errno 9] Bad file descriptor
</code></pre>
<p>Visiblement, l&apos;ajout de route n&apos;a pas fonctionn&#xE9;, car l&apos;envoi du message sur la socket netlink a &#xE9;chou&#xE9;. Mais pourquoi cette diff&#xE9;rence entre les deux versions de Python ?</p>
<p>Le message d&apos;erreur indique qu&apos;il y a un probl&#xE8;me sur la socket Netlink. Pour voir ce qu&apos;il en est, on peut poser un point d&apos;arr&#xEA;t avant l&apos;ajout de la route pour inspecter le contexte IPRoute:</p>
<pre><code>&gt; /home/etienne/work/article-devblog/temporary_route.py(22)temporary_route()
-&gt; ipr.route(&quot;add&quot;, dst=dst, gateway=gateway)
(Pdb) p ipr
&lt;pyroute2.iproute.IPRoute object at 0x7fd503f77b70&gt;
(Pdb) p ipr.closed
True
(Pdb)
</code></pre>
<p>La sortie est la m&#xEA;me en Python 2 et 3: le contexte IPRoute est d&#xE9;j&#xE0; ferm&#xE9;.</p>
<p>Pourquoi est-il ferm&#xE9;, et comment est-ce possible que &#xE7;a fonctionne quand m&#xEA;me en Python 2 ? C&apos;est le moment de ressortir le fid&#xE8;le <a href="https://github.com/strace/strace?ref=techblog.wifirst.net">strace</a>, qui pourra nous en dire plus long sur ce qu&apos;il se passe r&#xE9;ellement niveau socket. En relan&#xE7;ant avec le point d&apos;arr&#xEA;t + strace:</p>
<pre><code>$ sudo strace -e socket,close python2 temporary_route.py
[...]
socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE) = 5
close(4)                                = 0
close(3)                                = 0
close(3)                                = 0
&gt; /home/etienne/work/article-devblog/temporary_route.py(24)temporary_route()
-&gt; ipr.route(&quot;add&quot;, dst=dst, gateway=gateway)
close(3)                                = 0
(Pdb) p ipr.closed
True

$ sudo strace -e socket,close python3 temporary_route.py
[...]
socket(AF_NETLINK, SOCK_DGRAM|SOCK_CLOEXEC, NETLINK_ROUTE) = 5
close(4)                                = 0
close(3)                                = 0
close(5)                                = 0
close(3)                                = 0
&gt; /home/etienne/work/article-devblog/temporary_route.py(24)temporary_route()
-&gt; ipr.route(&quot;add&quot;, dst=dst, gateway=gateway)
(Pdb) p ipr.closed
True
</code></pre>
<p>Dans les deux cas, une socket Netlink est ouverte (descripteur de fichier num&#xE9;ro 5), mais elle est ferm&#xE9;e <em>avant</em> l&apos;ajout de la route en Python 3, alors qu&apos;elle reste ouverte en Python 2 ! Par contre, l&apos;objet IPRoute associ&#xE9; est consid&#xE9;r&#xE9; comme ferm&#xE9;.</p>
<p>On a donc deux soucis:</p>
<ul>
<li>le contexte IPRoute ferm&#xE9; avec sa socket toujours ouverte,</li>
<li>le fait qu&apos;il soit ferm&#xE9; dans <code>needs_iproute_context</code></li>
</ul>
<h3 id="lasocketdeschrdinger">La socket de Schr&#xF6;dinger</h3>
<p>Pour confirmer ce probl&#xE8;me de socket consid&#xE9;r&#xE9;e comme ferm&#xE9;e par l&apos;objet IPRoute alors qu&apos;elle est toujours ouverte par le syst&#xE8;me, on peut faire un test simple :</p>
<pre><code class="language-python">from pyroute2 import IPRoute
ipr = IPRoute()
ipr.get_links()
ipr.close()
ipr.get_links()
</code></pre>
<p>&#xC7;a &quot;fonctionne&quot; en Python 2, mais en Python 3 le deuxi&#xE8;me appel &#xE0; <code>get_links</code> &#xE9;choue.<br>
C&apos;est ennuyant pour nous, car le comportement en Python 3 est correct, ce qui signifie que <code>temporary_route</code> n&apos;aurai jamais d&#xFB; fonctionner.</p>
<p>Pourquoi cette diff&#xE9;rence de comportement ? &#xC9;tant donn&#xE9; que le code de pyroute2 est le m&#xEA;me pour Python 2 et 3, j&apos;ai regard&#xE9; s&apos;il y avait une diff&#xE9;rence dans la fermeture des sockets dans le module <code>socket</code> de Python. Et c&apos;est effectivement le cas:</p>
<p><strong>Python 2.7:</strong></p>
<pre><code class="language-python">def close(self, _closedsocket=_closedsocket,
          _delegate_methods=_delegate_methods, setattr=setattr):
    # This function should not reference any globals. See issue #808164.
    self._sock = _closedsocket()
    dummy = self._sock._dummy
    for method in _delegate_methods:
        setattr(self, method, dummy)
</code></pre>
<p><strong>Python 3.6:</strong></p>
<pre><code class="language-python">    def close(self):
        # This function should not reference any globals. See issue #808164.
        self._closed = True
        if self._io_refs &lt;= 0:
            self._real_close()
</code></pre>
<p>En Python 2, les m&#xE9;thodes associ&#xE9;es &#xE0; la socket (<code>_delegate_methods</code>), comme <code>sendto</code> et <code>recv</code> sont remplac&#xE9;es lors de la fermeture par des m&#xE9;thodes factices qui se contentent de lever une erreur, pas en Python 3.</p>
<p>Pendant ce temps l&#xE0;, du c&#xF4;t&#xE9; de pyroute2:<br>
(extrait de <code>NetlinkSocket.post_init</code>)</p>
<pre><code class="language-python">self._sendto = getattr(self._sock, &apos;sendto&apos;)
self._recv = getattr(self._sock, &apos;recv&apos;)
self._recv_into = getattr(self._sock, &apos;recv_into&apos;)
</code></pre>
<p>La classe <code>NetlinkSocket</code> copie lors de son initialisation une r&#xE9;f&#xE9;rence vers les m&#xE9;thodes de la socket. En Python 2, cela cr&#xE9;&#xE9; un &#xE9;tat incoh&#xE9;rent lors de la fermeture: la socket a remplac&#xE9; ses m&#xE9;thodes par des factices, mais <code>NetlinkSocket</code> pointe toujours sur les &quot;vraies&quot; m&#xE9;thodes. Des donn&#xE9;s peuvent donc toujours &#xEA;tre envoy&#xE9;es sur la socket si elle n&apos;a pas encore &#xE9;t&#xE9; ferm&#xE9;e au niveau syst&#xE8;me, ce qui semble &#xEA;tre le cas.</p>
<p>Consid&#xE9;rant que ce comportement &#xE9;tait un bug, j&apos;ai ouvert <a href="https://github.com/svinota/pyroute2/pull/443?ref=techblog.wifirst.net">une pull request sur pyroute2</a>, une correction rapide de <code>NetlinkSocket</code> qui fait en sorte de ne plus garder une r&#xE9;f&#xE9;rence sur des m&#xE9;thodes qui peuvent avoir chang&#xE9;.</p>
<h3 id="dcorerungestionnairedecontexte">D&#xE9;corer un gestionnaire de contexte ?</h3>
<p>Avec un comportement correct de fermeture des sockets par pyroute2, on se retrouve avec un autre probl&#xE8;me: <code>temporary_route</code> ne fonctionne pas du tout car la socket est ferm&#xE9;e avant m&#xEA;me d&apos;entrer dans ce gestionnaire de contexte.</p>
<p>Les plus vers&#xE9;s dans les arcanes de Python auront peut &#xEA;tre d&#xE9;j&#xE0; compris pourquoi, mais pour ceux (moi inclus) pour qui &#xE7;a ne tombe pas sous le sens, r&#xE9;&#xE9;crire le gestionnaire de contexte dans sa forme non condens&#xE9;e (c&apos;est &#xE0; dire sans utiliser le d&#xE9;corateur <a href="https://docs.python.org/3.6/library/contextlib.html?highlight=contextmanager&amp;ref=techblog.wifirst.net#contextlib.contextmanager">contextmanager</a>) rend les choses bien plus claires:</p>
<pre><code class="language-python">class TemporaryRoute(object):

    @needs_iproute_context
    def __init__(self, dst, gateway, ipr=None):
        self.ipr = ipr
        self.dst = dst
        self.gateway = gateway

    def __enter__(self):
        self.ipr.route(&quot;add&quot;, dst=self.dst, gateway=self.gateway)

    def __exit__(self, *_):
        self.ipr.route(&quot;del&quot;, dst=self.dst, gateway=self.gateway)
</code></pre>
<p><code>needs_iproute_context</code> ne peut pas d&#xE9;corer un gestionnaire de contexte, car c&apos;est une classe. Que l&apos;on d&#xE9;core le constructeur ou la classe elle-m&#xEA;me, cela revient au m&#xEA;me; le contexte est ferm&#xE9; implicitement &#xE0; la fin du bloc <code>with IPRoute() as ipr:</code> de <code>needs_iproute_context</code> et n&apos;est donc valable que dans <code>__init__</code>.</p>
<p>La meilleure solution a donc &#xE9;t&#xE9; de r&#xE9;impl&#xE9;menter <code>temporary_route</code> pour g&#xE9;rer son propre contexte IPRoute, ce qui fonctionne &#xE0; la fois en Python 2 et 3.</p>
<h3 id="aufinal">Au final</h3>
<p>Porter un seul gestionnaire de contexte aura permis de trouver un souci dans pyroute2, un autre dans notre code, et d&apos;en apprendre davantage sur le Python; le portage vers Python 3 est donc un bon investissement !</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Quelques détails après l'interview]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Par un heureux hasard, je me suis retrouv&#xE9; <a href="http://tapoueh.org/blog/2017/12/mastering-postgresql-a-readers-interview/?ref=techblog.wifirst.net">&#xE0; &#xEA;tre interview&#xE9;</a> par l&apos;auteur du livre <a href="http://masteringpostgresql.com/?ref=techblog.wifirst.net">Mastering PostgreSQL in Application Development</a>. Comme l&apos;exercice de l&apos;interview ne se pr&#xEA;te pas compl&#xE8;tement aux d&#xE9;tails techniques, j&apos;en</p>]]></description><link>https://techblog.wifirst.net/quelques-details-apres-linterview-du-livre/</link><guid isPermaLink="false">5c26359fd8d84a0001d4a475</guid><dc:creator><![CDATA[Florent Fourcot]]></dc:creator><pubDate>Thu, 14 Dec 2017 17:53:47 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Par un heureux hasard, je me suis retrouv&#xE9; <a href="http://tapoueh.org/blog/2017/12/mastering-postgresql-a-readers-interview/?ref=techblog.wifirst.net">&#xE0; &#xEA;tre interview&#xE9;</a> par l&apos;auteur du livre <a href="http://masteringpostgresql.com/?ref=techblog.wifirst.net">Mastering PostgreSQL in Application Development</a>. Comme l&apos;exercice de l&apos;interview ne se pr&#xEA;te pas compl&#xE8;tement aux d&#xE9;tails techniques, j&apos;en profite pour compl&#xE9;ter ici un peu ce que nous avons chang&#xE9;s sur nos django.</p>
<p>Premier exemple assez trivial, nous avions du code qui v&#xE9;rifiait si un objet n&apos;avait pas &#xE9;t&#xE9; vu depuis un certain temps. L&apos;approche pr&#xE9;c&#xE9;dente &#xE9;tait de faire cette v&#xE9;rification en python :</p>
<pre><code class="language-python">    def is_seen(self, now=None):
        if self.last_seen is None:
            return False
        
        if now is None:
            now = timezone.now()
        return now - self.last_seen &lt; LOST_DELTA
</code></pre>
<p>La micro-optimisation sur la variable now n&apos;est pas tr&#xE8;s importante, sauf quand cette m&#xE9;thode est appel&#xE9;e sur des milliers d&apos;objets diff&#xE9;rents.</p>
<p>Ce n&apos;est pas tr&#xE8;s spectaculaire, mais nous avons ajout&#xE9; une simple m&#xE9;thode sur le manager de l&apos;objet :</p>
<pre><code class="language-python">    def add_connected(self):
        return self.annotate(connected=Case(When(last_seen__gt=(timezone.now() - LOST_DELTA),
                                                 then=Value(True)),
                                            default=Value(False),
                                            output_field=BooleanField()))

</code></pre>
<p>On a ainsi directement un attribut bool&#xE9;en sur tous les objets quand nous en avons besoin.</p>
<p>Second exemple, nous avons dans notre mod&#xE8;le de base de donn&#xE9;es des lignes ADSL. Ces lignes ADSL sont li&#xE9;es &#xE0; des sites (un site pouvant avoir N lignes). Nous souhaitons savoir tr&#xE8;s rapidement si un site a une ligne ADSL ou pas. Le code existant utilisait la solution standard en Django en utilisant un JOIN avec un DISTINCT :</p>
<pre><code class="language-python">Site.objects.filter(line__isnull=False).order_by(&quot;pk&quot;).distinct(&quot;pk&quot;)
</code></pre>
<p>Cela g&#xE9;n&#xE9;rait ce genre de requ&#xEA;te SQL :</p>
<pre><code class="language-sql">SELECT DISTINCT ON (&quot;appli_site&quot;.&quot;id&quot;) &quot;appli_site&quot;.&quot;id&quot;, &quot;appli_site&quot;.&quot;name&quot;
FROM &quot;appli_site&quot; INNER JOIN &quot;appli_line&quot; ON (&quot;appli_site&quot;.&quot;id&quot; = &quot;appli_line&quot;.&quot;site_id&quot;)
WHERE &quot;appli_line&quot;.&quot;id&quot; IS NOT NULL ORDER BY &quot;appli_site&quot;.&quot;id&quot;
</code></pre>
<p>Ce n&apos;est malheureusement pas tr&#xE8;s efficace. Il y a d&apos;ailleurs un <a href="https://code.djangoproject.com/ticket/25789?ref=techblog.wifirst.net">bug ouvert</a> &#xE0; ce sujet sur le projet Django.</p>
<p>Nous l&apos;avons remplac&#xE9; par un EXIST, environ quatre fois plus efficace sur notre application :</p>
<pre><code class="language-sql">SELECT &quot;appli_site&quot;.&quot;id&quot;, &quot;appli_site&quot;.&quot;name&quot; FROM &quot;appli_site&quot;
WHERE (EXISTS (SELECT 1 FROM &quot;appli_line&quot; WHERE &quot;appli_line&quot;.&quot;site_id&quot; = &quot;appli_site&quot;.&quot;id&quot;
</code></pre>
<p>En attendant que la fonctionnalit&#xE9; existe chez Django, nous avons &#xE9;galement rajout&#xE9; une m&#xE9;thode sur le manager de l&apos;objet :</p>
<pre><code class="language-python">def has_line(self): 
    return self.extra(where=[&apos;EXISTS (SELECT 1 FROM &quot;appli_line&quot; WHERE &quot;appli_line&quot;.&quot;site_id&quot; = &quot;  appli_site&quot;.&quot;id&quot;)&apos;])
</code></pre>
<p>Dernier exemple, nous voulions compter les points d&apos;acc&#xE8;s WiFi joignables et non joignables, par site. Tout comme les lignes, les objets points d&apos;acc&#xE8;s sont li&#xE9;s aux sites par un ForeignKey.</p>
<p>Le code pr&#xE9;c&#xE9;dent it&#xE9;rait sur l&apos;ensemble des points d&apos;acc&#xE8;s, et s&apos;occupait du comptage en python dans un dictionnaire (le code est simplifi&#xE9;, notamment le nom des variables est r&#xE9;duit pour simplifier la lecture) :</p>
<pre><code class="language-python">for site_id, is_reachable in AP.objects.values_list(&apos;site&apos;, &apos;is_reachable&apos;):
    key = &apos;ok&apos; if is_reachable else &apos;down&apos;
    counters[site_id][key] += 1
</code></pre>
<p>Quand le nombre d&apos;objets AP est tr&#xE8;s important, cela g&#xE9;n&#xE8;re beaucoup de lignes de r&#xE9;sultats &#xE0; transf&#xE9;rer (et encore, on ne g&#xE9;n&#xE8;re pas d&apos;objets python en utilisant values_list). Une meilleure solution tout en SQL :</p>
<pre><code class="language-sql">SELECT &quot;appli_site&quot;.&quot;id&quot;,
       (COUNT(&quot;appli_ap&quot;.&quot;mac&quot;) FILTER (WHERE is_reachable = true)) AS &quot;ap_oks&quot;,
       (COUNT(&quot;appli_ap&quot;.&quot;mac&quot;) FILTER (WHERE is_reachable = false)) AS &quot;ap_downs&quot;
FROM &quot;appli_site&quot; LEFT OUTER JOIN &quot;appli_ap&quot; ON (&quot;appli_site&quot;.&quot;id&quot; = &quot;appli_ap&quot;.&quot;site_id&quot;)
GROUP BY &quot;appli_site&quot;.&quot;id&quot;
</code></pre>
<p>Quand le nombre de site est faible par rapport au nombre de points d&apos;acc&#xE8;s (ce qui est tr&#xE8;s vrai dans notre cas), cette requ&#xEA;te va beaucoup plus vite que l&apos;&#xE9;valuation en python. &#xC0; noter que cette requ&#xEA;te est possible enti&#xE8;rement en Django &#xE0; partir de la version 2.0, <a href="https://docs.djangoproject.com/en/2.0/topics/db/aggregation/?ref=techblog.wifirst.net#cheat-sheet">gr&#xE2;ce au niveau param&#xE8;tre filter</a> ajout&#xE9; aux aggr&#xE9;gations.</p>
<p>Ce ne sont pas les seules optimisations, mais avec d&apos;autres petits changements (un jour de d&#xE9;veloppement), on a r&#xE9;ussi &#xE0; arriver au r&#xE9;sultat cit&#xE9; dans l&apos;interview, &#xE0; savoir une diminution par cinq des donn&#xE9;es transf&#xE9;r&#xE9;es entre la base de donn&#xE9;es et l&apos;application Django.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Deux présentations à la Pycon-fr 2017]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Si nous avons pris l&apos;habitude de nous rendre &#xE0; la <a href="https://www.pycon.fr/?ref=techblog.wifirst.net">Pycon-fr</a> tous les ans, nous avons pour la premi&#xE8;re fois cette ann&#xE9;e &#xE9;galement particip&#xE9;s en tant que pr&#xE9;sentateurs &#xE0; cette &#xE9;dition &#xE0; Toulouse.</p>
<p>Notre premi&#xE8;re pr&</p>]]></description><link>https://techblog.wifirst.net/deux-presentations-a-la-pycon-fr/</link><guid isPermaLink="false">5c26359fd8d84a0001d4a474</guid><dc:creator><![CDATA[Florent Fourcot]]></dc:creator><pubDate>Mon, 25 Sep 2017 15:53:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Si nous avons pris l&apos;habitude de nous rendre &#xE0; la <a href="https://www.pycon.fr/?ref=techblog.wifirst.net">Pycon-fr</a> tous les ans, nous avons pour la premi&#xE8;re fois cette ann&#xE9;e &#xE9;galement particip&#xE9;s en tant que pr&#xE9;sentateurs &#xE0; cette &#xE9;dition &#xE0; Toulouse.</p>
<p>Notre premi&#xE8;re pr&#xE9;sentation (chronologiquement) parlait d&apos;une biblioth&#xE8;que Python (<a href="https://github.com/svinota/pyroute2?ref=techblog.wifirst.net">pyroute2</a>) que nous utilisons massivement et sur laquelle nous contribuons en maintenant les modules concernant les ipsets, ainsi que par des patchs divers et vari&#xE9;s. M&#xEA;me si le son n&apos;est pas terrible (je n&apos;avais pas de micro), <a href="http://pyvideo.org/pycon-fr-2017/pyroute2-configurer-votre-reseau-sous-linux-avec-python.html?ref=techblog.wifirst.net">la vid&#xE9;o est disponible</a>, ainsi que les <a href="https://devblog.wifirst.net/attach/2017-09-23_pyroute2.pdf?ref=techblog.wifirst.net">slides</a>.</p>
<p>La seconde &#xE9;tait une pr&#xE9;sentation plus g&#xE9;n&#xE9;rale de nos activit&#xE9;s et un retour d&apos;exp&#xE9;rience sur les outils que nous utilisons (ansible, SNMP en python, etc). De la m&#xEA;me fa&#xE7;on, <a href="http://pyvideo.org/pycon-fr-2017/construire-et-gerer-un-operateur-internet-en-python.html?ref=techblog.wifirst.net">la vid&#xE9;o</a> et les <a href="https://devblog.wifirst.net/attach/2017-09-24_wifirst.pdf?ref=techblog.wifirst.net">slides</a> sont disponibles.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>