VOL-2632 Error propagation from HashFlowStats

Change-Id: If2872e97e2b6c3c751f64dadfef47bfde3a77551
diff --git a/go.mod b/go.mod
index 9888abb..3612c0a 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,7 @@
 	github.com/gogo/protobuf v1.3.0
 	github.com/golang/protobuf v1.3.2
 	github.com/google/uuid v1.1.1
-	github.com/opencord/voltha-lib-go/v3 v3.0.14
+	github.com/opencord/voltha-lib-go/v3 v3.0.15
 	github.com/opencord/voltha-protos/v3 v3.2.3
 	github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
 	github.com/stretchr/testify v1.4.0
diff --git a/go.sum b/go.sum
index fcdaf47..d8fa50f 100644
--- a/go.sum
+++ b/go.sum
@@ -190,8 +190,8 @@
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I=
 github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
-github.com/opencord/voltha-lib-go/v3 v3.0.14 h1:klI6Qt5iA9bTqI42jflSbU6YyEMJRcpaqh6rRw7qNnI=
-github.com/opencord/voltha-lib-go/v3 v3.0.14/go.mod h1:69Y+rVd25Nq2SUeoY7Q1BXtwrcUPllG0erhq+aK8Qec=
+github.com/opencord/voltha-lib-go/v3 v3.0.15 h1:CA+L1iqzugusANjUYI5bneU4dY657mSzlSdzyHalMoU=
+github.com/opencord/voltha-lib-go/v3 v3.0.15/go.mod h1:69Y+rVd25Nq2SUeoY7Q1BXtwrcUPllG0erhq+aK8Qec=
 github.com/opencord/voltha-protos/v3 v3.2.3 h1:Wv73mw1Ye0bCfyhOk5svgrlE2tLizHq6tQluoDq9Vg8=
 github.com/opencord/voltha-protos/v3 v3.2.3/go.mod h1:RIGHt7b80BHpHh3ceodknh0DxUjUHCWSbYbZqRx7Og0=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
diff --git a/rw_core/core/logical_device_agent.go b/rw_core/core/logical_device_agent.go
index cfd486a..fc9dd92 100644
--- a/rw_core/core/logical_device_agent.go
+++ b/rw_core/core/logical_device_agent.go
@@ -814,6 +814,7 @@
 	var flows []*ofp.OfpFlowStats
 	var meters []*ofp.OfpMeterEntry
 	var flow *ofp.OfpFlowStats
