VOL-3431: Following enhancement/changes are done in this patch
- Process ONUs in bulk rather than serial, significantly improves run time
- Used a separate API to get flow-id. This flow-id is not freed on failure,
  as this adds to unnecessary complexity and unwarranted for a test tool .
- Print the total execution time at the end of the test
- Fixed the Dockerfile to not build vendor module at each docker build time
- Introduced new functions to retry scheduler, queue and flow adds on failure,
  but these are not currently used
- Add vendor modules to repo just like all other ONF VOLTHA golang projects do
- Tested all three workflows - ATT, DT and TT
- Bump version to 1.1.0

Change-Id: I6102cb206e78ea04b49b7125b101946ca6f36bfb
diff --git a/core/olt_manager.go b/core/olt_manager.go
index 78dd97e..f4e3143 100644
--- a/core/olt_manager.go
+++ b/core/olt_manager.go
@@ -44,7 +44,6 @@
 const (
 	ReasonOk          = "OK"
 	TechProfileKVPath = "service/voltha/technology_profiles/%s/%d" // service/voltha/technology_profiles/xgspon/<tech_profile_tableID>
-	DTWorkFlow        = "DT"
 )
 
 type OnuDeviceKey struct {
@@ -62,6 +61,7 @@
 	testConfig    *config.OpenOltScaleTesterConfig
 	rsrMgr        *OpenOltResourceMgr
 	lockRsrAlloc  sync.RWMutex
+	lockOpenOltManager sync.RWMutex
 }
 
 func init() {
@@ -74,6 +74,7 @@
 		ipPort:       ipPort,
 		OnuDeviceMap: make(map[OnuDeviceKey]*OnuDevice),
 		lockRsrAlloc: sync.RWMutex{},
+		lockOpenOltManager: sync.RWMutex{},
 	}
 }
 
