Mercurial > code > home > repos > homeauto
changeset 531:f372e9d358d2
rewrite dhcpleases to use dnsmasq's data files, and all the new build stuff
Ignore-this: 2dffc17b676253d4fec234fc096db4d5
author | drewp@bigasterisk.com |
---|---|
date | Tue, 23 Apr 2019 02:56:07 -0700 |
parents | f09fc1a18b66 |
children | 71aa55cd8433 |
files | service/dhcpleases/Dockerfile service/dhcpleases/dhcpleases.py service/dhcpleases/index.html service/dhcpleases/pydeps service/dhcpleases/requirements.txt service/dhcpleases/tasks.py |
diffstat | 6 files changed, 147 insertions(+), 106 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/service/dhcpleases/Dockerfile Tue Apr 23 02:56:07 2019 -0700 @@ -0,0 +1,15 @@ +FROM bang6:5000/base_x86 + +WORKDIR /opt + +COPY requirements.txt ./ + +RUN pip3 install --index-url https://projects.bigasterisk.com/ --extra-index-url https://pypi.org/simple -r requirements.txt +# not sure why this doesn't work from inside requirements.txt +RUN pip3 install -U 'https://github.com/drewp/cyclone/archive/python3.zip?v3' + +COPY *.py *.html ./ + +EXPOSE 9073 + +CMD [ "python3", "dhcpleases.py" ]
--- a/service/dhcpleases/dhcpleases.py Tue Apr 23 02:18:07 2019 -0700 +++ b/service/dhcpleases/dhcpleases.py Tue Apr 23 02:56:07 2019 -0700 @@ -3,71 +3,93 @@ also read 'arp -an' and our dns list """ -import sys -import datetime -sys.path.append("/my/site/magma") -from stategraph import StateGraph +import datetime, itertools, os + +from docopt import docopt +from dateutil.tz import tzlocal from rdflib import URIRef, Namespace, Literal, RDF, RDFS, XSD, ConjunctiveGraph -from dateutil.tz import tzlocal +from twisted.internet import reactor, task import cyclone.web -from twisted.internet import reactor, task -from isc_dhcp_leases.iscdhcpleases import IscDhcpLeases -sys.path.append("/my/proj/homeauto/lib") + +from greplin import scales +from greplin.scales.cyclonehandler import StatsHandler from patchablegraph import PatchableGraph, CycloneGraphEventsHandler, CycloneGraphHandler -sys.path.append("/my/proj/rdfdb") -from rdfdb.patch import Patch +from standardservice.logsetup import log, verboseLogging DEV = Namespace("http://projects.bigasterisk.com/device/") ROOM = Namespace("http://projects.bigasterisk.com/room/") +ctx = DEV['dhcp'] + +STATS = scales.collection('/root', + scales.PmfStat('readLeases'), + scales.IntStat('filesDidntChange'), + ) def timeLiteral(dt): return Literal(dt.replace(tzinfo=tzlocal()).isoformat(), datatype=XSD.dateTime) + +def macUri(macAddress: str) -> URIRef: + return URIRef("http://bigasterisk.com/mac/%s" % macAddress.lower()) -def update(masterGraph): - g = ConjunctiveGraph() - ctx = DEV['dhcp'] +class Poller: + def __init__(self, graph): + self.graph = graph + self.fileTimes = {'/opt/dnsmasq/10.1/leases': 0, '/opt/dnsmasq/10.2/leases': 0} + task.LoopingCall(self.poll).start(2) - now = datetime.datetime.now() - for mac, lease in IscDhcpLeases('/var/lib/dhcp/dhcpd.leases' - ).get_current().items(): - uri = URIRef("http://bigasterisk.com/dhcpLease/%s" % lease.ethernet) + def anythingToRead(self): + ret = False + for f, t in self.fileTimes.items(): + mtime = os.path.getmtime(f) + if mtime > t: + self.fileTimes[f] = mtime + ret = True + return ret + + def poll(self): + if not self.anythingToRead(): + STATS.filesDidntChange += 1 + return - g.add((uri, RDF.type, ROOM['DhcpLease'], ctx)) - g.add((uri, ROOM['leaseStartTime'], timeLiteral(lease.start), ctx)) - g.add((uri, ROOM['leaseEndTime'], timeLiteral(lease.end), ctx)) - if lease.end < now: - g.add((uri, RDF.type, ROOM['ExpiredLease'], ctx)) - ip = URIRef("http://bigasterisk.com/localNet/%s/" % lease.ip) - g.add((uri, ROOM['assignedIp'], ip, ctx)) - g.add((ip, RDFS.label, Literal(lease.ip), ctx)) - mac = URIRef("http://bigasterisk.com/mac/%s" % lease.ethernet) - g.add((uri, ROOM['ethernetAddress'], mac, ctx)) - g.add((mac, ROOM['macAddress'], Literal(lease.ethernet), ctx)) - if lease.hostname: - g.add((mac, ROOM['dhcpHostname'], Literal(lease.hostname), ctx)) - masterGraph.setToGraph(g) - + with STATS.readLeases.time(): + g = ConjunctiveGraph() + for line in itertools.chain(*[open(f) for f in self.fileTimes]): + # http://lists.thekelleys.org.uk/pipermail/dnsmasq-discuss/2016q2/010595.html + expiration_secs, addr, ip, hostname, clientid = line.strip().split(' ') + + uri = macUri(addr) + g.add((uri, RDF.type, ROOM['HasDhcpLease'], ctx)) + g.add((uri, ROOM['macAddress'], Literal(addr), ctx)) + g.add((uri, ROOM['assignedIp'], Literal(ip), ctx)) + + if hostname != '*': + g.add((uri, ROOM['dhcpHostname'], Literal(hostname), ctx)) + + self.graph.setToGraph(g) + if __name__ == '__main__': - config = { - 'servePort' : 9073, - } - from twisted.python import log as twlog - twlog.startLogging(sys.stdout) - #log.setLevel(10) - #log.setLevel(logging.DEBUG) + arg = docopt(""" + Usage: store.py [options] + + -v Verbose + --port PORT Serve on port [default: 9073]. + """) + + verboseLogging(arg['-v']) + masterGraph = PatchableGraph() - task.LoopingCall(update, masterGraph).start(1) + poller = Poller(masterGraph) reactor.listenTCP( - config['servePort'], + int(arg['--port']), cyclone.web.Application( [ (r"/()", cyclone.web.StaticFileHandler, {"path": ".", "default_filename": "index.html"}), - (r'/graph', CycloneGraphHandler, {'masterGraph': masterGraph}), (r'/graph/events', CycloneGraphEventsHandler, {'masterGraph': masterGraph}), + (r'/stats/(.*)', StatsHandler, {'serverName': 'dhcpleases'}), ], masterGraph=masterGraph )) reactor.run()
--- a/service/dhcpleases/index.html Tue Apr 23 02:18:07 2019 -0700 +++ b/service/dhcpleases/index.html Tue Apr 23 02:56:07 2019 -0700 @@ -4,71 +4,43 @@ <title>dhcp leases</title> <meta charset="utf-8" /> <script src="/lib/polymer/1.0.9/webcomponentsjs/webcomponents-lite.min.js"></script> - <script src="/lib/underscore-1.5.2.min.js"></script> - <link rel="import" href="/rdf/n3+polymer/trig-store.html"> - <link rel="import" href="/lib/polymer/1.0.9/iron-ajax/iron-ajax.html"> + + <script src="/lib/require/require-2.3.3.js"></script> + <script src="/rdf/common_paths_and_ns.js"></script> + + <link rel="import" href="/rdf/streamed-graph.html"> + <link rel="import" href="/lib/polymer/1.0.9/polymer/polymer.html"> + + <meta name="mobile-web-app-capable" content="yes"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + </head> <body> <h1>Active dhcp leases</h1> - <p><a href="../wifi">go to wifi</a></p> + <p><a href="../wifi">go to wifi</a>; <a href="/rdf/browse/?graph=https%3A%2F%2Fbigasterisk.com%2Fsse_collector%2Fgraph%2Fnetwork">merge with wifi</a></p> + + <template id="t" is="dom-bind"> - <dom-module id="dhcp-table"> - <template> - <iron-ajax auto url="graph" - params='{"pruneExpired": "true"}' - handle-as="text" - last-response="{{ajaxResponse}}"></iron-ajax> - <trig-store id="ts" trig-input="{{ajaxResponse}}"></trig-store> + <streamed-graph url="graph/events" graph="{{graph}}"></streamed-graph> + <div id="out"></div> + <script type="module" src="/rdf/streamed_graph_view.js"></script> - <table> - <template is="dom-repeat" items="{{devices}}"> - <tr> - <td>{{item.dhcpHostname}}</td> - <td>{{item.ip}}</td> - <td>{{item.mac}}</td> - </tr> - </template> - </table> - </template> - </dom-module> - - <script> - HTMLImports.whenReady(function () { - Polymer({ - is: "dhcp-table", - ready: function() { - this.$.ts.addEventListener('store-changed', this.storeChanged.bind(this)); - this.devices = []; - }, - storeChanged: function(ev) { - var store = ev.detail.value; - var find = function(s, p, o) { return store.findAllGraphs(s, p, o); }; - var findOne = function(s, p, o) { - var rows = find(s, p, o); - return rows[0]; - }; + </template> + <style> + .served-resources { + margin-top: 4em; + border-top: 1px solid gray; + padding-top: 1em; + } + .served-resources a { + padding-right: 2em; + } + </style> - this.devices = []; - - find(null, "room:dhcpHostname", null).forEach(function(row) { - var out = {dhcpHostname: N3.Util.getLiteralValue(row.object)} - var lease = findOne(null, "room:ethernetAddress", - row.subject).subject; - out.ip = N3.Util.getLiteralValue( - findOne(findOne(lease, "room:assignedIp", null).object, - "rdfs:label", null).object); - out.mac = N3.Util.getLiteralValue( - findOne(findOne(lease, "room:ethernetAddress", null).object, - "room:macAddress", null).object); - this.devices.push(out); - }.bind(this)); - this.devices = _.sortBy(this.devices, 'dhcpHostname'); - } - }); - }); - </script> - <dhcp-table></dhcp-table> - - <iframe src="https://bigasterisk.com/wifi" height="500" style="width: 100%"></iframe> + <div class="served-resources"> + <a href="stats/">/stats/</a> + <a href="graph">/graph</a> + <a href="graph/events">/graph/events</a> + </div> </body> </html>
--- a/service/dhcpleases/pydeps Tue Apr 23 02:18:07 2019 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ -cyclone==1.1 -python-dateutil==2.4.1 -rdflib==4.2.0 -https://github.com/drewp/python-isc-dhcp-leases/archive/master.zip \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/service/dhcpleases/requirements.txt Tue Apr 23 02:56:07 2019 -0700 @@ -0,0 +1,9 @@ +docopt +git+http://github.com/drewp/scales.git@448d59fb491b7631877528e7695a93553bfaaa93#egg=scales +https://github.com/drewp/cyclone/archive/python3.zip +rdflib==4.2.2 + +cycloneerr +patchablegraph==0.5.0 +rdfdb==0.8.0 +standardservice==0.3.0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/service/dhcpleases/tasks.py Tue Apr 23 02:56:07 2019 -0700 @@ -0,0 +1,27 @@ +from invoke import task + +JOB = 'dhcpleases' +PORT = 9073 +TAG = f'bang6:5000/{JOB}_x86:latest' + + +@task +def build_image(ctx): + ctx.run(f'docker build --network=host -t {TAG} .') + +@task(pre=[build_image]) +def push_image(ctx): + ctx.run(f'docker push {TAG}') + +@task(pre=[build_image]) +def shell(ctx): + ctx.run(f'docker run --rm -it --cap-add SYS_PTRACE --net=host {TAG} /bin/bash', pty=True) + +@task(pre=[build_image]) +def local_run(ctx): + ctx.run(f'docker run --rm -it -p {PORT}:{PORT} --net=host -v /opt/dnsmasq:/opt/dnsmasq {TAG} python3 dhcpleases.py -v', pty=True) + +@task(pre=[push_image]) +def redeploy(ctx): + ctx.run(f'sudo /my/proj/ansible/playbook -l bang -t {JOB}') + ctx.run(f'supervisorctl -s http://bang:9001/ restart {JOB}_{PORT}')