+	var err error
 
 	if lDevice.Flows != nil && lDevice.Flows.Items != nil {
 		flows = lDevice.Flows.Items
@@ -832,13 +833,19 @@
 			log.Warnw("overlapped-flows", log.Fields{"logicaldeviceId": agent.logicalDeviceID})
 		} else {
 			//	Add flow
-			flow = fu.FlowStatsEntryFromFlowModMessage(mod)
+			flow, err = fu.FlowStatsEntryFromFlowModMessage(mod)
+			if err != nil {
+				return err
+			}
 			flows = append(flows, flow)
 			updatedFlows = append(updatedFlows, flow)
 			changed = true
 		}
 	} else {
-		flow = fu.FlowStatsEntryFromFlowModMessage(mod)
+		flow, err = fu.FlowStatsEntryFromFlowModMessage(mod)
+		if err != nil {
+			return err
+		}
 		idx := fu.FindFlows(flows, flow)
 		if idx >= 0 {
 			oldFlow := flows[idx]
@@ -964,7 +971,11 @@
 	toDelete := make([]*ofp.OfpFlowStats, 0)
 	for _, f := range flows {
 		// Check whether the flow and the flowmod matches
-		if fu.FlowMatch(f, fu.FlowStatsEntryFromFlowModMessage(mod)) {
+		fs, err := fu.FlowStatsEntryFromFlowModMessage(mod)
+		if err != nil {
+			return err
+		}
+		if fu.FlowMatch(f, fs) {
 			toDelete = append(toDelete, f)
 			continue
 		}
@@ -1098,7 +1109,10 @@
 
 	changedFlow := false
 	changedMeter := false
-	flow := fu.FlowStatsEntryFromFlowModMessage(mod)
+	flow, err := fu.FlowStatsEntryFromFlowModMessage(mod)
+	if err != nil {
+		return err
+	}
 	flowsToDelete := make([]*ofp.OfpFlowStats, 0)
 	idx := fu.FindFlows(flows, flow)
 	if idx >= 0 {
diff --git a/rw_core/flowdecomposition/flow_decomposer.go b/rw_core/flowdecomposition/flow_decomposer.go
index 9e996dc..16754b9 100644
--- a/rw_core/flowdecomposition/flow_decomposer.go
+++ b/rw_core/flowdecomposition/flow_decomposer.go
@@ -74,7 +74,7 @@
 
 // Handles special case of any controller-bound flow for a parent device
 func (fd *FlowDecomposer) updateOutputPortForControllerBoundFlowForParentDevide(flow *ofp.OfpFlowStats,
-	dr *fu.DeviceRules) *fu.DeviceRules {
+	dr *fu.DeviceRules) (*fu.DeviceRules, error) {
 	EAPOL := fu.EthType(0x888e)
 	IGMP := fu.IpProto(2)
 	UDP := fu.IpProto(17)
@@ -100,18 +100,21 @@
 						uint32(ofp.OfpPortNo_OFPP_CONTROLLER))
 				}
 				// Update flow Id as a change in the instruction field will result in a new flow ID
-				f.Id = fu.HashFlowStats(f)
+				var err error
+				if f.Id, err = fu.HashFlowStats(f); err != nil {
+					return nil, err
+				}
 				newDeviceRules.AddFlow(deviceID, (proto.Clone(f)).(*ofp.OfpFlowStats))
 			}
 		}
 	}
 
-	return newDeviceRules
+	return newDeviceRules, nil
 }
 
 //processControllerBoundFlow decomposes trap flows
 func (fd *FlowDecomposer) processControllerBoundFlow(ctx context.Context, agent coreif.LogicalDeviceAgent, path []route.Hop,
-	inPortNo uint32, outPortNo uint32, flow *ofp.OfpFlowStats) *fu.DeviceRules {
+	inPortNo uint32, outPortNo uint32, flow *ofp.OfpFlowStats) (*fu.DeviceRules, error) {
 
 	log.Debugw("trap-flow", log.Fields{"inPortNo": inPortNo, "outPortNo": outPortNo, "flow": flow})
 	deviceRules := fu.NewDeviceRules()
@@ -136,7 +139,11 @@
 		// Augment the matchfields with the ofpfields from the flow
 		fg := fu.NewFlowsAndGroups()
 		fa.MatchFields = append(fa.MatchFields, fu.GetOfbFields(flow, fu.IN_PORT)...)
-		fg.AddFlow(fu.MkFlowStat(fa))
+		fs, err := fu.MkFlowStat(fa)
+		if err != nil {
+			return nil, err
+		}
+		fg.AddFlow(fs)
 		deviceRules.AddFlowsAndGroup(egressHop.DeviceID, fg)
 	} else {
 		// Trap flow for UNI port
@@ -166,7 +173,11 @@
 			// Augment the matchfields with the ofpfields from the flow
 			faParent.MatchFields = append(faParent.MatchFields, fu.GetOfbFields(flow, fu.IN_PORT)...)
 			fgParent := fu.NewFlowsAndGroups()
-			fgParent.AddFlow(fu.MkFlowStat(faParent))
+			fs, err := fu.MkFlowStat(faParent)
+			if err != nil {
+				return nil, err
+			}
+			fgParent.AddFlow(fs)
 			deviceRules.AddFlowsAndGroup(egressHop.DeviceID, fgParent)
 			log.Debugw("parent-trap-flow-set", log.Fields{"flow": faParent})
 
@@ -203,13 +214,17 @@
 				faChild.MatchFields = append(faChild.MatchFields, fu.GetOfbFields(flow, fu.IN_PORT)...)
 			}
 			fgChild := fu.NewFlowsAndGroups()
-			fgChild.AddFlow(fu.MkFlowStat(faChild))
+			fs, err = fu.MkFlowStat(faChild)
+			if err != nil {
+				return nil, err
+			}
+			fgChild.AddFlow(fs)
 			deviceRules.AddFlowsAndGroup(ingressHop.DeviceID, fgChild)
 			log.Debugw("child-trap-flow-set", log.Fields{"flow": faChild})
 		}
 	}
 
-	return deviceRules
+	return deviceRules, nil
 }
 
 // processUpstreamNonControllerBoundFlow processes non-controller bound flow. We assume that anything that is
