Many CLI cleanups and flow preintegration

Changes:
- auto-completion for device and logical device IDs
- a set of test CLI commands to push down various flows
  to Voltha (aids test and integration)
- sample code in simulated_olt and onu to show how
  to process incoming bulk flow table
- extended Tibit OLT and ONU code with remaining flow
  directives they need to handle in the PON use-case

Change-Id: Id101e087cc79f4493805e3b4a051a10a4619bf53
diff --git a/cli/device.py b/cli/device.py
index 94e24e6..3ab4b0d 100644
--- a/cli/device.py
+++ b/cli/device.py
@@ -21,6 +21,7 @@
 from cmd2 import Cmd
 from simplejson import dumps
 
+from cli.table import print_pb_as_table, print_pb_list_as_table
 from cli.utils import print_flows, pb2dict
 from voltha.protos import third_party
 
@@ -47,8 +48,16 @@
 
     def do_show(self, line):
         """Show detailed device information"""
-        self.poutput(dumps(pb2dict(self.get_device(depth=-1)),
-                     indent=4, sort_keys=True))
+        print_pb_as_table('Device {}'.format(self.device_id),
+                          self.get_device(depth=-1))
+
+    def do_ports(self, line):
+        """Show ports of device"""
+        device = self.get_device(depth=-1)
+        omit_fields = {
+        }
+        print_pb_list_as_table('Device ports:', device.ports,
+                               omit_fields, self.poutput)
 
     def do_flows(self, line):
         """Show flow table for device"""
diff --git a/cli/logical_device.py b/cli/logical_device.py
index 286faf0..3e90f41 100644
--- a/cli/logical_device.py
+++ b/cli/logical_device.py
@@ -21,9 +21,11 @@
 from cmd2 import Cmd
 from simplejson import dumps
 
+from cli.table import print_pb_as_table, print_pb_list_as_table
 from cli.utils import pb2dict
 from cli.utils import print_flows
 from voltha.protos import third_party
+from google.protobuf.empty_pb2 import Empty
 
 _ = third_party
 from voltha.protos import voltha_pb2
@@ -45,14 +47,34 @@
                                     metadata=(('get-depth', str(depth)), ))
         return res
 
+    def get_device(self, id):
+        stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
+        return stub.GetDevice(voltha_pb2.ID(id=id))
+
+    def get_devices(self):
+        stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
+        res = stub.ListDevices(Empty())
+        return res.items
+
     do_exit = Cmd.do_quit
 
-    def do_show(self, arg):
+    def do_show(self, _):
         """Show detailed logical device information"""
-        self.poutput(dumps(pb2dict(self.get_logical_device(depth=-1)),
-                     indent=4, sort_keys=True))
+        print_pb_as_table('Logical device {}'.format(self.logical_device_id),
+                          self.get_logical_device(depth=-1))
 
-    def do_flows(self, arg):
+    def do_ports(self, _):
+        """Show ports of logical device"""
+        device = self.get_logical_device(depth=-1)
+        omit_fields = {
+            'ofp_port.advertised',
+            'ofp_port.peer',
+            'ofp_port.max_speed',
+        }
+        print_pb_list_as_table('Logical device ports:', device.ports,
+                               omit_fields, self.poutput)
+
+    def do_flows(self, _):
         """Show flow table for logical device"""
         logical_device = pb2dict(self.get_logical_device(-1))
         print_flows(
@@ -63,3 +85,22 @@
             groups=logical_device['flow_groups']['items']
         )
 
+    def do_devices(self, line):
+        """List devices that belong to this logical device"""
+        logical_device = self.get_logical_device()
+        root_device_id = logical_device.root_device_id
+        devices = [self.get_device(root_device_id)]
+        for d in self.get_devices():
+            if d.parent_id == root_device_id:
+                devices.append(d)
+        omit_fields = {
+            'adapter',
+            'vendor',
+            'model',
+            'hardware_version',
+            'software_version',
+            'firmware_version',
+            'serial_number'
+        }
+        print_pb_list_as_table('Devices:', devices, omit_fields, self.poutput)
+
diff --git a/cli/main.py b/cli/main.py
index 7b4b241..49fc812 100755
--- a/cli/main.py
+++ b/cli/main.py
@@ -27,11 +27,11 @@
 
 from cli.device import DeviceCli
 from cli.logical_device import LogicalDeviceCli
-from cli.table import TablePrinter, print_pb_table
+from cli.table import TablePrinter, print_pb_list_as_table
 from voltha.core.flow_decomposer import *
 from voltha.protos import third_party
 from voltha.protos import voltha_pb2
-from voltha.protos.openflow_13_pb2 import FlowTableUpdate
+from voltha.protos.openflow_13_pb2 import FlowTableUpdate, FlowGroupTableUpdate
 
 _ = third_party
 from cli.utils import pb2dict, dict2line
@@ -144,7 +144,7 @@
         stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
         res = stub.ListAdapters(Empty())
         omit_fields = {}
