blob: 62e3262288b53c97205c38d09f9a447af64e8ac3 [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
Pavlin Radoslavov79649d92014-07-14 14:30:23 -070015 $ ./onoscli -c show switch
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -070016
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 Radoslavov79649d92014-07-14 14:30:23 -0700165 Command("show host", "Show all hosts", self.show_host),
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700166 #
167 Command("show intent", "Show intents"),
168 #
169 Command("show intent high",
170 """Show all high-level intents
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700171 show intent high --intent-id INTENT_ID Show a high-level intent""",
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700172 self.show_intent_high,
173 [
174 ("--intent-id", dict(required=False, type=int))
175 ]
176 ),
177 #
178 Command("show intent low",
179 """Show all low-level intents
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700180 show intent low --intent-id INTENT_ID Show a low-level intent""",
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700181 self.show_intent_low,
182 [
183 ("--intent-id", dict(required=False))
184 ]
185 ),
186 #
Pavlin Radoslavov79649d92014-07-14 14:30:23 -0700187 Command("show link", "Show all links", self.show_link),
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700188 #
Pavlin Radoslavov6cc3f742014-07-09 18:04:04 -0700189 Command("show metrics",
190 """Show all metrics
191 show metrics --metric-id METRIC_ID Show a metric""",
192 self.show_metrics,
193 [
194 ("--metric-id", dict(required=False, type=str)),
195 ]
196 ),
Ray Milkey4d238292014-07-03 11:07:33 -0700197 #
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700198 Command("show path", "Show a path"),
199 #
200 Command("show path shortest",
201 """Show a shortest path
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700202 Usage:
203 show path shortest --src-dpid SRC_DPID --dst-dpid DST_DPID
204 Arguments:
205 --src-dpid SRC_DPID Source Switch DPID
206 --dst-dpid DST_DPID Destination Switch DPID""",
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700207 self.show_path_shortest,
208 [
209 ("--src-dpid", dict(required=True)),
210 ("--dst-dpid", dict(required=True))
211 ]),
212 #
Pavlin Radoslavov79649d92014-07-14 14:30:23 -0700213 Command("show switch", "Show all switches", self.show_switch),
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700214 #
Pavlin Radoslavov79649d92014-07-14 14:30:23 -0700215 Command("show topology", "Show network topology", self.show_topology)
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700216 ]
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700217
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700218 # Sort the commands by the level in the CLI command hierarchy
219 self.commands = sorted(init_commands, key = lambda c: len(c.name.split()))
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700220
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700221 # Create a dictionary with all commands: name -> Command
222 for c in self.commands:
223 self.commands_dict[c.name] = c
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700224
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700225 # Create a tree with all commands
226 for c in self.commands:
227 if c.parent_name:
228 pc = self.commands_dict[c.parent_name]
229 pc.children.append(c)
230 c.parent = pc
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700231
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700232 # Create the parsers and the sub-parsers
233 for c in self.commands:
234 # Add a parser
235 parser = None
236 if c.parent is None:
237 # Add a top-level parser
238 parser = argparse.ArgumentParser(description=c.help,
239 prog=c.name,
240 add_help=False)
241 else:
242 # Add a parser from the parent's subparser
243 parent_subparser = self.subparsers_dict[c.parent_name]
244 parser = parent_subparser.add_parser(c.last_subname,
245 help=c.help,
246 add_help=False)
247 self.parsers_dict[c.name] = parser
248 # Add a sub-parser
249 if c.children:
250 subparser = parser.add_subparsers(help=c.help)
251 self.subparsers_dict[c.name] = subparser
252 # Setup the callback
253 if c.callback is not None:
254 parser.set_defaults(func=c.callback)
255 # Init the argument parser
256 if c.add_parser_args is not None:
257 for a in c.add_parser_args:
258 (p1, p2) = a
259 parser.add_argument(p1, **p2)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700260
261 def delete_intent(self, args):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700262 "CLI command callback: delete intent"
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700263
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700264 url = ""
265 if args.all:
266 # Delete all intents
267 url = "http://%s:%s/wm/onos/intent/high" % (self.onos_ip, self.onos_port)
268 else:
269 if args.intent_id is None:
270 print "*** Unknown syntax:"
271 self.help_delete()
Pavlin Radoslavov6cc3f742014-07-09 18:04:04 -0700272 return
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700273 # Delete an intent
274 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 -0700275
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700276 result = delete_json(url)
277 # NOTE: No need to print the response
278 # if len(result) != 0:
279 # self.print_json_result(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700280
281 def set_intent(self, args):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700282 "CLI command callback: set intent"
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700283
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700284 intents = []
285 oper = {}
286 # Create the POST payload
287 oper['intentId'] = args.intent_id
Pavlin Radoslavova9b78582014-06-06 15:41:32 -0700288 oper['intentType'] = 'SHORTEST_PATH' # XXX: Hardcoded
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700289 oper['staticPath'] = False # XXX: Hardcoded
290 oper['srcSwitchDpid'] = args.src_dpid
291 oper['srcSwitchPort'] = args.src_port
292 oper['dstSwitchDpid'] = args.dst_dpid
293 oper['dstSwitchPort'] = args.dst_port
294 oper['matchSrcMac'] = args.match_src_mac
295 oper['matchDstMac'] = args.match_dst_mac
296 intents.append(oper)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700297
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700298 url = "http://%s:%s/wm/onos/intent/high" % (self.onos_ip, self.onos_port)
299 result = post_json(url, intents)
300 # NOTE: No need to print the response
301 # if len(result) != 0:
302 # self.print_json_result(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700303
Pavlin Radoslavov79649d92014-07-14 14:30:23 -0700304 def show_host(self, args):
305 "CLI command callback: show host"
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700306
Pavlin Radoslavov308337c2014-06-11 10:25:44 -0700307 url = "http://%s:%s/wm/onos/topology/hosts" % (self.onos_ip, self.onos_port)
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700308 result = get_json(url)
309 self.print_json_result(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700310
311 def show_intent_high(self, args):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700312 "CLI command callback: show intent high"
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700313
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700314 if args.intent_id is None:
315 # Show all intents
316 url = "http://%s:%s/wm/onos/intent/high" % (self.onos_ip, self.onos_port)
317 else:
318 # Show a single intent
319 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 -0700320
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700321 result = get_json(url)
322 self.print_json_result(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700323
324 def show_intent_low(self, args):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700325 "CLI command callback: show intent low"
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700326
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700327 if args.intent_id is None:
328 # Show all intents
329 url = "http://%s:%s/wm/onos/intent/low" % (self.onos_ip, self.onos_port)
330 else:
331 # Show a single intent
332 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 -0700333
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700334 result = get_json(url)
335 self.print_json_result(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700336
Pavlin Radoslavov79649d92014-07-14 14:30:23 -0700337 def show_link(self, args):
338 "CLI command callback: show link"
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700339
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700340 url = "http://%s:%s/wm/onos/topology/links" % (self.onos_ip, self.onos_port)
341 result = get_json(url)
342 #
343 if (self.output_format == "json"):
344 self.print_json_result(result)
345 else:
346 # NOTE: The code below is for demo purpose only how to
347 # decode and print the links in text format. It will be
348 # reimplemented in the future.
349 links = result
350 print "# src_dpid src_port -> dst_dpid dst_port"
Pavlin Radoslavov79649d92014-07-14 14:30:23 -0700351 for v in sorted(links, key=lambda x: x['src']['dpid']):
352 src_dpid = str(v['src']['dpid'])
353 src_port = str(v['src']['portNumber'])
354 dst_dpid = str(v['dst']['dpid'])
355 dst_port = str(v['dst']['portNumber'])
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700356 self.print_result("%s %s -> %s %s" % (src_dpid, src_port, dst_dpid, dst_port))
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700357
Pavlin Radoslavov6cc3f742014-07-09 18:04:04 -0700358 def show_metrics(self, args):
359 "CLI command callback: show metrics"
Ray Milkey4d238292014-07-03 11:07:33 -0700360
Pavlin Radoslavov6cc3f742014-07-09 18:04:04 -0700361 if args.metric_id is None:
362 # Show all metrics
363 url = "http://%s:%s/wm/onos/metrics" % (self.onos_ip, self.onos_port)
364 else:
365 # Show a single metric
366 url = "http://%s:%s/wm/onos/metrics?ids=%s" % (self.onos_ip, self.onos_port, args.metric_id)
367
Ray Milkey4d238292014-07-03 11:07:33 -0700368 result = get_json(url)
Ray Milkey4d238292014-07-03 11:07:33 -0700369 self.print_json_result(result)
370
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700371 def show_path_shortest(self, args):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700372 "CLI command callback: show path shortest"
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700373
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700374 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)
375 result = get_json(url)
376 #
377 self.print_json_result(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700378
Pavlin Radoslavov79649d92014-07-14 14:30:23 -0700379 def show_switch(self, args):
380 "CLI command callback: show switch"
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700381
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700382 url = "http://%s:%s/wm/onos/topology/switches" % (self.onos_ip, self.onos_port)
383 result = get_json(url)
384 #
385 self.print_json_result(result)
Pavlin Radoslavovcf2b5532014-05-23 18:12:23 -0700386
Pavlin Radoslavov79649d92014-07-14 14:30:23 -0700387 def show_topology(self, args):
388 "CLI command callback: show topology"
Pavlin Radoslavovcf2b5532014-05-23 18:12:23 -0700389
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700390 url = "http://%s:%s/wm/onos/topology" % (self.onos_ip, self.onos_port)
391 result = get_json(url)
392 #
393 self.print_json_result(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700394
395 #
396 # Implement "delete" top-level command
397 #
398 def do_delete(self, arg):
399 "Top-level 'delete' command"
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700400 self.impl_do_command('delete', arg)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700401 def complete_delete(self, text, line, begidx, endidx):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700402 "Completion of top-level 'delete' command"
403 return self.impl_complete_command('delete', text, line, begidx, endidx)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700404 def help_delete(self):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700405 "Help for top-level 'delete' command"
406 self.impl_help_command('delete')
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700407
408 #
409 # Implement "set" top-level command
410 #
411 def do_set(self, arg):
412 "Top-level 'set' command"
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700413 self.impl_do_command('set', arg)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700414 def complete_set(self, text, line, begidx, endidx):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700415 "Completion of top-level 'set' command"
416 return self.impl_complete_command('set', text, line, begidx, endidx)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700417 def help_set(self):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700418 "Help for top-level 'set' command"
419 self.impl_help_command('set')
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700420
421 #
422 # Implement "show" top-level command
423 #
424 def do_show(self, arg):
425 "Top-level 'show' command"
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700426 self.impl_do_command('show', arg)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700427 def complete_show(self, text, line, begidx, endidx):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700428 "Completion of top-level 'show' command"
429 return self.impl_complete_command('show', text, line, begidx, endidx)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700430 def help_show(self):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700431 "Help for top-level 'show' command"
432 self.impl_help_command('show')
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700433
434 #
435 # Implement the "do_something" top-level command execution
436 #
437 def impl_do_command(self, root_name, arg):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700438 "Implementation of top-level 'do_something' command execution"
439 parser = self.parsers_dict[root_name]
440 parsed_args = parser.parse_args(arg.split())
441 parsed_args.func(parsed_args)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700442
443 #
444 # Implement the "complete_something" top-level command completion
445 #
446 def impl_complete_command(self, root_name, text, line, begidx, endidx):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700447 "Implementation of top-level 'complete_something' command completion"
448 root_command = self.commands_dict[root_name]
449 subtree_commands = self.collect_subtree_commands(root_command)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700450
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700451 #
452 # Loop through the commands and add their portion
453 # of the sub-name to the list of completions.
454 #
455 # NOTE: We add a command only if it has a callback.
456 #
457 completions = []
458 for c in subtree_commands:
459 if c.callback is None:
460 continue
461 name = c.split_name[len(root_command.split_name):]
462 completions.append(' '.join(name))
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700463
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700464 mline = line.partition(" ")[2]
465 offs = len(mline) - len(text)
466 return [s[offs:] for s in completions if s.startswith(mline)]
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700467
468 #
469 # Implement the "help_something" top-level command help
470 #
471 def impl_help_command(self, root_name):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700472 "Implementation of top-level 'help_something' command help"
473 root_command = self.commands_dict[root_name]
474 subtree_commands = self.collect_subtree_commands(root_command)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700475
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700476 #
477 # Loop through the commands and print the help for each command.
478 # NOTE: We add a command only if it has a callback.
479 #
480 print "Help for the `%s` command:" % (root_name)
481 for c in subtree_commands:
482 if c.callback is None:
483 continue
484 print " {0:30}{1:30}".format(c.name, c.help)
485 # if c.init_arg_parser is not None:
486 # parser = self.parsers_dict[c.name]
487 # parser.print_help()
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700488
489 #
Pavlin Radoslavov79649d92014-07-14 14:30:23 -0700490 # Traverse (depth-first) a subtree and return all nodes except the
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700491 # root node.
492 #
493 def collect_subtree_commands(self, root_command):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700494 """Collect a subtree of commands.
Pavlin Radoslavov6cc3f742014-07-09 18:04:04 -0700495 Traverses (depth-first) a subtree of commands and returns
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700496 all nodes except the root node."""
497
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700498 commands = []
499 subtree_commands = []
Pavlin Radoslavov6cc3f742014-07-09 18:04:04 -0700500 # Use depth-first to traverse the subtree
501 for c in root_command.children:
502 commands.append(c)
503 subtree_commands = self.collect_subtree_commands(c)
504 if len(subtree_commands):
505 commands.extend(subtree_commands)
506 return commands
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700507
508 def log_debug(self, msg):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700509 """Log debug information.
510 msg: the message to log
511 Use the following CLI commands to enable/disable debugging:
512 paramset debug true
513 paramset debug false
514 """
515 if self.debug:
516 print "%s" % (msg)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700517
518 def print_json_result(self, json_result):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700519 """Print JSON result."""
520 if len(json_result) == 0:
521 return
522 result = json.dumps(json_result, indent=4)
523 self.print_result(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700524
525 def print_result(self, result):
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700526 """Print parsed result."""
527 print "%s" % (result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700528
529 #
530 # Implementation of the "paramshow" CLI command.
531 #
532 # NOTE: The do_paramshow implementation below is copied from
533 # the cmd2.do_show() implementation
534 #
535 @options([make_option('-l', '--long', action="store_true",
536 help="describe function of parameter")])
537 def do_paramshow(self, arg, opts):
538 '''Shows value of a parameter.'''
539 param = arg.strip().lower()
540 result = {}
541 maxlen = 0
542 for p in self.settable:
543 if (not param) or p.startswith(param):
544 result[p] = '%s: %s' % (p, str(getattr(self, p)))
545 maxlen = max(maxlen, len(result[p]))
546 if result:
547 for p in sorted(result):
548 if opts.long:
549 self.poutput('%s # %s' % (result[p].ljust(maxlen), self.settable[p]))
550 else:
551 self.poutput(result[p])
552 else:
553 raise NotImplementedError("Parameter '%s' not supported (type 'show' for list of parameters)." % param)
554
555 #
556 # Implementation of the "paramset" CLI command.
557 #
558 #
559 # NOTE: The do_paramset implementation below is copied from
560 # the cmd2.do_set() implementation (with minor modifications).
561 #
562 def do_paramset(self, arg):
563 '''
564 Sets a cmd2 parameter. Accepts abbreviated parameter names so long
565 as there is no ambiguity. Call without arguments for a list of
566 settable parameters with their values.'''
567
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700568 class NotSettableError(Exception):
569 pass
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700570
571 try:
572 statement, paramName, val = arg.parsed.raw.split(None, 2)
573 val = val.strip()
574 paramName = paramName.strip().lower()
575 if paramName not in self.settable:
576 hits = [p for p in self.settable if p.startswith(paramName)]
577 if len(hits) == 1:
578 paramName = hits[0]
579 else:
580 return self.do_paramshow(paramName)
581 currentVal = getattr(self, paramName)
582 if (val[0] == val[-1]) and val[0] in ("'", '"'):
583 val = val[1:-1]
584 else:
585 val = cmd2.cast(currentVal, val)
586 setattr(self, paramName, val)
587 self.stdout.write('%s - was: %s\nnow: %s\n' % (paramName, currentVal, val))
588 if currentVal != val:
589 try:
590 onchange_hook = getattr(self, '_onchange_%s' % paramName)
591 onchange_hook(old=currentVal, new=val)
592 except AttributeError:
593 pass
594 except (ValueError, AttributeError, NotSettableError) as exc:
595 self.do_paramshow(arg)
596
597
598def get_json(url):
599 """Make a REST GET call and return the JSON result
600 url: the URL to call"""
601
Pavlin Radoslavovcf2b5532014-05-23 18:12:23 -0700602 parsed_result = []
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700603 try:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700604 response = urllib2.urlopen(url)
605 result = response.read()
606 response.close()
607 if len(result) != 0:
608 parsed_result = json.loads(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700609 except HTTPError as exc:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700610 print "ERROR:"
611 print " REST GET URL: %s" % url
612 # NOTE: exc.fp contains the object with the response payload
613 error_payload = json.loads(exc.fp.read())
614 print " REST Error Code: %s" % (error_payload['code'])
615 print " REST Error Summary: %s" % (error_payload['summary'])
616 print " REST Error Description: %s" % (error_payload['formattedDescription'])
617 print " HTTP Error Code: %s" % exc.code
618 print " HTTP Error Reason: %s" % exc.reason
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700619 except URLError as exc:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700620 print "ERROR:"
621 print " REST GET URL: %s" % url
622 print " URL Error Reason: %s" % exc.reason
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700623 return parsed_result
624
625def post_json(url, data):
626 """Make a REST POST call and return the JSON result
627 url: the URL to call
628 data: the data to POST"""
629
Pavlin Radoslavovcf2b5532014-05-23 18:12:23 -0700630 parsed_result = []
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700631 data_json = json.dumps(data)
632 try:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700633 request = urllib2.Request(url, data_json)
634 request.add_header("Content-Type", "application/json")
635 response = urllib2.urlopen(request)
636 result = response.read()
637 response.close()
638 if len(result) != 0:
639 parsed_result = json.loads(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700640 except HTTPError as exc:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700641 print "ERROR:"
642 print " REST POST URL: %s" % url
643 # NOTE: exc.fp contains the object with the response payload
644 error_payload = json.loads(exc.fp.read())
645 print " REST Error Code: %s" % (error_payload['code'])
646 print " REST Error Summary: %s" % (error_payload['summary'])
647 print " REST Error Description: %s" % (error_payload['formattedDescription'])
648 print " HTTP Error Code: %s" % exc.code
649 print " HTTP Error Reason: %s" % exc.reason
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700650 except URLError as exc:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700651 print "ERROR:"
652 print " REST POST URL: %s" % url
653 print " URL Error Reason: %s" % exc.reason
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700654 return parsed_result
655
656def delete_json(url):
657 """Make a REST DELETE call and return the JSON result
658 url: the URL to call"""
659
Pavlin Radoslavovcf2b5532014-05-23 18:12:23 -0700660 parsed_result = []
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700661 try:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700662 request = urllib2.Request(url)
663 request.get_method = lambda: 'DELETE'
664 response = urllib2.urlopen(request)
665 result = response.read()
666 response.close()
667 if len(result) != 0:
668 parsed_result = json.loads(result)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700669 except HTTPError as exc:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700670 print "ERROR:"
671 print " REST DELETE URL: %s" % url
672 # NOTE: exc.fp contains the object with the response payload
673 error_payload = json.loads(exc.fp.read())
674 print " REST Error Code: %s" % (error_payload['code'])
675 print " REST Error Summary: %s" % (error_payload['summary'])
676 print " REST Error Description: %s" % (error_payload['formattedDescription'])
677 print " HTTP Error Code: %s" % exc.code
678 print " HTTP Error Reason: %s" % exc.reason
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700679 except URLError as exc:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700680 print "ERROR:"
681 print " REST DELETE URL: %s" % url
682 print " URL Error Reason: %s" % exc.reason
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700683 return parsed_result
684
685if __name__ == '__main__':
686 onosCli = OnosCli()
687
688 # Setup the parser
689 parser = argparse.ArgumentParser()
690 parser.add_argument('-c', '--command', nargs=argparse.REMAINDER,
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700691 help="Run arguments to the end of the line as a CLI command")
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700692 parser.add_argument('--onos-ip',
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700693 help="Set the ONOS IP address (for REST calls)")
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700694 parser.add_argument('--onos-port',
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700695 help="Set the ONOS port number (for REST calls)")
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700696 parser.add_argument('-t', '--test', nargs='+',
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700697 help="Test against transcript(s) in FILE (wildcards OK)")
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700698
699 # Parse the arguments
700 parsed_args = parser.parse_args()
701 if parsed_args.onos_ip:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700702 onosCli.onos_ip = parsed_args.onos_ip
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700703 if parsed_args.onos_port:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700704 onosCli.onos_port = parsed_args.onos_port
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700705 #
706 # NOTE: We have to reset the command-line options so the Cmd2 parser
707 # doesn't process them again.
708 #
709 sys.argv = [sys.argv[0]]
710
711 # Run the CLI as appropriate
712 if parsed_args.test:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700713 # Run CLI Transcript Tests
714 onosCli.runTranscriptTests(parsed_args.test)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700715 elif parsed_args.command:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700716 # Run arguments as a CLI command
717 command_line = ' '.join(parsed_args.command)
718 onosCli.onecmd(command_line)
Pavlin Radoslavovb94817b2014-05-22 21:12:47 -0700719 else:
Pavlin Radoslavov8500fc02014-05-28 14:04:55 -0700720 # Run interactive CLI
721 onosCli.cmdloop()