@@ -217,7 +232,7 @@
 // goto-statement. We also assume that the inner tag is applied at the ONU, while the outer tag is
 // applied at the OLT
 func (fd *FlowDecomposer) processUpstreamNonControllerBoundFlow(ctx context.Context,
-	path []route.Hop, inPortNo uint32, outPortNo uint32, flow *ofp.OfpFlowStats) *fu.DeviceRules {
+	path []route.Hop, inPortNo uint32, outPortNo uint32, flow *ofp.OfpFlowStats) (*fu.DeviceRules, error) {
 
 	log.Debugw("upstream-non-controller-bound-flow", log.Fields{"inPortNo": inPortNo, "outPortNo": outPortNo})
 	deviceRules := fu.NewDeviceRules()
@@ -232,7 +247,7 @@
 		log.Debugw("decomposing-onu-flow-in-upstream-has-next-table", log.Fields{"table_id": flow.TableId})
 		if outPortNo != 0 {
 			log.Warnw("outPort-should-not-be-specified", log.Fields{"outPortNo": outPortNo})
-			return deviceRules
+			return deviceRules, nil
 		}
 		fa := &fu.FlowArgs{
 			KV: fu.OfpFlowModArgs{"priority": uint64(flow.Priority), "cookie": flow.Cookie, "meter_id": uint64(meterID), "write_metadata": metadataFromwriteMetadata},
@@ -249,7 +264,11 @@
 		fa.Actions = append(fa.Actions, fu.Output(ingressHop.Egress))
 
 		fg := fu.NewFlowsAndGroups()
-		fg.AddFlow(fu.MkFlowStat(fa))
+		fs, err := fu.MkFlowStat(fa)
+		if err != nil {
+			return nil, err
+		}
+		fg.AddFlow(fs)
 		deviceRules.AddFlowsAndGroup(ingressHop.DeviceID, fg)
 	} else if flow.TableId == 1 && outPortNo != 0 {
 		log.Debugw("decomposing-olt-flow-in-upstream-has-next-table", log.Fields{"table_id": flow.TableId})
@@ -269,15 +288,19 @@
 		fa.Actions = filteredAction
 
 		fg := fu.NewFlowsAndGroups()
-		fg.AddFlow(fu.MkFlowStat(fa))
+		fs, err := fu.MkFlowStat(fa)
+		if err != nil {
+			return nil, err
+		}
+		fg.AddFlow(fs)
 		deviceRules.AddFlowsAndGroup(egressHop.DeviceID, fg)
 	}
-	return deviceRules
+	return deviceRules, nil
 }
 
 // processDownstreamFlowWithNextTable decomposes downstream flows containing next table ID instructions
 func (fd *FlowDecomposer) processDownstreamFlowWithNextTable(ctx context.Context, agent coreif.LogicalDeviceAgent, path []route.Hop,
-	inPortNo uint32, outPortNo uint32, flow *ofp.OfpFlowStats) *fu.DeviceRules {
+	inPortNo uint32, outPortNo uint32, flow *ofp.OfpFlowStats) (*fu.DeviceRules, error) {
 	log.Debugw("decomposing-olt-flow-in-downstream-flow-with-next-table", log.Fields{"inPortNo": inPortNo, "outPortNo": outPortNo})
 	deviceRules := fu.NewDeviceRules()
 	meterID := fu.GetMeterIdFromFlow(flow)
@@ -285,12 +308,12 @@
 
 	if outPortNo != 0 {
 		log.Warnw("outPort-should-not-be-specified", log.Fields{"outPortNo": outPortNo})
-		return deviceRules
+		return deviceRules, nil
 	}
 
 	if flow.TableId != 0 {
 		log.Warnw("This is not olt pipeline table, so skipping", log.Fields{"tableId": flow.TableId})
-		return deviceRules
+		return deviceRules, nil
 	}
 
 	ingressHop := path[0]
@@ -302,18 +325,18 @@
 			recalculatedRoute, err := agent.GetRoute(ctx, inPortNo, portNumber)
 			if err != nil {
 				log.Errorw("no-route-double-tag", log.Fields{"inPortNo": inPortNo, "outPortNo": outPortNo, "metadata": metadataFromwriteMetadata, "error": err})
-				return deviceRules
+				return deviceRules, nil
 			}
 			switch len(recalculatedRoute) {
 			case 0:
 				log.Errorw("no-route-double-tag", log.Fields{"inPortNo": inPortNo, "outPortNo": portNumber, "comment": "deleting-flow", "metadata": metadataFromwriteMetadata})
 				//TODO: Delete flow
-				return deviceRules
+				return deviceRules, nil
 			case 2:
 				log.Debugw("route-found", log.Fields{"ingressHop": ingressHop, "egressHop": egressHop})
 			default:
 				log.Errorw("invalid-route-length", log.Fields{"routeLen": len(path)})
-				return deviceRules
+				return deviceRules, nil
 			}
 			ingressHop = recalculatedRoute[0]
 		}
@@ -321,7 +344,7 @@
 		if innerTag == 0 {
 			log.Errorw("no-inner-route-double-tag", log.Fields{"inPortNo": inPortNo, "outPortNo": portNumber, "comment": "deleting-flow", "metadata": metadataFromwriteMetadata})
 			//TODO: Delete flow
-			return deviceRules
+			return deviceRules, nil
 		}
 		fa := &fu.FlowArgs{
 			KV: fu.OfpFlowModArgs{"priority": uint64(flow.Priority), "cookie": flow.Cookie, "meter_id": uint64(meterID), "write_metadata": metadataFromwriteMetadata},
@@ -339,7 +362,11 @@
 		fa.Actions = append(fa.Actions, fu.Output(ingressHop.Egress))
 
 		fg := fu.NewFlowsAndGroups()
-		fg.AddFlow(fu.MkFlowStat(fa))
+		fs, err := fu.MkFlowStat(fa)
+		if err != nil {
+			return nil, err
+		}
+		fg.AddFlow(fs)
 		deviceRules.AddFlowsAndGroup(ingressHop.DeviceID, fg)
 	} else { // Create standard flow
 		log.Debugw("creating-standard-flow", log.Fields{"flow": flow})
@@ -358,16 +385,20 @@
 		fa.Actions = append(fa.Actions, fu.Output(ingressHop.Egress))
 
 		fg := fu.NewFlowsAndGroups()
-		fg.AddFlow(fu.MkFlowStat(fa))
+		fs, err := fu.MkFlowStat(fa)
+		if err != nil {
+			return nil, err
+		}
+		fg.AddFlow(fs)
 		deviceRules.AddFlowsAndGroup(ingressHop.DeviceID, fg)
 	}
 
