176 lines
5.3 KiB
Go
176 lines
5.3 KiB
Go
/*
|
|
Copyright The containerd Authors.
|
|
|
|
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 opts
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
|
|
"github.com/container-orchestrated-devices/container-device-interface/pkg/cdi"
|
|
"github.com/containerd/cgroups/v3"
|
|
"github.com/sirupsen/logrus"
|
|
"golang.org/x/sys/unix"
|
|
|
|
"github.com/containerd/containerd/containers"
|
|
"github.com/containerd/containerd/log"
|
|
"github.com/containerd/containerd/oci"
|
|
)
|
|
|
|
// Linux dependent OCI spec opts.
|
|
|
|
var (
|
|
swapControllerAvailability bool
|
|
swapControllerAvailabilityOnce sync.Once
|
|
)
|
|
|
|
// SwapControllerAvailable returns true if the swap controller is available
|
|
func SwapControllerAvailable() bool {
|
|
swapControllerAvailabilityOnce.Do(func() {
|
|
const warn = "Failed to detect the availability of the swap controller, assuming not available"
|
|
p := "/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes"
|
|
if cgroups.Mode() == cgroups.Unified {
|
|
// memory.swap.max does not exist in the cgroup root, so we check /sys/fs/cgroup/<SELF>/memory.swap.max
|
|
_, unified, err := cgroups.ParseCgroupFileUnified("/proc/self/cgroup")
|
|
if err != nil {
|
|
err = fmt.Errorf("failed to parse /proc/self/cgroup: %w", err)
|
|
logrus.WithError(err).Warn(warn)
|
|
return
|
|
}
|
|
p = filepath.Join("/sys/fs/cgroup", unified, "memory.swap.max")
|
|
}
|
|
if _, err := os.Stat(p); err != nil {
|
|
if !errors.Is(err, os.ErrNotExist) {
|
|
logrus.WithError(err).Warn(warn)
|
|
}
|
|
return
|
|
}
|
|
swapControllerAvailability = true
|
|
})
|
|
return swapControllerAvailability
|
|
}
|
|
|
|
var (
|
|
supportsHugetlbOnce sync.Once
|
|
supportsHugetlb bool
|
|
)
|
|
|
|
func isHugetlbControllerPresent() bool {
|
|
supportsHugetlbOnce.Do(func() {
|
|
supportsHugetlb = false
|
|
if IsCgroup2UnifiedMode() {
|
|
supportsHugetlb, _ = cgroupv2HasHugetlb()
|
|
} else {
|
|
supportsHugetlb, _ = cgroupv1HasHugetlb()
|
|
}
|
|
})
|
|
return supportsHugetlb
|
|
}
|
|
|
|
var (
|
|
_cgroupv1HasHugetlbOnce sync.Once
|
|
_cgroupv1HasHugetlb bool
|
|
_cgroupv1HasHugetlbErr error
|
|
_cgroupv2HasHugetlbOnce sync.Once
|
|
_cgroupv2HasHugetlb bool
|
|
_cgroupv2HasHugetlbErr error
|
|
isUnifiedOnce sync.Once
|
|
isUnified bool
|
|
)
|
|
|
|
// cgroupv1HasHugetlb returns whether the hugetlb controller is present on
|
|
// cgroup v1.
|
|
func cgroupv1HasHugetlb() (bool, error) {
|
|
_cgroupv1HasHugetlbOnce.Do(func() {
|
|
if _, err := os.ReadDir("/sys/fs/cgroup/hugetlb"); err != nil {
|
|
_cgroupv1HasHugetlbErr = fmt.Errorf("readdir /sys/fs/cgroup/hugetlb: %w", err)
|
|
_cgroupv1HasHugetlb = false
|
|
} else {
|
|
_cgroupv1HasHugetlbErr = nil
|
|
_cgroupv1HasHugetlb = true
|
|
}
|
|
})
|
|
return _cgroupv1HasHugetlb, _cgroupv1HasHugetlbErr
|
|
}
|
|
|
|
// cgroupv2HasHugetlb returns whether the hugetlb controller is present on
|
|
// cgroup v2.
|
|
func cgroupv2HasHugetlb() (bool, error) {
|
|
_cgroupv2HasHugetlbOnce.Do(func() {
|
|
controllers, err := os.ReadFile("/sys/fs/cgroup/cgroup.controllers")
|
|
if err != nil {
|
|
_cgroupv2HasHugetlbErr = fmt.Errorf("read /sys/fs/cgroup/cgroup.controllers: %w", err)
|
|
return
|
|
}
|
|
_cgroupv2HasHugetlb = strings.Contains(string(controllers), "hugetlb")
|
|
})
|
|
return _cgroupv2HasHugetlb, _cgroupv2HasHugetlbErr
|
|
}
|
|
|
|
// IsCgroup2UnifiedMode returns whether we are running in cgroup v2 unified mode.
|
|
func IsCgroup2UnifiedMode() bool {
|
|
isUnifiedOnce.Do(func() {
|
|
var st syscall.Statfs_t
|
|
if err := syscall.Statfs("/sys/fs/cgroup", &st); err != nil {
|
|
panic("cannot statfs cgroup root")
|
|
}
|
|
isUnified = st.Type == unix.CGROUP2_SUPER_MAGIC
|
|
})
|
|
return isUnified
|
|
}
|
|
|
|
// WithCDI updates OCI spec with CDI content
|
|
func WithCDI(annotations map[string]string) oci.SpecOpts {
|
|
return func(ctx context.Context, _ oci.Client, c *containers.Container, s *oci.Spec) error {
|
|
// TODO: Once CRI is extended with native CDI support this will need to be updated...
|
|
_, cdiDevices, err := cdi.ParseAnnotations(annotations)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse CDI device annotations: %w", err)
|
|
}
|
|
if cdiDevices == nil {
|
|
return nil
|
|
}
|
|
|
|
log.G(ctx).Infof("container %v: CDI devices: %v", c.ID, cdiDevices)
|
|
|
|
registry := cdi.GetRegistry()
|
|
if err = registry.Refresh(); err != nil {
|
|
// We don't consider registry refresh failure a fatal error.
|
|
// For instance, a dynamically generated invalid CDI Spec file for
|
|
// any particular vendor shouldn't prevent injection of devices of
|
|
// different vendors. CDI itself knows better and it will fail the
|
|
// injection if necessary.
|
|
log.G(ctx).Warnf("CDI registry refresh failed: %v", err)
|
|
}
|
|
|
|
if _, err := registry.InjectDevices(s, cdiDevices...); err != nil {
|
|
return fmt.Errorf("CDI device injection failed: %w", err)
|
|
}
|
|
|
|
// One crucial thing to keep in mind is that CDI device injection
|
|
// might add OCI Spec environment variables, hooks, and mounts as
|
|
// well. Therefore it is important that none of the corresponding
|
|
// OCI Spec fields are reset up in the call stack once we return.
|
|
return nil
|
|
}
|
|
}
|