diff --git a/cmd/packetrusher.go b/cmd/packetrusher.go index 43f72d71..6fb49e39 100644 --- a/cmd/packetrusher.go +++ b/cmd/packetrusher.go @@ -98,7 +98,7 @@ func main() { &cli.IntFlag{Name: "timeBeforeIdle", Value: 0, Aliases: []string{"idl"}, Usage: "The time in ms, before switching UE to Idle. 0 to disable Idling."}, &cli.IntFlag{Name: "timeBeforeReconnecting", Value: 1000, Aliases: []string{"tbr"}, Usage: "The time in ms, before reconnecting to gNodeB after switching to Idle state. Default is 1000 ms. Only work in conjunction with timeBeforeIdle."}, &cli.IntFlag{Name: "numPduSessions", Value: 1, Aliases: []string{"nPdu"}, Usage: "The number of PDU Sessions to create"}, - &cli.BoolFlag{Name: "loop", Aliases: []string{"l"}, Usage: "Register UEs in a loop."}, + &cli.BoolFlag{Name: "loop", Aliases: []string{"l"}, Usage: "Loop over all procedures."}, &cli.BoolFlag{Name: "tunnel", Aliases: []string{"t"}, Usage: "Enable the creation of the GTP-U tunnel interface."}, &cli.BoolFlag{Name: "tunnel-vrf", Value: true, Usage: "Enable/disable VRP usage of the GTP-U tunnel interface."}, &cli.BoolFlag{Name: "dedicatedGnb", Aliases: []string{"d"}, Usage: "Enable the creation of a dedicated gNB per UE. Require one IP on N2/N3 per gNB."}, diff --git a/config/config.go b/config/config.go index 1b37aeab..8d471ab8 100644 --- a/config/config.go +++ b/config/config.go @@ -195,7 +195,7 @@ func setLogLevel(cfg Config) { } -func (config *Config) GetUESecurityCapability() *nasType.UESecurityCapability { +func (ue *Ue) GetUESecurityCapability() *nasType.UESecurityCapability { UESecurityCapability := &nasType.UESecurityCapability{ Iei: nasMessage.RegistrationRequestUESecurityCapabilityType, Len: 2, @@ -203,16 +203,16 @@ func (config *Config) GetUESecurityCapability() *nasType.UESecurityCapability { } // Ciphering algorithms - UESecurityCapability.SetEA0_5G(boolToUint8(config.Ue.Ciphering.Nea0)) - UESecurityCapability.SetEA1_128_5G(boolToUint8(config.Ue.Ciphering.Nea1)) - UESecurityCapability.SetEA2_128_5G(boolToUint8(config.Ue.Ciphering.Nea2)) - UESecurityCapability.SetEA3_128_5G(boolToUint8(config.Ue.Ciphering.Nea3)) + UESecurityCapability.SetEA0_5G(boolToUint8(ue.Ciphering.Nea0)) + UESecurityCapability.SetEA1_128_5G(boolToUint8(ue.Ciphering.Nea1)) + UESecurityCapability.SetEA2_128_5G(boolToUint8(ue.Ciphering.Nea2)) + UESecurityCapability.SetEA3_128_5G(boolToUint8(ue.Ciphering.Nea3)) // Integrity algorithms - UESecurityCapability.SetIA0_5G(boolToUint8(config.Ue.Integrity.Nia0)) - UESecurityCapability.SetIA1_128_5G(boolToUint8(config.Ue.Integrity.Nia1)) - UESecurityCapability.SetIA2_128_5G(boolToUint8(config.Ue.Integrity.Nia2)) - UESecurityCapability.SetIA3_128_5G(boolToUint8(config.Ue.Integrity.Nia3)) + UESecurityCapability.SetIA0_5G(boolToUint8(ue.Integrity.Nia0)) + UESecurityCapability.SetIA1_128_5G(boolToUint8(ue.Integrity.Nia1)) + UESecurityCapability.SetIA2_128_5G(boolToUint8(ue.Integrity.Nia2)) + UESecurityCapability.SetIA3_128_5G(boolToUint8(ue.Integrity.Nia3)) return UESecurityCapability } diff --git a/config/config.yml b/config/config.yml index 58bdcc70..9c1e2cf0 100644 --- a/config/config.yml +++ b/config/config.yml @@ -41,4 +41,4 @@ amfif: - ip: "192.168.11.30" port: 38412 logs: - level: 4 \ No newline at end of file + level: 4 diff --git a/internal/common/tools/tools.go b/internal/common/tools/tools.go index a3dae7bc..a6ed4695 100644 --- a/internal/common/tools/tools.go +++ b/internal/common/tools/tools.go @@ -38,7 +38,7 @@ func CreateGnbs(count int, cfg config.Config, wg *sync.WaitGroup) map[string]*gn cfg.GNodeB.ControlIF.Ip = n2Ip cfg.GNodeB.DataIF.Ip = n3Ip - gnbs[cfg.GNodeB.PlmnList.GnbId] = gnb.InitGnb(cfg, wg) + gnbs[cfg.GNodeB.PlmnList.GnbId] = gnb.InitGnb(cfg.GNodeB, cfg.AMFs, wg) wg.Add(1) // TODO: We could find the interfaces where N2/N3 are @@ -127,7 +127,7 @@ func SimulateSingleUE(simConfig UESimulationConfig, wg *sync.WaitGroup) { // Create a new UE coroutine // ue.NewUE returns context of the new UE - ueTx := ue.NewUE(ueCfg, ueId, ueRx, simConfig.Gnbs[gnbIdGen(0)].GetInboundChannel(), wg) + ueTx := ue.NewUE(ueCfg.Ue, ueId, ueRx, simConfig.Gnbs[gnbIdGen(0)].GetInboundChannel(), wg) // We tell the UE to perform a registration ueRx <- procedures.UeTesterMessage{Type: procedures.Registration} diff --git a/internal/control_test_engine/gnb/gnb.go b/internal/control_test_engine/gnb/gnb.go index d4c218b1..8a877261 100644 --- a/internal/control_test_engine/gnb/gnb.go +++ b/internal/control_test_engine/gnb/gnb.go @@ -20,26 +20,26 @@ import ( log "github.com/sirupsen/logrus" ) -func InitGnb(conf config.Config, wg *sync.WaitGroup) *context.GNBContext { +func InitGnb(gnbConf config.GNodeB, amfConfs []*config.AMF, wg *sync.WaitGroup) *context.GNBContext { // instance new gnb. gnb := &context.GNBContext{} // new gnb context. gnb.NewRanGnbContext( - conf.GNodeB.PlmnList.GnbId, - conf.GNodeB.PlmnList.Mcc, - conf.GNodeB.PlmnList.Mnc, - conf.GNodeB.PlmnList.Tac, - conf.GNodeB.SliceSupportList.Sst, - conf.GNodeB.SliceSupportList.Sd, - conf.GNodeB.ControlIF.Ip, - conf.GNodeB.DataIF.Ip, - conf.GNodeB.ControlIF.Port, - conf.GNodeB.DataIF.Port) + gnbConf.PlmnList.GnbId, + gnbConf.PlmnList.Mcc, + gnbConf.PlmnList.Mnc, + gnbConf.PlmnList.Tac, + gnbConf.SliceSupportList.Sst, + gnbConf.SliceSupportList.Sd, + gnbConf.ControlIF.Ip, + gnbConf.DataIF.Ip, + gnbConf.ControlIF.Port, + gnbConf.DataIF.Port) // start communication with AMF (server SCTP). - for _, amfConfig := range conf.AMFs { + for _, amfConfig := range amfConfs { // new AMF context. amf := gnb.NewGnBAmf(amfConfig.Ip, amfConfig.Port) diff --git a/internal/control_test_engine/ue/ue.go b/internal/control_test_engine/ue/ue.go index 482b825a..77c96f72 100644 --- a/internal/control_test_engine/ue/ue.go +++ b/internal/control_test_engine/ue/ue.go @@ -22,27 +22,27 @@ import ( log "github.com/sirupsen/logrus" ) -func NewUE(conf config.Config, id int, ueMgrChannel chan procedures.UeTesterMessage, gnbInboundChannel chan context2.UEMessage, wg *sync.WaitGroup) chan scenario.ScenarioMessage { +func NewUE(conf config.Ue, id int, ueMgrChannel chan procedures.UeTesterMessage, gnbInboundChannel chan context2.UEMessage, wg *sync.WaitGroup) chan scenario.ScenarioMessage { // new UE instance. ue := &context.UEContext{} scenarioChan := make(chan scenario.ScenarioMessage) // new UE context ue.NewRanUeContext( - conf.Ue.Msin, + conf.Msin, conf.GetUESecurityCapability(), - conf.Ue.Key, - conf.Ue.Opc, + conf.Key, + conf.Opc, "c9e8763286b5b9ffbdf56e1297d0887b", - conf.Ue.Amf, - conf.Ue.Sqn, - conf.Ue.Hplmn.Mcc, - conf.Ue.Hplmn.Mnc, - conf.Ue.RoutingIndicator, - conf.Ue.Dnn, - int32(conf.Ue.Snssai.Sst), - conf.Ue.Snssai.Sd, - conf.Ue.TunnelMode, + conf.Amf, + conf.Sqn, + conf.Hplmn.Mcc, + conf.Hplmn.Mnc, + conf.RoutingIndicator, + conf.Dnn, + int32(conf.Snssai.Sst), + conf.Snssai.Sd, + conf.TunnelMode, scenarioChan, gnbInboundChannel, id) @@ -59,7 +59,7 @@ func NewUE(conf config.Config, id int, ueMgrChannel chan procedures.UeTesterMess select { case msg, open := <-ue.GetGnbTx(): if !open { - log.Warn("[UE][", ue.GetMsin(), "] Stopping UE as communication with gNB was closed") + log.Warn("[UE][", ue.GetMsin(), "] communication with gNB was closed") ue.SetGnbTx(nil) break } diff --git a/internal/scenario/scenario.go b/internal/scenario/scenario.go new file mode 100644 index 00000000..b658cbfd --- /dev/null +++ b/internal/scenario/scenario.go @@ -0,0 +1,17 @@ +/** + * SPDX-License-Identifier: Apache-2.0 + * © Copyright 2024 Hewlett Packard Enterprise Development LP + */ +package scenario + +import ( + "my5G-RANTester/config" +) + +// Description of the scenario to be run for one UE +type UEScenario struct { + Config config.Ue + Tasks []Task + Loop bool // Restart scenario once done + Persist bool // Keep scenario going after all tasks are done +} diff --git a/internal/scenario/scenarioManager.go b/internal/scenario/scenarioManager.go new file mode 100644 index 00000000..1913d8e9 --- /dev/null +++ b/internal/scenario/scenarioManager.go @@ -0,0 +1,273 @@ +/** + * SPDX-License-Identifier: Apache-2.0 + * © Copyright 2024 Hewlett Packard Enterprise Development LP + */ +package scenario + +import ( + "errors" + "fmt" + "my5G-RANTester/config" + "my5G-RANTester/internal/control_test_engine/gnb" + "my5G-RANTester/internal/control_test_engine/gnb/context" + "os" + "os/signal" + "sync" + "time" + + log "github.com/sirupsen/logrus" +) + +var ( + InitGnb = gnb.InitGnb // using var to overide InitGnb function during tests +) + +type ScenarioManager struct { + gnbs map[string]*context.GNBContext + ues map[int]*ueTasksCtx + reportChan chan report + registrationQueue chan order + gnbWg *sync.WaitGroup + taskExecutorWg *sync.WaitGroup + sigStop chan os.Signal + ueScenarios []UEScenario + stop bool +} + +// Context of running ue scenario +type ueTasksCtx struct { + done bool + nextTasks []Task + workerChan chan Task +} + +type report struct { + ueId int + success bool + reason string +} + +type order struct { + task Task + workerChan chan Task +} + +func Start(gnbConfs []config.GNodeB, amfConfs []*config.AMF, ueScenarios []UEScenario, timeBetweenRegistration int, timeout int) { + log.Info("------------------------------------ Starting scenario ------------------------------------") + s := initManager(ueScenarios, timeBetweenRegistration, timeout) + s.setupGnbs(gnbConfs, amfConfs) + s.setupScenarios(ueScenarios) + s.executeScenarios() + s.cleanup() + log.Info("------------------------------------ Scenario finished ------------------------------------") +} + +func initManager(ueScenarios []UEScenario, timeBetweenRegistration int, timeout int) *ScenarioManager { + log.Debug("Init manager with timeBetweenRegistration = ", timeBetweenRegistration) + s := &ScenarioManager{} + + s.ues = make(map[int]*ueTasksCtx) + + s.taskExecutorWg = &sync.WaitGroup{} + s.gnbWg = &sync.WaitGroup{} + + s.reportChan = make(chan report, 10) + + s.registrationQueue = make(chan order, len(ueScenarios)) + go startOrderRateController(s.registrationQueue, timeBetweenRegistration) + + s.sigStop = make(chan os.Signal, 1) + signal.Notify(s.sigStop, os.Interrupt) + if timeout > 0 { + time.AfterFunc(time.Duration(timeout)*time.Millisecond, func() { + log.Debug("Scenario Timeout, sending interrupt signal") + s.sigStop <- os.Interrupt + }) + } + return s +} + +func startOrderRateController(orderChan <-chan order, timeBetweenRegistration int) { + waitTime := time.Duration(timeBetweenRegistration) * time.Millisecond + orderTicker := time.NewTicker(waitTime) + var order order + var open bool + for { + <-orderTicker.C + orderTicker.Stop() + order, open = <-orderChan + if open { + order.workerChan <- order.task + orderTicker.Reset(waitTime) + } else { + return + } + } +} + +func (s *ScenarioManager) setupGnbs(gnbConfs []config.GNodeB, amfConfs []*config.AMF) { + s.gnbs = make(map[string]*context.GNBContext) + + for gnbConf := range gnbConfs { + s.gnbs[gnbConfs[gnbConf].PlmnList.GnbId] = InitGnb(gnbConfs[gnbConf], amfConfs, s.gnbWg) + s.gnbWg.Add(1) + } + + // Wait for gNB to be connected before registering UEs + // TODO: We should wait for NGSetupResponse instead + time.Sleep(2 * time.Second) +} + +func (s *ScenarioManager) setupScenarios(ueScenarios []UEScenario) { + s.stop = false + s.ueScenarios = ueScenarios + for ueId := 1; ueId <= len(ueScenarios); ueId++ { + if err := s.setupUeTaskExecutor(ueId, ueScenarios[ueId-1]); err != nil { + log.Errorf("scenario for UE %d could not be started: %v", ueId, err) + } + } +} + +func (s *ScenarioManager) setupUeTaskExecutor(ueId int, ueScenario UEScenario) error { + if ueId < 1 { + return errors.New("ueId must be at least 1") + } + _, exist := s.ues[ueId] + if exist { + return errors.New("this ue already have a task executor") + } + if ueScenario.Tasks == nil { + return errors.New("tasks list is nil") + } + if ueScenario.Config == (config.Ue{}) { + return errors.New("config is empty") + } + if s.reportChan == nil { + return errors.New("scenario manager's report channel is not set") + } + if s.gnbs == nil { + return errors.New("scenario manager's gnb list is not set") + } + ue := ueTasksCtx{ + done: false, + nextTasks: ueScenario.Tasks, + workerChan: make(chan Task, 1), + } + + worker := ueTaskExecutor{ + UeId: ueId, + UeCfg: ueScenario.Config, + TaskChan: ue.workerChan, + ReportChan: s.reportChan, + Gnbs: s.gnbs, + Wg: s.taskExecutorWg, + } + worker.Run() + + s.ues[ueId] = &ue + return nil +} + +func (s *ScenarioManager) executeScenarios() { + for !s.stop { + select { + case <-s.sigStop: + s.stop = true + case report := <-s.reportChan: + s.handleReport(report) + if err := s.manageNextTask(report.ueId, s.ueScenarios[report.ueId-1]); err != nil { + if !s.ueScenarios[report.ueId-1].Persist { + log.Debugf("Stopping simulation for ue %d, reason: %s", report.ueId, err) + s.ues[report.ueId].done = true + s.stop = true + for i := range s.ues { + if !s.ues[i].done { + s.stop = false + } + } + } + } + } + } +} + +func (s *ScenarioManager) handleReport(report report) { + if !report.success { + s.ues[report.ueId].nextTasks = []Task{} + log.Errorf("[UE-%d] Reported error: %s", report.ueId, report.reason) + return + } + log.Infof("[UE-%d] Reported succes: %s", report.ueId, report.reason) +} + +func (s *ScenarioManager) manageNextTask(ueId int, ueScenarios UEScenario) error { + var err error + if err = s.sendNextTask(ueId); err == nil { + return nil + } + if ueScenarios.Loop { + return s.restartUeScenario(ueId, ueScenarios) + } + return err +} + +func (s *ScenarioManager) sendNextTask(ueId int) error { + _, exist := s.ues[ueId] + if !exist { + return errors.New("ue not found") + } + if len(s.ues[ueId].nextTasks) > 0 { + if s.ues[ueId].nextTasks[0].TaskType == Registration { + s.registrationQueue <- order{task: s.ues[ueId].nextTasks[0], workerChan: s.ues[ueId].workerChan} + } else { + s.ues[ueId].workerChan <- s.ues[ueId].nextTasks[0] + } + s.ues[ueId].nextTasks = s.ues[ueId].nextTasks[1:] + return nil + } + return errors.New("ue does not have task to do") +} + +func (s *ScenarioManager) restartUeScenario(ueId int, ueScenario UEScenario) error { + _, exist := s.ues[ueId] + if !exist { + return errors.New("ue not found") + } + if ueScenario.Tasks == nil { + return fmt.Errorf("ue %d tasks is nil", ueId) + } + log.Debugf("[UE-%d] starting new loop", ueId) + s.ues[ueId].workerChan <- Task{TaskType: Kill} + s.ues[ueId].nextTasks = ueScenario.Tasks + return nil +} + +func (s *ScenarioManager) cleanup() { + drainAndCloseOrderRateController(s.registrationQueue) + + go func() { + open := true + var report report + for open { + report, open = <-s.reportChan + log.Debug("Got report from UE ", report.ueId, " during cleanup: ", report.reason) + } + }() + + for _, ue := range s.ues { + ue.workerChan <- Task{ + TaskType: Terminate, + } + } + + s.taskExecutorWg.Wait() + close(s.reportChan) +} + +func drainAndCloseOrderRateController(orderChan chan order) { + close(orderChan) + open := true + for open { + _, open = <-orderChan + } +} diff --git a/internal/scenario/scenarioManager_test.go b/internal/scenario/scenarioManager_test.go new file mode 100644 index 00000000..0845d8cc --- /dev/null +++ b/internal/scenario/scenarioManager_test.go @@ -0,0 +1,865 @@ +/** + * SPDX-License-Identifier: Apache-2.0 + * © Copyright 2024 Hewlett Packard Enterprise Development LP + */ +package scenario + +import ( + "io" + "my5G-RANTester/config" + "my5G-RANTester/internal/control_test_engine/gnb/context" + "os" + "sync" + "testing" + "time" + + log "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" +) + +var defaultUeId int = 1 + +func TestMain(m *testing.M) { + log.SetOutput(io.Discard) + os.Exit(m.Run()) +} + +func getDefaultManager() ScenarioManager { + s := ScenarioManager{ + ues: map[int]*ueTasksCtx{}, + } + s.registrationQueue = make(chan order, 5) + ue := &ueTasksCtx{} + ue.workerChan = make(chan Task, 5) + ue.nextTasks = []Task{} + s.ues[defaultUeId] = ue + s.taskExecutorWg = &sync.WaitGroup{} + s.gnbWg = &sync.WaitGroup{} + return s +} + +// create gnb context without starting services +func gnbInitMock(gnbConf config.GNodeB, amfConfs []*config.AMF, wg *sync.WaitGroup) *context.GNBContext { + // instance new gnb. + gnb := &context.GNBContext{} + + // new gnb context. + gnb.NewRanGnbContext( + gnbConf.PlmnList.GnbId, + gnbConf.PlmnList.Mcc, + gnbConf.PlmnList.Mnc, + gnbConf.PlmnList.Tac, + gnbConf.SliceSupportList.Sst, + gnbConf.SliceSupportList.Sd, + gnbConf.ControlIF.Ip, + gnbConf.DataIF.Ip, + gnbConf.ControlIF.Port, + gnbConf.DataIF.Port) + + gnb.NewGnBAmf(amfConfs[0].Ip, amfConfs[0].Port) + + return gnb +} + +func TestHandleReport(t *testing.T) { + type input struct { + tasks []Task + report report + } + + type testCases struct { + description string + input input + expected []Task + } + + tasks := []Task{ + { + TaskType: Registration, + Delay: 2000, + }, + { + TaskType: Idle, + Delay: 500, + }, + } + + scenarios := []testCases{ + { + description: "successful report should not change tasks", + input: input{ + tasks: tasks, + report: report{ + ueId: defaultUeId, + success: true, + reason: "sucessful", + }, + }, + expected: tasks, + }, + { + description: "failed report should empty tasks list", + input: input{ + tasks: tasks, + report: report{ + ueId: defaultUeId, + success: false, + reason: "fail", + }, + }, + expected: []Task{}, + }, + } + + for _, scenario := range scenarios { + t.Run(scenario.description, func(t *testing.T) { + s := &ScenarioManager{ + ues: map[int]*ueTasksCtx{defaultUeId: {nextTasks: tasks}}, + } + s.handleReport(scenario.input.report) + assert.Equal(t, scenario.expected, s.ues[defaultUeId].nextTasks) + }) + } +} + +func TestManageNextTask(t *testing.T) { + type input struct { + manager ScenarioManager + tasks []Task + UeScenario UEScenario + } + + type expected struct { + nextTasks []Task + err bool + } + + type testCases struct { + description string + input input + expected expected + } + + initialTasks := []Task{{ + TaskType: AttachToGNB, + Delay: 2000, + }, { + TaskType: Registration, + Delay: 2000, + }, { + TaskType: Idle, + Delay: 500, + }} + + scenarios := []testCases{{ + description: "Should send next task", + input: input{ + tasks: initialTasks, + manager: getDefaultManager(), + UeScenario: UEScenario{ + Tasks: initialTasks, + Loop: false, + }, + }, + expected: expected{ + nextTasks: []Task{{ + TaskType: Registration, + Delay: 2000, + }, { + TaskType: Idle, + Delay: 500, + }}, + err: false, + }, + }, { + description: "Should send next task without restarting scenario", + input: input{ + tasks: initialTasks, + manager: getDefaultManager(), + UeScenario: UEScenario{ + Tasks: initialTasks, + Loop: true, + }, + }, + expected: expected{ + nextTasks: []Task{{ + TaskType: Registration, + Delay: 2000, + }, { + TaskType: Idle, + Delay: 500, + }}, + err: false, + }, + }, { + description: "Should restart scenario", + input: input{ + tasks: []Task{}, + manager: getDefaultManager(), + UeScenario: UEScenario{ + Tasks: initialTasks, + Loop: true, + }, + }, + expected: expected{ + nextTasks: initialTasks, + err: false, + }, + }, { + description: "Should return error", + input: input{ + tasks: []Task{}, + manager: getDefaultManager(), + UeScenario: UEScenario{ + Tasks: initialTasks, + Loop: false, + }, + }, + expected: expected{ + nextTasks: []Task{}, + err: true, + }, + }} + + for _, scenario := range scenarios { + t.Run(scenario.description, func(t *testing.T) { + s := scenario.input.manager + s.ues[defaultUeId].nextTasks = scenario.input.tasks + err := s.manageNextTask(defaultUeId, scenario.input.UeScenario) + assert.Equal(t, scenario.expected.nextTasks, s.ues[defaultUeId].nextTasks) + if scenario.expected.err { + assert.Error(t, err) + } else { + assert.Nil(t, err) + } + }) + } +} + +func TestSendNextTask(t *testing.T) { + type input struct { + manager ScenarioManager + ueid int + tasks []Task + } + + type expected struct { + nextTasks []Task + sentToWorker Task + sentToQueue order + err bool + } + + type testCases struct { + description string + input input + expected expected + } + + manager := getDefaultManager() + + scenarios := []testCases{ + { + description: "task is send to worker", + input: input{ + tasks: []Task{{ + TaskType: AttachToGNB, + Delay: 2000, + }, { + TaskType: Registration, + Delay: 2000, + }, { + TaskType: Idle, + Delay: 500, + }}, + manager: getDefaultManager(), + ueid: defaultUeId, + }, + expected: expected{ + nextTasks: []Task{{ + TaskType: Registration, + Delay: 2000, + }, { + TaskType: Idle, + Delay: 500, + }}, + sentToWorker: Task{ + TaskType: AttachToGNB, + Delay: 2000, + }, + sentToQueue: order{}, + err: false, + }, + }, + { + description: "registration task is send to queue for timer handling", + input: input{ + tasks: []Task{{ + TaskType: Registration, + Delay: 2000, + }, { + TaskType: Idle, + Delay: 500, + }}, + manager: manager, + ueid: defaultUeId, + }, + expected: expected{ + nextTasks: []Task{{ + TaskType: Idle, + Delay: 500, + }}, + sentToWorker: Task{}, + sentToQueue: order{ + workerChan: manager.ues[defaultUeId].workerChan, + task: Task{ + TaskType: Registration, + Delay: 2000, + }, + }, + err: false, + }, + }, + { + description: "Does not have task left", + input: input{ + tasks: []Task{}, + manager: getDefaultManager(), + ueid: defaultUeId, + }, + expected: expected{ + nextTasks: []Task{}, + sentToWorker: Task{}, + sentToQueue: order{}, + err: true, + }, + }, + { + description: "Ue not found", + input: input{ + tasks: []Task{}, + manager: getDefaultManager(), + ueid: 2, + }, + expected: expected{ + nextTasks: []Task{}, + sentToWorker: Task{}, + sentToQueue: order{}, + err: true, + }, + }, + } + + for _, scenario := range scenarios { + t.Run(scenario.description, func(t *testing.T) { + + s := scenario.input.manager + s.ues[defaultUeId].nextTasks = scenario.input.tasks + res := s.sendNextTask(defaultUeId) + + var err string + + if scenario.expected.sentToWorker != (Task{}) { + select { + case task := <-s.ues[defaultUeId].workerChan: + assert.Equal(t, scenario.expected.sentToWorker, task) + default: + t.Error("Task expected to be sent to worker but was not.") + } + err = "More than one task was send to worker" + } else { + err = "Worker received a task but was not supposed to" + } + select { + case <-s.ues[defaultUeId].workerChan: + t.Error(err) + default: + } + + if scenario.expected.sentToQueue != (order{}) { + select { + case task := <-s.registrationQueue: + assert.Equal(t, scenario.expected.sentToQueue, task) + default: + t.Error("Task expected to be sent to registration queue but was not.") + } + err = "More than one task was send to registration queue" + } else { + err = "Registration queue received a task but was not supposed to" + } + select { + case <-s.registrationQueue: + t.Error(err) + default: + } + + if scenario.expected.err { + assert.Error(t, res) + } else { + assert.Nil(t, res, "SendNextTask returned an error but was not supposed to") + } + + assert.Equal(t, scenario.expected.nextTasks, s.ues[defaultUeId].nextTasks) + }) + } + +} + +func TestRestartUeScenario(t *testing.T) { + type input struct { + manager ScenarioManager + tasks []Task + } + + type testCases struct { + description string + input input + expected []Task + } + + tasks := []Task{{ + TaskType: AttachToGNB, + Delay: 0, + }, { + TaskType: Registration, + Delay: 2000, + }, { + TaskType: NewPDUSession, + Delay: 0, + }, { + TaskType: Idle, + Delay: 500, + }} + + scenarios := []testCases{{ + description: "UE should be killed and tasks reset", + input: input{ + manager: getDefaultManager(), + tasks: []Task{{ + TaskType: Idle, + Delay: 500, + }}, + }, + expected: tasks, + }, { + description: "UE should be killed and tasks reset", + input: input{ + manager: getDefaultManager(), + tasks: []Task{}, + }, + expected: tasks, + }} + + for _, scenario := range scenarios { + t.Run(scenario.description, func(t *testing.T) { + s := scenario.input.manager + ueScenario := UEScenario{ + Tasks: tasks, + } + s.restartUeScenario(defaultUeId, ueScenario) + + select { + case task := <-s.ues[defaultUeId].workerChan: + assert.Equal(t, Task{TaskType: Kill}, task) + default: + t.Error("Kill Task expected to be sent to worker but was not.") + } + assert.Equal(t, scenario.expected, s.ues[defaultUeId].nextTasks) + }) + } +} + +func TestRestartUeScenarioHasErrors(t *testing.T) { + + type input struct { + manager ScenarioManager + tasks []Task + ueId int + } + + type testCases struct { + description string + input input + } + + scenarios := []testCases{{ + description: "Ue does not exist", + input: input{ + manager: getDefaultManager(), + tasks: []Task{}, + ueId: 2, + }}, { + description: "UeScenarios is nil", + input: input{ + manager: getDefaultManager(), + tasks: nil, + ueId: defaultUeId, + }, + }} + + for _, scenario := range scenarios { + t.Run(scenario.description, func(t *testing.T) { + s := scenario.input.manager + ueScenario := UEScenario{ + Tasks: scenario.input.tasks, + } + assert.Error(t, s.restartUeScenario(scenario.input.ueId, ueScenario)) + }) + } +} + +func TestSetupGnbs(t *testing.T) { + type input struct { + manager ScenarioManager + gnbConfs []config.GNodeB + amfConfs []*config.AMF + } + + type testCases struct { + description string + input input + expected int + } + + gnbConfs1 := []config.GNodeB{ + { + ControlIF: config.ControlIF{ + Ip: "127.0.0.1", + Port: 9487, + }, + DataIF: config.DataIF{ + Ip: "127.0.0.2", + Port: 2152, + }, + PlmnList: config.PlmnList{ + Mcc: "999", + Mnc: "70", + Tac: "000001", + GnbId: "000001", + }, + SliceSupportList: config.SliceSupportList{ + Sst: "01", + }, + }, + } + + gnbConfs2 := []config.GNodeB{ + { + ControlIF: config.ControlIF{ + Ip: "192.168.11.12", + Port: 5000, + }, + DataIF: config.DataIF{ + Ip: "192.168.11.12", + Port: 3000, + }, + PlmnList: config.PlmnList{ + Mcc: "999", + Mnc: "70", + Tac: "000001", + GnbId: "000002", + }, + SliceSupportList: config.SliceSupportList{ + Sst: "01", + }, + }, + { + ControlIF: config.ControlIF{ + Ip: "192.168.11.13", + Port: 9487, + }, + DataIF: config.DataIF{ + Ip: "192.168.11.13", + Port: 2152, + }, + PlmnList: config.PlmnList{ + Mcc: "999", + Mnc: "70", + Tac: "000001", + GnbId: "000003", + }, + SliceSupportList: config.SliceSupportList{ + Sst: "01", + }, + }, + } + + amfConfs := []*config.AMF{{ + Ip: "192.168.11.30", + Port: 38412, + }} + + InitGnb = gnbInitMock + + scenarios := []testCases{{ + description: "1 gnb should be created", + input: input{ + manager: getDefaultManager(), + gnbConfs: gnbConfs1, + amfConfs: amfConfs, + }, + expected: len(gnbConfs1), + }, { + description: "2 gnbs should be created", + input: input{ + manager: getDefaultManager(), + gnbConfs: gnbConfs2, + amfConfs: amfConfs, + }, + expected: len(gnbConfs2), + }, + } + + for _, scenario := range scenarios { + t.Run(scenario.description, func(t *testing.T) { + s := scenario.input.manager + s.setupGnbs(scenario.input.gnbConfs, scenario.input.amfConfs) + for gnb := range scenario.input.gnbConfs { + gnbId := scenario.input.gnbConfs[gnb].PlmnList.GnbId + assert.Equal(t, gnbId, s.gnbs[gnbId].GetGnbId(), "GNB ID between key and values does not match") + } + assert.Equal(t, len(scenario.input.gnbConfs), len(s.gnbs), "Configured gnb and created gnb count does not match") + }) + } +} + +func TestSetupUeTaskExecutor(t *testing.T) { + type input struct { + manager ScenarioManager + ueScenario UEScenario + ueId int + gnbs *context.GNBContext + } + + type expected struct { + ueId int + config config.Ue + gnbs *context.GNBContext + tasks []Task + } + + type testCases struct { + description string + input input + expected expected + } + + gnbConf := config.GNodeB{ + ControlIF: config.ControlIF{ + Ip: "127.0.0.1", + Port: 9487, + }, + DataIF: config.DataIF{ + Ip: "127.0.0.2", + Port: 2152, + }, + PlmnList: config.PlmnList{ + Mcc: "999", + Mnc: "70", + Tac: "000001", + GnbId: "000001", + }, + SliceSupportList: config.SliceSupportList{ + Sst: "01", + }, + } + + amfConfs := []*config.AMF{{ + Ip: "192.168.11.30", + Port: 38412, + }} + + wg := &sync.WaitGroup{} + + gnbs := gnbInitMock(gnbConf, amfConfs, wg) + tasks := []Task{{ + TaskType: Registration, + Delay: 2000, + }, { + TaskType: NewPDUSession, + Delay: 0, + }, { + TaskType: Idle, + Delay: 0, + }} + + scenarios := []testCases{{ + description: "Test normal setup", + input: input{ + manager: ScenarioManager{ + ues: map[int]*ueTasksCtx{}, + }, + ueScenario: UEScenario{ + Config: config.Ue{ + Msin: "0000001000", + }, + Tasks: tasks, + }, + ueId: 1, + gnbs: gnbs, + }, + expected: expected{ + ueId: 1, + config: config.Ue{ + Msin: "0000001000", + }, + gnbs: gnbs, + tasks: tasks, + }, + }, + } + + for _, scenario := range scenarios { + t.Run(scenario.description, func(t *testing.T) { + s := scenario.input.manager + s.reportChan = make(chan report, 1) + s.gnbs = map[string]*context.GNBContext{scenario.input.gnbs.GetGnbId(): scenario.input.gnbs} + s.taskExecutorWg = &sync.WaitGroup{} + s.setupUeTaskExecutor(scenario.input.ueId, scenario.input.ueScenario) + + time.Sleep(time.Duration(100) * time.Millisecond) + + assert.Equal(t, scenario.expected.tasks, s.ues[scenario.input.ueId].nextTasks) + assert.Falsef(t, s.ues[scenario.input.ueId].done, "ueTasksCtx should not be done right after initialization") + select { + case report := <-s.reportChan: + assert.Equal(t, scenario.expected.ueId, report.ueId) + assert.Equal(t, "simulation started", report.reason) + default: + t.Error("no response from worker, there might be an issue with channels") + } + }) + } + +} + +func TestSetupUeTaskExecutorHasError(t *testing.T) { + + gnbConf := config.GNodeB{ + ControlIF: config.ControlIF{ + Ip: "127.0.0.1", + Port: 9487, + }, + DataIF: config.DataIF{ + Ip: "127.0.0.2", + Port: 2152, + }, + PlmnList: config.PlmnList{ + Mcc: "999", + Mnc: "70", + Tac: "000001", + GnbId: "000001", + }, + SliceSupportList: config.SliceSupportList{ + Sst: "01", + }, + } + + amfConfs := []*config.AMF{{ + Ip: "192.168.11.30", + Port: 38412, + }} + + wg := &sync.WaitGroup{} + + gnb := gnbInitMock(gnbConf, amfConfs, wg) + tasks := []Task{{ + TaskType: Registration, + Delay: 2000, + }, { + TaskType: NewPDUSession, + Delay: 0, + }, { + TaskType: Idle, + Delay: 0, + }} + ueScenario := UEScenario{ + Config: config.Ue{ + Msin: "0000001000", + }, + Tasks: tasks, + } + + t.Run("invalid UE Id", func(t *testing.T) { + + s := getDefaultManager() + s.reportChan = make(chan report, 1) + s.gnbs = map[string]*context.GNBContext{gnb.GetGnbId(): gnb} + s.taskExecutorWg = &sync.WaitGroup{} + ueid := 0 + assert.Error(t, s.setupUeTaskExecutor(ueid, ueScenario)) + }) + t.Run("Task Executor already exists", func(t *testing.T) { + + s := getDefaultManager() + s.reportChan = make(chan report, 1) + s.gnbs = map[string]*context.GNBContext{gnb.GetGnbId(): gnb} + s.taskExecutorWg = &sync.WaitGroup{} + ueid := 0 + s.setupUeTaskExecutor(ueid, ueScenario) + assert.Error(t, s.setupUeTaskExecutor(ueid, ueScenario)) + }) +} + +func TestStartOrderRateController(t *testing.T) { + orderCount := 20 + timeBetweenRegistration := 500 + orderChan := make(chan order, 5) + stopChan := make(chan int, 1) + taskChan := make(chan Task, 1) + go startOrderRateController(orderChan, timeBetweenRegistration) + + go func() { + for i := 0; i < orderCount; i++ { + orderChan <- order{task: Task{}, workerChan: taskChan} + } + }() + time.Sleep(time.Duration(timeBetweenRegistration/2) * time.Millisecond) + ticker := time.NewTicker(time.Duration(timeBetweenRegistration) * time.Millisecond) + endTicker := time.NewTicker(time.Duration((orderCount+5)*timeBetweenRegistration) * time.Millisecond) + expected := 0 + actual := 0 + stop := false + for !stop { + select { + case <-taskChan: + actual++ + case <-ticker.C: + if expected < orderCount { + expected++ + assert.Equal(t, expected, actual) + } + case <-endTicker.C: + stop = true + stopChan <- 0 + } + } + close(orderChan) + assert.Equal(t, orderCount, actual) +} + +func TestCleanup(t *testing.T) { + + s := getDefaultManager() + s.taskExecutorWg.Add(1) + time.AfterFunc(time.Duration(1)*time.Second, func() { s.taskExecutorWg.Done() }) + s.reportChan = make(chan report, 2) + s.reportChan <- report{ueId: 1, reason: "test"} + s.reportChan <- report{ueId: 2, reason: "test"} + + s.registrationQueue <- order{} + + s.cleanup() + + select { + case task := <-s.ues[1].workerChan: + assert.Equal(t, Terminate, task.TaskType) + default: + t.Error("Terminating task not send to UE") + } + + _, open := <-s.reportChan + assert.False(t, open, "report channel should be closed") + _, open = <-s.registrationQueue + assert.False(t, open, "registrationQueue channel should be closed") +} diff --git a/internal/scenario/task.go b/internal/scenario/task.go new file mode 100644 index 00000000..832470c5 --- /dev/null +++ b/internal/scenario/task.go @@ -0,0 +1,58 @@ +/** + * SPDX-License-Identifier: Apache-2.0 + * © Copyright 2024 Hewlett Packard Enterprise Development LP + */ +package scenario + +type Task struct { + TaskType TaskType + Delay int + Parameters struct { + GnbId string + } +} + +type TaskType int32 + +const ( + AttachToGNB TaskType = iota + Registration + Deregistration + NewPDUSession + Terminate + Kill + Idle + ServiceRequest + NGAPHandover + XNHandover + // DestroyPDUSession +) + +func (t TaskType) ToStr() string { + switch t { + case AttachToGNB: + return "AttachToGNB" + case Registration: + return "Registration" + case Deregistration: + return "Deregistration" + case NewPDUSession: + return "NewPDUSession" + // case DestroyPDUSession: + // return "" + case Terminate: + return "Terminate" + case Kill: + return "Kill" + case Idle: + return "Idle" + case ServiceRequest: + return "ServiceRequest" + case NGAPHandover: + return "NGAPHandover" + case XNHandover: + return "XNHandover" + default: + return "Undefined" + } +} diff --git a/internal/scenario/ueTaskExecuter.go b/internal/scenario/ueTaskExecuter.go new file mode 100644 index 00000000..4427b87a --- /dev/null +++ b/internal/scenario/ueTaskExecuter.go @@ -0,0 +1,279 @@ +/** + * SPDX-License-Identifier: Apache-2.0 + * © Copyright 2024 Hewlett Packard Enterprise Development LP + */ +package scenario + +import ( + "fmt" + "my5G-RANTester/config" + "my5G-RANTester/internal/control_test_engine/gnb/context" + "my5G-RANTester/internal/control_test_engine/gnb/ngap/trigger" + "my5G-RANTester/internal/control_test_engine/procedures" + "my5G-RANTester/internal/control_test_engine/ue" + ueCtx "my5G-RANTester/internal/control_test_engine/ue/context" + "my5G-RANTester/internal/control_test_engine/ue/scenario" + "sync" + "time" + + log "github.com/sirupsen/logrus" +) + +var ( + NewUe = ue.NewUE +) + +type ueTaskExecutor struct { + UeId int + UeCfg config.Ue + TaskChan chan Task + ReportChan chan report + Gnbs map[string]*context.GNBContext + Wg *sync.WaitGroup + + uewg *sync.WaitGroup + running bool + ueRx chan procedures.UeTesterMessage + ueTx chan scenario.ScenarioMessage + attachedGnb string + timedTasks map[Task]*time.Timer + lastReceivedTaskTime time.Time + state int + targetState int + lock sync.Mutex + loop bool +} + +func (e *ueTaskExecutor) Run() { + if e.running { + log.Errorf("simulation for ue %d failed: already running", e.UeId) + return + } + if e.UeCfg == (config.Ue{}) { + log.Errorf("simulation for ue %d failed: no config", e.UeId) + return + } + if e.TaskChan == nil { + log.Errorf("simulation for ue %d failed: no taskChan", e.UeId) + return + } + if e.ReportChan == nil { + log.Errorf("simulation for ue %d failed: no reportChan", e.UeId) + return + } + if e.Gnbs == nil { + log.Errorf("simulation for ue %d failed: no gnbs", e.UeId) + return + } + if e.Wg == nil { + log.Errorf("simulation for ue %d failed: no WaitGroup is given", e.UeId) + return + } + + e.running = true + + e.timedTasks = map[Task]*time.Timer{} + e.state = ueCtx.MM5G_NULL + e.targetState = ueCtx.MM5G_NULL + e.lastReceivedTaskTime = time.Now() + e.Wg.Add(1) + e.uewg = &sync.WaitGroup{} + go e.listen() +} + +func (e *ueTaskExecutor) listen() { + e.loop = true + log.Infof("simulation started for ue %d", e.UeId) + e.sendReport(true, "simulation started") + for e.loop { + select { + case task := <-e.TaskChan: + e.handleTaskTimer(task) + case ueMsg, open := <-e.ueTx: + if open { + e.handleUeMsg(ueMsg) + } + } + } + e.running = false +} + +func (e *ueTaskExecutor) handleUeMsg(ueMsg scenario.ScenarioMessage) { + msg := fmt.Sprintf("Switched from state %d to state %d ", e.state, ueMsg.StateChange) + if ueMsg.StateChange == e.targetState { + e.sendReport(true, msg) + } + e.state = ueMsg.StateChange +} + +func (e *ueTaskExecutor) handleTaskTimer(task Task) { + if task.Delay > 0 { + task.Delay = max(0, task.Delay-int(time.Since(e.lastReceivedTaskTime).Milliseconds())) + e.timedTasks[task] = time.AfterFunc(time.Duration(task.Delay)*time.Millisecond, func() { + e.handleTask(task) + e.lock.Lock() + delete(e.timedTasks, task) + e.lock.Unlock() + }) + } else { + e.handleTask(task) + } + e.lastReceivedTaskTime = time.Now() +} + +func (e *ueTaskExecutor) handleTask(task Task) { + log.Debugf("[UE-%d] Received Task: %s", e.UeId, task.TaskType.ToStr()) + log.Debugf("UE %d is attached to %s, tasked to %s", e.UeId, e.attachedGnb, task.TaskType.ToStr()) + + if e.attachedGnb == "" && task.TaskType != AttachToGNB && task.TaskType != Terminate { + msg := fmt.Sprintf("UE %d is not attached to a GNB", e.UeId) + e.sendReport(false, msg) + } + + e.dispatchTask(task) +} + +func (e *ueTaskExecutor) dispatchTask(task Task) { + switch task.TaskType { + case AttachToGNB: + e.handleAttachToGnb(task) + case Registration: + e.handleRegistration() + case Deregistration: + e.handleDeregistration() + case Idle: + e.handleIdle() + case ServiceRequest: + e.handleServiceRequest() + case NewPDUSession: + e.handleNewPduSession(task) + case XNHandover: + e.handleXnHandover(task) + case NGAPHandover: + e.handleNgapHandover(task) + case Terminate: + e.handleTerminate() + case Kill: + e.handleKill() + default: + e.sendReport(false, "unknown task") + } +} + +func (e *ueTaskExecutor) handleAttachToGnb(task Task) { + if e.Gnbs[task.Parameters.GnbId] != nil { + // Create a new UE coroutine + // ue.NewUE returns context of the new UE + e.lock.Lock() + e.ueRx = make(chan procedures.UeTesterMessage) + log.Debug("add wg") + e.uewg.Add(1) + e.ueTx = ue.NewUE(e.UeCfg, e.UeId, e.ueRx, e.Gnbs[task.Parameters.GnbId].GetInboundChannel(), e.uewg) + e.attachedGnb = task.Parameters.GnbId + e.lock.Unlock() + e.sendReport(true, "UE successfully attached to GNB "+task.Parameters.GnbId) + } else { + msg := fmt.Sprintf("GNB %s not found", task.Parameters.GnbId) + e.sendReport(false, msg) + } +} + +func (e *ueTaskExecutor) handleRegistration() { + e.ueRx <- procedures.UeTesterMessage{Type: procedures.Registration} + e.targetState = ueCtx.MM5G_REGISTERED +} + +func (e *ueTaskExecutor) handleDeregistration() { + e.ueRx <- procedures.UeTesterMessage{Type: procedures.Deregistration} + e.targetState = ueCtx.MM5G_DEREGISTERED +} + +func (e *ueTaskExecutor) handleIdle() { + e.ueRx <- procedures.UeTesterMessage{Type: procedures.Idle} + e.targetState = ueCtx.MM5G_IDLE + // TODO Should sync with gnb before sending Idle success report +} + +func (e *ueTaskExecutor) handleServiceRequest() { + e.ueRx <- procedures.UeTesterMessage{Type: procedures.ServiceRequest} + e.targetState = ueCtx.MM5G_REGISTERED +} + +func (e *ueTaskExecutor) handleNewPduSession(task Task) { + if e.state == ueCtx.MM5G_REGISTERED { + e.ueRx <- procedures.UeTesterMessage{Type: procedures.NewPDUSession} + // TODO: Implement way to check if pduSession is successful + time.Sleep(2 * time.Second) + e.sendReport(true, "sent PDU session request") + } else { + msg := fmt.Sprintf("UE %d is not registered", e.UeId) + e.sendReport(false, msg) + } +} + +func (e *ueTaskExecutor) handleXnHandover(task Task) { + if e.Gnbs[task.Parameters.GnbId] != nil { + trigger.TriggerXnHandover(e.Gnbs[e.attachedGnb], e.Gnbs[task.Parameters.GnbId], int64(e.UeId)) + e.attachedGnb = task.Parameters.GnbId + // Wait for succesful handover + // TODO: We should wait for confimation from gnb instead of timer + time.Sleep(2 * time.Second) + e.sendReport(true, "succesful XNHandover") + e.targetState = ueCtx.MM5G_REGISTERED + } else { + msg := fmt.Sprintf("GNB %s not found", task.Parameters.GnbId) + e.sendReport(false, msg) + } +} + +func (e *ueTaskExecutor) handleNgapHandover(task Task) { + if e.Gnbs[task.Parameters.GnbId] != nil { + trigger.TriggerNgapHandover(e.Gnbs[e.attachedGnb], e.Gnbs[task.Parameters.GnbId], int64(e.UeId)) + e.attachedGnb = task.Parameters.GnbId + // Wait for succesful handover + // TODO: We should wait for confimation from gnb instead of timer + time.Sleep(2 * time.Second) + e.sendReport(true, "succesful NGAPHandover") + e.targetState = ueCtx.MM5G_REGISTERED + } else { + msg := fmt.Sprintf("GNB %s not found", task.Parameters.GnbId) + e.sendReport(false, msg) + } +} + +func (e *ueTaskExecutor) handleTerminate() { + e.ueRx <- procedures.UeTesterMessage{Type: procedures.Terminate} + open := true + for open { + _, open = <-e.ueTx + } + e.reset() + e.uewg.Wait() + e.sendReport(true, "UE Terminated") + close(e.TaskChan) + e.loop = false + e.Wg.Done() +} + +func (e *ueTaskExecutor) handleKill() { + e.ueRx <- procedures.UeTesterMessage{Type: procedures.Kill} + e.reset() + e.sendReport(true, "UE Killed") +} + +func (e *ueTaskExecutor) reset() { + e.lock.Lock() + e.ueRx = nil + e.ueTx = nil + e.state = ueCtx.MM5G_NULL + e.attachedGnb = "" + for t := range e.timedTasks { + e.timedTasks[t].Stop() + delete(e.timedTasks, t) + } + e.lock.Unlock() +} + +func (e *ueTaskExecutor) sendReport(success bool, msg string) { + e.ReportChan <- report{success: success, reason: msg, ueId: e.UeId} +} diff --git a/internal/templates/test-attach-gnb-with-configuration.go b/internal/templates/test-attach-gnb-with-configuration.go index 0e704fc2..556f999f 100644 --- a/internal/templates/test-attach-gnb-with-configuration.go +++ b/internal/templates/test-attach-gnb-with-configuration.go @@ -23,7 +23,7 @@ func TestAttachGnbWithConfiguration() { // cfg.GNodeB.SliceSupportList.St = "10" // cfg.GNodeB.SliceSupportList.Sst = "010239" - go gnb.InitGnb(cfg, &wg) + go gnb.InitGnb(cfg.GNodeB, cfg.AMFs, &wg) wg.Add(1) diff --git a/internal/templates/test-custom-scenario.go b/internal/templates/test-custom-scenario.go index 363325db..38db6f95 100644 --- a/internal/templates/test-custom-scenario.go +++ b/internal/templates/test-custom-scenario.go @@ -25,7 +25,7 @@ func TestWithCustomScenario(scenarioPath string) { wg.Add(1) - gnb := gnb.InitGnb(cfg, &wg) + gnb := gnb.InitGnb(cfg.GNodeB, cfg.AMFs, &wg) time.Sleep(1 * time.Second) @@ -33,7 +33,7 @@ func TestWithCustomScenario(scenarioPath string) { wg.Add(1) - _ = ue.NewUE(cfg, 1, ueChan, gnb.GetInboundChannel(), &wg) + _ = ue.NewUE(cfg.Ue, 1, ueChan, gnb.GetInboundChannel(), &wg) ctx, runtime := script.NewCustomScenario(scenarioPath) @@ -81,7 +81,7 @@ func TestWithCustomScenario(scenarioPath string) { module, err := runtime.InstantiateWithConfig(ctx, addWasm, config) if err != nil { - log.Fatal("failed to instantiate module: %v", err) + log.Fatalf("failed to instantiate module: %v", err) } // Call the `add` function and print the results to the console. diff --git a/internal/templates/test-multi-ues-in-queue.go b/internal/templates/test-multi-ues-in-queue.go index 7e5d99c9..a8ba6055 100644 --- a/internal/templates/test-multi-ues-in-queue.go +++ b/internal/templates/test-multi-ues-in-queue.go @@ -5,111 +5,152 @@ package templates import ( + "fmt" "my5G-RANTester/config" "my5G-RANTester/internal/common/tools" - "my5G-RANTester/internal/control_test_engine/procedures" - "os" - "os/signal" - "sync" - "time" + "my5G-RANTester/internal/scenario" + "sort" + "strconv" log "github.com/sirupsen/logrus" ) -func TestMultiUesInQueue(numUes int, tunnelMode config.TunnelMode, dedicatedGnb bool, loop bool, timeBetweenRegistration int, timeBeforeDeregistration int, timeBeforeNgapHandover int, timeBeforeXnHandover int, timeBeforeIdle int, timeBeforeReconnecting int, numPduSessions int) { - if tunnelMode != config.TunnelDisabled { - if !dedicatedGnb { - log.Fatal("You cannot use the --tunnel option, without using the --dedicatedGnb option") - } - if timeBetweenRegistration < 500 { - log.Fatal("When using the --tunnel option, --timeBetweenRegistration must be equal to at least 500 ms, or else gtp5g kernel module may crash if you create tunnels too rapidly.") - } +func TestMultiUesInQueue(numUes int, tunnelMode config.TunnelMode, dedicatedGnb bool, loop bool, timeBetweenRegistration int, timeBeforeDeregistration int, timeBeforeNgapHandover int, timeBeforeXnHandover int, timeBeforeIdle int, timeBeforeServiceRequest int, numPduSessions int) { + if tunnelMode != config.TunnelDisabled && timeBetweenRegistration < 500 { + log.Fatal("When using the --tunnel option, --timeBetweenRegistration must be equal to at least 500 ms, or else gtp5g kernel module may crash if you create tunnels too rapidly.") } if numPduSessions > 16 { log.Fatal("You can't have more than 16 PDU Sessions per UE as per spec.") } - wg := sync.WaitGroup{} - cfg := config.GetConfig() + cfg.Ue.TunnelMode = tunnelMode - var numGnb int - if dedicatedGnb { - numGnb = numUes - } else { - numGnb = 1 + gnb := cfg.GNodeB + gnbs := []config.GNodeB{gnb} + nextgnb := 0 + if timeBeforeNgapHandover > 0 || timeBeforeXnHandover > 0 { + gnb = nextGnbConf(gnb, 1, cfg.GNodeB.PlmnList.GnbId) + gnbs = append(gnbs, gnb) } - if numGnb <= 1 && (timeBeforeXnHandover != 0 || timeBeforeNgapHandover != 0) { - log.Warn("[TESTER] We are increasing the number of gNodeB to two for handover test cases. Make you sure you fill the requirements for having two gNodeBs.") - numGnb++ - } - gnbs := tools.CreateGnbs(numGnb, cfg, &wg) - - // Wait for gNB to be connected before registering UEs - // TODO: We should wait for NGSetupResponse instead - time.Sleep(1 * time.Second) - - cfg.Ue.TunnelMode = tunnelMode + ueScenarios := []scenario.UEScenario{} + for i := 0; i < numUes; i++ { + if dedicatedGnb && i != 0 { + gnb = nextGnbConf(gnb, len(gnbs), cfg.GNodeB.PlmnList.GnbId) + gnbs = append(gnbs, gnb) + nextgnb = len(gnbs) - 1 + if timeBeforeNgapHandover > 0 || timeBeforeXnHandover > 0 { + gnb = nextGnbConf(gnb, len(gnbs), cfg.GNodeB.PlmnList.GnbId) + gnbs = append(gnbs, gnb) + } + } - scenarioChans := make([]chan procedures.UeTesterMessage, numUes+1) + tasks := []scenario.Task{ + { + TaskType: scenario.AttachToGNB, + Parameters: struct { + GnbId string + }{gnbs[nextgnb].PlmnList.GnbId}, + }, + { + TaskType: scenario.Registration, + }, + } - sigStop := make(chan os.Signal, 1) - signal.Notify(sigStop, os.Interrupt) + for i := 0; i < numPduSessions; i++ { + tasks = append(tasks, scenario.Task{ + TaskType: scenario.NewPDUSession, + }) + } - ueSimCfg := tools.UESimulationConfig{ - Gnbs: gnbs, - Cfg: cfg, - TimeBeforeDeregistration: timeBeforeDeregistration, - TimeBeforeNgapHandover: timeBeforeNgapHandover, - TimeBeforeXnHandover: timeBeforeXnHandover, - TimeBeforeIdle: timeBeforeIdle, - TimeBeforeReconnecting: timeBeforeReconnecting, - NumPduSessions: numPduSessions, - } + if timeBeforeNgapHandover != 0 { + tasks = append(tasks, scenario.Task{ + TaskType: scenario.NGAPHandover, + Delay: timeBeforeNgapHandover, + }) + } - stopSignal := true - for stopSignal { - // If CTRL-C signal has been received, - // stop creating new UEs, else we create numUes UEs - for ueSimCfg.UeId = 1; stopSignal && ueSimCfg.UeId <= numUes; ueSimCfg.UeId++ { - // If there is currently a coroutine handling current UE - // kill it, before creating a new coroutine with same UE - // Use case: Registration of N UEs in loop, when loop = true - if scenarioChans[ueSimCfg.UeId] != nil { - scenarioChans[ueSimCfg.UeId] <- procedures.UeTesterMessage{Type: procedures.Kill} - close(scenarioChans[ueSimCfg.UeId]) - scenarioChans[ueSimCfg.UeId] = nil + if timeBeforeXnHandover != 0 { + tasks = append(tasks, scenario.Task{ + TaskType: scenario.XNHandover, + Delay: timeBeforeXnHandover, + }) + } + if timeBeforeIdle != 0 { + tasks = append(tasks, scenario.Task{ + TaskType: scenario.Idle, + Delay: timeBeforeIdle, + }) + if timeBeforeServiceRequest != 0 { + tasks = append(tasks, scenario.Task{ + TaskType: scenario.ServiceRequest, + Delay: timeBeforeIdle + timeBeforeServiceRequest, + }) } - scenarioChans[ueSimCfg.UeId] = make(chan procedures.UeTesterMessage) - ueSimCfg.ScenarioChan = scenarioChans[ueSimCfg.UeId] + } + + if timeBeforeDeregistration != 0 { + tasks = append(tasks, scenario.Task{ + TaskType: scenario.Deregistration, + Delay: timeBeforeDeregistration, + }) + } - tools.SimulateSingleUE(ueSimCfg, &wg) + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Delay < tasks[j].Delay + }) - // Before creating a new UE, we wait for timeBetweenRegistration ms - time.Sleep(time.Duration(timeBetweenRegistration) * time.Millisecond) + sumDelay := 0 + for j := 0; j < len(tasks); j++ { + tasks[j].Delay = tasks[j].Delay - sumDelay + sumDelay += tasks[j].Delay - select { - case <-sigStop: - stopSignal = false - default: + if tasks[j].TaskType == scenario.NGAPHandover || tasks[j].TaskType == scenario.XNHandover { + tasks[j].Parameters.GnbId = gnbs[(len(gnbs)-2)+(nextgnb+1)%2].PlmnList.GnbId } } - // If loop = false, we don't go over the for loop a second time - // and we only do the numUes registration once - if !loop { - break + + ueCfg := cfg.Ue + ueCfg.Msin = tools.IncrementMsin(i+1, cfg.Ue.Msin) + ueScenario := scenario.UEScenario{ + Config: ueCfg, + Tasks: tasks, } + + ueScenario.Loop = loop + if tasks[len(tasks)-1].TaskType != scenario.Deregistration { + ueScenario.Persist = true + } + + ueScenarios = append(ueScenarios, ueScenario) } - if stopSignal { - <-sigStop + scenario.Start(gnbs, cfg.AMFs, ueScenarios, timeBetweenRegistration, 0) +} + +func nextGnbConf(gnb config.GNodeB, i int, baseId string) config.GNodeB { + var err error + gnb.PlmnList.GnbId = generateGnbId(i, baseId) + gnb.ControlIF.Ip, err = tools.IncrementIP(gnb.ControlIF.Ip, "0.0.0.0/0") + if err != nil { + log.Fatal("[GNB][CONFIG] Error while allocating ip for N2: " + err.Error()) } - for _, scenarioChan := range scenarioChans { - if scenarioChan != nil { - scenarioChan <- procedures.UeTesterMessage{Type: procedures.Terminate} - } + gnb.DataIF.Ip, err = tools.IncrementIP(gnb.DataIF.Ip, "0.0.0.0/0") + if err != nil { + log.Fatal("[GNB][CONFIG] Error while allocating ip for N3: " + err.Error()) + } + return gnb +} + +func generateGnbId(i int, gnbId string) string { + + gnbId_int, err := strconv.ParseInt(gnbId, 16, 0) + if err != nil { + log.Fatal("[UE][CONFIG] Given gnbId is invalid") } + base := int(gnbId_int) + i - time.Sleep(time.Second * 1) + gnbId = fmt.Sprintf("%06X", base) + return gnbId } diff --git a/test/aio5gc/context/sessionManagement.go b/test/aio5gc/context/sessionManagement.go index 96e4ea0a..33c327b6 100644 --- a/test/aio5gc/context/sessionManagement.go +++ b/test/aio5gc/context/sessionManagement.go @@ -228,6 +228,6 @@ func ForceReleaseAllPDUSession(ue *UEContext) { if err != nil { log.Error("[5GC] Failed to release pdu session " + fmt.Sprint(smCtx.GetPduSessionId()) + " for ue " + ue.securityContext.msin + ": " + err.Error()) } - ue.DeleteSmContext(smCtx.GetPduSessionId()) + delete(ue.smContexts, smCtx.GetPduSessionId()) }) } diff --git a/test/aio5gc/lib/tools/tools.go b/test/aio5gc/lib/tools/tools.go deleted file mode 100644 index 88654320..00000000 --- a/test/aio5gc/lib/tools/tools.go +++ /dev/null @@ -1,59 +0,0 @@ -/** - * SPDX-License-Identifier: Apache-2.0 - * © Copyright 2023 Hewlett Packard Enterprise Development LP - */ -package tools - -import ( - "my5G-RANTester/config" -) - -func GenerateDefaultConf(controlIF config.ControlIF, dataIF config.DataIF, amfs []*config.AMF) config.Config { - return config.Config{ - GNodeB: config.GNodeB{ - ControlIF: controlIF, - DataIF: dataIF, - PlmnList: config.PlmnList{ - Mcc: "999", - Mnc: "70", - Tac: "000001", - GnbId: "000008", - }, - SliceSupportList: config.SliceSupportList{ - Sst: "01", - Sd: "000001", - }, - }, - Ue: config.Ue{ - Msin: "0000000120", - Key: "00112233445566778899AABBCCDDEEFF", - Opc: "00112233445566778899AABBCCDDEEFF", - Amf: "8000", - Sqn: "00000000", - Dnn: "internet", - RoutingIndicator: "4567", - Hplmn: config.Hplmn{ - Mcc: "999", - Mnc: "70", - }, - Snssai: config.Snssai{ - Sst: 01, - Sd: "000001", - }, - Integrity: config.Integrity{ - Nia0: false, - Nia1: true, - Nia2: true, - }, - Ciphering: config.Ciphering{ - Nea0: false, - Nea1: true, - Nea2: true, - }, - }, - AMFs: amfs, - Logs: config.Logs{ - Level: 4, - }, - } -} diff --git a/test/aio5gc/msg/nas/handler/ueOriginatingDeregistration.go b/test/aio5gc/msg/nas/handler/ueOriginatingDeregistration.go index 4126d04a..4485ef7d 100644 --- a/test/aio5gc/msg/nas/handler/ueOriginatingDeregistration.go +++ b/test/aio5gc/msg/nas/handler/ueOriginatingDeregistration.go @@ -38,7 +38,6 @@ func DefaultUEOriginatingDeregistration(nasReq *nas.Message, amf *context.AMFCon context.ForceReleaseAllPDUSession(ue) err := ue.GetUeFsm().SendEvent(ue.GetState(), context.DeregistrationRequest, fsm.ArgsType{"ue": ue}, log.NewEntry(log.StandardLogger())) - if err != nil { return err } diff --git a/test/pr_test.go b/test/pr_test.go index fad2c933..1caad10f 100644 --- a/test/pr_test.go +++ b/test/pr_test.go @@ -8,10 +8,9 @@ package test import ( "my5G-RANTester/config" "my5G-RANTester/internal/common/tools" - "my5G-RANTester/internal/control_test_engine/procedures" + "my5G-RANTester/internal/scenario" "my5G-RANTester/test/aio5gc" "my5G-RANTester/test/aio5gc/context" - amfTools "my5G-RANTester/test/aio5gc/lib/tools" "os" "sync" "testing" @@ -23,24 +22,86 @@ import ( "github.com/stretchr/testify/assert" ) -func TestRegistrationToCtxReleaseWithPDUSession(t *testing.T) { +var ( + testCount = 0 + mutex sync.Mutex +) - controlIFConfig := config.ControlIF{ - Ip: "127.0.0.1", - Port: 9489, +func TestSingleUe(t *testing.T) { + conf := generateDefaultConf() + type UECheck struct { + HasAuthOnce bool } - dataIFConfig := config.DataIF{ - Ip: "127.0.0.1", - Port: 2154, + ueChecks := map[string]*UECheck{} + + // Setup 5GC + builder := aio5gc.FiveGCBuilder{} + fiveGC, err := builder. + WithConfig(conf). + WithUeCallback(context.Authenticated, func(state *fsm.State, event fsm.EventType, args fsm.ArgsType) { + ue := args["ue"].(*context.UEContext) + check, ok := ueChecks[ue.GetSecurityContext().GetMsin()] + if !ok { + check = &UECheck{} + ueChecks[ue.GetSecurityContext().GetMsin()] = check + } + check.HasAuthOnce = true + }). + Build() + if err != nil { + log.Printf("[5GC] Error during 5GC creation %v", err) + os.Exit(1) } - amfListConfig := []*config.AMF{ - { - Ip: "127.0.0.1", - Port: 38414, + time.Sleep(1 * time.Second) + + securityContext := context.SecurityContext{} + securityContext.SetMsin(conf.Ue.Msin) + securityContext.SetAuthSubscription(conf.Ue.Key, conf.Ue.Opc, "c9e8763286b5b9ffbdf56e1297d0887b", conf.Ue.Amf, conf.Ue.Sqn) + securityContext.SetAbba([]uint8{0x00, 0x00}) + + amfContext := fiveGC.GetAMFContext() + amfContext.Provision(models.Snssai{Sst: int32(conf.Ue.Snssai.Sst), Sd: conf.Ue.Snssai.Sd}, securityContext) + + gnbs := []config.GNodeB{conf.GNodeB} + ueScenario := scenario.UEScenario{ + Config: conf.Ue, + Loop: false, + Persist: false, + Tasks: []scenario.Task{ + { + TaskType: scenario.AttachToGNB, + Parameters: struct { + GnbId string + }{conf.GNodeB.PlmnList.GnbId}, + }, + { + TaskType: scenario.Registration, + }, + { + TaskType: scenario.NewPDUSession, + }, + { + TaskType: scenario.Deregistration, + Delay: 2000, + }, }, } + ueScenarios := []scenario.UEScenario{ueScenario} + + scenario.Start(gnbs, conf.AMFs, ueScenarios, 50, 30000) - conf := amfTools.GenerateDefaultConf(controlIFConfig, dataIFConfig, amfListConfig) + time.Sleep(2 * time.Second) + + fiveGC.GetAMFContext().ExecuteForAllUe( + func(ue *context.UEContext) { + assert.Equalf(t, context.Deregistered, ue.GetState().Current(), "Expected all ue to be in Deregistered state but was not") + assert.True(t, ueChecks[ue.GetSecurityContext().GetMsin()].HasAuthOnce, "UE has never changed state") + }) +} + +func TestRegistrationToCtxReleaseWithPDUSession(t *testing.T) { + + conf := generateDefaultConf() type UECheck struct { HasAuthOnce bool @@ -78,50 +139,52 @@ func TestRegistrationToCtxReleaseWithPDUSession(t *testing.T) { } time.Sleep(1 * time.Second) - // Setup gNodeB - gnbCount := 1 - wg := sync.WaitGroup{} - gnbs := tools.CreateGnbs(gnbCount, conf, &wg) - - time.Sleep(1 * time.Second) - - keys := make([]string, 0) - for k := range gnbs { - keys = append(keys, k) - } - // Setup UE ueCount := 10 - scenarioChans := make([]chan procedures.UeTesterMessage, ueCount+1) - ueSimCfg := tools.UESimulationConfig{ - Gnbs: gnbs, - Cfg: conf, - TimeBeforeDeregistration: 400, - TimeBeforeNgapHandover: 0, - TimeBeforeXnHandover: 0, - NumPduSessions: 1, - } - - for ueSimCfg.UeId = 1; ueSimCfg.UeId <= ueCount; ueSimCfg.UeId++ { - ueSimCfg.ScenarioChan = scenarioChans[ueSimCfg.UeId] - - imsi := tools.IncrementMsin(ueSimCfg.UeId, ueSimCfg.Cfg.Ue.Msin) + amfContext := fiveGC.GetAMFContext() + ueScenarios := []scenario.UEScenario{} + for i := 1; i <= ueCount; i++ { + tmpConf := conf + tmpConf.Ue.Msin = tools.IncrementMsin(i, conf.Ue.Msin) + ueScenario := scenario.UEScenario{ + Config: tmpConf.Ue, + Loop: false, + Persist: false, + Tasks: []scenario.Task{ + { + TaskType: scenario.AttachToGNB, + Parameters: struct { + GnbId string + }{tmpConf.GNodeB.PlmnList.GnbId}, + }, + { + TaskType: scenario.Registration, + }, + { + TaskType: scenario.NewPDUSession, + }, + { + TaskType: scenario.Deregistration, + Delay: 2000, + }, + }, + } + ueScenarios = append(ueScenarios, ueScenario) securityContext := context.SecurityContext{} - securityContext.SetMsin(imsi) - securityContext.SetAuthSubscription(ueSimCfg.Cfg.Ue.Key, ueSimCfg.Cfg.Ue.Opc, "c9e8763286b5b9ffbdf56e1297d0887b", conf.Ue.Amf, conf.Ue.Sqn) + securityContext.SetMsin(tmpConf.Ue.Msin) + securityContext.SetAuthSubscription(tmpConf.Ue.Key, tmpConf.Ue.Opc, "c9e8763286b5b9ffbdf56e1297d0887b", conf.Ue.Amf, conf.Ue.Sqn) securityContext.SetAbba([]uint8{0x00, 0x00}) - amfContext := fiveGC.GetAMFContext() - amfContext.Provision(models.Snssai{Sst: int32(ueSimCfg.Cfg.Ue.Snssai.Sst), Sd: ueSimCfg.Cfg.Ue.Snssai.Sd}, securityContext) - - tools.SimulateSingleUE(ueSimCfg, &wg) + amfContext.Provision(models.Snssai{Sst: int32(tmpConf.Ue.Snssai.Sst), Sd: tmpConf.Ue.Snssai.Sd}, securityContext) - // Before creating a new UE, we wait for 5 ms - time.Sleep(time.Duration(5) * time.Millisecond) } - time.Sleep(time.Duration(5000) * time.Millisecond) + gnbs := []config.GNodeB{conf.GNodeB} + scenario.Start(gnbs, conf.AMFs, ueScenarios, 500, 40000) + + time.Sleep(2 * time.Second) + i := 0 fiveGC.GetAMFContext().ExecuteForAllUe( func(ue *context.UEContext) { @@ -140,27 +203,13 @@ func TestRegistrationToCtxReleaseWithPDUSession(t *testing.T) { } func TestUERegistrationLoop(t *testing.T) { - controlIFConfig := config.ControlIF{ - Ip: "127.0.0.1", - Port: 9490, - } - dataIFConfig := config.DataIF{ - Ip: "127.0.0.1", - Port: 2155, - } - amfListConfig := []*config.AMF{ - { - Ip: "127.0.0.1", - Port: 38415, - }, - } type UECheck struct { HasAuthOnce bool } ueChecks := map[string]*UECheck{} - conf := amfTools.GenerateDefaultConf(controlIFConfig, dataIFConfig, amfListConfig) + conf := generateDefaultConf() // Setup 5GC builder := aio5gc.FiveGCBuilder{} @@ -182,58 +231,120 @@ func TestUERegistrationLoop(t *testing.T) { } time.Sleep(1 * time.Second) - // Setup gNodeB - gnbCount := 1 - wg := sync.WaitGroup{} - gnbs := tools.CreateGnbs(gnbCount, conf, &wg) - - time.Sleep(1 * time.Second) - - keys := make([]string, 0) - for k := range gnbs { - keys = append(keys, k) - } - // Setup UE - loopCount := 5 - scenarioChans := make([]chan procedures.UeTesterMessage, 2) - ueSimCfg := tools.UESimulationConfig{ - UeId: 1, - Gnbs: gnbs, - Cfg: conf, - TimeBeforeDeregistration: 3000, - TimeBeforeNgapHandover: 0, - TimeBeforeXnHandover: 0, - NumPduSessions: 1, + securityContext := context.SecurityContext{} + securityContext.SetMsin(conf.Ue.Msin) + securityContext.SetAuthSubscription(conf.Ue.Key, conf.Ue.Opc, "c9e8763286b5b9ffbdf56e1297d0887b", conf.Ue.Amf, conf.Ue.Sqn) + securityContext.SetAbba([]uint8{0x00, 0x00}) + + amfContext := fiveGC.GetAMFContext() + amfContext.Provision(models.Snssai{Sst: int32(conf.Ue.Snssai.Sst), Sd: conf.Ue.Snssai.Sd}, securityContext) + + gnbs := []config.GNodeB{conf.GNodeB} + ueScenario := scenario.UEScenario{ + Config: conf.Ue, + Loop: true, + Persist: false, + Tasks: []scenario.Task{ + { + TaskType: scenario.AttachToGNB, + Parameters: struct { + GnbId string + }{conf.GNodeB.PlmnList.GnbId}, + }, + { + TaskType: scenario.Registration, + }, + { + TaskType: scenario.NewPDUSession, + }, + { + TaskType: scenario.Deregistration, + Delay: 2000, + }, + }, } + ueScenarios := []scenario.UEScenario{ueScenario} - for i := 0; i < loopCount; i++ { - if scenarioChans[ueSimCfg.UeId] != nil { - scenarioChans[ueSimCfg.UeId] <- procedures.UeTesterMessage{Type: procedures.Kill} - close(scenarioChans[ueSimCfg.UeId]) - scenarioChans[ueSimCfg.UeId] = nil - } - scenarioChans[ueSimCfg.UeId] = make(chan procedures.UeTesterMessage) - ueSimCfg.ScenarioChan = scenarioChans[ueSimCfg.UeId] - - securityContext := context.SecurityContext{} - securityContext.SetMsin(tools.IncrementMsin(ueSimCfg.UeId, ueSimCfg.Cfg.Ue.Msin)) - securityContext.SetAuthSubscription(ueSimCfg.Cfg.Ue.Key, ueSimCfg.Cfg.Ue.Opc, "c9e8763286b5b9ffbdf56e1297d0887b", conf.Ue.Amf, conf.Ue.Sqn) - securityContext.SetAbba([]uint8{0x00, 0x00}) - - amfContext := fiveGC.GetAMFContext() - amfContext.Provision(models.Snssai{Sst: int32(ueSimCfg.Cfg.Ue.Snssai.Sst), Sd: ueSimCfg.Cfg.Ue.Snssai.Sd}, securityContext) - - tools.SimulateSingleUE(ueSimCfg, &wg) + scenario.Start(gnbs, conf.AMFs, ueScenarios, 50, 15000) - // Before creating a new UE, we wait for 2000 ms - time.Sleep(time.Duration(2000) * time.Millisecond) - } - - time.Sleep(time.Duration(5000) * time.Millisecond) + time.Sleep(2 * time.Second) + i := 0 fiveGC.GetAMFContext().ExecuteForAllUe( func(ue *context.UEContext) { + i++ assert.Equalf(t, context.Deregistered, ue.GetState().Current(), "Expected all ue to be in Deregistered state but was not") assert.True(t, ueChecks[ue.GetSecurityContext().GetMsin()].HasAuthOnce, "UE has never changed state") + }) + assert.GreaterOrEqual(t, i, 3) +} + +func generateDefaultConf() config.Config { + + conf := config.Config{ + GNodeB: config.GNodeB{ + ControlIF: config.ControlIF{ + Ip: "127.0.0.1", + Port: 9489, + }, + DataIF: config.DataIF{ + Ip: "127.0.0.1", + Port: 2154, + }, + PlmnList: config.PlmnList{ + Mcc: "999", + Mnc: "70", + Tac: "000001", + GnbId: "000008", + }, + SliceSupportList: config.SliceSupportList{ + Sst: "01", + Sd: "000001", + }, + }, + Ue: config.Ue{ + Msin: "0000000120", + Key: "00112233445566778899AABBCCDDEEFF", + Opc: "00112233445566778899AABBCCDDEEFF", + Amf: "8000", + Sqn: "00000000", + Dnn: "internet", + RoutingIndicator: "4567", + Hplmn: config.Hplmn{ + Mcc: "999", + Mnc: "70", + }, + Snssai: config.Snssai{ + Sst: 01, + Sd: "000001", + }, + Integrity: config.Integrity{ + Nia0: false, + Nia1: true, + Nia2: true, + }, + Ciphering: config.Ciphering{ + Nea0: false, + Nea1: true, + Nea2: true, + }, + }, + AMFs: []*config.AMF{ + { + Ip: "127.0.0.1", + Port: 38414, + }}, + Logs: config.Logs{ + Level: 4, + }, + } + + mutex.Lock() + conf.AMFs[0].Port = conf.AMFs[0].Port + testCount + conf.GNodeB.ControlIF.Port = conf.GNodeB.ControlIF.Port + testCount + conf.GNodeB.DataIF.Port = conf.GNodeB.DataIF.Port + testCount + testCount++ + mutex.Unlock() + return conf }