VOL-2242: Default TP not chosen when TP not available on KV store

- Choose a default TP when given TP table-id is not found on kv store
- Signicant simplication from previous interpretation of instance-control attributes
- Fix logs
- Bump version to 2.2.17

Change-Id: I2e49d9942fdcb00d1354fd51575f26bccedf1471
diff --git a/VERSION b/VERSION
index da36238..c36c648 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.2.17-dev
+2.2.17
diff --git a/pkg/techprofile/tech_profile.go b/pkg/techprofile/tech_profile.go
index 8d391eb..2c0edae 100644
--- a/pkg/techprofile/tech_profile.go
+++ b/pkg/techprofile/tech_profile.go
@@ -290,57 +290,23 @@
 	var KvTpIns TechProfile
 	var resPtr *TechProfile = &KvTpIns
 	var err error
-	var tp *DefaultTechProfile = nil
 	var kvResult *kvstore.KVPair
-	if tp = t.getTPFromKVStore(techProfiletblID); tp != nil {
-		if err := t.validateInstanceControlAttr(tp.InstanceCtrl); err != nil {
-			return nil, errors.New("invalid-instance-ctl-attr")
-		}
-	} else {
-		return nil, errors.New("tp-not-found-on-kv-store")
-	}
-	if tp.InstanceCtrl.Onu == "multi-instance" {
-		// When InstanceCtrl.Onu is "multi-instance" there can be multiple instance of the same
-		// TP across different UNIs. We either find a pre-existing TP Instance on that UNI or
-		// create a new one.
-		log.Infow("Getting tech profile instance from KV store", log.Fields{"path": path})
-		kvResult, err = t.config.KVBackend.Get(path)
-	} else { // "single-instance"
-		// When InstanceCtrl.Onu is "single-instance" there can be only one instance of the
-		// TP across all UNIs. The TP instances for the given TP ID will have the same alloc_id,
-		// but different gemport-ids (per UNI).
-		// We do the following
-		// 1. Find a pre-existing TP Instance for the given TP ID and on the given UNI.
-		//    If exists, return, else step 2.
-		// 2. Find TP instance for the given TP ID and on any other UNI on that ONU.
-		//    If exists, make a copy of the TP instance, replace the gem-port IDs, place it
-		//    in the the current UNIs TP instance path.
-		//    If no other UNI have TP instance too, then return nil (a new TP instance will
-		//    get created for the given TP ID).
-		kvResult, err = t.config.KVBackend.Get(path)
-		if kvResult == nil {
-			if resPtr, err = t.findAndAssignTpInstance(path); resPtr != nil {
-				log.Infow("successfully-found-and-assigned-tp-instance", log.Fields{"tpPath": path})
-				return resPtr, err
-			}
-		}
-	}
 
-	if err != nil {
-		log.Errorw("Error while fetching tech-profile instance  from KV backend", log.Fields{"key": path})
-		return nil, err
-	}
+	kvResult, _ = t.config.KVBackend.Get(path)
 	if kvResult == nil {
-		log.Infow("Tech profile does not exist in KV store", log.Fields{"key": path})
-		resPtr = nil
+		log.Infow("tp-instance-not-found-on-kv", log.Fields{"key": path})
+		return nil, nil
 	} else {
 		if value, err := kvstore.ToByte(kvResult.Value); err == nil {
 			if err = json.Unmarshal(value, resPtr); err != nil {
-				log.Errorw("Error while unmarshal KV result", log.Fields{"key": path, "value": value})
+				log.Errorw("error-unmarshal-kv-result", log.Fields{"key": path, "value": value})
+				return nil, errors.New("error-unmarshal-kv-result")
+			} else {
+				return resPtr, nil
 			}
 		}
 	}
