blob: 8bdec135a8cecc138718427b9ed8e689605541c7 [file] [log] [blame]
Ashlesh Gawande6c86e302019-09-17 22:27:05 -05001# -*- Mode:python; c-file-style:"gnu"; indent-tabs-mode:nil -*- */
2#
Saurab Dulal576a4192020-08-25 00:55:22 -05003# Copyright (C) 2015-2020, The University of Memphis,
Ashlesh Gawande6c86e302019-09-17 22:27:05 -05004# Arizona Board of Regents,
5# Regents of the University of California.
6#
7# This file is part of Mini-NDN.
8# See AUTHORS.md for a complete list of Mini-NDN authors and contributors.
9#
10# Mini-NDN is free software: you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation, either version 3 of the License, or
13# (at your option) any later version.
14#
15# Mini-NDN is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with Mini-NDN, e.g., in COPYING.md file.
22# If not, see <http://www.gnu.org/licenses/>.
23
24import argparse
25import sys
26import time
27import os
28import configparser
Saurab Dulal576a4192020-08-25 00:55:22 -050029from subprocess import call, Popen, PIPE
phmollad8d37e2020-03-03 12:53:08 +010030import shutil
31import glob
Alexander Laneea2d5d62019-10-04 16:48:52 -050032from traceback import format_exc
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050033
34from mininet.topo import Topo
35from mininet.net import Mininet
36from mininet.link import TCLink
37from mininet.node import Switch
38from mininet.util import ipStr, ipParse
Alexander Laneea2d5d62019-10-04 16:48:52 -050039from mininet.log import info, debug, error
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050040
phmollad8d37e2020-03-03 12:53:08 +010041
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050042class Minindn(object):
43 """ This class provides the following features to the user:
44 1) Wrapper around Mininet object with option to pass topology directly
45 1.1) Users can pass custom argument parser to extend the default on here
46 2) Parses the topology file given via command line if user does not pass a topology object
47 3) Provides way to stop Mini-NDN via stop
48 3.1) Applications register their clean up function with this class
49 4) Sets IPs on neighbors for connectivity required in a switch-less topology
50 5) Some other utility functions
51 """
52 ndnSecurityDisabled = False
Italo Valcyccd85b12020-07-24 12:35:20 -050053 workDir = '/tmp/minindn'
54 resultDir = None
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050055
56 def __init__(self, parser=argparse.ArgumentParser(), topo=None, topoFile=None, **mininetParams):
57 """Create MiniNDN object
58 parser: Parent parser of Mini-NDN parser
59 topo: Mininet topo object (optional)
60 topoFile: Mininet topology file location (optional)
61 mininetParams: Any params to pass to Mininet
62 """
63 self.parser = Minindn.parseArgs(parser)
64 self.args = self.parser.parse_args()
65
Italo Valcyccd85b12020-07-24 12:35:20 -050066 Minindn.workDir = os.path.abspath(self.args.workDir)
67 Minindn.resultDir = self.args.resultDir
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050068
69 if not topoFile:
70 # Args has default topology if none specified
71 self.topoFile = self.args.topoFile
72 else:
73 self.topoFile = topoFile
74
75 if topo is None:
76 try:
77 info('Using topology file {}\n'.format(self.topoFile))
78 self.topo = self.processTopo(self.topoFile)
79 except configparser.NoSectionError as e:
80 info('Error reading config file: {}\n'.format(e))
81 sys.exit(1)
82 else:
83 self.topo = topo
84
85 self.net = Mininet(topo=self.topo, link=TCLink, **mininetParams)
86
87 for host in self.net.hosts:
88 if 'params' not in host.params:
89 host.params['params'] = {}
90
Italo Valcyccd85b12020-07-24 12:35:20 -050091 homeDir = '{}/{}'.format(Minindn.workDir, host.name)
Ashlesh Gawande6c86e302019-09-17 22:27:05 -050092 host.params['params']['homeDir'] = homeDir
93 host.cmd('mkdir -p {}'.format(homeDir))
94 host.cmd('export HOME={} && cd ~'.format(homeDir))
95
96 self.cleanups = []
97
98 if not self.net.switches:
99 self.ethernetPairConnectivity()
100
101 try:
Saurab Dulal576a4192020-08-25 00:55:22 -0500102 process = Popen(['ndnsec-get-default', '-k'], stdout=PIPE, stderr=PIPE)
103 output, error = process.communicate()
104 if process.returncode == 0:
105 Minindn.ndnSecurityDisabled = '/dummy/KEY/-%9C%28r%B8%AA%3B%60' in output
106 info('Dummy key chain patch is installed in ndn-cxx. Security will be disabled.\n')
107 else:
108 debug(error)
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500109 except:
110 pass
111
112 @staticmethod
113 def parseArgs(parent):
114 parser = argparse.ArgumentParser(prog='minindn', parents=[parent], add_help=False)
115
116 # nargs='?' required here since optional argument
117 parser.add_argument('topoFile', nargs='?', default='/usr/local/etc/mini-ndn/default-topology.conf',
118 help='If no template_file is given, topologies/default-topology.conf will be used.')
119
120 parser.add_argument('--work-dir', action='store', dest='workDir', default='/tmp/minindn',
121 help='Specify the working directory; default is /tmp/minindn')
122
123 parser.add_argument('--result-dir', action='store', dest='resultDir', default=None,
124 help='Specify the full path destination folder where experiment results will be moved')
125
126 return parser
127
128 def ethernetPairConnectivity(self):
129 ndnNetBase = '10.0.0.0'
130 interfaces = []
131 for host in self.net.hosts:
132 for intf in host.intfList():
133 link = intf.link
134 node1, node2 = link.intf1.node, link.intf2.node
135
136 if isinstance(node1, Switch) or isinstance(node2, Switch):
137 continue
138
139 if link.intf1 not in interfaces and link.intf2 not in interfaces:
140 interfaces.append(link.intf1)
141 interfaces.append(link.intf2)
142 node1.setIP(ipStr(ipParse(ndnNetBase) + 1) + '/30', intf=link.intf1)
143 node2.setIP(ipStr(ipParse(ndnNetBase) + 2) + '/30', intf=link.intf2)
144 ndnNetBase = ipStr(ipParse(ndnNetBase) + 4)
145
146 @staticmethod
147 def processTopo(topoFile):
Chad Cothraneef6ee82021-03-22 11:38:33 -0500148 config = configparser.ConfigParser(delimiters=' ', allow_no_value=True)
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500149 config.read(topoFile)
150 topo = Topo()
151
152 items = config.items('nodes')
dulalsaurab5c79db02020-02-27 06:04:56 +0000153 coordinates = []
154
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500155 for item in items:
156 name = item[0].split(':')[0]
Chad Cothraneef6ee82021-03-22 11:38:33 -0500157 if item[1] in coordinates and item[1] != '_':
phmollad8d37e2020-03-03 12:53:08 +0100158 error("FATAL: Duplicate Coordinate, \'{}\' used by multiple nodes\n" \
dulalsaurab5c79db02020-02-27 06:04:56 +0000159 .format(item[1]))
160 sys.exit(1)
161 coordinates.append(item[1])
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500162
163 params = {}
164 for param in item[1].split(' '):
165 if param == '_':
166 continue
167 params[param.split('=')[0]] = param.split('=')[1]
168
169 topo.addHost(name, params=params)
170
171 try:
172 items = config.items('switches')
173 for item in items:
174 name = item[0].split(':')[0]
175 topo.addSwitch(name)
176 except configparser.NoSectionError:
177 # Switches are optional
178 pass
179
180 items = config.items('links')
181 for item in items:
182 link = item[0].split(':')
183
184 params = {}
185 for param in item[1].split(' '):
186 key = param.split('=')[0]
187 value = param.split('=')[1]
188 if key in ['bw', 'jitter', 'max_queue_size']:
189 value = int(value)
190 if key == 'loss':
191 value = float(value)
192 params[key] = value
193
194 topo.addLink(link[0], link[1], **params)
195
196 return topo
197
198 def start(self):
199 self.net.start()
200 time.sleep(3)
201
202 def stop(self):
203 for cleanup in self.cleanups:
204 cleanup()
205 self.net.stop()
206
Italo Valcyccd85b12020-07-24 12:35:20 -0500207 if Minindn.resultDir is not None:
208 info("Moving results to \'{}\'\n".format(Minindn.resultDir))
209 os.system("mkdir -p {}".format(Minindn.resultDir))
210 for file in glob.glob('{}/*'.format(Minindn.workDir)):
211 shutil.move(file, Minindn.resultDir)
phmollad8d37e2020-03-03 12:53:08 +0100212
Ashlesh Gawande6c86e302019-09-17 22:27:05 -0500213 @staticmethod
214 def cleanUp():
215 devnull = open(os.devnull, 'w')
216 call('nfd-stop', stdout=devnull, stderr=devnull)
217 call('mn --clean'.split(), stdout=devnull, stderr=devnull)
218
219 @staticmethod
220 def verifyDependencies():
221 """Prevent MiniNDN from running without necessary dependencies"""
222 dependencies = ['nfd', 'nlsr', 'infoedit', 'ndnping', 'ndnpingserver']
223 devnull = open(os.devnull, 'w')
224 # Checks that each program is in the system path
225 for program in dependencies:
226 if call(['which', program], stdout=devnull):
227 error('{} is missing from the system path! Exiting...\n'.format(program))
228 sys.exit(1)
229 devnull.close()
230
231 @staticmethod
232 def sleep(seconds):
233 # sleep is not required if ndn-cxx is using in-memory keychain
234 if not Minindn.ndnSecurityDisabled:
235 time.sleep(seconds)
Alexander Laneea2d5d62019-10-04 16:48:52 -0500236
237 @staticmethod
238 def handleException():
239 'Utility method to perform cleanup steps and exit after catching exception'
240 Minindn.cleanUp()
241 info(format_exc())
242 exit(1)