-	return deviceRules
+	return deviceRules, nil
 }
 
 // processUnicastFlow decomposes unicast flows
 func (fd *FlowDecomposer) processUnicastFlow(ctx context.Context, path []route.Hop,
-	inPortNo uint32, outPortNo uint32, flow *ofp.OfpFlowStats) *fu.DeviceRules {
+	inPortNo uint32, outPortNo uint32, flow *ofp.OfpFlowStats) (*fu.DeviceRules, error) {
 
 	log.Debugw("decomposing-onu-flow-in-downstream-unicast-flow", log.Fields{"inPortNo": inPortNo, "outPortNo": outPortNo})
 	deviceRules := fu.NewDeviceRules()
@@ -391,9 +422,13 @@
 	fa.Actions = filteredAction
 
 	fg := fu.NewFlowsAndGroups()
-	fg.AddFlow(fu.MkFlowStat(fa))
+	fs, err := fu.MkFlowStat(fa)
+	if err != nil {
+		return nil, err
+	}
+	fg.AddFlow(fs)
 	deviceRules.AddFlowsAndGroup(egressHop.DeviceID, fg)
-	return deviceRules
+	return deviceRules, nil
 }
 
 // processMulticastFlow decompose multicast flows
@@ -457,7 +492,10 @@
 
 	// Process controller bound flow
 	if outPortNo != 0 && (outPortNo&0x7fffffff) == uint32(ofp.OfpPortNo_OFPP_CONTROLLER) {
-		deviceRules = fd.processControllerBoundFlow(ctx, agent, path, inPortNo, outPortNo, flow)
+		deviceRules, err = fd.processControllerBoundFlow(ctx, agent, path, inPortNo, outPortNo, flow)
+		if err != nil {
+			return nil, err
+		}
 	} else {
 		var ingressDevice *voltha.Device
 		var err error
@@ -467,13 +505,22 @@
 		isUpstream := !ingressDevice.Root
 		if isUpstream { // Unicast OLT and ONU UL
 			log.Debug("process-olt-nd-onu-upstream-noncontrollerbound-unicast-flows", log.Fields{"flows": flow})
-			deviceRules = fd.processUpstreamNonControllerBoundFlow(ctx, path, inPortNo, outPortNo, flow)
+			deviceRules, err = fd.processUpstreamNonControllerBoundFlow(ctx, path, inPortNo, outPortNo, flow)
+			if err != nil {
+				return nil, err
+			}
 		} else if fu.HasNextTable(flow) && flow.TableId == 0 { // Unicast OLT flow DL
 			log.Debugw("process-olt-downstream-noncontrollerbound-flow-with-nexttable", log.Fields{"flows": flow})
-			deviceRules = fd.processDownstreamFlowWithNextTable(ctx, agent, path, inPortNo, outPortNo, flow)
+			deviceRules, err = fd.processDownstreamFlowWithNextTable(ctx, agent, path, inPortNo, outPortNo, flow)
+			if err != nil {
+				return nil, err
+			}
 		} else if flow.TableId == 1 && outPortNo != 0 { // Unicast ONU flow DL
 			log.Debugw("process-onu-downstream-unicast-flow", log.Fields{"flows": flow})
-			deviceRules = fd.processUnicastFlow(ctx, path, inPortNo, outPortNo, flow)
+			deviceRules, err = fd.processUnicastFlow(ctx, path, inPortNo, outPortNo, flow)
+			if err != nil {
+				return nil, err
+			}
 		} else if grpID := fu.GetGroup(flow); grpID != 0 && flow.TableId == 0 { //Multicast
 			log.Debugw("process-multicast-flow", log.Fields{"flows": flow})
 			deviceRules = fd.processMulticastFlow(ctx, path, inPortNo, outPortNo, flow, grpID, groupMap)
@@ -481,6 +528,6 @@
 			return deviceRules, status.Errorf(codes.Aborted, "unknown downstream flow %v", *flow)
 		}
 	}
-	deviceRules = fd.updateOutputPortForControllerBoundFlowForParentDevide(flow, deviceRules)
-	return deviceRules, nil
+	deviceRules, err = fd.updateOutputPortForControllerBoundFlowForParentDevide(flow, deviceRules)
+	return deviceRules, err
 }
