Commit f662f733 authored by Yann Garcia's avatar Yann Garcia
Browse files

Bug foxed in simu5g_activate.py

parent 19cf6456
Loading
Loading
Loading
Loading
+1 −0
Changes for charts/platform-core/meep-virt-engine/values.yaml: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ image:
    MEEP_NEM_URL: ""
    MEEP_NEM_TIMEOUT_MS: "3000"
    MEEP_NEM_ACTIVATE_TIMEOUT_MS: "180000"
    MEEP_SIMU5G_HOST_AGENT_URL: ""
  envSecret:

service:
+1 −0
Changes for go-apps/meepctl/cmd/delete.go: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -107,6 +107,7 @@ func deleteRun(cmd *cobra.Command, args []string) {
		} else {
			deleteApps(deleteData.coreApps, cmd)
		}
		deleteNwSimulatorSidecars(cmd)
	case "dep":
		if targetApp != "" {
			deleteSingleDepApp(targetApp, cmd)
+157 −32
Changes for go-apps/meepctl/cmd/deploy.go: 157 added lines, 32 removed lines.
Original line number Diff line number Diff line
@@ -267,6 +267,7 @@ func deployCoreRun(cmd *cobra.Command, args []string) {

	start := time.Now()
	deploySetup(cmd)
	syncNwSimulatorSidecars(cmd)

	for _, target := range targets {
		if target == "all" {
@@ -1198,17 +1199,94 @@ func getItemList(target string) string {
}

const nemProxyListenPort = "18787"
const hostAgentListenPort = "8790"
const nwSimHostAgentName = "meep-nw-sim-host-agent"

func nwSimulatorType() string {
	t := strings.ToLower(strings.TrimSpace(utils.RepoCfg.GetString("repo.nw_simulator.type")))
	if t == "" {
		return "internal"
	}
	return t
}

func nwSimulatorExternal() bool {
	t := nwSimulatorType()
	return t == "simu5g" || t == "oai"
}

func hostAgentURL() string {
	u := strings.TrimRight(utils.RepoCfg.GetString("repo.nw_simulator.host-agent-url"), "/")
	if u != "" {
		return u
	}
	if nodeIP := viper.GetString("node.ip"); nodeIP != "" {
		return "http://" + nodeIP + ":" + hostAgentListenPort
	}
	return ""
}

func operatorHomeDir() string {
	if h, err := homedir.Dir(); err == nil && strings.TrimSpace(h) != "" {
		return h
	}
	return "/home/ubuntu-template"
}

// syncNwSimulatorSidecars applies or removes hostNetwork sidecars for
// simu5g/oai even when Helm core apps are skipped (already deployed).
func syncNwSimulatorSidecars(cobraCmd *cobra.Command) {
	if !nwSimulatorExternal() {
		deleteNwSimulatorSidecars(cobraCmd)
		return
	}
	nemUrl := utils.RepoCfg.GetString("repo.nw_simulator.nem-url")
	if nemUrl != "" {
		if err := deployNemHostProxy(nemUrl, cobraCmd); err != nil {
			fmt.Println(utils.FormatWarning("NEM host proxy: " + err.Error()))
		}
	}
	if err := deployNwSimHostAgent(cobraCmd); err != nil {
		fmt.Println(utils.FormatWarning("nw-sim host agent: " + err.Error()))
		return
	}
	fmt.Println(utils.FormatStep("Deployed " + nwSimHostAgentName + " (simu5g/oai host TUN + adapters)"))
}

func deleteNwSimulatorSidecars(cobraCmd *cobra.Command) {
	for _, name := range []string{nwSimHostAgentName, "meep-nem-proxy"} {
		cmd := exec.Command("kubectl", "delete", "deploy", name, "-n", "default", "--ignore-not-found=true")
		_, _ = utils.ExecuteCmd(cmd, cobraCmd)
	}
}

func applyK8sManifest(prefix, manifest string, cobraCmd *cobra.Command) error {
	tmp, err := os.CreateTemp("", prefix+"-*.yaml")
	if err != nil {
		return err
	}
	path := tmp.Name()
	defer func() { _ = os.Remove(path) }()
	if _, err := tmp.WriteString(manifest); err != nil {
		_ = tmp.Close()
		return err
	}
	if err := tmp.Close(); err != nil {
		return err
	}
	cmd := exec.Command("kubectl", "apply", "-f", path)
	_, err = utils.ExecuteCmd(cmd, cobraCmd)
	return err
}

// setNwSimulatorHelmFlags copies repo.nw_simulator from .meepctl-repocfg.yaml
// into core app env (MEEP_NW_SIMULATOR_TYPE, MEEP_NEM_URL, MEEP_NEM_TIMEOUT_MS,
// MEEP_NEM_ACTIVATE_TIMEOUT_MS). Overlay pods cannot reach the radio-host NEM
// on the node LAN, so simu5g/oai listing and instance lifecycle go through a
// hostNetwork proxy on node.ip:18787 (same pattern as meep-auth-svc).
// MEEP_NEM_ACTIVATE_TIMEOUT_MS, MEEP_SIMU5G_HOST_AGENT_URL). Overlay pods cannot
// reach the radio-host NEM on the node LAN, so simu5g/oai listing and instance
// lifecycle go through a hostNetwork proxy on node.ip:18787.
func setNwSimulatorHelmFlags(flags [][]string, cobraCmd *cobra.Command) [][]string {
	nwSimType := utils.RepoCfg.GetString("repo.nw_simulator.type")
	if nwSimType == "" {
		nwSimType = "internal"
	}
	_ = cobraCmd
	nwSimType := nwSimulatorType()
	nemUrl := utils.RepoCfg.GetString("repo.nw_simulator.nem-url")
	timeoutMs := utils.RepoCfg.GetInt("repo.nw_simulator.timeout-ms")
	if timeoutMs <= 0 {
@@ -1218,22 +1296,17 @@ func setNwSimulatorHelmFlags(flags [][]string, cobraCmd *cobra.Command) [][]stri
	if activateTimeoutMs <= 0 {
		activateTimeoutMs = 180000
	}
	if (nwSimType == "simu5g" || nwSimType == "oai") && nemUrl != "" {
	if nwSimulatorExternal() && nemUrl != "" {
		if nodeIP := viper.GetString("node.ip"); nodeIP != "" {
			if err := deployNemHostProxy(nemUrl, cobraCmd); err != nil {
				fmt.Println(utils.FormatWarning("NEM host proxy: " + err.Error()))
			} else {
			nemUrl = "http://" + nodeIP + ":" + nemProxyListenPort
		}
	}
	}
	flags = utils.HelmFlags(flags, "--set", "image.env.MEEP_NW_SIMULATOR_TYPE="+nwSimType)
	// --set-string keeps http://host:port from being parsed as a Helm nested map.
	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 != "" {
	if hostAgent := hostAgentURL(); hostAgent != "" {
		flags = utils.HelmFlags(flags, "--set-string", "image.env.MEEP_SIMU5G_HOST_AGENT_URL="+hostAgent)
	}
	return flags
@@ -1280,27 +1353,79 @@ spec:
      - operator: Exists
`, nemProxyListenPort, backend, nemProxyListenPort, nemProxyListenPort)

	tmp, err := os.CreateTemp("", "meep-nem-proxy-*.yaml")
	if err != nil {
		return err
	}
	path := tmp.Name()
	defer func() { _ = os.Remove(path) }()
	if _, err := tmp.WriteString(manifest); err != nil {
		_ = tmp.Close()
		return err
	}
	if err := tmp.Close(); err != nil {
		return err
	}

	// Replace a previous one-off Pod of the same name, if present.
	del := exec.Command("kubectl", "delete", "pod", "meep-nem-proxy", "--ignore-not-found")
	_, _ = utils.ExecuteCmd(del, cobraCmd)
	return applyK8sManifest("meep-nem-proxy", manifest, cobraCmd)
}

	cmd := exec.Command("kubectl", "apply", "-f", path)
	_, err = utils.ExecuteCmd(cmd, cobraCmd)
	return err
func deployNwSimHostAgent(cobraCmd *cobra.Command) error {
	gitdir := strings.TrimSuffix(deployData.gitdir, "/")
	if gitdir == "" {
		gitdir = strings.TrimSuffix(viper.GetString("meep.gitdir"), "/")
	}
	if gitdir == "" {
		return fmt.Errorf("meep.gitdir is empty")
	}
	script := gitdir + "/tools/simu5g/simu5g_runner.py"
	registry := deployData.registry
	if registry == "" {
		registry = strings.TrimSuffix(viper.GetString("meep.registry"), "/")
	}
	if registry == "" {
		registry = "meep-docker-registry:30001"
	}
	tag := deployData.tag
	if tag == "" {
		tag = "latest"
	}
	image := registry + "/meep-sandbox-ctrl:" + tag
	home := operatorHomeDir()
	kubeconfig := home + "/.kube/config"
	nemUrl := strings.TrimRight(utils.RepoCfg.GetString("repo.nw_simulator.nem-url"), "/")
	if nemUrl == "" {
		nemUrl = "http://172.29.10.20:8787"
	}
	manifest := fmt.Sprintf(`apiVersion: apps/v1
kind: Deployment
metadata:
  name: %s
  namespace: default
  labels:
    app: %s
spec:
  replicas: 1
  selector:
    matchLabels:
      app: %s
  template:
    metadata:
      labels:
        app: %s
    spec:
      hostNetwork: true
      hostPID: true
      dnsPolicy: ClusterFirstWithHostNet
      containers:
      - name: host-agent
        image: %s
        imagePullPolicy: IfNotPresent
        securityContext:
          privileged: true
        env:
        - name: HOME
          value: %q
        - name: KUBECONFIG
          value: %q
        - name: PYTHONUNBUFFERED
          value: "1"
        command: ["nsenter", "-t", "1", "-m", "-u", "-i", "-n", "-p", "-C", "--",
                  "python3", %q, "serve", "--watch",
                  "-n", "*", "--nem-url", %q]
      tolerations:
      - operator: Exists
`, nwSimHostAgentName, nwSimHostAgentName, nwSimHostAgentName, nwSimHostAgentName,
		image, home, kubeconfig, script, nemUrl)
	return applyK8sManifest("meep-nw-sim-host-agent", manifest, cobraCmd)
}

func getProgressBar(current, total int) string {
+47 −13
Changes for tools/simu5g/simu5g_activate.py: 47 added lines, 13 removed lines.
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@ one-shot privileged hostNetwork Job that nsenter's into the host.

from __future__ import annotations

import copy
import json
import os
import subprocess
@@ -471,28 +472,60 @@ def deactivate(args):
    run_via_kubectl_job(args.namespace, "deactivate", config_file=cfg_path)


def watching_all(namespace):
    return str(namespace or "").strip().lower() in ("*", "all")


def list_sandbox_ids():
    """Sandbox namespaces from meep-sandbox-ctrl Deployments, plus sbx* NS."""
    ids = []
    r = kubectl(
        "get", "deploy", "-A", "-l", "app=meep-sandbox-ctrl",
        "-o", r'jsonpath={range .items[*]}{.metadata.namespace}{"\n"}{end}',
    )
    for line in (r.stdout or "").splitlines():
        ns = line.strip()
        if ns and ns not in ids:
            ids.append(ns)
    r = kubectl("get", "ns", "-o", "jsonpath={.items[*].metadata.name}")
    for ns in (r.stdout or "").split():
        if ns.startswith("sbx") and ns not in ids:
            ids.append(ns)
    return ids


def watch_loop(args):
    started = set()
    if watching_all(args.namespace):
        _log(f"watching NEM {args.nem_url} for all sandboxes")
    else:
        _log(f"watching NEM {args.nem_url} for sandbox {args.namespace}")
    while True:
        inst = fetch_nem_instance(args.nem_url, args.namespace)
        ids = list_sandbox_ids() if watching_all(args.namespace) else [args.namespace]
        for sid in list(started):
            if sid not in ids:
                ids.append(sid)
        for sid in ids:
            one = copy.copy(args)
            one.namespace = sid
            one.force = False
            inst = fetch_nem_instance(args.nem_url, sid)
            ready = instance_ready(inst)
        if ready and args.namespace not in started:
            _log("NEM READY — starting TUN + mobility + RNIS")
            if ready and sid not in started:
                _log(f"NEM READY sandbox={sid} — starting TUN + mobility + RNIS")
                try:
                activate(args)
                started.add(args.namespace)
                    activate(one)
                    started.add(sid)
                except SystemExit as exc:
                _log(f"activate failed: {exc}")
                    _log(f"activate failed sandbox={sid}: {exc}")
                    time.sleep(max(args.interval, 15.0))
                continue
        elif not ready and args.namespace in started:
            _log(f"NEM {inst.get('state')} — stopping host adapters")
            elif not ready and sid in started:
                _log(f"NEM {inst.get('state')} sandbox={sid} — stopping host adapters")
                try:
                deactivate(args)
                    deactivate(one)
                except SystemExit as exc:
                _log(f"deactivate failed: {exc}")
            started.discard(args.namespace)
                    _log(f"deactivate failed sandbox={sid}: {exc}")
                started.discard(sid)
        time.sleep(args.interval)


@@ -563,7 +596,8 @@ def add_activate_parsers(subparsers):
        p.add_argument(
            "-n", "--namespace",
            default=os.environ.get("SANDBOX_NS", "sbx4lesbpo"),
            help="Sandbox / NEM instance id (Kubernetes namespace).",
            help="Sandbox / NEM instance id (Kubernetes namespace). "
                 "Use '*' with serve --watch to cover every sandbox.",
        )
        p.add_argument(
            "--config-file",
+8 −0
Changes for tools/simu5g/test_simu5g_runner.py: 8 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -118,6 +118,14 @@ class TestMergeNem(unittest.TestCase):
        self.assertEqual(tun, "s5gbr2")
        self.assertTrue(path.endswith(".json"))

    def test_watching_all(self):
        import simu5g_activate as act
        self.assertTrue(act.watching_all("*"))
        self.assertTrue(act.watching_all("all"))
        self.assertTrue(act.watching_all("ALL"))
        self.assertFalse(act.watching_all("sbxnsptfga"))
        self.assertFalse(act.watching_all(""))


if __name__ == "__main__":
    unittest.main()