blob: c129875ce29bac43fdd2c02c8b7dc6a0952dd402 [file] [log] [blame]
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -07001#! /usr/bin/env python
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -07002# -*- Mode: python; py-indent-offset: 4; tab-width: 8; -*-
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -07003
4"""
5onoscli : ONOS-specific Command Line Interface
6
7Usage:
8 # Running the CLI in interactive mode:
9 $ ./onoscli
10
11 # Running multiple CLI commands in batch mode
12 $ cat commands.txt | ./onoscli
13
14 # Running a single command from the system command-line
15 $ ./onoscli -c show switch all
16
17 # Run the following command for additional help
18 $ ./onoscli -h
19"""
20
21#
22# INSTALLATION NOTES: MUST install Python cmd2 module. E.g., on Ubuntu:
23# sudo apt-get install python-cmd2
Pavlin Radoslavova9b78582014-06-06 15:41:32 -070024# On older Ubuntu installations (e.g., Ubuntu-12.04 or Ubuntu-13.04), install
25# also:
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -070026# sudo apt-get install python-pyparsing
27#
28
29#
30# ADDING A NEW CLI COMMAND:
31# 1. Add the appropriate Command entry (or entries) inside array
32# OnosCli.init_commands.
33# See the comments for init_commands for details.
34# 2. Add the appropriate callback (inside class OnosCli) for the CLI command
35#
36#
37
38import sys
39import argparse
40import json
41from optparse import make_option
42import urllib2
43from urllib2 import URLError, HTTPError
44import cmd2
45from cmd2 import Cmd
46from cmd2 import options
47
48
49class Command():
50 "Command node. A hierarchy of nodes are organized in a command tree."
51
52 def __init__(self, name, help, callback=None, add_parser_args=None):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -070053 """name: a string with the full command name
54 help: the help string for the command
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -070055 callback: the method to be called if the command is executed
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -070056 add_parser_args: the parser arguments to add to the command: a dictionary of argparse arguments"""
57 # Normalize the name by removing extra spaces
58 self.split_name = name.split() # List of the words in the name
59 self.name = ' '.join(self.split_name) # Normalized name
60 self.last_subname = self.split_name[-1] # Last word in the name
61 self.parent_name = ' '.join(self.split_name[:-1]) # Name of parent command
62 self.help = help # THe help string
63 self.callback = callback # The command callback
64 self.add_parser_args = add_parser_args # Parser arguments to add
65 self.parent = None # The parent Command
66 self.children = [] # The children Command entries
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -070067
68
69class OnosCli(Cmd):
70 "The main ONOS CLI class"
71
72 # Setup generic CLI fields
73 intro = "Welcome to the ONOS CLI. Type help or ? to list commands.\n"
74 prompt = "(onos) "
75 settable = Cmd.settable + "prompt CLI prompt"
76
77 # Setup ONOS-specific fields
78 onos_ip = "127.0.0.1"
79 settable = settable + "onos_ip ONOS IP address"
80 onos_port = 8080
81 settable = settable + "onos_port ONOS REST port number"
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -070082 output_format = "json" # Valid values: json, text
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -070083 settable = settable + "output_format The output format: `text` or `json`"
84
85 # Collection of commands sorted by the level in the CLI command hierarchy
86 commands = []
87 # Commands, parsers and subparsers keyed by the command name
88 commands_dict = {}
89 parsers_dict = {}
90 subparsers_dict = {}
91
92 def __init__(self):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -070093 Cmd.__init__(self)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -070094
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -070095 #
96 # An array of the ONOS-specific CLI commands.
97 # Each entry is a Command instance, and must have at least
98 # two arguments:
99 # * Command name as typed on the CLI. E.g.:
100 # "show intent"
101 # * Command help description. E.g.:
102 # "Show intents"
103 #
104 # Executable commands should have a third Command argument, which is
105 # the name of the method to be called when the command is executed.
106 # The method will be called with the (argparse) parsed arguments
107 # for that command.
108 #
109 # If an executable command takes arguments, those should be described
110 # in the Command's fourth argument. It is a list of pairs:
111 # [
112 # ("--argument-name1", dict(...)),
113 # ("--argument-name2", dict(...)),
114 # ...
115 # ]
116 # where the first entry in the pair is the argument name, and the
117 # second entry in the pair is a dictionary with argparse-specific
118 # description of the argument.
119 #
120 init_commands = [
121 Command("delete", "Delete command"),
122 #
123 Command("delete intent",
124 """Delete high-level intents
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700125 Usage:
126 delete intent --intent-id INTENT_ID Delete a high-level intent
127 delete intent --all Delete all high-level intents
128 Arguments:
129 --intent-id INTENT_ID The Intent ID (an integer)
130 --all Delete all high-level intents""",
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700131 self.delete_intent,
132 [
133 ("--intent-id", dict(required=False, type=int)),
134 ("--all", dict(required=False, action='store_true'))
135 ]
136 ),
137 #
138 Command("set", "Set command"),
139 #
140 Command("set intent",
141 """Set a high-level intent
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700142 Usage:
143 set intent <ARGS>
144 Arguments:
145 --intent-id INTENT_ID The Intent ID (an integer) (REQUIRED)
146 --src-dpid SRC_DPID Source Switch DPID (REQUIRED)
147 --src-port SRC_PORT Source Switch Port (REQUIRED)
148 --dst-dpid DST_DPID Destination Switch DPID (REQUIRED)
149 --dst-port DST_PORT Destination Switch Port (REQUIRED)
150 --match-src-mac MATCH_SRC_MAC Matching Source MAC Address (REQUIRED)
151 --match-dst-mac MATCH_DST_MAC Matching Destination MAC Address (REQUIRED)""",
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700152 self.set_intent,
153 [
154 ("--intent-id", dict(required=True, type=int)),
155 ("--src-dpid", dict(required=True)),
156 ("--src-port", dict(required=True, type=int)),
157 ("--dst-dpid", dict(required=True)),
158 ("--dst-port", dict(required=True, type=int)),
159 ("--match-src-mac", dict(required=True)),
160 ("--match-dst-mac", dict(required=True))
161 ]),
162 #
163 Command("show", "Show command"),
164 #
Pavlin Radoslavov308337c2014-06-11 10:25:44 -0700165 Command("show host", "Show hosts"),
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700166 #
Pavlin Radoslavov308337c2014-06-11 10:25:44 -0700167 Command("show host all", "Show all hosts", self.show_host_all),
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700168 #
169 Command("show intent", "Show intents"),
170 #
171 Command("show intent high",
172 """Show all high-level intents
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700173 show intent high --intent-id INTENT_ID Show a high-level intent""",
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700174 self.show_intent_high,
175 [
176 ("--intent-id", dict(required=False, type=int))
177 ]
178 ),
179 #
180 Command("show intent low",
181 """Show all low-level intents
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700182 show intent low --intent-id INTENT_ID Show a low-level intent""",
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700183 self.show_intent_low,
184 [
185 ("--intent-id", dict(required=False))
186 ]
187 ),
188 #
189 Command("show link", "Show links"),
190 #
191 Command("show link all", "Show all links", self.show_link_all),
192 #
Pavlin Radoslavov6cc3f742014-07-09 18:04:04 -0700193 Command("show metrics",
194 """Show all metrics
195 show metrics --metric-id METRIC_ID Show a metric""",
196 self.show_metrics,
197 [
198 ("--metric-id", dict(required=False, type=str)),
199 ]
200 ),
Ray Milkey4d238292014-07-03 11:07:33 -0700201 #
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700202 Command("show path", "Show a path"),
203 #
204 Command("show path shortest",
205 """Show a shortest path
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700206 Usage:
207 show path shortest --src-dpid SRC_DPID --dst-dpid DST_DPID
208 Arguments:
209 --src-dpid SRC_DPID Source Switch DPID
210 --dst-dpid DST_DPID Destination Switch DPID""",
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700211 self.show_path_shortest,
212 [
213 ("--src-dpid", dict(required=True)),
214 ("--dst-dpid", dict(required=True))
215 ]),
216 #
217 Command("show switch", "Show switches"),
218 #
219 Command("show switch all", "Show all switches", self.show_switch_all),
220 #
221 Command("show topology", "Show network topology"),
222 #
223 Command("show topology all", "Show whole network topology", self.show_topology_all)
224 ]
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700225
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700226 # Sort the commands by the level in the CLI command hierarchy
227 self.commands = sorted(init_commands, key = lambda c: len(c.name.split()))
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700228
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700229 # Create a dictionary with all commands: name -> Command
230 for c in self.commands:
231 self.commands_dict[c.name] = c
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700232
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700233 # Create a tree with all commands
234 for c in self.commands:
235 if c.parent_name:
236 pc = self.commands_dict[c.parent_name]
237 pc.children.append(c)
238 c.parent = pc
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700239
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700240 # Create the parsers and the sub-parsers
241 for c in self.commands:
242 # Add a parser
243 parser = None
244 if c.parent is None:
245 # Add a top-level parser
246 parser = argparse.ArgumentParser(description=c.help,
247 prog=c.name,
248 add_help=False)
249 else:
250 # Add a parser from the parent's subparser
251 parent_subparser = self.subparsers_dict[c.parent_name]
252 parser = parent_subparser.add_parser(c.last_subname,
253 help=c.help,
254 add_help=False)
255 self.parsers_dict[c.name] = parser
256 # Add a sub-parser
257 if c.children:
258 subparser = parser.add_subparsers(help=c.help)
259 self.subparsers_dict[c.name] = subparser
260 # Setup the callback
261 if c.callback is not None:
262 parser.set_defaults(func=c.callback)
263 # Init the argument parser
264 if c.add_parser_args is not None:
265 for a in c.add_parser_args:
266 (p1, p2) = a
267 parser.add_argument(p1, **p2)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700268
269 def delete_intent(self, args):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700270 "CLI command callback: delete intent"
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700271
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700272 url = ""
273 if args.all:
274 # Delete all intents
275 url = "http://%s:%s/wm/onos/intent/high" % (self.onos_ip, self.onos_port)
276 else:
277 if args.intent_id is None:
278 print "*** Unknown syntax:"
279 self.help_delete()
Pavlin Radoslavov6cc3f742014-07-09 18:04:04 -0700280 return
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700281 # Delete an intent
282 url = "http://%s:%s/wm/onos/intent/high/%s" % (self.onos_ip, self.onos_port, args.intent_id)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700283
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700284 result = delete_json(url)
285 # NOTE: No need to print the response
286 # if len(result) != 0:
287 # self.print_json_result(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700288
289 def set_intent(self, args):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700290 "CLI command callback: set intent"
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700291
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700292 intents = []
293 oper = {}
294 # Create the POST payload
295 oper['intentId'] = args.intent_id
Pavlin Radoslavova9b78582014-06-06 15:41:32 -0700296 oper['intentType'] = 'SHORTEST_PATH' # XXX: Hardcoded
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700297 oper['staticPath'] = False # XXX: Hardcoded
298 oper['srcSwitchDpid'] = args.src_dpid
299 oper['srcSwitchPort'] = args.src_port
300 oper['dstSwitchDpid'] = args.dst_dpid
301 oper['dstSwitchPort'] = args.dst_port
302 oper['matchSrcMac'] = args.match_src_mac
303 oper['matchDstMac'] = args.match_dst_mac
304 intents.append(oper)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700305
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700306 url = "http://%s:%s/wm/onos/intent/high" % (self.onos_ip, self.onos_port)
307 result = post_json(url, intents)
308 # NOTE: No need to print the response
309 # if len(result) != 0:
310 # self.print_json_result(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700311
Pavlin Radoslavov308337c2014-06-11 10:25:44 -0700312 def show_host_all(self, args):
313 "CLI command callback: show host all"
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700314
Pavlin Radoslavov308337c2014-06-11 10:25:44 -0700315 url = "http://%s:%s/wm/onos/topology/hosts" % (self.onos_ip, self.onos_port)
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700316 result = get_json(url)
317 self.print_json_result(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700318
319 def show_intent_high(self, args):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700320 "CLI command callback: show intent high"
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700321
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700322 if args.intent_id is None:
323 # Show all intents
324 url = "http://%s:%s/wm/onos/intent/high" % (self.onos_ip, self.onos_port)
325 else:
326 # Show a single intent
327 url = "http://%s:%s/wm/onos/intent/high/%s" % (self.onos_ip, self.onos_port, args.intent_id)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700328
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700329 result = get_json(url)
330 self.print_json_result(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700331
332 def show_intent_low(self, args):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700333 "CLI command callback: show intent low"
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700334
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700335 if args.intent_id is None:
336 # Show all intents
337 url = "http://%s:%s/wm/onos/intent/low" % (self.onos_ip, self.onos_port)
338 else:
339 # Show a single intent
340 url = "http://%s:%s/wm/onos/intent/low/%s" % (self.onos_ip, self.onos_port, args.intent_id)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700341
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700342 result = get_json(url)
343 self.print_json_result(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700344
345 def show_link_all(self, args):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700346 "CLI command callback: show link all"
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700347
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700348 url = "http://%s:%s/wm/onos/topology/links" % (self.onos_ip, self.onos_port)
349 result = get_json(url)
350 #
351 if (self.output_format == "json"):
352 self.print_json_result(result)
353 else:
354 # NOTE: The code below is for demo purpose only how to
355 # decode and print the links in text format. It will be
356 # reimplemented in the future.
357 links = result
358 print "# src_dpid src_port -> dst_dpid dst_port"
359 for v in sorted(links, key=lambda x: x['src-switch']):
360 if v.has_key('dst-switch'):
361 dst_dpid = str(v['dst-switch'])
362 if v.has_key('src-switch'):
363 src_dpid = str(v['src-switch'])
364 if v.has_key('src-port'):
365 src_port = str(v['src-port'])
366 if v.has_key('dst-port'):
367 dst_port = str(v['dst-port'])
368 self.print_result("%s %s -> %s %s" % (src_dpid, src_port, dst_dpid, dst_port))
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700369
Pavlin Radoslavov6cc3f742014-07-09 18:04:04 -0700370 def show_metrics(self, args):
371 "CLI command callback: show metrics"
Ray Milkey4d238292014-07-03 11:07:33 -0700372
Pavlin Radoslavov6cc3f742014-07-09 18:04:04 -0700373 if args.metric_id is None:
374 # Show all metrics
375 url = "http://%s:%s/wm/onos/metrics" % (self.onos_ip, self.onos_port)
376 else:
377 # Show a single metric
378 url = "http://%s:%s/wm/onos/metrics?ids=%s" % (self.onos_ip, self.onos_port, args.metric_id)
379
Ray Milkey4d238292014-07-03 11:07:33 -0700380 result = get_json(url)
Ray Milkey4d238292014-07-03 11:07:33 -0700381 self.print_json_result(result)
382
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700383 def show_path_shortest(self, args):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700384 "CLI command callback: show path shortest"
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700385
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700386 url = "http://%s:%s/wm/onos/intent/path/switch/%s/shortest-path/%s" % (self.onos_ip, self.onos_port, args.src_dpid, args.dst_dpid)
387 result = get_json(url)
388 #
389 self.print_json_result(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700390
391 def show_switch_all(self, args):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700392 "CLI command callback: show switch all"
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700393
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700394 url = "http://%s:%s/wm/onos/topology/switches" % (self.onos_ip, self.onos_port)
395 result = get_json(url)
396 #
397 self.print_json_result(result)
Pavlin Radoslavovcf2b5532014-05-23 18:12:23 -0700398
399 def show_topology_all(self, args):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700400 "CLI command callback: show topology all"
Pavlin Radoslavovcf2b5532014-05-23 18:12:23 -0700401
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700402 url = "http://%s:%s/wm/onos/topology" % (self.onos_ip, self.onos_port)
403 result = get_json(url)
404 #
405 self.print_json_result(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700406
407 #
408 # Implement "delete" top-level command
409 #
410 def do_delete(self, arg):
411 "Top-level 'delete' command"
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700412 self.impl_do_command('delete', arg)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700413 def complete_delete(self, text, line, begidx, endidx):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700414 "Completion of top-level 'delete' command"
415 return self.impl_complete_command('delete', text, line, begidx, endidx)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700416 def help_delete(self):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700417 "Help for top-level 'delete' command"
418 self.impl_help_command('delete')
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700419
420 #
421 # Implement "set" top-level command
422 #
423 def do_set(self, arg):
424 "Top-level 'set' command"
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700425 self.impl_do_command('set', arg)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700426 def complete_set(self, text, line, begidx, endidx):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700427 "Completion of top-level 'set' command"
428 return self.impl_complete_command('set', text, line, begidx, endidx)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700429 def help_set(self):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700430 "Help for top-level 'set' command"
431 self.impl_help_command('set')
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700432
433 #
434 # Implement "show" top-level command
435 #
436 def do_show(self, arg):
437 "Top-level 'show' command"
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700438 self.impl_do_command('show', arg)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700439 def complete_show(self, text, line, begidx, endidx):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700440 "Completion of top-level 'show' command"
441 return self.impl_complete_command('show', text, line, begidx, endidx)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700442 def help_show(self):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700443 "Help for top-level 'show' command"
444 self.impl_help_command('show')
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700445
446 #
447 # Implement the "do_something" top-level command execution
448 #
449 def impl_do_command(self, root_name, arg):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700450 "Implementation of top-level 'do_something' command execution"
451 parser = self.parsers_dict[root_name]
452 parsed_args = parser.parse_args(arg.split())
453 parsed_args.func(parsed_args)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700454
455 #
456 # Implement the "complete_something" top-level command completion
457 #
458 def impl_complete_command(self, root_name, text, line, begidx, endidx):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700459 "Implementation of top-level 'complete_something' command completion"
460 root_command = self.commands_dict[root_name]
461 subtree_commands = self.collect_subtree_commands(root_command)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700462
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700463 #
464 # Loop through the commands and add their portion
465 # of the sub-name to the list of completions.
466 #
467 # NOTE: We add a command only if it has a callback.
468 #
469 completions = []
470 for c in subtree_commands:
471 if c.callback is None:
472 continue
473 name = c.split_name[len(root_command.split_name):]
474 completions.append(' '.join(name))
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700475
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700476 mline = line.partition(" ")[2]
477 offs = len(mline) - len(text)
478 return [s[offs:] for s in completions if s.startswith(mline)]
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700479
480 #
481 # Implement the "help_something" top-level command help
482 #
483 def impl_help_command(self, root_name):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700484 "Implementation of top-level 'help_something' command help"
485 root_command = self.commands_dict[root_name]
486 subtree_commands = self.collect_subtree_commands(root_command)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700487
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700488 #
489 # Loop through the commands and print the help for each command.
490 # NOTE: We add a command only if it has a callback.
491 #
492 print "Help for the `%s` command:" % (root_name)
493 for c in subtree_commands:
494 if c.callback is None:
495 continue
496 print " {0:30}{1:30}".format(c.name, c.help)
497 # if c.init_arg_parser is not None:
498 # parser = self.parsers_dict[c.name]
499 # parser.print_help()
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700500
501 #
502 # Traverse (breadth-first) a subtree and return all nodes except the
503 # root node.
504 #
505 def collect_subtree_commands(self, root_command):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700506 """Collect a subtree of commands.
Pavlin Radoslavov6cc3f742014-07-09 18:04:04 -0700507 Traverses (depth-first) a subtree of commands and returns
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700508 all nodes except the root node."""
509
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700510 commands = []
511 subtree_commands = []
Pavlin Radoslavov6cc3f742014-07-09 18:04:04 -0700512 # Use depth-first to traverse the subtree
513 for c in root_command.children:
514 commands.append(c)
515 subtree_commands = self.collect_subtree_commands(c)
516 if len(subtree_commands):
517 commands.extend(subtree_commands)
518 return commands
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700519
520 def log_debug(self, msg):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700521 """Log debug information.
522 msg: the message to log
523 Use the following CLI commands to enable/disable debugging:
524 paramset debug true
525 paramset debug false
526 """
527 if self.debug:
528 print "%s" % (msg)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700529
530 def print_json_result(self, json_result):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700531 """Print JSON result."""
532 if len(json_result) == 0:
533 return
534 result = json.dumps(json_result, indent=4)
535 self.print_result(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700536
537 def print_result(self, result):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700538 """Print parsed result."""
539 print "%s" % (result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700540
541 #
542 # Implementation of the "paramshow" CLI command.
543 #
544 # NOTE: The do_paramshow implementation below is copied from
545 # the cmd2.do_show() implementation
546 #
547 @options([make_option('-l', '--long', action="store_true",
548 help="describe function of parameter")])
549 def do_paramshow(self, arg, opts):
550 '''Shows value of a parameter.'''
551 param = arg.strip().lower()
552 result = {}
553 maxlen = 0
554 for p in self.settable:
555 if (not param) or p.startswith(param):
556 result[p] = '%s: %s' % (p, str(getattr(self, p)))
557 maxlen = max(maxlen, len(result[p]))
558 if result:
559 for p in sorted(result):
560 if opts.long:
561 self.poutput('%s # %s' % (result[p].ljust(maxlen), self.settable[p]))
562 else:
563 self.poutput(result[p])
564 else:
565 raise NotImplementedError("Parameter '%s' not supported (type 'show' for list of parameters)." % param)
566
567 #
568 # Implementation of the "paramset" CLI command.
569 #
570 #
571 # NOTE: The do_paramset implementation below is copied from
572 # the cmd2.do_set() implementation (with minor modifications).
573 #
574 def do_paramset(self, arg):
575 '''
576 Sets a cmd2 parameter. Accepts abbreviated parameter names so long
577 as there is no ambiguity. Call without arguments for a list of
578 settable parameters with their values.'''
579
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700580 class NotSettableError(Exception):
581 pass
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700582
583 try:
584 statement, paramName, val = arg.parsed.raw.split(None, 2)
585 val = val.strip()
586 paramName = paramName.strip().lower()
587 if paramName not in self.settable:
588 hits = [p for p in self.settable if p.startswith(paramName)]
589 if len(hits) == 1:
590 paramName = hits[0]
591 else:
592 return self.do_paramshow(paramName)
593 currentVal = getattr(self, paramName)
594 if (val[0] == val[-1]) and val[0] in ("'", '"'):
595 val = val[1:-1]
596 else:
597 val = cmd2.cast(currentVal, val)
598 setattr(self, paramName, val)
599 self.stdout.write('%s - was: %s\nnow: %s\n' % (paramName, currentVal, val))
600 if currentVal != val:
601 try:
602 onchange_hook = getattr(self, '_onchange_%s' % paramName)
603 onchange_hook(old=currentVal, new=val)
604 except AttributeError:
605 pass
606 except (ValueError, AttributeError, NotSettableError) as exc:
607 self.do_paramshow(arg)
608
609
610def get_json(url):
611 """Make a REST GET call and return the JSON result
612 url: the URL to call"""
613
Pavlin Radoslavovcf2b5532014-05-23 18:12:23 -0700614 parsed_result = []
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700615 try:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700616 response = urllib2.urlopen(url)
617 result = response.read()
618 response.close()
619 if len(result) != 0:
620 parsed_result = json.loads(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700621 except HTTPError as exc:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700622 print "ERROR:"
623 print " REST GET URL: %s" % url
624 # NOTE: exc.fp contains the object with the response payload
625 error_payload = json.loads(exc.fp.read())
626 print " REST Error Code: %s" % (error_payload['code'])
627 print " REST Error Summary: %s" % (error_payload['summary'])
628 print " REST Error Description: %s" % (error_payload['formattedDescription'])
629 print " HTTP Error Code: %s" % exc.code
630 print " HTTP Error Reason: %s" % exc.reason
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700631 except URLError as exc:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700632 print "ERROR:"
633 print " REST GET URL: %s" % url
634 print " URL Error Reason: %s" % exc.reason
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700635 return parsed_result
636
637def post_json(url, data):
638 """Make a REST POST call and return the JSON result
639 url: the URL to call
640 data: the data to POST"""
641
Pavlin Radoslavovcf2b5532014-05-23 18:12:23 -0700642 parsed_result = []
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700643 data_json = json.dumps(data)
644 try:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700645 request = urllib2.Request(url, data_json)
646 request.add_header("Content-Type", "application/json")
647 response = urllib2.urlopen(request)
648 result = response.read()
649 response.close()
650 if len(result) != 0:
651 parsed_result = json.loads(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700652 except HTTPError as exc:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700653 print "ERROR:"
654 print " REST POST URL: %s" % url
655 # NOTE: exc.fp contains the object with the response payload
656 error_payload = json.loads(exc.fp.read())
657 print " REST Error Code: %s" % (error_payload['code'])
658 print " REST Error Summary: %s" % (error_payload['summary'])
659 print " REST Error Description: %s" % (error_payload['formattedDescription'])
660 print " HTTP Error Code: %s" % exc.code
661 print " HTTP Error Reason: %s" % exc.reason
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700662 except URLError as exc:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700663 print "ERROR:"
664 print " REST POST URL: %s" % url
665 print " URL Error Reason: %s" % exc.reason
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700666 return parsed_result
667
668def delete_json(url):
669 """Make a REST DELETE call and return the JSON result
670 url: the URL to call"""
671
Pavlin Radoslavovcf2b5532014-05-23 18:12:23 -0700672 parsed_result = []
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700673 try:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700674 request = urllib2.Request(url)
675 request.get_method = lambda: 'DELETE'
676 response = urllib2.urlopen(request)
677 result = response.read()
678 response.close()
679 if len(result) != 0:
680 parsed_result = json.loads(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700681 except HTTPError as exc:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700682 print "ERROR:"
683 print " REST DELETE URL: %s" % url
684 # NOTE: exc.fp contains the object with the response payload
685 error_payload = json.loads(exc.fp.read())
686 print " REST Error Code: %s" % (error_payload['code'])
687 print " REST Error Summary: %s" % (error_payload['summary'])
688 print " REST Error Description: %s" % (error_payload['formattedDescription'])
689 print " HTTP Error Code: %s" % exc.code
690 print " HTTP Error Reason: %s" % exc.reason
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700691 except URLError as exc:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700692 print "ERROR:"
693 print " REST DELETE URL: %s" % url
694 print " URL Error Reason: %s" % exc.reason
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700695 return parsed_result
696
697if __name__ == '__main__':
698 onosCli = OnosCli()
699
700 # Setup the parser
701 parser = argparse.ArgumentParser()
702 parser.add_argument('-c', '--command', nargs=argparse.REMAINDER,
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700703 help="Run arguments to the end of the line as a CLI command")
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700704 parser.add_argument('--onos-ip',
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700705 help="Set the ONOS IP address (for REST calls)")
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700706 parser.add_argument('--onos-port',
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700707 help="Set the ONOS port number (for REST calls)")
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700708 parser.add_argument('-t', '--test', nargs='+',
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700709 help="Test against transcript(s) in FILE (wildcards OK)")
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700710
711 # Parse the arguments
712 parsed_args = parser.parse_args()
713 if parsed_args.onos_ip:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700714 onosCli.onos_ip = parsed_args.onos_ip
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700715 if parsed_args.onos_port:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700716 onosCli.onos_port = parsed_args.onos_port
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700717 #
718 # NOTE: We have to reset the command-line options so the Cmd2 parser
719 # doesn't process them again.
720 #
721 sys.argv = [sys.argv[0]]
722
723 # Run the CLI as appropriate
724 if parsed_args.test:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700725 # Run CLI Transcript Tests
726 onosCli.runTranscriptTests(parsed_args.test)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700727 elif parsed_args.command:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700728 # Run arguments as a CLI command
729 command_line = ' '.join(parsed_args.command)
730 onosCli.onecmd(command_line)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700731 else:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700732 # Run interactive CLI
733 onosCli.cmdloop()