[VOL-2995] Improve Core performance

This commit consists of the following changes with the aim to
improve the Core performance:

1) Use a hybrid approach of pre-route calculation and route
calculation on-demand.  For example, attempts to pre-calculate
routes will be done whenever a nni/uni port is discovered.  The
attempt may fail if there are not enough ports to generate a
route.  When a flow is received and the route is not available
then only the route relevant to that flow will be created on
demand.

2) Changes some of the route calculation flow such that the
process does not need to go and grab the latest version of the
device which could lead to higher latency, expecially if that
device is busy with other processing.

3) Change the logic when reporting added ports to ONOS such that
routes are calculated (at least an attempt made) before sending
a port create notification to ONOS.

4) Move peer port creation into its own go routine thereby
removing the lock on a child device much earlier.

5) Wait until a request for port capability is received before
removing the lock on a device.   A better approach is required
where the adapter will need to report the port capability along
with the port creation event.  However, this require another
Jira as changes will be required in the API.

6) Remove some unnecessary proto.clones.  Those are the obvious
ones.  Removal of other proto.clones will be done in a separate
commit.

7) Fix a core panic when concurrent requests are made to the
route map

Change-Id: I2bafc99dbf10d7026572a44af0b88a31b5eb1887
diff --git a/rw_core/core/device/logical_agent_route.go b/rw_core/core/device/logical_agent_route.go
index 6736160..fa96caf 100644
--- a/rw_core/core/device/logical_agent_route.go
+++ b/rw_core/core/device/logical_agent_route.go
@@ -18,30 +18,26 @@
 
 import (
 	"context"
-	"errors"
 	"fmt"
-
 	"github.com/opencord/voltha-go/rw_core/route"
 	"github.com/opencord/voltha-lib-go/v3/pkg/log"
 	ofp "github.com/opencord/voltha-protos/v3/go/openflow_13"
 	"github.com/opencord/voltha-protos/v3/go/voltha"
-	"google.golang.org/grpc/codes"
-	"google.golang.org/grpc/status"
 )
 