diff --git a/rw_core/flowdecomposition/flow_decomposer_test.go b/rw_core/flowdecomposition/flow_decomposer_test.go
index 29c1a6a..6164f0d 100644
--- a/rw_core/flowdecomposition/flow_decomposer_test.go
+++ b/rw_core/flowdecomposition/flow_decomposer_test.go
@@ -125,7 +125,7 @@
 	logicalPortsNo map[uint32]bool
 }
 
-func newTestFlowDecomposer(deviceMgr *testDeviceManager) *testFlowDecomposer {
+func newTestFlowDecomposer(t *testing.T, deviceMgr *testDeviceManager) *testFlowDecomposer {
 	var tfd testFlowDecomposer
 	tfd.dMgr = deviceMgr
 
@@ -346,7 +346,9 @@
 			fu.Output(1),
 		},
 	}
-	fg.AddFlow(fu.MkFlowStat(fa))
+	fs, err := fu.MkFlowStat(fa)
+	assert.Nil(t, err)
+	fg.AddFlow(fs)
 	tfd.defaultRules.AddFlowsAndGroup("onu1", fg)
 
 	fg = fu.NewFlowsAndGroups()
@@ -360,7 +362,9 @@
 			fu.Output(1),
 		},
 	}
-	fg.AddFlow(fu.MkFlowStat(fa))
+	fs, err = fu.MkFlowStat(fa)
+	assert.Nil(t, err)
+	fg.AddFlow(fs)
 	tfd.defaultRules.AddFlowsAndGroup("onu2", fg)
 
 	fg = fu.NewFlowsAndGroups()
@@ -374,7 +378,9 @@
 			fu.Output(1),
 		},
 	}
-	fg.AddFlow(fu.MkFlowStat(fa))
+	fs, err = fu.MkFlowStat(fa)
+	assert.Nil(t, err)
+	fg.AddFlow(fs)
 	tfd.defaultRules.AddFlowsAndGroup("onu3", fg)
 
 	fg = fu.NewFlowsAndGroups()
@@ -388,7 +394,9 @@
 			fu.Output(1),
 		},
 	}
-	fg.AddFlow(fu.MkFlowStat(fa))
+	fs, err = fu.MkFlowStat(fa)
+	assert.Nil(t, err)
+	fg.AddFlow(fs)
 	tfd.defaultRules.AddFlowsAndGroup("onu4", fg)
 
 	//Set up the device graph - flow decomposer uses it only to verify whether a port is a root port.
@@ -482,9 +490,11 @@
 		},
 	}
 
-	flows := ofp.Flows{Items: []*ofp.OfpFlowStats{fu.MkFlowStat(fa)}}
+	fs, err := fu.MkFlowStat(fa)
+	assert.Nil(t, err)
+	flows := ofp.Flows{Items: []*ofp.OfpFlowStats{fs}}
 	groups := ofp.FlowGroups{}
-	tfd := newTestFlowDecomposer(newTestDeviceManager())
+	tfd := newTestFlowDecomposer(t, newTestDeviceManager())
 
 	deviceRules, err := tfd.fd.DecomposeRules(context.Background(), tfd, flows, groups)
 	assert.Nil(t, err)
@@ -508,7 +518,8 @@
 			fu.Output(uint32(ofp.OfpPortNo_OFPP_CONTROLLER)),
 		},
 	}
-	expectedOltFlow := fu.MkFlowStat(faParent)
+	expectedOltFlow, err := fu.MkFlowStat(faParent)
+	assert.Nil(t, err)
 	derivedFlow := oltFlowAndGroup.GetFlow(0)
 	assert.Equal(t, expectedOltFlow.String(), derivedFlow.String())
 
@@ -525,7 +536,8 @@
 			fu.Output(1),
 		},
 	}
-	expectedOnuFlow := fu.MkFlowStat(faChild)
+	expectedOnuFlow, err := fu.MkFlowStat(faChild)
+	assert.Nil(t, err)
 	derivedFlow = onu1FlowAndGroup.GetFlow(0)
 	assert.Equal(t, expectedOnuFlow.String(), derivedFlow.String())
 }
@@ -545,9 +557,11 @@
 		},
 	}
 
-	flows := ofp.Flows{Items: []*ofp.OfpFlowStats{fu.MkFlowStat(fa)}}
+	fs, err := fu.MkFlowStat(fa)
+	assert.Nil(t, err)
+	flows := ofp.Flows{Items: []*ofp.OfpFlowStats{fs}}
 	groups := ofp.FlowGroups{}
-	tfd := newTestFlowDecomposer(newTestDeviceManager())
+	tfd := newTestFlowDecomposer(t, newTestDeviceManager())
 
 	deviceRules, err := tfd.fd.DecomposeRules(context.Background(), tfd, flows, groups)
 	assert.Nil(t, err)
@@ -571,7 +585,8 @@
 			fu.Output(uint32(ofp.OfpPortNo_OFPP_CONTROLLER)),
 		},
 	}
