diff --git a/python/tools/benchmark.py b/python/tools/benchmark.py
index fe3bfc99..21a32956 100755
--- a/python/tools/benchmark.py
+++ b/python/tools/benchmark.py
@@ -19,22 +19,22 @@
import os
import sys
import subprocess
-import time
-import random
-import string
-import threading
-import queue
import signal
import argparse
-import re
+import time
-from pyroute2.netns.process.proxy import NSPopen
import numpy as np
import matplotlib.pyplot as plt
-from dhtnetwork import DhtNetwork
+from dht.network import DhtNetwork
+from dht.network import DhtNetworkSubProcess
+from dht.tests import PerformanceTest, PersistenceTest
+from dht import virtual_network_builder
+from dht import network as dhtnetwork
+
from opendht import *
+
class WorkBench():
"""
This contains the initialisation information, such as ipv4/ipv6, number of
@@ -67,7 +67,7 @@ class WorkBench():
def create_virtual_net(self):
if self.virtual_locs > 1:
- cmd = ["python3", "virtual_network_builder.py", "-i", self.ifname, "-n", str(self.clusters), '-l', str(self.loss), '-d', str(self.delay)]
+ cmd = ["python3", os.path.abspath(virtual_network_builder.__file__), "-i", self.ifname, "-n", str(self.clusters), '-l', str(self.loss), '-d', str(self.delay)]
if not self.disable_ipv4:
cmd.append('-4')
if not self.disable_ipv6:
@@ -79,11 +79,11 @@ class WorkBench():
def destroy_virtual_net(self):
print('Shuting down the virtual IP network.')
- subprocess.call(["python3", "virtual_network_builder.py", "-i", self.ifname, "-n", str(self.clusters), "-r"])
+ subprocess.call(["python3", os.path.abspath(virtual_network_builder.__file__), "-i", self.ifname, "-n", str(self.clusters), "-r"])
def start_cluster(self, i):
if self.local_bootstrap:
- cmd = ["python3", "dhtnetwork.py", "-n", str(self.node_per_loc), '-I', self.ifname+str(i)+'.1']
+ cmd = ["python3", os.path.abspath(dhtnetwork.__file__), "-n", str(self.node_per_loc), '-I', self.ifname+str(i)+'.1']
if self.remote_bootstrap:
cmd.extend(['-b', self.remote_bootstrap, '-bp', "5000"])
else:
@@ -132,708 +132,6 @@ class WorkBench():
self.stop_cluster(procs_count-i-1)
-class DhtNetworkSubProcess(NSPopen):
- """
- Handles communication with DhtNetwork sub process.
-
- When instanciated, the object's thread is started and will read the sub
- process' stdout until it finds 'DhtNetworkSubProcess.NOTIFY_TOKEN' token,
- therefor, waits for the sub process to spawn.
- """
- # requests
- REMOVE_NODE_REQ = b"rn"
- SHUTDOWN_NODE_REQ = b"sdn"
- SHUTDOWN_REPLACE_NODE_REQ = b'sdrn'
- SHUTDOWN_CLUSTER_REQ = b"sdc"
- DUMP_STORAGE_REQ = b"strl"
- MESSAGE_STATS = b"gms"
-
-
- # tokens
- NOTIFY_TOKEN = 'notify'
- NOTIFY_END_TOKEN = 'notifyend'
-
- def __init__(self, ns, cmd, quit=False, **kwargs):
- super(DhtNetworkSubProcess, self).__init__(ns, cmd, **kwargs)
- self._setStdoutFlags()
- self._virtual_ns = ns
-
- self._quit = quit
- self._lock = threading.Condition()
- self._in_queue = queue.Queue()
- self._out_queue = queue.Queue()
-
- # starting thread
- self._thread = threading.Thread(target=self._communicate)
- self._thread.daemon = True
- self._thread.start()
-
- def __repr__(self):
- return 'DhtNetwork on virtual namespace "%s"' % self._virtual_ns
-
- def _setStdoutFlags(self):
- """
- Sets non-blocking read flags for subprocess stdout file descriptor.
- """
- import fcntl
- flags = self.stdout.fcntl(fcntl.F_GETFL)
- self.stdout.fcntl(fcntl.F_SETFL, flags | os.O_NDELAY)
-
- def _communicate(self):
- """
- Communication thread. This reads and writes to the sub process.
- """
- ENCODING = 'utf-8'
- sleep_time = 0.1
- stdin_line, stdout_line = '', ''
-
- # first read of process living. Expecting NOTIFY_TOKEN
- while DhtNetworkSubProcess.NOTIFY_TOKEN not in stdout_line:
- stdout_line = self.stdout.readline().decode()
- time.sleep(sleep_time)
-
- with self._lock:
- self._out_queue.put(stdout_line)
-
- while not self._quit:
- with self._lock:
- try:
- stdin_line = self._in_queue.get_nowait()
-
- # sending data to sub process
- self.stdin.write(stdin_line if isinstance(stdin_line, bytes) else
- bytes(str(stdin_line), encoding=ENCODING))
- self.stdin.flush()
- except queue.Empty:
- #waiting for next stdin req to send
- self._lock.wait(timeout=sleep_time)
-
- # reading response from sub process
- for stdout_line in iter(self.stdout.readline, b''):
- stdout_line = stdout_line.decode().replace('\n', '')
- if stdout_line:
- with self._lock:
- self._out_queue.put(stdout_line)
-
- with self._lock:
- self._lock.notify()
-
- def stop_communicating(self):
- """
- Stops the I/O thread from communicating with the subprocess.
- """
- if not self._quit:
- self._quit = True
- with self._lock:
- self._lock.notify()
- self._lock.wait()
-
- def quit(self):
- """
- Notifies thread and sub process to terminate. This is blocking call
- until the sub process finishes.
- """
- self.stop_communicating()
- self.send_signal(signal.SIGINT);
- self.wait()
- self.release()
-
- def send(self, msg):
- """
- Send data to sub process.
- """
- with self._lock:
- self._in_queue.put(msg)
- self._lock.notify()
-
- def getline(self):
- """
- Read line from sub process.
-
- @return: A line on sub process' stdout.
- @rtype : str
- """
- line = ''
- with self._lock:
- try:
- line = self._out_queue.get_nowait()
- except queue.Empty:
- pass
- return line
-
- def getlinesUntilNotify(self, answer_cb=None):
- """
- Reads the stdout queue until a proper notification is given by the sub
- process.
-
- @param answer_cb: Callback to call when an answer is given after notify.
- The function takes a list of lines as argument.
- @type answer_cb: function
- """
- notified = False
- answer = []
- while True:
- out = self.getline()
- if out.split(' ')[0] == DhtNetworkSubProcess.NOTIFY_TOKEN:
- notified = True
- elif notified and out.split(' ')[0] == DhtNetworkSubProcess.NOTIFY_END_TOKEN:
- if answer_cb:
- answer_cb(answer)
- break
- elif notified:
- answer.append(out)
- elif out:
- yield out
- else:
- time.sleep(0.1)
-
- def sendGetMessageStats(self):
- """
- Sends DhtNetwork sub process statistics request about nodes messages
- sent.
-
- @return: A list [num_nodes, ping, find, get, put, listen].
- @rtype : list
- """
- stats = []
- def cb(answer):
- """
- Callback fed to getlinesUntilNotify made to recover answer from the
- DhtNetwork sub process.
-
- :answer: the list of lines answered by the sub process.
- """
- nonlocal stats
- if answer:
- stats = [int(v) for v in re.findall("[0-9]+", answer.pop())]
-
- self.send(DhtNetworkSubProcess.MESSAGE_STATS + b'\n')
- for line in self.getlinesUntilNotify(answer_cb=cb):
- DhtNetwork.log(line)
-
- return stats
-
- def sendNodesRequest(self, request, ids):
- """
- Shutsdown nodes on the DhtNetwork sub process.
-
- @param request: The request
- @type request: bytes
- @param ids: ids of nodes concerned by the request.
- @type ids: list
- """
- serialized_req = request + b' ' + b' '.join(map(bytes, ids))
- self.send(serialized_req + b'\n')
- for line in self.getlinesUntilNotify():
- DhtNetwork.log(line)
-
- def sendShutdown(self):
- """
- Shutdown the whole cluster. This does not terminate comunicating thread;
- use quit().
- """
- self.send(DhtNetworkSubProcess.SHUTDOWN_CLUSTER_REQ + b'\n')
- for line in self.getlinesUntilNotify():
- DhtNetwork.log(line)
-
- def sendDumpStorage(self, ids):
- """
- Dumps storage log from nodes with id in `ids`.
- """
- serialized_req = DhtNetworkSubProcess.DUMP_STORAGE_REQ + b' ' + \
- b' '.join(map(bytes, ids))
- self.send(serialized_req + b'\n')
- for line in self.getlinesUntilNotify():
- DhtNetwork.log(line)
-
-
-def random_hash():
- return InfoHash(''.join(random.SystemRandom().choice(string.hexdigits) for _ in range(40)).encode())
-
-class FeatureTest(object):
- """
- This is base test. A method run() implementation is required.
- """
- #static variables used by class callbacks
- successfullTransfer = lambda lv,fv: len(lv) == len(fv)
- done = 0
- lock = None
- foreign_nodes = None
- foreign_values = None
-
- def __init__(self, test, workbench):
- self._test = test
- self.wb = workbench
- self.bootstrap = self.wb.get_bootstrap()
-
- @staticmethod
- def getcb(value):
- DhtNetwork.log('[GET]: %s' % value)
- FeatureTest.foreign_values.append(value)
- return True
-
- @staticmethod
- def putDoneCb(ok, nodes):
- if not ok:
- DhtNetwork.log("[PUT]: failed!")
- with FeatureTest.lock:
- FeatureTest.done -= 1
- FeatureTest.lock.notify()
-
- @staticmethod
- def getDoneCb(ok, nodes):
- with FeatureTest.lock:
- if not ok:
- DhtNetwork.log("[GET]: failed!")
- else:
- for node in nodes:
- if not node.getNode().isExpired():
- FeatureTest.foreign_nodes.append(node.getId().toString())
- FeatureTest.done -= 1
- FeatureTest.lock.notify()
-
- def _dhtPut(self, producer, _hash, *values):
- for val in values:
- with FeatureTest.lock:
- DhtNetwork.log('[PUT]: %s' % val)
- FeatureTest.done += 1
- producer.put(_hash, val, FeatureTest.putDoneCb)
- while FeatureTest.done > 0:
- FeatureTest.lock.wait()
-
- def _dhtGet(self, consumer, _hash):
- FeatureTest.foreign_values = []
- FeatureTest.foreign_nodes = []
- with FeatureTest.lock:
- FeatureTest.done += 1
- consumer.get(_hash, FeatureTest.getcb, FeatureTest.getDoneCb)
- while FeatureTest.done > 0:
- FeatureTest.lock.wait()
-
-
- def run(self):
- raise NotImplementedError('This method must be implemented.')
-
-class PersistenceTest(FeatureTest):
- """
- This tests persistence of data on the network.
- """
-
- def __init__(self, test, workbench, *opts):
- """
- @param test: is one of the following:
- - 'mult_time': test persistence of data based on internal
- OpenDHT storage maintenance timings.
- - 'delete': test persistence of data upon deletion of
- nodes.
- - 'replace': replacing cluster successively.
- @type test: string
-
-
- OPTIONS
-
- - dump_str_log: enables storage log at test ending.
- """
-
- # opts
- super(PersistenceTest, self).__init__(test, workbench)
- self._dump_storage = True if 'dump_str_log' in opts else False
- self._plot = True if 'plot' in opts else False
-
- def _result(self, local_values, new_nodes):
- bootstrap = self.bootstrap
- if not FeatureTest.successfullTransfer(local_values, FeatureTest.foreign_values):
- DhtNetwork.log('[GET]: Only %s on %s values persisted.' %
- (len(FeatureTest.foreign_values), len(local_values)))
- else:
- DhtNetwork.log('[GET]: All values successfully persisted.')
- if FeatureTest.foreign_values:
- if new_nodes:
- DhtNetwork.log('Values are newly found on:')
- for node in new_nodes:
- DhtNetwork.log(node)
- if self._dump_storage:
- DhtNetwork.log('Dumping all storage log from '\
- 'hosting nodes.')
-
- for proc in self.wb.procs:
- proc.sendDumpStorage(FeatureTest.foreign_nodes)
- else:
- DhtNetwork.log("Values didn't reach new hosting nodes after shutdown.")
-
- def run(self):
- try:
- if self._test == 'delete':
- self._deleteTest()
- elif self._test == 'replace':
- self._resplaceClusterTest()
- elif self._test == 'mult_time':
- self._multTimeTest()
- except Exception as e:
- print(e)
- finally:
- bootstrap.resize(1)
-
- #-----------
- #- Tests -
- #-----------
-
- def _deleteTest(self):
- """
- It uses Dht shutdown call from the API to gracefuly finish the nodes one
- after the other.
- """
- FeatureTest.done = 0
- FeatureTest.lock = threading.Condition()
- FeatureTest.foreign_nodes = []
- FeatureTest.foreign_values = []
-
- bootstrap = self.bootstrap
-
- ops_count = []
-
- bootstrap.resize(3)
- consumer = bootstrap.get(1)
- producer = bootstrap.get(2)
-
- myhash = random_hash()
- local_values = [Value(b'foo'), Value(b'bar'), Value(b'foobar')]
-
- self._dhtPut(producer, myhash, *local_values)
-
- #checking if values were transfered
- self._dhtGet(consumer, myhash)
- if not FeatureTest.successfullTransfer(local_values, FeatureTest.foreign_values):
- if FeatureTest.foreign_values:
- DhtNetwork.log('[GET]: Only ', len(FeatureTest.foreign_values) ,' on ',
- len(local_values), ' values successfully put.')
- else:
- DhtNetwork.log('[GET]: 0 values successfully put')
-
-
- if FeatureTest.foreign_values and FeatureTest.foreign_nodes:
- DhtNetwork.log('Values are found on :')
- for node in FeatureTest.foreign_nodes:
- DhtNetwork.log(node)
-
- DhtNetwork.log("Waiting a minute for the network to settle down.")
- time.sleep(60)
-
- for _ in range(max(1, int(self.wb.node_num/32))):
- DhtNetwork.log('Removing all nodes hosting target values...')
- cluster_ops_count = 0
- for proc in self.wb.procs:
- DhtNetwork.log('[REMOVE]: sending shutdown request to', proc)
- proc.sendNodesRequest(
- DhtNetworkSubProcess.SHUTDOWN_NODE_REQ,
- FeatureTest.foreign_nodes
- )
- DhtNetwork.log('sending message stats request')
- stats = proc.sendGetMessageStats()
- cluster_ops_count += sum(stats[1:])
- #DhtNetwork.log("Waiting 15 seconds for packets to work their way effectively.")
- #time.sleep(15)
- ops_count.append(cluster_ops_count/self.wb.node_num)
-
- # checking if values were transfered to new nodes
- foreign_nodes_before_delete = FeatureTest.foreign_nodes
- DhtNetwork.log('[GET]: trying to fetch persistent values')
- self._dhtGet(consumer, myhash)
- new_nodes = set(FeatureTest.foreign_nodes) - set(foreign_nodes_before_delete)
-
- self._result(local_values, new_nodes)
-
- if self._plot:
- plt.plot(ops_count, color='blue')
- plt.draw()
- plt.ioff()
- plt.show()
- else:
- DhtNetwork.log("[GET]: either couldn't fetch values or nodes hosting values...")
-
- def _resplaceClusterTest(self):
- """
- It replaces all clusters one after the other.
- """
- FeatureTest.done = 0
- FeatureTest.lock = threading.Condition()
- FeatureTest.foreign_nodes = []
- FeatureTest.foreign_values = []
-
- clusters = opts['clusters'] if 'clusters' in opts else 5
-
- bootstrap = self.bootstrap
-
- bootstrap.resize(3)
- consumer = bootstrap.get(1)
- producer = bootstrap.get(2)
-
- myhash = random_hash()
- local_values = [Value(b'foo'), Value(b'bar'), Value(b'foobar')]
-
- self._dhtPut(producer, myhash, *local_values)
- self._dhtGet(consumer, myhash)
- initial_nodes = FeatureTest.foreign_nodes
-
- DhtNetwork.log('Replacing', clusters, 'random clusters successively...')
- for n in range(clusters):
- i = random.randint(0, len(self.wb.procs)-1)
- proc = self.wb.procs[i]
- DhtNetwork.log('Replacing', proc)
- proc.sendShutdown()
- self.wb.stop_cluster(i)
- self.wb.start_cluster(i)
-
- DhtNetwork.log('[GET]: trying to fetch persistent values')
- self._dhtGet(consumer, myhash)
- new_nodes = set(FeatureTest.foreign_nodes) - set(initial_nodes)
-
- self._result(local_values, new_nodes)
-
- def _multTimeTest(self):
- """
- Multiple put() calls are made from multiple nodes to multiple hashes
- after what a set of 8 nodes is created around each hashes in order to
- enable storage maintenance each nodes. Therefor, this tests will wait 10
- minutes for the nodes to trigger storage maintenance.
- """
- FeatureTest.done = 0
- FeatureTest.lock = threading.Condition()
- FeatureTest.foreign_nodes = []
- FeatureTest.foreign_values = []
- bootstrap = self.bootstrap
-
- N_PRODUCERS = 16
-
- hashes = []
- values = [Value(b'foo')]
- nodes = set([])
-
- # prevents garbage collecting of unused flood nodes during the test.
- flood_nodes = []
-
- def gottaGetThemAllPokeNodes(nodes=None):
- nonlocal consumer, hashes
- for h in hashes:
- self._dhtGet(consumer, h)
- if nodes is not None:
- for n in FeatureTest.foreign_nodes:
- nodes.add(n)
-
- def createNodesAroundHash(_hash, radius=4):
- nonlocal flood_nodes
-
- _hash_str = _hash.toString().decode()
- _hash_int = int(_hash_str, 16)
- for i in range(-radius, radius+1):
- _hash_str = '{:40x}'.format(_hash_int + i)
- config = DhtConfig()
- config.setNodeId(InfoHash(_hash_str.encode()))
- n = DhtRunner()
- n.run(config=config)
- n.bootstrap(self.bootstrap.ip4,
- str(self.bootstrap.port))
- flood_nodes.append(n)
-
- bootstrap.resize(N_PRODUCERS+2)
- consumer = bootstrap.get(1)
- producers = (bootstrap.get(n) for n in range(2,N_PRODUCERS+2))
- for p in producers:
- hashes.append(random_hash())
- self._dhtPut(p, hashes[-1], *values)
-
- gottaGetThemAllPokeNodes(nodes=nodes)
-
- DhtNetwork.log("Values are found on:")
- for n in nodes:
- DhtNetwork.log(n)
-
- DhtNetwork.log("Creating 8 nodes around all of these nodes...")
- for _hash in hashes:
- createNodesAroundHash(_hash)
-
- DhtNetwork.log('Waiting 10 minutes for normal storage maintenance.')
- time.sleep(10*60)
-
- DhtNetwork.log('Deleting old nodes from previous search.')
- for proc in self.wb.procs:
- DhtNetwork.log('[REMOVE]: sending shutdown request to', proc)
- proc.sendNodesRequest(
- DhtNetworkSubProcess.REMOVE_NODE_REQ,
- nodes
- )
-
- # new consumer (fresh cache)
- bootstrap.resize(N_PRODUCERS+3)
- consumer = bootstrap.get(N_PRODUCERS+2)
-
- nodes_after_time = set([])
- gottaGetThemAllPokeNodes(nodes=nodes_after_time)
- self._result(values, nodes_after_time - nodes)
-
-class PerformanceTest(FeatureTest):
- """
- Tests for general performance of dht operations.
- """
-
- def __init__(self, test, workbench, *opts):
- """
- @param test: is one of the following:
- - 'gets': multiple get operations and statistical results.
- - 'delete': perform multiple put() operations followed
- by targeted deletion of nodes hosting the values. Doing
- so until half of the nodes on the network remain.
- @type test: string
- """
- super(PerformanceTest, self).__init__(test, workbench)
-
- def run(self):
- try:
- if self._test == 'gets':
- self._getsTimesTest()
- elif self._test == 'delete':
- self._delete()
- except Exception as e:
- print(e)
- finally:
- self.bootstrap.resize(1)
-
-
- ###########
- # Tests #
- ###########
-
- def _getsTimesTest(self):
- """
- Tests for performance of the DHT doing multiple get() operation.
- """
- bootstrap = self.bootstrap
-
- plt.ion()
-
- fig, axes = plt.subplots(2, 1)
- fig.tight_layout()
-
- lax = axes[0]
- hax = axes[1]
-
- lines = None#ax.plot([])
- #plt.ylabel('time (s)')
- hax.set_ylim(0, 2)
-
- # let the network stabilise
- plt.pause(60)
-
- #start = time.time()
- times = []
-
- lock = threading.Condition()
- done = 0
-
- def getcb(v):
- nonlocal bootstrap
- DhtNetwork.log("found", v)
- return True
-
- def donecb(ok, nodes):
- nonlocal bootstrap, lock, done, times
- t = time.time()-start
- with lock:
- if not ok:
- DhtNetwork.log("failed !")
- times.append(t)
- done -= 1
- lock.notify()
-
- def update_plot():
- nonlocal lines
- while lines:
- l = lines.pop()
- l.remove()
- del l
- lines = plt.plot(times, color='blue')
- plt.draw()
-
- def run_get():
- nonlocal done
- done += 1
- start = time.time()
- bootstrap.front().get(InfoHash.getRandom(), getcb, lambda ok, nodes: donecb(ok, nodes, start))
-
- plt.pause(5)
-
- plt.show()
- update_plot()
-
- times = []
- for n in range(10):
- self.wb.replace_cluster()
- plt.pause(2)
- DhtNetwork.log("Getting 50 random hashes succesively.")
- for i in range(50):
- with lock:
- done += 1
- start = time.time()
- bootstrap.front().get(InfoHash.getRandom(), getcb, donecb)
- while done > 0:
- lock.wait()
- update_plot()
- update_plot()
- print("Took", np.sum(times), "mean", np.mean(times), "std", np.std(times), "min", np.min(times), "max", np.max(times))
-
- print('GET calls timings benchmark test : DONE. ' \
- 'Close Matplotlib window for terminating the program.')
- plt.ioff()
- plt.show()
-
- def _delete(self):
- """
- Tests for performance of get() and put() operations on the network while
- deleting around the target hash.
- """
-
- FeatureTest.done = 0
- FeatureTest.lock = threading.Condition()
- FeatureTest.foreign_nodes = []
- FeatureTest.foreign_values = []
-
- bootstrap = self.bootstrap
-
- bootstrap.resize(3)
- consumer = bootstrap.get(1)
- producer = bootstrap.get(2)
-
- myhash = random_hash()
- local_values = [Value(b'foo'), Value(b'bar'), Value(b'foobar')]
-
- for _ in range(max(1, int(self.wb.node_num/32))):
- self._dhtGet(consumer, myhash)
- DhtNetwork.log("Waiting 15 seconds...")
- time.sleep(15)
-
- self._dhtPut(producer, myhash, *local_values)
-
- #checking if values were transfered
- self._dhtGet(consumer, myhash)
- DhtNetwork.log('Values are found on :')
- for node in FeatureTest.foreign_nodes:
- DhtNetwork.log(node)
-
- if not FeatureTest.successfullTransfer(local_values, FeatureTest.foreign_values):
- if FeatureTest.foreign_values:
- DhtNetwork.log('[GET]: Only ', len(FeatureTest.foreign_values) ,' on ',
- len(local_values), ' values successfully put.')
- else:
- DhtNetwork.log('[GET]: 0 values successfully put')
-
- DhtNetwork.log('Removing all nodes hosting target values...')
- for proc in self.wb.procs:
- DhtNetwork.log('[REMOVE]: sending shutdown request to', proc)
- proc.sendNodesRequest(
- DhtNetworkSubProcess.SHUTDOWN_NODE_REQ,
- FeatureTest.foreign_nodes
- )
-
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Run, test and benchmark a '\
diff --git a/python/tools/dht/__init__.py b/python/tools/dht/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/python/tools/dhtnetwork.py b/python/tools/dht/network.py
similarity index 58%
rename from python/tools/dhtnetwork.py
rename to python/tools/dht/network.py
index c3dda601..46dafab2 100755
--- a/python/tools/dhtnetwork.py
+++ b/python/tools/dht/network.py
@@ -16,20 +16,239 @@
# You should have received a copy of the GNU General Public License
# along with this program; If not, see .
+import os
import sys
import signal
import random
import time
import threading
import queue
+import re
import ipaddress
import netifaces
-
import numpy as np
+from pyroute2.netns.process.proxy import NSPopen
from opendht import *
+
+# TODO: find where token "notifyend" gets printed...
+class DhtNetworkSubProcess(NSPopen):
+ """
+ Handles communication with DhtNetwork sub process.
+
+ When instanciated, the object's thread is started and will read the sub
+ process' stdout until it finds 'DhtNetworkSubProcess.NOTIFY_TOKEN' token,
+ therefor, waits for the sub process to spawn.
+ """
+ # requests
+ REMOVE_NODE_REQ = b"rn"
+ SHUTDOWN_NODE_REQ = b"sdn"
+ SHUTDOWN_REPLACE_NODE_REQ = b'sdrn'
+ SHUTDOWN_CLUSTER_REQ = b"sdc"
+ DUMP_STORAGE_REQ = b"strl"
+ MESSAGE_STATS = b"gms"
+
+
+ # tokens
+ NOTIFY_TOKEN = 'notify'
+ NOTIFY_END_TOKEN = 'notifyend'
+
+ def __init__(self, ns, cmd, quit=False, **kwargs):
+ super(DhtNetworkSubProcess, self).__init__(ns, cmd, **kwargs)
+ self._setStdoutFlags()
+ self._virtual_ns = ns
+
+ self._quit = quit
+ self._lock = threading.Condition()
+ self._in_queue = queue.Queue()
+ self._out_queue = queue.Queue()
+
+ # starting thread
+ self._thread = threading.Thread(target=self._communicate)
+ self._thread.daemon = True
+ self._thread.start()
+
+ def __repr__(self):
+ return 'DhtNetwork on virtual namespace "%s"' % self._virtual_ns
+
+ def _setStdoutFlags(self):
+ """
+ Sets non-blocking read flags for subprocess stdout file descriptor.
+ """
+ import fcntl
+ flags = self.stdout.fcntl(fcntl.F_GETFL)
+ self.stdout.fcntl(fcntl.F_SETFL, flags | os.O_NDELAY)
+
+ def _communicate(self):
+ """
+ Communication thread. This reads and writes to the sub process.
+ """
+ ENCODING = 'utf-8'
+ sleep_time = 0.1
+ stdin_line, stdout_line = '', ''
+
+ # first read of process living. Expecting NOTIFY_TOKEN
+ while DhtNetworkSubProcess.NOTIFY_TOKEN not in stdout_line:
+ stdout_line = self.stdout.readline().decode()
+ time.sleep(sleep_time)
+
+ with self._lock:
+ self._out_queue.put(stdout_line)
+
+ while not self._quit:
+ with self._lock:
+ try:
+ stdin_line = self._in_queue.get_nowait()
+
+ # sending data to sub process
+ self.stdin.write(stdin_line if isinstance(stdin_line, bytes) else
+ bytes(str(stdin_line), encoding=ENCODING))
+ self.stdin.flush()
+ except queue.Empty:
+ #waiting for next stdin req to send
+ self._lock.wait(timeout=sleep_time)
+
+ # reading response from sub process
+ for stdout_line in iter(self.stdout.readline, b''):
+ stdout_line = stdout_line.decode().replace('\n', '')
+ if stdout_line:
+ with self._lock:
+ self._out_queue.put(stdout_line)
+
+ with self._lock:
+ self._lock.notify()
+
+ def stop_communicating(self):
+ """
+ Stops the I/O thread from communicating with the subprocess.
+ """
+ if not self._quit:
+ self._quit = True
+ with self._lock:
+ self._lock.notify()
+ self._lock.wait()
+
+ def quit(self):
+ """
+ Notifies thread and sub process to terminate. This is blocking call
+ until the sub process finishes.
+ """
+ self.stop_communicating()
+ self.send_signal(signal.SIGINT);
+ self.wait()
+ self.release()
+
+ def send(self, msg):
+ """
+ Send data to sub process.
+ """
+ with self._lock:
+ self._in_queue.put(msg)
+ self._lock.notify()
+
+ def getline(self):
+ """
+ Read line from sub process.
+
+ @return: A line on sub process' stdout.
+ @rtype : str
+ """
+ line = ''
+ with self._lock:
+ try:
+ line = self._out_queue.get_nowait()
+ except queue.Empty:
+ pass
+ return line
+
+ def getlinesUntilNotify(self, answer_cb=None):
+ """
+ Reads the stdout queue until a proper notification is given by the sub
+ process.
+
+ @param answer_cb: Callback to call when an answer is given after notify.
+ The function takes a list of lines as argument.
+ @type answer_cb: function
+ """
+ notified = False
+ answer = []
+ while True:
+ out = self.getline()
+ if out.split(' ')[0] == DhtNetworkSubProcess.NOTIFY_TOKEN:
+ notified = True
+ elif notified and out.split(' ')[0] == DhtNetworkSubProcess.NOTIFY_END_TOKEN:
+ if answer_cb:
+ answer_cb(answer)
+ break
+ elif notified:
+ answer.append(out)
+ elif out:
+ yield out
+ else:
+ time.sleep(0.1)
+
+ def sendGetMessageStats(self):
+ """
+ Sends DhtNetwork sub process statistics request about nodes messages
+ sent.
+
+ @return: A list [num_nodes, ping, find, get, put, listen].
+ @rtype : list
+ """
+ stats = []
+ def cb(answer):
+ """
+ Callback fed to getlinesUntilNotify made to recover answer from the
+ DhtNetwork sub process.
+
+ :answer: the list of lines answered by the sub process.
+ """
+ nonlocal stats
+ if answer:
+ stats = [int(v) for v in re.findall("[0-9]+", answer.pop())]
+
+ self.send(DhtNetworkSubProcess.MESSAGE_STATS + b'\n')
+ for line in self.getlinesUntilNotify(answer_cb=cb):
+ DhtNetwork.log(line)
+
+ return stats
+
+ def sendNodesRequest(self, request, ids):
+ """
+ Shutsdown nodes on the DhtNetwork sub process.
+
+ @param request: The request
+ @type request: bytes
+ @param ids: ids of nodes concerned by the request.
+ @type ids: list
+ """
+ serialized_req = request + b' ' + b' '.join(map(bytes, ids))
+ self.send(serialized_req + b'\n')
+ for line in self.getlinesUntilNotify():
+ DhtNetwork.log(line)
+
+ def sendShutdown(self):
+ """
+ Shutdown the whole cluster. This does not terminate comunicating thread;
+ use quit().
+ """
+ self.send(DhtNetworkSubProcess.SHUTDOWN_CLUSTER_REQ + b'\n')
+ for line in self.getlinesUntilNotify():
+ DhtNetwork.log(line)
+
+ def sendDumpStorage(self, ids):
+ """
+ Dumps storage log from nodes with id in `ids`.
+ """
+ serialized_req = DhtNetworkSubProcess.DUMP_STORAGE_REQ + b' ' + \
+ b' '.join(map(bytes, ids))
+ self.send(serialized_req + b'\n')
+ for line in self.getlinesUntilNotify():
+ DhtNetwork.log(line)
+
+
class DhtNetwork(object):
nodes = []
@@ -94,10 +313,10 @@ class DhtNetwork(object):
def end_node(self, id=None, shutdown=False, last_msg_stats=None):
"""
Ends a running node.
-
+
@param id: The 40 hex chars id of the node.
@type id: bytes
-
+
@return: If a node was deleted or not.
@rtype : boolean
"""
@@ -159,6 +378,7 @@ class DhtNetwork(object):
stats_list.extend(stats.tolist())
return stats_list
+
if __name__ == '__main__':
import argparse
diff --git a/python/tools/dht/tests.py b/python/tools/dht/tests.py
new file mode 100644
index 00000000..b94c3794
--- /dev/null
+++ b/python/tools/dht/tests.py
@@ -0,0 +1,505 @@
+# Copyright (C) 2015 Savoir-Faire Linux Inc.
+# Author(s): Adrien Béraud
+# Simon Désaulniers
+
+import threading
+import random
+import string
+import time
+
+from opendht import *
+from dht.network import DhtNetwork, DhtNetworkSubProcess
+
+
+def random_hash():
+ return InfoHash(''.join(random.SystemRandom().choice(string.hexdigits) for _ in range(40)).encode())
+
+
+class FeatureTest(object):
+ """
+ This is base test. A method run() implementation is required.
+ """
+ #static variables used by class callbacks
+ successfullTransfer = lambda lv,fv: len(lv) == len(fv)
+ done = 0
+ lock = None
+ foreign_nodes = None
+ foreign_values = None
+
+ def __init__(self, test, workbench):
+ self._test = test
+ self.wb = workbench
+ self.bootstrap = self.wb.get_bootstrap()
+
+ @staticmethod
+ def getcb(value):
+ DhtNetwork.log('[GET]: %s' % value)
+ FeatureTest.foreign_values.append(value)
+ return True
+
+ @staticmethod
+ def putDoneCb(ok, nodes):
+ if not ok:
+ DhtNetwork.log("[PUT]: failed!")
+ with FeatureTest.lock:
+ FeatureTest.done -= 1
+ FeatureTest.lock.notify()
+
+ @staticmethod
+ def getDoneCb(ok, nodes):
+ with FeatureTest.lock:
+ if not ok:
+ DhtNetwork.log("[GET]: failed!")
+ else:
+ for node in nodes:
+ if not node.getNode().isExpired():
+ FeatureTest.foreign_nodes.append(node.getId().toString())
+ FeatureTest.done -= 1
+ FeatureTest.lock.notify()
+
+ def _dhtPut(self, producer, _hash, *values):
+ for val in values:
+ with FeatureTest.lock:
+ DhtNetwork.log('[PUT]: %s' % val)
+ FeatureTest.done += 1
+ producer.put(_hash, val, FeatureTest.putDoneCb)
+ while FeatureTest.done > 0:
+ FeatureTest.lock.wait()
+
+ def _dhtGet(self, consumer, _hash):
+ FeatureTest.foreign_values = []
+ FeatureTest.foreign_nodes = []
+ with FeatureTest.lock:
+ FeatureTest.done += 1
+ consumer.get(_hash, FeatureTest.getcb, FeatureTest.getDoneCb)
+ while FeatureTest.done > 0:
+ FeatureTest.lock.wait()
+
+
+ def run(self):
+ raise NotImplementedError('This method must be implemented.')
+
+
+class PersistenceTest(FeatureTest):
+ """
+ This tests persistence of data on the network.
+ """
+
+ def __init__(self, test, workbench, *opts):
+ """
+ @param test: is one of the following:
+ - 'mult_time': test persistence of data based on internal
+ OpenDHT storage maintenance timings.
+ - 'delete': test persistence of data upon deletion of
+ nodes.
+ - 'replace': replacing cluster successively.
+ @type test: string
+
+
+ OPTIONS
+
+ - dump_str_log: enables storage log at test ending.
+ """
+
+ # opts
+ super(PersistenceTest, self).__init__(test, workbench)
+ self._dump_storage = True if 'dump_str_log' in opts else False
+ self._plot = True if 'plot' in opts else False
+
+ def _result(self, local_values, new_nodes):
+ bootstrap = self.bootstrap
+ if not FeatureTest.successfullTransfer(local_values, FeatureTest.foreign_values):
+ DhtNetwork.log('[GET]: Only %s on %s values persisted.' %
+ (len(FeatureTest.foreign_values), len(local_values)))
+ else:
+ DhtNetwork.log('[GET]: All values successfully persisted.')
+ if FeatureTest.foreign_values:
+ if new_nodes:
+ DhtNetwork.log('Values are newly found on:')
+ for node in new_nodes:
+ DhtNetwork.log(node)
+ if self._dump_storage:
+ DhtNetwork.log('Dumping all storage log from '\
+ 'hosting nodes.')
+
+ for proc in self.wb.procs:
+ proc.sendDumpStorage(FeatureTest.foreign_nodes)
+ else:
+ DhtNetwork.log("Values didn't reach new hosting nodes after shutdown.")
+
+ def run(self):
+ try:
+ if self._test == 'delete':
+ self._deleteTest()
+ elif self._test == 'replace':
+ self._replaceClusterTest()
+ elif self._test == 'mult_time':
+ self._multTimeTest()
+ except Exception as e:
+ print(e)
+ finally:
+ self.bootstrap.resize(1)
+
+ #-----------
+ #- Tests -
+ #-----------
+
+ def _deleteTest(self):
+ """
+ It uses Dht shutdown call from the API to gracefuly finish the nodes one
+ after the other.
+ """
+ FeatureTest.done = 0
+ FeatureTest.lock = threading.Condition()
+ FeatureTest.foreign_nodes = []
+ FeatureTest.foreign_values = []
+
+ bootstrap = self.bootstrap
+
+ ops_count = []
+
+ bootstrap.resize(3)
+ consumer = bootstrap.get(1)
+ producer = bootstrap.get(2)
+
+ myhash = random_hash()
+ local_values = [Value(b'foo'), Value(b'bar'), Value(b'foobar')]
+
+ self._dhtPut(producer, myhash, *local_values)
+
+ #checking if values were transfered
+ self._dhtGet(consumer, myhash)
+ if not FeatureTest.successfullTransfer(local_values, FeatureTest.foreign_values):
+ if FeatureTest.foreign_values:
+ DhtNetwork.log('[GET]: Only ', len(FeatureTest.foreign_values) ,' on ',
+ len(local_values), ' values successfully put.')
+ else:
+ DhtNetwork.log('[GET]: 0 values successfully put')
+
+
+ if FeatureTest.foreign_values and FeatureTest.foreign_nodes:
+ DhtNetwork.log('Values are found on :')
+ for node in FeatureTest.foreign_nodes:
+ DhtNetwork.log(node)
+
+ #DhtNetwork.log("Waiting a minute for the network to settle down.")
+ #time.sleep(60)
+
+ for _ in range(max(1, int(self.wb.node_num/32))):
+ DhtNetwork.log('Removing all nodes hosting target values...')
+ cluster_ops_count = 0
+ for proc in self.wb.procs:
+ DhtNetwork.log('[REMOVE]: sending shutdown request to', proc)
+ proc.sendNodesRequest(
+ DhtNetworkSubProcess.SHUTDOWN_NODE_REQ,
+ FeatureTest.foreign_nodes
+ )
+ DhtNetwork.log('sending message stats request')
+ stats = proc.sendGetMessageStats()
+ cluster_ops_count += sum(stats[1:])
+ DhtNetwork.log("3 seconds wait...")
+ time.sleep(3)
+ ops_count.append(cluster_ops_count/self.wb.node_num)
+
+ # checking if values were transfered to new nodes
+ foreign_nodes_before_delete = FeatureTest.foreign_nodes
+ DhtNetwork.log('[GET]: trying to fetch persistent values')
+ self._dhtGet(consumer, myhash)
+ new_nodes = set(FeatureTest.foreign_nodes) - set(foreign_nodes_before_delete)
+
+ self._result(local_values, new_nodes)
+
+ if self._plot:
+ plt.plot(ops_count, color='blue')
+ plt.draw()
+ plt.ioff()
+ plt.show()
+ else:
+ DhtNetwork.log("[GET]: either couldn't fetch values or nodes hosting values...")
+
+ #TODO: complete this test.
+ def _replaceClusterTest(self):
+ """
+ It replaces all clusters one after the other.
+ """
+ FeatureTest.done = 0
+ FeatureTest.lock = threading.Condition()
+ FeatureTest.foreign_nodes = []
+ FeatureTest.foreign_values = []
+
+ #clusters = opts['clusters'] if 'clusters' in opts else 5
+ clusters = 5
+
+ bootstrap = self.bootstrap
+
+ bootstrap.resize(3)
+ consumer = bootstrap.get(1)
+ producer = bootstrap.get(2)
+
+ myhash = random_hash()
+ local_values = [Value(b'foo'), Value(b'bar'), Value(b'foobar')]
+
+ self._dhtPut(producer, myhash, *local_values)
+ self._dhtGet(consumer, myhash)
+ initial_nodes = FeatureTest.foreign_nodes
+
+ DhtNetwork.log('Replacing', clusters, 'random clusters successively...')
+ for n in range(clusters):
+ i = random.randint(0, len(self.wb.procs)-1)
+ proc = self.wb.procs[i]
+ DhtNetwork.log('Replacing', proc)
+ proc.sendShutdown()
+ self.wb.stop_cluster(i)
+ self.wb.start_cluster(i)
+
+ DhtNetwork.log('[GET]: trying to fetch persistent values')
+ self._dhtGet(consumer, myhash)
+ new_nodes = set(FeatureTest.foreign_nodes) - set(initial_nodes)
+
+ self._result(local_values, new_nodes)
+
+ #TODO: complete this test.
+ def _multTimeTest(self):
+ """
+ Multiple put() calls are made from multiple nodes to multiple hashes
+ after what a set of 8 nodes is created around each hashes in order to
+ enable storage maintenance each nodes. Therefor, this tests will wait 10
+ minutes for the nodes to trigger storage maintenance.
+ """
+ FeatureTest.done = 0
+ FeatureTest.lock = threading.Condition()
+ FeatureTest.foreign_nodes = []
+ FeatureTest.foreign_values = []
+ bootstrap = self.bootstrap
+
+ N_PRODUCERS = 16
+
+ hashes = []
+ values = [Value(b'foo')]
+ nodes = set([])
+
+ # prevents garbage collecting of unused flood nodes during the test.
+ flood_nodes = []
+
+ def gottaGetThemAllPokeNodes(nodes=None):
+ nonlocal consumer, hashes
+ for h in hashes:
+ self._dhtGet(consumer, h)
+ if nodes is not None:
+ for n in FeatureTest.foreign_nodes:
+ nodes.add(n)
+
+ def createNodesAroundHash(_hash, radius=4):
+ nonlocal flood_nodes
+
+ _hash_str = _hash.toString().decode()
+ _hash_int = int(_hash_str, 16)
+ for i in range(-radius, radius+1):
+ _hash_str = '{:40x}'.format(_hash_int + i)
+ config = DhtConfig()
+ config.setNodeId(InfoHash(_hash_str.encode()))
+ n = DhtRunner()
+ n.run(config=config)
+ n.bootstrap(self.bootstrap.ip4,
+ str(self.bootstrap.port))
+ flood_nodes.append(n)
+
+ bootstrap.resize(N_PRODUCERS+2)
+ consumer = bootstrap.get(1)
+ producers = (bootstrap.get(n) for n in range(2,N_PRODUCERS+2))
+ for p in producers:
+ hashes.append(random_hash())
+ self._dhtPut(p, hashes[-1], *values)
+
+ gottaGetThemAllPokeNodes(nodes=nodes)
+
+ DhtNetwork.log("Values are found on:")
+ for n in nodes:
+ DhtNetwork.log(n)
+
+ DhtNetwork.log("Creating 8 nodes around all of these nodes...")
+ for _hash in hashes:
+ createNodesAroundHash(_hash)
+
+ DhtNetwork.log('Waiting 10 minutes for normal storage maintenance.')
+ time.sleep(10*60)
+
+ DhtNetwork.log('Deleting old nodes from previous search.')
+ for proc in self.wb.procs:
+ DhtNetwork.log('[REMOVE]: sending shutdown request to', proc)
+ proc.sendNodesRequest(
+ DhtNetworkSubProcess.REMOVE_NODE_REQ,
+ nodes
+ )
+
+ # new consumer (fresh cache)
+ bootstrap.resize(N_PRODUCERS+3)
+ consumer = bootstrap.get(N_PRODUCERS+2)
+
+ nodes_after_time = set([])
+ gottaGetThemAllPokeNodes(nodes=nodes_after_time)
+ self._result(values, nodes_after_time - nodes)
+
+
+class PerformanceTest(FeatureTest):
+ """
+ Tests for general performance of dht operations.
+ """
+
+ def __init__(self, test, workbench, *opts):
+ """
+ @param test: is one of the following:
+ - 'gets': multiple get operations and statistical results.
+ - 'delete': perform multiple put() operations followed
+ by targeted deletion of nodes hosting the values. Doing
+ so until half of the nodes on the network remain.
+ @type test: string
+ """
+ super(PerformanceTest, self).__init__(test, workbench)
+
+ def run(self):
+ try:
+ if self._test == 'gets':
+ self._getsTimesTest()
+ elif self._test == 'delete':
+ self._delete()
+ except Exception as e:
+ print(e)
+ finally:
+ self.bootstrap.resize(1)
+
+
+ ###########
+ # Tests #
+ ###########
+
+ def _getsTimesTest(self):
+ """
+ Tests for performance of the DHT doing multiple get() operation.
+ """
+ bootstrap = self.bootstrap
+
+ plt.ion()
+
+ fig, axes = plt.subplots(2, 1)
+ fig.tight_layout()
+
+ lax = axes[0]
+ hax = axes[1]
+
+ lines = None#ax.plot([])
+ #plt.ylabel('time (s)')
+ hax.set_ylim(0, 2)
+
+ # let the network stabilise
+ plt.pause(60)
+
+ #start = time.time()
+ times = []
+
+ lock = threading.Condition()
+ done = 0
+
+ def getcb(v):
+ nonlocal bootstrap
+ DhtNetwork.log("found", v)
+ return True
+
+ def donecb(ok, nodes):
+ nonlocal bootstrap, lock, done, times
+ t = time.time()-start
+ with lock:
+ if not ok:
+ DhtNetwork.log("failed !")
+ times.append(t)
+ done -= 1
+ lock.notify()
+
+ def update_plot():
+ nonlocal lines
+ while lines:
+ l = lines.pop()
+ l.remove()
+ del l
+ lines = plt.plot(times, color='blue')
+ plt.draw()
+
+ def run_get():
+ nonlocal done
+ done += 1
+ start = time.time()
+ bootstrap.front().get(InfoHash.getRandom(), getcb, lambda ok, nodes: donecb(ok, nodes, start))
+
+ plt.pause(5)
+
+ plt.show()
+ update_plot()
+
+ times = []
+ for n in range(10):
+ self.wb.replace_cluster()
+ plt.pause(2)
+ DhtNetwork.log("Getting 50 random hashes succesively.")
+ for i in range(50):
+ with lock:
+ done += 1
+ start = time.time()
+ bootstrap.front().get(InfoHash.getRandom(), getcb, donecb)
+ while done > 0:
+ lock.wait()
+ update_plot()
+ update_plot()
+ print("Took", np.sum(times), "mean", np.mean(times), "std", np.std(times), "min", np.min(times), "max", np.max(times))
+
+ print('GET calls timings benchmark test : DONE. ' \
+ 'Close Matplotlib window for terminating the program.')
+ plt.ioff()
+ plt.show()
+
+ def _delete(self):
+ """
+ Tests for performance of get() and put() operations on the network while
+ deleting around the target hash.
+ """
+
+ FeatureTest.done = 0
+ FeatureTest.lock = threading.Condition()
+ FeatureTest.foreign_nodes = []
+ FeatureTest.foreign_values = []
+
+ bootstrap = self.bootstrap
+
+ bootstrap.resize(3)
+ consumer = bootstrap.get(1)
+ producer = bootstrap.get(2)
+
+ myhash = random_hash()
+ local_values = [Value(b'foo'), Value(b'bar'), Value(b'foobar')]
+
+ for _ in range(max(1, int(self.wb.node_num/32))):
+ self._dhtGet(consumer, myhash)
+ DhtNetwork.log("Waiting 15 seconds...")
+ time.sleep(15)
+
+ self._dhtPut(producer, myhash, *local_values)
+
+ #checking if values were transfered
+ self._dhtGet(consumer, myhash)
+ DhtNetwork.log('Values are found on :')
+ for node in FeatureTest.foreign_nodes:
+ DhtNetwork.log(node)
+
+ if not FeatureTest.successfullTransfer(local_values, FeatureTest.foreign_values):
+ if FeatureTest.foreign_values:
+ DhtNetwork.log('[GET]: Only ', len(FeatureTest.foreign_values) ,' on ',
+ len(local_values), ' values successfully put.')
+ else:
+ DhtNetwork.log('[GET]: 0 values successfully put')
+
+ DhtNetwork.log('Removing all nodes hosting target values...')
+ for proc in self.wb.procs:
+ DhtNetwork.log('[REMOVE]: sending shutdown request to', proc)
+ proc.sendNodesRequest(
+ DhtNetworkSubProcess.SHUTDOWN_NODE_REQ,
+ FeatureTest.foreign_nodes
+ )
diff --git a/python/tools/dht/virtual_network_builder.py b/python/tools/dht/virtual_network_builder.py
new file mode 100755
index 00000000..a48fd3eb
--- /dev/null
+++ b/python/tools/dht/virtual_network_builder.py
@@ -0,0 +1,120 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015-2016 Savoir-faire Linux Inc.
+# Author: Adrien Béraud
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; If not, see .
+
+import argparse, subprocess
+
+from pyroute2 import IPDB, NetNS
+from pyroute2.netns.process.proxy import NSPopen
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description='Creates a virtual network topology for testing')
+ parser.add_argument('-i', '--ifname', help='interface name', default='ethdht')
+ parser.add_argument('-n', '--ifnum', type=int, help='number of isolated interfaces to create', default=1)
+ parser.add_argument('-r', '--remove', help='remove instead of adding network interfaces', action="store_true")
+ parser.add_argument('-l', '--loss', help='simulated packet loss (percent)', type=int, default=0)
+ parser.add_argument('-d', '--delay', help='simulated latency (ms)', type=int, default=0)
+ parser.add_argument('-4', '--ipv4', help='Enable IPv4', action="store_true")
+ parser.add_argument('-6', '--ipv6', help='Enable IPv6', action="store_true")
+
+ args = parser.parse_args()
+
+ local_addr4 = '10.0.42.'
+ local_addr6 = '2001:db9::'
+ brige_name = 'br'+args.ifname
+
+ ip = None
+ try:
+ ip = IPDB()
+ if args.remove:
+ # cleanup interfaces
+ for ifn in range(args.ifnum):
+ iface = args.ifname+str(ifn)
+ if iface in ip.interfaces:
+ with ip.interfaces[iface] as i:
+ i.remove()
+ if 'tap'+args.ifname in ip.interfaces:
+ with ip.interfaces['tap'+args.ifname] as i:
+ i.remove()
+ if brige_name in ip.interfaces:
+ with ip.interfaces[brige_name] as i:
+ i.remove()
+ for ifn in range(args.ifnum):
+ netns = NetNS('node'+str(ifn))
+ netns.close()
+ netns.remove()
+ else:
+ for ifn in range(args.ifnum):
+ iface = args.ifname+str(ifn)
+ if not iface in ip.interfaces:
+ ip.create(kind='veth', ifname=iface, peer=iface+'.1').commit()
+
+ ip.create(kind='tuntap', ifname='tap'+args.ifname, mode='tap').commit()
+
+ with ip.create(kind='bridge', ifname=brige_name) as i:
+ for ifn in range(args.ifnum):
+ iface = args.ifname+str(ifn)
+ i.add_port(ip.interfaces[iface])
+ i.add_port(ip.interfaces['tap'+args.ifname])
+ if args.ipv4:
+ i.add_ip(local_addr4+'1/24')
+ if args.ipv6:
+ i.add_ip(local_addr6+'1/64')
+ i.up()
+
+ with ip.interfaces['tap'+args.ifname] as tap:
+ tap.up()
+
+ for ifn in range(args.ifnum):
+ iface = args.ifname+str(ifn)
+
+ nns = NetNS('node'+str(ifn))
+ iface1 = iface+'.1'
+ with ip.interfaces[iface1] as i:
+ i.net_ns_fd = nns.netns
+
+ with ip.interfaces[iface] as i:
+ i.up()
+
+ ip_ns = IPDB(nl=nns)
+ try:
+ with ip_ns.interfaces.lo as lo:
+ lo.up()
+ with ip_ns.interfaces[iface1] as i:
+ if args.ipv4:
+ i.add_ip(local_addr4+str(ifn+8)+'/24')
+ if args.ipv6:
+ i.add_ip(local_addr6+str(ifn+8)+'/64')
+ i.up()
+ finally:
+ ip_ns.release()
+
+ nsp = NSPopen(nns.netns, ["tc", "qdisc", "add", "dev", iface1, "root", "netem", "delay", str(args.delay)+"ms", str(int(args.delay/2))+"ms", "loss", str(args.loss)+"%", "25%"], stdout=subprocess.PIPE)
+ #print(nsp.communicate()[0].decode())
+ nsp.communicate()
+ nsp.wait()
+ nsp.release()
+
+ if args.ipv4:
+ subprocess.call(["sysctl", "-w", "net.ipv4.conf."+brige_name+".forwarding=1"])
+ if args.ipv6:
+ subprocess.call(["sysctl", "-w", "net.ipv6.conf."+brige_name+".forwarding=1"])
+
+ except Exception as e:
+ print('Error',e)
+ finally:
+ if ip:
+ ip.release()
diff --git a/python/tools/virtual_network_builder.py b/python/tools/virtual_network_builder.py
deleted file mode 100755
index 682d2351..00000000
--- a/python/tools/virtual_network_builder.py
+++ /dev/null
@@ -1,119 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (c) 2015-2016 Savoir-faire Linux Inc.
-# Author: Adrien Béraud
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; If not, see .
-
-import argparse, subprocess
-
-from pyroute2 import IPDB, NetNS
-from pyroute2.netns.process.proxy import NSPopen
-
-parser = argparse.ArgumentParser(description='Creates a virtual network topology for testing')
-parser.add_argument('-i', '--ifname', help='interface name', default='ethdht')
-parser.add_argument('-n', '--ifnum', type=int, help='number of isolated interfaces to create', default=1)
-parser.add_argument('-r', '--remove', help='remove instead of adding network interfaces', action="store_true")
-parser.add_argument('-l', '--loss', help='simulated packet loss (percent)', type=int, default=0)
-parser.add_argument('-d', '--delay', help='simulated latency (ms)', type=int, default=0)
-parser.add_argument('-4', '--ipv4', help='Enable IPv4', action="store_true")
-parser.add_argument('-6', '--ipv6', help='Enable IPv6', action="store_true")
-
-args = parser.parse_args()
-
-local_addr4 = '10.0.42.'
-local_addr6 = '2001:db9::'
-brige_name = 'br'+args.ifname
-
-ip = None
-try:
- ip = IPDB()
- if args.remove:
- # cleanup interfaces
- for ifn in range(args.ifnum):
- iface = args.ifname+str(ifn)
- if iface in ip.interfaces:
- with ip.interfaces[iface] as i:
- i.remove()
- if 'tap'+args.ifname in ip.interfaces:
- with ip.interfaces['tap'+args.ifname] as i:
- i.remove()
- if brige_name in ip.interfaces:
- with ip.interfaces[brige_name] as i:
- i.remove()
- for ifn in range(args.ifnum):
- netns = NetNS('node'+str(ifn))
- netns.close()
- netns.remove()
- else:
- for ifn in range(args.ifnum):
- iface = args.ifname+str(ifn)
- if not iface in ip.interfaces:
- ip.create(kind='veth', ifname=iface, peer=iface+'.1').commit()
-
- ip.create(kind='tuntap', ifname='tap'+args.ifname, mode='tap').commit()
-
- with ip.create(kind='bridge', ifname=brige_name) as i:
- for ifn in range(args.ifnum):
- iface = args.ifname+str(ifn)
- i.add_port(ip.interfaces[iface])
- i.add_port(ip.interfaces['tap'+args.ifname])
- if args.ipv4:
- i.add_ip(local_addr4+'1/24')
- if args.ipv6:
- i.add_ip(local_addr6+'1/64')
- i.up()
-
- with ip.interfaces['tap'+args.ifname] as tap:
- tap.up()
-
- for ifn in range(args.ifnum):
- iface = args.ifname+str(ifn)
-
- nns = NetNS('node'+str(ifn))
- iface1 = iface+'.1'
- with ip.interfaces[iface1] as i:
- i.net_ns_fd = nns.netns
-
- with ip.interfaces[iface] as i:
- i.up()
-
- ip_ns = IPDB(nl=nns)
- try:
- with ip_ns.interfaces.lo as lo:
- lo.up()
- with ip_ns.interfaces[iface1] as i:
- if args.ipv4:
- i.add_ip(local_addr4+str(ifn+8)+'/24')
- if args.ipv6:
- i.add_ip(local_addr6+str(ifn+8)+'/64')
- i.up()
- finally:
- ip_ns.release()
-
- nsp = NSPopen(nns.netns, ["tc", "qdisc", "add", "dev", iface1, "root", "netem", "delay", str(args.delay)+"ms", str(int(args.delay/2))+"ms", "loss", str(args.loss)+"%", "25%"], stdout=subprocess.PIPE)
- #print(nsp.communicate()[0].decode())
- nsp.communicate()
- nsp.wait()
- nsp.release()
-
- if args.ipv4:
- subprocess.call(["sysctl", "-w", "net.ipv4.conf."+brige_name+".forwarding=1"])
- if args.ipv6:
- subprocess.call(["sysctl", "-w", "net.ipv6.conf."+brige_name+".forwarding=1"])
-
-except Exception as e:
- print('Error',e)
-finally:
- if ip:
- ip.release()