Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 80 additions & 1 deletion .github/workflows/L2-tests.yml
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ on: [push, pull_request]

env:
MEMCR_REF: "b58f2b8e26cab6b67eceaa36fd6ce5a6d04dcd28"
# GCOV settings for coverage - must be at workflow level so all processes inherit
GCOV_PREFIX: /tmp/gcov
GCOV_PREFIX_STRIP: "3"

jobs:
build:
Expand Down Expand Up @@ -116,9 +119,71 @@ jobs:
</policy>
</busconfig>' > "/etc/dbus-1/system.d/org.rdk.dobby.conf"

- name: Setup network interfaces for testing
run: |
# Create dummy network interfaces that tests expect
sudo ip link add enp0s8 type dummy || true
sudo ip link add enp0s3 type dummy || true
sudo ip link set enp0s8 up || true
sudo ip link set enp0s3 up || true

# Create bridge for Dobby networking
sudo ip link add dobby0 type bridge || true
sudo ip link set dobby0 up || true
# Assign IP to dobby0 bridge for container networking
sudo ip addr add 100.64.11.1/24 dev dobby0 || true

# Enable IP forwarding for container network access
sudo sysctl -w net.ipv4.ip_forward=1

# Detect the real default-route interface (not hardcoded eth0)
EXT_IF=$(ip -o -4 route show to default | awk '{print $5}' | head -n1)
echo "Detected external interface: $EXT_IF"

# Setup NAT for container outbound traffic using detected interface
sudo iptables -t nat -A POSTROUTING -s 100.64.11.0/24 -o "$EXT_IF" -j MASQUERADE || true
sudo iptables -A FORWARD -i dobby0 -o "$EXT_IF" -j ACCEPT || true
sudo iptables -A FORWARD -i "$EXT_IF" -o dobby0 -m state --state RELATED,ESTABLISHED -j ACCEPT || true

# Force a resolv.conf that works well in CI/container contexts
# (avoid systemd stub resolver issues)
sudo rm -f /etc/resolv.conf
printf "nameserver 1.1.1.1\nnameserver 8.8.8.8\n" | sudo tee /etc/resolv.conf

# Verify network setup
echo "=== Network configuration ==="
ip addr show dobby0
cat /etc/resolv.conf
echo "=== iptables NAT ==="
sudo iptables -t nat -L POSTROUTING -n -v | head -5

- name: Setup permissions and directories
run: |
# Create and set permissions for profiling/coverage directories
sudo mkdir -p /home/runner/work
sudo chmod -R 777 /home/runner/work
sudo chmod -R 777 $GITHUB_WORKSPACE || true
# Create tmp directory for container logs
sudo mkdir -p /tmp
sudo chmod 1777 /tmp

# Setup GCOV output directory - CRITICAL for coverage builds
# The hook processes (DobbyPluginLauncher) need this to be writable
sudo mkdir -p /tmp/gcov
sudo chmod -R 777 /tmp/gcov

# Make GCOV env vars available to ALL processes including those started by sudo/systemd
echo "GCOV_PREFIX=/tmp/gcov" | sudo tee -a /etc/environment
echo "GCOV_PREFIX_STRIP=3" | sudo tee -a /etc/environment

# Also export to current shell for subsequent steps
echo "GCOV_PREFIX=/tmp/gcov" >> $GITHUB_ENV
echo "GCOV_PREFIX_STRIP=3" >> $GITHUB_ENV

- name: build Dobby
run: |
sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf
# Don't override resolv.conf - we already set it up in network setup step
# sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf
cd Dobby
sudo cp /usr/lib/x86_64-linux-gnu/dbus-1.0/include/dbus/dbus-arch-deps.h /usr/include/dbus-1.0/dbus
sudo mkdir -p /usr/lib/plugins/dobby
Expand Down Expand Up @@ -223,6 +288,19 @@ jobs:
- name: Run the l2 test
working-directory: Dobby/tests/L2_testing/test_runner/
run: |
# Verify GCOV env is set (should be inherited from job-level env)
echo "GCOV_PREFIX=$GCOV_PREFIX"
echo "GCOV_PREFIX_STRIP=$GCOV_PREFIX_STRIP"

# Ensure directory exists and is writable
sudo mkdir -p /tmp/gcov
sudo chmod -R 777 /tmp/gcov

# Verify DNS is working before running tests
echo "Testing DNS resolution..."
nslookup example.com || echo "DNS lookup warning (may affect some tests)"

# Run tests - GCOV_PREFIX is inherited from job env
python3 runner.py -p 3 -v 5
cp $GITHUB_WORKSPACE/Dobby/tests/L2_testing/test_runner/DobbyL2TestResults.json $GITHUB_WORKSPACE

Expand Down Expand Up @@ -254,3 +332,4 @@ jobs:
DobbyL2TestResults.json
l2coverage
if-no-files-found: warn

