diff --git a/.github/linters/urunc-dict.txt b/.github/linters/urunc-dict.txt index 538649e0..27950462 100644 --- a/.github/linters/urunc-dict.txt +++ b/.github/linters/urunc-dict.txt @@ -405,4 +405,6 @@ Logr onsi ESRCH Prafful -praffq \ No newline at end of file +praffq +rlimits +Rlimits diff --git a/pkg/unikontainers/types/types.go b/pkg/unikontainers/types/types.go index 9daa8c0f..15fb8580 100644 --- a/pkg/unikontainers/types/types.go +++ b/pkg/unikontainers/types/types.go @@ -15,6 +15,8 @@ //revive:disable:var-naming package types +import specs "github.com/opencontainers/runtime-spec/specs-go" + type Unikernel interface { Init(UnikernelParams) error CommandString() (string, error) @@ -71,9 +73,10 @@ type RootfsParams struct { // Specific to Linux type ProcessConfig struct { - UID uint32 // The uid of the process inside the guest - GID uint32 // The gid of the process inside the guest - WorkDir string // The workdir of the process inside the guest + UID uint32 // The uid of the process inside the guest + GID uint32 // The gid of the process inside the guest + WorkDir string // The workdir of the process inside the guest + Rlimits []specs.POSIXRlimit // The rlimits for the process inside the guest } // UnikernelParams holds the data required to build the unikernels commandline diff --git a/pkg/unikontainers/unikernels/linux.go b/pkg/unikontainers/unikernels/linux.go index d6bb8095..53bb5178 100644 --- a/pkg/unikontainers/unikernels/linux.go +++ b/pkg/unikontainers/unikernels/linux.go @@ -314,6 +314,15 @@ func (l *Linux) buildUrunitConfig() string { sb.WriteString("WD:") sb.WriteString(l.ProcConfig.WorkDir) sb.WriteString("\n") + for _, rl := range l.ProcConfig.Rlimits { + sb.WriteString("RLIMIT:") + sb.WriteString(rl.Type) + sb.WriteString(":") + sb.WriteString(strconv.FormatUint(rl.Soft, 10)) + sb.WriteString(":") + sb.WriteString(strconv.FormatUint(rl.Hard, 10)) + sb.WriteString("\n") + } sb.WriteString(lpcEndMarker) sb.WriteString("\n") sb.WriteString(blkStartMarker) diff --git a/pkg/unikontainers/unikernels/linux_test.go b/pkg/unikontainers/unikernels/linux_test.go new file mode 100644 index 00000000..a5597419 --- /dev/null +++ b/pkg/unikontainers/unikernels/linux_test.go @@ -0,0 +1,113 @@ +// Copyright (c) 2023-2026, Nubificus LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package unikernels + +import ( + "strings" + "testing" + + specs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/stretchr/testify/assert" + "github.com/urunc-dev/urunc/pkg/unikontainers/types" +) + +func newTestLinux(rlimits []specs.POSIXRlimit) *Linux { + return &Linux{ + Env: []string{"PATH=/usr/local/bin"}, + Monitor: "qemu", + ProcConfig: types.ProcessConfig{ + UID: 1000, + GID: 1000, + WorkDir: "/app", + Rlimits: rlimits, + }, + } +} + +func TestBuildUrunitConfigNoRlimits(t *testing.T) { + tests := []struct { + name string + rlimits []specs.POSIXRlimit + }{ + {name: "nil", rlimits: nil}, + {name: "empty", rlimits: []specs.POSIXRlimit{}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + l := newTestLinux(tt.rlimits) + conf := l.buildUrunitConfig() + t.Logf("generated urunit.conf:\n%s", conf) + assert.NotContains(t, conf, "RLIMIT:", "expected no RLIMIT lines") + }) + } +} + +func TestBuildUrunitConfigSingleRlimit(t *testing.T) { + l := newTestLinux([]specs.POSIXRlimit{ + {Type: "RLIMIT_NOFILE", Soft: 1024, Hard: 4096}, + }) + conf := l.buildUrunitConfig() + t.Logf("generated urunit.conf:\n%s", conf) + + assert.Contains(t, conf, "RLIMIT:RLIMIT_NOFILE:1024:4096\n") +} + +func TestBuildUrunitConfigMultipleRlimits(t *testing.T) { + l := newTestLinux([]specs.POSIXRlimit{ + {Type: "RLIMIT_NOFILE", Soft: 1024, Hard: 4096}, + {Type: "RLIMIT_NPROC", Soft: 512, Hard: 1024}, + {Type: "RLIMIT_AS", Soft: 0, Hard: 0}, + }) + conf := l.buildUrunitConfig() + t.Logf("generated urunit.conf:\n%s", conf) + + assert.Contains(t, conf, "RLIMIT:RLIMIT_NOFILE:1024:4096\n") + assert.Contains(t, conf, "RLIMIT:RLIMIT_NPROC:512:1024\n") + assert.Contains(t, conf, "RLIMIT:RLIMIT_AS:0:0\n") +} + +func TestBuildUrunitConfigRlimitsInsideProcessBlock(t *testing.T) { + l := newTestLinux([]specs.POSIXRlimit{ + {Type: "RLIMIT_NOFILE", Soft: 1024, Hard: 4096}, + }) + conf := l.buildUrunitConfig() + t.Logf("generated urunit.conf:\n%s", conf) + + ucs := strings.Index(conf, "UCS\n") + uce := strings.Index(conf, "UCE\n") + if ucs < 0 || uce < 0 || ucs >= uce { + t.Fatalf("invalid UCS/UCE markers:\n%s", conf) + } + + block := conf[ucs : uce+4] + assert.Contains(t, block, "RLIMIT:RLIMIT_NOFILE:1024:4096\n", "expected RLIMIT line inside process block") +} + +func TestBuildUrunitConfigUIDGIDWorkdir(t *testing.T) { + l := &Linux{ + ProcConfig: types.ProcessConfig{ + UID: 500, + GID: 501, + WorkDir: "/workdir", + }, + } + conf := l.buildUrunitConfig() + t.Logf("generated urunit.conf:\n%s", conf) + + assert.Contains(t, conf, "UID:500\n") + assert.Contains(t, conf, "GID:501\n") + assert.Contains(t, conf, "WD:/workdir\n") +} diff --git a/pkg/unikontainers/unikontainers.go b/pkg/unikontainers/unikontainers.go index d63809bd..037868e1 100644 --- a/pkg/unikontainers/unikontainers.go +++ b/pkg/unikontainers/unikontainers.go @@ -286,6 +286,7 @@ func (u *Unikontainer) Exec(metrics m.Writer) error { UID: u.Spec.Process.User.UID, GID: u.Spec.Process.User.GID, WorkDir: u.Spec.Process.Cwd, + Rlimits: u.Spec.Process.Rlimits, } // UnikernelParams // populate unikernel params