-	expectedOltFlow := fu.MkFlowStat(faParent)
+	expectedOltFlow, err := fu.MkFlowStat(faParent)
+	assert.Nil(t, err)
 	derivedFlow := oltFlowAndGroup.GetFlow(0)
 	assert.Equal(t, expectedOltFlow.String(), derivedFlow.String())
 
@@ -588,7 +603,8 @@
 			fu.Output(1),
 		},
 	}
-	expectedOnuFlow := fu.MkFlowStat(faChild)
+	expectedOnuFlow, err := fu.MkFlowStat(faChild)
+	assert.Nil(t, err)
 	derivedFlow = onu1FlowAndGroup.GetFlow(0)
 	assert.Equal(t, expectedOnuFlow.String(), derivedFlow.String())
 }
@@ -607,9 +623,11 @@
 		},
 	}
 
-	flows := ofp.Flows{Items: []*ofp.OfpFlowStats{fu.MkFlowStat(fa)}}
+	fs, err := fu.MkFlowStat(fa)
+	assert.Nil(t, err)
+	flows := ofp.Flows{Items: []*ofp.OfpFlowStats{fs}}
 	groups := ofp.FlowGroups{}
-	tfd := newTestFlowDecomposer(newTestDeviceManager())
+	tfd := newTestFlowDecomposer(t, newTestDeviceManager())
 
 	deviceRules, err := tfd.fd.DecomposeRules(context.Background(), tfd, flows, groups)
 	assert.Nil(t, err)
@@ -632,7 +650,8 @@
 			fu.Output(uint32(ofp.OfpPortNo_OFPP_CONTROLLER)),
 		},
 	}
-	expectedOltFlow := fu.MkFlowStat(faParent)
+	expectedOltFlow, err := fu.MkFlowStat(faParent)
+	assert.Nil(t, err)
 	derivedFlow := oltFlowAndGroup.GetFlow(0)
 	assert.Equal(t, expectedOltFlow.String(), derivedFlow.String())
 
@@ -647,7 +666,8 @@
 			fu.Output(1),
 		},
 	}
-	expectedOnuFlow := fu.MkFlowStat(faChild)
+	expectedOnuFlow, err := fu.MkFlowStat(faChild)
+	assert.Nil(t, err)
 	derivedFlow = onu1FlowAndGroup.GetFlow(0)
 	assert.Equal(t, expectedOnuFlow.String(), derivedFlow.String())
 }
@@ -669,9 +689,11 @@
 		},
 	}
 
-	flows := ofp.Flows{Items: []*ofp.OfpFlowStats{fu.MkFlowStat(fa)}}
+	fs, err := fu.MkFlowStat(fa)
+	assert.Nil(t, err)
+	flows := ofp.Flows{Items: []*ofp.OfpFlowStats{fs}}
 	groups := ofp.FlowGroups{}
-	tfd := newTestFlowDecomposer(newTestDeviceManager())
+	tfd := newTestFlowDecomposer(t, newTestDeviceManager())
 
 	deviceRules, err := tfd.fd.DecomposeRules(context.Background(), tfd, flows, groups)
 	assert.Nil(t, err)
@@ -699,7 +721,8 @@
 			fu.Output(uint32(ofp.OfpPortNo_OFPP_CONTROLLER)),
 		},
 	}
-	expectedOltFlow := fu.MkFlowStat(faParent)
+	expectedOltFlow, err := fu.MkFlowStat(faParent)
+	assert.Nil(t, err)
 	derivedFlow := oltFlowAndGroup.GetFlow(0)
 	assert.Equal(t, expectedOltFlow.String(), derivedFlow.String())
 
@@ -718,7 +741,8 @@
 			fu.Output(1),
 		},
 	}
-	expectedOnuFlow := fu.MkFlowStat(faChild)
+	expectedOnuFlow, err := fu.MkFlowStat(faChild)
+	assert.Nil(t, err)
 	derivedFlow = onu1FlowAndGroup.GetFlow(0)
 	assert.Equal(t, expectedOnuFlow.String(), derivedFlow.String())
 }
@@ -735,9 +759,11 @@
 		},
 	}
 
-	flows := ofp.Flows{Items: []*ofp.OfpFlowStats{fu.MkFlowStat(fa)}}
+	fs, err := fu.MkFlowStat(fa)
+	assert.Nil(t, err)
+	flows := ofp.Flows{Items: []*ofp.OfpFlowStats{fs}}
 	groups := ofp.FlowGroups{}
-	tfd := newTestFlowDecomposer(newTestDeviceManager())
+	tfd := newTestFlowDecomposer(t, newTestDeviceManager())
 	deviceRules, err := tfd.fd.DecomposeRules(context.Background(), tfd, flows, groups)
 	assert.Nil(t, err)
 	onu1FlowAndGroup := deviceRules.Rules["onu1"]
@@ -756,7 +782,8 @@
 			fu.Output(uint32(ofp.OfpPortNo_OFPP_CONTROLLER)),
 		},
 	}
