Skip to content

Commit

Permalink
Allow dockerDNS module to have more interactions with caller.
Browse files Browse the repository at this point in the history
 * Replace run function by a class
 * Allow this class to be startable and stopable
 * Make tests autonomous
 * Add Makefile in order to run tests
 * Modify dockerDNs module version
  • Loading branch information
dangoncalves committed Jan 27, 2022
1 parent c250407 commit e92b6e0
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 47 deletions.
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
test:
@python -m twisted.trial tests
39 changes: 35 additions & 4 deletions dockerDNS.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@


import argparse
from dockerDNS import DNS_PORT, LISTEN_ADDRESS, run
import signal
import sys
from dockerDNS import DNS_PORT, LISTEN_ADDRESS, DockerDNS


def getForwarders(forwarders=None, listenAddress=LISTEN_ADDRESS):
Expand All @@ -32,6 +34,30 @@ def getForwarders(forwarders=None, listenAddress=LISTEN_ADDRESS):
return forwarders


class DNSHandler():
"""Handle DockerDNS start and stop"""
def __init__(self, port=DNS_PORT,
listenAddress=LISTEN_ADDRESS,
forwarders=None):
self.runner = DockerDNS(port=port,
listenAddress=listenAddress,
forwarders=forwarders)
signal.signal(signal.SIGTERM, self.signal_handler)
signal.signal(signal.SIGINT, self.signal_handler)

def signal_handler(self, signum, frame):
"""Signal handler that can stop the runner"""
self.runner.stop()

def start(self):
"""Runner starter"""
self.runner.start()

def clean(self):
"""Runner cleaner"""
self.runner.clean()


if __name__ == "__main__":
description = "Resolve docker container's name into IPv4 address"
parser = argparse.ArgumentParser(description=description)
Expand All @@ -46,6 +72,11 @@ def getForwarders(forwarders=None, listenAddress=LISTEN_ADDRESS):
options = parser.parse_args()
forwarders = getForwarders(forwarders=options.forwarders,
listenAddress=options.listenAddress)
run(port=options.port,
listenAddress=options.listenAddress,
forwarders=forwarders)

DNSHandler = DNSHandler(port=options.port,
listenAddress=options.listenAddress,
forwarders=forwarders)
DNSHandler.start()
DNSHandler.clean()

sys.exit()
6 changes: 3 additions & 3 deletions dockerDNS/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from .dockerDNS import DNS_PORT, LISTEN_ADDRESS, run
from .dockerDNS import DNS_PORT, LISTEN_ADDRESS, DockerDNS

__all__ = [
"DNS_PORT",
"LISTEN_ADDRESS",
"run"
"DockerDNS"
]

__version__ = 0.2
__version__ = 0.3
79 changes: 54 additions & 25 deletions dockerDNS/dockerDNS.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
#!/usr/bin/env python3
"""
Resolve docker container's name into IPv4 address
"""Resolve docker container's name into IPv4 address"""

python3 docker-dns.py
"""

import os
import docker
from threading import Thread
from twisted.internet import reactor, defer
from twisted.names import client, dns, server


LISTEN_ADDRESS = "127.0.0.1"
DNS_PORT = 53

Expand Down Expand Up @@ -52,15 +46,20 @@ class EventsListener(Thread):
def __init__(self, resolver):
super().__init__()
self.resolver = resolver
self.eventListener = None

def run(self):
eventListener = self.resolver.dockerClient.events(
self.eventListener = self.resolver.dockerClient.events(
filters={"event": ["start", "die"]},
decode=True)
for e in eventListener:
for e in self.eventListener:
callback = getattr(self, e["Action"] + "Callback")
callback(e)

def join(self, timeout=None):
self.eventListener.close()
super().join(timeout)

def startCallback(self, event):
containerName = event["Actor"]["Attributes"]["name"]
api = self.resolver.dockerClient.api
Expand All @@ -73,19 +72,49 @@ def dieCallback(self, event):
self.resolver.removeContainer(containerName)


def run(port=DNS_PORT, listenAddress=LISTEN_ADDRESS, forwarders=None):
"""Configure and execute the DNS server."""
dockerClient = docker.from_env()
resolver = DockerResolver(dockerClient=dockerClient,
servers=forwarders)
eventsListener = EventsListener(resolver)
eventsListener.start()
factory = server.DNSServerFactory(clients=[resolver])
protocol = dns.DNSDatagramProtocol(controller=factory)
reactor.listenUDP(port=port, protocol=protocol, interface=listenAddress)
reactor.listenTCP(port=port, factory=factory, interface=listenAddress)
reactor.run()
eventsListener.join(1)
# For an unknown reason sys.exit() does not work
# so we use this hack.
os._exit(0)
class DockerDNS():
"""Start and stop DockerDNS Service"""
def __init__(self, port=None, listenAddress=None, forwarders=None):
self.port = port
self.listenAddress = listenAddress
self.forwarders = forwarders

self.eventsListener = None

self.udp_listener = None
self.tcp_listener = None

if self.port is None:
self.port = DNS_PORT
if self.listenAddress is None:
self.listenAddress = LISTEN_ADDRESS

def start(self):
"""Configure and execute the DNS server."""
dockerClient = docker.from_env()
resolver = DockerResolver(dockerClient=dockerClient,
servers=self.forwarders)

self.eventsListener = EventsListener(resolver)
self.eventsListener.start()
factory = server.DNSServerFactory(clients=[resolver])
protocol = dns.DNSDatagramProtocol(controller=factory)
self.udp_listener = reactor.listenUDP(port=self.port,
protocol=protocol,
interface=self.listenAddress)
self.tcp_listener = reactor.listenTCP(port=self.port,
factory=factory,
interface=self.listenAddress)
reactor.run()

def clean(self):
"""Clean all the resources"""
self.stop()
self.eventsListener.join()

def stop(self):
"""Stop the reactor if running"""
if reactor.running:
self.udp_listener.stopListening()
self.tcp_listener.stopListening()
reactor.stop()
32 changes: 17 additions & 15 deletions tests/test_dockerDNS.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
import unittest
import docker
import time
import os
import signal
from multiprocessing import Process
from dockerDNS import DockerDNS
from dnslib.dns import DNSRecord
# from dockerDNS import run


def resolveDNS(query, server, port):
Expand All @@ -19,23 +22,22 @@ def resolveDNS(query, server, port):

class TestDockerDNS(unittest.TestCase):

def dockerDNSProcess(self):
p = DockerDNS(port=35353,
listenAddress="127.0.0.1",
forwarders="8.8.8.8")
p.start()
p.clean()

def setUp(self):
self.dockerClient = docker.from_env()

# FIXME: Tests should be autonomous.
#
# Server cannot be started at this time
# because we have to stop it gracefully
# and the run function does not support
# that actually.
#
# By the way, this mean that we have to
# start the server manually before starting
# tests.
# Keep in mind that we have to use the
# port 35353 in order to start the server
# as a regular user.
# run(35353, "127.0.0.1", "8.8.8.8")
self.process = Process(target=self.dockerDNSProcess)
self.process.start()

def tearDown(self):
os.kill(self.process.pid, signal.SIGTERM)
self.process.join()

def test_basic_dns_request(self):
self.dockerClient.containers.run(
Expand Down

0 comments on commit e92b6e0

Please sign in to comment.