177 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			177 lines
		
	
	
		
			5.4 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/containerd/cgroups/v3/cgroup1"
 | |
| 	"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 := cgroup1.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
 | |
| 	}
 | |
| }
 | 
