blob: 20f9bc76f3eb5b5f60c19f3e2432fcb008790c88 [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
24# On older Ubuntu installations (e.g., Ubuntu-12.04), install also:
25# sudo apt-get install python-pyparsing
26#
27
28#
29# ADDING A NEW CLI COMMAND:
30# 1. Add the appropriate Command entry (or entries) inside array
31# OnosCli.init_commands.
32# See the comments for init_commands for details.
33# 2. Add the appropriate callback (inside class OnosCli) for the CLI command
34#
35#
36
37import sys
38import argparse
39import json
40from optparse import make_option
41import urllib2
42from urllib2 import URLError, HTTPError
43import cmd2
44from cmd2 import Cmd
45from cmd2 import options
46
47
48class Command():
49 "Command node. A hierarchy of nodes are organized in a command tree."
50
51 def __init__(self, name, help, callback=None, add_parser_args=None):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -070052 """name: a string with the full command name
53 help: the help string for the command
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -070054 callback: the method to be called if the command is executed
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -070055 add_parser_args: the parser arguments to add to the command: a dictionary of argparse arguments"""
56 # Normalize the name by removing extra spaces
57 self.split_name = name.split() # List of the words in the name
58 self.name = ' '.join(self.split_name) # Normalized name
59 self.last_subname = self.split_name[-1] # Last word in the name
60 self.parent_name = ' '.join(self.split_name[:-1]) # Name of parent command
61 self.help = help # THe help string
62 self.callback = callback # The command callback
63 self.add_parser_args = add_parser_args # Parser arguments to add
64 self.parent = None # The parent Command
65 self.children = [] # The children Command entries
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -070066
67
68class OnosCli(Cmd):
69 "The main ONOS CLI class"
70
71 # Setup generic CLI fields
72 intro = "Welcome to the ONOS CLI. Type help or ? to list commands.\n"
73 prompt = "(onos) "
74 settable = Cmd.settable + "prompt CLI prompt"
75
76 # Setup ONOS-specific fields
77 onos_ip = "127.0.0.1"
78 settable = settable + "onos_ip ONOS IP address"
79 onos_port = 8080
80 settable = settable + "onos_port ONOS REST port number"
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -070081 output_format = "json" # Valid values: json, text
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -070082 settable = settable + "output_format The output format: `text` or `json`"
83
84 # Collection of commands sorted by the level in the CLI command hierarchy
85 commands = []
86 # Commands, parsers and subparsers keyed by the command name
87 commands_dict = {}
88 parsers_dict = {}
89 subparsers_dict = {}
90
91 def __init__(self):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -070092 Cmd.__init__(self)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -070093
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -070094 #
95 # An array of the ONOS-specific CLI commands.
96 # Each entry is a Command instance, and must have at least
97 # two arguments:
98 # * Command name as typed on the CLI. E.g.:
99 # "show intent"
100 # * Command help description. E.g.:
101 # "Show intents"
102 #
103 # Executable commands should have a third Command argument, which is
104 # the name of the method to be called when the command is executed.
105 # The method will be called with the (argparse) parsed arguments
106 # for that command.
107 #
108 # If an executable command takes arguments, those should be described
109 # in the Command's fourth argument. It is a list of pairs:
110 # [
111 # ("--argument-name1", dict(...)),
112 # ("--argument-name2", dict(...)),
113 # ...
114 # ]
115 # where the first entry in the pair is the argument name, and the
116 # second entry in the pair is a dictionary with argparse-specific
117 # description of the argument.
118 #
119 init_commands = [
120 Command("delete", "Delete command"),
121 #
122 Command("delete intent",
123 """Delete high-level intents
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700124 Usage:
125 delete intent --intent-id INTENT_ID Delete a high-level intent
126 delete intent --all Delete all high-level intents
127 Arguments:
128 --intent-id INTENT_ID The Intent ID (an integer)
129 --all Delete all high-level intents""",
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700130 self.delete_intent,
131 [
132 ("--intent-id", dict(required=False, type=int)),
133 ("--all", dict(required=False, action='store_true'))
134 ]
135 ),
136 #
137 Command("set", "Set command"),
138 #
139 Command("set intent",
140 """Set a high-level intent
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700141 Usage:
142 set intent <ARGS>
143 Arguments:
144 --intent-id INTENT_ID The Intent ID (an integer) (REQUIRED)
145 --src-dpid SRC_DPID Source Switch DPID (REQUIRED)
146 --src-port SRC_PORT Source Switch Port (REQUIRED)
147 --dst-dpid DST_DPID Destination Switch DPID (REQUIRED)
148 --dst-port DST_PORT Destination Switch Port (REQUIRED)
149 --match-src-mac MATCH_SRC_MAC Matching Source MAC Address (REQUIRED)
150 --match-dst-mac MATCH_DST_MAC Matching Destination MAC Address (REQUIRED)""",
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700151 self.set_intent,
152 [
153 ("--intent-id", dict(required=True, type=int)),
154 ("--src-dpid", dict(required=True)),
155 ("--src-port", dict(required=True, type=int)),
156 ("--dst-dpid", dict(required=True)),
157 ("--dst-port", dict(required=True, type=int)),
158 ("--match-src-mac", dict(required=True)),
159 ("--match-dst-mac", dict(required=True))
160 ]),
161 #
162 Command("show", "Show command"),
163 #
164 Command("show device", "Show devices"),
165 #
166 Command("show device all", "Show all devices", self.show_device_all),
167 #
168 Command("show intent", "Show intents"),
169 #
170 Command("show intent high",
171 """Show all high-level intents
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700172 show intent high --intent-id INTENT_ID Show a high-level intent""",
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700173 self.show_intent_high,
174 [
175 ("--intent-id", dict(required=False, type=int))
176 ]
177 ),
178 #
179 Command("show intent low",
180 """Show all low-level intents
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700181 show intent low --intent-id INTENT_ID Show a low-level intent""",
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700182 self.show_intent_low,
183 [
184 ("--intent-id", dict(required=False))
185 ]
186 ),
187 #
188 Command("show link", "Show links"),
189 #
190 Command("show link all", "Show all links", self.show_link_all),
191 #
192 Command("show path", "Show a path"),
193 #
194 Command("show path shortest",
195 """Show a shortest path
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700196 Usage:
197 show path shortest --src-dpid SRC_DPID --dst-dpid DST_DPID
198 Arguments:
199 --src-dpid SRC_DPID Source Switch DPID
200 --dst-dpid DST_DPID Destination Switch DPID""",
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700201 self.show_path_shortest,
202 [
203 ("--src-dpid", dict(required=True)),
204 ("--dst-dpid", dict(required=True))
205 ]),
206 #
207 Command("show switch", "Show switches"),
208 #
209 Command("show switch all", "Show all switches", self.show_switch_all),
210 #
211 Command("show topology", "Show network topology"),
212 #
213 Command("show topology all", "Show whole network topology", self.show_topology_all)
214 ]
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700215
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700216 # Sort the commands by the level in the CLI command hierarchy
217 self.commands = sorted(init_commands, key = lambda c: len(c.name.split()))
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700218
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700219 # Create a dictionary with all commands: name -> Command
220 for c in self.commands:
221 self.commands_dict[c.name] = c
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700222
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700223 # Create a tree with all commands
224 for c in self.commands:
225 if c.parent_name:
226 pc = self.commands_dict[c.parent_name]
227 pc.children.append(c)
228 c.parent = pc
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700229
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700230 # Create the parsers and the sub-parsers
231 for c in self.commands:
232 # Add a parser
233 parser = None
234 if c.parent is None:
235 # Add a top-level parser
236 parser = argparse.ArgumentParser(description=c.help,
237 prog=c.name,
238 add_help=False)
239 else:
240 # Add a parser from the parent's subparser
241 parent_subparser = self.subparsers_dict[c.parent_name]
242 parser = parent_subparser.add_parser(c.last_subname,
243 help=c.help,
244 add_help=False)
245 self.parsers_dict[c.name] = parser
246 # Add a sub-parser
247 if c.children:
248 subparser = parser.add_subparsers(help=c.help)
249 self.subparsers_dict[c.name] = subparser
250 # Setup the callback
251 if c.callback is not None:
252 parser.set_defaults(func=c.callback)
253 # Init the argument parser
254 if c.add_parser_args is not None:
255 for a in c.add_parser_args:
256 (p1, p2) = a
257 parser.add_argument(p1, **p2)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700258
259 def delete_intent(self, args):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700260 "CLI command callback: delete intent"
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700261
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700262 url = ""
263 if args.all:
264 # Delete all intents
265 url = "http://%s:%s/wm/onos/intent/high" % (self.onos_ip, self.onos_port)
266 else:
267 if args.intent_id is None:
268 print "*** Unknown syntax:"
269 self.help_delete()
270 return;
271 # Delete an intent
272 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 -0700273
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700274 result = delete_json(url)
275 # NOTE: No need to print the response
276 # if len(result) != 0:
277 # self.print_json_result(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700278
279 def set_intent(self, args):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700280 "CLI command callback: set intent"
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700281
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700282 intents = []
283 oper = {}
284 # Create the POST payload
285 oper['intentId'] = args.intent_id
286 oper['intentType'] = 'CONSTRAINED_SHORTEST_PATH' # XXX: Hardcoded
287 oper['staticPath'] = False # XXX: Hardcoded
288 oper['srcSwitchDpid'] = args.src_dpid
289 oper['srcSwitchPort'] = args.src_port
290 oper['dstSwitchDpid'] = args.dst_dpid
291 oper['dstSwitchPort'] = args.dst_port
292 oper['matchSrcMac'] = args.match_src_mac
293 oper['matchDstMac'] = args.match_dst_mac
294 intents.append(oper)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700295
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700296 url = "http://%s:%s/wm/onos/intent/high" % (self.onos_ip, self.onos_port)
297 result = post_json(url, intents)
298 # NOTE: No need to print the response
299 # if len(result) != 0:
300 # self.print_json_result(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700301
302 def show_device_all(self, args):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700303 "CLI command callback: show device all"
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700304
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700305 url = "http://%s:%s/wm/onos/topology/devices" % (self.onos_ip, self.onos_port)
306 result = get_json(url)
307 self.print_json_result(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700308
309 def show_intent_high(self, args):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700310 "CLI command callback: show intent high"
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700311
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700312 if args.intent_id is None:
313 # Show all intents
314 url = "http://%s:%s/wm/onos/intent/high" % (self.onos_ip, self.onos_port)
315 else:
316 # Show a single intent
317 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 -0700318
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700319 result = get_json(url)
320 self.print_json_result(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700321
322 def show_intent_low(self, args):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700323 "CLI command callback: show intent low"
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700324
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700325 if args.intent_id is None:
326 # Show all intents
327 url = "http://%s:%s/wm/onos/intent/low" % (self.onos_ip, self.onos_port)
328 else:
329 # Show a single intent
330 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 -0700331
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700332 result = get_json(url)
333 self.print_json_result(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700334
335 def show_link_all(self, args):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700336 "CLI command callback: show link all"
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700337
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700338 url = "http://%s:%s/wm/onos/topology/links" % (self.onos_ip, self.onos_port)
339 result = get_json(url)
340 #
341 if (self.output_format == "json"):
342 self.print_json_result(result)
343 else:
344 # NOTE: The code below is for demo purpose only how to
345 # decode and print the links in text format. It will be
346 # reimplemented in the future.
347 links = result
348 print "# src_dpid src_port -> dst_dpid dst_port"
349 for v in sorted(links, key=lambda x: x['src-switch']):
350 if v.has_key('dst-switch'):
351 dst_dpid = str(v['dst-switch'])
352 if v.has_key('src-switch'):
353 src_dpid = str(v['src-switch'])
354 if v.has_key('src-port'):
355 src_port = str(v['src-port'])
356 if v.has_key('dst-port'):
357 dst_port = str(v['dst-port'])
358 self.print_result("%s %s -> %s %s" % (src_dpid, src_port, dst_dpid, dst_port))
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700359
360 def show_path_shortest(self, args):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700361 "CLI command callback: show path shortest"
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700362
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700363 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)
364 result = get_json(url)
365 #
366 self.print_json_result(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700367
368 def show_switch_all(self, args):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700369 "CLI command callback: show switch all"
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700370
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700371 url = "http://%s:%s/wm/onos/topology/switches" % (self.onos_ip, self.onos_port)
372 result = get_json(url)
373 #
374 self.print_json_result(result)
Pavlin Radoslavovcf2b5532014-05-23 18:12:23 -0700375
376 def show_topology_all(self, args):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700377 "CLI command callback: show topology all"
Pavlin Radoslavovcf2b5532014-05-23 18:12:23 -0700378
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700379 url = "http://%s:%s/wm/onos/topology" % (self.onos_ip, self.onos_port)
380 result = get_json(url)
381 #
382 self.print_json_result(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700383
384 #
385 # Implement "delete" top-level command
386 #
387 def do_delete(self, arg):
388 "Top-level 'delete' command"
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700389 self.impl_do_command('delete', arg)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700390 def complete_delete(self, text, line, begidx, endidx):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700391 "Completion of top-level 'delete' command"
392 return self.impl_complete_command('delete', text, line, begidx, endidx)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700393 def help_delete(self):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700394 "Help for top-level 'delete' command"
395 self.impl_help_command('delete')
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700396
397 #
398 # Implement "set" top-level command
399 #
400 def do_set(self, arg):
401 "Top-level 'set' command"
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700402 self.impl_do_command('set', arg)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700403 def complete_set(self, text, line, begidx, endidx):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700404 "Completion of top-level 'set' command"
405 return self.impl_complete_command('set', text, line, begidx, endidx)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700406 def help_set(self):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700407 "Help for top-level 'set' command"
408 self.impl_help_command('set')
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700409
410 #
411 # Implement "show" top-level command
412 #
413 def do_show(self, arg):
414 "Top-level 'show' command"
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700415 self.impl_do_command('show', arg)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700416 def complete_show(self, text, line, begidx, endidx):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700417 "Completion of top-level 'show' command"
418 return self.impl_complete_command('show', text, line, begidx, endidx)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700419 def help_show(self):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700420 "Help for top-level 'show' command"
421 self.impl_help_command('show')
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700422
423 #
424 # Implement the "do_something" top-level command execution
425 #
426 def impl_do_command(self, root_name, arg):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700427 "Implementation of top-level 'do_something' command execution"
428 parser = self.parsers_dict[root_name]
429 parsed_args = parser.parse_args(arg.split())
430 parsed_args.func(parsed_args)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700431
432 #
433 # Implement the "complete_something" top-level command completion
434 #
435 def impl_complete_command(self, root_name, text, line, begidx, endidx):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700436 "Implementation of top-level 'complete_something' command completion"
437 root_command = self.commands_dict[root_name]
438 subtree_commands = self.collect_subtree_commands(root_command)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700439
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700440 #
441 # Loop through the commands and add their portion
442 # of the sub-name to the list of completions.
443 #
444 # NOTE: We add a command only if it has a callback.
445 #
446 completions = []
447 for c in subtree_commands:
448 if c.callback is None:
449 continue
450 name = c.split_name[len(root_command.split_name):]
451 completions.append(' '.join(name))
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700452
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700453 mline = line.partition(" ")[2]
454 offs = len(mline) - len(text)
455 return [s[offs:] for s in completions if s.startswith(mline)]
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700456
457 #
458 # Implement the "help_something" top-level command help
459 #
460 def impl_help_command(self, root_name):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700461 "Implementation of top-level 'help_something' command help"
462 root_command = self.commands_dict[root_name]
463 subtree_commands = self.collect_subtree_commands(root_command)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700464
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700465 #
466 # Loop through the commands and print the help for each command.
467 # NOTE: We add a command only if it has a callback.
468 #
469 print "Help for the `%s` command:" % (root_name)
470 for c in subtree_commands:
471 if c.callback is None:
472 continue
473 print " {0:30}{1:30}".format(c.name, c.help)
474 # if c.init_arg_parser is not None:
475 # parser = self.parsers_dict[c.name]
476 # parser.print_help()
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700477
478 #
479 # Traverse (breadth-first) a subtree and return all nodes except the
480 # root node.
481 #
482 def collect_subtree_commands(self, root_command):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700483 """Collect a subtree of commands.
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700484 Traverses (breadth-first) a subtree of commands and returns
485 all nodes except the root node."""
486
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700487 commands = []
488 subtree_commands = []
489 commands.append(root_command)
490 # Use breadth-first to traverse the subtree
491 while commands:
492 pc = commands.pop(0)
493 for c in pc.children:
494 commands.append(c)
495 subtree_commands.append(c)
496 return subtree_commands
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700497
498 def log_debug(self, msg):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700499 """Log debug information.
500 msg: the message to log
501 Use the following CLI commands to enable/disable debugging:
502 paramset debug true
503 paramset debug false
504 """
505 if self.debug:
506 print "%s" % (msg)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700507
508 def print_json_result(self, json_result):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700509 """Print JSON result."""
510 if len(json_result) == 0:
511 return
512 result = json.dumps(json_result, indent=4)
513 self.print_result(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700514
515 def print_result(self, result):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700516 """Print parsed result."""
517 print "%s" % (result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700518
519 #
520 # Implementation of the "paramshow" CLI command.
521 #
522 # NOTE: The do_paramshow implementation below is copied from
523 # the cmd2.do_show() implementation
524 #
525 @options([make_option('-l', '--long', action="store_true",
526 help="describe function of parameter")])
527 def do_paramshow(self, arg, opts):
528 '''Shows value of a parameter.'''
529 param = arg.strip().lower()
530 result = {}
531 maxlen = 0
532 for p in self.settable:
533 if (not param) or p.startswith(param):
534 result[p] = '%s: %s' % (p, str(getattr(self, p)))
535 maxlen = max(maxlen, len(result[p]))
536 if result:
537 for p in sorted(result):
538 if opts.long:
539 self.poutput('%s # %s' % (result[p].ljust(maxlen), self.settable[p]))
540 else:
541 self.poutput(result[p])
542 else:
543 raise NotImplementedError("Parameter '%s' not supported (type 'show' for list of parameters)." % param)
544
545 #
546 # Implementation of the "paramset" CLI command.
547 #
548 #
549 # NOTE: The do_paramset implementation below is copied from
550 # the cmd2.do_set() implementation (with minor modifications).
551 #
552 def do_paramset(self, arg):
553 '''
554 Sets a cmd2 parameter. Accepts abbreviated parameter names so long
555 as there is no ambiguity. Call without arguments for a list of
556 settable parameters with their values.'''
557
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700558 class NotSettableError(Exception):
559 pass
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700560
561 try:
562 statement, paramName, val = arg.parsed.raw.split(None, 2)
563 val = val.strip()
564 paramName = paramName.strip().lower()
565 if paramName not in self.settable:
566 hits = [p for p in self.settable if p.startswith(paramName)]
567 if len(hits) == 1:
568 paramName = hits[0]
569 else:
570 return self.do_paramshow(paramName)
571 currentVal = getattr(self, paramName)
572 if (val[0] == val[-1]) and val[0] in ("'", '"'):
573 val = val[1:-1]
574 else:
575 val = cmd2.cast(currentVal, val)
576 setattr(self, paramName, val)
577 self.stdout.write('%s - was: %s\nnow: %s\n' % (paramName, currentVal, val))
578 if currentVal != val:
579 try:
580 onchange_hook = getattr(self, '_onchange_%s' % paramName)
581 onchange_hook(old=currentVal, new=val)
582 except AttributeError:
583 pass
584 except (ValueError, AttributeError, NotSettableError) as exc:
585 self.do_paramshow(arg)
586
587
588def get_json(url):
589 """Make a REST GET call and return the JSON result
590 url: the URL to call"""
591
Pavlin Radoslavovcf2b5532014-05-23 18:12:23 -0700592 parsed_result = []
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700593 try:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700594 response = urllib2.urlopen(url)
595 result = response.read()
596 response.close()
597 if len(result) != 0:
598 parsed_result = json.loads(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700599 except HTTPError as exc:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700600 print "ERROR:"
601 print " REST GET URL: %s" % url
602 # NOTE: exc.fp contains the object with the response payload
603 error_payload = json.loads(exc.fp.read())
604 print " REST Error Code: %s" % (error_payload['code'])
605 print " REST Error Summary: %s" % (error_payload['summary'])
606 print " REST Error Description: %s" % (error_payload['formattedDescription'])
607 print " HTTP Error Code: %s" % exc.code
608 print " HTTP Error Reason: %s" % exc.reason
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700609 except URLError as exc:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700610 print "ERROR:"
611 print " REST GET URL: %s" % url
612 print " URL Error Reason: %s" % exc.reason
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700613 return parsed_result
614
615def post_json(url, data):
616 """Make a REST POST call and return the JSON result
617 url: the URL to call
618 data: the data to POST"""
619
Pavlin Radoslavovcf2b5532014-05-23 18:12:23 -0700620 parsed_result = []
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700621 data_json = json.dumps(data)
622 try:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700623 request = urllib2.Request(url, data_json)
624 request.add_header("Content-Type", "application/json")
625 response = urllib2.urlopen(request)
626 result = response.read()
627 response.close()
628 if len(result) != 0:
629 parsed_result = json.loads(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700630 except HTTPError as exc:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700631 print "ERROR:"
632 print " REST POST URL: %s" % url
633 # NOTE: exc.fp contains the object with the response payload
634 error_payload = json.loads(exc.fp.read())
635 print " REST Error Code: %s" % (error_payload['code'])
636 print " REST Error Summary: %s" % (error_payload['summary'])
637 print " REST Error Description: %s" % (error_payload['formattedDescription'])
638 print " HTTP Error Code: %s" % exc.code
639 print " HTTP Error Reason: %s" % exc.reason
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700640 except URLError as exc:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700641 print "ERROR:"
642 print " REST POST URL: %s" % url
643 print " URL Error Reason: %s" % exc.reason
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700644 return parsed_result
645
646def delete_json(url):
647 """Make a REST DELETE call and return the JSON result
648 url: the URL to call"""
649
Pavlin Radoslavovcf2b5532014-05-23 18:12:23 -0700650 parsed_result = []
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700651 try:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700652 request = urllib2.Request(url)
653 request.get_method = lambda: 'DELETE'
654 response = urllib2.urlopen(request)
655 result = response.read()
656 response.close()
657 if len(result) != 0:
658 parsed_result = json.loads(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700659 except HTTPError as exc:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700660 print "ERROR:"
661 print " REST DELETE URL: %s" % url
662 # NOTE: exc.fp contains the object with the response payload
663 error_payload = json.loads(exc.fp.read())
664 print " REST Error Code: %s" % (error_payload['code'])
665 print " REST Error Summary: %s" % (error_payload['summary'])
666 print " REST Error Description: %s" % (error_payload['formattedDescription'])
667 print " HTTP Error Code: %s" % exc.code
668 print " HTTP Error Reason: %s" % exc.reason
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700669 except URLError as exc:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700670 print "ERROR:"
671 print " REST DELETE URL: %s" % url
672 print " URL Error Reason: %s" % exc.reason
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700673 return parsed_result
674
675if __name__ == '__main__':
676 onosCli = OnosCli()
677
678 # Setup the parser
679 parser = argparse.ArgumentParser()
680 parser.add_argument('-c', '--command', nargs=argparse.REMAINDER,
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700681 help="Run arguments to the end of the line as a CLI command")
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700682 parser.add_argument('--onos-ip',
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700683 help="Set the ONOS IP address (for REST calls)")
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700684 parser.add_argument('--onos-port',
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700685 help="Set the ONOS port number (for REST calls)")
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700686 parser.add_argument('-t', '--test', nargs='+',
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700687 help="Test against transcript(s) in FILE (wildcards OK)")
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700688
689 # Parse the arguments
690 parsed_args = parser.parse_args()
691 if parsed_args.onos_ip:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700692 onosCli.onos_ip = parsed_args.onos_ip
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700693 if parsed_args.onos_port:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700694 onosCli.onos_port = parsed_args.onos_port
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700695 #
696 # NOTE: We have to reset the command-line options so the Cmd2 parser
697 # doesn't process them again.
698 #
699 sys.argv = [sys.argv[0]]
700
701 # Run the CLI as appropriate
702 if parsed_args.test:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700703 # Run CLI Transcript Tests
704 onosCli.runTranscriptTests(parsed_args.test)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700705 elif parsed_args.command:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700706 # Run arguments as a CLI command
707 command_line = ' '.join(parsed_args.command)
708 onosCli.onecmd(command_line)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700709 else:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700710 # Run interactive CLI
711 onosCli.cmdloop()