-	return resPtr, err
+	return nil, err
 }
 
 func (t *TechProfileMgr) addTechProfInstanceToKVStore(techProfiletblID uint32, uniPortName string, tpInstance *TechProfile) error {
@@ -378,7 +344,7 @@
 }
 func (t *TechProfileMgr) CreateTechProfInstance(techProfiletblID uint32, uniPortName string, intfId uint32) *TechProfile {
 	var tpInstance *TechProfile
-	log.Infow("Creating tech profile instance ", log.Fields{"tableid": techProfiletblID, "uni": uniPortName, "intId": intfId})
+	log.Infow("creating-tp-instance", log.Fields{"tableid": techProfiletblID, "uni": uniPortName, "intId": intfId})
 
 	// Make sure the uniPortName is as per format pon-{[0-9]+}/onu-{[0-9]+}/uni-{[0-9]+}
 	if !uniPortNameFormat.Match([]byte(uniPortName)) {
@@ -388,17 +354,26 @@
 
 	tp := t.getTPFromKVStore(techProfiletblID)
 	if tp != nil {
-		log.Infow("Creating tech profile instance with profile from KV store", log.Fields{"tpid": techProfiletblID})
+		if err := t.validateInstanceControlAttr(tp.InstanceCtrl); err != nil {
+			log.Error("invalid-instance-ctrl-attr--using-default-tp")
+			tp = t.getDefaultTechProfile()
+		} else {
+			log.Infow("using-specified-tp-from-kv-store", log.Fields{"tpid": techProfiletblID})
+		}
 	} else {
+		log.Info("tp-not-found-on-kv--creating-default-tp")
 		tp = t.getDefaultTechProfile()
-		log.Infow("Creating tech profile instance with default values", log.Fields{"tpid": techProfiletblID})
 	}
-	tpInstance = t.allocateTPInstance(uniPortName, tp, intfId)
-	if err := t.addTechProfInstanceToKVStore(techProfiletblID, uniPortName, tpInstance); err != nil {
-		log.Errorw("Error in adding tech profile instance to KV ", log.Fields{"tableid": techProfiletblID, "uni": uniPortName})
+	tpInstancePath := t.GetTechProfileInstanceKVPath(techProfiletblID, uniPortName)
+	if tpInstance = t.allocateTPInstance(uniPortName, tp, intfId, tpInstancePath); tpInstance == nil {
+		log.Error("tp-intance-allocation-failed")
 		return nil
 	}
-	log.Infow("Added tech profile instance to KV store successfully ",
+	if err := t.addTechProfInstanceToKVStore(techProfiletblID, uniPortName, tpInstance); err != nil {
+		log.Errorw("error-adding-tp-to-kv-store ", log.Fields{"tableid": techProfiletblID, "uni": uniPortName})
+		return nil
+	}
+	log.Infow("tp-added-to-kv-store-successfully",
 		log.Fields{"tpid": techProfiletblID, "uni": uniPortName, "intfId": intfId})
 	return tpInstance
 }
@@ -427,7 +402,7 @@
 	return nil
 }
 
-func (t *TechProfileMgr) allocateTPInstance(uniPortName string, tp *DefaultTechProfile, intfId uint32) *TechProfile {
+func (t *TechProfileMgr) allocateTPInstance(uniPortName string, tp *DefaultTechProfile, intfId uint32, tpInstPath string) *TechProfile {
 
 	var usGemPortAttributeList []iGemPortAttribute
 	var dsGemPortAttributeList []iGemPortAttribute
@@ -437,15 +412,24 @@
 
 	log.Infow("Allocating TechProfileMgr instance from techprofile template", log.Fields{"uniPortName": uniPortName, "intfId": intfId, "numGem": tp.NumGemPorts})
 
-	err = t.validateInstanceControlAttr(tp.InstanceCtrl)
-	if err != nil {
-		log.Error("invalid-tp-instance-control-attributes")
-		return nil
-	}
-
-	if tcontIDs, err = t.resourceMgr.GetResourceID(intfId, t.resourceMgr.GetResourceTypeAllocID(), 1); err != nil {
-		log.Errorw("Error getting alloc id from rsrcrMgr", log.Fields{"intfId": intfId})
-		return nil
+	if tp.InstanceCtrl.Onu == "multi-instance" {
+		if tcontIDs, err = t.resourceMgr.GetResourceID(intfId, t.resourceMgr.GetResourceTypeAllocID(), 1); err != nil {
+			log.Errorw("Error getting alloc id from rsrcrMgr", log.Fields{"intfId": intfId})
+			return nil
+		}
+	} else { // "single-instance"
+		tpInst, err := t.getSingleInstanceTp(tpInstPath)
+		if tpInst == nil {
+			// No "single-instance" tp found on one any uni port for the given TP ID
+			// Allocate a new TcontID or AllocID
+			if tcontIDs, err = t.resourceMgr.GetResourceID(intfId, t.resourceMgr.GetResourceTypeAllocID(), 1); err != nil {
+				log.Errorw("Error getting alloc id from rsrcrMgr", log.Fields{"intfId": intfId})
+				return nil
+			}
+		} else {
+			// Use the alloc-id from the existing TpInstance
+			tcontIDs = append(tcontIDs, tpInst.UsScheduler.AllocID)
+		}
 	}
 	log.Debugw("Num GEM ports in TP:", log.Fields{"NumGemPorts": tp.NumGemPorts})
 	if gemPorts, err = t.resourceMgr.GetResourceID(intfId, t.resourceMgr.GetResourceTypeGemPortID(), tp.NumGemPorts); err != nil {
@@ -500,21 +484,10 @@
 		DownstreamGemPortAttributeList: dsGemPortAttributeList}
 }
 
-// findAndAssignTpInstance finds out if there is another TpInstance for an ONU on a different
-// uni port for the same TP ID.
-// If it finds one:
-// 1. It will make a copy of the TpInstance
-// 2. Retain the AllocID
-// 3. Replace the GemPort IDs
-// 4. Copy the new TpInstance on the given tpPath
-// ** NOTE ** : This is to be used only when the instance control attribute is as below
-// uni: single-instance, onu: single-instance
-func (t *TechProfileMgr) findAndAssignTpInstance(tpPath string) (*TechProfile, error) {
+// getSingleInstanceTp returns another TpInstance for an ONU on a different
+// uni port for the same TP ID, if it finds one, else nil.
+func (t *TechProfileMgr) getSingleInstanceTp(tpPath string) (*TechProfile, error) {
 	var tpInst TechProfile
-	var foundValidTpInst = false
-	var gemPortIDs []uint32
-	var intfID uint64
-	var err error
 
 	// For example:
 	// tpPath like "service/voltha/technology_profiles/xgspon/64/pon-{0}/onu-{1}/uni-{1}"
@@ -522,19 +495,6 @@
 	uniPathSlice := regexp.MustCompile(`/uni-{[0-9]+}$`).Split(tpPath, 2)
 	kvPairs, _ := t.config.KVBackend.List(uniPathSlice[0])
 
-	// Find the PON interface ID from the TP Path
-	var tpPathRgx = regexp.MustCompile(`pon-{([0-9]+)}`)
-	intfStrMatch := tpPathRgx.FindStringSubmatch(tpPath)
-	if intfStrMatch == nil {
-		log.Error("could-not-find-pon-intf-id-in-tp-path")
-		return nil, errors.New("could-not-find-pon-intf-id-in-tp-path")
-	} else {
-		if intfID, err = strconv.ParseUint(intfStrMatch[1], 10, 64); err != nil {
-			log.Errorw("error-converting-pon-intfid-str-to-unint", log.Fields{"intfIdStr": intfStrMatch[1]})
-			return nil, errors.New("error-converting-pon-intfid-str-to-unint")
-		}
-	}
-
 	// Find a valid TP Instance among all the UNIs of that ONU for the given TP ID
 	for keyPath, kvPair := range kvPairs {
 		if value, err := kvstore.ToByte(kvPair.Value); err == nil {
@@ -543,39 +503,10 @@
 				return nil, errors.New("error-unmarshal-kv-pair")
 			} else {
 				log.Debugw("found-valid-tp-instance-on-another-uni", log.Fields{"keyPath": keyPath})
-				foundValidTpInst = true
-				break
+				return &tpInst, nil
 			}
 		}
 	}
-	if foundValidTpInst {
-		// Get new GemPort IDs
-		if gemPortIDs, err = t.resourceMgr.GetResourceID(uint32(intfID), t.resourceMgr.GetResourceTypeGemPortID(), tpInst.NumGemPorts); err != nil {
-			log.Errorw("gem-port-assignment-failed", log.Fields{"intfId": intfID, "numGemports": tpInst.NumGemPorts})
-			return nil, errors.New("gem-port-assignment-failed")
-		}
-		// Update the new GemPort IDs to the TpInstance
-		for i := 0; i < int(tpInst.NumGemPorts); i++ {
-			tpInst.DownstreamGemPortAttributeList[i].GemportID = gemPortIDs[i]
-			tpInst.UpstreamGemPortAttributeList[i].GemportID = gemPortIDs[i]
-		}
-
-		tpInstanceJson, err := json.Marshal(tpInst)
-		if err == nil {
-			// Backend will convert JSON byte array into string format
-			log.Debugw("store-tp-instance", log.Fields{"tpPath": tpPath, "val": tpInstanceJson})
-			if err = t.config.KVBackend.Put(tpPath, tpInstanceJson); err != nil {
-				return nil, errors.New("error-store-instance-on-kv")
-			}
-			// We have succesfully placed the new TP Instance if we land here.
-			log.Debugw("successfully-placed-single-instance-tp-for-tp-path", log.Fields{"tpPath": tpPath})
-			return &tpInst, nil
-		} else {
-			log.Errorw("error-marshal-tp-to-json", log.Fields{"tpPath": tpPath, "tpInstance": tpInst})
-			return nil, errors.New("error-marshal-tp-to-json")
-		}
-	}
-	log.Debug("no-pre-existing-tp-instance-found-on-another-uni")
 	return nil, nil
 }