-	expectedOltFlow := fu.MkFlowStat(fa)
+	expectedOltFlow, err := fu.MkFlowStat(fa)
+	assert.Nil(t, err)
 	derivedFlow := oltFlowAndGroup.GetFlow(0)
 	assert.Equal(t, expectedOltFlow.String(), derivedFlow.String())
 }
@@ -789,7 +816,11 @@
 		},
 	}
 
-	flows := ofp.Flows{Items: []*ofp.OfpFlowStats{fu.MkFlowStat(fa), fu.MkFlowStat(fa2)}}
+	fs, err := fu.MkFlowStat(fa)
+	assert.Nil(t, err)
+	fs2, err := fu.MkFlowStat(fa2)
+	assert.Nil(t, err)
+	flows := ofp.Flows{Items: []*ofp.OfpFlowStats{fs, fs2}}
 	flows.Items[0].Instructions = []*ofp.OfpInstruction{{
 		Type: uint32(ofp.OfpInstructionType_OFPIT_GOTO_TABLE),
 		Data: &ofp.OfpInstruction_GotoTable{
@@ -799,7 +830,7 @@
 		}}}
 
 	groups := ofp.FlowGroups{}
-	tfd := newTestFlowDecomposer(newTestDeviceManager())
+	tfd := newTestFlowDecomposer(t, newTestDeviceManager())
 
 	deviceRules, err := tfd.fd.DecomposeRules(context.Background(), tfd, flows, groups)
 	assert.Nil(t, err)
@@ -828,7 +859,8 @@
 
 	derivedFlow := onu1FlowAndGroup.GetFlow(0)
 	// Form the expected flow
-	expectedOnu1Flow := fu.MkFlowStat(fa)
+	expectedOnu1Flow, err := fu.MkFlowStat(fa)
+	assert.Nil(t, err)
 	expectedOnu1Flow.Instructions = []*ofp.OfpInstruction{{
 		Type: uint32(ofp.OfpInstructionType_OFPIT_APPLY_ACTIONS),
 		Data: &ofp.OfpInstruction_Actions{
@@ -860,7 +892,8 @@
 			fu.Output(2),
 		},
 	}
-	expectedOltFlow := fu.MkFlowStat(fa)
+	expectedOltFlow, err := fu.MkFlowStat(fa)
+	assert.Nil(t, err)
 	derivedFlow = oltFlowAndGroup.GetFlow(0)
 	assert.Equal(t, expectedOltFlow.String(), derivedFlow.String())
 }
@@ -892,7 +925,11 @@
 		},
 	}
 
-	flows := ofp.Flows{Items: []*ofp.OfpFlowStats{fu.MkFlowStat(fa1), fu.MkFlowStat(fa2)}}
+	fs1, err := fu.MkFlowStat(fa1)
+	assert.Nil(t, err)
+	fs2, err := fu.MkFlowStat(fa2)
+	assert.Nil(t, err)
+	flows := ofp.Flows{Items: []*ofp.OfpFlowStats{fs1, fs2}}
 	flows.Items[0].Instructions = []*ofp.OfpInstruction{{
 		Type: uint32(ofp.OfpInstructionType_OFPIT_GOTO_TABLE),
 		Data: &ofp.OfpInstruction_GotoTable{
@@ -902,7 +939,7 @@
 		}}}
 
 	groups := ofp.FlowGroups{}
-	tfd := newTestFlowDecomposer(newTestDeviceManager())
+	tfd := newTestFlowDecomposer(t, newTestDeviceManager())
 
 	deviceRules, err := tfd.fd.DecomposeRules(context.Background(), tfd, flows, groups)
 	assert.Nil(t, err)
@@ -928,7 +965,8 @@
 	}
 
 	derivedFlow := oltFlowAndGroup.GetFlow(0)
-	expectedOltFlow := fu.MkFlowStat(fa1)
+	expectedOltFlow, err := fu.MkFlowStat(fa1)
+	assert.Nil(t, err)
 	expectedOltFlow.Instructions = []*ofp.OfpInstruction{{
 		Type: uint32(ofp.OfpInstructionType_OFPIT_APPLY_ACTIONS),
 		Data: &ofp.OfpInstruction_Actions{
@@ -956,7 +994,8 @@
 			fu.Output(2),
 		},
 	}
-	expectedOnu1Flow := fu.MkFlowStat(fa1)
+	expectedOnu1Flow, err := fu.MkFlowStat(fa1)
+	assert.Nil(t, err)
 	derivedFlow = onu1FlowAndGroup.GetFlow(0)
 	assert.Equal(t, expectedOnu1Flow.String(), derivedFlow.String())
 }
@@ -987,9 +1026,11 @@
 		},
 	}
 
-	flows := ofp.Flows{Items: []*ofp.OfpFlowStats{fu.MkFlowStat(fa)}}
+	fs, err := fu.MkFlowStat(fa)
+	assert.Nil(t, err)
+	flows := ofp.Flows{Items: []*ofp.OfpFlowStats{fs}}
 	groups := ofp.FlowGroups{Items: []*ofp.OfpGroupEntry{fu.MkGroupStat(ga)}}
