Skip to content
Merged
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
56 changes: 34 additions & 22 deletions .github/workflows/L1-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ jobs:
strategy:
fail-fast: false
matrix:
compiler: [ gcc, clang ]
coverage: [ with-coverage, without-coverage ]
compiler: [gcc, clang]
coverage: [with-coverage, without-coverage]
exclude:
- compiler: clang
coverage: with-coverage
Expand All @@ -25,7 +25,17 @@ jobs:
# If adding a RUN_TESTS cmake option, it will build with enabling optional_flags and run the L1 tests
# matrix runs both versions
build_type: ["Release", "Debug"]
extra_flags: [ "RUN_TESTS", "-DLEGACY_COMPONENTS=ON", "-DLEGACY_COMPONENTS=OFF", "-DUSE_SYSTEMD=ON", "-DUSE_SYSTEMD=OFF", "-DDOBBY_HIBERNATE_MEMCR_IMPL=ON -DDOBBY_HIBERNATE_MEMCR_PARAMS_ENABLED=OFF", "-DDOBBY_HIBERNATE_MEMCR_IMPL=ON -DDOBBY_HIBERNATE_MEMCR_PARAMS_ENABLED=ON", "-DDOBBY_HIBERNATE_MEMCR_IMPL=OFF"]
extra_flags:
[
"RUN_TESTS",
"-DLEGACY_COMPONENTS=ON",
"-DLEGACY_COMPONENTS=OFF",
"-DUSE_SYSTEMD=ON",
"-DUSE_SYSTEMD=OFF",
"-DDOBBY_HIBERNATE_MEMCR_IMPL=ON -DDOBBY_HIBERNATE_MEMCR_PARAMS_ENABLED=OFF",
"-DDOBBY_HIBERNATE_MEMCR_IMPL=ON -DDOBBY_HIBERNATE_MEMCR_PARAMS_ENABLED=ON",
"-DDOBBY_HIBERNATE_MEMCR_IMPL=OFF",
]
Comment thread
ks734 marked this conversation as resolved.
name: Build in ${{ matrix.build_type }} Mode (${{ matrix.extra_flags }})
steps:
- name: checkout
Expand All @@ -48,42 +58,43 @@ jobs:

- name: Install gmock
run: |
cd $GITHUB_WORKSPACE
git clone https://github.com/google/googletest.git -b release-1.11.0
cd googletest
mkdir build
cd build
cmake ..
make
sudo make install
cd $GITHUB_WORKSPACE
git clone https://github.com/google/googletest.git -b release-1.11.0
cd googletest
mkdir build
cd build
cmake ..
make
sudo make install

- name: build dobby
run: |
cd $GITHUB_WORKSPACE
mkdir build
cd build
if [ ${{ matrix.extra_flags }} = "RUN_TESTS" ]
then
cmake -DCMAKE_TOOLCHAIN_FILE="${{ env.TOOLCHAIN_FILE }}" -DRDK_PLATFORM=DEV_VM -DCMAKE_INSTALL_PREFIX:PATH=/usr -DENABLE_DOBBYL1TEST=ON -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} ${{ env.optional_flags }} ${{ env.optional_plugins }} ..
else
cmake -DCMAKE_TOOLCHAIN_FILE="${{ env.TOOLCHAIN_FILE }}" -DRDK_PLATFORM=DEV_VM -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} ${{ matrix.extra_flags }} ${{ env.optional_plugins }} ..
fi
make -j $(nproc)
cd $GITHUB_WORKSPACE
mkdir build
cd build
if [ ${{ matrix.extra_flags }} = "RUN_TESTS" ]
then
cmake -DCMAKE_TOOLCHAIN_FILE="${{ env.TOOLCHAIN_FILE }}" -DRDK_PLATFORM=DEV_VM -DCMAKE_INSTALL_PREFIX:PATH=/usr -DENABLE_DOBBYL1TEST=ON -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} ${{ env.optional_flags }} ${{ env.optional_plugins }} ..
else
cmake -DCMAKE_TOOLCHAIN_FILE="${{ env.TOOLCHAIN_FILE }}" -DRDK_PLATFORM=DEV_VM -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} ${{ matrix.extra_flags }} ${{ env.optional_plugins }} ..
fi
make -j $(nproc)

