VOL-1623-meter support and handling  techprofile and fix for flow delete , now migrated to onosproject/onos:1.13.9-rc4

Change in flowupdate API towards adapters

Remove meter_get API from adapter to core

Added dependent vendor library files downloaded  by "dep-ensure -update"

Added techprofile changes in the single commit

Review comments are addressed

submiting patch for  integration tests for meter changes and modifications in unit test for updated flow decomposer logic
  - submitting on behalf of "Salman.Siddiqui@radisys.com"

Load test for meter updated and other flow management test cases with meter
- Performed load test for 1K meters serially and parallely and added more TC in flow management

Rebased

Load test for meter updated and other flow management test cases with meter
- Performed load test for 1K meters serially and parallely and added more TC in flow management
- submitting on behalf of "Salman.Siddiqui@radisys.com"

pulled latest protos

verified EAPOL/DHCP/HSIA data with Edgecore OLT & TW ONT kit for one subcriber
verified delete/re-add is working end to end for the same subscriber

Change-Id: Idb232b7a0f05dc0c7e68266ac885740a3adff317
diff --git a/python/cli/logical_device.py b/python/cli/logical_device.py
index 187dd88..a49f6f8 100644
--- a/python/cli/logical_device.py
+++ b/python/cli/logical_device.py
@@ -96,6 +96,16 @@
             groups=logical_device['flow_groups']['items']
         )
 
+    def do_meters(self, _):
+        """Show flow meter table for logical device"""
+        logical_device = pb2dict(self.get_logical_device(-1))
+        print_meters(
+            'Logical Device',
+            self.logical_device_id,
+            type='n/a',
+            meters=logical_device['meters']['items']
+        )
+
     def do_devices(self, line):
         """List devices that belong to this logical device"""
         logical_device = self.get_logical_device()
diff --git a/python/cli/utils.py b/python/cli/utils.py
index 1f72be3..7668b7c 100644
--- a/python/cli/utils.py
+++ b/python/cli/utils.py
@@ -140,15 +140,21 @@
 
         for instruction in flow['instructions']:
             itype = instruction['type']
-            if itype == 4:
+            if itype == 4 or itype == 3:
                 for action in instruction['actions']['actions']:
                     atype = action['type'][len('OFPAT_'):]
                     table.add_cell(i, *action_printers[atype](action))
             elif itype == 1:
                 table.add_cell(i, 10000, 'goto-table',
                                instruction['goto_table']['table_id'])
+            elif itype == 2:
+                table.add_cell(i, 10001, 'write-metadata',
+                               instruction['write_metadata']['metadata'])
             elif itype == 5:
-                table.add_cell(i, 10000, 'clear-actions', [])
+                table.add_cell(i, 10002, 'clear-actions', [])
+            elif itype == 6:
+                table.add_cell(i, 10003, 'meter',
+                               instruction['meter']['meter_id'])
             else:
                 raise NotImplementedError(
                     'not handling instruction type {}'.format(itype))
@@ -177,6 +183,26 @@
 
     table.print_table(header, printfn)
 
+def print_meters(what, id, type, meters, printfn=_printfn):
+    header = ''.join([
+        '{} '.format(what),
+        colored(id, color='green', attrs=['bold']),
+        ' (type: ',
+        colored(type, color='blue'),
+        ')'
+    ]) + '\nMeters ({}):'.format(len(meters))
+
+    table = TablePrinter()
+    for i, meter in enumerate(meters):
+        bands = []
+        for meter_band in meter['config']['bands']:
+            bands.append(meter_band)
+        table.add_cell(i, 0, 'meter_id', value=str(meter['config']['meter_id']))
+        table.add_cell(i, 1, 'meter_bands', value=str(dict(bands=bands)))
+
+    table.print_table(header, printfn)
+
+
 def dict2line(d):
     assert isinstance(d, dict)
     return ', '.join('{}: {}'.format(k, v) for k, v in sorted(d.items()))
