VOL-1474: GetChildDevice support

Changes also needed to support more child
device search criteria, including onu id.

Also ran a go fmt

Change-Id: Id253ae0ae0a5a68379c8dca5fc9511ac0deb9158
diff --git a/compose/system-test.yml b/compose/system-test.yml
index 61afff2..f44ac73 100644
--- a/compose/system-test.yml
+++ b/compose/system-test.yml
@@ -169,6 +169,8 @@
       "--name=brcm_openomci_onu",
       "--kafka_adapter=${DOCKER_HOST_IP}:9092",
       "--kafka_cluster=${DOCKER_HOST_IP}:9092",
+      "--backend=etcd",
+      "--etcd=${DOCKER_HOST_IP}:2379",
       "--core_topic=rwcore"
     ]
     networks:
diff --git a/rw_core/core/adapter_request_handler.go b/rw_core/core/adapter_request_handler.go
index 1be0cff..01c289c 100644
--- a/rw_core/core/adapter_request_handler.go
+++ b/rw_core/core/adapter_request_handler.go
@@ -250,7 +250,7 @@
 }
 
 func (rhp *AdapterRequestHandlerProxy) GetChildDevice(args []*ic.Argument) (*voltha.Device, error) {
-	if len(args) < 2 {
+	if len(args) < 3 {
 		log.Warn("invalid-number-of-args", log.Fields{"args": args})
 		err := errors.New("invalid-number-of-args")
 		return nil, err
@@ -258,6 +258,9 @@
 
 	pID := &voltha.ID{}
 	transactionID := &ic.StrType{}
+	serialNumber := &ic.StrType{}
+	onuId := &ic.IntType{}
+	parentPortNo := &ic.IntType{}
 	for _, arg := range args {
 		switch arg.Key {
 		case "device_id":
@@ -265,6 +268,21 @@
 				log.Warnw("cannot-unmarshal-ID", log.Fields{"error": err})
 				return nil, err
 			}
+		case "serial_number":
+			if err := ptypes.UnmarshalAny(arg.Value, serialNumber); err != nil {
+				log.Warnw("cannot-unmarshal-ID", log.Fields{"error": err})
+				return nil, err
+			}
+		case "onu_id":
+			if err := ptypes.UnmarshalAny(arg.Value, onuId); err != nil {
+				log.Warnw("cannot-unmarshal-ID", log.Fields{"error": err})
+				return nil, err
+			}
+		case "parent_port_no":
+			if err := ptypes.UnmarshalAny(arg.Value, parentPortNo); err != nil {
+				log.Warnw("cannot-unmarshal-ID", log.Fields{"error": err})
+				return nil, err
+			}
 		case kafka.TransactionKey:
 			if err := ptypes.UnmarshalAny(arg.Value, transactionID); err != nil {
 				log.Warnw("cannot-unmarshal-transaction-ID", log.Fields{"error": err})
@@ -272,7 +290,7 @@
 			}
 		}
 	}
-	log.Debugw("GetChildDevice", log.Fields{"deviceId": pID.Id, "transactionID": transactionID.Val})
+	log.Debugw("GetChildDevice", log.Fields{"parentDeviceId": pID.Id, "args": args, "transactionID": transactionID.Val})
 
 	// Try to grab the transaction as this core may be competing with another Core
 	if rhp.competeForTransaction() {
@@ -288,7 +306,7 @@
 	if rhp.TestMode { // Execute only for test cases
 		return &voltha.Device{Id: pID.Id}, nil
 	}
-	return rhp.deviceMgr.GetDevice(pID.Id)
+	return rhp.deviceMgr.GetChildDevice(pID.Id, serialNumber.Val, onuId.Val, parentPortNo.Val)
 }
 
 func (rhp *AdapterRequestHandlerProxy) GetPorts(args []*ic.Argument) (*voltha.Ports, error) {
@@ -374,7 +392,7 @@
 
 // ChildDeviceDetected is invoked when a child device is detected.  The following
 // parameters are expected:
-// {parent_device_id, parent_port_no, child_device_type, proxy_address, admin_state, **kw)
+// {parent_device_id, parent_port_no, child_device_type, channel_id, vendor_id, serial_number)
 func (rhp *AdapterRequestHandlerProxy) ChildDeviceDetected(args []*ic.Argument) (*empty.Empty, error) {
 	if len(args) < 5 {
 		log.Warn("invalid-number-of-args", log.Fields{"args": args})
@@ -387,6 +405,9 @@
 	dt := &ic.StrType{}
 	chnlId := &ic.IntType{}
 	transactionID := &ic.StrType{}
+	serialNumber := &ic.StrType{}
+	vendorId := &ic.StrType{}
+	onuId := &ic.IntType{}
 	for _, arg := range args {
 		switch arg.Key {
 		case "parent_device_id":
@@ -409,6 +430,21 @@
 				log.Warnw("cannot-unmarshal-channel-id", log.Fields{"error": err})
 				return nil, err
 			}
+		case "vendor_id":
+			if err := ptypes.UnmarshalAny(arg.Value, vendorId); err != nil {
+				log.Warnw("cannot-unmarshal-vendor-id", log.Fields{"error": err})
+				return nil, err
+			}
+		case "serial_number":
+			if err := ptypes.UnmarshalAny(arg.Value, serialNumber); err != nil {
+				log.Warnw("cannot-unmarshal-serial-number", log.Fields{"error": err})
+				return nil, err
+			}
+		case "onu_id":
+			if err := ptypes.UnmarshalAny(arg.Value, onuId); err != nil {
+				log.Warnw("cannot-unmarshal-onu-id", log.Fields{"error": err})
+				return nil, err
+			}
 		case kafka.TransactionKey:
 			if err := ptypes.UnmarshalAny(arg.Value, transactionID); err != nil {
 				log.Warnw("cannot-unmarshal-transaction-ID", log.Fields{"error": err})
@@ -417,7 +453,8 @@
 		}
 	}
 	log.Debugw("ChildDeviceDetected", log.Fields{"parentDeviceId": pID.Id, "parentPortNo": portNo.Val,
-		"deviceType": dt.Val, "channelId": chnlId.Val, "transactionID": transactionID.Val})
+		"deviceType": dt.Val, "channelId": chnlId.Val, "serialNumber": serialNumber.Val,
+		"vendorId": vendorId.Val, "onuId": onuId.Val, "transactionID": transactionID.Val})
 
 	// Try to grab the transaction as this core may be competing with another Core
 	if rhp.competeForTransaction() {
@@ -434,7 +471,7 @@
 		return nil, nil
 	}
 	// Run child detection in it's own go routine as it can be a lengthy process
-	go rhp.deviceMgr.childDeviceDetected(pID.Id, portNo.Val, dt.Val, chnlId.Val)
+	go rhp.deviceMgr.childDeviceDetected(pID.Id, portNo.Val, dt.Val, chnlId.Val, vendorId.Val, serialNumber.Val, onuId.Val)
 
 	return new(empty.Empty), nil
 }
diff --git a/rw_core/core/device_manager.go b/rw_core/core/device_manager.go
index b36e8d4..c40b8cd 100644
--- a/rw_core/core/device_manager.go
+++ b/rw_core/core/device_manager.go
@@ -201,6 +201,67 @@
 	return nil, status.Errorf(codes.NotFound, "%s", id)
 }
 
+func (dMgr *DeviceManager) GetChildDevice(parentDeviceId string, serialNumber string, onuId int64, parentPortNo int64) (*voltha.Device, error) {
+	log.Debugw("GetChildDevice", log.Fields{"parentDeviceid": parentDeviceId, "serialNumber": serialNumber})
+
+	var parentDevice *voltha.Device
+	var err error
+	if parentDevice, err = dMgr.GetDevice(parentDeviceId); err != nil {
+		return nil, status.Errorf(codes.Aborted, "%s", err.Error())
+	}
+	var childDeviceIds []string
+	if childDeviceIds, err = dMgr.getAllChildDeviceIds(parentDevice); err != nil {
+		return nil, status.Errorf(codes.Aborted, "%s", err.Error())
+	}
+	if len(childDeviceIds) == 0 {
+		log.Debugw("no-child-devices", log.Fields{"parentDeviceId": parentDevice.Id})
+		return nil, status.Errorf(codes.NotFound, "%s", parentDeviceId)
+	}
+
+	var foundChildDevice *voltha.Device
+	for _, childDeviceId := range childDeviceIds {
+		found := false
+		if searchDevice, err := dMgr.GetDevice(childDeviceId); err == nil {
+
+			foundOnuId := false
+			if searchDevice.ProxyAddress.OnuId == uint32(onuId) {
+				if searchDevice.ParentPortNo == uint32(parentPortNo) {
+					log.Debugw("found-child-by-onuid", log.Fields{"parentDeviceId": parentDevice.Id, "onuId": onuId})
+					foundOnuId = true
+				}
+			}
+
+			foundSerialNumber := false
+			if searchDevice.SerialNumber == serialNumber {
+				log.Debugw("found-child-by-serialnumber", log.Fields{"parentDeviceId": parentDevice.Id, "serialNumber": serialNumber})
+				foundSerialNumber = true
+			}
+
+			// if both onuId and serialNumber are provided both must be true for the device to be found
+			// otherwise whichever one found a match is good enough
+			if onuId > 0 && serialNumber != "" {
+				found = foundOnuId && foundSerialNumber
+			} else {
+				found = foundOnuId || foundSerialNumber
+			}
+
+			if found == true {
+				foundChildDevice = searchDevice
+				break
+			}
+		}
+	}
+
+	if foundChildDevice != nil {
+		log.Debugw("child-device-found", log.Fields{"parentDeviceId": parentDevice.Id, "foundChildDevice": foundChildDevice})
+		return foundChildDevice, nil
+	}
+
+	log.Warnw("child-device-not-found", log.Fields{"parentDeviceId": parentDevice.Id,
+		"serialNumber": serialNumber, "onuId": onuId, "parentPortNo": parentPortNo})
+	return nil, status.Errorf(codes.NotFound, "%s", parentDeviceId)
+}
+
 func (dMgr *DeviceManager) IsDeviceInCache(id string) bool {
 	dMgr.lockDeviceAgentsMap.Lock()
 	defer dMgr.lockDeviceAgentsMap.Unlock()
@@ -256,7 +317,7 @@
 	if agent := dMgr.getDeviceAgent(deviceId); agent != nil {
 		return agent, nil
 	}
-	return nil, status.Error(codes.NotFound, deviceId)  // This should nto happen
+	return nil, status.Error(codes.NotFound, deviceId) // This should not happen
 }
 
 // loadRootDeviceParentAndChildren loads the children and parents of a root device in memory
@@ -349,7 +410,7 @@
 				//	Device Id not in memory
 				log.Debugw("reconciling-device", log.Fields{"id": id.Id})
 				// Load device from dB
-				agent := newDeviceAgent(dMgr.adapterProxy, &voltha.Device{Id:id.Id}, dMgr, dMgr.clusterDataProxy)
+				agent := newDeviceAgent(dMgr.adapterProxy, &voltha.Device{Id: id.Id}, dMgr, dMgr.clusterDataProxy)
 				if err := agent.start(nil, true); err != nil {
 					log.Warnw("failure-loading-device", log.Fields{"deviceId": id.Id})
 					agent.stop(nil)
@@ -486,7 +547,8 @@
 	return status.Errorf(codes.NotFound, "%s", deviceId)
 }
 
-func (dMgr *DeviceManager) childDeviceDetected(parentDeviceId string, parentPortNo int64, deviceType string, channelId int64) error {
+func (dMgr *DeviceManager) childDeviceDetected(parentDeviceId string, parentPortNo int64, deviceType string,
+	channelId int64, vendorId string, serialNumber string, onuId int64) error {
 	log.Debugw("childDeviceDetected", log.Fields{"parentDeviceId": parentDeviceId})
 
 	// Create the ONU device
@@ -494,6 +556,8 @@
 	childDevice.Type = deviceType
 	childDevice.ParentId = parentDeviceId
 	childDevice.ParentPortNo = uint32(parentPortNo)
+	childDevice.VendorId = vendorId
+	childDevice.SerialNumber = serialNumber
 	childDevice.Root = false
 
 	//Get parent device type
@@ -503,7 +567,12 @@
 		return status.Errorf(codes.NotFound, "%s", parentDeviceId)
 	}
 
-	childDevice.ProxyAddress = &voltha.Device_ProxyAddress{DeviceId: parentDeviceId, DeviceType: parent.Type, ChannelId: uint32(channelId)}
+	if _, err := dMgr.GetChildDevice(parentDeviceId, serialNumber, onuId, parentPortNo); err == nil {
+		log.Warnw("child-device-exists", log.Fields{"parentId": parentDeviceId, "serialNumber": serialNumber})
+		return status.Errorf(codes.AlreadyExists, "%s", serialNumber)
+	}
+
+	childDevice.ProxyAddress = &voltha.Device_ProxyAddress{DeviceId: parentDeviceId, DeviceType: parent.Type, ChannelId: uint32(channelId), OnuId: uint32(onuId)}
 
 	// Create and start a device agent for that device
 	agent := newDeviceAgent(dMgr.adapterProxy, childDevice, dMgr, dMgr.clusterDataProxy)
@@ -688,6 +757,7 @@
 			}
 		}
 	}
+	log.Debugw("returning-getAllChildDeviceIds", log.Fields{"parentDeviceId": parentDevice.Id, "childDeviceIds": childDeviceIds})
 	return childDeviceIds, nil
 }