-        print_pb_table('Adapters:', res.items, omit_fields, self.poutput)
+        print_pb_list_as_table('Adapters:', res.items, omit_fields, self.poutput)
 
     def get_devices(self):
         stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
@@ -168,7 +168,7 @@
             'firmware_version',
             'serial_number'
         }
-        print_pb_table('Devices:', devices, omit_fields, self.poutput)
+        print_pb_list_as_table('Devices:', devices, omit_fields, self.poutput)
 
     def do_logical_devices(self, line):
         """List logical devices in Voltha"""
@@ -182,8 +182,8 @@
             'desc.serial_number',
             'switch_features.capabilities'
         }
-        print_pb_table('Logical devices:', res.items, omit_fields,
-                       self.poutput)
+        print_pb_list_as_table('Logical devices:', res.items, omit_fields,
+                               self.poutput)
 
     def do_device(self, line):
         """Enter device level command mode"""
@@ -257,6 +257,12 @@
         self.prompt = '(' + self.colorize(self.colorize('test', 'cyan'),
             'bold') + ') '
 
+    def get_device(self, device_id, depth=0):
+        stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
+        res = stub.GetDevice(voltha_pb2.ID(id=device_id),
+                             metadata=(('get-depth', str(depth)), ))
+        return res
+
     @options([
         make_option('-t', '--device-type', action="store", dest='device_type',
                      help="Device type", default='simulated_olt'),
@@ -305,15 +311,41 @@
 
     def do_arrive_onus(self, line):
         """
-        Simulate the arrival of ONUs
+        Simulate the arrival of ONUs (available only on simulated_olt)
         """
         device_id = line or self.default_device_id
+
+        # verify that device is of type simulated_olt
+        device = self.get_device(device_id)
+        assert device.type == 'simulated_olt', (
+            'Cannot use it on this device type (only on simulated_olt type)')
+
         requests.get('http://{}/devices/{}/detect_onus'.format(
             self.voltha_sim_rest, device_id
         ))
 
     complete_arrive_onus = VolthaCli.complete_device
 
+    def get_logical_ports(self, logical_device_id):
+        """
+        Return the NNI port number and the first usable UNI port of logical
+        device, and the vlan associated with the latter.
+        """
+        stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
+        ports = stub.ListLogicalDevicePorts(
+            voltha_pb2.ID(id=logical_device_id)).items
+        nni = uni = vlan = None
+        for port in ports:
+            if nni is None and port.root_port:
+                nni = port.ofp_port.port_no
+            if uni is None and not port.root_port:
+                uni = port.ofp_port.port_no
+                uni_device = self.get_device(port.device_id)
+                vlan = uni_device.vlan
+            if nni is not None and uni is not None:
+                return nni, uni, vlan
+        raise Exception('No valid port pair found (no ONUs yet?)')
+
     def do_install_eapol_flow(self, line):
         """
         Install an EAPOL flow on the given logical device. If device is not
@@ -321,14 +353,19 @@
         OLT device.
         """
         logical_device_id = line or self.default_logical_device_id
+
+        # gather NNI and UNI port IDs
+        nni_port_no, uni_port_no = self.get_logical_ports(logical_device_id)
+
+        # construct and push flow rule
         update = FlowTableUpdate(
             id=logical_device_id,
-            flow_mod = mk_simple_flow_mod(
+            flow_mod=mk_simple_flow_mod(
                 priority=2000,
-                match_fields=[in_port(241), eth_type(0x888e)],
+                match_fields=[in_port(uni_port_no), eth_type(0x888e)],
                 actions=[
-                    push_vlan(0x8100),
-                    set_field(vlan_vid(4096 + 4000)),
+                    # push_vlan(0x8100),
+                    # set_field(vlan_vid(4096 + 4000)),
                     output(ofp.OFPP_CONTROLLER)
                 ]
             )
@@ -339,6 +376,255 @@
 
     complete_install_eapol_flow = VolthaCli.complete_logical_device
 
+    def do_install_all_controller_bound_flows(self, line):
+        """
+        Install all flow rules for controller bound flows, including EAPOL,
+        IGMP and DHCP. If device is not given, it will be applied to logical
+        device of the last pre-provisioned OLT device.
+        """
+        logical_device_id = line or self.default_logical_device_id
+
+        # gather NNI and UNI port IDs
+        nni_port_no, uni_port_no = self.get_logical_ports(logical_device_id)
+
+        # construct and push flow rules
+        stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
+
+        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+            id=logical_device_id,
+            flow_mod=mk_simple_flow_mod(
+                priority=2000,
+                match_fields=[in_port(uni_port_no), eth_type(0x888e)],
+                actions=[
+                    # push_vlan(0x8100),
+                    # set_field(vlan_vid(4096 + 4000)),
+                    output(ofp.OFPP_CONTROLLER)
+                ]
+            )
+        ))
+        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+            id=logical_device_id,
+            flow_mod=mk_simple_flow_mod(
+                priority=1000,
+                match_fields=[eth_type(0x800), ip_proto(2)],
+                actions=[output(ofp.OFPP_CONTROLLER)]
+            )
+        ))
+        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+            id=logical_device_id,
+            flow_mod=mk_simple_flow_mod(
+                priority=1000,
+                match_fields=[eth_type(0x800), ip_proto(17), udp_dst(67)],
+                actions=[output(ofp.OFPP_CONTROLLER)]
+            )
+        ))
+
+        self.poutput('success')
+
+    complete_install_all_controller_bound_flows = \
+        VolthaCli.complete_logical_device
+
+    def do_install_all_sample_flows(self, line):
+        """
+        Install all flows that are representative of the virtualized access
+        scenario in a PON network.
+        """
+        logical_device_id = line or self.default_logical_device_id
+
+        # gather NNI and UNI port IDs
+        nni_port_no, uni_port_no, c_vid = \
+            self.get_logical_ports(logical_device_id)
+
+        # construct and push flow rules
+        stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
+
+        # Controller-bound flows
+        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+            id=logical_device_id,
+            flow_mod=mk_simple_flow_mod(
+                priority=2000,
+                match_fields=[in_port(uni_port_no), eth_type(0x888e)],
+                actions=[
+                    # push_vlan(0x8100),
+                    # set_field(vlan_vid(4096 + 4000)),
+                    output(ofp.OFPP_CONTROLLER)
+                ]
+            )
+        ))
+        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+            id=logical_device_id,
+            flow_mod=mk_simple_flow_mod(
+                priority=1000,
+                match_fields=[eth_type(0x800), ip_proto(2)],
+                actions=[output(ofp.OFPP_CONTROLLER)]
+            )
+        ))
+        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+            id=logical_device_id,
+            flow_mod=mk_simple_flow_mod(
+                priority=1000,
+                match_fields=[eth_type(0x800), ip_proto(17), udp_dst(67)],
+                actions=[output(ofp.OFPP_CONTROLLER)]
+            )
+        ))
+
+        # Unicast flows:
+        # Downstream flow 1
+        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+            id=logical_device_id,
+            flow_mod=mk_simple_flow_mod(
+                priority=500,
+                match_fields=[
+                    in_port(nni_port_no),
+                    vlan_vid(4096 + 1000)
+                ],
+                actions=[pop_vlan()],
+                next_table_id=1
+            )
+        ))
+        # Downstream flow 2
+        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+            id=logical_device_id,
+            flow_mod=mk_simple_flow_mod(
+                priority=500,
+                match_fields=[in_port(nni_port_no), vlan_vid(4096 + c_vid)],
+                actions=[set_field(vlan_vid(4096 + 0)), output(uni_port_no)]
+            )
+        ))
+        # Upstream flow 1 for 0-tagged case
+        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+            id=logical_device_id,
+            flow_mod=mk_simple_flow_mod(
+                priority=500,
+                match_fields=[in_port(uni_port_no), vlan_vid(4096 + 0)],
+                actions=[set_field(vlan_vid(4096 + c_vid))],
+                next_table_id=1
+            )
+        ))
+        # Upstream flow 1 for untagged case
+        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+            id=logical_device_id,
+            flow_mod=mk_simple_flow_mod(
+                priority=500,
+                match_fields=[in_port(uni_port_no), vlan_vid(0)],
+                actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + c_vid))],
+                next_table_id=1
+            )
+        ))
+        # Upstream flow 2 for s-tag
+        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+            id=logical_device_id,
+            flow_mod=mk_simple_flow_mod(
+                priority=500,
+                match_fields=[in_port(uni_port_no), vlan_vid(4096 + c_vid)],
+                actions=[
+                    push_vlan(0x8100),
+                    set_field(vlan_vid(4096 + 1000)),
+                    output(nni_port_no)
+                ]
+            )
+        ))
+
+        # Push a few multicast flows
+        # 1st with one bucket for our uni
+        stub.UpdateLogicalDeviceFlowGroupTable(FlowGroupTableUpdate(
+            id=logical_device_id,
+            group_mod=mk_multicast_group_mod(
+                group_id=1,
+                buckets=[
+                    ofp.ofp_bucket(actions=[pop_vlan(), output(uni_port_no)])
+                ]
+            )
+        ))
+        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+            id=logical_device_id,
+            flow_mod=mk_simple_flow_mod(
+                priority=1000,
+                match_fields=[
+                    in_port(nni_port_no),
+                    eth_type(0x800),
+                    vlan_vid(4096 + 140),
+                    ipv4_dst(0xe4010101)
+                ],
+                actions=[group(1)]
+            )
+        ))
+        # 2st with one bucket for our uni
+        stub.UpdateLogicalDeviceFlowGroupTable(FlowGroupTableUpdate(
+            id=logical_device_id,
+            group_mod=mk_multicast_group_mod(
+                group_id=2,
+                buckets=[
+                    ofp.ofp_bucket(actions=[pop_vlan(), output(uni_port_no)])
+                ]
+            )
+        ))
+        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+            id=logical_device_id,
+            flow_mod=mk_simple_flow_mod(
+                priority=1000,
+                match_fields=[
+                    in_port(nni_port_no),
+                    eth_type(0x800),
+                    vlan_vid(4096 + 140),
+                    ipv4_dst(0xe4020202)
+                ],
+                actions=[group(2)]
+            )
+        ))
+        # 3rd with empty bucket
+        stub.UpdateLogicalDeviceFlowGroupTable(FlowGroupTableUpdate(
+            id=logical_device_id,
+            group_mod=mk_multicast_group_mod(
+                group_id=3,
+                buckets=[]
+            )
+        ))
+        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+            id=logical_device_id,
+            flow_mod=mk_simple_flow_mod(
+                priority=1000,
+                match_fields=[
+                    in_port(nni_port_no),
+                    eth_type(0x800),
+                    vlan_vid(4096 + 140),
+                    ipv4_dst(0xe4030303)
+                ],
+                actions=[group(3)]
+            )
+        ))
+
+        self.poutput('success')
+
+    complete_install_all_sample_flows = VolthaCli.complete_logical_device
+
+    def do_delete_all_flows(self, line):
+        """
+        Remove all flows and flow groups from given logical device
+        """
+        logical_device_id = line or self.default_logical_device_id
+        stub = voltha_pb2.VolthaLocalServiceStub(self.get_channel())
+        stub.UpdateLogicalDeviceFlowTable(FlowTableUpdate(
+            id=logical_device_id,
+            flow_mod=ofp.ofp_flow_mod(
+                command=ofp.OFPFC_DELETE,
+                table_id=ofp.OFPTT_ALL,
+                cookie_mask=0,
+                out_port=ofp.OFPP_ANY,
+                out_group=ofp.OFPG_ANY
+            )
+        ))
+        stub.UpdateLogicalDeviceFlowGroupTable(FlowGroupTableUpdate(
+            id=logical_device_id,
+            group_mod=ofp.ofp_group_mod(
+                command=ofp.OFPGC_DELETE,
+                group_id=ofp.OFPG_ALL
+            )
+        ))
+        self.poutput('success')
+
+    complete_delete_all_flows = VolthaCli.complete_logical_device
+
     def do_send_simulated_upstream_eapol(self, line):
         """
         Send an EAPOL upstream from a simulated OLT
diff --git a/cli/table.py b/cli/table.py
index 3eb828a..6485787 100644
--- a/cli/table.py
+++ b/cli/table.py
@@ -15,6 +15,7 @@
 #
 import sys
 
+from google.protobuf.internal.containers import RepeatedCompositeFieldContainer
 from google.protobuf.message import Message
 from termcolor import colored
 
@@ -37,6 +38,9 @@
         row[field_key] = value
         self._update_max_length(field_key, value)
 
+    def number_of_rows(self):
+        return len(self.cell_values)
+
     def print_table(self, header=None, printfn=_printfn, dividers=10):
 
         if header is not None:
@@ -85,7 +89,7 @@
             assert self.field_names[field_key] == field_name
 
 
-def print_pb_table(header, items, fields_to_omit=None, printfn=_printfn):
+def print_pb_list_as_table(header, items, fields_to_omit=None, printfn=_printfn):
     from cli.utils import pb2dict
 
     t = TablePrinter()
@@ -111,6 +115,34 @@
     t.print_table(header, printfn)
 
 
+def print_pb_as_table(header, pb, fields_to_omit={}, printfn=_printfn):
+    from cli.utils import pb2dict
+
+    t = TablePrinter()
+
+    def pr(_pb, prefix=''):
+        d = pb2dict(_pb)
+        for field in sorted(_pb._fields, key=lambda f: f.number):
+            fname = prefix + field.name
+            if fname in fields_to_omit:
+                continue
+            value = getattr(_pb, field.name)
+            if isinstance(value, Message):
+                pr(value, fname + '.')
+            elif isinstance(value, RepeatedCompositeFieldContainer):
+                row = t.number_of_rows()
+                t.add_cell(row, 0, 'field', fname)
+                t.add_cell(row, 1, 'value', '{} item(s)'.format(
+                    len(d.get(field.name))))
+            else:
+                row = t.number_of_rows()
+                t.add_cell(row, 0, 'field', fname)
+                t.add_cell(row, 1, 'value', d.get(field.name))
+
+    pr(pb)
+
+    t.print_table(header, printfn)
+
 if __name__ == '__main__':
     import random
     t = TablePrinter()
diff --git a/cli/utils.py b/cli/utils.py
index 941ad9c..b1aad1a 100644
--- a/cli/utils.py
+++ b/cli/utils.py
@@ -83,8 +83,12 @@
     'VLAN_VID': lambda f: (101, 'vlan_vid', p_vlan_vid(f['vlan_vid'])),
     'VLAN_PCP': lambda f: (102, 'vlan_pcp', str(f['vlan_pcp'])),
     'ETH_TYPE': lambda f: (103, 'eth_type', '%X' % f['eth_type']),
-    'IPV4_DST': lambda f: (104, 'ipv4_dst', p_ipv4(f['ipv4_dst'])),
-    'IP_PROTO': lambda f: (105, 'ip_proto', str(f['ip_proto']))
+    'IP_PROTO': lambda f: (104, 'ip_proto', str(f['ip_proto'])),
+    'IPV4_DST': lambda f: (105, 'ipv4_dst', p_ipv4(f['ipv4_dst'])),
+    'UDP_SRC': lambda f: (106, 'udp_src', str(f['udp_src'])),
+    'UDP_DST': lambda f: (107, 'udp_dst', str(f['udp_dst'])),
+    'TCP_SRC': lambda f: (108, 'tcp_src', str(f['tcp_src'])),
+    'TCP_DST': lambda f: (109, 'tcp_dst', str(f['tcp_dst'])),
 }
 
 
diff --git a/voltha/adapters/simulated_olt/simulated_olt.py b/voltha/adapters/simulated_olt/simulated_olt.py
index f550f97..1fdc965 100644
--- a/voltha/adapters/simulated_olt/simulated_olt.py
+++ b/voltha/adapters/simulated_olt/simulated_olt.py
@@ -30,6 +30,7 @@
 
 from common.utils.asleep import asleep
 from voltha.adapters.interface import IAdapterInterface
+from voltha.core.flow_decomposer import *
 from voltha.core.logical_device_agent import mac_str_to_tuple
 from voltha.protos.adapter_pb2 import Adapter, AdapterConfig
 from voltha.protos.device_pb2 import DeviceType, DeviceTypes, Device, Port
@@ -355,6 +356,143 @@
         log.debug('bulk-flow-update', device_id=device.id,
                   flows=flows, groups=groups)
 
+        # sample code that analyzes the incoming flow table
+        assert len(groups.items) == 0, "Cannot yet deal with groups"
+
+        for flow in flows.items:
+            in_port = get_in_port(flow)
+            assert in_port is not None
+
+            if in_port == 2:
+
+                # Downstream rule
+
+                for field in get_ofb_fields(flow):
+                    if field.type == ETH_TYPE:
+                        _type = field.eth_type
+                        pass  # construct ether type based condition here
+
+                    elif field.type == IP_PROTO:
+                        _proto = field.ip_proto
+                        pass  # construct ip_proto based condition here
+
+                    elif field.type == IN_PORT:
+                        _port = field.port
+                        pass  # construct in_port based condition here
+
+                    elif field.type == VLAN_VID:
+                        _vlan_vid = field.vlan_vid
+                        pass  # construct VLAN ID based filter condition here
+
+                    elif field.type == VLAN_PCP:
+                        _vlan_pcp = field.vlan_pcp
+                        pass  # construct VLAN PCP based filter condition here
+
+                    # TODO
+                    else:
+                        raise NotImplementedError('field.type={}'.format(
+                            field.type))
+
+                for action in get_actions(flow):
+
+                    if action.type == OUTPUT:
+                        pass  # construct packet emit rule here
+
+                    elif action.type == PUSH_VLAN:
+                        if action.push.ethertype != 0x8100:
+                            log.error('unhandled-ether-type',
+                                      ethertype=action.push.ethertype)
+                        pass  # construct vlan push command here
+
+                    elif action.type == POP_VLAN:
+                        pass  # construct vlan pop command here
+
+                    elif action.type == SET_FIELD:
+                        assert (action.set_field.field.oxm_class ==
+                                ofp.OFPXMC_OPENFLOW_BASIC)
+                        field = action.set_field.field.ofb_field
+                        if field.type == VLAN_VID:
+                            pass  # construct vlan_id set command here
+                        else:
+                            log.error('unsupported-action-set-field-type',
+                                      field_type=field.type)
+
+                    else:
+                        log.error('unsupported-action-type',
+                                  action_type=action.type)
+
+                # final assembly of low level device flow rule and pushing it
+                # down to device
+                pass
+
+            elif in_port == 1:
+
+                # Upstream rule
+
+                for field in get_ofb_fields(flow):
+
+                    if field.type == ETH_TYPE:
+                        _type = field.eth_type
+                        pass  # construct ether type based condition here
+
+                    elif field.type == IP_PROTO:
+                        _proto = field.ip_proto
+                        pass  # construct ip_proto based condition here
+
+                    elif field.type == IN_PORT:
+                        _port = field.port
+                        pass  # construct in_port based condition here
+
+                    elif field.type == VLAN_VID:
+                        _vlan_vid = field.vlan_vid
+                        pass  # construct VLAN ID based filter condition here
+
+                    elif field.type == VLAN_PCP:
+                        _vlan_pcp = field.vlan_pcp
+                        pass  # construct VLAN PCP based filter condition here
+
+                    elif field.type == UDP_DST:
+                        _udp_dst = field.udp_dst
+                        pass  # construct UDP SDT based filter here
+
+                    # TODO
+                    else:
+                        raise NotImplementedError('field.type={}'.format(
+                            field.type))
+
+                for action in get_actions(flow):
+
+                    if action.type == OUTPUT:
+                        pass  # construct packet emit rule here
+
+                    elif action.type == PUSH_VLAN:
+                        if action.push.ethertype != 0x8100:
+                            log.error('unhandled-ether-type',
+                                      ethertype=action.push.ethertype)
+                        pass  # construct vlan push command here
+
+                    elif action.type == SET_FIELD:
+                        assert (action.set_field.field.oxm_class ==
+                                ofp.OFPXMC_OPENFLOW_BASIC)
+                        field = action.set_field.field.ofb_field
+                        if field.type == VLAN_VID:
+                            pass  # construct vlan_id set command here
+                        else:
+                            log.error('unsupported-action-set-field-type',
+                                      field_type=field.type)
+
+                    else:
+                        log.error('unsupported-action-type',
+                                  action_type=action.type)
+
+                # final assembly of low level device flow rule and pushing it
+                # down to device
+                pass
+
+            else:
+                raise Exception('Port should be 1 or 2 by our convention')
+
+
     def update_flows_incrementally(self, device, flow_changes, group_changes):
         raise NotImplementedError()
 
diff --git a/voltha/adapters/simulated_onu/simulated_onu.py b/voltha/adapters/simulated_onu/simulated_onu.py
index 73a2f8f..bdddb99 100644
--- a/voltha/adapters/simulated_onu/simulated_onu.py
+++ b/voltha/adapters/simulated_onu/simulated_onu.py
@@ -26,6 +26,7 @@
 
 from common.utils.asleep import asleep
 from voltha.adapters.interface import IAdapterInterface
+from voltha.core.flow_decomposer import *
 from voltha.core.logical_device_agent import mac_str_to_tuple
 from voltha.protos.adapter_pb2 import Adapter, AdapterConfig
 from voltha.protos.device_pb2 import DeviceType, DeviceTypes, Device, Port
@@ -184,6 +185,146 @@
         log.debug('bulk-flow-update', device_id=device.id,
                   flows=flows, groups=groups)
 
+        # sample code that analyzes the incoming flow table
+        assert len(groups.items) == 0, "Cannot yet deal with groups"
+
+        for flow in flows.items:
+            in_port = get_in_port(flow)
+            assert in_port is not None
+
+            if in_port == 2:
+
+                # Downstream rule
+
+                for field in get_ofb_fields(flow):
+                    if field.type == ETH_TYPE:
+                        _type = field.eth_type
+                        pass  # construct ether type based condition here
+
+                    elif field.type == IP_PROTO:
+                        _proto = field.ip_proto
+                        pass  # construct ip_proto based condition here
+
+                    elif field.type == IN_PORT:
+                        _port = field.port
+                        pass  # construct in_port based condition here
+
+                    elif field.type == VLAN_VID:
+                        _vlan_vid = field.vlan_vid
+                        pass  # construct VLAN ID based filter condition here
+
+                    elif field.type == VLAN_PCP:
+                        _vlan_pcp = field.vlan_pcp
+                        pass  # construct VLAN PCP based filter condition here
+
+                    # TODO
+                    else:
+                        raise NotImplementedError('field.type={}'.format(
+                            field.type))
+
+                for action in get_actions(flow):
+
+                    if action.type == OUTPUT:
+                        pass  # construct packet emit rule here
+
+                    elif action.type == PUSH_VLAN:
+                        if action.push.ethertype != 0x8100:
+                            log.error('unhandled-ether-type',
+                                      ethertype=action.push.ethertype)
+                        pass  # construct vlan push command here
+
+                    elif action.type == POP_VLAN:
+                        pass  # construct vlan pop command here
+
+                    elif action.type == SET_FIELD:
+                        assert (action.set_field.field.oxm_class ==
+                                ofp.OFPXMC_OPENFLOW_BASIC)
+                        field = action.set_field.field.ofb_field
+                        if field.type == VLAN_VID:
+                            pass  # construct vlan_id set command here
+                        else:
+                            log.error('unsupported-action-set-field-type',
+                                      field_type=field.type)
+
+                    else:
+                        log.error('unsupported-action-type',
+                                  action_type=action.type)
+
+                # final assembly of low level device flow rule and pushing it
+                # down to device
+                pass
+
+            elif in_port == 1:
+
+                # Upstream rule
+
+                for field in get_ofb_fields(flow):
+                    if field.type == ETH_TYPE:
+                        _type = field.eth_type
+                        pass  # construct ether type based condition here
+
+                    elif field.type == IP_PROTO:
+                        _proto = field.ip_proto
+                        pass  # construct ip_proto based condition here
+
+                    elif field.type == IN_PORT:
+                        _port = field.port
+                        pass  # construct in_port based condition here
+
+                    elif field.type == VLAN_VID:
+                        _vlan_vid = field.vlan_vid
+                        pass  # construct VLAN ID based filter condition here
+
+                    elif field.type == VLAN_PCP:
+                        _vlan_pcp = field.vlan_pcp
+                        pass  # construct VLAN PCP based filter condition here
+
+                    elif field.type == IPV4_DST:
+                        _ipv4_dst = field.ipv4_dst
+                        pass  # construct IPv4 DST address based condition
+
+                    elif field.type == UDP_DST:
+                        _udp_dst = field.udp_dst
+                        pass  # construct UDP SDT based filter here
+
+                    # TODO
+                    else:
+                        raise NotImplementedError('field.type={}'.format(
+                            field.type))
+
+                for action in get_actions(flow):
+
+                    if action.type == OUTPUT:
+                        pass  # construct packet emit rule here
+
+                    elif action.type == PUSH_VLAN:
+                        if action.push.ethertype != 0x8100:
+                            log.error('unhandled-ether-type',
+                                      ethertype=action.push.ethertype)
+                        pass  # construct vlan push command here
+
+                    elif action.type == SET_FIELD:
+                        assert (action.set_field.field.oxm_class ==
+                                ofp.OFPXMC_OPENFLOW_BASIC)
+                        field = action.set_field.field.ofb_field
+                        if field.type == VLAN_VID:
+                            pass  # construct vlan_id set command here
+                        else:
+                            log.error('unsupported-action-set-field-type',
+                                      field_type=field.type)
+
+                    else:
+                        log.error('unsupported-action-type',
+                                  action_type=action.type)
+
+                # final assembly of low level device flow rule and pushing it
+                # down to device
+                pass
+
+            else:
+                raise Exception('Port should be 1 or 2 by our convention')
+
+
     def update_flows_incrementally(self, device, flow_changes, group_changes):
         raise NotImplementedError()
 
diff --git a/voltha/adapters/tibit_olt/tibit_olt.py b/voltha/adapters/tibit_olt/tibit_olt.py
index 45d610f..094b01d 100644
--- a/voltha/adapters/tibit_olt/tibit_olt.py
+++ b/voltha/adapters/tibit_olt/tibit_olt.py
@@ -59,6 +59,7 @@
 is_tibit_frame = BpfProgramFilter(frame_match)
 #is_tibit_frame = lambda x: True
 
+
 # To be removed in favor of OAM
 class TBJSON(Packet):
     """ TBJSON 'packet' layer. """
@@ -67,6 +68,7 @@
 
 bind_layers(Ether, TBJSON, type=0x9001)
 
+
 @implementer(IAdapterInterface)
 class TibitOltAdapter(object):
 
@@ -363,6 +365,7 @@
                 req /= PortIngressRuleHeader(precedence=precedence)
 
                 for field in get_ofb_fields(flow):
+
                     if field.type == ETH_TYPE:
                         _type = field.eth_type
                         req /= PortIngressRuleClauseMatchLength02(
@@ -370,15 +373,39 @@
                             operator=1,
                             match0=(_type >> 8) & 0xff,
                             match1=_type & 0xff)
+
                     elif field.type == IP_PROTO:
-                        pass
-                    # TODO etc
+                        _proto = field.ip_proto
+                        pass  # construct ip_proto based condition here
+
+                    elif field.type == IN_PORT:
+                        _port = field.port
+                        pass  # construct in_port based condition here
+
+                    elif field.type == VLAN_VID:
+                        _vlan_vid = field.vlan_vid
+                        pass  # construct VLAN ID based filter condition here
+
+                    elif field.type == VLAN_PCP:
+                        _vlan_pcp = field.vlan_pcp
+                        pass  # construct VLAN PCP based filter condition here
+
+                    elif field.type == UDP_DST:
+                        _udp_dst = field.udp_dst
+                        pass  # construct UDP SDT based filter here
+
+                    else:
+                        raise NotImplementedError('field.type={}'.format(
+                            field.type))
 
                 for action in get_actions(flow):
 
                     if action.type == OUTPUT:
                         req /= PortIngressRuleResultForward()
 
+                    elif action.type == POP_VLAN:
+                        pass  # construct vlan pop command here
+
                     elif action.type == PUSH_VLAN:
                         if action.push.ethertype != 0x8100:
                             log.error('unhandled-ether-type',
diff --git a/voltha/adapters/tibit_onu/tibit_onu.py b/voltha/adapters/tibit_onu/tibit_onu.py
index 1d7f279..1ab620d 100644
--- a/voltha/adapters/tibit_onu/tibit_onu.py
+++ b/voltha/adapters/tibit_onu/tibit_onu.py
@@ -30,6 +30,7 @@
 from twisted.internet.defer import DeferredQueue, inlineCallbacks
 from twisted.internet import reactor
 
+from voltha.core.flow_decomposer import *
 from voltha.core.logical_device_agent import mac_str_to_tuple
 
 from voltha.adapters.interface import IAdapterInterface
@@ -197,6 +198,145 @@
         log.debug('bulk-flow-update', device_id=device.id,
                   flows=flows, groups=groups)
 
+        # sample code that analyzes the incoming flow table
+        assert len(groups.items) == 0, "Cannot yet deal with groups"
+
+        for flow in flows.items:
+            in_port = get_in_port(flow)
+            assert in_port is not None
+
+            if in_port == 2:
+
+                # Downstream rule
+
+                for field in get_ofb_fields(flow):
+                    if field.type == ETH_TYPE:
+                        _type = field.eth_type
+                        pass  # construct ether type based condition here
+
+                    elif field.type == IP_PROTO:
+                        _proto = field.ip_proto
+                        pass  # construct ip_proto based condition here
+
+                    elif field.type == IN_PORT:
+                        _port = field.port
+                        pass  # construct in_port based condition here
+
+                    elif field.type == VLAN_VID:
+                        _vlan_vid = field.vlan_vid
+                        pass  # construct VLAN ID based filter condition here
+
+                    elif field.type == VLAN_PCP:
+                        _vlan_pcp = field.vlan_pcp
+                        pass  # construct VLAN PCP based filter condition here
+
+                    # TODO
+                    else:
+                        raise NotImplementedError('field.type={}'.format(
+                            field.type))
+
+                for action in get_actions(flow):
+
+                    if action.type == OUTPUT:
+                        pass  # construct packet emit rule here
+
+                    elif action.type == PUSH_VLAN:
+                        if action.push.ethertype != 0x8100:
+                            log.error('unhandled-ether-type',
+                                      ethertype=action.push.ethertype)
+                        pass  # construct vlan push command here
+
+                    elif action.type == POP_VLAN:
+                        pass  # construct vlan pop command here
+
+                    elif action.type == SET_FIELD:
+                        assert (action.set_field.field.oxm_class ==
+                                ofp.OFPXMC_OPENFLOW_BASIC)
+                        field = action.set_field.field.ofb_field
+                        if field.type == VLAN_VID:
+                            pass  # construct vlan_id set command here
+                        else:
+                            log.error('unsupported-action-set-field-type',
+                                      field_type=field.type)
+
+                    else:
+                        log.error('unsupported-action-type',
+                                  action_type=action.type)
+
+                # final assembly of low level device flow rule and pushing it
+                # down to device
+                pass
+
+            elif in_port == 1:
+
+                # Upstream rule
+
+                for field in get_ofb_fields(flow):
+                    if field.type == ETH_TYPE:
+                        _type = field.eth_type
+                        pass  # construct ether type based condition here
+
+                    elif field.type == IP_PROTO:
+                        _proto = field.ip_proto
+                        pass  # construct ip_proto based condition here
+
+                    elif field.type == IN_PORT:
+                        _port = field.port
+                        pass  # construct in_port based condition here
+
+                    elif field.type == VLAN_VID:
+                        _vlan_vid = field.vlan_vid
+                        pass  # construct VLAN ID based filter condition here
+
+                    elif field.type == VLAN_PCP:
+                        _vlan_pcp = field.vlan_pcp
+                        pass  # construct VLAN PCP based filter condition here
+
+                    elif field.type == IPV4_DST:
+                        _ipv4_dst = field.ipv4_dst
+                        pass  # construct IPv4 DST address based condition
+
+                    elif field.type == UDP_DST:
+                        _udp_dst = field.udp_dst
+                        pass  # construct UDP SDT based filter here
+
+                    # TODO
+                    else:
+                        raise NotImplementedError('field.type={}'.format(
+                            field.type))
+
+                for action in get_actions(flow):
+
+                    if action.type == OUTPUT:
+                        pass  # construct packet emit rule here
+
+                    elif action.type == PUSH_VLAN:
+                        if action.push.ethertype != 0x8100:
+                            log.error('unhandled-ether-type',
+                                      ethertype=action.push.ethertype)
+                        pass  # construct vlan push command here
+
+                    elif action.type == SET_FIELD:
+                        assert (action.set_field.field.oxm_class ==
+                                ofp.OFPXMC_OPENFLOW_BASIC)
+                        field = action.set_field.field.ofb_field
+                        if field.type == VLAN_VID:
+                            pass  # construct vlan_id set command here
+                        else:
+                            log.error('unsupported-action-set-field-type',
+                                      field_type=field.type)
+
+                    else:
+                        log.error('unsupported-action-type',
+                                  action_type=action.type)
+
+                # final assembly of low level device flow rule and pushing it
+                # down to device
+                pass
+
+            else:
+                raise Exception('Port should be 1 or 2 by our convention')
+
     def update_flows_incrementally(self, device, flow_changes, group_changes):
         raise NotImplementedError()
 
diff --git a/voltha/core/logical_device_agent.py b/voltha/core/logical_device_agent.py
index afc4ef3..f11247e 100644
--- a/voltha/core/logical_device_agent.py
+++ b/voltha/core/logical_device_agent.py
@@ -417,7 +417,8 @@
 
             else:
                 flows = list(self.flows_proxy.get('/').items)
-                flows_changed, flows = self.flows_delete_by_group_id(flows, group_id)
+                flows_changed, flows = self.flows_delete_by_group_id(
+                    flows, group_id)
                 del groups[group_id]
                 groups_changed = True
                 self.log.debug('group-deleted', group_id=group_id)