diff --git a/python/ofagent/converter.py b/python/ofagent/converter.py
index 185fb59..6280331 100755
--- a/python/ofagent/converter.py
+++ b/python/ofagent/converter.py
@@ -65,6 +65,24 @@
     kw = pb2dict(pb)
     return of13.port_stats_entry(**kw)
 
+
+def ofp_meter_stats_to_loxi_meter_stats(pb):
+    return of13.meter_stats(
+        meter_id=pb.meter_id,
+        flow_count=pb.flow_count,
+        packet_in_count=pb.packet_in_count,
+        byte_in_count=pb.byte_in_count,
+        duration_sec=pb.duration_sec,
+        duration_nsec=pb.duration_nsec,
+        band_stats=[to_loxi(band_stat) for band_stat in pb.band_stats])
+
+
+def ofp_meter_band_stats_to_loxi_meter_stats(pb):
+    return of13.meter_band_stats(
+        packet_band_count=pb.packet_band_count,
+        byte_band_count=pb.byte_band_count
+    )
+
 def make_loxi_field(oxm_field):
     assert oxm_field['oxm_class'] == pb2.OFPXMC_OPENFLOW_BASIC
     ofb_field = oxm_field['ofb_field']
@@ -179,6 +197,7 @@
         elif type == pb2.OFPIT_METER:
             return of13.instruction.meter(
                 meter_id=inst['meter']['meter_id'])
+
         else:
             raise NotImplementedError('Instruction type %d' % type)
 
@@ -244,7 +263,9 @@
     'ofp_bucket_counter': ofp_bucket_counter_to_loxy_bucket_counter,
     'ofp_bucket': ofp_bucket_to_loxi_bucket,
     'ofp_action': make_loxi_action,
-    'ofp_port_stats': ofp_port_stats_to_loxi_port_stats
+    'ofp_port_stats': ofp_port_stats_to_loxi_port_stats,
+    'ofp_meter_stats': ofp_meter_stats_to_loxi_meter_stats,
+    'ofp_meter_band_stats': ofp_meter_band_stats_to_loxi_meter_stats
 }
 
 
@@ -264,6 +285,97 @@
         match=to_grpc(lo.match),
         instructions=[to_grpc(i) for i in lo.instructions])
 
