Commit 1136cd0f authored by Yann Garcia's avatar Yann Garcia
Browse files

Integration to EdgeNative-Connector

parent 51b2f461
Loading
Loading
Loading
Loading
+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.")
}
+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
@@ -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"
@@ -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()
@@ -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
}

@@ -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()

+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
@@ -137,6 +137,7 @@ type SandboxTemplate struct {
	NemURL               string
	NemTimeoutMs         string
	NemActivateTimeoutMs string
	HostAgentURL         string
}

// Deploy - Generate charts & deploy single process or entire scenario
@@ -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) {
+4 −0
Changes for go-apps/meepctl/cmd/deploy.go: 4 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -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
}

+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
@@ -17,8 +17,10 @@
package netchar

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"sort"
	"strconv"
	"strings"
@@ -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
}
@@ -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{}
@@ -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
@@ -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