github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/pkg/cgroups/cgroups.go (about)

     1  package cgroups
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"math"
     8  	"os"
     9  	"path/filepath"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/containers/libpod/pkg/rootless"
    14  	systemdDbus "github.com/coreos/go-systemd/v22/dbus"
    15  	"github.com/godbus/dbus/v5"
    16  	spec "github.com/opencontainers/runtime-spec/specs-go"
    17  	"github.com/pkg/errors"
    18  	"github.com/sirupsen/logrus"
    19  )
    20  
    21  var (
    22  	// ErrCgroupDeleted means the cgroup was deleted
    23  	ErrCgroupDeleted = errors.New("cgroup deleted")
    24  	// ErrCgroupV1Rootless means the cgroup v1 were attempted to be used in rootless environmen
    25  	ErrCgroupV1Rootless = errors.New("no support for CGroups V1 in rootless environments")
    26  )
    27  
    28  // CgroupControl controls a cgroup hierarchy
    29  type CgroupControl struct {
    30  	cgroup2 bool
    31  	path    string
    32  	systemd bool
    33  	// List of additional cgroup subsystems joined that
    34  	// do not have a custom handler.
    35  	additionalControllers []controller
    36  }
    37  
    38  // CPUUsage keeps stats for the CPU usage (unit: nanoseconds)
    39  type CPUUsage struct {
    40  	Kernel uint64
    41  	Total  uint64
    42  	PerCPU []uint64
    43  }
    44  
    45  // MemoryUsage keeps stats for the memory usage
    46  type MemoryUsage struct {
    47  	Usage uint64
    48  	Limit uint64
    49  }
    50  
    51  // CPUMetrics keeps stats for the CPU usage
    52  type CPUMetrics struct {
    53  	Usage CPUUsage
    54  }
    55  
    56  // BlkIOEntry describes an entry in the blkio stats
    57  type BlkIOEntry struct {
    58  	Op    string
    59  	Major uint64
    60  	Minor uint64
    61  	Value uint64
    62  }
    63  
    64  // BlkioMetrics keeps usage stats for the blkio cgroup controller
    65  type BlkioMetrics struct {
    66  	IoServiceBytesRecursive []BlkIOEntry
    67  }
    68  
    69  // MemoryMetrics keeps usage stats for the memory cgroup controller
    70  type MemoryMetrics struct {
    71  	Usage MemoryUsage
    72  }
    73  
    74  // PidsMetrics keeps usage stats for the pids cgroup controller
    75  type PidsMetrics struct {
    76  	Current uint64
    77  }
    78  
    79  // Metrics keeps usage stats for the cgroup controllers
    80  type Metrics struct {
    81  	CPU    CPUMetrics
    82  	Blkio  BlkioMetrics
    83  	Memory MemoryMetrics
    84  	Pids   PidsMetrics
    85  }
    86  
    87  type controller struct {
    88  	name    string
    89  	symlink bool
    90  }
    91  
    92  type controllerHandler interface {
    93  	Create(*CgroupControl) (bool, error)
    94  	Apply(*CgroupControl, *spec.LinuxResources) error
    95  	Destroy(*CgroupControl) error
    96  	Stat(*CgroupControl, *Metrics) error
    97  }
    98  
    99  const (
   100  	cgroupRoot = "/sys/fs/cgroup"
   101  	// CPU is the cpu controller
   102  	CPU = "cpu"
   103  	// CPUAcct is the cpuacct controller
   104  	CPUAcct = "cpuacct"
   105  	// CPUset is the cpuset controller
   106  	CPUset = "cpuset"
   107  	// Memory is the memory controller
   108  	Memory = "memory"
   109  	// Pids is the pids controller
   110  	Pids = "pids"
   111  	// Blkio is the blkio controller
   112  	Blkio = "blkio"
   113  )
   114  
   115  var handlers map[string]controllerHandler
   116  
   117  func init() {
   118  	handlers = make(map[string]controllerHandler)
   119  	handlers[CPU] = getCPUHandler()
   120  	handlers[CPUset] = getCpusetHandler()
   121  	handlers[Memory] = getMemoryHandler()
   122  	handlers[Pids] = getPidsHandler()
   123  	handlers[Blkio] = getBlkioHandler()
   124  }
   125  
   126  // getAvailableControllers get the available controllers
   127  func getAvailableControllers(exclude map[string]controllerHandler, cgroup2 bool) ([]controller, error) {
   128  	if cgroup2 {
   129  		return nil, fmt.Errorf("getAvailableControllers not implemented yet for cgroup v2")
   130  	}
   131  
   132  	infos, err := ioutil.ReadDir(cgroupRoot)
   133  	if err != nil {
   134  		return nil, errors.Wrapf(err, "read directory %s", cgroupRoot)
   135  	}
   136  	var controllers []controller
   137  	for _, i := range infos {
   138  		name := i.Name()
   139  		if _, found := exclude[name]; found {
   140  			continue
   141  		}
   142  		c := controller{
   143  			name:    name,
   144  			symlink: !i.IsDir(),
   145  		}
   146  		controllers = append(controllers, c)
   147  	}
   148  	return controllers, nil
   149  }
   150  
   151  // getCgroupv1Path is a helper function to get the cgroup v1 path
   152  func (c *CgroupControl) getCgroupv1Path(name string) string {
   153  	return filepath.Join(cgroupRoot, name, c.path)
   154  }
   155  
   156  // createCgroupv2Path creates the cgroupv2 path and enables all the available controllers
   157  func createCgroupv2Path(path string) (deferredError error) {
   158  	content, err := ioutil.ReadFile("/sys/fs/cgroup/cgroup.controllers")
   159  	if err != nil {
   160  		return errors.Wrapf(err, "read /sys/fs/cgroup/cgroup.controllers")
   161  	}
   162  	if !strings.HasPrefix(path, "/sys/fs/cgroup/") {
   163  		return fmt.Errorf("invalid cgroup path %s", path)
   164  	}
   165  
   166  	res := ""
   167  	for i, c := range strings.Split(strings.TrimSpace(string(content)), " ") {
   168  		if i == 0 {
   169  			res = fmt.Sprintf("+%s", c)
   170  		} else {
   171  			res += fmt.Sprintf(" +%s", c)
   172  		}
   173  	}
   174  	resByte := []byte(res)
   175  
   176  	current := "/sys/fs"
   177  	elements := strings.Split(path, "/")
   178  	for i, e := range elements[3:] {
   179  		current = filepath.Join(current, e)
   180  		if i > 0 {
   181  			if err := os.Mkdir(current, 0755); err != nil {
   182  				if !os.IsExist(err) {
   183  					return errors.Wrapf(err, "mkdir %s", path)
   184  				}
   185  			} else {
   186  				// If the directory was created, be sure it is not left around on errors.
   187  				defer func() {
   188  					if deferredError != nil {
   189  						os.Remove(current)
   190  					}
   191  				}()
   192  			}
   193  		}
   194  		// We enable the controllers for all the path components except the last one.  It is not allowed to add
   195  		// PIDs if there are already enabled controllers.
   196  		if i < len(elements[3:])-1 {
   197  			if err := ioutil.WriteFile(filepath.Join(current, "cgroup.subtree_control"), resByte, 0755); err != nil {
   198  				return errors.Wrapf(err, "write %s", filepath.Join(current, "cgroup.subtree_control"))
   199  			}
   200  		}
   201  	}
   202  	return nil
   203  }
   204  
   205  // initialize initializes the specified hierarchy
   206  func (c *CgroupControl) initialize() (err error) {
   207  	createdSoFar := map[string]controllerHandler{}
   208  	defer func() {
   209  		if err != nil {
   210  			for name, ctr := range createdSoFar {
   211  				if err := ctr.Destroy(c); err != nil {
   212  					logrus.Warningf("error cleaning up controller %s for %s", name, c.path)
   213  				}
   214  			}
   215  		}
   216  	}()
   217  	if c.cgroup2 {
   218  		if err := createCgroupv2Path(filepath.Join(cgroupRoot, c.path)); err != nil {
   219  			return errors.Wrapf(err, "error creating cgroup path %s", c.path)
   220  		}
   221  	}
   222  	for name, handler := range handlers {
   223  		created, err := handler.Create(c)
   224  		if err != nil {
   225  			return err
   226  		}
   227  		if created {
   228  			createdSoFar[name] = handler
   229  		}
   230  	}
   231  
   232  	if !c.cgroup2 {
   233  		// We won't need to do this for cgroup v2
   234  		for _, ctr := range c.additionalControllers {
   235  			if ctr.symlink {
   236  				continue
   237  			}
   238  			path := c.getCgroupv1Path(ctr.name)
   239  			if err := os.MkdirAll(path, 0755); err != nil {
   240  				return errors.Wrapf(err, "error creating cgroup path %s for %s", path, ctr.name)
   241  			}
   242  		}
   243  	}
   244  
   245  	return nil
   246  }
   247  
   248  func (c *CgroupControl) createCgroupDirectory(controller string) (bool, error) {
   249  	cPath := c.getCgroupv1Path(controller)
   250  	_, err := os.Stat(cPath)
   251  	if err == nil {
   252  		return false, nil
   253  	}
   254  
   255  	if !os.IsNotExist(err) {
   256  		return false, err
   257  	}
   258  
   259  	if err := os.MkdirAll(cPath, 0755); err != nil {
   260  		return false, errors.Wrapf(err, "error creating cgroup for %s", controller)
   261  	}
   262  	return true, nil
   263  }
   264  
   265  func readFileAsUint64(path string) (uint64, error) {
   266  	data, err := ioutil.ReadFile(path)
   267  	if err != nil {
   268  		return 0, errors.Wrapf(err, "open %s", path)
   269  	}
   270  	v := cleanString(string(data))
   271  	if v == "max" {
   272  		return math.MaxUint64, nil
   273  	}
   274  	ret, err := strconv.ParseUint(v, 10, 0)
   275  	if err != nil {
   276  		return ret, errors.Wrapf(err, "parse %s from %s", v, path)
   277  	}
   278  	return ret, nil
   279  }
   280  
   281  // New creates a new cgroup control
   282  func New(path string, resources *spec.LinuxResources) (*CgroupControl, error) {
   283  	cgroup2, err := IsCgroup2UnifiedMode()
   284  	if err != nil {
   285  		return nil, err
   286  	}
   287  	control := &CgroupControl{
   288  		cgroup2: cgroup2,
   289  		path:    path,
   290  	}
   291  
   292  	if !cgroup2 {
   293  		controllers, err := getAvailableControllers(handlers, false)
   294  		if err != nil {
   295  			return nil, err
   296  		}
   297  		control.additionalControllers = controllers
   298  	}
   299  
   300  	if err := control.initialize(); err != nil {
   301  		return nil, err
   302  	}
   303  
   304  	return control, nil
   305  }
   306  
   307  // NewSystemd creates a new cgroup control
   308  func NewSystemd(path string) (*CgroupControl, error) {
   309  	cgroup2, err := IsCgroup2UnifiedMode()
   310  	if err != nil {
   311  		return nil, err
   312  	}
   313  	control := &CgroupControl{
   314  		cgroup2: cgroup2,
   315  		path:    path,
   316  		systemd: true,
   317  	}
   318  	return control, nil
   319  }
   320  
   321  // Load loads an existing cgroup control
   322  func Load(path string) (*CgroupControl, error) {
   323  	cgroup2, err := IsCgroup2UnifiedMode()
   324  	if err != nil {
   325  		return nil, err
   326  	}
   327  	control := &CgroupControl{
   328  		cgroup2: cgroup2,
   329  		path:    path,
   330  		systemd: false,
   331  	}
   332  	if !cgroup2 {
   333  		controllers, err := getAvailableControllers(handlers, false)
   334  		if err != nil {
   335  			return nil, err
   336  		}
   337  		control.additionalControllers = controllers
   338  	}
   339  	if !cgroup2 {
   340  		for name := range handlers {
   341  			p := control.getCgroupv1Path(name)
   342  			if _, err := os.Stat(p); err != nil {
   343  				if os.IsNotExist(err) {
   344  					if rootless.IsRootless() {
   345  						return nil, ErrCgroupV1Rootless
   346  					}
   347  					// compatible with the error code
   348  					// used by containerd/cgroups
   349  					return nil, ErrCgroupDeleted
   350  				}
   351  			}
   352  		}
   353  	}
   354  	return control, nil
   355  }
   356  
   357  // CreateSystemdUnit creates the systemd cgroup
   358  func (c *CgroupControl) CreateSystemdUnit(path string) error {
   359  	if !c.systemd {
   360  		return fmt.Errorf("the cgroup controller is not using systemd")
   361  	}
   362  
   363  	conn, err := systemdDbus.New()
   364  	if err != nil {
   365  		return err
   366  	}
   367  	defer conn.Close()
   368  
   369  	return systemdCreate(path, conn)
   370  }
   371  
   372  // GetUserConnection returns an user connection to D-BUS
   373  func GetUserConnection(uid int) (*systemdDbus.Conn, error) {
   374  	return systemdDbus.NewConnection(func() (*dbus.Conn, error) {
   375  		return dbusAuthConnection(uid, dbus.SessionBusPrivate)
   376  	})
   377  }
   378  
   379  // CreateSystemdUserUnit creates the systemd cgroup for the specified user
   380  func (c *CgroupControl) CreateSystemdUserUnit(path string, uid int) error {
   381  	if !c.systemd {
   382  		return fmt.Errorf("the cgroup controller is not using systemd")
   383  	}
   384  
   385  	conn, err := GetUserConnection(uid)
   386  	if err != nil {
   387  		return err
   388  	}
   389  	defer conn.Close()
   390  
   391  	return systemdCreate(path, conn)
   392  }
   393  
   394  func dbusAuthConnection(uid int, createBus func(opts ...dbus.ConnOption) (*dbus.Conn, error)) (*dbus.Conn, error) {
   395  	conn, err := createBus()
   396  	if err != nil {
   397  		return nil, err
   398  	}
   399  
   400  	methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(uid))}
   401  
   402  	err = conn.Auth(methods)
   403  	if err != nil {
   404  		conn.Close()
   405  		return nil, err
   406  	}
   407  	if err := conn.Hello(); err != nil {
   408  		return nil, err
   409  	}
   410  
   411  	return conn, nil
   412  }
   413  
   414  // Delete cleans a cgroup
   415  func (c *CgroupControl) Delete() error {
   416  	return c.DeleteByPath(c.path)
   417  }
   418  
   419  // rmDirRecursively delete recursively a cgroup directory.
   420  // It differs from os.RemoveAll as it doesn't attempt to unlink files.
   421  // On cgroupfs we are allowed only to rmdir empty directories.
   422  func rmDirRecursively(path string) error {
   423  	if err := os.Remove(path); err == nil || os.IsNotExist(err) {
   424  		return nil
   425  	}
   426  	entries, err := ioutil.ReadDir(path)
   427  	if err != nil {
   428  		return errors.Wrapf(err, "read %s", path)
   429  	}
   430  	for _, i := range entries {
   431  		if i.IsDir() {
   432  			if err := rmDirRecursively(filepath.Join(path, i.Name())); err != nil {
   433  				return err
   434  			}
   435  		}
   436  	}
   437  	if err := os.Remove(path); err != nil {
   438  		if !os.IsNotExist(err) {
   439  			return errors.Wrapf(err, "remove %s", path)
   440  		}
   441  	}
   442  	return nil
   443  }
   444  
   445  // DeleteByPathConn deletes the specified cgroup path using the specified
   446  // dbus connection if needed.
   447  func (c *CgroupControl) DeleteByPathConn(path string, conn *systemdDbus.Conn) error {
   448  	if c.systemd {
   449  		return systemdDestroyConn(path, conn)
   450  	}
   451  	if c.cgroup2 {
   452  		return rmDirRecursively(filepath.Join(cgroupRoot, c.path))
   453  	}
   454  	var lastError error
   455  	for _, h := range handlers {
   456  		if err := h.Destroy(c); err != nil {
   457  			lastError = err
   458  		}
   459  	}
   460  
   461  	for _, ctr := range c.additionalControllers {
   462  		if ctr.symlink {
   463  			continue
   464  		}
   465  		p := c.getCgroupv1Path(ctr.name)
   466  		if err := rmDirRecursively(p); err != nil {
   467  			lastError = errors.Wrapf(err, "remove %s", p)
   468  		}
   469  	}
   470  	return lastError
   471  }
   472  
   473  // DeleteByPath deletes the specified cgroup path
   474  func (c *CgroupControl) DeleteByPath(path string) error {
   475  	if c.systemd {
   476  		conn, err := systemdDbus.New()
   477  		if err != nil {
   478  			return err
   479  		}
   480  		defer conn.Close()
   481  		return c.DeleteByPathConn(path, conn)
   482  	}
   483  	return c.DeleteByPathConn(path, nil)
   484  }
   485  
   486  // Update updates the cgroups
   487  func (c *CgroupControl) Update(resources *spec.LinuxResources) error {
   488  	for _, h := range handlers {
   489  		if err := h.Apply(c, resources); err != nil {
   490  			return err
   491  		}
   492  	}
   493  	return nil
   494  }
   495  
   496  // AddPid moves the specified pid to the cgroup
   497  func (c *CgroupControl) AddPid(pid int) error {
   498  	pidString := []byte(fmt.Sprintf("%d\n", pid))
   499  
   500  	if c.cgroup2 {
   501  		p := filepath.Join(cgroupRoot, c.path, "cgroup.procs")
   502  		if err := ioutil.WriteFile(p, pidString, 0644); err != nil {
   503  			return errors.Wrapf(err, "write %s", p)
   504  		}
   505  		return nil
   506  	}
   507  
   508  	var names []string
   509  	for n := range handlers {
   510  		names = append(names, n)
   511  	}
   512  
   513  	for _, c := range c.additionalControllers {
   514  		if !c.symlink {
   515  			names = append(names, c.name)
   516  		}
   517  	}
   518  
   519  	for _, n := range names {
   520  		// If we aren't using cgroup2, we won't write correctly to unified hierarchy
   521  		if !c.cgroup2 && n == "unified" {
   522  			continue
   523  		}
   524  		p := filepath.Join(c.getCgroupv1Path(n), "tasks")
   525  		if err := ioutil.WriteFile(p, pidString, 0644); err != nil {
   526  			return errors.Wrapf(err, "write %s", p)
   527  		}
   528  	}
   529  	return nil
   530  }
   531  
   532  // Stat returns usage statistics for the cgroup
   533  func (c *CgroupControl) Stat() (*Metrics, error) {
   534  	m := Metrics{}
   535  	for _, h := range handlers {
   536  		if err := h.Stat(c, &m); err != nil {
   537  			return nil, err
   538  		}
   539  	}
   540  	return &m, nil
   541  }
   542  
   543  func readCgroup2MapPath(path string) (map[string][]string, error) {
   544  	ret := map[string][]string{}
   545  	f, err := os.Open(path)
   546  	if err != nil {
   547  		if os.IsNotExist(err) {
   548  			return ret, nil
   549  		}
   550  		return nil, errors.Wrapf(err, "open file %s", path)
   551  	}
   552  	defer f.Close()
   553  	scanner := bufio.NewScanner(f)
   554  	for scanner.Scan() {
   555  		line := scanner.Text()
   556  		parts := strings.Fields(line)
   557  		if len(parts) < 2 {
   558  			continue
   559  		}
   560  		ret[parts[0]] = parts[1:]
   561  	}
   562  	if err := scanner.Err(); err != nil {
   563  		return nil, errors.Wrapf(err, "parsing file %s", path)
   564  	}
   565  	return ret, nil
   566  }
   567  
   568  func readCgroup2MapFile(ctr *CgroupControl, name string) (map[string][]string, error) {
   569  	p := filepath.Join(cgroupRoot, ctr.path, name)
   570  
   571  	return readCgroup2MapPath(p)
   572  }