<div dir="ltr">Updates regarding one of the NetJSON implementations.<br><br><div class="gmail_quote"><div dir="ltr">---------- Forwarded message ---------<br>From: Federico Capoano <<a href="mailto:federico.capoano@gmail.com">federico.capoano@gmail.com</a>><br>Date: Wed, May 31, 2017 at 12:55 PM<br>Subject: [netjsonconfig] Motivation of changes in 0.6.0 alpha<br>To: OpenWISP <<a href="mailto:openwisp@googlegroups.com">openwisp@googlegroups.com</a>>, Edoardo Putti <<a href="mailto:edoardo.putti@gmail.com">edoardo.putti@gmail.com</a>>, Ritwick D'Souza <<a href="mailto:dsouza.ritwick@gmail.com">dsouza.ritwick@gmail.com</a>><br></div><br><br><div dir="ltr"><div>Hi everyone,</div><div><br></div><div>recently I started introducing some important changes in <a href="https://github.com/openwisp/netjsonconfig" target="_blank">netjsonconfig</a>, the library used to generate configuration of routers.</div><div><br></div><div>This change should not affect openwisp2 users negatively, I'm already testing the current master on 3 instances and already fixed a few bugs.</div><div><br></div><div>Nevertheless, the change is affecting negatively the 2 GSoC students who have started to work on the AirOS and Raspbian backends, so I will try to explain why this change is needed.</div><div><br></div><div><b>Backward conversion</b></div><div><br></div><div>One of the features that is missing in OpenWISP 2 is the possibility to easily store the full configuration of devices in the controller.</div><div><br></div><div>Since OpenWRT/LEDE allows merging a remote configuration with the local one, that's the default mode we are using now in OpenWISP 2 (it was also the only mode available in OpenWISP 1).</div><div><br></div><div>This method has its own disadvantages:</div><div><ul><li>operators have to remember what is in their local configuration, in order to write a remote configuration that works well with the local one</li><li>some packages use anonymous UCI blocks by default, which are impossible for openwisp to override in the merge mode, hence creating duplicate configurations</li></ul></div><div>These disadvantages are holding down OpenWISP 2 from becoming easier to use. OpenWISP 2 is very powerful, but hard to use properly. Which is a shame, because it means many people will give up and use some kind of home baked solution with its own class of bugs, reinventing the wheel: no good framework and ecosystem of tools for low cost open source networks can emerge for such a situation.</div><div><br></div><div>That's why we need to add a way to store the full configuration of routers in the controller and this needs to happen automatically: here where backward conversion in netjsonconfig comes into play.</div><div><br></div><div>Without the backward conversion, we cannot even use the AirOS or Raspbian backends because those don't have a way to easily merge different configurations like OpenWRT/LEDE (uci import --merge), users would need to manually enter the full configuration of their devices in openwisp2: that will never happen!</div><div><br></div><div><b>The new implementation</b><br></div><div><br></div><div>I've been designing and planning this feature since at least a year, but never got the time to implement because there were more urgent matters. Now is the right time to introduce it because it's very much needed.<br></div><div><br></div><div>The new implementation adds a few new concepts in the codebase:</div><div><ul><li>intermediate data structure<br></li><li>converters</li><li>parsers</li></ul></div><div>Everything revolves around the data structures: the <b><a href="http://netjson.org/" target="_blank">NetJSON</a> configuration dictionary</b> and the <b>intermediate data structures</b>.</div><div><br></div><div>Remember what <b>Linux Torvalds</b> said?</div><div><b><i>"Bad programmers worry about the code. Good programmers worry about data structures and their relationships."</i></b><br></div><div><br></div><div>This intermediate data structure represents the native configuration of the backend in a python data structure (lists and dictionaries).</div><div><br></div><div>This data structure is a convenient way to simplify conversion in both direction, parsing and rendering, and will be different for each backend.</div><div><br></div><div>Take a look at this diagram:</div><div><img src="cid:15c5e0f60db5b79cb4d1" alt="netjsonconfig backward conversion.jpg" class="" style="max-width: 100%;"><br></div><div><b>Converter</b> classes take care of converting between NetJSON and intermediate data structure (and vice versa):</div><div><ul><li><font face="monospace">to_intermediate</font> converts the NetJSON configuration dictionary to the intermediate data structure</li><li><font face="monospace">to_netjson</font> (not pushed to the repository yet) converts the intermediate data structure to NetJSON configuration dictionary</li></ul><div>These two main methods can then call helper methods in order to split complex logic in smaller methods that are easier for the human mind to grasp.</div></div><div><br></div><div><b>Renderers</b> take care of rendering the intermediate data structure to the native format.</div><div><br></div><div><b>Parsers</b> perform the opposite operation of Renderers: they take care of parsing native format and build the intermediate data structure.</div><div><br></div><div>In case of OpenWRT and AirOS, we can get away with just 1 renderer (with just 1 jinja2 tempate) and 1 parser. In the case of Raspbian, we may need to have a render and a template for each format that needs to be rendered (eg: /etc/network/interfaces, ini files, dnsmasq, iptables, ecc).</div><div><br></div><div><b>Advantages</b></div><div><ul><li>Cleaner, well defined workflow - also easier to document and learn</li><li>Less boilerplate code in those cases where the native configuration of the router is centralized in one format (OpenWRT / AirOS / PfSense / vyos), because most of the logic goes in converters while there can be only 1 renderer and 1 parser</li><li>Last but not least, makes it possible to implement backward conversion without going crazy with messy spaghetti code. Our sanity is very important not only for our work but for our life!</li></ul></div><div><b>Disadvantages</b></div><div><br></div><div>Developers using lower level features of netjsonconfig in their programs and GSOC students working on the netjsonconfig backends will have to rewrite part of their code. A bit of more work needed, but in my opinion a worthy price to pay for a cleaner and more robust implementation.</div><div><br></div><div><b>How to upgrade your code</b></div><div><b><br></b></div><div>Proceed as follows:</div><div><ul><li>the logic that before was contained in the renderers, should most probably go into the converters</li><li>design an intermediate data structure with the following features:</li><ul><li>closely represents your native configuration format (or formats)</li><li>can be easily rendered with jinja2</li><li>can be easily built when parsing the native format (or formats)</li></ul><li>simplify your renderers so that you have 1 renderer and 1 template for each format supported</li></ul><div><b>Conclusion</b></div><div><br></div><div>That's it. Sorry for the length. I hope the explanation is clear enough.</div></div></div><div dir="ltr"><div><br></div><div>Federico</div></div></div></div>