CLI to aid integration and testing
Change-Id: If18f194e45a8fc090a6b7869bb6d81728397ec9b
diff --git a/.gitignore b/.gitignore
index 54c7891..50df298 100644
--- a/.gitignore
+++ b/.gitignore
@@ -60,3 +60,7 @@
# Files copied over during make
ofagent/protos/third_party/google
+
+# Voltha CLI hostory files
+.voltha_cli_history
+**/.voltha_cli_history
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..9b97814
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,36 @@
+# TODO
+
+Miscellaneous next steps across the Voltha code base. These may not be entered
+in Jira yet. Items are organized per area:
+
+
+## Voltha
+
+* Ctrl-C should reliably stop Voltha
+
+
+## Chameleon
+
+* Ctrl-C should reliably stop Voltha
+
+
+## OF Agent
+
+* Ctrl-C should reliably stop Voltha
+* Make it reconnect after Voltha restarted
+
+## CLI
+
+* Add auto-completion for most common args like device and logical device ids
+* Add consistent argument checking
+* Unify code that retrieves data from gRPC
+* Unify code that prints out data/response, to allow:
+ * Selectable output mode:
+ * JSON
+ * Tabular
+* Organize history per sub context so that in each context the commands
+ entered in that context will show
+* Metaprogramming [BIG ONE]: Make large part of the commands come from annotations embedded in
+ the protobuf files and have corresponding handler auto-generated by protoc
+* Package CLI as docker container, bake it into composition
+* Add commands in order of usage priority
diff --git a/cli/README.md b/cli/README.md
new file mode 100644
index 0000000..c810df4
--- /dev/null
+++ b/cli/README.md
@@ -0,0 +1,14 @@
+## CLI (~/cli)
+
+* Add auto-completion for most common args like device and logical device ids
+* Add consistent argument checking
+* Unify code that retrieves data from gRPC
+* Unify code that prints out data/response, to allow:
+ * Selectable output mode:
+ * JSON
+ * Tabular
+* Organize history per sub context so that in each context the commands
+ entered in that context will show
+* Metaprogramming [BIG ONE]: Make large part of the commands come from annotations embedded in
+ the protobuf files and have corresponding handler auto-generated by protoc
+* Package CLI as docker container, bake it into composition
diff --git a/cli/device.py b/cli/device.py
new file mode 100644
index 0000000..be5450b
--- /dev/null
+++ b/cli/device.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+#
+# Copyright 2016 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""
+Device level CLI commands
+"""
+from cmd2 import Cmd
+from simplejson import dumps
+
+from cli.utils import print_flows, pb2dict
+from voltha.protos import third_party
+
+_ = third_party
+from voltha.protos import voltha_pb2
+
+
+class DeviceCli(Cmd):
+
+ def __init__(self, get_channel, device_id):
+ Cmd.__init__(self)
+ self.get_channel = get_channel
+ self.device_id = device_id
+ self.prompt = '(' + self.colorize(
+ self.colorize('device {}'.format(device_id), 'red'), 'bold') + ') '
+
+ def get_device(self, depth=0):
+ stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
+ res = stub.GetDevice(voltha_pb2.ID(id=self.device_id),
+ metadata=(('get-depth', str(depth)), ))
+ return res
+
+ def do_show(self, arg):
+ """Show detailed device information"""
+ print dumps(pb2dict(self.get_device(depth=-1)),
+ indent=4, sort_keys=True)
+
+ def do_flows(self, arg):
+ """Show flow table for device"""
+ device = pb2dict(self.get_device(-1))
+ print_flows(
+ 'Device',
+ self.device_id,
+ type=device['type'],
+ flows=device['flows']['items'],
+ groups=device['flow_groups']['items']
+ )
+
diff --git a/cli/logical_device.py b/cli/logical_device.py
new file mode 100644
index 0000000..d60c6eb
--- /dev/null
+++ b/cli/logical_device.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+#
+# Copyright 2016 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""
+Logical device level CLI commands
+"""
+from cmd2 import Cmd
+from simplejson import dumps
+
+from cli.utils import pb2dict
+from cli.utils import print_flows
+from voltha.protos import third_party
+
+_ = third_party
+from voltha.protos import voltha_pb2
+
+
+class LogicalDeviceCli(Cmd):
+
+ def __init__(self, get_channel, logical_device_id):
+ Cmd.__init__(self)
+ self.get_channel = get_channel
+ self.logical_device_id = logical_device_id
+ self.prompt = '(' + self.colorize(
+ self.colorize('logical device {}'.format(logical_device_id), 'red'),
+ 'bold') + ') '
+
+ def get_logical_device(self, depth=0):
+ stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
+ res = stub.GetLogicalDevice(voltha_pb2.ID(id=self.logical_device_id),
+ metadata=(('get-depth', str(depth)), ))
+ return res
+
+ def do_show(self, arg):
+ """Show detailed logical device information"""
+ print dumps(pb2dict(self.get_logical_device(depth=-1)),
+ indent=4, sort_keys=True)
+
+ def do_flows(self, arg):
+ """Show flow table for logical device"""
+ logical_device = pb2dict(self.get_logical_device(-1))
+ print_flows(
+ 'Logical Device',
+ self.logical_device_id,
+ type='n/a',
+ flows=logical_device['flows']['items'],
+ groups=logical_device['flow_groups']['items']
+ )
+
diff --git a/cli/main.py b/cli/main.py
index 7b90038..e9e881a 100755
--- a/cli/main.py
+++ b/cli/main.py
@@ -14,38 +14,59 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-import sys
-from cmd2 import Cmd, make_option, options
import readline
+import sys
+from optparse import make_option
+from time import sleep
+
import grpc
+import requests
+from cmd2 import Cmd, options
+from google.protobuf.empty_pb2 import Empty
from simplejson import dumps
+from cli.device import DeviceCli
+from cli.logical_device import LogicalDeviceCli
+from voltha.core.flow_decomposer import *
from voltha.protos import third_party
from voltha.protos import voltha_pb2
-from google.protobuf.empty_pb2 import Empty
-from google.protobuf.json_format import MessageToDict
+from voltha.protos.openflow_13_pb2 import FlowTableUpdate
+
_ = third_party
-from cli.utils import print_flows
+from cli.utils import pb2dict
-def pb2dict(pb_msg):
- d = MessageToDict(pb_msg, including_default_value_fields=1,
- preserving_proto_field_name=1)
- return d
-
+banner = """\
+ _ _ _ _ _
+ __ _____| | |_| |_ __ _ __| (_)
+ \ V / _ \ | _| ' \/ _` | / _| | |
+ \_/\___/_|\__|_||_\__,_| \__|_|_|
+(to exit type q, exit or quit or hit Ctrl-D)
+"""
class VolthaCli(Cmd):
prompt = 'voltha'
history_file_name = '.voltha_cli_history'
+
+ # Settable CLI parameters
+ voltha_grpc = 'localhost:50055'
+ voltha_sim_rest = 'localhost:18880'
max_history_lines = 500
+ default_device_id = None
+ default_logical_device_id = None
Cmd.settable.update(dict(
- voltha_grpc='Voltha GRPC endpoint in form of <host>:<port>'
+ voltha_grpc='Voltha GRPC endpoint in form of <host>:<port>',
+ voltha_sim_rest='Voltha simulation back door for testing in form '
+ 'of <host>:<port>',
+ max_history_lines='Maximum number of history lines stored across '
+ 'sessions',
+ default_device_id='Device id used when no device id is specified',
+ default_logical_device_id='Logical device id used when no device id '
+ 'is specified',
))
- voltha_grpc = 'localhost:50055'
-
def __init__(self, *args, **kw):
Cmd.__init__(self, *args, **kw)
self.prompt = '(' + self.colorize(
@@ -79,6 +100,9 @@
self.channel = grpc.insecure_channel(self.voltha_grpc)
return self.channel
+ def preloop(self):
+ self.poutput(banner)
+
def do_reset_history(self, arg):
"""Reset CLI history"""
while self.history:
@@ -111,12 +135,18 @@
def do_device(self, arg):
"""Enter device level command mode"""
- sub = DeviceCli(self.get_channel, arg)
+ device_id = arg or self.default_device_id
+ if not device_id:
+ raise Exception('<device-id> parameter needed')
+ sub = DeviceCli(self.get_channel, device_id)
sub.cmdloop()
def do_logical_device(self, arg):
"""Enter logical device level command mode"""
- sub = LogicalDeviceCli(self.get_channel, arg)
+ logical_device_id = arg or self.default_logical_device_id
+ if not logical_device_id:
+ raise Exception('<logical-device-id> parameter needed')
+ sub = LogicalDeviceCli(self.get_channel, logical_device_id)
sub.cmdloop()
def do_debug(self, arg):
@@ -130,70 +160,111 @@
res = stub.GetHealthStatus(Empty())
print dumps(pb2dict(res), indent=4)
+ def do_test(self, arg):
+ """Enter test mode, which makes a bunch on new commands available"""
+ sub = TestCli(self.history, self.get_channel)
+ sub.cmdloop()
-class DeviceCli(Cmd):
- def __init__(self, get_channel, device_id):
- Cmd.__init__(self)
+class TestCli(VolthaCli):
+
+ def __init__(self, history, get_channel):
+ VolthaCli.__init__(self)
+ self.history = history
self.get_channel = get_channel
- self.device_id = device_id
- self.prompt = '(' + self.colorize(
- self.colorize('device {}'.format(device_id), 'red'), 'bold') + ') '
-
- def get_device(self, depth=0):
- stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
- res = stub.GetDevice(voltha_pb2.ID(id=self.device_id),
- metadata=(('get-depth', str(depth)), ))
- return res
-
- def do_show(self, arg):
- """Show detailed device information"""
- print dumps(pb2dict(self.get_device(depth=-1)),
- indent=4, sort_keys=True)
-
- def do_flows(self, arg):
- """Show flow table for device"""
- device = pb2dict(self.get_device(-1))
- print_flows(
- 'Device',
- self.device_id,
- type=device['type'],
- flows=device['flows']['items'],
- groups=device['flow_groups']['items']
- )
-
-
-class LogicalDeviceCli(Cmd):
-
- def __init__(self, get_channel, logical_device_id):
- Cmd.__init__(self)
- self.get_channel = get_channel
- self.logical_device_id = logical_device_id
- self.prompt = '(' + self.colorize(
- self.colorize('device {}'.format(logical_device_id), 'red'),
+ self.prompt = '(' + self.colorize(self.colorize('test', 'cyan'),
'bold') + ') '
- def get_logical_device(self, depth=0):
+ @options([
+ make_option('-t', '--device-type', action="store", dest='device_type',
+ help="Device type", default='simulated_olt'),
+ make_option('-m', '--mac-address', action='store', dest='mac_address',
+ default='00:0c:e2:31:40:00'),
+ make_option('-i', '--ip-address', action='store', dest='ip_address'),
+ ])
+ def do_preprovision_olt(self, arg, opts):
+ """Preprovision a new OLT with given device type"""
stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
- res = stub.GetLogicalDevice(voltha_pb2.ID(id=self.logical_device_id),
- metadata=(('get-depth', str(depth)), ))
- return res
+ kw = dict(type=opts.device_type)
+ if opts.ip_address:
+ kw['ipv4_address'] = opts.ip_address
+ elif opts.mac_address:
+ kw['mac_address'] = opts.mac_address
+ else:
+ raise Exception('Either IP address or Mac Address is needed')
+ device = voltha_pb2.Device(**kw)
+ device = stub.CreateDevice(device)
+ print 'success (device id = {})'.format(device.id)
+ self.default_device_id = device.id
- def do_show(self, arg):
- """Show detailed logical device information"""
- print dumps(pb2dict(self.get_logical_device(depth=-1)),
- indent=4, sort_keys=True)
+ def do_activate_olt(self, arg):
+ """
+ Activate an OLT. If the <id> is not provided, it will be on the last
+ pre-provisioned OLT.
+ """
+ device_id = arg or self.default_device_id
+ print 'activating', device_id
+ stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
+ stub.ActivateDevice(voltha_pb2.ID(id=device_id))
- def do_flows(self, arg):
- """Show flow table for logical device"""
- logical_device = pb2dict(self.get_logical_device(-1))
- print_flows(
- 'Logical Device',
- self.logical_device_id,
- type='n/a',
- flows=logical_device['flows']['items'],
- groups=logical_device['flow_groups']['items']
+ # try to acquire logical device id
+ while True:
+ device = stub.GetDevice(voltha_pb2.ID(id=device_id))
+ if device.oper_status == voltha_pb2.OperStatus.ACTIVE:
+ assert device.parent_id
+ self.default_logical_device_id = device.parent_id
+ break
+ print 'waiting for device to be activated...'
+ sleep(1)
+ print 'success (logical device id = {})'.format(
+ self.default_logical_device_id)
+
+ def do_arrive_onus(self, arg):
+ """
+ Simulate the arrival of ONUs
+ """
+ device_id = arg or self.default_device_id
+ requests.get('http://{}/devices/{}/detect_onus'.format(
+ self.voltha_sim_rest, device_id
+ ))
+
+ def do_install_eapol_flow(self, arg):
+ """
+ Install an EAPOL flow on the given logical device. If device is not
+ given, it will be applied to logical device of the last pre-provisioned
+ OLT device.
+ """
+ logical_device_id = arg or self.default_logical_device_id
+ update = FlowTableUpdate(
+ id=logical_device_id,
+ flow_mod = mk_simple_flow_mod(
+ priority=2000,
+ match_fields=[in_port(101), eth_type(0x888e)],
+ actions=[
+ push_vlan(0x8100),
+ set_field(vlan_vid(4096 + 4000)),
+ output(ofp.OFPP_CONTROLLER)
+ ]
+ )
)
+ stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
+ res = stub.UpdateLogicalDeviceFlowTable(update)
+ print 'success', res
+
+ def do_send_simulated_upstream_eapol(self, arg):
+ """
+ Send an EAPOL upstream from a simulated OLT
+ """
+ device_id = arg or self.default_device_id
+ requests.get('http://{}/devices/{}/test_eapol_in'.format(
+ self.voltha_sim_rest, device_id
+ ))
+
+ def do_inject_eapol_start(self, arg):
+ """
+ Send out an an EAPOL start message into the given Unix interface
+ """
+ pass
if __name__ == '__main__':
diff --git a/cli/utils.py b/cli/utils.py
index 23fab7f..5e3b7dd 100644
--- a/cli/utils.py
+++ b/cli/utils.py
@@ -1,10 +1,17 @@
import os
import sys
import requests
+from google.protobuf.json_format import MessageToDict
from termcolor import cprint, colored
from os.path import join as pjoin
+def pb2dict(pb_msg):
+ d = MessageToDict(pb_msg, including_default_value_fields=1,
+ preserving_proto_field_name=1)
+ return d
+
+
def p_cookie(cookie):
cookie = str(cookie)
if len(cookie) > 8:
diff --git a/tmp_integration.md b/tmp_integration.md
new file mode 100644
index 0000000..d170012
--- /dev/null
+++ b/tmp_integration.md
@@ -0,0 +1,12 @@
+## ONOS bring-up
+
+```
+docker pull onosproject/onos
+docker run -itd --name=onos onosproject/onos
+ssh -p 8101 karaf@localhost
+```
+
+Loaded olt, config
+
+
+TODO:
diff --git a/voltha/adapters/simulated_olt/simulated_olt.py b/voltha/adapters/simulated_olt/simulated_olt.py
index d407c4b..f550f97 100644
--- a/voltha/adapters/simulated_olt/simulated_olt.py
+++ b/voltha/adapters/simulated_olt/simulated_olt.py
@@ -78,7 +78,7 @@
self.control_endpoint.listen(self.get_test_control_site())
# TODO tmp: populate some devices and logical devices
- reactor.callLater(0, self._tmp_populate_stuff)
+ # reactor.callLater(0, self._tmp_populate_stuff)
log.info('started')
def stop(self):
@@ -103,8 +103,8 @@
return device
def abandon_device(self, device):
- raise NotImplementedError(0
- )
+ raise NotImplementedError()
+
def deactivate_device(self, device):
raise NotImplementedError()
@@ -274,10 +274,8 @@
# then shortly after we create the logical device with one port
# that will correspond to the NNI port
yield asleep(0.05)
- logical_device_id = uuid4().hex[:12]
ld = LogicalDevice(
- id=logical_device_id,
- datapath_id=int('0x' + logical_device_id[:8], 16), # from id
+ # not setting id and datapth_id will let the adapter agent pick id
desc=ofp_desc(
mfr_desc='cord porject',
hw_desc='simualted pon',
@@ -297,9 +295,9 @@
),
root_device_id=device.id
)
- self.adapter_agent.create_logical_device(ld)
+ ld_initialized = self.adapter_agent.create_logical_device(ld)
cap = OFPPF_1GB_FD | OFPPF_FIBER
- self.adapter_agent.add_logical_port(ld.id, LogicalPort(
+ self.adapter_agent.add_logical_port(ld_initialized.id, LogicalPort(
id='nni',
ofp_port=ofp_port(
port_no=129,
@@ -320,7 +318,7 @@
# and finally update to active
device = self.adapter_agent.get_device(device.id)
- device.parent_id = ld.id
+ device.parent_id = ld_initialized.id
device.oper_status = OperStatus.ACTIVE
self.adapter_agent.update_device(device)
@@ -389,10 +387,10 @@
"""Simulate a packet in message posted upstream"""
log.info('test_eapol_in', request=request, device_id=device_id)
eapol_start = str(
- (Ether(src='00:11:22:33:44:55') / EAPOL(type=1))
- / Padding(load=42*'\x00')
+ Ether(src='00:11:22:33:44:55') /
+ EAPOL(type=1, len=0) /
+ Padding(load=42*'\x00')
)
-
device = self.adapter_agent.get_device(device_id)
self.adapter_agent.send_packet_in(logical_device_id=device.parent_id,
logical_port_no=1,
diff --git a/voltha/core/adapter_agent.py b/voltha/core/adapter_agent.py
index 2a6c61b..0c5448f 100644
--- a/voltha/core/adapter_agent.py
+++ b/voltha/core/adapter_agent.py
@@ -189,11 +189,29 @@
self._make_up_to_date('/devices/{}/ports'.format(device_id),
port.port_no, port)
+ def _find_first_available_id(self):
+ logical_devices = self.root_proxy.get('/logical_devices')
+ existing_ids = set(ld.id for ld in logical_devices)
+ existing_datapath_ids = set(ld.datapath_id for ld in logical_devices)
+ i = 1
+ while True:
+ if i not in existing_datapath_ids and str(i) not in existing_ids:
+ return i
+ i += 1
+
def create_logical_device(self, logical_device):
assert isinstance(logical_device, LogicalDevice)
+
+ if not logical_device.id:
+ id = self._find_first_available_id()
+ logical_device.id = str(id)
+ logical_device.datapath_id = id
+
self._make_up_to_date('/logical_devices',
logical_device.id, logical_device)
+ return logical_device
+
def add_logical_port(self, logical_device_id, port):
assert isinstance(port, LogicalPort)
self._make_up_to_date(