- name: run l1-tests
if: ${{ matrix.extra_flags == 'RUN_TESTS' && matrix.build_type == 'Debug' }}
run: |
sudo valgrind --tool=memcheck --leak-check=yes --show-reachable=yes --track-fds=yes --fair-sched=try $GITHUB_WORKSPACE/build/tests/L1_testing/tests/DobbyTest/DobbyL1Test --gtest_output="json:$(pwd)/DobbyL1TestResults.json"
sudo $GITHUB_WORKSPACE/build/tests/L1_testing/tests/DobbyUtilsTest/DobbyUtilsL1Test --gtest_output="json:$(pwd)/DobbyUtilsL1TestResults.json"
sudo valgrind --tool=memcheck --leak-check=yes --show-reachable=yes --track-fds=yes --fair-sched=try $GITHUB_WORKSPACE/build/tests/L1_testing/tests/DobbyManagerTest/DobbyManagerL1Test --gtest_output="json:$(pwd)/DobbyManagerL1TestResults.json"
sudo valgrind --tool=memcheck --leak-check=yes --show-reachable=yes --track-fds=yes --fair-sched=try $GITHUB_WORKSPACE/build/tests/L1_testing/tests/DobbySpecConfigTest/DobbySpecConfigL1Test --gtest_output="json:$(pwd)/DobbySpecConfigL1TestResults.json"

