178 lines
		
	
	
		
			5.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			178 lines
		
	
	
		
			5.2 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/containerd/cgroups/v3"
 | |
| 	"golang.org/x/sys/unix"
 | |
| 	runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
 | |
| 	"tags.cncf.io/container-device-interface/pkg/cdi"
 | |
| 
 | |
| 	"github.com/containerd/containerd/v2/core/containers"
 | |
| 	"github.com/containerd/containerd/v2/pkg/oci"
 | |
| 	"github.com/containerd/log"
 | |
| )
 | |
| 
 | |
| // 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)
 | |
| 				log.L.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) {
 | |
| 				log.L.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
 | |
| 	_cgroupv2HasHugetlbOnce sync.Once
 | |
| 	_cgroupv2HasHugetlb     bool
 | |
| 	isUnifiedOnce           sync.Once
 | |
| 	isUnified               bool
 | |
| )
 | |
| 
 | |
| // cgroupv1HasHugetlb returns whether the hugetlb controller is present on
 | |
| // cgroup v1.
 | |
| func cgroupv1HasHugetlb() bool {
 | |
| 	_cgroupv1HasHugetlbOnce.Do(func() {
 | |
| 		if _, err := os.ReadDir("/sys/fs/cgroup/hugetlb"); err != nil {
 | |
| 			_cgroupv1HasHugetlb = false
 | |
| 		} else {
 | |
| 			_cgroupv1HasHugetlb = true
 | |
| 		}
 | |
| 	})
 | |
| 	return _cgroupv1HasHugetlb
 | |
| }
 | |
| 
 | |
| // cgroupv2HasHugetlb returns whether the hugetlb controller is present on
 | |
| // cgroup v2.
 | |
| func cgroupv2HasHugetlb() bool {
 | |
| 	_cgroupv2HasHugetlbOnce.Do(func() {
 | |
| 		controllers, err := os.ReadFile("/sys/fs/cgroup/cgroup.controllers")
 | |
| 		if err != nil {
 | |
| 			return
 | |
| 		}
 | |
| 		_cgroupv2HasHugetlb = strings.Contains(string(controllers), "hugetlb")
 | |
| 	})
 | |
| 	return _cgroupv2HasHugetlb
 | |
| }
 | |
| 
 | |
| // 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, CDIDevices []*runtime.CDIDevice) oci.SpecOpts {
 | |
| 	return func(ctx context.Context, client oci.Client, c *containers.Container, s *oci.Spec) error {
 | |
| 		seen := make(map[string]bool)
 | |
| 		// Add devices from CDIDevices CRI field
 | |
| 		var devices []string
 | |
| 		var err error
 | |
| 		for _, device := range CDIDevices {
 | |
| 			deviceName := device.Name
 | |
| 			if seen[deviceName] {
 | |
| 				log.G(ctx).Debugf("Skipping duplicated CDI device %s", deviceName)
 | |
| 				continue
 | |
| 			}
 | |
| 			devices = append(devices, deviceName)
 | |
| 			seen[deviceName] = true
 | |
| 		}
 | |
| 		log.G(ctx).Infof("Container %v: CDI devices from CRI Config.CDIDevices: %v", c.ID, devices)
 | |
| 
 | |
| 		// Add devices from CDI annotations
 | |
| 		_, devsFromAnnotations, err := cdi.ParseAnnotations(annotations)
 | |
| 		if err != nil {
 | |
| 			return fmt.Errorf("failed to parse CDI device annotations: %w", err)
 | |
| 		}
 | |
| 
 | |
| 		if devsFromAnnotations != nil {
 | |
| 			log.G(ctx).Infof("Container %v: CDI devices from annotations: %v", c.ID, devsFromAnnotations)
 | |
| 			for _, deviceName := range devsFromAnnotations {
 | |
| 				if seen[deviceName] {
 | |
| 					// TODO: change to Warning when passing CDI devices as annotations is deprecated
 | |
| 					log.G(ctx).Debugf("Skipping duplicated CDI device %s", deviceName)
 | |
| 					continue
 | |
| 				}
 | |
| 				devices = append(devices, deviceName)
 | |
| 				seen[deviceName] = true
 | |
| 			}
 | |
| 			// TODO: change to Warning when passing CDI devices as annotations is deprecated
 | |
| 			log.G(ctx).Debug("Passing CDI devices as annotations will be deprecated soon, please use CRI CDIDevices instead")
 | |
| 		}
 | |
| 
 | |
| 		return oci.WithCDIDevices(devices...)(ctx, client, c, s)
 | |
| 	}
 | |
| }
 | 
