Added initial implementation of the ONOS CLI.

Usage:
    # Running the CLI in interactive mode:
    $ ./onoscli

    # Running multiple CLI commands in batch mode
    $ cat commands.txt | ./onoscli

    # Running a single command from the system command-line
    $ ./onoscli -c show switch all

    # Run the following command for additional help
    $ ./onoscli -h


Change-Id: I0eb917487c0187a7b4b01953d45d7b53ea070f52
diff --git a/README.md b/README.md
index e57a000..f8c9852 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@
 ---------------------
 1. Required packages
 
-        $ sudo apt-get install maven python-flask
+        $ sudo apt-get install maven python-flask python-cmd2
 
 2. ZooKeeper
 
diff --git a/onoscli b/onoscli
new file mode 100755
index 0000000..127ec95
--- /dev/null
+++ b/onoscli
@@ -0,0 +1,698 @@
+#! /usr/bin/env python
+# -*- Mode: python; py-indent-offset: 4; tab-width: 8; indent-tabs-mode: t; -*-
+
+"""
+onoscli : ONOS-specific Command Line Interface
+
+Usage:
+    # Running the CLI in interactive mode:
+    $ ./onoscli
+
+    # Running multiple CLI commands in batch mode
+    $ cat commands.txt | ./onoscli
+
+    # Running a single command from the system command-line
+    $ ./onoscli -c show switch all
+
+    # Run the following command for additional help
+    $ ./onoscli -h
+"""
+
+#
+# INSTALLATION NOTES: MUST install Python cmd2 module. E.g., on Ubuntu:
+#    sudo apt-get install python-cmd2
+# On older Ubuntu installations (e.g., Ubuntu-12.04), install also:
+#    sudo apt-get install python-pyparsing
+#
+
+#
+# ADDING A NEW CLI COMMAND:
+#  1. Add the appropriate Command entry (or entries) inside array
+#     OnosCli.init_commands.
+#     See the comments for init_commands for details.
+#  2. Add the appropriate callback (inside class OnosCli) for the CLI command
+#
+#
+
+import sys
+import argparse
+import json
+from optparse import make_option
+import urllib2
+from urllib2 import URLError, HTTPError
+import cmd2
+from cmd2 import Cmd
+from cmd2 import options
+
+
+class Command():
+    "Command node. A hierarchy of nodes are organized in a command tree."
+
+    def __init__(self, name, help, callback=None, add_parser_args=None):
+	"""name: a string with the full command name
+	   help: the help string for the command
+           callback: the method to be called if the command is executed
+	   add_parser_args: the parser arguments to add to the command: a dictionary of argparse arguments"""
+	# Normalize the name by removing extra spaces
+	self.split_name = name.split()	# List of the words in the name
+	self.name = ' '.join(self.split_name)	# Normalized name
+	self.last_subname = self.split_name[-1]	# Last word in the name
+	self.parent_name = ' '.join(self.split_name[:-1]) # Name of parent command
+	self.help = help		# THe help string
+	self.callback = callback	# The command callback
+	self.add_parser_args = add_parser_args	# Parser arguments to add
+	self.parent = None		# The parent Command
+	self.children = []		# The children Command entries
+
+
+class OnosCli(Cmd):
+    "The main ONOS CLI class"
+
+    # Setup generic CLI fields
+    intro = "Welcome to the ONOS CLI. Type help or ? to list commands.\n"
+    prompt = "(onos) "
+    settable = Cmd.settable + "prompt CLI prompt"
+
+    # Setup ONOS-specific fields
+    onos_ip = "127.0.0.1"
+    settable = settable + "onos_ip ONOS IP address"
+    onos_port = 8080
+    settable = settable + "onos_port ONOS REST port number"
+    output_format = "json"	# Valid values: json, text
+    settable = settable + "output_format The output format: `text` or `json`"
+
+    # Collection of commands sorted by the level in the CLI command hierarchy
+    commands = []
+    # Commands, parsers and subparsers keyed by the command name
+    commands_dict = {}
+    parsers_dict = {}
+    subparsers_dict = {}
+
+    def __init__(self):
+	Cmd.__init__(self)
+
+	#
+	# An array of the ONOS-specific CLI commands.
+	# Each entry is a Command instance, and must have at least
+	# two arguments:
+	#  * Command name as typed on the CLI. E.g.:
+	#    "show intent"
+	#  * Command help description. E.g.:
+	#    "Show intents"
+	#
+	# Executable commands should have a third Command argument, which is
+	# the name of the method to be called when the command is executed.
+	# The method will be called with the (argparse) parsed arguments
+	# for that command.
+	#
+	# If an executable command takes arguments, those should be described
+	# in the Command's fourth argument. It is a list of pairs:
+	#   [
+	#    ("--argument-name1", dict(...)),
+	#    ("--argument-name2", dict(...)),
+	#    ...
+	#   ]
+	# where the first entry in the pair is the argument name, and the
+	# second entry in the pair is a dictionary with argparse-specific
+	# description of the argument.
+	#
+	init_commands = [
+	    Command("delete", "Delete command"),
+	    #
+	    Command("delete intent",
+		    """Delete high-level intents
+    Usage:
+      delete intent --intent-id INTENT_ID  Delete a high-level intent
+      delete intent --all                  Delete all high-level intents
+    Arguments:
+      --intent-id INTENT_ID    The Intent ID (an integer)
+      --all                    Delete all high-level intents""",
+		    self.delete_intent,
+		    [
+		    ("--intent-id", dict(required=False, type=int)),
+		    ("--all", dict(required=False, action='store_true'))
+		    ]
+		    ),
+	    #
+	    Command("set", "Set command"),
+	    #
+	    Command("set intent",
+		    """Set a high-level intent
+    Usage:
+      set intent <ARGS>
+    Arguments:
+      --intent-id INTENT_ID     The Intent ID (an integer) (REQUIRED)
+      --src-dpid SRC_DPID       Source Switch DPID (REQUIRED)
+      --src-port SRC_PORT       Source Switch Port (REQUIRED)
+      --dst-dpid DST_DPID       Destination Switch DPID (REQUIRED)
+      --dst-port DST_PORT       Destination Switch Port (REQUIRED)
+      --match-src-mac MATCH_SRC_MAC Matching Source MAC Address (REQUIRED)
+      --match-dst-mac MATCH_DST_MAC Matching Destination MAC Address (REQUIRED)""",
+		    self.set_intent,
+		    [
+		    ("--intent-id", dict(required=True, type=int)),
+		    ("--src-dpid", dict(required=True)),
+		    ("--src-port", dict(required=True, type=int)),
+		    ("--dst-dpid", dict(required=True)),
+		    ("--dst-port", dict(required=True, type=int)),
+		    ("--match-src-mac", dict(required=True)),
+		    ("--match-dst-mac", dict(required=True))
+		    ]),
+	    #
+	    Command("show", "Show command"),
+	    #
+	    Command("show device", "Show devices"),
+	    #
+	    Command("show device all", "Show all devices", self.show_device_all),
+	    #
+	    Command("show intent", "Show intents"),
+	    #
+	    Command("show intent high",
+		    """Show all high-level intents
+  show intent high --intent-id INTENT_ID    Show a high-level intent""",
+		    self.show_intent_high,
+		    [
+		    ("--intent-id", dict(required=False, type=int))
+		    ]
+		    ),
+	    #
+	    Command("show intent low",
+		    """Show all low-level intents
+  show intent low  --intent-id INTENT_ID    Show a low-level intent""",
+		    self.show_intent_low,
+		    [
+		    ("--intent-id", dict(required=False))
+		    ]
+		    ),
+	    #
+	    Command("show link", "Show links"),
+	    #
+	    Command("show link all", "Show all links", self.show_link_all),
+	    #
+	    Command("show path", "Show a path"),
+	    #
+	    Command("show path shortest",
+		    """Show a shortest path
+    Usage:
+      show path shortest --src-dpid SRC_DPID --dst-dpid DST_DPID
+    Arguments:
+      --src-dpid SRC_DPID       Source Switch DPID
+      --dst-dpid DST_DPID       Destination Switch DPID""",
+		    self.show_path_shortest,
+		    [
+		    ("--src-dpid", dict(required=True)),
+		    ("--dst-dpid", dict(required=True))
+		    ]),
+	    #
+	    Command("show switch", "Show switches"),
+	    #
+	    Command("show switch all", "Show all switches", self.show_switch_all)
+	]
+
+	# Sort the commands by the level in the CLI command hierarchy
+	self.commands = sorted(init_commands, key = lambda c: len(c.name.split()))
+
+	# Create a dictionary with all commands: name -> Command
+	for c in self.commands:
+	    self.commands_dict[c.name] = c
+
+	# Create a tree with all commands
+	for c in self.commands:
+	    if c.parent_name:
+		pc = self.commands_dict[c.parent_name]
+		pc.children.append(c)
+		c.parent = pc
+
+	# Create the parsers and the sub-parsers
+	for c in self.commands:
+	    # Add a parser
+	    parser = None
+	    if c.parent is None:
+		# Add a top-level parser
+		parser = argparse.ArgumentParser(description=c.help,
+						 prog=c.name,
+						 add_help=False)
+	    else:
+		# Add a parser from the parent's subparser
+		parent_subparser = self.subparsers_dict[c.parent_name]
+		parser = parent_subparser.add_parser(c.last_subname,
+						     help=c.help,
+						     add_help=False)
+	    self.parsers_dict[c.name] = parser
+	    # Add a sub-parser
+	    if c.children:
+		subparser = parser.add_subparsers(help=c.help)
+		self.subparsers_dict[c.name] = subparser
+	    # Setup the callback
+	    if c.callback is not None:
+		parser.set_defaults(func=c.callback)
+	    # Init the argument parser
+	    if c.add_parser_args is not None:
+		for a in c.add_parser_args:
+		    (p1, p2) = a
+		    parser.add_argument(p1, **p2)
+
+    def delete_intent(self, args):
+	"CLI command callback: delete intent"
+
+	url = ""
+	if args.all:
+	    # Delete all intents
+	    url = "http://%s:%s/wm/onos/intent/high" % (self.onos_ip, self.onos_port)
+	else:
+	    if args.intent_id is None:
+		print "*** Unknown syntax:"
+		self.help_delete()
+		return;
+	    # Delete an intent
+	    url = "http://%s:%s/wm/onos/intent/high/%s" % (self.onos_ip, self.onos_port, args.intent_id)
+
+	result = delete_json(url)
+	# NOTE: No need to print the response
+	# if len(result) != 0:
+	#     self.print_json_result(result)
+
+    def set_intent(self, args):
+	"CLI command callback: set intent"
+
+	intents = []
+	oper = {}
+	# Create the POST payload
+	oper['intentId'] = args.intent_id
+	oper['intentType'] = 'CONSTRAINED_SHORTEST_PATH'	# XXX: Hardcoded
+	oper['staticPath'] = False		# XXX: Hardcoded
+	oper['srcSwitchDpid'] = args.src_dpid
+	oper['srcSwitchPort'] = args.src_port
+	oper['dstSwitchDpid'] = args.dst_dpid
+	oper['dstSwitchPort'] = args.dst_port
+	oper['matchSrcMac'] = args.match_src_mac
+	oper['matchDstMac'] = args.match_dst_mac
+	intents.append(oper)
+
+	url = "http://%s:%s/wm/onos/intent/high" % (self.onos_ip, self.onos_port)
+	result = post_json(url, intents)
+	# NOTE: No need to print the response
+	# if len(result) != 0:
+	#     self.print_json_result(result)
+
+    def show_device_all(self, args):
+	"CLI command callback: show device all"
+
+	url = "http://%s:%s/wm/onos/topology/devices/json" % (self.onos_ip, self.onos_port)
+	result = get_json(url)
+	self.print_json_result(result)
+
+    def show_intent_high(self, args):
+	"CLI command callback: show intent high"
+
+	if args.intent_id is None:
+	    # Show all intents
+	    url = "http://%s:%s/wm/onos/intent/high" % (self.onos_ip, self.onos_port)
+	else:
+	    # Show a single intent
+	    url = "http://%s:%s/wm/onos/intent/high/%s" % (self.onos_ip, self.onos_port, args.intent_id)
+
+	result = get_json(url)
+	self.print_json_result(result)
+
+    def show_intent_low(self, args):
+	"CLI command callback: show intent low"
+
+	if args.intent_id is None:
+	    # Show all intents
+	    url = "http://%s:%s/wm/onos/intent/low" % (self.onos_ip, self.onos_port)
+	else:
+	    # Show a single intent
+	    url = "http://%s:%s/wm/onos/intent/low/%s" % (self.onos_ip, self.onos_port, args.intent_id)
+
+	result = get_json(url)
+	self.print_json_result(result)
+
+    def show_link_all(self, args):
+	"CLI command callback: show link all"
+
+	url = "http://%s:%s/wm/onos/topology/links/json" % (self.onos_ip, self.onos_port)
+	result = get_json(url)
+	#
+	if (self.output_format == "json"):
+	    self.print_json_result(result)
+	else:
+	    # NOTE: The code below is for demo purpose only how to
+	    # decode and print the links in text format. It will be
+	    # reimplemented in the future.
+	    links = result
+	    print "# src_dpid src_port -> dst_dpid dst_port"
+	    for v in sorted(links, key=lambda x: x['src-switch']):
+		if v.has_key('dst-switch'):
+		    dst_dpid = str(v['dst-switch'])
+		if v.has_key('src-switch'):
+		    src_dpid = str(v['src-switch'])
+		if v.has_key('src-port'):
+		    src_port = str(v['src-port'])
+		if v.has_key('dst-port'):
+		    dst_port = str(v['dst-port'])
+		self.print_result("%s %s -> %s %s" % (src_dpid, src_port, dst_dpid, dst_port))
+
+    def show_path_shortest(self, args):
+	"CLI command callback: show path shortest"
+
+	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)
+	result = get_json(url)
+	#
+	self.print_json_result(result)
+
+    def show_switch_all(self, args):
+	"CLI command callback: show switch all"
+
+	url = "http://%s:%s/wm/onos/topology/switches/json" % (self.onos_ip, self.onos_port)
+	result = get_json(url)
+	#
+	self.print_json_result(result)
+
+    #
+    # Implement "delete" top-level command
+    #
+    def do_delete(self, arg):
+        "Top-level 'delete' command"
+	self.impl_do_command('delete', arg)
+    def complete_delete(self, text, line, begidx, endidx):
+	"Completion of top-level 'delete' command"
+	return self.impl_complete_command('delete', text, line, begidx, endidx)
+    def help_delete(self):
+	"Help for top-level 'delete' command"
+	self.impl_help_command('delete')
+
+    #
+    # Implement "set" top-level command
+    #
+    def do_set(self, arg):
+        "Top-level 'set' command"
+	self.impl_do_command('set', arg)
+    def complete_set(self, text, line, begidx, endidx):
+	"Completion of top-level 'set' command"
+	return self.impl_complete_command('set', text, line, begidx, endidx)
+    def help_set(self):
+	"Help for top-level 'set' command"
+	self.impl_help_command('set')
+
+    #
+    # Implement "show" top-level command
+    #
+    def do_show(self, arg):
+        "Top-level 'show' command"
+	self.impl_do_command('show', arg)
+    def complete_show(self, text, line, begidx, endidx):
+	"Completion of top-level 'show' command"
+	return self.impl_complete_command('show', text, line, begidx, endidx)
+    def help_show(self):
+	"Help for top-level 'show' command"
+	self.impl_help_command('show')
+
+    #
+    # Implement the "do_something" top-level command execution
+    #
+    def impl_do_command(self, root_name, arg):
+	"Implementation of top-level 'do_something' command execution"
+	parser = self.parsers_dict[root_name]
+	parsed_args = parser.parse_args(arg.split())
+	parsed_args.func(parsed_args)
+
+    #
+    # Implement the "complete_something" top-level command completion
+    #
+    def impl_complete_command(self, root_name, text, line, begidx, endidx):
+	"Implementation of top-level 'complete_something' command completion"
+	root_command = self.commands_dict[root_name]
+	subtree_commands = self.collect_subtree_commands(root_command)
+
+	#
+	# Loop through the commands and add their portion
+	# of the sub-name to the list of completions.
+	#
+	# NOTE: We add a command only if it has a callback.
+	#
+	completions = []
+	for c in subtree_commands:
+	    if c.callback is None:
+		continue
+	    name = c.split_name[len(root_command.split_name):]
+	    completions.append(' '.join(name))
+
+	mline = line.partition(" ")[2]
+	offs = len(mline) - len(text)
+	return [s[offs:] for s in completions if s.startswith(mline)]
+
+    #
+    # Implement the "help_something" top-level command help
+    #
+    def impl_help_command(self, root_name):
+	"Implementation of top-level 'help_something' command help"
+	root_command = self.commands_dict[root_name]
+	subtree_commands = self.collect_subtree_commands(root_command)
+
+	#
+	# Loop through the commands and print the help for each command.
+	# NOTE: We add a command only if it has a callback.
+	#
+	print "Help for the `%s` command:" % (root_name)
+	for c in subtree_commands:
+	    if c.callback is None:
+		continue
+	    print "  {0:30}{1:30}".format(c.name, c.help)
+	    # if c.init_arg_parser is not None:
+	    # 	parser = self.parsers_dict[c.name]
+	    # 	parser.print_help()
+
+    #
+    # Traverse (breadth-first) a subtree and return all nodes except the
+    # root node.
+    #
+    def collect_subtree_commands(self, root_command):
+	"""Collect a subtree of commands.
+           Traverses (breadth-first) a subtree of commands and returns
+           all nodes except the root node."""
+
+	commands = []
+	subtree_commands = []
+	commands.append(root_command)
+	# Use breadth-first to traverse the subtree
+	while commands:
+	    pc = commands.pop(0)
+	    for c in pc.children:
+		commands.append(c)
+		subtree_commands.append(c)
+	return subtree_commands
+
+    def log_debug(self, msg):
+	"""Log debug information.
+	msg: the message to log
+	Use the following CLI commands to enable/disable debugging:
+	  paramset debug true
+	  paramset debug false
+	"""
+	if self.debug:
+	    print "%s" % (msg)
+
+    def print_json_result(self, json_result):
+	"""Print JSON result."""
+	if len(json_result) == 0:
+	    return
+	result = json.dumps(json_result, indent=4)
+	self.print_result(result)
+
+    def print_result(self, result):
+	"""Print parsed result."""
+	print "%s" % (result)
+
+    #
+    # Implementation of the "paramshow" CLI command.
+    #
+    # NOTE: The do_paramshow implementation below is copied from
+    # the cmd2.do_show() implementation
+    #
+    @options([make_option('-l', '--long', action="store_true",
+                 help="describe function of parameter")])
+    def do_paramshow(self, arg, opts):
+        '''Shows value of a parameter.'''
+        param = arg.strip().lower()
+        result = {}
+        maxlen = 0
+        for p in self.settable:
+            if (not param) or p.startswith(param):
+                result[p] = '%s: %s' % (p, str(getattr(self, p)))
+                maxlen = max(maxlen, len(result[p]))
+        if result:
+            for p in sorted(result):
+                if opts.long:
+                    self.poutput('%s # %s' % (result[p].ljust(maxlen), self.settable[p]))
+                else:
+                    self.poutput(result[p])
+        else:
+            raise NotImplementedError("Parameter '%s' not supported (type 'show' for list of parameters)." % param)
+
+    #
+    # Implementation of the "paramset" CLI command.
+    #
+    #
+    # NOTE: The do_paramset implementation below is copied from
+    # the cmd2.do_set() implementation (with minor modifications).
+    #
+    def do_paramset(self, arg):
+        '''
+        Sets a cmd2 parameter.  Accepts abbreviated parameter names so long
+        as there is no ambiguity.  Call without arguments for a list of
+        settable parameters with their values.'''
+
+	class NotSettableError(Exception):
+	    pass
+
+        try:
+            statement, paramName, val = arg.parsed.raw.split(None, 2)
+            val = val.strip()
+            paramName = paramName.strip().lower()
+            if paramName not in self.settable:
+                hits = [p for p in self.settable if p.startswith(paramName)]
+                if len(hits) == 1:
+                    paramName = hits[0]
+                else:
+                    return self.do_paramshow(paramName)
+            currentVal = getattr(self, paramName)
+            if (val[0] == val[-1]) and val[0] in ("'", '"'):
+                val = val[1:-1]
+            else:
+                val = cmd2.cast(currentVal, val)
+            setattr(self, paramName, val)
+            self.stdout.write('%s - was: %s\nnow: %s\n' % (paramName, currentVal, val))
+            if currentVal != val:
+                try:
+                    onchange_hook = getattr(self, '_onchange_%s' % paramName)
+                    onchange_hook(old=currentVal, new=val)
+                except AttributeError:
+                    pass
+        except (ValueError, AttributeError, NotSettableError) as exc:
+            self.do_paramshow(arg)
+
+
+def get_json(url):
+    """Make a REST GET call and return the JSON result
+       url: the URL to call"""
+
+    parsed_result = ""
+    try:
+	response = urllib2.urlopen(url)
+	result = response.read()
+	response.close()
+	parsed_result = json.loads(result)
+    except HTTPError as exc:
+	print "ERROR:"
+	print "  REST GET URL: %s" % url
+	# NOTE: exc.fp contains the object with the response payload
+	error_payload = json.loads(exc.fp.read())
+	print "  REST Error Code: %s" % (error_payload['code'])
+	print "  REST Error Summary: %s" % (error_payload['summary'])
+	print "  REST Error Description: %s" % (error_payload['formattedDescription'])
+	print "  HTTP Error Code: %s" % exc.code
+	print "  HTTP Error Reason: %s" % exc.reason
+    except URLError as exc:
+	print "ERROR:"
+	print "  REST GET URL: %s" % url
+	print "  URL Error Reason: %s" % exc.reason
+    return parsed_result
+
+def post_json(url, data):
+    """Make a REST POST call and return the JSON result
+       url: the URL to call
+       data: the data to POST"""
+
+    parsed_result = ""
+    data_json = json.dumps(data)
+    try:
+	request = urllib2.Request(url, data_json)
+	request.add_header("Content-Type", "application/json")
+	response = urllib2.urlopen(request)
+	result = response.read()
+	response.close()
+	if len(result) != 0:
+	    parsed_result = json.loads(result)
+    except HTTPError as exc:
+	print "ERROR:"
+	print "  REST POST URL: %s" % url
+	# NOTE: exc.fp contains the object with the response payload
+	error_payload = json.loads(exc.fp.read())
+	print "  REST Error Code: %s" % (error_payload['code'])
+	print "  REST Error Summary: %s" % (error_payload['summary'])
+	print "  REST Error Description: %s" % (error_payload['formattedDescription'])
+	print "  HTTP Error Code: %s" % exc.code
+	print "  HTTP Error Reason: %s" % exc.reason
+    except URLError as exc:
+	print "ERROR:"
+	print "  REST POST URL: %s" % url
+	print "  URL Error Reason: %s" % exc.reason
+    return parsed_result
+
+def delete_json(url):
+    """Make a REST DELETE call and return the JSON result
+       url: the URL to call"""
+
+    parsed_result = ""
+    try:
+	request = urllib2.Request(url)
+	request.get_method = lambda: 'DELETE'
+	response = urllib2.urlopen(request)
+	result = response.read()
+	response.close()
+	if len(result) != 0:
+	    parsed_result = json.loads(result)
+    except HTTPError as exc:
+	print "ERROR:"
+	print "  REST DELETE URL: %s" % url
+	# NOTE: exc.fp contains the object with the response payload
+	error_payload = json.loads(exc.fp.read())
+	print "  REST Error Code: %s" % (error_payload['code'])
+	print "  REST Error Summary: %s" % (error_payload['summary'])
+	print "  REST Error Description: %s" % (error_payload['formattedDescription'])
+	print "  HTTP Error Code: %s" % exc.code
+	print "  HTTP Error Reason: %s" % exc.reason
+    except URLError as exc:
+	print "ERROR:"
+	print "  REST DELETE URL: %s" % url
+	print "  URL Error Reason: %s" % exc.reason
+    return parsed_result
+
+if __name__ == '__main__':
+    onosCli = OnosCli()
+
+    # Setup the parser
+    parser = argparse.ArgumentParser()
+    parser.add_argument('-c', '--command', nargs=argparse.REMAINDER,
+			help="Run arguments to the end of the line as a CLI command")
+    parser.add_argument('--onos-ip',
+			help="Set the ONOS IP address (for REST calls)")
+    parser.add_argument('--onos-port',
+			help="Set the ONOS port number (for REST calls)")
+    parser.add_argument('-t', '--test', nargs='+',
+			help="Test against transcript(s) in FILE (wildcards OK)")
+
+    # Parse the arguments
+    parsed_args = parser.parse_args()
+    if parsed_args.onos_ip:
+	onosCli.onos_ip = parsed_args.onos_ip
+    if parsed_args.onos_port:
+	onosCli.onos_port = parsed_args.onos_port
+    #
+    # NOTE: We have to reset the command-line options so the Cmd2 parser
+    # doesn't process them again.
+    #
+    sys.argv = [sys.argv[0]]
+
+    # Run the CLI as appropriate
+    if parsed_args.test:
+	# Run CLI Transcript Tests
+	onosCli.runTranscriptTests(parsed_args.test)
+    elif  parsed_args.command:
+	# Run arguments as a CLI command
+	command_line = ' '.join(parsed_args.command)
+	onosCli.onecmd(command_line)
+    else:
+	# Run interactive CLI
+	onosCli.cmdloop()