45 changes: 28 additions & 17 deletions bundle/lib/source/DobbyConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -659,42 +659,49 @@ void DobbyConfig::addPluginLauncherHooks(std::shared_ptr<rt_dobby_schema> cfg, c
cfg->hooks = (rt_dobby_schema_hooks*)calloc(1, sizeof(rt_dobby_schema_hooks));
}

// createRuntime, createContainer, poststart and poststop hook paths must
// resolve in the runtime namespace - config is in bundle
std::string configPath = bundlePath + "/config.json";
// createRuntime and poststop hook paths must resolve in the runtime namespace
// and execute in runtime namespace, so they can use the bundle path directly
std::string runtimeConfigPath = bundlePath + "/config.json";

// createContainer hook runs in container namespace, so it needs to use
// the fixed path where we mounted the config.json
std::string containerConfigPath = "/tmp/dobby_config.json";

// populate createRuntime hook with DobbyPluginLauncher args
rt_defs_hook *createRuntimeEntry = (rt_defs_hook*)calloc(1, sizeof(rt_defs_hook));
setPluginHookEntry(createRuntimeEntry, "createRuntime", configPath);
setPluginHookEntry(createRuntimeEntry, "createRuntime", runtimeConfigPath);
cfg->hooks->create_runtime = (rt_defs_hook**)realloc(cfg->hooks->create_runtime, sizeof(rt_defs_hook*) * ++cfg->hooks->create_runtime_len);
cfg->hooks->create_runtime[cfg->hooks->create_runtime_len-1] = createRuntimeEntry;

// populate createContainer hook with DobbyPluginLauncher args
// Uses container namespace path since hook executes in container namespace
rt_defs_hook *createContainerEntry = (rt_defs_hook*)calloc(1, sizeof(rt_defs_hook));
setPluginHookEntry(createContainerEntry, "createContainer", configPath);
setPluginHookEntry(createContainerEntry, "createContainer", containerConfigPath);
cfg->hooks->create_container = (rt_defs_hook**)realloc(cfg->hooks->create_container, sizeof(rt_defs_hook*) * ++cfg->hooks->create_container_len);
cfg->hooks->create_container[cfg->hooks->create_container_len-1] = createContainerEntry;

// populate poststart hook with DobbyPluginLauncher args
// poststart runs in runtime namespace, so use runtime path
rt_defs_hook *poststartEntry = (rt_defs_hook*)calloc(1, sizeof(rt_defs_hook));
setPluginHookEntry(poststartEntry, "poststart", configPath);
setPluginHookEntry(poststartEntry, "poststart", runtimeConfigPath);
cfg->hooks->poststart = (rt_defs_hook**)realloc(cfg->hooks->poststart, sizeof(rt_defs_hook*) * ++cfg->hooks->poststart_len);
cfg->hooks->poststart[cfg->hooks->poststart_len-1] = poststartEntry;

// populate poststop hook with DobbyPluginLauncher args
// poststop runs in runtime namespace, so use runtime path
rt_defs_hook *poststopEntry = (rt_defs_hook*)calloc(1, sizeof(rt_defs_hook));
setPluginHookEntry(poststopEntry, "poststop", configPath);
setPluginHookEntry(poststopEntry, "poststop", runtimeConfigPath);
cfg->hooks->poststop = (rt_defs_hook**)realloc(cfg->hooks->poststop, sizeof(rt_defs_hook*) * ++cfg->hooks->poststop_len);
cfg->hooks->poststop[cfg->hooks->poststop_len-1] = poststopEntry;

#ifdef USE_STARTCONTAINER_HOOK
// startContainer hook paths must resolve in the container namespace,
// config is in container rootdir
configPath = "/tmp/config.json";
// startContainer hook runs in container namespace after pivot_root,
// use the same path where config was mounted for createContainer
std::string startContainerConfigPath = "/tmp/dobby_config.json";

// populate startContainer hook with DobbyPluginLauncher args
rt_defs_hook *startContainerEntry = (rt_defs_hook*)calloc(1, sizeof(rt_defs_hook));
setPluginHookEntry(startContainerEntry, "startContainer", configPath);
setPluginHookEntry(startContainerEntry, "startContainer", startContainerConfigPath);
cfg->hooks->start_container = (rt_defs_hook**)realloc(cfg->hooks->start_container, sizeof(rt_defs_hook*) * ++cfg->hooks->start_container_len);
Comment on lines 697 to 705
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addPluginLauncherHooks configures the startContainer hook to use /tmp/config.json, but in updateBundleConfig the bind-mount of bundlePath + "/config.json" to /tmp/config.json appears to have been removed. Unless /tmp/config.json is created elsewhere, the startContainer hook will fail to resolve the config path. Either reinstate the /tmp/config.json mount under USE_STARTCONTAINER_HOOK or update the hook’s -c path to match the newly mounted /tmp/dobby_config.json (or another guaranteed path).