@@ -208,43 +209,76 @@
 
 func (om *OpenOltManager) provisionONUs() {
 	var numOfONUsPerPon uint
-	var i, j, onuID uint32
+	var i, j, k, onuID uint32
 	var err error
-	oltChan := make(chan bool)
-	numOfONUsPerPon = om.testConfig.NumOfOnu / uint(om.deviceInfo.PonPorts)
-	if oddONUs := om.testConfig.NumOfOnu % uint(om.deviceInfo.PonPorts); oddONUs > 0 {
-		log.Warnw("Odd number ONUs left out of provisioning", log.Fields{"oddONUs": oddONUs})
+	var onuWg sync.WaitGroup
+
+	defer func() {
+		// Stop the process once the job is done
+		_ = syscall.Kill(syscall.Getpid(), syscall.SIGINT)
+	}()
+
+	// If the number of ONUs to provision is not a power of 2, stop execution
+	// This is needed for ensure even distribution of ONUs across all PONs
+	if !isPowerOfTwo(om.testConfig.NumOfOnu) {
+		log.Errorw("num-of-onus-to-provision-is-not-a-power-of-2", log.Fields{"numOfOnus": om.testConfig.NumOfOnu})
+		return
 	}
+
+	// Number of ONUs to provision should not be less than the number of PON ports.
+	// We need at least one ONU per PON
+	if om.testConfig.NumOfOnu < uint(om.deviceInfo.PonPorts) {
+		log.Errorw("num-of-onu-is-less-than-num-of-pon-port", log.Fields{"numOfOnus":om.testConfig.NumOfOnu, "numOfPon": om.deviceInfo.PonPorts})
+		return
+	}
+
+	numOfONUsPerPon = om.testConfig.NumOfOnu / uint(om.deviceInfo.PonPorts)
 	totalOnusToProvision := numOfONUsPerPon * uint(om.deviceInfo.PonPorts)
 	log.Infow("***** all-onu-provision-started ******",
 		log.Fields{"totalNumOnus": totalOnusToProvision,
 			"numOfOnusPerPon": numOfONUsPerPon,
 			"numOfPons":       om.deviceInfo.PonPorts})
-	for i = 0; i < om.deviceInfo.PonPorts; i++ {
-		for j = 0; j < uint32(numOfONUsPerPon); j++ {
-			// TODO: More work with ONU provisioning
-			om.lockRsrAlloc.Lock()
-			sn := GenerateNextONUSerialNumber()
-			om.lockRsrAlloc.Unlock()
-			log.Debugw("provisioning onu", log.Fields{"onuID": j, "ponPort": i, "serialNum": sn})
-			if onuID, err = om.rsrMgr.GetONUID(i); err != nil {
-				log.Errorw("error getting onu id", log.Fields{"err": err})
-				continue
-			}
-			log.Infow("onu-provision-started-from-olt-manager", log.Fields{"onuId": onuID, "ponIntf": i})
-			go om.activateONU(i, onuID, sn, om.stringifySerialNumber(sn), oltChan)
-			// Wait for complete ONU provision to succeed, including provisioning the subscriber
-			<-oltChan
-			log.Infow("onu-provision-completed-from-olt-manager", log.Fields{"onuId": onuID, "ponIntf": i})
 
-			// Sleep for configured time before provisioning next ONU
-			time.Sleep(time.Duration(om.testConfig.TimeIntervalBetweenSubs))
-		}
+	// These are the number of ONUs that will be provisioned per PON port per batch.
+	// Such number of ONUs will be chosen across all PON ports per batch
+	var onusPerIterationPerPonPort uint32 = 4
+
+	// If the total number of ONUs per PON is lesser than the default ONU to provision per pon port per batch
+	// then keep halving the ONU to provision per pon port per batch until we reach an acceptable number
+	// Note: the least possible value for onusPerIterationPerPonPort is 1
+	for uint32(numOfONUsPerPon) < onusPerIterationPerPonPort {
+		onusPerIterationPerPonPort /= 2
 	}
-	log.Info("******** all-onu-provisioning-completed *******")
 
-	// TODO: We need to dump the results at the end. But below json marshall does not work
-	// We will need custom Marshal function.
+	startTime := time.Now()
+	// Start provisioning the ONUs
+	for i = 0; i < uint32(numOfONUsPerPon)/onusPerIterationPerPonPort; i++ {
+		for j = 0; j < om.deviceInfo.PonPorts; j++ {
+			for k = 0; k < onusPerIterationPerPonPort; k++ {
+				om.lockRsrAlloc.Lock()
+				sn := GenerateNextONUSerialNumber()
+				om.lockRsrAlloc.Unlock()
+				log.Debugw("provisioning onu", log.Fields{"onuID": j, "ponPort": i, "serialNum": sn})
+				if onuID, err = om.rsrMgr.GetONUID(j); err != nil {
+					log.Errorw("error getting onu id", log.Fields{"err": err})
+					continue
+				}
+				log.Infow("onu-provision-started-from-olt-manager", log.Fields{"onuId": onuID, "ponIntf": i})
+
+				onuWg.Add(1)
+				go om.activateONU(j, onuID, sn, om.stringifySerialNumber(sn), &onuWg)
+			}
+		}
+		// Wait for the group of ONUs to complete processing before going to next batch of ONUs
+		onuWg.Wait()
+	}
+	endTime := time.Now()
+	log.Info("******** all-onu-provisioning-completed *******")
+	totalTime := endTime.Sub(startTime)
+	out := time.Time{}.Add(totalTime)
+	log.Infof("****** Total Time to provision all the ONUs is => %s", out.Format("15:04:05"))
+
+	// TODO: We need to dump the results at the end. But below json marshall does not work. We will need custom Marshal function.
 	/*
 		e, err := json.Marshal(om)
 		if err != nil {
@@ -253,12 +287,9 @@
 		}
 		fmt.Println(string(e))
 	*/
-
-	// Stop the process once the job is done
-	_ = syscall.Kill(syscall.Getpid(), syscall.SIGINT)
 }
 
-func (om *OpenOltManager) activateONU(intfID uint32, onuID uint32, serialNum *oop.SerialNumber, serialNumber string, oltCh chan bool) {
+func (om *OpenOltManager) activateONU(intfID uint32, onuID uint32, serialNum *oop.SerialNumber, serialNumber string, onuWg *sync.WaitGroup) {
 	log.Debugw("activate-onu", log.Fields{"intfID": intfID, "onuID": onuID, "serialNum": serialNum, "serialNumber": serialNumber})
 	// TODO: need resource manager
 	var pir uint32 = 1000000
@@ -269,6 +300,7 @@
 		openOltClient: om.openOltClient,
 		testConfig:    om.testConfig,
 		rsrMgr:        om.rsrMgr,
+		onuWg:         onuWg,
 	}
 	var err error
 	onuDeviceKey := OnuDeviceKey{onuID: onuID, ponInfID: intfID}
@@ -281,7 +313,6 @@
 		st, _ := status.FromError(err)
 		if st.Code() == codes.AlreadyExists {
 			log.Debug("ONU activation is in progress", log.Fields{"SerialNumber": serialNumber})
-			oltCh <- false
 		} else {
 			nanos = now.UnixNano()
 			milliEnd := nanos / 1000000
@@ -289,7 +320,6 @@
 			onuDevice.OnuProvisionDurationInMs = milliEnd - milliStart
 			log.Errorw("activate-onu-failed", log.Fields{"Onu": Onu, "err ": err})
 			onuDevice.Reason = err.Error()
-			oltCh <- false
 		}
 	} else {
 		nanos = now.UnixNano()
@@ -300,12 +330,16 @@
 		log.Infow("activated-onu", log.Fields{"SerialNumber": serialNumber})
 	}
 
+	om.lockOpenOltManager.Lock()
 	om.OnuDeviceMap[onuDeviceKey] = &onuDevice
+	om.lockOpenOltManager.Unlock()
 
 	// If ONU activation was success provision the ONU
 	if err == nil {
-		// start provisioning the ONU
-		go om.OnuDeviceMap[onuDeviceKey].Start(oltCh)
+		om.lockOpenOltManager.RLock()
+		go om.OnuDeviceMap[onuDeviceKey].Start()
+		om.lockOpenOltManager.RUnlock()
+
 	}
 }
 
@@ -441,3 +475,7 @@
 		log.Fields{"numofTech": tpCount, "numPonPorts": om.deviceInfo.GetPonPorts()})
 	return nil
 }
+
+func isPowerOfTwo(numOfOnus uint) bool {
+	return (numOfOnus & (numOfOnus - 1)) == 0
+}
\ No newline at end of file