+def loxi_meter_mod_to_ofp_meter_mod(lo):
+    return pb2.ofp_meter_mod(
+        command=lo.command,
+        flags=lo.flags,
+        meter_id=lo.meter_id,
+        bands=[to_grpc(i) for i in lo.meters])
+
+
+def loxi_meter_band_drop_to_ofp_meter_band_drop(lo):
+    return pb2.ofp_meter_band_header(
+        type=lo.type,
+        rate=lo.rate,
+        burst_size=lo.burst_size)
+
+
+def loxi_meter_band_dscp_remark_to_ofp_meter_band_dscp_remark(lo):
+    return pb2.ofp_meter_band_header(
+        type=lo.type,
+        rate=lo.rate,
+        burst_size=lo.burst_size,
+        dscp_remark=pb2.ofp_meter_band_dscp_remark(prec_level=lo.prec_level))
+
+
+def loxi_meter_band_experimenter_to_ofp_meter_band_experimenter(lo):
+    return pb2.ofp_meter_band_header(
+        type=lo.type,
+        rate=lo.rate,
+        burst_size=lo.burst_size,
+        experimenter=pb2.ofp_meter_band_experimenter(experimenter=lo.experimenter))
+
+
+def loxi_meter_multipart_request_to_ofp_meter_multipart_request(lo):
+    return pb2.ofp_meter_multipart_request(
+        meter_id=lo.meter_id)
+
+
+def loxi_meter_stats_to_ofp_meter_stats(lo):
+    return pb2.ofp_meter_stats(
+        meter_id=lo.meter_id,
+        flow_count=lo.flow_count,
+        packet_in_count =lo.packet_in_count,
+        byte_in_count=lo.byte_in_count,
+        duration_sec=lo.duration_sec,
+        duration_nsec=lo.duration_nsec,
+        band_stats=lo.band_stats)
+
+
+def loxi_meter_mod_to_ofp_meter_mod(lo):
+    return pb2.ofp_meter_mod(
+        command=lo.command,
+        flags=lo.flags,
+        meter_id=lo.meter_id,
+        bands=[to_grpc(i) for i in lo.meters])
+
+
+def loxi_meter_band_drop_to_ofp_meter_band_drop(lo):
+    return pb2.ofp_meter_band_header(
+        type=lo.type,
+        rate=lo.rate,
+        burst_size=lo.burst_size)
+
+
+def loxi_meter_band_dscp_remark_to_ofp_meter_band_dscp_remark(lo):
+    return pb2.ofp_meter_band_header(
+        type=lo.type,
+        rate=lo.rate,
+        burst_size=lo.burst_size,
+        dscp_remark=pb2.ofp_meter_band_dscp_remark(prec_level=lo.prec_level))
+
+
+def loxi_meter_band_experimenter_to_ofp_meter_band_experimenter(lo):
+    return pb2.ofp_meter_band_header(
+        type=lo.type,
+        rate=lo.rate,
+        burst_size=lo.burst_size,
+        experimenter=pb2.ofp_meter_band_experimenter(experimenter=lo.experimenter))
+
+
+def loxi_meter_multipart_request_to_ofp_meter_multipart_request(lo):
+    return pb2.ofp_meter_multipart_request(
+        meter_id=lo.meter_id)
+
+def loxi_meter_stats_to_ofp_meter_stats(lo):
+    return pb2.ofp_meter_stats(
+        meter_id=lo.meter_id,
+        flow_count=lo.flow_count,
+        packet_in_count =lo.packet_in_count,
+        byte_in_count=lo.byte_in_count,
+        duration_sec=lo.duration_sec,
+        duration_nsec=lo.duration_nsec,
+        band_stats=lo.band_stats)
 
 def loxi_group_mod_to_ofp_group_mod(lo):
     return pb2.ofp_group_mod(
@@ -384,12 +496,39 @@
         goto_table=pb2.ofp_instruction_goto_table(table_id=lo.table_id))
 
 
+def loxi_write_metadata_to_ofp_instruction(lo):
+    return pb2.ofp_instruction(
+        type=pb2.OFPIT_WRITE_METADATA,
+        write_metadata=pb2.ofp_instruction_write_metadata(
+            metadata=lo.metadata,
+            metadata_mask=lo.metadata_mask))
+
+
+def loxi_meter_to_ofp_instruction(lo):
+    return pb2.ofp_instruction(
+        type=pb2.OFPIT_METER,
+        meter=pb2.ofp_instruction_meter(meter_id=lo.meter_id))
+
+
 def loxi_output_action_to_ofp_action(lo):
     return pb2.ofp_action(
         type=pb2.OFPAT_OUTPUT,
         output=pb2.ofp_action_output(port=lo.port, max_len=lo.max_len))
 
 
