[SEBA-918][VOL-2984] Allowing DHCP without completing eapol

For DT and TT workflow is required that DHCP is triggered without doing
anything for EAPOL. If you start BBSim with only the "-dhcp" option that
is now possible.

Change-Id: Iacfb75e70c47a2f7cfa64af58d6d848881f54974
diff --git a/internal/bbsim/devices/onu_flow_test.go b/internal/bbsim/devices/onu_flow_test.go
index e8fa451..9ab8abb 100644
--- a/internal/bbsim/devices/onu_flow_test.go
+++ b/internal/bbsim/devices/onu_flow_test.go
@@ -21,7 +21,9 @@
 	"github.com/looplab/fsm"
 	"github.com/opencord/voltha-protos/v2/go/openolt"
 	"gotest.tools/assert"
+	"sync"
 	"testing"
+	"time"
 )
 
 func Test_Onu_SendEapolFlow(t *testing.T) {
@@ -46,15 +48,16 @@
 }
 
 // validates that when an ONU receives an EAPOL flow for UNI 0
+// and the GemPort has already been configured
 // it transition to auth_started state
-func Test_HandleFlowUpdateEapolFromGem(t *testing.T) {
+func Test_HandleFlowUpdateEapolWithGem(t *testing.T) {
 
 	onu := createMockOnu(1, 1, 900, 900, true, false)
 
 	onu.InternalState = fsm.NewFSM(
-		"gem_port_added",
+		"enabled",
 		fsm.Events{
-			{Name: "start_auth", Src: []string{"eapol_flow_received", "gem_port_added"}, Dst: "auth_started"},
+			{Name: "start_auth", Src: []string{"enabled"}, Dst: "auth_started"},
 		},
 		fsm.Callbacks{},
 	)
@@ -87,97 +90,15 @@
 }
 
 // validates that when an ONU receives an EAPOL flow for UNI that is not 0
-// no action is taken
-func Test_HandleFlowUpdateEapolFromGemIgnore(t *testing.T) {
+// no action is taken (this is independent of GemPort status
+func Test_HandleFlowUpdateEapolWrongUNI(t *testing.T) {
 
-	onu := createMockOnu(1, 1, 900, 900, false, false)
-
-	onu.InternalState = fsm.NewFSM(
-		"gem_port_added",
-		fsm.Events{
-			{Name: "start_auth", Src: []string{"eapol_flow_received", "gem_port_added"}, Dst: "auth_started"},
-		},
-		fsm.Callbacks{},
-	)
-
-	flow := openolt.Flow{
-		AccessIntfId:  int32(onu.PonPortID),
-		OnuId:         int32(onu.ID),
-		UniId:         int32(1),
-		FlowId:        uint32(onu.ID),
-		FlowType:      "downstream",
-		AllocId:       int32(0),
-		NetworkIntfId: int32(0),
-		Classifier: &openolt.Classifier{
-			EthType: uint32(layers.EthernetTypeEAPOL),
-			OVid:    4091,
-		},
-		Action:   &openolt.Action{},
-		Priority: int32(100),
-		PortNo:   uint32(onu.ID), // NOTE we are using this to map an incoming packetIndication to an ONU
-	}
-
-	msg := OnuFlowUpdateMessage{
-		PonPortID: 1,
-		OnuID:     1,
-		Flow:      &flow,
-	}
-
-	onu.handleFlowUpdate(msg)
-	assert.Equal(t, onu.InternalState.Current(), "gem_port_added")
-}
-
-// validates that when an ONU receives an EAPOL flow for UNI 0
-// it transition to auth_started state
-func Test_HandleFlowUpdateEapolFromEnabled(t *testing.T) {
-
-	onu := createMockOnu(1, 1, 900, 900, false, false)
+	onu := createMockOnu(1, 1, 900, 900, true, false)
 
 	onu.InternalState = fsm.NewFSM(
 		"enabled",
 		fsm.Events{
-			{Name: "receive_eapol_flow", Src: []string{"enabled", "gem_port_added"}, Dst: "eapol_flow_received"},
-		},
-		fsm.Callbacks{},
-	)
-
-	flow := openolt.Flow{
-		AccessIntfId:  int32(onu.PonPortID),
-		OnuId:         int32(onu.ID),
-		UniId:         int32(0),
-		FlowId:        uint32(onu.ID),
-		FlowType:      "downstream",
-		AllocId:       int32(0),
-		NetworkIntfId: int32(0),
-		Classifier: &openolt.Classifier{
-			EthType: uint32(layers.EthernetTypeEAPOL),
-			OVid:    4091,
-		},
-		Action:   &openolt.Action{},
-		Priority: int32(100),
-		PortNo:   uint32(onu.ID), // NOTE we are using this to map an incoming packetIndication to an ONU
-	}
-
-	msg := OnuFlowUpdateMessage{
-		PonPortID: 1,
-		OnuID:     1,
-		Flow:      &flow,
-	}
-
-	onu.handleFlowUpdate(msg)
-	assert.Equal(t, onu.InternalState.Current(), "eapol_flow_received")
-}
-
-// validates that when an ONU receives an EAPOL flow for UNI that is not 0
-// no action is taken
-func Test_HandleFlowUpdateEapolFromEnabledIgnore(t *testing.T) {
-
-	onu := createMockOnu(1, 1, 900, 900, false, false)
-
-	onu.InternalState = fsm.NewFSM(
-		"enabled",
-		fsm.Events{
-			{Name: "receive_eapol_flow", Src: []string{"enabled", "gem_port_added"}, Dst: "eapol_flow_received"},
+			{Name: "start_auth", Src: []string{"enabled"}, Dst: "auth_started"},
 		},
 		fsm.Callbacks{},
 	)
@@ -210,14 +131,17 @@
 }
 
 // validates that when an ONU receives an EAPOL flow for UNI 0
-// but the noAuth bit is set no action is taken
-func Test_HandleFlowUpdateEapolNoAuth(t *testing.T) {
-	onu := createMockOnu(1, 1, 900, 900, false, false)
+// and the GemPort has not yet been configured
+// it transition to auth_started state
+func Test_HandleFlowUpdateEapolWithoutGem(t *testing.T) {
+
+	onu := createMockOnu(1, 1, 900, 900, true, false)
+	onu.GemPortAdded = false
 
 	onu.InternalState = fsm.NewFSM(
-		"gem_port_added",
+		"enabled",
 		fsm.Events{
-			{Name: "start_auth", Src: []string{"eapol_flow_received", "gem_port_added"}, Dst: "auth_started"},
+			{Name: "start_auth", Src: []string{"enabled"}, Dst: "auth_started"},
 		},
 		fsm.Callbacks{},
 	)
@@ -246,9 +170,68 @@
 	}
 
 	onu.handleFlowUpdate(msg)
-	assert.Equal(t, onu.InternalState.Current(), "gem_port_added")
+
+	wg := sync.WaitGroup{}
+	wg.Add(1)
+	go func(wg *sync.WaitGroup) {
+		defer wg.Done()
+		time.Sleep(100 * time.Millisecond)
+
+		// emulate the addition of a GemPort
+		for _, ch := range onu.GemPortChannels {
+			ch <- true
+		}
+
+		time.Sleep(100 * time.Millisecond)
+		assert.Equal(t, onu.InternalState.Current(), "auth_started")
+	}(&wg)
+	wg.Wait()
+
 }
 
+// validates that when an ONU receives an EAPOL flow for UNI 0
+// but the noAuth bit is set no action is taken
+func Test_HandleFlowUpdateEapolNoAuth(t *testing.T) {
+	onu := createMockOnu(1, 1, 900, 900, false, false)
+
+	onu.InternalState = fsm.NewFSM(
+		"enabled",
+		fsm.Events{
+			{Name: "start_auth", Src: []string{"enabled"}, Dst: "auth_started"},
+		},
+		fsm.Callbacks{},
+	)
+
+	flow := openolt.Flow{
+		AccessIntfId:  int32(onu.PonPortID),
+		OnuId:         int32(onu.ID),
+		UniId:         int32(0),
+		FlowId:        uint32(onu.ID),
+		FlowType:      "downstream",
+		AllocId:       int32(0),
+		NetworkIntfId: int32(0),
+		Classifier: &openolt.Classifier{
+			EthType: uint32(layers.EthernetTypeEAPOL),
+			OVid:    4091,
+		},
+		Action:   &openolt.Action{},
+		Priority: int32(100),
+		PortNo:   uint32(onu.ID), // NOTE we are using this to map an incoming packetIndication to an ONU
+	}
+
+	msg := OnuFlowUpdateMessage{
+		PonPortID: 1,
+		OnuID:     1,
+		Flow:      &flow,
+	}
+
+	onu.handleFlowUpdate(msg)
+	assert.Equal(t, onu.InternalState.Current(), "enabled")
+}
+
+// validates that when an ONU receives a DHCP flow for UNI 0 and pbit 0
+// and the GemPort has already been configured
+// it transition to dhcp_started state
 func Test_HandleFlowUpdateDhcp(t *testing.T) {
 	onu := createMockOnu(1, 1, 900, 900, false, true)
 
@@ -272,7 +255,7 @@
 			EthType: uint32(layers.EthernetTypeIPv4),
 			SrcPort: uint32(68),
 			DstPort: uint32(67),
-			OPbits: 0,
+			OPbits:  0,
 		},
 		Action:   &openolt.Action{},
 		Priority: int32(100),
@@ -290,6 +273,9 @@
 	assert.Equal(t, onu.DhcpFlowReceived, true)
 }
 
+// validates that when an ONU receives a DHCP flow for UNI 0 and pbit 255
+// and the GemPort has already been configured
+// it transition to dhcp_started state
 func Test_HandleFlowUpdateDhcpPBit255(t *testing.T) {
 	onu := createMockOnu(1, 1, 900, 900, false, true)
 
@@ -313,7 +299,7 @@
 			EthType: uint32(layers.EthernetTypeIPv4),
 			SrcPort: uint32(68),
 			DstPort: uint32(67),
-			OPbits: 0,
+			OPbits:  255,
 		},
 		Action:   &openolt.Action{},
 		Priority: int32(100),
@@ -331,6 +317,9 @@
 	assert.Equal(t, onu.DhcpFlowReceived, true)
 }
 
