k8s.io/kubernetes@v1.29.3/pkg/kubelet/nodeshutdown/systemd/inhibit_linux.go (about) 1 //go:build linux 2 // +build linux 3 4 /* 5 Copyright 2020 The Kubernetes Authors. 6 7 Licensed under the Apache License, Version 2.0 (the "License"); 8 you may not use this file except in compliance with the License. 9 You may obtain a copy of the License at 10 11 http://www.apache.org/licenses/LICENSE-2.0 12 13 Unless required by applicable law or agreed to in writing, software 14 distributed under the License is distributed on an "AS IS" BASIS, 15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 See the License for the specific language governing permissions and 17 limitations under the License. 18 */ 19 20 package systemd 21 22 import ( 23 "fmt" 24 "os" 25 "path/filepath" 26 "syscall" 27 "time" 28 29 "github.com/godbus/dbus/v5" 30 "k8s.io/klog/v2" 31 ) 32 33 const ( 34 logindService = "org.freedesktop.login1" 35 logindObject = dbus.ObjectPath("/org/freedesktop/login1") 36 logindInterface = "org.freedesktop.login1.Manager" 37 ) 38 39 type dBusConnector interface { 40 Object(dest string, path dbus.ObjectPath) dbus.BusObject 41 AddMatchSignal(options ...dbus.MatchOption) error 42 Signal(ch chan<- *dbus.Signal) 43 } 44 45 // DBusCon has functions that can be used to interact with systemd and logind over dbus. 46 type DBusCon struct { 47 SystemBus dBusConnector 48 } 49 50 func NewDBusCon() (*DBusCon, error) { 51 conn, err := dbus.SystemBus() 52 if err != nil { 53 return nil, err 54 } 55 56 return &DBusCon{ 57 SystemBus: conn, 58 }, nil 59 } 60 61 // InhibitLock is a lock obtained after creating an systemd inhibitor by calling InhibitShutdown(). 62 type InhibitLock uint32 63 64 // CurrentInhibitDelay returns the current delay inhibitor timeout value as configured in logind.conf(5). 65 // see https://www.freedesktop.org/software/systemd/man/logind.conf.html for more details. 66 func (bus *DBusCon) CurrentInhibitDelay() (time.Duration, error) { 67 obj := bus.SystemBus.Object(logindService, logindObject) 68 res, err := obj.GetProperty(logindInterface + ".InhibitDelayMaxUSec") 69 if err != nil { 70 return 0, fmt.Errorf("failed reading InhibitDelayMaxUSec property from logind: %w", err) 71 } 72 73 delay, ok := res.Value().(uint64) 74 if !ok { 75 return 0, fmt.Errorf("InhibitDelayMaxUSec from logind is not a uint64 as expected") 76 } 77 78 // InhibitDelayMaxUSec is in microseconds 79 duration := time.Duration(delay) * time.Microsecond 80 return duration, nil 81 } 82 83 // InhibitShutdown creates an systemd inhibitor by calling logind's Inhibt() and returns the inhibitor lock 84 // see https://www.freedesktop.org/wiki/Software/systemd/inhibit/ for more details. 85 func (bus *DBusCon) InhibitShutdown() (InhibitLock, error) { 86 obj := bus.SystemBus.Object(logindService, logindObject) 87 what := "shutdown" 88 who := "kubelet" 89 why := "Kubelet needs time to handle node shutdown" 90 mode := "delay" 91 92 call := obj.Call("org.freedesktop.login1.Manager.Inhibit", 0, what, who, why, mode) 93 if call.Err != nil { 94 return InhibitLock(0), fmt.Errorf("failed creating systemd inhibitor: %w", call.Err) 95 } 96 97 var fd uint32 98 err := call.Store(&fd) 99 if err != nil { 100 return InhibitLock(0), fmt.Errorf("failed storing inhibit lock file descriptor: %w", err) 101 } 102 103 return InhibitLock(fd), nil 104 } 105 106 // ReleaseInhibitLock will release the underlying inhibit lock which will cause the shutdown to start. 107 func (bus *DBusCon) ReleaseInhibitLock(lock InhibitLock) error { 108 err := syscall.Close(int(lock)) 109 110 if err != nil { 111 return fmt.Errorf("unable to close systemd inhibitor lock: %w", err) 112 } 113 114 return nil 115 } 116 117 // ReloadLogindConf uses dbus to send a SIGHUP to the systemd-logind service causing logind to reload it's configuration. 118 func (bus *DBusCon) ReloadLogindConf() error { 119 systemdService := "org.freedesktop.systemd1" 120 systemdObject := "/org/freedesktop/systemd1" 121 systemdInterface := "org.freedesktop.systemd1.Manager" 122 123 obj := bus.SystemBus.Object(systemdService, dbus.ObjectPath(systemdObject)) 124 unit := "systemd-logind.service" 125 who := "all" 126 var signal int32 = 1 // SIGHUP 127 128 call := obj.Call(systemdInterface+".KillUnit", 0, unit, who, signal) 129 if call.Err != nil { 130 return fmt.Errorf("unable to reload logind conf: %w", call.Err) 131 } 132 133 return nil 134 } 135 136 // MonitorShutdown detects the node shutdown by watching for "PrepareForShutdown" logind events. 137 // see https://www.freedesktop.org/wiki/Software/systemd/inhibit/ for more details. 138 func (bus *DBusCon) MonitorShutdown() (<-chan bool, error) { 139 err := bus.SystemBus.AddMatchSignal(dbus.WithMatchInterface(logindInterface), dbus.WithMatchMember("PrepareForShutdown"), dbus.WithMatchObjectPath("/org/freedesktop/login1")) 140 141 if err != nil { 142 return nil, err 143 } 144 145 busChan := make(chan *dbus.Signal, 1) 146 bus.SystemBus.Signal(busChan) 147 148 shutdownChan := make(chan bool, 1) 149 150 go func() { 151 for { 152 event, ok := <-busChan 153 if !ok { 154 close(shutdownChan) 155 return 156 } 157 if event == nil || len(event.Body) == 0 { 158 klog.ErrorS(nil, "Failed obtaining shutdown event, PrepareForShutdown event was empty") 159 continue 160 } 161 shutdownActive, ok := event.Body[0].(bool) 162 if !ok { 163 klog.ErrorS(nil, "Failed obtaining shutdown event, PrepareForShutdown event was not bool type as expected") 164 continue 165 } 166 shutdownChan <- shutdownActive 167 } 168 }() 169 170 return shutdownChan, nil 171 } 172 173 const ( 174 logindConfigDirectory = "/etc/systemd/logind.conf.d/" 175 kubeletLogindConf = "99-kubelet.conf" 176 ) 177 178 // OverrideInhibitDelay writes a config file to logind overriding InhibitDelayMaxSec to the value desired. 179 func (bus *DBusCon) OverrideInhibitDelay(inhibitDelayMax time.Duration) error { 180 err := os.MkdirAll(logindConfigDirectory, 0755) 181 if err != nil { 182 return fmt.Errorf("failed creating %v directory: %w", logindConfigDirectory, err) 183 } 184 185 // This attempts to set the `InhibitDelayMaxUSec` dbus property of logind which is MaxInhibitDelay measured in microseconds. 186 // The corresponding logind config file property is named `InhibitDelayMaxSec` and is measured in seconds which is set via logind.conf config. 187 // Refer to https://www.freedesktop.org/software/systemd/man/logind.conf.html for more details. 188 189 inhibitOverride := fmt.Sprintf(`# Kubelet logind override 190 [Login] 191 InhibitDelayMaxSec=%.0f 192 `, inhibitDelayMax.Seconds()) 193 194 logindOverridePath := filepath.Join(logindConfigDirectory, kubeletLogindConf) 195 if err := os.WriteFile(logindOverridePath, []byte(inhibitOverride), 0644); err != nil { 196 return fmt.Errorf("failed writing logind shutdown inhibit override file %v: %w", logindOverridePath, err) 197 } 198 199 return nil 200 }