-// GetRoute returns route
+// GetRoute returns a route
 func (agent *LogicalAgent) GetRoute(ctx context.Context, ingressPortNo uint32, egressPortNo uint32) ([]route.Hop, error) {
 	logger.Debugw("getting-route", log.Fields{"ingress-port": ingressPortNo, "egress-port": egressPortNo})
 	routes := make([]route.Hop, 0)
 
 	// Note: A port value of 0 is equivalent to a nil port
 
-	//	Consider different possibilities
+	// Controller-bound flow
 	if egressPortNo != 0 && ((egressPortNo & 0x7fffffff) == uint32(ofp.OfpPortNo_OFPP_CONTROLLER)) {
 		logger.Debugw("controller-flow", log.Fields{"ingressPortNo": ingressPortNo, "egressPortNo": egressPortNo, "logicalPortsNo": agent.logicalPortsNo})
 		if agent.isNNIPort(ingressPortNo) {
 			//This is a trap on the NNI Port
-			if len(agent.deviceRoutes.Routes) == 0 {
+			if agent.deviceRoutes.IsRoutesEmpty() {
 				// If there are no routes set (usually when the logical device has only NNI port(s), then just return an
 				// route with same IngressHop and EgressHop
 				hop := route.Hop{DeviceID: agent.rootDeviceID, Ingress: ingressPortNo, Egress: ingressPortNo}
@@ -49,62 +45,43 @@
 				routes = append(routes, hop)
 				return routes, nil
 			}
-			//Return a 'half' route to make the flow decomposer logic happy
-			for routeLink, path := range agent.deviceRoutes.Routes {
-				if agent.isNNIPort(routeLink.Egress) {
-					routes = append(routes, route.Hop{}) // first hop is set to empty
-					routes = append(routes, path[1])
-					return routes, nil
-				}
+
+			// Return a 'half' route to make the flow decomposer logic happy
+			routes, err := agent.deviceRoutes.GetHalfRoute(true, 0, 0)
+			if err != nil {
+				return nil, fmt.Errorf("no upstream route from:%d to:%d :%w", ingressPortNo, egressPortNo, route.ErrNoRoute)
 			}
-			return nil, fmt.Errorf("no upstream route from:%d to:%d :%w", ingressPortNo, egressPortNo, route.ErrNoRoute)
+			return routes, nil
 		}
-		//treat it as if the output port is the first NNI of the OLT
+		// Treat it as if the output port is the first NNI of the OLT
 		var err error
 		if egressPortNo, err = agent.getFirstNNIPort(); err != nil {
 			logger.Warnw("no-nni-port", log.Fields{"error": err})
 			return nil, err
 		}
 	}
-	//If ingress port is not specified (nil), it may be a wildcarded
-	//route if egress port is OFPP_CONTROLLER or a nni logical port,
-	//in which case we need to create a half-route where only the egress
-	//hop is filled, the first hop is nil
+
+	//If ingress port is not specified (nil), it may be a wildcarded route if egress port is OFPP_CONTROLLER or a nni
+	// logical port, in which case we need to create a half-route where only the egress hop is filled, the first hop is nil
 	if ingressPortNo == 0 && agent.isNNIPort(egressPortNo) {
-		// We can use the 2nd hop of any upstream route, so just find the first upstream:
-		for routeLink, path := range agent.deviceRoutes.Routes {
-			if agent.isNNIPort(routeLink.Egress) {
-				routes = append(routes, route.Hop{}) // first hop is set to empty
-				routes = append(routes, path[1])
-				return routes, nil
-			}
+		routes, err := agent.deviceRoutes.GetHalfRoute(true, ingressPortNo, egressPortNo)
+		if err != nil {
+			return nil, fmt.Errorf("no upstream route from:%d to:%d :%w", ingressPortNo, egressPortNo, route.ErrNoRoute)
 		}
-		return nil, fmt.Errorf("no upstream route from:%d to:%d :%w", ingressPortNo, egressPortNo, route.ErrNoRoute)
+		return routes, nil
 	}
+
 	//If egress port is not specified (nil), we can also can return a "half" route
 	if egressPortNo == 0 {
-		for routeLink, path := range agent.deviceRoutes.Routes {
-			if routeLink.Ingress == ingressPortNo {
-				routes = append(routes, path[0])
-				routes = append(routes, route.Hop{})
-				return routes, nil
-			}
+		routes, err := agent.deviceRoutes.GetHalfRoute(false, ingressPortNo, egressPortNo)
+		if err != nil {
+			return nil, fmt.Errorf("no downstream route from:%d to:%d :%w", ingressPortNo, egressPortNo, route.ErrNoRoute)
 		}
-		return nil, fmt.Errorf("no downstream route from:%d to:%d :%w", ingressPortNo, egressPortNo, route.ErrNoRoute)
+		return routes, nil
 	}
-	//	Return the pre-calculated route
-	return agent.getPreCalculatedRoute(ingressPortNo, egressPortNo)
-}
 
-func (agent *LogicalAgent) getPreCalculatedRoute(ingress, egress uint32) ([]route.Hop, error) {
-	logger.Debugw("ROUTE", log.Fields{"len": len(agent.deviceRoutes.Routes)})
-	for routeLink, route := range agent.deviceRoutes.Routes {
-		logger.Debugw("ROUTELINKS", log.Fields{"ingress": ingress, "egress": egress, "routelink": routeLink})
-		if ingress == routeLink.Ingress && egress == routeLink.Egress {
-			return route, nil
-		}
-	}
-	return nil, status.Errorf(codes.FailedPrecondition, "no route from:%d to:%d", ingress, egress)
+	//	Return the pre-calculated route
+	return agent.deviceRoutes.GetRoute(ctx, ingressPortNo, egressPortNo)
 }
 
 // GetDeviceRoutes returns device graph
@@ -112,30 +89,6 @@
 	return agent.deviceRoutes
 }
 
-//generateDeviceRoutesIfNeeded generates the device routes if the logical device has been updated since the last time
-//that device graph was generated.
-func (agent *LogicalAgent) generateDeviceRoutesIfNeeded(ctx context.Context) error {
-	agent.lockDeviceRoutes.Lock()
-	defer agent.lockDeviceRoutes.Unlock()
-
-	ld, err := agent.GetLogicalDevice(ctx)
-	if err != nil {
-		return err
-	}
-
-	if agent.deviceRoutes != nil && agent.deviceRoutes.IsUpToDate(ld) {
-		return nil
-	}
-	logger.Debug("Generation of device route required")
-	if err := agent.buildRoutes(ctx); err != nil {
-		// No Route is not an error
-		if !errors.Is(err, route.ErrNoRoute) {
-			return err
-		}
-	}
-	return nil
-}
-
 //rebuildRoutes rebuilds the device routes
 func (agent *LogicalAgent) buildRoutes(ctx context.Context) error {
 	logger.Debugf("building-routes", log.Fields{"logical-device-id": agent.logicalDeviceID})
@@ -160,17 +113,28 @@
 }
 
 //updateRoutes updates the device routes
-func (agent *LogicalAgent) updateRoutes(ctx context.Context, lp *voltha.LogicalPort) error {
-	logger.Debugw("updateRoutes", log.Fields{"logicalDeviceId": agent.logicalDeviceID})
-	if err := agent.requestQueue.WaitForGreenLight(ctx); err != nil {
+func (agent *LogicalAgent) updateRoutes(ctx context.Context, device *voltha.Device, lp *voltha.LogicalPort, lps []*voltha.LogicalPort) error {
+	logger.Debugw("updateRoutes", log.Fields{"logical-device-id": agent.logicalDeviceID, "device-id": device.Id, "port:": lp})
+
+	if err := agent.deviceRoutes.AddPort(ctx, lp, device, lps); err != nil {
 		return err
 	}
-	defer agent.requestQueue.RequestComplete()
-
-	if agent.deviceRoutes == nil {
-		agent.deviceRoutes = route.NewDeviceRoutes(agent.logicalDeviceID, agent.deviceMgr.getDevice)
+	if err := agent.deviceRoutes.Print(); err != nil {
+		return err
 	}
-	if err := agent.deviceRoutes.AddPort(ctx, lp, agent.logicalDevice.Ports); err != nil {
+	return nil
+}
+
+//updateAllRoutes updates the device routes using all the logical ports on that device
+func (agent *LogicalAgent) updateAllRoutes(ctx context.Context, device *voltha.Device) error {
+	logger.Debugw("updateAllRoutes", log.Fields{"logical-device-id": agent.logicalDeviceID, "device-id": device.Id, "ports-count": len(device.Ports)})
+
+	ld, err := agent.GetLogicalDevice(ctx)
+	if err != nil {
+		return err
+	}
+
+	if err := agent.deviceRoutes.AddAllPorts(ctx, device, ld.Ports); err != nil {
 		return err
 	}
 	if err := agent.deviceRoutes.Print(); err != nil {