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 }