...

Source file src/k8s.io/kubernetes/pkg/kubelet/nodeshutdown/systemd/inhibit_linux.go

Documentation: k8s.io/kubernetes/pkg/kubelet/nodeshutdown/systemd

     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  }
   201  

View as plain text