PageRenderTime 40ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/mininet/cli.py

https://github.com/capveg/mininet
Python | 405 lines | 397 code | 0 blank | 8 comment | 2 complexity | b6ed2fc03c27ca4461a3f5c02edec52f MD5 | raw file
  1. """
  2. A simple command-line interface for Mininet.
  3. The Mininet CLI provides a simple control console which
  4. makes it easy to talk to nodes. For example, the command
  5. mininet> h27 ifconfig
  6. runs 'ifconfig' on host h27.
  7. Having a single console rather than, for example, an xterm for each
  8. node is particularly convenient for networks of any reasonable
  9. size.
  10. The CLI automatically substitutes IP addresses for node names,
  11. so commands like
  12. mininet> h2 ping h3
  13. should work correctly and allow host h2 to ping host h3
  14. Several useful commands are provided, including the ability to
  15. list all nodes ('nodes'), to print out the network topology
  16. ('net') and to check connectivity ('pingall', 'pingpair')
  17. and bandwidth ('iperf'.)
  18. """
  19. from subprocess import call
  20. from cmd import Cmd
  21. from os import isatty
  22. from select import poll, POLLIN
  23. import sys
  24. from mininet.log import info, output, error
  25. from mininet.term import makeTerms
  26. from mininet.util import quietRun, isShellBuiltin
  27. class CLI( Cmd ):
  28. "Simple command-line interface to talk to nodes."
  29. prompt = 'mininet> '
  30. def __init__( self, mininet, stdin=sys.stdin, script=None ):
  31. self.mn = mininet
  32. self.nodelist = self.mn.controllers + self.mn.switches + self.mn.hosts
  33. self.nodemap = {} # map names to Node objects
  34. for node in self.nodelist:
  35. self.nodemap[ node.name ] = node
  36. # Attempt to handle input
  37. self.stdin = stdin
  38. self.inPoller = poll()
  39. self.inPoller.register( stdin )
  40. self.inputFile = script
  41. Cmd.__init__( self )
  42. info( '*** Starting CLI:\n' )
  43. if self.inputFile:
  44. self.do_source( self.inputFile )
  45. return
  46. while True:
  47. try:
  48. # Make sure no nodes are still waiting
  49. for node in self.nodelist:
  50. while node.waiting:
  51. node.sendInt()
  52. node.monitor()
  53. if self.isatty():
  54. quietRun( 'stty sane' )
  55. self.cmdloop()
  56. break
  57. except KeyboardInterrupt:
  58. output( '\nInterrupt\n' )
  59. def emptyline( self ):
  60. "Don't repeat last command when you hit return."
  61. pass
  62. # Disable pylint "Unused argument: 'arg's'" messages, as well as
  63. # "method could be a function" warning, since each CLI function
  64. # must have the same interface
  65. # pylint: disable-msg=W0613,R0201
  66. helpStr = (
  67. 'You may also send a command to a node using:\n'
  68. ' <node> command {args}\n'
  69. 'For example:\n'
  70. ' mininet> h1 ifconfig\n'
  71. '\n'
  72. 'The interpreter automatically substitutes IP addresses\n'
  73. 'for node names when a node is the first arg, so commands\n'
  74. 'like\n'
  75. ' mininet> h2 ping h3\n'
  76. 'should work.\n'
  77. '\n'
  78. 'Some character-oriented interactive commands require\n'
  79. 'noecho:\n'
  80. ' mininet> noecho h2 vi foo.py\n'
  81. 'However, starting up an xterm/gterm is generally better:\n'
  82. ' mininet> xterm h2\n\n'
  83. )
  84. def do_help( self, line ):
  85. "Describe available CLI commands."
  86. Cmd.do_help( self, line )
  87. if line is '':
  88. output( self.helpStr )
  89. def do_nodes( self, line ):
  90. "List all nodes."
  91. nodes = ' '.join( [ node.name for node in sorted( self.nodelist ) ] )
  92. output( 'available nodes are: \n%s\n' % nodes )
  93. def do_net( self, line ):
  94. "List network connections."
  95. for switch in self.mn.switches:
  96. output( switch.name, '<->' )
  97. for intf in switch.intfs.values():
  98. # Ugly, but pylint wants it
  99. name = switch.connection.get( intf,
  100. ( None, 'Unknown ' ) )[ 1 ]
  101. output( ' %s' % name )
  102. output( '\n' )
  103. def do_sh( self, line ):
  104. "Run an external shell command"
  105. call( line, shell=True )
  106. # do_py() needs to catch any exception during eval()
  107. # pylint: disable-msg=W0703
  108. def do_py( self, line ):
  109. """Evaluate a Python expression.
  110. Node names may be used, e.g.: h1.cmd('ls')"""
  111. try:
  112. result = eval( line, globals(), self.nodemap )
  113. if not result:
  114. return
  115. elif isinstance( result, str ):
  116. output( result + '\n' )
  117. else:
  118. output( repr( result ) + '\n' )
  119. except Exception, e:
  120. output( str( e ) + '\n' )
  121. # pylint: enable-msg=W0703
  122. def do_pingall( self, line ):
  123. "Ping between all hosts."
  124. args = line.split()
  125. numPerPing = 1
  126. if args:
  127. if len(args) == 1:
  128. try:
  129. numPerPing = int(args[0])
  130. except ValueError:
  131. error( 'invalid arg: ping numberPacketPerPing, where numberPacketPerPing is a number.\n')
  132. return
  133. else:
  134. error( 'invalid number of args: ping numberPacketPerPing\n')
  135. return
  136. self.mn.pingAll(numPerPing=numPerPing)
  137. def do_pingpair( self, line ):
  138. "Ping between first two hosts, useful for testing."
  139. self.mn.pingPair()
  140. def do_pingset (self, line ):
  141. "Ping between all hosts in a set."
  142. args = line.split()
  143. if not args or len(args) < 2:
  144. error( "requires at least two hosts specified" )
  145. else:
  146. hosts = []
  147. err = False
  148. for arg in args:
  149. if arg not in self.nodemap:
  150. err = True
  151. error( "node '%s' not in network\n" % arg )
  152. else:
  153. hosts.append( self.nodemap[ arg ] )
  154. if not err:
  155. self.mn.ping( hosts )
  156. def do_iperf( self, line ):
  157. "Simple iperf TCP test between two (optionally specified) hosts."
  158. args = line.split()
  159. if not args:
  160. self.mn.iperf()
  161. elif len(args) == 2:
  162. hosts = []
  163. err = False
  164. for arg in args:
  165. if arg not in self.nodemap:
  166. err = True
  167. error( "node '%s' not in network\n" % arg )
  168. else:
  169. hosts.append( self.nodemap[ arg ] )
  170. if not err:
  171. self.mn.iperf( hosts )
  172. else:
  173. error( 'invalid number of args: iperf src dst\n' )
  174. def do_iperfudp( self, line ):
  175. "Simple iperf TCP test between two (optionally specified) hosts."
  176. args = line.split()
  177. if not args:
  178. self.mn.iperf( l4Type='UDP' )
  179. elif len(args) == 3:
  180. udpBw = args[ 0 ]
  181. hosts = []
  182. err = False
  183. for arg in args[ 1:3 ]:
  184. if arg not in self.nodemap:
  185. err = True
  186. error( "node '%s' not in network\n" % arg )
  187. else:
  188. hosts.append( self.nodemap[ arg ] )
  189. if not err:
  190. self.mn.iperf( hosts, l4Type='UDP', udpBw=udpBw )
  191. else:
  192. error( 'invalid number of args: iperfudp bw src dst\n' +
  193. 'bw examples: 10M\n' )
  194. def do_intfs( self, line ):
  195. "List interfaces."
  196. for node in self.nodelist:
  197. output( '%s: %s\n' %
  198. ( node.name, ' '.join( sorted( node.intfs.values() ) ) ) )
  199. def do_dump( self, line ):
  200. "Dump node info."
  201. for node in self.nodelist:
  202. output( '%s\n' % node )
  203. def do_link( self, line ):
  204. "Bring link(s) between two nodes up or down."
  205. args = line.split()
  206. if len(args) != 3:
  207. error( 'invalid number of args: link end1 end2 [up down]\n' )
  208. elif args[ 2 ] not in [ 'up', 'down' ]:
  209. error( 'invalid type: link end1 end2 [up down]\n' )
  210. else:
  211. self.mn.configLinkStatus( *args )
  212. def do_attach( self, line ):
  213. "Create new link between a host and a switch"
  214. args = line.split()
  215. if len(args) != 2:
  216. error( 'invalid number of args: attach host switch\n' )
  217. else:
  218. self.mn.attachHost(*args)
  219. def do_detach( self, line ):
  220. "Detach a host from a switch"
  221. args = line.split()
  222. if len(args) < 1 or len(args) > 2:
  223. error( 'invalid number of args: detach host [switch]\n' )
  224. else:
  225. self.mn.detachHost(*args)
  226. def do_xterm( self, line, term='xterm' ):
  227. "Spawn xterm(s) for the given node(s)."
  228. args = line.split()
  229. if not args:
  230. error( 'usage: %s node1 node2 ...\n' % term )
  231. else:
  232. for arg in args:
  233. if arg not in self.nodemap:
  234. error( "node '%s' not in network\n" % arg )
  235. else:
  236. node = self.nodemap[ arg ]
  237. self.mn.terms += makeTerms( [ node ], term = term )
  238. def do_gterm( self, line ):
  239. "Spawn gnome-terminal(s) for the given node(s)."
  240. self.do_xterm( line, term='gterm' )
  241. def do_exit( self, line ):
  242. "Exit"
  243. return 'exited by user command'
  244. def do_quit( self, line ):
  245. "Exit"
  246. return self.do_exit( line )
  247. def do_EOF( self, line ):
  248. "Exit"
  249. output( '\n' )
  250. return self.do_exit( line )
  251. def isatty( self ):
  252. "Is our standard input a tty?"
  253. return isatty( self.stdin.fileno() )
  254. def do_noecho( self, line ):
  255. "Run an interactive command with echoing turned off."
  256. if self.isatty():
  257. quietRun( 'stty -echo' )
  258. self.default( line )
  259. if self.isatty():
  260. quietRun( 'stty echo' )
  261. def do_source( self, line ):
  262. "Read commands from an input file."
  263. args = line.split()
  264. if len(args) != 1:
  265. error( 'usage: source <file>\n' )
  266. return
  267. try:
  268. self.inputFile = open( args[ 0 ] )
  269. while True:
  270. line = self.inputFile.readline()
  271. if len( line ) > 0:
  272. self.onecmd( line )
  273. else:
  274. break
  275. except IOError:
  276. error( 'error reading file %s\n' % args[ 0 ] )
  277. self.inputFile = None
  278. def do_dpctl( self, line ):
  279. "Run dpctl command on all switches."
  280. args = line.split()
  281. if len(args) == 0:
  282. error( 'usage: dpctl command [arg1] [arg2] ...\n' )
  283. return
  284. if not self.mn.listenPort:
  285. error( "can't run dpctl w/no passive listening port\n")
  286. return
  287. for sw in self.mn.switches:
  288. output( '*** ' + sw.name + ' ' + ('-' * 72) + '\n' )
  289. output( sw.cmd( 'dpctl ' + ' '.join(args) +
  290. ' tcp:127.0.0.1:%i' % sw.listenPort ) )
  291. def default( self, line ):
  292. """Called on an input line when the command prefix is not recognized.
  293. Overridden to run shell commands when a node is the first CLI argument.
  294. Past the first CLI argument, node names are automatically replaced with
  295. corresponding IP addrs."""
  296. first, args, line = self.parseline( line )
  297. if args and len(args) > 0 and args[ -1 ] == '\n':
  298. args = args[ :-1 ]
  299. rest = args.split( ' ' )
  300. if first in self.nodemap:
  301. node = self.nodemap[ first ]
  302. # Substitute IP addresses for node names in command
  303. for index in range(len(rest)):
  304. arg = rest[index]
  305. if arg in self.nodemap:
  306. ip = self.nodemap[arg].IP()
  307. if not ip:
  308. error('%s is an unreachable, detached host\n' % arg)
  309. return
  310. rest[index] = ip
  311. #rest = [ self.nodemap[ arg ].IP()
  312. # if arg in self.nodemap else arg
  313. # for arg in rest ]
  314. rest = ' '.join( rest )
  315. # Run cmd on node:
  316. builtin = isShellBuiltin( first )
  317. node.sendCmd( rest, printPid=( not builtin ) )
  318. self.waitForNode( node )
  319. else:
  320. error( '*** Unknown command: %s\n' % first )
  321. # pylint: enable-msg=W0613,R0201
  322. def waitForNode( self, node ):
  323. "Wait for a node to finish, and print its output."
  324. # Pollers
  325. nodePoller = poll()
  326. nodePoller.register( node.stdout )
  327. bothPoller = poll()
  328. bothPoller.register( self.stdin )
  329. bothPoller.register( node.stdout )
  330. while True:
  331. try:
  332. bothPoller.poll()
  333. # XXX BL: this doesn't quite do what we want.
  334. if False and self.inputFile:
  335. key = self.inputFile.read( 1 )
  336. if key is not '':
  337. node.write(key)
  338. else:
  339. self.inputFile = None
  340. if isReadable( self.inPoller ):
  341. key = self.stdin.read( 1 )
  342. node.write( key )
  343. if isReadable( nodePoller ):
  344. data = node.monitor()
  345. output( data )
  346. if not node.waiting:
  347. break
  348. except KeyboardInterrupt:
  349. node.sendInt()
  350. # Helper functions
  351. def isReadable( poller ):
  352. "Check whether a Poll object has a readable fd."
  353. for fdmask in poller.poll( 0 ):
  354. mask = fdmask[ 1 ]
  355. if mask & POLLIN:
  356. return True