Loading go-apps/meep-sandbox-ctrl/server/host_dataplane.go 0 → 100644 +128 −0 Changes for go-apps/meep-sandbox-ctrl/server/host_dataplane.go: 128 added lines, 0 removed lines. Original line number Diff line number Diff line /* * Copyright (c) 2026 The AdvantEDGE Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. */ package server import ( "bytes" "encoding/json" "fmt" "io" "net/http" "os" "strings" "time" log "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-logger" nwcat "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-nw-simu-catalog" ) func hostAgentURL() string { return strings.TrimRight(strings.TrimSpace(os.Getenv("MEEP_SIMU5G_HOST_AGENT_URL")), "/") } func (s *SandboxCtrl) startHostDataplane(inst *nwcat.Instance) { if !s.usesNem() { return } go s.notifyHostAgent(true, inst) go s.applyGisAutomation() } func (s *SandboxCtrl) stopHostDataplane() { if !s.usesNem() { return } s.notifyHostAgent(false, nil) } func (s *SandboxCtrl) notifyHostAgent(start bool, inst *nwcat.Instance) { base := hostAgentURL() if base == "" { log.Info("MEEP_SIMU5G_HOST_AGENT_URL unset; host TUN/adapters rely on simu5g_runner.py serve --watch") return } path := "/v1/stop" var body []byte if start { path = "/v1/start" if inst == nil { log.Warn("host agent start skipped: empty NEM instance") return } var err error body, err = json.Marshal(inst) if err != nil { log.Error("host agent marshal: ", err.Error()) return } } else { body, _ = json.Marshal(map[string]string{"sandbox_id": s.sandboxName}) } ctxWait := 30 * time.Second client := &http.Client{Timeout: ctxWait} req, err := http.NewRequest(http.MethodPost, base+path, bytes.NewReader(body)) if err != nil { log.Error("host agent request: ", err.Error()) return } req.Header.Set("Content-Type", "application/json") resp, err := client.Do(req) if err != nil { log.Error("host agent ", path, ": ", err.Error(), " — run: python3 tools/simu5g/simu5g_runner.py serve --watch") return } defer func() { _ = resp.Body.Close() }() raw, _ := io.ReadAll(resp.Body) if resp.StatusCode >= 300 { log.Error("host agent ", path, " HTTP ", resp.StatusCode, ": ", string(raw)) return } log.Info("host agent ", path, " ok") } func (s *SandboxCtrl) applyGisAutomation() { base := fmt.Sprintf("http://meep-gis-engine.%s.svc.cluster.local/gis/v1", s.sandboxName) calls := []struct { typ string run bool }{ {"MOVEMENT", false}, {"MOBILITY", true}, {"POAS-IN-RANGE", true}, {"NETWORK-CHARACTERISTICS-UPDATE", false}, } client := &http.Client{Timeout: 3 * time.Second} deadline := time.Now().Add(90 * time.Second) for time.Now().Before(deadline) { ok := true for _, c := range calls { url := fmt.Sprintf("%s/automation/%s?run=%t", base, c.typ, c.run) req, err := http.NewRequest(http.MethodPost, url, nil) if err != nil { ok = false break } resp, err := client.Do(req) if err != nil { ok = false break } _ = resp.Body.Close() if resp.StatusCode >= 300 { ok = false break } } if ok { log.Info("GIS automation: MOVEMENT off, MOBILITY on, POAS-IN-RANGE on, NCU off") return } time.Sleep(3 * time.Second) } log.Warn("GIS automation not applied (engine not ready). Frontend updateAutomation may still set it.") } go-apps/meep-sandbox-ctrl/server/nem.go +9 −0 Changes for go-apps/meep-sandbox-ctrl/server/nem.go: 9 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import ( "errors" "fmt" "net/http" "time" log "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-logger" nwcat "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-nw-simu-catalog" Loading Loading @@ -70,6 +71,11 @@ func (s *SandboxCtrl) createNemInstance(scenarioName string) error { return err } if err := nwcat.WaitTCP(nwcat.BridgeDialAddr(inst), 5*time.Second); err != nil { s.deleteNemInstance() return err } s.mutex.Lock() s.nemInstance = inst s.mutex.Unlock() Loading @@ -83,6 +89,7 @@ func (s *SandboxCtrl) createNemInstance(scenarioName string) error { " traci_port=", inst.Endpoints.TraciPort, " rni_udp_port=", inst.Endpoints.RniUDPPort, " ue_telemetry_port=", inst.Endpoints.UeTelemetryPort) s.startHostDataplane(inst) return nil } Loading @@ -90,6 +97,8 @@ func (s *SandboxCtrl) deleteNemInstance() { if !s.usesNem() { return } s.stopHostDataplane() ctx, cancel := context.WithTimeout(context.Background(), s.nwCatalog.Cfg().ActivateTimeout()) defer cancel() Loading go-apps/meep-virt-engine/server/chart-template.go +2 −0 Changes for go-apps/meep-virt-engine/server/chart-template.go: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -137,6 +137,7 @@ type SandboxTemplate struct { NemURL string NemTimeoutMs string NemActivateTimeoutMs string HostAgentURL string } // Deploy - Generate charts & deploy single process or entire scenario Loading Loading @@ -757,6 +758,7 @@ func applyNwSimulatorEnv(t *SandboxTemplate) { if t.NemActivateTimeoutMs == "" { t.NemActivateTimeoutMs = "180000" } t.HostAgentURL = strings.TrimRight(strings.TrimSpace(os.Getenv("MEEP_SIMU5G_HOST_AGENT_URL")), "/") } func generateSandboxCharts(sandboxName string) (charts []helm.Chart, err error) { Loading go-apps/meepctl/cmd/deploy.go +4 −0 Changes for go-apps/meepctl/cmd/deploy.go: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -1232,6 +1232,10 @@ func setNwSimulatorHelmFlags(flags [][]string, cobraCmd *cobra.Command) [][]stri flags = utils.HelmFlags(flags, "--set-string", "image.env.MEEP_NEM_URL="+nemUrl) flags = utils.HelmFlags(flags, "--set", "image.env.MEEP_NEM_TIMEOUT_MS="+strconv.Itoa(timeoutMs)) flags = utils.HelmFlags(flags, "--set", "image.env.MEEP_NEM_ACTIVATE_TIMEOUT_MS="+strconv.Itoa(activateTimeoutMs)) hostAgent := strings.TrimRight(utils.RepoCfg.GetString("repo.nw_simulator.host-agent-url"), "/") if hostAgent != "" { flags = utils.HelmFlags(flags, "--set-string", "image.env.MEEP_SIMU5G_HOST_AGENT_URL="+hostAgent) } return flags } Loading go-packages/meep-net-char-mgr/algo-segment.go +71 −0 Changes for go-packages/meep-net-char-mgr/algo-segment.go: 71 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -17,8 +17,10 @@ package netchar import ( "encoding/json" "errors" "fmt" "os" "sort" "strconv" "strings" Loading Loading @@ -128,6 +130,7 @@ type SegmentAlgorithm struct { D2DDeviceList map[string]string D2DMaxDistance float32 D2DViaNetworkDisabled bool SkipCellularTc bool // Simu5G/OAI: do not TC-shape UE↔POA radio hops Config SegAlgoConfig rc *redis.Connector } Loading Loading @@ -197,6 +200,7 @@ func (algo *SegmentAlgorithm) ProcessScenario(model *mod.Model, pduSessions map[ algo.D2DViaNetworkDisabled = deployment.D2d.DisableD2dViaNetwork } } algo.SkipCellularTc = skipCellularTc(deployment) // Get D2D capable device list algo.D2DDeviceList = map[string]string{} Loading Loading @@ -845,6 +849,14 @@ func (algo *SegmentAlgorithm) createSegment(elemName string, direction string, f segment.ConfiguredNetChar.Jitter = float64(nc.LatencyVariation) segment.ConfiguredNetChar.PacketLoss = float64(nc.PacketLoss) segment.ConfiguredNetChar.Throughput = float64(ncThroughput) if algo.SkipCellularTc && isCellularAccessElem(elemName, model) { // Radio hop is owned by Simu5G/OAI; keep backhaul (zone/domain/edge) TC. segment.ConfiguredNetChar.Latency = 0 segment.ConfiguredNetChar.Jitter = 0 segment.ConfiguredNetChar.PacketLoss = 0 segment.ConfiguredNetChar.Throughput = MAX_THROUGHPUT ncThroughput = MAX_THROUGHPUT } maxThroughput := ncThroughput // Initialize segment-specific BW attributes from Algo config Loading Loading @@ -1154,6 +1166,65 @@ func getNetChars(elemName string, model *mod.Model) *dataModel.NetworkCharacteri return nc } func isExternalNwSimulatorType(t string) bool { switch strings.ToLower(strings.TrimSpace(t)) { case "simu5g", "oai": return true default: return false } } func userMetaExternalNwSimulator(userMeta map[string]string) bool { if userMeta == nil { return false } if raw, ok := userMeta["network-info"]; ok && raw != "" { var info struct { Type string `json:"type"` } if json.Unmarshal([]byte(raw), &info) == nil && isExternalNwSimulatorType(info.Type) { return true } } if raw, ok := userMeta["mec-sandbox"]; ok && raw != "" { var ms struct { ConnectivityModel string `json:"connectivityModel"` } if json.Unmarshal([]byte(raw), &ms) == nil && isExternalNwSimulatorType(ms.ConnectivityModel) { return true } } return false } func skipCellularTc(deployment *dataModel.Deployment) bool { if isExternalNwSimulatorType(os.Getenv("MEEP_NW_SIMULATOR_TYPE")) { return true } if deployment == nil { return false } return userMetaExternalNwSimulator(deployment.UserMeta) } func isCellularAccessElem(elemName string, model *mod.Model) bool { if strings.HasPrefix(elemName, ElemTypeD2D) { return true } if model == nil { return false } node := model.GetNode(elemName) if pl, ok := node.(*dataModel.PhysicalLocation); ok { return mod.IsUe(pl.Type_) } if nl, ok := node.(*dataModel.NetworkLocation); ok { return mod.IsNetLoc(nl.Type_) && !mod.IsDefaultNetLoc(nl.Type_) } return false } // printFlowNamesFromList - func printFlowNamesFromList(list []*SegAlgoFlow) string { str := "" Loading Loading
go-apps/meep-sandbox-ctrl/server/host_dataplane.go 0 → 100644 +128 −0 Changes for go-apps/meep-sandbox-ctrl/server/host_dataplane.go: 128 added lines, 0 removed lines. Original line number Diff line number Diff line /* * Copyright (c) 2026 The AdvantEDGE Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. */ package server import ( "bytes" "encoding/json" "fmt" "io" "net/http" "os" "strings" "time" log "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-logger" nwcat "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-nw-simu-catalog" ) func hostAgentURL() string { return strings.TrimRight(strings.TrimSpace(os.Getenv("MEEP_SIMU5G_HOST_AGENT_URL")), "/") } func (s *SandboxCtrl) startHostDataplane(inst *nwcat.Instance) { if !s.usesNem() { return } go s.notifyHostAgent(true, inst) go s.applyGisAutomation() } func (s *SandboxCtrl) stopHostDataplane() { if !s.usesNem() { return } s.notifyHostAgent(false, nil) } func (s *SandboxCtrl) notifyHostAgent(start bool, inst *nwcat.Instance) { base := hostAgentURL() if base == "" { log.Info("MEEP_SIMU5G_HOST_AGENT_URL unset; host TUN/adapters rely on simu5g_runner.py serve --watch") return } path := "/v1/stop" var body []byte if start { path = "/v1/start" if inst == nil { log.Warn("host agent start skipped: empty NEM instance") return } var err error body, err = json.Marshal(inst) if err != nil { log.Error("host agent marshal: ", err.Error()) return } } else { body, _ = json.Marshal(map[string]string{"sandbox_id": s.sandboxName}) } ctxWait := 30 * time.Second client := &http.Client{Timeout: ctxWait} req, err := http.NewRequest(http.MethodPost, base+path, bytes.NewReader(body)) if err != nil { log.Error("host agent request: ", err.Error()) return } req.Header.Set("Content-Type", "application/json") resp, err := client.Do(req) if err != nil { log.Error("host agent ", path, ": ", err.Error(), " — run: python3 tools/simu5g/simu5g_runner.py serve --watch") return } defer func() { _ = resp.Body.Close() }() raw, _ := io.ReadAll(resp.Body) if resp.StatusCode >= 300 { log.Error("host agent ", path, " HTTP ", resp.StatusCode, ": ", string(raw)) return } log.Info("host agent ", path, " ok") } func (s *SandboxCtrl) applyGisAutomation() { base := fmt.Sprintf("http://meep-gis-engine.%s.svc.cluster.local/gis/v1", s.sandboxName) calls := []struct { typ string run bool }{ {"MOVEMENT", false}, {"MOBILITY", true}, {"POAS-IN-RANGE", true}, {"NETWORK-CHARACTERISTICS-UPDATE", false}, } client := &http.Client{Timeout: 3 * time.Second} deadline := time.Now().Add(90 * time.Second) for time.Now().Before(deadline) { ok := true for _, c := range calls { url := fmt.Sprintf("%s/automation/%s?run=%t", base, c.typ, c.run) req, err := http.NewRequest(http.MethodPost, url, nil) if err != nil { ok = false break } resp, err := client.Do(req) if err != nil { ok = false break } _ = resp.Body.Close() if resp.StatusCode >= 300 { ok = false break } } if ok { log.Info("GIS automation: MOVEMENT off, MOBILITY on, POAS-IN-RANGE on, NCU off") return } time.Sleep(3 * time.Second) } log.Warn("GIS automation not applied (engine not ready). Frontend updateAutomation may still set it.") }
go-apps/meep-sandbox-ctrl/server/nem.go +9 −0 Changes for go-apps/meep-sandbox-ctrl/server/nem.go: 9 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import ( "errors" "fmt" "net/http" "time" log "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-logger" nwcat "github.com/InterDigitalInc/AdvantEDGE/go-packages/meep-nw-simu-catalog" Loading Loading @@ -70,6 +71,11 @@ func (s *SandboxCtrl) createNemInstance(scenarioName string) error { return err } if err := nwcat.WaitTCP(nwcat.BridgeDialAddr(inst), 5*time.Second); err != nil { s.deleteNemInstance() return err } s.mutex.Lock() s.nemInstance = inst s.mutex.Unlock() Loading @@ -83,6 +89,7 @@ func (s *SandboxCtrl) createNemInstance(scenarioName string) error { " traci_port=", inst.Endpoints.TraciPort, " rni_udp_port=", inst.Endpoints.RniUDPPort, " ue_telemetry_port=", inst.Endpoints.UeTelemetryPort) s.startHostDataplane(inst) return nil } Loading @@ -90,6 +97,8 @@ func (s *SandboxCtrl) deleteNemInstance() { if !s.usesNem() { return } s.stopHostDataplane() ctx, cancel := context.WithTimeout(context.Background(), s.nwCatalog.Cfg().ActivateTimeout()) defer cancel() Loading
go-apps/meep-virt-engine/server/chart-template.go +2 −0 Changes for go-apps/meep-virt-engine/server/chart-template.go: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -137,6 +137,7 @@ type SandboxTemplate struct { NemURL string NemTimeoutMs string NemActivateTimeoutMs string HostAgentURL string } // Deploy - Generate charts & deploy single process or entire scenario Loading Loading @@ -757,6 +758,7 @@ func applyNwSimulatorEnv(t *SandboxTemplate) { if t.NemActivateTimeoutMs == "" { t.NemActivateTimeoutMs = "180000" } t.HostAgentURL = strings.TrimRight(strings.TrimSpace(os.Getenv("MEEP_SIMU5G_HOST_AGENT_URL")), "/") } func generateSandboxCharts(sandboxName string) (charts []helm.Chart, err error) { Loading
go-apps/meepctl/cmd/deploy.go +4 −0 Changes for go-apps/meepctl/cmd/deploy.go: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -1232,6 +1232,10 @@ func setNwSimulatorHelmFlags(flags [][]string, cobraCmd *cobra.Command) [][]stri flags = utils.HelmFlags(flags, "--set-string", "image.env.MEEP_NEM_URL="+nemUrl) flags = utils.HelmFlags(flags, "--set", "image.env.MEEP_NEM_TIMEOUT_MS="+strconv.Itoa(timeoutMs)) flags = utils.HelmFlags(flags, "--set", "image.env.MEEP_NEM_ACTIVATE_TIMEOUT_MS="+strconv.Itoa(activateTimeoutMs)) hostAgent := strings.TrimRight(utils.RepoCfg.GetString("repo.nw_simulator.host-agent-url"), "/") if hostAgent != "" { flags = utils.HelmFlags(flags, "--set-string", "image.env.MEEP_SIMU5G_HOST_AGENT_URL="+hostAgent) } return flags } Loading
go-packages/meep-net-char-mgr/algo-segment.go +71 −0 Changes for go-packages/meep-net-char-mgr/algo-segment.go: 71 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -17,8 +17,10 @@ package netchar import ( "encoding/json" "errors" "fmt" "os" "sort" "strconv" "strings" Loading Loading @@ -128,6 +130,7 @@ type SegmentAlgorithm struct { D2DDeviceList map[string]string D2DMaxDistance float32 D2DViaNetworkDisabled bool SkipCellularTc bool // Simu5G/OAI: do not TC-shape UE↔POA radio hops Config SegAlgoConfig rc *redis.Connector } Loading Loading @@ -197,6 +200,7 @@ func (algo *SegmentAlgorithm) ProcessScenario(model *mod.Model, pduSessions map[ algo.D2DViaNetworkDisabled = deployment.D2d.DisableD2dViaNetwork } } algo.SkipCellularTc = skipCellularTc(deployment) // Get D2D capable device list algo.D2DDeviceList = map[string]string{} Loading Loading @@ -845,6 +849,14 @@ func (algo *SegmentAlgorithm) createSegment(elemName string, direction string, f segment.ConfiguredNetChar.Jitter = float64(nc.LatencyVariation) segment.ConfiguredNetChar.PacketLoss = float64(nc.PacketLoss) segment.ConfiguredNetChar.Throughput = float64(ncThroughput) if algo.SkipCellularTc && isCellularAccessElem(elemName, model) { // Radio hop is owned by Simu5G/OAI; keep backhaul (zone/domain/edge) TC. segment.ConfiguredNetChar.Latency = 0 segment.ConfiguredNetChar.Jitter = 0 segment.ConfiguredNetChar.PacketLoss = 0 segment.ConfiguredNetChar.Throughput = MAX_THROUGHPUT ncThroughput = MAX_THROUGHPUT } maxThroughput := ncThroughput // Initialize segment-specific BW attributes from Algo config Loading Loading @@ -1154,6 +1166,65 @@ func getNetChars(elemName string, model *mod.Model) *dataModel.NetworkCharacteri return nc } func isExternalNwSimulatorType(t string) bool { switch strings.ToLower(strings.TrimSpace(t)) { case "simu5g", "oai": return true default: return false } } func userMetaExternalNwSimulator(userMeta map[string]string) bool { if userMeta == nil { return false } if raw, ok := userMeta["network-info"]; ok && raw != "" { var info struct { Type string `json:"type"` } if json.Unmarshal([]byte(raw), &info) == nil && isExternalNwSimulatorType(info.Type) { return true } } if raw, ok := userMeta["mec-sandbox"]; ok && raw != "" { var ms struct { ConnectivityModel string `json:"connectivityModel"` } if json.Unmarshal([]byte(raw), &ms) == nil && isExternalNwSimulatorType(ms.ConnectivityModel) { return true } } return false } func skipCellularTc(deployment *dataModel.Deployment) bool { if isExternalNwSimulatorType(os.Getenv("MEEP_NW_SIMULATOR_TYPE")) { return true } if deployment == nil { return false } return userMetaExternalNwSimulator(deployment.UserMeta) } func isCellularAccessElem(elemName string, model *mod.Model) bool { if strings.HasPrefix(elemName, ElemTypeD2D) { return true } if model == nil { return false } node := model.GetNode(elemName) if pl, ok := node.(*dataModel.PhysicalLocation); ok { return mod.IsUe(pl.Type_) } if nl, ok := node.(*dataModel.NetworkLocation); ok { return mod.IsNetLoc(nl.Type_) && !mod.IsDefaultNetLoc(nl.Type_) } return false } // printFlowNamesFromList - func printFlowNamesFromList(list []*SegAlgoFlow) string { str := "" Loading