[VOL-3837] Checking that OnuId, AllocId and GemPorts are unique per PON
Validate that received flow carry valid GemPortId and AllocIds

Change-Id: I1b8928c7a9e580c9711f61320595a449df7c30f5
diff --git a/internal/bbsim/devices/olt_test.go b/internal/bbsim/devices/olt_test.go
index 154942b..acbe7a7 100644
--- a/internal/bbsim/devices/olt_test.go
+++ b/internal/bbsim/devices/olt_test.go
@@ -28,15 +28,27 @@
 
 func createMockOlt(numPon int, numOnu int, services []ServiceIf) *OltDevice {
 	olt := &OltDevice{
-		ID: 0,
+		ID:         0,
+		AllocIDs:   make(map[uint32]map[uint32]map[uint32]map[int32]map[uint64]bool),
+		GemPortIDs: make(map[uint32]map[uint32]map[uint32]map[int32]map[uint64]bool),
 	}
 
 	for i := 0; i < numPon; i++ {
+
+		// initialize the resource maps for every PON Ports
+		olt.AllocIDs[uint32(i)] = make(map[uint32]map[uint32]map[int32]map[uint64]bool)
+		olt.GemPortIDs[uint32(i)] = make(map[uint32]map[uint32]map[int32]map[uint64]bool)
+
 		pon := PonPort{
 			ID: uint32(i),
 		}
 
 		for j := 0; j < numOnu; j++ {
+
+			// initialize the resource maps for every ONU and the first UNI
+			olt.AllocIDs[uint32(i)][uint32(j)] = make(map[uint32]map[int32]map[uint64]bool)
+			olt.GemPortIDs[uint32(i)][uint32(j)] = make(map[uint32]map[int32]map[uint64]bool)
+
 			onuId := uint32(i + j)
 			onu := Onu{
 				ID:        onuId,
@@ -51,7 +63,7 @@
 				onu.Services = append(onu.Services, service)
 			}
 
-			onu.SerialNumber = onu.NewSN(olt.ID, pon.ID, onu.ID)
+			onu.SerialNumber = NewSN(olt.ID, pon.ID, onu.ID)
 			pon.Onus = append(pon.Onus, &onu)
 		}
 		olt.Pons = append(olt.Pons, &pon)
@@ -227,3 +239,209 @@
 	assert.Equal(t, err, nil)
 	assert.Equal(t, found.Sn(), onu1.Sn())
 }
+
+func Test_Olt_storeGemPortId(t *testing.T) {
+
+	const (
+		pon  = 1
+		onu  = 1
+		uni  = 16
+		gem1 = 1024
+		gem2 = 1025
+	)
+
+	numPon := 2
+	numOnu := 2
+
+	olt := createMockOlt(numPon, numOnu, []ServiceIf{})
+
+	// add a first flow on the ONU
+	flow1 := &openolt.Flow{
+		AccessIntfId: pon,
+		OnuId:        onu,
+		PortNo:       uni,
+		FlowId:       1,
+		GemportId:    gem1,
+	}
+
+	olt.storeGemPortId(flow1)
+	assert.Equal(t, len(olt.GemPortIDs[pon][onu][uni]), 1)       // we have 1 gem port
+	assert.Equal(t, len(olt.GemPortIDs[pon][onu][uni][gem1]), 1) // and one flow referencing it
+
+	// add a second flow on the ONU (same gem)
+	flow2 := &openolt.Flow{
+		AccessIntfId: pon,
+		OnuId:        onu,
+		PortNo:       uni,
+		FlowId:       2,
+		GemportId:    gem1,
+	}
+
+	olt.storeGemPortId(flow2)
+	assert.Equal(t, len(olt.GemPortIDs[pon][onu][uni]), 1)       // we have 1 gem port
+	assert.Equal(t, len(olt.GemPortIDs[pon][onu][uni][gem1]), 2) // and two flows referencing it
+
+	// add a third flow on the ONU (different gem)
+	flow3 := &openolt.Flow{
+		AccessIntfId: pon,
+		OnuId:        onu,
+		PortNo:       uni,
+		FlowId:       2,
+		GemportId:    1025,
+	}
+
+	olt.storeGemPortId(flow3)
+	assert.Equal(t, len(olt.GemPortIDs[pon][onu][uni]), 2)       // we have 2 gem ports
+	assert.Equal(t, len(olt.GemPortIDs[pon][onu][uni][gem1]), 2) // two flows referencing the first one
+	assert.Equal(t, len(olt.GemPortIDs[pon][onu][uni][gem2]), 1) // and one flow referencing the second one
+}
+
+func Test_Olt_freeGemPortId(t *testing.T) {
+	const (
+		pon   = 1
+		onu   = 1
+		uni   = 16
+		gem1  = 1024
+		gem2  = 1025
+		flow1 = 1
+		flow2 = 2
+		flow3 = 3
+	)
+
+	numPon := 2
+	numOnu := 2
+
+	olt := createMockOlt(numPon, numOnu, []ServiceIf{})
+
+	olt.GemPortIDs[pon][onu][uni] = make(map[int32]map[uint64]bool)
+	olt.GemPortIDs[pon][onu][uni][gem1] = make(map[uint64]bool)
+	olt.GemPortIDs[pon][onu][uni][gem1][flow1] = true
+	olt.GemPortIDs[pon][onu][uni][gem1][flow2] = true
+	olt.GemPortIDs[pon][onu][uni][gem2] = make(map[uint64]bool)
+	olt.GemPortIDs[pon][onu][uni][gem2][flow3] = true
+
+	// remove one flow on the first gem, check that the gem is still allocated as there is still a flow referencing it
+	// NOTE that the flow remove only carries the flow ID, no other information
+	flowGem1 := &openolt.Flow{
+		FlowId: flow1,
+	}
+
+	olt.freeGemPortId(flowGem1)
+	// we still have two unis in the map
+	assert.Equal(t, len(olt.GemPortIDs[pon][onu][uni]), 2)
+
+	// we should now have a single gem referenced on this UNI
+	assert.Equal(t, len(olt.GemPortIDs[pon][onu][uni][gem1]), 1, "gemport-not-removed")
+
+	// the gem should still reference flow 2
+	assert.Equal(t, olt.GemPortIDs[pon][onu][uni][gem1][flow2], true)
+	// but should not reference flow1
+	_, flow1Exists := olt.GemPortIDs[pon][onu][uni][gem1][flow1]
+	assert.Equal(t, flow1Exists, false)
+
+	// this is the only flow remaining on this gem, the gem should be removed
+	flowGem2 := &openolt.Flow{
+		FlowId: flow2,
+	}
+	olt.freeGemPortId(flowGem2)
+
+	// we should now have a single gem referenced on this UNI
+	assert.Equal(t, len(olt.GemPortIDs[pon][onu][uni]), 1, "gemport-not-removed")
+
+	// and it should be gem2
+	_, gem1exists := olt.GemPortIDs[pon][onu][uni][gem1]
+	assert.Equal(t, gem1exists, false)
+	_, gem2exists := olt.GemPortIDs[pon][onu][uni][gem2]
+	assert.Equal(t, gem2exists, true)
+}
+
+func Test_Olt_validateFlow(t *testing.T) {
+
+	const (
+		pon0            = 0
+		pon1            = 1
+		onu0            = 0
+		onu1            = 1
+		uniPort         = 0
+		usedGemIdPon0   = 1024
+		usedGemIdPon1   = 1025
+		usedAllocIdPon0 = 1
+		usedAllocIdPon1 = 2
+		flowId          = 1
+	)
+
+	numPon := 2
+	numOnu := 2
+
+	olt := createMockOlt(numPon, numOnu, []ServiceIf{})
+
+	olt.GemPortIDs[pon0][onu0][uniPort] = make(map[int32]map[uint64]bool)
+	olt.GemPortIDs[pon1][onu0][uniPort] = make(map[int32]map[uint64]bool)
+
+	olt.GemPortIDs[pon0][onu0][uniPort][usedGemIdPon0] = make(map[uint64]bool)
+	olt.GemPortIDs[pon0][onu0][uniPort][usedGemIdPon0][flowId] = true
+	olt.GemPortIDs[pon1][onu0][uniPort][usedGemIdPon1] = make(map[uint64]bool)
+	olt.GemPortIDs[pon1][onu0][uniPort][usedGemIdPon1][flowId] = true
+
+	olt.AllocIDs[pon0][onu0][uniPort] = make(map[int32]map[uint64]bool)
+	olt.AllocIDs[pon1][onu0][uniPort] = make(map[int32]map[uint64]bool)
+	olt.AllocIDs[pon0][onu0][uniPort][usedAllocIdPon0] = make(map[uint64]bool)
+	olt.AllocIDs[pon0][onu0][uniPort][usedAllocIdPon0][flowId] = true
+	olt.AllocIDs[pon1][onu0][uniPort][usedAllocIdPon1] = make(map[uint64]bool)
+	olt.AllocIDs[pon1][onu0][uniPort][usedAllocIdPon1][flowId] = true
+
+	// a GemPortID can be referenced across multiple flows on the same ONU
+	validGemFlow := &openolt.Flow{
+		AccessIntfId: pon0,
+		OnuId:        onu0,
+		GemportId:    usedGemIdPon0,
+	}
+
+	err := olt.validateFlow(validGemFlow)
+	assert.NilError(t, err)
+
+	// a GemPortID can NOT be referenced across different ONUs on the same PON
+	invalidGemFlow := &openolt.Flow{
+		AccessIntfId: pon0,
+		OnuId:        onu1,
+		GemportId:    usedGemIdPon0,
+	}
+	err = olt.validateFlow(invalidGemFlow)
+	assert.Error(t, err, "gem-1024-already-in-use-on-uni-0-onu-0")
+
+	// if a flow reference the same GEM on a different PON it's a valid flow
+	invalidGemDifferentPonFlow := &openolt.Flow{
+		AccessIntfId: pon1,
+		OnuId:        onu1,
+		GemportId:    usedGemIdPon0,
+	}
+	err = olt.validateFlow(invalidGemDifferentPonFlow)
+	assert.NilError(t, err)
+
+	// an allocId can be referenced across multiple flows on the same ONU
+	validAllocFlow := &openolt.Flow{
+		AccessIntfId: pon0,
+		OnuId:        onu0,
+		AllocId:      usedAllocIdPon0,
+	}
+	err = olt.validateFlow(validAllocFlow)
+	assert.NilError(t, err)
+
+	// an allocId can NOT be referenced across different ONUs on the same PON
+	invalidAllocFlow := &openolt.Flow{
+		AccessIntfId: pon0,
+		OnuId:        onu1,
+		AllocId:      usedAllocIdPon0,
+	}
+	err = olt.validateFlow(invalidAllocFlow)
+	assert.Error(t, err, "allocId-1-already-in-use-on-uni-0-onu-0")
+
+	// if a flow reference the same AllocId on a different PON it's a valid flow
+	invalidAllocDifferentPonFlow := &openolt.Flow{
+		AccessIntfId: pon1,
+		OnuId:        onu1,
+		AllocId:      usedAllocIdPon0,
+	}
+	err = olt.validateFlow(invalidAllocDifferentPonFlow)
+	assert.NilError(t, err)
+}