Copilot uses AI. Check for mistakes.
cfg->hooks->start_container[cfg->hooks->start_container_len-1] = startContainerEntry;
#endif
Expand Down Expand Up @@ -748,16 +755,19 @@ bool DobbyConfig::updateBundleConfig(const ContainerId& id, std::shared_ptr<rt_d
// if there are any rdk plugins, set up DobbyPluginLauncher in config
if (cfg->rdk_plugins && cfg->rdk_plugins->plugins_count)
{
#ifdef USE_STARTCONTAINER_HOOK
// bindmount DobbyPluginLauncher to container
if(!addMount(PLUGINLAUNCHER_PATH, PLUGINLAUNCHER_PATH, "bind", 0,
// Bind mount the config.json file to a fixed path inside the container.
// This is needed because createContainer hooks run in the container's mount
// namespace but need to access the config.json file which exists on the host.
// We use /tmp/dobby_config.json as a well-known location that exists in all containers.
if(!addMount(bundlePath + "/config.json", "/tmp/dobby_config.json", "bind", 0,
{ "bind", "ro", "nosuid", "nodev" }))
{
return false;
AI_LOG_WARN("Failed to add config mount for hooks, createContainer may fail");
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the bind-mount of the host config.json into the container fails, the code now only logs a warning and continues. Since the createContainer hook’s -c path depends on this mount, continuing can lead to hard-to-debug hook failures later. Consider treating this as a hard failure (return false) or, if it’s intentionally best-effort, also disable/omit the createContainer hook when the mount can’t be added.

Suggested change
AI_LOG_WARN("Failed to add config mount for hooks, createContainer may fail");
AI_LOG_ERROR("Failed to add config mount for hooks, aborting bundle config update");
return false;

Copilot uses AI. Check for mistakes.
}

// bindmount the config file to container
if(!addMount(bundlePath + "/config.json", "/tmp/config.json", "bind", 0,
#ifdef USE_STARTCONTAINER_HOOK
// bindmount DobbyPluginLauncher to container
if(!addMount(PLUGINLAUNCHER_PATH, PLUGINLAUNCHER_PATH, "bind", 0,
{ "bind", "ro", "nosuid", "nodev" }))
{
return false;
Expand Down Expand Up @@ -1006,3 +1016,4 @@ bool DobbyConfig::convertToCompliant(const ContainerId& id, std::shared_ptr<rt_d
AI_LOG_FN_EXIT();
return true;
}

14 changes: 12 additions & 2 deletions bundle/lib/source/DobbyTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -467,16 +467,25 @@ void DobbyTemplate::setTemplateCpuRtSched()

long cpuRtRuntime = 0;
long cpuRtPeriod = 0;
bool isCgroupV2 = false;
while ((mnt = getmntent_r(procMounts, &mntBuf, buf, sizeof(buf))) != nullptr)
{
// skip entries that don't have a mount point, type or options
if (!mnt->mnt_type || !mnt->mnt_dir || !mnt->mnt_opts)
continue;

// skip non-cgroup mounts
if (strcmp(mnt->mnt_type, "cgroup") != 0)
// skip non-cgroup mounts (check for both cgroup v1 and v2)
if (strcmp(mnt->mnt_type, "cgroup") != 0 && strcmp(mnt->mnt_type, "cgroup2") != 0)
continue;

// cgroupv2 doesn't support cpu.rt_runtime_us in the same way
if (strcmp(mnt->mnt_type, "cgroup2") == 0)
{
AI_LOG_INFO("cgroup v2 detected, CPU RT runtime defaults to disabled");
isCgroupV2 = true;
break;
}
Comment on lines +470 to +487
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isCgroupV2 is set but never used, which will trigger an unused-but-set-variable warning on many toolchains. Since the cgroupv2 early-break already forces the RT runtime values to remain disabled, consider removing isCgroupV2 or using it to make the intent explicit.

Copilot uses AI. Check for mistakes.

// check if a cpu cgroup mount
char* mntopt = hasmntopt(mnt, "cpu");
if (!mntopt || (strncmp(mntopt, "cpu", 3) != 0))
Expand Down Expand Up @@ -700,3 +709,4 @@ bool DobbyTemplate::applyAt(int dirFd, const std::string& fileName,
{
return instance()->_applyAt(dirFd, fileName, dictionary, prettyPrint);
}

4 changes: 2 additions & 2 deletions bundle/lib/source/templates/OciConfigJson1.0.2-dobby.template
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,7 @@ static const char* ociJsonTemplate = R"JSON(
],
"memory": {
"limit": {{MEM_LIMIT}},
"swap": {{MEM_LIMIT}},
"swappiness": 60
"swap": {{MEM_LIMIT}}
},
"cpu": {
{{#CPU_SHARES_ENABLED}}
Expand Down Expand Up @@ -401,3 +400,4 @@ static const char* ociJsonTemplate = R"JSON(
{{/ENABLE_RDK_PLUGINS}}
}
)JSON";

Original file line number Diff line number Diff line change
Expand Up @@ -339,8 +339,7 @@ static const char* ociJsonTemplate = R"JSON(
],
"memory": {
"limit": {{MEM_LIMIT}},
"swap": {{MEM_LIMIT}},
"swappiness": 60
"swap": {{MEM_LIMIT}}
},
"cpu": {
{{#CPU_SHARES_ENABLED}}
Expand Down Expand Up @@ -412,3 +411,4 @@ static const char* ociJsonTemplate = R"JSON(
{{/ENABLE_RDK_PLUGINS}}
}
)JSON";

58 changes: 41 additions & 17 deletions daemon/lib/source/DobbyEnv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,31 +166,54 @@ std::map<IDobbyEnv::Cgroup, std::string> DobbyEnv::getCgroupMountPoints()
struct mntent* mnt;
char buf[PATH_MAX + 256];

// Check for cgroupv2 unified hierarchy first
bool isCgroupV2 = false;
while ((mnt = getmntent_r(procMounts, &mntBuf, buf, sizeof(buf))) != nullptr)
{
// skip entries that don't have a mountpount, type or options
if (!mnt->mnt_type || !mnt->mnt_dir || !mnt->mnt_opts)
continue;

// skip non-cgroup mounts
if (strcmp(mnt->mnt_type, "cgroup") != 0)
continue;

// check for the cgroup type
for (const std::pair<const std::string, IDobbyEnv::Cgroup> cgroup : cgroupNames)
if (mnt->mnt_type && strcmp(mnt->mnt_type, "cgroup2") == 0)
{
char* mntopt = hasmntopt(mnt, cgroup.first.c_str());
if (!mntopt)
isCgroupV2 = true;
AI_LOG_INFO("detected cgroup v2 unified hierarchy @ '%s'", mnt->mnt_dir);
// For cgroupv2, all controllers are at the same mount point
std::string cgroupPath = mnt->mnt_dir;
for (const auto& cgroup : cgroupNames)
{
mounts[cgroup.second] = cgroupPath;
}
break;
Comment on lines +169 to +183
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the cgroupv2 scan loop, the code no longer checks mnt->mnt_dir / mnt->mnt_opts for null before using mnt->mnt_dir in logging and assignments. This can lead to a crash if those fields are missing. Also, for cgroupv2 the code populates all controller mount points (including GPU/ION) with the unified mount, which makes callers treat unsupported controllers as available (e.g., DobbyStats will start attempting reads under that path). Consider restoring the null checks and only populating entries for controllers that are actually supported under cgroupv2 (or adding explicit cgroupv2 handling in callers).

Copilot uses AI. Check for mistakes.
}
}

// Reset to scan for cgroupv1 if not v2
if (!isCgroupV2)
{
rewind(procMounts);
while ((mnt = getmntent_r(procMounts, &mntBuf, buf, sizeof(buf))) != nullptr)
{
// skip entries that don't have a mountpount, type or options
if (!mnt->mnt_type || !mnt->mnt_dir || !mnt->mnt_opts)
continue;

if (strcmp(mntopt, cgroup.first.c_str()) != 0)
// skip non-cgroup mounts
if (strcmp(mnt->mnt_type, "cgroup") != 0)
continue;

AI_LOG_INFO("found cgroup '%s' mounted @ '%s'",
cgroup.first.c_str(), mnt->mnt_dir);
// check for the cgroup type
for (const std::pair<const std::string, IDobbyEnv::Cgroup> cgroup : cgroupNames)
{
char* mntopt = hasmntopt(mnt, cgroup.first.c_str());
if (!mntopt)
continue;

mounts[cgroup.second] = mnt->mnt_dir;
break;
if (strcmp(mntopt, cgroup.first.c_str()) != 0)
continue;

AI_LOG_INFO("found cgroup '%s' mounted @ '%s'",
cgroup.first.c_str(), mnt->mnt_dir);

mounts[cgroup.second] = mnt->mnt_dir;
break;
}
}
}

Expand All @@ -200,3 +223,4 @@ std::map<IDobbyEnv::Cgroup, std::string> DobbyEnv::getCgroupMountPoints()
return mounts;
}


3 changes: 2 additions & 1 deletion daemon/process/settings/dobby.dev_vm.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
],

"network": {
"externalInterfaces": [ "enp0s8", "enp0s3", "docker0", "eth0" ],
"externalInterfaces": [ "eth0", "enp0s8", "enp0s3", "docker0" ],
"addressRange": "100.64.11.0"
},

Expand All @@ -32,3 +32,4 @@




Loading
Loading