+def loxi_write_actions_to_ofp_instruction(lo):
+    return pb2.ofp_instruction(
+        type=pb2.OFPIT_WRITE_ACTIONS,
+        actions=pb2.ofp_instruction_actions(
+            actions=[to_grpc(a) for a in lo.actions]))
+
+
+def loxi_meter_to_ofp_instruction(lo):
+    return pb2.ofp_instruction(
+        type=pb2.OFPIT_METER,
+        meter=pb2.ofp_instruction_meter(meter_id=lo.meter_id))
+
+
 def loxi_group_action_to_ofp_action(lo):
     return pb2.ofp_action(
         type=pb2.OFPAT_GROUP,
@@ -419,12 +558,18 @@
     of13.message.flow_delete_strict: loxi_flow_mod_to_ofp_flow_mod,
     of13.message.flow_modify: loxi_flow_mod_to_ofp_flow_mod,
     of13.message.flow_modify_strict: loxi_flow_mod_to_ofp_flow_mod,
+    of13.message.meter_mod: loxi_meter_mod_to_ofp_meter_mod,
+    of13.message.meter_stats_request: loxi_meter_stats_to_ofp_meter_stats,
 
     of13.message.group_add: loxi_group_mod_to_ofp_group_mod,
     of13.message.group_delete: loxi_group_mod_to_ofp_group_mod,
     of13.message.group_modify: loxi_group_mod_to_ofp_group_mod,
     of13.message.packet_out: loxi_packet_out_to_ofp_packet_out,
 
+    of13.meter_band.drop: loxi_meter_band_drop_to_ofp_meter_band_drop,
+    of13.meter_band.dscp_remark: loxi_meter_band_dscp_remark_to_ofp_meter_band_dscp_remark,
+    of13.meter_band.experimenter: loxi_meter_band_experimenter_to_ofp_meter_band_experimenter,
+
     of13.common.match_v3: loxi_match_v3_to_ofp_match,
     of13.common.bucket: loxi_bucket_to_ofp_bucket,
 
@@ -440,7 +585,10 @@
 
     of13.instruction.apply_actions: loxi_apply_actions_to_ofp_instruction,
     of13.instruction.clear_actions: loxi_clear_actions_to_ofp_instruction,
+    of13.instruction.write_actions: loxi_write_actions_to_ofp_instruction,
     of13.instruction.goto_table: loxi_goto_table_to_ofp_instruction,
+    of13.instruction.write_metadata: loxi_write_metadata_to_ofp_instruction,
+    of13.instruction.meter: loxi_meter_to_ofp_instruction,
 
     of13.action.output: loxi_output_action_to_ofp_action,
     of13.action.group: loxi_group_action_to_ofp_action,
diff --git a/python/ofagent/grpc_client.py b/python/ofagent/grpc_client.py
index 42e8510..e4b260e 100755
--- a/python/ofagent/grpc_client.py
+++ b/python/ofagent/grpc_client.py
@@ -29,7 +29,7 @@
 from twisted.internet.defer import inlineCallbacks, returnValue, DeferredQueue
 
 from voltha_protos.voltha_pb2_grpc import VolthaServiceStub
-from voltha_protos.voltha_pb2 import ID, FlowTableUpdate, \
+from voltha_protos.voltha_pb2 import ID, FlowTableUpdate, MeterModUpdate, \
     FlowGroupTableUpdate, PacketOut
 from voltha_protos.logical_device_pb2 import LogicalPortId
 from google.protobuf import empty_pb2
@@ -246,6 +246,20 @@
         returnValue(res)
 
     @inlineCallbacks
+    def update_meter_mod_table(self, device_id, meter_mod):
+        log.debug('In update_meter_mod_table grpc')
+        req = MeterModUpdate(
+            id=device_id,
+            meter_mod=meter_mod
+        )
+        res = yield threads.deferToThread(
+            self.grpc_stub.UpdateLogicalDeviceMeterTable, req, timeout=self.grpc_timeout,
+            metadata=((self.core_group_id_key, self.core_group_id),
+                      self.get_core_transaction_metadata(),))
+        log.debug('update_meter_mod_table grpc done')
+        returnValue(res)
+
+    @inlineCallbacks
     def update_group_table(self, device_id, group_mod):
         req = FlowGroupTableUpdate(
             id=device_id,
@@ -310,3 +324,14 @@
                     self.core_group_id = pair[1]
                     log.debug('core-binding', core_group=self.core_group_id)
         returnValue(res)
+
+    @inlineCallbacks
+    def list_meters(self, device_id):
+        log.debug('list_meters')
+        req = ID(id=device_id)
+        res = yield threads.deferToThread(
+            self.grpc_stub.ListLogicalDeviceMeters, req, timeout=self.grpc_timeout,
+            metadata=((self.core_group_id_key, self.core_group_id),
+                      self.get_core_transaction_metadata(),))
+        log.debug('done stat query', resp=res)
+        returnValue(res.items)
diff --git a/python/ofagent/of_protocol_handler.py b/python/ofagent/of_protocol_handler.py
index 604ce3c..e90ca5b 100755
--- a/python/ofagent/of_protocol_handler.py
+++ b/python/ofagent/of_protocol_handler.py
@@ -29,6 +29,10 @@
 
     ofp_version = [4]  # OFAgent supported versions
 
+    MAX_METER_IDS = 4294967295
+    MAX_METER_BANDS = 255
+    MAX_METER_COLORS = 255
+
     def __init__(self, datapath_id, device_id, agent, cxn, rpc):
         """
         The upper half of the OpenFlow protocol, focusing on message
@@ -128,6 +132,30 @@
         elif self.role == ofp.OFPCR_ROLE_SLAVE:
            self.cxn.send(ofp.message.bad_request_error_msg(code=ofp.OFPBRC_IS_SLAVE))
 
+
+    def handle_meter_mod_request(self, req):
+        log.info('Received  handle_meter_mod_request', request=req)
+        if self.role == ofp.OFPCR_ROLE_MASTER or self.role == ofp.OFPCR_ROLE_EQUAL:
+            try:
+                grpc_req = to_grpc(req)
+            except Exception, e:
+                log.exception('failed-to-convert-meter-mod-request', e=e)
+            else:
+                return self.rpc.update_meter_mod_table(self.device_id, grpc_req)
+
+        elif self.role == ofp.OFPCR_ROLE_SLAVE:
+            self.cxn.send(ofp.message.bad_request_error_msg(code=ofp.OFPBRC_IS_SLAVE))
+
+    @inlineCallbacks
+    def handle_meter_stats_request(self, req):
+        log.info('Received  handle_meter_stats_request', request=req)
+        try:
+            meters = yield self.rpc.list_meters(self.device_id)
+            self.cxn.send(ofp.message.meter_stats_reply(
+                xid=req.xid, entries=[to_loxi(m.stats) for m in meters]))
+        except Exception, e:
+            log.exception("failed-meter-stats-request", req=req, e=e)
+
     def handle_get_async_request(self, req):
         raise NotImplementedError()
 
@@ -144,10 +172,6 @@
         elif self.role == ofp.OFPCR_ROLE_SLAVE:
            self.cxn.send(ofp.message.bad_request_error_msg(code=ofp.OFPBRC_IS_SLAVE))
 
-
-    def handle_meter_mod_request(self, req):
-        raise NotImplementedError()
-
     def handle_role_request(self, req):
         if req.role == ofp.OFPCR_ROLE_MASTER or req.role == ofp.OFPCR_ROLE_SLAVE:
             if self.agent.generation_is_defined and (
@@ -238,16 +262,17 @@
     def handle_group_features_request(self, req):
         raise NotImplementedError()
 
-    def handle_meter_stats_request(self, req):
-        meter_stats = []  # see https://jira.opencord.org/browse/CORD-825
-        self.cxn.send(ofp.message.meter_stats_reply(
-            xid=req.xid, entries=meter_stats))
-
     def handle_meter_config_request(self, req):
         raise NotImplementedError()
 
     def handle_meter_features_request(self, req):
-        self.cxn.send(ofp.message.bad_request_error_msg())
+        feature = ofp.meter_features(max_meter=OpenFlowProtocolHandler.MAX_METER_IDS,
+                                     band_types=ofp.OFPMBT_DROP,
+                                     capabilities=ofp.OFPMF_KBPS,
+                                     max_bands=OpenFlowProtocolHandler.MAX_METER_BANDS,
+                                     max_color=OpenFlowProtocolHandler.MAX_METER_COLORS)
+        self.cxn.send(ofp.message.meter_features_stats_reply(xid=req.xid, flags=None,
+                                                             features=feature))
 
     @inlineCallbacks
     def handle_port_stats_request(self, req):
diff --git a/python/requirements.txt b/python/requirements.txt
index 556e5e3..b82f310 100644
--- a/python/requirements.txt
+++ b/python/requirements.txt
@@ -59,5 +59,5 @@
 pexpect==4.6.0
 python-consul==0.6.2
 afkak==3.0.0.dev20181106
-voltha-protos>=0.1.4
+voltha-protos==1.0.0
 pyvoltha==0.2.2