- name: Generate coverage
if: ${{ matrix.coverage == 'with-coverage' && matrix.extra_flags == 'RUN_TESTS' && matrix.build_type == 'Debug' }}
run: >
lcov
--rc geninfo_unexecuted_blocks=1
--ignore-errors source
--ignore-errors mismatch
--ignore-errors mismatch
-c
-o coverage.info
-d $GITHUB_WORKSPACE
Expand All @@ -108,5 +119,6 @@ jobs:
DobbyL1TestResults.json
DobbyUtilsL1TestResults.json
DobbyManagerL1TestResults.json
DobbySpecConfigL1TestResults.json
coverage
if-no-files-found: warn
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,51 @@ Usage: DobbyBundleGenerator <option(s)>
-o, --outputDirectory=PATH Where to save the generated OCI bundle
```

## Dobby Spec Format
When using `DobbyDaemon` or `DobbyBundleGenerator`, containers are described using a Dobby-specific JSON spec file. Example specs can be found in `tests/L2_testing/dobby_specs/`.

The table below lists the supported top-level fields. Fields marked **mandatory** must always be present.

| Field | Type | Mandatory | Description |
|-------|------|-----------|-------------|
| `version` | string | Yes | Spec version. Currently `"1.0"` or `"1.1"`. |
| `args` | array | Yes | Command and arguments to run inside the container. |
| `user` | object | Yes | `uid` and `gid` the container process runs as. |
| `memLimit` | integer | Yes | Memory limit in bytes (`memory.limit_in_bytes`). Values below 256 KiB are accepted but will only generate a warning and may not be effective. |
| `swapLimit` | integer | No | Swap+memory limit in bytes (`memory.memsw.limit_in_bytes`). Must be ≥ `memLimit`. Defaults to `memLimit` (no extra swap). |
| `env` | array | No | Environment variables in `"KEY=VALUE"` format. |
Comment thread
ks734 marked this conversation as resolved.
| `cwd` | string | No | Working directory inside the container. |
| `console` | object | No | Console log settings: `path` and `limit` (bytes). |
| `etc` | object | No | Inline `/etc` file content (`passwd`, `group`, `hosts`, `services`, `ld.so.preload`). |
| `network` | string | No | Network mode: `"nat"`, `"open"`, or `"private"`. Defaults to `"private"`. |
| `mounts` | array | No | Additional bind-mounts into the container. |
| `cpu` | object | No | CPU cgroup settings: `shares` (percentage 1–100) and `cores` (bitmask string). |
| `rtPriority` | object | No | Real-time scheduling priority settings. |
| `userNs` | boolean | No | Enable user namespacing. Defaults to `true`. |
| `gpu` | object | No | GPU device node access settings. |
| `vpu` | object | No | VPU device node access settings. |
| `devices` | array | No | Additional device nodes to whitelist. |
| `capabilities` | array | No | Linux capabilities to grant the container. |
| `seccomp` | object | No | Seccomp syscall filter profile. |
| `syslog` | object | No | Syslog plugin configuration. |
| `dbus` | object | No | D-Bus access configuration. |
| `restartOnCrash` | boolean | No | Restart the container automatically if it crashes. |
| `plugins` | object | No | Legacy plugin configuration (prefer `rdkPlugins`). |

### Memory configuration example

```json
{
"version": "1.0",
"args": [ "/usr/bin/myapp" ],
"user": { "uid": 1000, "gid": 1000 },
"memLimit": 67108864,
"swapLimit": 134217728
}
```

`swapLimit` sets the combined memory+swap ceiling enforced by the kernel cgroup (`memory.memsw.limit_in_bytes`). When omitted, swap is capped at the same value as `memLimit`, effectively disabling extra swap for the container.

## DobbyTool
This is a simple command line tool that is used for debugging purporses. It connects to the Dobby daemon over dbus and allows for debugging and testing containers.

Expand Down
1 change: 1 addition & 0 deletions bundle/lib/include/DobbySpecConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ class DobbySpecConfig : public DobbyConfig
JSON_FIELD_PROCESSOR(processMounts);
JSON_FIELD_PROCESSOR(processLegacyPlugins);
JSON_FIELD_PROCESSOR(processMemLimit);
JSON_FIELD_PROCESSOR(processSwapLimit);
JSON_FIELD_PROCESSOR(processGpu);
JSON_FIELD_PROCESSOR(processVpu);
JSON_FIELD_PROCESSOR(processDbus);
Expand Down
90 changes: 89 additions & 1 deletion bundle/lib/source/DobbySpecConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <array>
#include <atomic>
#include <algorithm>
#include <cinttypes>
#include <grp.h>
#include <fcntl.h>
#include <limits.h>
Expand Down Expand Up @@ -62,6 +63,8 @@ static const ctemplate::StaticTemplateString USERNS_DISABLED =

static const ctemplate::StaticTemplateString MEM_LIMIT =
STS_INIT(MEM_LIMIT, "MEM_LIMIT");
static const ctemplate::StaticTemplateString MEM_SWAP =
STS_INIT(MEM_SWAP, "MEM_SWAP");

static const ctemplate::StaticTemplateString CPU_SHARES_ENABLED =
STS_INIT(CPU_SHARES_ENABLED, "CPU_SHARES_ENABLED");
Expand Down Expand Up @@ -187,6 +190,7 @@ static const ctemplate::StaticTemplateString SECCOMP_SYSCALLS =
#define JSON_FLAG_FILECAPABILITIES (0x1U << 20)
#define JSON_FLAG_VPU (0x1U << 21)
#define JSON_FLAG_SECCOMP (0x1U << 22)
#define JSON_FLAG_SWAPLIMIT (0x1U << 23)

int DobbySpecConfig::mNumCores = -1;

Expand Down Expand Up @@ -504,7 +508,8 @@ bool DobbySpecConfig::parseSpec(ctemplate::TemplateDictionary* dictionary,
{ "cpu", { JSON_FLAG_CPU, &DobbySpecConfig::processCpu } },
{ "devices", { JSON_FLAG_DEVICES, &DobbySpecConfig::processDevices } },
{ "capabilities", { JSON_FLAG_CAPABILITIES, &DobbySpecConfig::processCapabilities } },
{ "seccomp", { JSON_FLAG_SECCOMP, &DobbySpecConfig::processSeccomp } }
{ "seccomp", { JSON_FLAG_SECCOMP, &DobbySpecConfig::processSeccomp } },
{ "swapLimit", { JSON_FLAG_SWAPLIMIT, &DobbySpecConfig::processSwapLimit } }
};

// step 1 - parse the 'dobby' spec document
Expand Down Expand Up @@ -627,6 +632,16 @@ bool DobbySpecConfig::parseSpec(ctemplate::TemplateDictionary* dictionary,
dictionary->SetIntValue(RLIMIT_RTPRIO, 0);
}

if (!(flags & JSON_FLAG_SWAPLIMIT))
{
// swapLimit not supplied: default swap to memLimit (no extra swap)
const Json::Value& memLimitVal = mSpec["memLimit"];
if (memLimitVal.isIntegral())
{
dictionary->SetIntValue(MEM_SWAP, memLimitVal.asUInt());
}
}

if (!(flags & JSON_FLAG_CAPABILITIES))
{
dictionary->SetValue(NO_NEW_PRIVS, "true");
Expand Down Expand Up @@ -1279,6 +1294,79 @@ bool DobbySpecConfig::processMemLimit(const Json::Value& value,
return true;
}

// -----------------------------------------------------------------------------
/**
* @brief Processes the optional swap limit field.
*
* When present, this value is used as the cgroup memory.memsw.limit_in_bytes,
* allowing swap to be configured independently of the memory limit. When
* absent the swap limit defaults to the same value as memLimit (i.e. no
* extra swap beyond the memory limit).
*
* The kernel requires swap >= memLimit, so an error is returned if the
* supplied value is smaller than the memLimit already set.
*
* Example json:
*
* "swapLimit": 2097152
*
*
*
* @param[in] value The json spec document from the client
* @param[in] dictionary Pointer to the OCI dictionary to populate
*
* @return true if correctly processed the value, otherwise false.
*/
bool DobbySpecConfig::processSwapLimit(const Json::Value& value,
ctemplate::TemplateDictionary* dictionary)
{
// Reject non-numeric values up front.
if (!value.isIntegral())
{
AI_LOG_ERROR("invalid swapLimit field");
return false;
}

// JsonCpp's isIntegral() returns true for negative integers too. A
// negative value would silently wrap to a huge unsigned number and bypass
// the swap >= memLimit guard, so we must check sign before casting.
const int64_t memSwapSigned = value.asInt64();
if (memSwapSigned < 0)
{
AI_LOG_ERROR("swapLimit must be non-negative, got %" PRId64, memSwapSigned);
return false;
Comment thread
ks734 marked this conversation as resolved.
}

// The kernel requires memory.memsw.limit_in_bytes >= memory.limit_in_bytes.
const Json::Value& memLimitVal = mSpec["memLimit"];
if (memLimitVal.isIntegral())
{
const int64_t memLimitSigned = memLimitVal.asInt64();
if (memLimitSigned < 0)
{
AI_LOG_ERROR("memLimit is negative; cannot validate swapLimit");
return false;
}
if (memSwapSigned < memLimitSigned)
{
AI_LOG_ERROR("swapLimit (%" PRId64 ") must be >= memLimit (%" PRId64 ")",
memSwapSigned, memLimitSigned);
return false;
}
}

Comment thread
ks734 marked this conversation as resolved.
if (memSwapSigned > static_cast<int64_t>(UINT_MAX))
{
AI_LOG_ERROR("swapLimit (%" PRId64 ") exceeds maximum supported value for template field (%u)",
memSwapSigned, UINT_MAX);
return false;
}

dictionary->SetIntValue(MEM_SWAP, static_cast<unsigned>(memSwapSigned));

return true;
}

// -----------------------------------------------------------------------------
/**
* @brief Adds the GPU device nodes (if any) to supplied dictionary.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ static const char* ociJsonTemplate = R"JSON(
],
"memory": {
"limit": {{MEM_LIMIT}},
"swap": {{MEM_LIMIT}},
"swap": {{MEM_SWAP}},
"swappiness": 60
},
"cpu": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ static const char* ociJsonTemplate = R"JSON(
],
"memory": {
"limit": {{MEM_LIMIT}},
"swap": {{MEM_LIMIT}},
"swap": {{MEM_SWAP}},
"swappiness": 60
},
"cpu": {
Expand Down
4 changes: 4 additions & 0 deletions tests/L1_testing/mocks/DobbyBundle.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class DobbyBundleImpl {
virtual ~DobbyBundleImpl() = default;

virtual void setPersistence(bool persist) = 0;
virtual bool getPersistence() const = 0;
virtual int dirFd() const = 0;
virtual bool isValid() const = 0;
virtual const std::string& path() const = 0;

Expand Down Expand Up @@ -58,6 +60,8 @@ class DobbyBundle {

static void setImpl(DobbyBundleImpl* newImpl);
void setPersistence(bool persist);
bool getPersistence() const;
int dirFd() const;
bool isValid() const;
};

Expand Down
11 changes: 11 additions & 0 deletions tests/L1_testing/mocks/DobbyBundleMock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,14 @@ const std::string& DobbyBundle::path() const
return impl->path();
}

bool DobbyBundle::getPersistence() const
{
EXPECT_NE(impl, nullptr);
return impl->getPersistence();
}

int DobbyBundle::dirFd() const
{
EXPECT_NE(impl, nullptr);
return impl->dirFd();
}
2 changes: 2 additions & 0 deletions tests/L1_testing/mocks/DobbyBundleMock.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class DobbyBundleMock : public DobbyBundleImpl {
virtual ~DobbyBundleMock() = default;

MOCK_METHOD(void, setPersistence, (bool persist), (override));
MOCK_METHOD(bool, getPersistence, (), (const,override));
MOCK_METHOD(int, dirFd, (), (const,override));
MOCK_METHOD(bool, isValid, (), (const,override));
MOCK_METHOD((const std::string&), path, (), (const,override));
};
4 changes: 3 additions & 1 deletion tests/L1_testing/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@

add_subdirectory(DobbyUtilsTest)
add_subdirectory(DobbyTest)
add_subdirectory(DobbyManagerTest)
add_subdirectory(DobbyManagerTest)
add_subdirectory(DobbySpecConfigTest)

Loading
Loading