systemd.go (4049B)
1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package cgroup1 18 19 import ( 20 "context" 21 "path/filepath" 22 "strings" 23 "sync" 24 25 systemdDbus "github.com/coreos/go-systemd/v22/dbus" 26 "github.com/godbus/dbus/v5" 27 specs "github.com/opencontainers/runtime-spec/specs-go" 28 ) 29 30 const ( 31 SystemdDbus Name = "systemd" 32 defaultSlice = "system.slice" 33 ) 34 35 var ( 36 canDelegate bool 37 once sync.Once 38 ) 39 40 func Systemd() ([]Subsystem, error) { 41 root, err := v1MountPoint() 42 if err != nil { 43 return nil, err 44 } 45 defaultSubsystems, err := defaults(root) 46 if err != nil { 47 return nil, err 48 } 49 s, err := NewSystemd(root) 50 if err != nil { 51 return nil, err 52 } 53 // make sure the systemd controller is added first 54 return append([]Subsystem{s}, defaultSubsystems...), nil 55 } 56 57 func Slice(slice, name string) Path { 58 if slice == "" { 59 slice = defaultSlice 60 } 61 return func(subsystem Name) (string, error) { 62 return filepath.Join(slice, name), nil 63 } 64 } 65 66 func NewSystemd(root string) (*SystemdController, error) { 67 return &SystemdController{ 68 root: root, 69 }, nil 70 } 71 72 type SystemdController struct { 73 mu sync.Mutex 74 root string 75 } 76 77 func (s *SystemdController) Name() Name { 78 return SystemdDbus 79 } 80 81 func (s *SystemdController) Create(path string, _ *specs.LinuxResources) error { 82 ctx := context.TODO() 83 conn, err := systemdDbus.NewWithContext(ctx) 84 if err != nil { 85 return err 86 } 87 defer conn.Close() 88 slice, name := splitName(path) 89 // We need to see if systemd can handle the delegate property 90 // Systemd will return an error if it cannot handle delegate regardless 91 // of its bool setting. 92 checkDelegate := func() { 93 canDelegate = true 94 dlSlice := newProperty("Delegate", true) 95 if _, err := conn.StartTransientUnitContext(ctx, slice, "testdelegate", []systemdDbus.Property{dlSlice}, nil); err != nil { 96 if dbusError, ok := err.(dbus.Error); ok { 97 // Starting with systemd v237, Delegate is not even a property of slices anymore, 98 // so the D-Bus call fails with "InvalidArgs" error. 99 if strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.PropertyReadOnly") || strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.InvalidArgs") { 100 canDelegate = false 101 } 102 } 103 } 104 105 _, _ = conn.StopUnitContext(ctx, slice, "testDelegate", nil) 106 } 107 once.Do(checkDelegate) 108 properties := []systemdDbus.Property{ 109 systemdDbus.PropDescription("cgroup " + name), 110 systemdDbus.PropWants(slice), 111 newProperty("DefaultDependencies", false), 112 newProperty("MemoryAccounting", true), 113 newProperty("CPUAccounting", true), 114 newProperty("BlockIOAccounting", true), 115 } 116 117 // If we can delegate, we add the property back in 118 if canDelegate { 119 properties = append(properties, newProperty("Delegate", true)) 120 } 121 122 ch := make(chan string) 123 _, err = conn.StartTransientUnitContext(ctx, name, "replace", properties, ch) 124 if err != nil { 125 return err 126 } 127 <-ch 128 return nil 129 } 130 131 func (s *SystemdController) Delete(path string) error { 132 ctx := context.TODO() 133 conn, err := systemdDbus.NewWithContext(ctx) 134 if err != nil { 135 return err 136 } 137 defer conn.Close() 138 _, name := splitName(path) 139 ch := make(chan string) 140 _, err = conn.StopUnitContext(ctx, name, "replace", ch) 141 if err != nil { 142 return err 143 } 144 <-ch 145 return nil 146 } 147 148 func newProperty(name string, units interface{}) systemdDbus.Property { 149 return systemdDbus.Property{ 150 Name: name, 151 Value: dbus.MakeVariant(units), 152 } 153 } 154 155 func splitName(path string) (slice string, unit string) { 156 slice, unit = filepath.Split(path) 157 return strings.TrimSuffix(slice, "/"), unit 158 }