+// validates that when an ONU receives a DHCP flow for UNI 0 and pbit not 0 or 255
+// and the GemPort has already been configured
+// it ignores the message
 func Test_HandleFlowUpdateDhcpIgnoreByPbit(t *testing.T) {
 	onu := createMockOnu(1, 1, 900, 900, false, true)
 
@@ -354,7 +343,7 @@
 			EthType: uint32(layers.EthernetTypeIPv4),
 			SrcPort: uint32(68),
 			DstPort: uint32(67),
-			OPbits: 1,
+			OPbits:  1,
 		},
 		Action:   &openolt.Action{},
 		Priority: int32(100),
@@ -372,6 +361,8 @@
 	assert.Equal(t, onu.DhcpFlowReceived, false)
 }
 
+// validates that when an ONU receives a DHCP flow for UNI 0
+// but the noDchp bit is set no action is taken
 func Test_HandleFlowUpdateDhcpNoDhcp(t *testing.T) {
 	onu := createMockOnu(1, 1, 900, 900, false, false)
 
@@ -411,3 +402,66 @@
 	assert.Equal(t, onu.InternalState.Current(), "eap_response_success_received")
 	assert.Equal(t, onu.DhcpFlowReceived, false)
 }
+
+// validates that when an ONU receives a DHCP flow for UNI 0 and pbit not 0 or 255
+// and the GemPort has not already been configured
+// it transition to dhcp_started state
+func Test_HandleFlowUpdateDhcpWithoutGem(t *testing.T) {
+	// NOTE that this feature is required when we do DHCP with no eapol
+	// as the DHCP flow will be the first one received
+	onu := createMockOnu(1, 1, 900, 900, false, true)
+
+	onu.GemPortAdded = false
+
+	onu.InternalState = fsm.NewFSM(
+		"enabled",
+		fsm.Events{
+			{Name: "start_dhcp", Src: []string{"enabled"}, Dst: "dhcp_started"},
+		},
+		fsm.Callbacks{},
+	)
+
+	flow := openolt.Flow{
+		AccessIntfId:  int32(onu.PonPortID),
+		OnuId:         int32(onu.ID),
+		UniId:         int32(0),
+		FlowId:        uint32(onu.ID),
+		FlowType:      "downstream",
+		AllocId:       int32(0),
+		NetworkIntfId: int32(0),
+		Classifier: &openolt.Classifier{
+			EthType: uint32(layers.EthernetTypeIPv4),
+			SrcPort: uint32(68),
+			DstPort: uint32(67),
+			OPbits:  0,
+		},
+		Action:   &openolt.Action{},
+		Priority: int32(100),
+		PortNo:   uint32(onu.ID), // NOTE we are using this to map an incoming packetIndication to an ONU
+	}
+
+	msg := OnuFlowUpdateMessage{
+		PonPortID: 1,
+		OnuID:     1,
+		Flow:      &flow,
+	}
+
+	onu.handleFlowUpdate(msg)
+
+	wg := sync.WaitGroup{}
+	wg.Add(1)
+	go func(wg *sync.WaitGroup) {
+		defer wg.Done()
+		time.Sleep(100 * time.Millisecond)
+
+		// emulate the addition of a GemPort
+		for _, ch := range onu.GemPortChannels {
+			ch <- true
+		}
+
+		time.Sleep(100 * time.Millisecond)
+		assert.Equal(t, onu.InternalState.Current(), "dhcp_started")
+		assert.Equal(t, onu.DhcpFlowReceived, true)
+	}(&wg)
+	wg.Wait()
+}