github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/pkg/cgroups/cgroups.go (about)

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