-	tfd := newTestFlowDecomposer(newTestDeviceManager())
+	tfd := newTestFlowDecomposer(t, newTestDeviceManager())
 
 	deviceRules, err := tfd.fd.DecomposeRules(context.Background(), tfd, flows, groups)
 	assert.Nil(t, err)
@@ -1010,7 +1051,8 @@
 			fu.Group(10),
 		},
 	}
-	expectedOltFlow := fu.MkFlowStat(fa)
+	expectedOltFlow, err := fu.MkFlowStat(fa)
+	assert.Nil(t, err)
 	derivedFlow := oltFlowAndGroup.GetFlow(0)
 	assert.Equal(t, expectedOltFlow.String(), derivedFlow.String())
 }
diff --git a/vendor/github.com/opencord/voltha-lib-go/v3/pkg/flows/flow_utils.go b/vendor/github.com/opencord/voltha-lib-go/v3/pkg/flows/flow_utils.go
index 4de929f..b2086cd 100644
--- a/vendor/github.com/opencord/voltha-lib-go/v3/pkg/flows/flow_utils.go
+++ b/vendor/github.com/opencord/voltha-lib-go/v3/pkg/flows/flow_utils.go
@@ -18,6 +18,7 @@
 import (
 	"bytes"
 	"crypto/md5"
+	"errors"
 	"fmt"
 	"github.com/cevaris/ordered_map"
 	"github.com/gogo/protobuf/proto"
@@ -678,9 +679,9 @@
 
 // Return unique 64-bit integer hash for flow covering the following attributes:
 // 'table_id', 'priority', 'flags', 'cookie', 'match', '_instruction_string'
-func HashFlowStats(flow *ofp.OfpFlowStats) uint64 {
+func HashFlowStats(flow *ofp.OfpFlowStats) (uint64, error) {
 	if flow == nil { // Should never happen
-		return 0
+		return 0, errors.New("hash-flow-stats-nil-flow")
 	}
 	// Create string with the instructions field first
 	var instructionString bytes.Buffer
@@ -690,19 +691,18 @@
 	var flowString = fmt.Sprintf("%d%d%d%d%s%s", flow.TableId, flow.Priority, flow.Flags, flow.Cookie, flow.Match.String(), instructionString.String())
 	h := md5.New()
 	if _, err := h.Write([]byte(flowString)); err != nil {
-		logger.Errorw("hash-flow-status", log.Fields{"error": err})
-		return 0
+		return 0, fmt.Errorf("hash-flow-stats-failed-hash: %v", err)
 	}
 	hash := big.NewInt(0)
 	hash.SetBytes(h.Sum(nil))
-	return hash.Uint64()
+	return hash.Uint64(), nil
 }
 
 // flowStatsEntryFromFlowModMessage maps an ofp_flow_mod message to an ofp_flow_stats message
-func FlowStatsEntryFromFlowModMessage(mod *ofp.OfpFlowMod) *ofp.OfpFlowStats {
+func FlowStatsEntryFromFlowModMessage(mod *ofp.OfpFlowMod) (*ofp.OfpFlowStats, error) {
 	flow := &ofp.OfpFlowStats{}
 	if mod == nil {
-		return flow
+		return flow, nil
 	}
 	flow.TableId = mod.TableId
 	flow.Priority = mod.Priority
@@ -712,8 +712,12 @@
 	flow.Cookie = mod.Cookie
 	flow.Match = mod.Match
 	flow.Instructions = mod.Instructions
-	flow.Id = HashFlowStats(flow)
-	return flow
+	var err error
+	if flow.Id, err = HashFlowStats(flow); err != nil {
+		return nil, err
+	}
+
+	return flow, nil
 }
 
 func GroupEntryFromGroupMod(mod *ofp.OfpGroupMod) *ofp.OfpGroupEntry {
@@ -913,7 +917,7 @@
 }
 
 // MkFlowStat is a helper method to build flows
-func MkFlowStat(fa *FlowArgs) *ofp.OfpFlowStats {
+func MkFlowStat(fa *FlowArgs) (*ofp.OfpFlowStats, error) {
 	//Build the match-fields
 	matchFields := make([]*ofp.OfpOxmField, 0)
 	for _, val := range fa.MatchFields {
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 534084d..ebf9c95 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -96,7 +96,7 @@
 github.com/modern-go/concurrent
 # github.com/modern-go/reflect2 v1.0.1
 github.com/modern-go/reflect2
-# github.com/opencord/voltha-lib-go/v3 v3.0.14
+# github.com/opencord/voltha-lib-go/v3 v3.0.15
 github.com/opencord/voltha-lib-go/v3/pkg/adapters
 github.com/opencord/voltha-lib-go/v3/pkg/adapters/adapterif
 github.com/opencord/voltha-lib-go/v3/pkg/adapters/common