Skip to content

Commit

Permalink
Added systemd manager
Browse files Browse the repository at this point in the history
Signed-off-by: bpopovschi <zyqsempai@mail.ru>
  • Loading branch information
Zyqsempai committed Jan 7, 2020
1 parent e077fb6 commit 5efa14e
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 0 deletions.
125 changes: 125 additions & 0 deletions v2/manager.go
Expand Up @@ -25,6 +25,7 @@ import (
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
"time"

Expand All @@ -33,12 +34,23 @@ import (
"golang.org/x/sys/unix"

"github.com/containerd/cgroups/v2/stats"
"github.com/godbus/dbus"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"

systemdDbus "github.com/coreos/go-systemd/dbus"
)

const (
subtreeControl = "cgroup.subtree_control"
controllersFile = "cgroup.controllers"
defaultSlice = "system.slice"
)

var (
canDelegate bool
once sync.Once
)

type cgValuer interface {
Expand Down Expand Up @@ -583,3 +595,116 @@ func setDevices(path string, devices []specs.LinuxDeviceCgroup) error {
}
return nil
}

func NewSystemd(path string, resources *specs.LinuxResources) (*Manager, error) {
conn, err := systemdDbus.New()
if err != nil {
return &Manager{}, err
}
defer conn.Close()
slice, name := splitName(path)

// We need to see if systemd can handle the delegate property
// Systemd will return an error if it cannot handle delegate regardless
// of its bool setting.
checkDelegate := func() {
canDelegate = true
dlSlice := newSystemdProperty("Delegate", true)
if _, err := conn.StartTransientUnit(slice, "testdelegate", []systemdDbus.Property{dlSlice}, nil); err != nil {
if dbusError, ok := err.(dbus.Error); ok {
// Starting with systemd v237, Delegate is not even a property of slices anymore,
// so the D-Bus call fails with "InvalidArgs" error.
if strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.PropertyReadOnly") || strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.InvalidArgs") {
canDelegate = false
}
}
}

conn.StopUnit(slice, "testDelegate", nil)
}
once.Do(checkDelegate)

properties := []systemdDbus.Property{
systemdDbus.PropDescription(fmt.Sprintf("cgroup %s", name)),
systemdDbus.PropWants(slice),
newSystemdProperty("DefaultDependencies", false),
newSystemdProperty("MemoryAccounting", true),
newSystemdProperty("CPUAccounting", true),
newSystemdProperty("BlockIOAccounting", true),
}

// cpu.cfs_quota_us and cpu.cfs_period_us are controlled by systemd.
if *resources.CPU.Quota != 0 && *resources.CPU.Period != 0 {
// corresponds to USEC_INFINITY in systemd
// if USEC_INFINITY is provided, CPUQuota is left unbound by systemd
// always setting a property value ensures we can apply a quota and remove it later
cpuQuotaPerSecUSec := uint64(math.MaxUint64)
if *resources.CPU.Quota > 0 {
// systemd converts CPUQuotaPerSecUSec (microseconds per CPU second) to CPUQuota
// (integer percentage of CPU) internally. This means that if a fractional percent of
// CPU is indicated by Resources.CpuQuota, we need to round up to the nearest
// 10ms (1% of a second) such that child cgroups can set the cpu.cfs_quota_us they expect.
cpuQuotaPerSecUSec = uint64(*resources.CPU.Quota*1000000) / *resources.CPU.Period
if cpuQuotaPerSecUSec%10000 != 0 {
cpuQuotaPerSecUSec = ((cpuQuotaPerSecUSec / 10000) + 1) * 10000
}
}
properties = append(properties,
newSystemdProperty("CPUQuotaPerSecUSec", cpuQuotaPerSecUSec))
}

// If we can delegate, we add the property back in
if canDelegate {
properties = append(properties, newSystemdProperty("Delegate", true))
}

if resources.Pids.Limit > 0 {
properties = append(properties,
newSystemdProperty("TasksAccounting", true),
newSystemdProperty("TasksMax", uint64(resources.Pids.Limit)))
}

statusChan := make(chan string, 1)
if _, err := conn.StartTransientUnit(name, "replace", properties, statusChan); err == nil {
select {
case <-statusChan:
case <-time.After(time.Second):
logrus.Warnf("Timed out while waiting for StartTransientUnit(%s) completion signal from dbus. Continuing...", name)
}
} else if !isUnitExists(err) {
return &Manager{}, err
}

return &Manager{
path: path,
}, nil
}

func LoadSystemd(path string) (*Manager, error) {
return &Manager{
path: path,
}, nil
}

func (c *Manager) DeleteSystemd() error {
conn, err := systemdDbus.New()
if err != nil {
return err
}
defer conn.Close()
_, name := splitName(c.path)
ch := make(chan string)
_, err = conn.StopUnit(name, "replace", ch)
if err != nil {
return err
}
<-ch
return nil
}

func newSystemdProperty(name string, units interface{}) systemdDbus.Property {
return systemdDbus.Property{
Name: name,
Value: dbus.MakeVariant(units),
}
}
23 changes: 23 additions & 0 deletions v2/utils.go
Expand Up @@ -19,6 +19,7 @@ package v2
import (
"bufio"
"fmt"
"github.com/godbus/dbus"
"io"
"io/ioutil"
"math"
Expand Down Expand Up @@ -361,3 +362,25 @@ func toRdmaEntry(strEntries []string) []*stats.RdmaEntry {
}
return rdmaEntries
}

func splitName(path string) (slice string, unit string) {
slice, unit = filepath.Split(path)
return strings.TrimSuffix(slice, "/"), unit
}

func Slice(slice, name string) string {
if slice == "" {
slice = defaultSlice
}
return filepath.Join(slice, name)
}

// isUnitExists returns true if the error is that a systemd unit already exists.
func isUnitExists(err error) bool {
if err != nil {
if dbusError, ok := err.(dbus.Error); ok {
return strings.Contains(dbusError.Name, "org.freedesktop.systemd1.UnitExists")
}
}
return false
}

0 comments on commit 5efa14e

Please sign in to comment.