<div dir="ltr">For people who have been around for a while, I am the Dave Cole who posted this message:<div><br></div><div><a href="https://lists.isc.org/pipermail/kea-dev/2017-November/000837.html">https://lists.isc.org/pipermail/kea-dev/2017-November/000837.html</a><br></div><div><br></div><div>I tried multiple times while working at the nbn to convince them to opensource the work I did embedding Python in Kea.  While everyone was enthusiastic about releasing the code, none of my managers ever seemed to be able to make in progress along those lines.</div><div><br></div><div>I left the nbn close to a year ago and it has been annoying me ever since.  These Christmas holidays I decided to see if I could start from scratch and embed Python 3.  Turns out these things are a lot easier the second time around.<br></div><div><br></div><div>In less than 2 weeks I have replicated the work I did at nbn and taken things a little further.  To ensure I was doing something useful I decided to re-implement the lease-cmds example hook from the Kea source.  <a href="https://kea.readthedocs.io/en/latest/arm/hooks.html#lease-cmds-lease-commands">https://kea.readthedocs.io/en/latest/arm/hooks.html#lease-cmds-lease-commands</a></div><div><br></div><div>I am wondering if anyone is interested in playing with this work?  Some more information follows...</div><div><br></div><div>I have only been working on IPv4 for now, but have implemented all of the IPv4 commands except lease4-add and lease4-update in 242 lines of Python.</div><div><br></div><div>The embedded interpreter can run Pythons threads.  For a stress test I wrote a simple hook that uses prometheus-client to export metrics like this:</div><div><div style="color:rgb(212,212,212);background-color:rgb(30,30,30);font-family:"Droid Sans Mono",monospace,monospace,"Droid Sans Fallback";font-size:14px;line-height:19px;white-space:pre"><div><span style="color:rgb(197,134,192)">from</span> kea <span style="color:rgb(197,134,192)">import</span> *</div><div><span style="color:rgb(197,134,192)">from</span> ipaddress <span style="color:rgb(197,134,192)">import</span> IPv4Address, IPv4Network</div><div><span style="color:rgb(197,134,192)">from</span> prometheus_client <span style="color:rgb(197,134,192)">import</span> start_http_server, Counter</div><br><div>PKT_RECEIVE = Counter(<span style="color:rgb(206,145,120)">'dhcp4_pkt_receive_total'</span>, <span style="color:rgb(206,145,120)">'Packets received'</span>, [<span style="color:rgb(206,145,120)">'type'</span>])</div><div>PKT_SEND = Counter(<span style="color:rgb(206,145,120)">'dhcp4_pkt_send_total'</span>, <span style="color:rgb(206,145,120)">'Packets sent'</span>, [<span style="color:rgb(206,145,120)">'type'</span>])</div><br><div><span style="color:rgb(86,156,214)">class</span> <span style="color:rgb(78,201,176)">Config</span>:</div><div>    <span style="color:rgb(86,156,214)">def</span> <span style="color:rgb(220,220,170)">__init__</span>(<span style="color:rgb(156,220,254)">self</span>, <span style="color:rgb(156,220,254)">conf</span>):</div><div>        dhcp4 = conf[<span style="color:rgb(206,145,120)">'Dhcp4'</span>]</div><div>        <span style="color:rgb(86,156,214)">self</span>.options = [Option(DHO_DHCP_LEASE_TIME).setUint32(dhcp4.get(<span style="color:rgb(206,145,120)">'valid-lifetime'</span>, <span style="color:rgb(181,206,168)">7200</span>)),</div><div>                        Option(DHO_DHCP_RENEWAL_TIME).setUint32(dhcp4.get(<span style="color:rgb(206,145,120)">'renew-timer'</span>, <span style="color:rgb(181,206,168)">1800</span>)),</div><div>                        Option(DHO_DHCP_REBINDING_TIME).setUint32(dhcp4.get(<span style="color:rgb(206,145,120)">'rebind-timer'</span>, <span style="color:rgb(181,206,168)">3600</span>))]</div><div>        <span style="color:rgb(106,153,85)"># snip extra code</span><br></div><div><br></div><div><span style="color:rgb(86,156,214)">def</span> <span style="color:rgb(220,220,170)">load</span>(<span style="color:rgb(156,220,254)">handle</span>):</div><div>    <span style="color:rgb(86,156,214)">global</span> config, type_to_label</div><div>    config = Config(CfgMgr().getStagingCfg().toElement())</div><div>    type_to_label = <span style="color:rgb(78,201,176)">dict</span>([(v, k[<span style="color:rgb(181,206,168)">4</span>:].lower())</div><div>                          <span style="color:rgb(197,134,192)">for</span> k, v <span style="color:rgb(86,156,214)">in</span> <span style="color:rgb(220,220,170)">globals</span>().items()</div><div>                          <span style="color:rgb(197,134,192)">if</span> k.startswith(<span style="color:rgb(206,145,120)">'DHCP'</span>)])</div><div>    start_http_server(<span style="color:rgb(181,206,168)">9100</span>)</div><div>    <span style="color:rgb(197,134,192)">return</span> <span style="color:rgb(181,206,168)">0</span></div><div style="line-height:19px"><div><span style="color:rgb(86,156,214)">
def</span> <span style="color:rgb(220,220,170)">pkt4_receive</span>(<span style="color:rgb(156,220,254)">handle</span>):</div><div>    query = handle.getArgument(<span style="color:rgb(206,145,120)">'query4'</span>)</div><div>    PKT_RECEIVE.labels(<span style="color:rgb(156,220,254)">type</span>=type_to_label.get(query.getType(), <span style="color:rgb(206,145,120)">'unknown'</span>)).inc()</div><div>    <span style="color:rgb(106,153,85)"># client must request address in Option 82, suboption 1.</span></div><div>    o = query.getOption(DHO_DHCP_AGENT_OPTIONS)</div><div>    <span style="color:rgb(197,134,192)">if</span> <span style="color:rgb(86,156,214)">not</span> o:</div><div>        <span style="color:rgb(197,134,192)">raise</span> <span style="color:rgb(78,201,176)">RuntimeError</span>(<span style="color:rgb(206,145,120)">'client must send option </span><span style="color:rgb(86,156,214)">%s</span><span style="color:rgb(206,145,120)">'</span> % DHO_DHCP_AGENT_OPTIONS)</div><div>    o = o.getOption(<span style="color:rgb(181,206,168)">1</span>)</div><div>    <span style="color:rgb(197,134,192)">if</span> <span style="color:rgb(86,156,214)">not</span> o:</div><div>        <span style="color:rgb(197,134,192)">raise</span> <span style="color:rgb(78,201,176)">RuntimeError</span>(<span style="color:rgb(206,145,120)">'missing suboption 1 in option </span><span style="color:rgb(86,156,214)">%s</span><span style="color:rgb(206,145,120)">'</span> % DHO_DHCP_AGENT_OPTIONS)</div><div>    handle.setContext(<span style="color:rgb(206,145,120)">'requested-addr'</span>, o.getString())</div><div>    <span style="color:rgb(197,134,192)">return</span> <span style="color:rgb(181,206,168)">0</span></div><br></div></div></div><div><br></div><div>Then I run another program that runs dhtest (<a href="https://github.com/saravana815/dhtest">https://github.com/saravana815/dhtest</a>) many times in parallel sending the address I want from Kea in option 82.  This lets me confirm that the server allocated the correct address.</div><div><br></div><div>Running the server and stress test on my laptop I get these results:</div><div><span style="font-family:monospace">root@d95ea00a891d:/workdir# python3 examples/stress-test/stress_test.py --parallel 50 --total 10000</span><br></div><div><font face="monospace">00:01: 713 at 714/sec<br>00:02: 1564 at 782/sec<br>00:03: 2364 at 803/sec<br>00:04: 3042 at 773/sec<br>00:05: 3756 at 730/sec<br>00:06: 4470 at 702/sec<br>00:07: 5203 at 720/sec<br>00:08: 5929 at 726/sec<br>00:09: 6703 at 744/sec<br>00:10: 7368 at 722/sec<br>00:11: 8089 at 719/sec<br>00:12: 8809 at 702/sec<br>00:13: 9499 at 711/sec<br>total 10000 at 730/sec with 0 errors<br></font></div><div><br></div><div>In another shell I continuously query prometheus metrics like this:</div><div><font face="monospace">root@f30313c2a09d:/workdir# while true; do curl -s <a href="http://localhost:9100/">http://localhost:9100/</a> | grep ^dhcp && echo; sleep 0.2; done<br></font></div><div><br></div><div>This is to give me some confidence that my threading is working correctly.  As soon as the first request hits the server it start producing output a bit like this:</div><div><font face="monospace">dhcp4_pkt_receive_total{type="request"} 50.0<br>dhcp4_pkt_receive_total{type="discover"} 50.0<br>dhcp4_pkt_send_total{type="ack"} 50.0<br>dhcp4_pkt_send_total{type="offer"} 50.0<br><br>dhcp4_pkt_receive_total{type="request"} 211.0<br>dhcp4_pkt_receive_total{type="discover"} 211.0<br>dhcp4_pkt_send_total{type="ack"} 211.0<br>dhcp4_pkt_send_total{type="offer"} 211.0<br><br>dhcp4_pkt_receive_total{type="request"} 389.0<br>dhcp4_pkt_receive_total{type="discover"} 395.0<br>dhcp4_pkt_send_total{type="ack"} 389.0<br>dhcp4_pkt_send_total{type="offer"} 395.0<br></font></div><div><br></div><div><br></div></div>