gitee.com/leisunstar/runtime@v0.0.0-20200521203717-5cef3e7b53f9/virtcontainers/pkg/cgroups/manager.go (about)

     1  // Copyright (c) 2020 Intel Corporation
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  //
     5  
     6  package cgroups
     7  
     8  import (
     9  	"bufio"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"os"
    13  	"path/filepath"
    14  	"strconv"
    15  	"strings"
    16  	"sync"
    17  
    18  	"github.com/kata-containers/runtime/virtcontainers/pkg/rootless"
    19  	libcontcgroups "github.com/opencontainers/runc/libcontainer/cgroups"
    20  	libcontcgroupsfs "github.com/opencontainers/runc/libcontainer/cgroups/fs"
    21  	libcontcgroupssystemd "github.com/opencontainers/runc/libcontainer/cgroups/systemd"
    22  	"github.com/opencontainers/runc/libcontainer/configs"
    23  	"github.com/opencontainers/runc/libcontainer/specconv"
    24  	"github.com/opencontainers/runtime-spec/specs-go"
    25  	"github.com/sirupsen/logrus"
    26  )
    27  
    28  type Config struct {
    29  	// Cgroups specifies specific cgroup settings for the various subsystems that the container is
    30  	// placed into to limit the resources the container has available
    31  	// If nil, New() will create one.
    32  	Cgroups *configs.Cgroup
    33  
    34  	// CgroupPaths contains paths to all the cgroups setup for a container. Key is cgroup subsystem name
    35  	// with the value as the path.
    36  	CgroupPaths map[string]string
    37  
    38  	// Resources represents the runtime resource constraints
    39  	Resources specs.LinuxResources
    40  
    41  	// CgroupPath is the OCI spec cgroup path
    42  	CgroupPath string
    43  }
    44  
    45  type Manager struct {
    46  	sync.Mutex
    47  	mgr libcontcgroups.Manager
    48  }
    49  
    50  const (
    51  	// file in the cgroup that contains the pids
    52  	cgroupProcs = "cgroup.procs"
    53  )
    54  
    55  var (
    56  	// If set to true, expects cgroupsPath to be of form "slice:prefix:name", otherwise cgroups creation will fail
    57  	systemdCgroup *bool
    58  
    59  	cgroupsLogger = logrus.WithField("source", "virtcontainers/pkg/cgroups")
    60  )
    61  
    62  func EnableSystemdCgroup() {
    63  	systemd := true
    64  	systemdCgroup = &systemd
    65  }
    66  
    67  func UseSystemdCgroup() bool {
    68  	if systemdCgroup != nil {
    69  		return *systemdCgroup
    70  	}
    71  	return false
    72  }
    73  
    74  // returns the list of devices that a hypervisor may need
    75  func hypervisorDevices() []specs.LinuxDeviceCgroup {
    76  	devices := []specs.LinuxDeviceCgroup{}
    77  
    78  	// Processes running in a device-cgroup are constrained, they have acccess
    79  	// only to the devices listed in the devices.list file.
    80  	// In order to run Virtual Machines and create virtqueues, hypervisors
    81  	// need access to certain character devices in the host, like kvm and vhost-net.
    82  	hypervisorDevices := []string{
    83  		"/dev/kvm",       // To run virtual machines
    84  		"/dev/vhost-net", // To create virtqueues
    85  		"/dev/vfio/vfio", // To access VFIO devices
    86  	}
    87  
    88  	for _, device := range hypervisorDevices {
    89  		ldevice, err := DeviceToLinuxDevice(device)
    90  		if err != nil {
    91  			cgroupsLogger.WithError(err).Warnf("Could not get device information")
    92  			continue
    93  		}
    94  		devices = append(devices, ldevice)
    95  	}
    96  
    97  	return devices
    98  }
    99  
   100  // New creates a new CgroupManager
   101  func New(config *Config) (*Manager, error) {
   102  	var err error
   103  	useSystemdCgroup := UseSystemdCgroup()
   104  
   105  	devices := config.Resources.Devices
   106  	devices = append(devices, hypervisorDevices()...)
   107  	// Do not modify original devices
   108  	config.Resources.Devices = devices
   109  
   110  	newSpec := specs.Spec{
   111  		Linux: &specs.Linux{
   112  			Resources: &config.Resources,
   113  		},
   114  	}
   115  
   116  	rootless := rootless.IsRootless()
   117  
   118  	cgroups := config.Cgroups
   119  	cgroupPaths := config.CgroupPaths
   120  
   121  	// Create a new cgroup if the current one is nil
   122  	// this cgroups must be saved later
   123  	if cgroups == nil {
   124  		if config.CgroupPath == "" && !rootless {
   125  			cgroupsLogger.Warn("cgroups have not been created and cgroup path is empty")
   126  		}
   127  
   128  		newSpec.Linux.CgroupsPath, err = ValidCgroupPath(config.CgroupPath, useSystemdCgroup)
   129  		if err != nil {
   130  			return nil, fmt.Errorf("Invalid cgroup path: %v", err)
   131  		}
   132  
   133  		if cgroups, err = specconv.CreateCgroupConfig(&specconv.CreateOpts{
   134  			// cgroup name is taken from spec
   135  			CgroupName:       "",
   136  			UseSystemdCgroup: useSystemdCgroup,
   137  			Spec:             &newSpec,
   138  			RootlessCgroups:  rootless,
   139  		}); err != nil {
   140  			return nil, fmt.Errorf("Could not create cgroup config: %v", err)
   141  		}
   142  	}
   143  
   144  	// Set cgroupPaths to nil when the map is empty, it can and will be
   145  	// populated by `Manager.Apply()` when the runtime or any other process
   146  	// is moved to the cgroup.
   147  	if len(cgroupPaths) == 0 {
   148  		cgroupPaths = nil
   149  	}
   150  
   151  	if useSystemdCgroup {
   152  		systemdCgroupFunc, err := libcontcgroupssystemd.NewSystemdCgroupsManager()
   153  		if err != nil {
   154  			return nil, fmt.Errorf("Could not create systemd cgroup manager: %v", err)
   155  		}
   156  		libcontcgroupssystemd.UseSystemd()
   157  		return &Manager{
   158  			mgr: systemdCgroupFunc(cgroups, cgroupPaths),
   159  		}, nil
   160  	}
   161  
   162  	return &Manager{
   163  		mgr: &libcontcgroupsfs.Manager{
   164  			Cgroups:  cgroups,
   165  			Rootless: rootless,
   166  			Paths:    cgroupPaths,
   167  		},
   168  	}, nil
   169  }
   170  
   171  // read all the pids in cgroupPath
   172  func readPids(cgroupPath string) ([]int, error) {
   173  	pids := []int{}
   174  	f, err := os.Open(filepath.Join(cgroupPath, cgroupProcs))
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  	defer f.Close()
   179  	buf := bufio.NewScanner(f)
   180  
   181  	for buf.Scan() {
   182  		if t := buf.Text(); t != "" {
   183  			pid, err := strconv.Atoi(t)
   184  			if err != nil {
   185  				return nil, err
   186  			}
   187  			pids = append(pids, pid)
   188  		}
   189  	}
   190  	return pids, nil
   191  }
   192  
   193  // write the pids into cgroup.procs
   194  func writePids(pids []int, cgroupPath string) error {
   195  	cgroupProcsPath := filepath.Join(cgroupPath, cgroupProcs)
   196  	for _, pid := range pids {
   197  		if err := ioutil.WriteFile(cgroupProcsPath,
   198  			[]byte(strconv.Itoa(pid)),
   199  			os.FileMode(0),
   200  		); err != nil {
   201  			return err
   202  		}
   203  	}
   204  	return nil
   205  }
   206  
   207  func (m *Manager) logger() *logrus.Entry {
   208  	return cgroupsLogger.WithField("source", "cgroup-manager")
   209  }
   210  
   211  // move all the processes in the current cgroup to the parent
   212  func (m *Manager) moveToParent() error {
   213  	m.Lock()
   214  	defer m.Unlock()
   215  	for _, cgroupPath := range m.mgr.GetPaths() {
   216  		pids, err := readPids(cgroupPath)
   217  		if err != nil {
   218  			return err
   219  		}
   220  
   221  		if len(pids) == 0 {
   222  			// no pids in this cgroup
   223  			continue
   224  		}
   225  
   226  		cgroupParentPath := filepath.Dir(filepath.Clean(cgroupPath))
   227  		if err = writePids(pids, cgroupParentPath); err != nil {
   228  			if !strings.Contains(err.Error(), "no such process") {
   229  				return err
   230  			}
   231  		}
   232  	}
   233  	return nil
   234  }
   235  
   236  // Add pid to cgroups
   237  func (m *Manager) Add(pid int) error {
   238  	if rootless.IsRootless() {
   239  		m.logger().Debug("Unable to setup add pids to cgroup: running rootless")
   240  		return nil
   241  	}
   242  
   243  	m.Lock()
   244  	defer m.Unlock()
   245  	return m.mgr.Apply(pid)
   246  }
   247  
   248  // Apply constraints
   249  func (m *Manager) Apply() error {
   250  	if rootless.IsRootless() {
   251  		m.logger().Debug("Unable to apply constraints: running rootless")
   252  		return nil
   253  	}
   254  
   255  	cgroups, err := m.GetCgroups()
   256  	if err != nil {
   257  		return err
   258  	}
   259  
   260  	m.Lock()
   261  	defer m.Unlock()
   262  	return m.mgr.Set(&configs.Config{
   263  		Cgroups: cgroups,
   264  	})
   265  }
   266  
   267  func (m *Manager) GetCgroups() (*configs.Cgroup, error) {
   268  	m.Lock()
   269  	defer m.Unlock()
   270  	return m.mgr.GetCgroups()
   271  }
   272  
   273  func (m *Manager) GetPaths() map[string]string {
   274  	m.Lock()
   275  	defer m.Unlock()
   276  	return m.mgr.GetPaths()
   277  }
   278  
   279  func (m *Manager) Destroy() error {
   280  	// cgroup can't be destroyed if it contains running processes
   281  	if err := m.moveToParent(); err != nil {
   282  		return fmt.Errorf("Could not move processes into parent cgroup: %v", err)
   283  	}
   284  
   285  	m.Lock()
   286  	defer m.Unlock()
   287  	return m.mgr.Destroy()
   288  }
   289  
   290  // AddDevice adds a device to the device cgroup
   291  func (m *Manager) AddDevice(device string) error {
   292  	cgroups, err := m.GetCgroups()
   293  	if err != nil {
   294  		return err
   295  	}
   296  
   297  	ld, err := DeviceToCgroupDevice(device)
   298  	if err != nil {
   299  		return err
   300  	}
   301  
   302  	m.Lock()
   303  	cgroups.Devices = append(cgroups.Devices, ld)
   304  	m.Unlock()
   305  
   306  	return m.Apply()
   307  }
   308  
   309  // RemoceDevice removed a device from the device cgroup
   310  func (m *Manager) RemoveDevice(device string) error {
   311  	cgroups, err := m.GetCgroups()
   312  	if err != nil {
   313  		return err
   314  	}
   315  
   316  	m.Lock()
   317  	for i, d := range cgroups.Devices {
   318  		if d.Path == device {
   319  			cgroups.Devices = append(cgroups.Devices[:i], cgroups.Devices[i+1:]...)
   320  			m.Unlock()
   321  			return m.Apply()
   322  		}
   323  	}
   324  	m.Unlock()
   325  	return fmt.Errorf("device %v not found in the cgroup", device)
   326  }