...
 
......@@ -90,7 +90,9 @@ At the top-level, no optional fields are defined. This may change in the future
Each Node defines the following properties:
- 'lans': A mapping of Emulab LANs a Node is connected to and its IP address on that lan
- 'management-ip': A single remote-accessible IP or hostname
- 'management-ip': A single remote-accessible IP or hostname. The interface to which this address belongs on the node is ignored by the automatic scripts
The `management-ip` field is required for use by the automated scripts, the `lans` field is not used
Each Link defines the following properties:
- 'netmask': The netmask of the Emulab LAN which corresponds to this link
......@@ -42,6 +42,10 @@ if __name__ == "__main__":
help="Username to use on all hosts. Defaults to current user's username")
parser.add_argument("--stop", action='store_true',
help="Stop all services (after writing config files)")
parser.add_argument("--controller-name", action='store', type=str, default=ospf_sniffer_configurator.DEFAULT_CONTROLLER,
help="Hostname or IP of the node which is listening to the OSPF reports (Default: {default})".format(default=ospf_sniffer_configurator.DEFAULT_CONTROLLER))
parser.add_argument("--controller-port", action='store', type=int, default=ospf_sniffer_configurator.DEFAULT_CONTROLLER_PORT,
help="Port number on the server listening for OSPF reports (Default: {default})".format(default=ospf_sniffer_configurator.DEFAULT_CONTROLLER_PORT))
parser.add_argument("--ovs-regex", action='store', type=str, default='.*ovs.*',
help="Regex to distinguish OVS nodes by label (Default \".*ovs.*\")")
parser.add_argument("--host-regex", action='store', type=str, default='.*host.*',
......@@ -88,7 +92,10 @@ if __name__ == "__main__":
ospf_sniffer_configurator.clone_repo_on_network(netgraph.graph, ignore_nodes=ovs_nodes + host_nodes)
ospf_sniffer_configurator.stop_sniffer_on_network(netgraph.graph, ignore_nodes=ovs_nodes + host_nodes) # Stopping with the app not running is not great, but better than starting twice
ospf_sniffer_configurator.start_sniffer_on_network(netgraph.graph, ignore_nodes=ovs_nodes + host_nodes)
ospf_sniffer_configurator.start_sniffer_on_network(netgraph.graph,
controller=args.controller_name,
port=args.controller_port,
ignore_nodes=ovs_nodes + host_nodes)
ssh_helper.network_graph_logout(netgraph.graph)
......
......@@ -26,6 +26,7 @@ import ssh_helper
DEFAULT_CLONE_PATH="~/sniffer/"
DEFAULT_CONTROLLER='core0'
DEFAULT_CONTROLLER_PORT = 11000
DEFAULT_EXECUTABLE='main.py'
DEFAULT_LOGFILE="/tmp/sniffer.log"
DEFAULT_PIDFILE="/tmp/sniffer.pid"
......@@ -44,6 +45,7 @@ def _generate_repo_update_command(path: str) -> str:
def _generate_start_command(path:str=DEFAULT_CLONE_PATH,
executable: str=DEFAULT_EXECUTABLE,
controller: str=DEFAULT_CONTROLLER,
port: int=DEFAULT_CONTROLLER_PORT,
logfile: str=DEFAULT_LOGFILE,
pidfile: str=DEFAULT_PIDFILE,
) -> str:
......@@ -55,20 +57,22 @@ def _generate_start_command(path:str=DEFAULT_CLONE_PATH,
:param path: Path into which the sniffer was cloned
:param executable: Filename of the sniffer daemon from the repository root
:param controller: Hostname or IP address to which sniffer reports should be sent
:param port: Port on the server listening for OSPF updates
:param logfile: File to which output from the sniffer should be written
:param pidfile: File to which the PID will be written
:return:
"""
return "{path}/{executable} --controller={controller} 1> {logfile} & echo $! > {pidfile} && disown".format(
return "sudo {path}/{executable} --controller={controller} --port={port} 1> {logfile} & echo $! > {pidfile} && disown".format(
path=path,
executable=executable,
controller=controller,
port=port,
logfile=logfile,
pidfile=pidfile,
)
def _generate_stop_command(pidfile: str=DEFAULT_PIDFILE) -> str:
return "kill $(cat {pidfile}) && rm {pidfile}".format(pidfile=pidfile)
return "kill $(cat {pidfile}); rm -f {pidfile}".format(pidfile=pidfile)
def clone_repo_on_network(graph: networkx.Graph,
......@@ -122,6 +126,7 @@ def start_sniffer_on_network(graph: networkx.Graph,
path:str=DEFAULT_CLONE_PATH,
executable: str=DEFAULT_EXECUTABLE,
controller: str=DEFAULT_CONTROLLER,
port: int=DEFAULT_CONTROLLER_PORT,
pidfile: str=DEFAULT_PIDFILE,
ignore_nodes: List[str]=None):
"""
......@@ -131,12 +136,13 @@ def start_sniffer_on_network(graph: networkx.Graph,
:param path: path into which the repository has been cloned
:param executable: executable to execute
:param controller: node which is listening for the OSPF reports
:param port: port on the controller listening for OSPF updates
:param pidfile: file to write the PID of the daemon
:param ignore_nodes: nodes which should not have the daemon run on them
:return:
"""
if ignore_nodes is None: ignore_nodes = []
command = _generate_start_command(path=path, executable=executable, controller=controller, pidfile=pidfile)
command = _generate_start_command(path=path, executable=executable, controller=controller, port=port, pidfile=pidfile)
hosts = [host for host in graph.nodes if host not in ignore_nodes]
commands = [command for host in hosts]
......@@ -176,10 +182,15 @@ def stop_sniffer_on_network(graph: networkx.Graph,
try:
output = ssh_helper.run_commands_on_many_hosts(sessions, commands)
except ssh_helper.SSHCommandErrorError as e:
acceptable_error = r".*{command}\s+cat: {pidfile}: No such file or directory.*".format(command=re.escape(command), pidfile=re.escape(pidfile))
# If the PID file does not exist, cat will have an error trying to read it. This is not a problem, since it probably just
# means the sniffer is not running or has already been stopped
acceptable_error_cat = r".*{command}\s+cat: {pidfile}: No such file or directory.*".format(command=re.escape(command), pidfile=re.escape(pidfile))
# If the sniffer cannot be killed, it probably means that it has already died for some reason. Since our goal was to stop it, mission accomplished
acceptable_error_kill = r".*kill: \([0-9]+\) - No such process.*".format(command=re.escape(command), pidfile=re.escape(pidfile))
while e is not None:
output = str.join(' ', e.output.splitlines()) # Get rid of newlines
if re.match(acceptable_error, output):
if re.match(acceptable_error_cat, output) or re.match(acceptable_error_kill, output):
# Presumably this is a sign of the stop command being run more than once or before the sniffer was started: no problem
e = e.next
continue
......@@ -203,6 +214,8 @@ if __name__ == "__main__":
help="File to write sniffer's output (Default: {default})".format(default=DEFAULT_LOGFILE))
parser.add_argument("--controller-name", action='store', type=str, default=DEFAULT_CONTROLLER,
help="Hostname or IP of the node which is listening to the OSPF reports (Default: {default})".format(default=DEFAULT_CONTROLLER))
parser.add_argument("--controller-port", action='store', type=int, default=DEFAULT_CONTROLLER_PORT,
help="Port number on the server listening for OSPF reports (Default: {default})".format(default=DEFAULT_CONTROLLER_PORT))
parser.add_argument("--repo-path", action='store', type=str, default=DEFAULT_REPO_URL,
help="(git) Repository which should be downloaded for the OSPF sniffer (Default: {default})".format(default=DEFAULT_REPO_URL))
parser.add_argument("--repo-branch", action='store', type=str, default=DEFAULT_REPO_BRANCH,
......@@ -229,6 +242,7 @@ if __name__ == "__main__":
start_sniffer_on_network(netgraph.graph,
path=args.clone_path,
controller=args.controller_name,
port=args.controller_port,
pidfile=args.pid_file,
ignore_nodes=ignore_nodes)
......