From e0fec1dc2895be67f31cdaf07ef3f82302d4f83c Mon Sep 17 00:00:00 2001 From: Kalya Subramanian Date: Mon, 20 May 2019 23:28:56 +0000 Subject: [PATCH 1/2] Include golang.org/x/sys/windows/svc/mgr to manage Windows services --- pkg/util/initsystem/BUILD | 4 + vendor/golang.org/x/sys/windows/svc/BUILD | 5 +- vendor/golang.org/x/sys/windows/svc/mgr/BUILD | 35 ++++ .../x/sys/windows/svc/mgr/config.go | 145 ++++++++++++++++ .../golang.org/x/sys/windows/svc/mgr/mgr.go | 162 ++++++++++++++++++ .../x/sys/windows/svc/mgr/recovery.go | 135 +++++++++++++++ .../x/sys/windows/svc/mgr/service.go | 72 ++++++++ vendor/modules.txt | 1 + 8 files changed, 558 insertions(+), 1 deletion(-) create mode 100644 vendor/golang.org/x/sys/windows/svc/mgr/BUILD create mode 100644 vendor/golang.org/x/sys/windows/svc/mgr/config.go create mode 100644 vendor/golang.org/x/sys/windows/svc/mgr/mgr.go create mode 100644 vendor/golang.org/x/sys/windows/svc/mgr/recovery.go create mode 100644 vendor/golang.org/x/sys/windows/svc/mgr/service.go diff --git a/pkg/util/initsystem/BUILD b/pkg/util/initsystem/BUILD index 114c4466f83..75f9cab8a1d 100644 --- a/pkg/util/initsystem/BUILD +++ b/pkg/util/initsystem/BUILD @@ -9,6 +9,10 @@ go_library( name = "go_default_library", srcs = ["initsystem.go"], importpath = "k8s.io/kubernetes/pkg/util/initsystem", + deps = [ + "//vendor/golang.org/x/sys/windows/svc:go_default_library", + "//vendor/golang.org/x/sys/windows/svc/mgr:go_default_library", + ], ) filegroup( diff --git a/vendor/golang.org/x/sys/windows/svc/BUILD b/vendor/golang.org/x/sys/windows/svc/BUILD index 9b2a84bf3c1..bdf53ea43fc 100644 --- a/vendor/golang.org/x/sys/windows/svc/BUILD +++ b/vendor/golang.org/x/sys/windows/svc/BUILD @@ -31,7 +31,10 @@ filegroup( filegroup( name = "all-srcs", - srcs = [":package-srcs"], + srcs = [ + ":package-srcs", + "//vendor/golang.org/x/sys/windows/svc/mgr:all-srcs", + ], tags = ["automanaged"], visibility = ["//visibility:public"], ) diff --git a/vendor/golang.org/x/sys/windows/svc/mgr/BUILD b/vendor/golang.org/x/sys/windows/svc/mgr/BUILD new file mode 100644 index 00000000000..0378cf35b16 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/mgr/BUILD @@ -0,0 +1,35 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "config.go", + "mgr.go", + "recovery.go", + "service.go", + ], + importmap = "k8s.io/kubernetes/vendor/golang.org/x/sys/windows/svc/mgr", + importpath = "golang.org/x/sys/windows/svc/mgr", + visibility = ["//visibility:public"], + deps = select({ + "@io_bazel_rules_go//go/platform:windows": [ + "//vendor/golang.org/x/sys/windows:go_default_library", + "//vendor/golang.org/x/sys/windows/svc:go_default_library", + ], + "//conditions:default": [], + }), +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/vendor/golang.org/x/sys/windows/svc/mgr/config.go b/vendor/golang.org/x/sys/windows/svc/mgr/config.go new file mode 100644 index 00000000000..d804e31f1f3 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/mgr/config.go @@ -0,0 +1,145 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package mgr + +import ( + "syscall" + "unicode/utf16" + "unsafe" + + "golang.org/x/sys/windows" +) + +const ( + // Service start types. + StartManual = windows.SERVICE_DEMAND_START // the service must be started manually + StartAutomatic = windows.SERVICE_AUTO_START // the service will start by itself whenever the computer reboots + StartDisabled = windows.SERVICE_DISABLED // the service cannot be started + + // The severity of the error, and action taken, + // if this service fails to start. + ErrorCritical = windows.SERVICE_ERROR_CRITICAL + ErrorIgnore = windows.SERVICE_ERROR_IGNORE + ErrorNormal = windows.SERVICE_ERROR_NORMAL + ErrorSevere = windows.SERVICE_ERROR_SEVERE +) + +// TODO(brainman): Password is not returned by windows.QueryServiceConfig, not sure how to get it. + +type Config struct { + ServiceType uint32 + StartType uint32 + ErrorControl uint32 + BinaryPathName string // fully qualified path to the service binary file, can also include arguments for an auto-start service + LoadOrderGroup string + TagId uint32 + Dependencies []string + ServiceStartName string // name of the account under which the service should run + DisplayName string + Password string + Description string +} + +func toString(p *uint16) string { + if p == nil { + return "" + } + return syscall.UTF16ToString((*[4096]uint16)(unsafe.Pointer(p))[:]) +} + +func toStringSlice(ps *uint16) []string { + if ps == nil { + return nil + } + r := make([]string, 0) + for from, i, p := 0, 0, (*[1 << 24]uint16)(unsafe.Pointer(ps)); true; i++ { + if p[i] == 0 { + // empty string marks the end + if i <= from { + break + } + r = append(r, string(utf16.Decode(p[from:i]))) + from = i + 1 + } + } + return r +} + +// Config retrieves service s configuration paramteres. +func (s *Service) Config() (Config, error) { + var p *windows.QUERY_SERVICE_CONFIG + n := uint32(1024) + for { + b := make([]byte, n) + p = (*windows.QUERY_SERVICE_CONFIG)(unsafe.Pointer(&b[0])) + err := windows.QueryServiceConfig(s.Handle, p, n, &n) + if err == nil { + break + } + if err.(syscall.Errno) != syscall.ERROR_INSUFFICIENT_BUFFER { + return Config{}, err + } + if n <= uint32(len(b)) { + return Config{}, err + } + } + + b, err := s.queryServiceConfig2(windows.SERVICE_CONFIG_DESCRIPTION) + if err != nil { + return Config{}, err + } + p2 := (*windows.SERVICE_DESCRIPTION)(unsafe.Pointer(&b[0])) + + return Config{ + ServiceType: p.ServiceType, + StartType: p.StartType, + ErrorControl: p.ErrorControl, + BinaryPathName: toString(p.BinaryPathName), + LoadOrderGroup: toString(p.LoadOrderGroup), + TagId: p.TagId, + Dependencies: toStringSlice(p.Dependencies), + ServiceStartName: toString(p.ServiceStartName), + DisplayName: toString(p.DisplayName), + Description: toString(p2.Description), + }, nil +} + +func updateDescription(handle windows.Handle, desc string) error { + d := windows.SERVICE_DESCRIPTION{Description: toPtr(desc)} + return windows.ChangeServiceConfig2(handle, + windows.SERVICE_CONFIG_DESCRIPTION, (*byte)(unsafe.Pointer(&d))) +} + +// UpdateConfig updates service s configuration parameters. +func (s *Service) UpdateConfig(c Config) error { + err := windows.ChangeServiceConfig(s.Handle, c.ServiceType, c.StartType, + c.ErrorControl, toPtr(c.BinaryPathName), toPtr(c.LoadOrderGroup), + nil, toStringBlock(c.Dependencies), toPtr(c.ServiceStartName), + toPtr(c.Password), toPtr(c.DisplayName)) + if err != nil { + return err + } + return updateDescription(s.Handle, c.Description) +} + +// queryServiceConfig2 calls Windows QueryServiceConfig2 with infoLevel parameter and returns retrieved service configuration information. +func (s *Service) queryServiceConfig2(infoLevel uint32) ([]byte, error) { + n := uint32(1024) + for { + b := make([]byte, n) + err := windows.QueryServiceConfig2(s.Handle, infoLevel, &b[0], n, &n) + if err == nil { + return b, nil + } + if err.(syscall.Errno) != syscall.ERROR_INSUFFICIENT_BUFFER { + return nil, err + } + if n <= uint32(len(b)) { + return nil, err + } + } +} diff --git a/vendor/golang.org/x/sys/windows/svc/mgr/mgr.go b/vendor/golang.org/x/sys/windows/svc/mgr/mgr.go new file mode 100644 index 00000000000..76965b56018 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/mgr/mgr.go @@ -0,0 +1,162 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +// Package mgr can be used to manage Windows service programs. +// It can be used to install and remove them. It can also start, +// stop and pause them. The package can query / change current +// service state and config parameters. +// +package mgr + +import ( + "syscall" + "unicode/utf16" + "unsafe" + + "golang.org/x/sys/windows" +) + +// Mgr is used to manage Windows service. +type Mgr struct { + Handle windows.Handle +} + +// Connect establishes a connection to the service control manager. +func Connect() (*Mgr, error) { + return ConnectRemote("") +} + +// ConnectRemote establishes a connection to the +// service control manager on computer named host. +func ConnectRemote(host string) (*Mgr, error) { + var s *uint16 + if host != "" { + s = syscall.StringToUTF16Ptr(host) + } + h, err := windows.OpenSCManager(s, nil, windows.SC_MANAGER_ALL_ACCESS) + if err != nil { + return nil, err + } + return &Mgr{Handle: h}, nil +} + +// Disconnect closes connection to the service control manager m. +func (m *Mgr) Disconnect() error { + return windows.CloseServiceHandle(m.Handle) +} + +func toPtr(s string) *uint16 { + if len(s) == 0 { + return nil + } + return syscall.StringToUTF16Ptr(s) +} + +// toStringBlock terminates strings in ss with 0, and then +// concatenates them together. It also adds extra 0 at the end. +func toStringBlock(ss []string) *uint16 { + if len(ss) == 0 { + return nil + } + t := "" + for _, s := range ss { + if s != "" { + t += s + "\x00" + } + } + if t == "" { + return nil + } + t += "\x00" + return &utf16.Encode([]rune(t))[0] +} + +// CreateService installs new service name on the system. +// The service will be executed by running exepath binary. +// Use config c to specify service parameters. +// Any args will be passed as command-line arguments when +// the service is started; these arguments are distinct from +// the arguments passed to Service.Start or via the "Start +// parameters" field in the service's Properties dialog box. +func (m *Mgr) CreateService(name, exepath string, c Config, args ...string) (*Service, error) { + if c.StartType == 0 { + c.StartType = StartManual + } + if c.ErrorControl == 0 { + c.ErrorControl = ErrorNormal + } + if c.ServiceType == 0 { + c.ServiceType = windows.SERVICE_WIN32_OWN_PROCESS + } + s := syscall.EscapeArg(exepath) + for _, v := range args { + s += " " + syscall.EscapeArg(v) + } + h, err := windows.CreateService(m.Handle, toPtr(name), toPtr(c.DisplayName), + windows.SERVICE_ALL_ACCESS, c.ServiceType, + c.StartType, c.ErrorControl, toPtr(s), toPtr(c.LoadOrderGroup), + nil, toStringBlock(c.Dependencies), toPtr(c.ServiceStartName), toPtr(c.Password)) + if err != nil { + return nil, err + } + if c.Description != "" { + err = updateDescription(h, c.Description) + if err != nil { + return nil, err + } + } + return &Service{Name: name, Handle: h}, nil +} + +// OpenService retrieves access to service name, so it can +// be interrogated and controlled. +func (m *Mgr) OpenService(name string) (*Service, error) { + h, err := windows.OpenService(m.Handle, syscall.StringToUTF16Ptr(name), windows.SERVICE_ALL_ACCESS) + if err != nil { + return nil, err + } + return &Service{Name: name, Handle: h}, nil +} + +// ListServices enumerates services in the specified +// service control manager database m. +// If the caller does not have the SERVICE_QUERY_STATUS +// access right to a service, the service is silently +// omitted from the list of services returned. +func (m *Mgr) ListServices() ([]string, error) { + var err error + var bytesNeeded, servicesReturned uint32 + var buf []byte + for { + var p *byte + if len(buf) > 0 { + p = &buf[0] + } + err = windows.EnumServicesStatusEx(m.Handle, windows.SC_ENUM_PROCESS_INFO, + windows.SERVICE_WIN32, windows.SERVICE_STATE_ALL, + p, uint32(len(buf)), &bytesNeeded, &servicesReturned, nil, nil) + if err == nil { + break + } + if err != syscall.ERROR_MORE_DATA { + return nil, err + } + if bytesNeeded <= uint32(len(buf)) { + return nil, err + } + buf = make([]byte, bytesNeeded) + } + if servicesReturned == 0 { + return nil, nil + } + services := (*[1 << 20]windows.ENUM_SERVICE_STATUS_PROCESS)(unsafe.Pointer(&buf[0]))[:servicesReturned] + var names []string + for _, s := range services { + name := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(s.ServiceName))[:]) + names = append(names, name) + } + return names, nil +} diff --git a/vendor/golang.org/x/sys/windows/svc/mgr/recovery.go b/vendor/golang.org/x/sys/windows/svc/mgr/recovery.go new file mode 100644 index 00000000000..71ce2b8199f --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/mgr/recovery.go @@ -0,0 +1,135 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package mgr + +import ( + "errors" + "syscall" + "time" + "unsafe" + + "golang.org/x/sys/windows" +) + +const ( + // Possible recovery actions that the service control manager can perform. + NoAction = windows.SC_ACTION_NONE // no action + ComputerReboot = windows.SC_ACTION_REBOOT // reboot the computer + ServiceRestart = windows.SC_ACTION_RESTART // restart the service + RunCommand = windows.SC_ACTION_RUN_COMMAND // run a command +) + +// RecoveryAction represents an action that the service control manager can perform when service fails. +// A service is considered failed when it terminates without reporting a status of SERVICE_STOPPED to the service controller. +type RecoveryAction struct { + Type int // one of NoAction, ComputerReboot, ServiceRestart or RunCommand + Delay time.Duration // the time to wait before performing the specified action +} + +// SetRecoveryActions sets actions that service controller performs when service fails and +// the time after which to reset the service failure count to zero if there are no failures, in seconds. +// Specify INFINITE to indicate that service failure count should never be reset. +func (s *Service) SetRecoveryActions(recoveryActions []RecoveryAction, resetPeriod uint32) error { + if recoveryActions == nil { + return errors.New("recoveryActions cannot be nil") + } + actions := []windows.SC_ACTION{} + for _, a := range recoveryActions { + action := windows.SC_ACTION{ + Type: uint32(a.Type), + Delay: uint32(a.Delay.Nanoseconds() / 1000000), + } + actions = append(actions, action) + } + rActions := windows.SERVICE_FAILURE_ACTIONS{ + ActionsCount: uint32(len(actions)), + Actions: &actions[0], + ResetPeriod: resetPeriod, + } + return windows.ChangeServiceConfig2(s.Handle, windows.SERVICE_CONFIG_FAILURE_ACTIONS, (*byte)(unsafe.Pointer(&rActions))) +} + +// RecoveryActions returns actions that service controller performs when service fails. +// The service control manager counts the number of times service s has failed since the system booted. +// The count is reset to 0 if the service has not failed for ResetPeriod seconds. +// When the service fails for the Nth time, the service controller performs the action specified in element [N-1] of returned slice. +// If N is greater than slice length, the service controller repeats the last action in the slice. +func (s *Service) RecoveryActions() ([]RecoveryAction, error) { + b, err := s.queryServiceConfig2(windows.SERVICE_CONFIG_FAILURE_ACTIONS) + if err != nil { + return nil, err + } + p := (*windows.SERVICE_FAILURE_ACTIONS)(unsafe.Pointer(&b[0])) + if p.Actions == nil { + return nil, err + } + + var recoveryActions []RecoveryAction + actions := (*[1024]windows.SC_ACTION)(unsafe.Pointer(p.Actions))[:p.ActionsCount] + for _, action := range actions { + recoveryActions = append(recoveryActions, RecoveryAction{Type: int(action.Type), Delay: time.Duration(action.Delay) * time.Millisecond}) + } + return recoveryActions, nil +} + +// ResetRecoveryActions deletes both reset period and array of failure actions. +func (s *Service) ResetRecoveryActions() error { + actions := make([]windows.SC_ACTION, 1) + rActions := windows.SERVICE_FAILURE_ACTIONS{ + Actions: &actions[0], + } + return windows.ChangeServiceConfig2(s.Handle, windows.SERVICE_CONFIG_FAILURE_ACTIONS, (*byte)(unsafe.Pointer(&rActions))) +} + +// ResetPeriod is the time after which to reset the service failure +// count to zero if there are no failures, in seconds. +func (s *Service) ResetPeriod() (uint32, error) { + b, err := s.queryServiceConfig2(windows.SERVICE_CONFIG_FAILURE_ACTIONS) + if err != nil { + return 0, err + } + p := (*windows.SERVICE_FAILURE_ACTIONS)(unsafe.Pointer(&b[0])) + return p.ResetPeriod, nil +} + +// SetRebootMessage sets service s reboot message. +// If msg is "", the reboot message is deleted and no message is broadcast. +func (s *Service) SetRebootMessage(msg string) error { + rActions := windows.SERVICE_FAILURE_ACTIONS{ + RebootMsg: syscall.StringToUTF16Ptr(msg), + } + return windows.ChangeServiceConfig2(s.Handle, windows.SERVICE_CONFIG_FAILURE_ACTIONS, (*byte)(unsafe.Pointer(&rActions))) +} + +// RebootMessage is broadcast to server users before rebooting in response to the ComputerReboot service controller action. +func (s *Service) RebootMessage() (string, error) { + b, err := s.queryServiceConfig2(windows.SERVICE_CONFIG_FAILURE_ACTIONS) + if err != nil { + return "", err + } + p := (*windows.SERVICE_FAILURE_ACTIONS)(unsafe.Pointer(&b[0])) + return toString(p.RebootMsg), nil +} + +// SetRecoveryCommand sets the command line of the process to execute in response to the RunCommand service controller action. +// If cmd is "", the command is deleted and no program is run when the service fails. +func (s *Service) SetRecoveryCommand(cmd string) error { + rActions := windows.SERVICE_FAILURE_ACTIONS{ + Command: syscall.StringToUTF16Ptr(cmd), + } + return windows.ChangeServiceConfig2(s.Handle, windows.SERVICE_CONFIG_FAILURE_ACTIONS, (*byte)(unsafe.Pointer(&rActions))) +} + +// RecoveryCommand is the command line of the process to execute in response to the RunCommand service controller action. This process runs under the same account as the service. +func (s *Service) RecoveryCommand() (string, error) { + b, err := s.queryServiceConfig2(windows.SERVICE_CONFIG_FAILURE_ACTIONS) + if err != nil { + return "", err + } + p := (*windows.SERVICE_FAILURE_ACTIONS)(unsafe.Pointer(&b[0])) + return toString(p.Command), nil +} diff --git a/vendor/golang.org/x/sys/windows/svc/mgr/service.go b/vendor/golang.org/x/sys/windows/svc/mgr/service.go new file mode 100644 index 00000000000..fdc46af5fcb --- /dev/null +++ b/vendor/golang.org/x/sys/windows/svc/mgr/service.go @@ -0,0 +1,72 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package mgr + +import ( + "syscall" + + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/svc" +) + +// TODO(brainman): Use EnumDependentServices to enumerate dependent services. + +// Service is used to access Windows service. +type Service struct { + Name string + Handle windows.Handle +} + +// Delete marks service s for deletion from the service control manager database. +func (s *Service) Delete() error { + return windows.DeleteService(s.Handle) +} + +// Close relinquish access to the service s. +func (s *Service) Close() error { + return windows.CloseServiceHandle(s.Handle) +} + +// Start starts service s. +// args will be passed to svc.Handler.Execute. +func (s *Service) Start(args ...string) error { + var p **uint16 + if len(args) > 0 { + vs := make([]*uint16, len(args)) + for i := range vs { + vs[i] = syscall.StringToUTF16Ptr(args[i]) + } + p = &vs[0] + } + return windows.StartService(s.Handle, uint32(len(args)), p) +} + +// Control sends state change request c to the servce s. +func (s *Service) Control(c svc.Cmd) (svc.Status, error) { + var t windows.SERVICE_STATUS + err := windows.ControlService(s.Handle, uint32(c), &t) + if err != nil { + return svc.Status{}, err + } + return svc.Status{ + State: svc.State(t.CurrentState), + Accepts: svc.Accepted(t.ControlsAccepted), + }, nil +} + +// Query returns current status of service s. +func (s *Service) Query() (svc.Status, error) { + var t windows.SERVICE_STATUS + err := windows.QueryServiceStatus(s.Handle, &t) + if err != nil { + return svc.Status{}, err + } + return svc.Status{ + State: svc.State(t.CurrentState), + Accepts: svc.Accepted(t.ControlsAccepted), + }, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 9ed0853bec4..b8f6ac2f2a3 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -872,6 +872,7 @@ golang.org/x/sys/unix golang.org/x/sys/windows golang.org/x/sys/windows/registry golang.org/x/sys/windows/svc +golang.org/x/sys/windows/svc/mgr # golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db => golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db golang.org/x/text/encoding golang.org/x/text/encoding/charmap From 4d7ee1cd5a6594dd3d22fdb4a94ab84213f4be3e Mon Sep 17 00:00:00 2001 From: ksubrmnn Date: Tue, 21 May 2019 12:38:10 -0700 Subject: [PATCH 2/2] Use system calls to manage Windows services --- cmd/kubeadm/app/preflight/checks.go | 4 +- pkg/util/initsystem/BUILD | 17 +- pkg/util/initsystem/initsystem.go | 205 ------------------- pkg/util/initsystem/initsystem_unix.go | 162 +++++++++++++++ pkg/util/initsystem/initsystem_windows.go | 238 ++++++++++++++++++++++ 5 files changed, 414 insertions(+), 212 deletions(-) create mode 100644 pkg/util/initsystem/initsystem_unix.go create mode 100644 pkg/util/initsystem/initsystem_windows.go diff --git a/cmd/kubeadm/app/preflight/checks.go b/cmd/kubeadm/app/preflight/checks.go index 0586e68b566..5b6448e63f9 100644 --- a/cmd/kubeadm/app/preflight/checks.go +++ b/cmd/kubeadm/app/preflight/checks.go @@ -143,8 +143,8 @@ func (sc ServiceCheck) Check() (warnings, errorList []error) { if !initSystem.ServiceIsEnabled(sc.Service) { warnings = append(warnings, - errors.Errorf("%s service is not enabled, please run 'systemctl enable %s.service'", - sc.Service, sc.Service)) + errors.Errorf("%s service is not enabled, please run '%s'", + sc.Service, initSystem.EnableCommand(sc.Service))) } if sc.CheckIfActive && !initSystem.ServiceIsActive(sc.Service) { diff --git a/pkg/util/initsystem/BUILD b/pkg/util/initsystem/BUILD index 75f9cab8a1d..30ab9be3e18 100644 --- a/pkg/util/initsystem/BUILD +++ b/pkg/util/initsystem/BUILD @@ -7,12 +7,19 @@ load( go_library( name = "go_default_library", - srcs = ["initsystem.go"], - importpath = "k8s.io/kubernetes/pkg/util/initsystem", - deps = [ - "//vendor/golang.org/x/sys/windows/svc:go_default_library", - "//vendor/golang.org/x/sys/windows/svc/mgr:go_default_library", + srcs = [ + "initsystem.go", + "initsystem_unix.go", + "initsystem_windows.go", ], + importpath = "k8s.io/kubernetes/pkg/util/initsystem", + deps = select({ + "@io_bazel_rules_go//go/platform:windows": [ + "//vendor/golang.org/x/sys/windows/svc:go_default_library", + "//vendor/golang.org/x/sys/windows/svc/mgr:go_default_library", + ], + "//conditions:default": [], + }), ) filegroup( diff --git a/pkg/util/initsystem/initsystem.go b/pkg/util/initsystem/initsystem.go index da24217ebc3..49266c74f90 100644 --- a/pkg/util/initsystem/initsystem.go +++ b/pkg/util/initsystem/initsystem.go @@ -16,12 +16,6 @@ limitations under the License. package initsystem -import ( - "fmt" - "os/exec" - "strings" -) - type InitSystem interface { // return a string describing how to enable a service EnableCommand(service string) string @@ -44,202 +38,3 @@ type InitSystem interface { // ServiceIsActive ensures the service is running, or attempting to run. (crash looping in the case of kubelet) ServiceIsActive(service string) bool } - -type OpenRCInitSystem struct{} - -func (openrc OpenRCInitSystem) ServiceStart(service string) error { - args := []string{service, "start"} - return exec.Command("rc-service", args...).Run() -} - -func (openrc OpenRCInitSystem) ServiceStop(service string) error { - args := []string{service, "stop"} - return exec.Command("rc-service", args...).Run() -} - -func (openrc OpenRCInitSystem) ServiceRestart(service string) error { - args := []string{service, "restart"} - return exec.Command("rc-service", args...).Run() -} - -// openrc writes to stderr if a service is not found or not enabled -// this is in contrast to systemd which only writes to stdout. -// Hence, we use the Combinedoutput, and ignore the error. -func (openrc OpenRCInitSystem) ServiceExists(service string) bool { - args := []string{service, "status"} - outBytes, _ := exec.Command("rc-service", args...).CombinedOutput() - if strings.Contains(string(outBytes), "does not exist") { - return false - } - return true -} - -func (openrc OpenRCInitSystem) ServiceIsEnabled(service string) bool { - args := []string{"show", "default"} - outBytes, _ := exec.Command("rc-update", args...).Output() - if strings.Contains(string(outBytes), service) { - return true - } - return false -} - -func (openrc OpenRCInitSystem) ServiceIsActive(service string) bool { - args := []string{service, "status"} - outBytes, _ := exec.Command("rc-service", args...).Output() - if strings.Contains(string(outBytes), "stopped") { - return false - } - return true -} - -func (openrc OpenRCInitSystem) EnableCommand(service string) string { - return fmt.Sprintf("rc-update add %s default", service) -} - -type SystemdInitSystem struct{} - -func (sysd SystemdInitSystem) EnableCommand(service string) string { - return fmt.Sprintf("systemctl enable %s.service", service) -} - -func (sysd SystemdInitSystem) reloadSystemd() error { - if err := exec.Command("systemctl", "daemon-reload").Run(); err != nil { - return fmt.Errorf("failed to reload systemd: %v", err) - } - return nil -} - -func (sysd SystemdInitSystem) ServiceStart(service string) error { - // Before we try to start any service, make sure that systemd is ready - if err := sysd.reloadSystemd(); err != nil { - return err - } - args := []string{"start", service} - return exec.Command("systemctl", args...).Run() -} - -func (sysd SystemdInitSystem) ServiceRestart(service string) error { - // Before we try to restart any service, make sure that systemd is ready - if err := sysd.reloadSystemd(); err != nil { - return err - } - args := []string{"restart", service} - return exec.Command("systemctl", args...).Run() -} - -func (sysd SystemdInitSystem) ServiceStop(service string) error { - args := []string{"stop", service} - return exec.Command("systemctl", args...).Run() -} - -func (sysd SystemdInitSystem) ServiceExists(service string) bool { - args := []string{"status", service} - outBytes, _ := exec.Command("systemctl", args...).Output() - output := string(outBytes) - if strings.Contains(output, "Loaded: not-found") { - return false - } - return true -} - -func (sysd SystemdInitSystem) ServiceIsEnabled(service string) bool { - args := []string{"is-enabled", service} - err := exec.Command("systemctl", args...).Run() - if err != nil { - return false - } - return true -} - -// ServiceIsActive will check is the service is "active". In the case of -// crash looping services (kubelet in our case) status will return as -// "activating", so we will consider this active as well. -func (sysd SystemdInitSystem) ServiceIsActive(service string) bool { - args := []string{"is-active", service} - // Ignoring error here, command returns non-0 if in "activating" status: - outBytes, _ := exec.Command("systemctl", args...).Output() - output := strings.TrimSpace(string(outBytes)) - if output == "active" || output == "activating" { - return true - } - return false -} - -// WindowsInitSystem is the windows implementation of InitSystem -type WindowsInitSystem struct{} - -func (sysd WindowsInitSystem) EnableCommand(service string) string { - return fmt.Sprintf("Set-Service '%s' -StartupType Automatic", service) -} - -func (sysd WindowsInitSystem) ServiceStart(service string) error { - args := []string{"Start-Service", service} - err := exec.Command("powershell", args...).Run() - return err -} - -func (sysd WindowsInitSystem) ServiceRestart(service string) error { - if err := sysd.ServiceStop(service); err != nil { - return fmt.Errorf("couldn't stop service: %v", err) - } - if err := sysd.ServiceStart(service); err != nil { - return fmt.Errorf("couldn't start service: %v", err) - } - return nil -} - -func (sysd WindowsInitSystem) ServiceStop(service string) error { - args := []string{"Stop-Service", service} - err := exec.Command("powershell", args...).Run() - return err -} - -func (sysd WindowsInitSystem) ServiceExists(service string) bool { - args := []string{"Get-Service", service} - err := exec.Command("powershell", args...).Run() - if err != nil { - return false - } - return true - -} - -func (sysd WindowsInitSystem) ServiceIsEnabled(service string) bool { - args := []string{"Get-Service", service + "| select -property starttype"} - outBytes, _ := exec.Command("powershell", args...).Output() - output := strings.TrimSpace(string(outBytes)) - if strings.Contains(output, "Automatic") { - return true - } - return false -} - -func (sysd WindowsInitSystem) ServiceIsActive(service string) bool { - args := []string{"Get-Service", service + "| select -property status"} - outBytes, _ := exec.Command("powershell", args...).Output() - output := strings.TrimSpace(string(outBytes)) - if strings.Contains(output, "Running") { - return true - } - return false -} - -// GetInitSystem returns an InitSystem for the current system, or nil -// if we cannot detect a supported init system for pre-flight checks. -// This indicates we will skip init system checks, not an error. -func GetInitSystem() (InitSystem, error) { - // Assume existence of systemctl in path implies this is a systemd system: - _, err := exec.LookPath("systemctl") - if err == nil { - return &SystemdInitSystem{}, nil - } - _, err = exec.LookPath("openrc") - if err == nil { - return &OpenRCInitSystem{}, nil - } - _, err = exec.LookPath("wininit.exe") - if err == nil { - return &WindowsInitSystem{}, nil - } - return nil, fmt.Errorf("no supported init system detected, skipping checking for services") -} diff --git a/pkg/util/initsystem/initsystem_unix.go b/pkg/util/initsystem/initsystem_unix.go new file mode 100644 index 00000000000..b6c2f45c9cf --- /dev/null +++ b/pkg/util/initsystem/initsystem_unix.go @@ -0,0 +1,162 @@ +// +build !windows + +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package initsystem + +import ( + "fmt" + "os/exec" + "strings" +) + +type OpenRCInitSystem struct{} + +func (openrc OpenRCInitSystem) ServiceStart(service string) error { + args := []string{service, "start"} + return exec.Command("rc-service", args...).Run() +} + +func (openrc OpenRCInitSystem) ServiceStop(service string) error { + args := []string{service, "stop"} + return exec.Command("rc-service", args...).Run() +} + +func (openrc OpenRCInitSystem) ServiceRestart(service string) error { + args := []string{service, "restart"} + return exec.Command("rc-service", args...).Run() +} + +// openrc writes to stderr if a service is not found or not enabled +// this is in contrast to systemd which only writes to stdout. +// Hence, we use the Combinedoutput, and ignore the error. +func (openrc OpenRCInitSystem) ServiceExists(service string) bool { + args := []string{service, "status"} + outBytes, _ := exec.Command("rc-service", args...).CombinedOutput() + if strings.Contains(string(outBytes), "does not exist") { + return false + } + return true +} + +func (openrc OpenRCInitSystem) ServiceIsEnabled(service string) bool { + args := []string{"show", "default"} + outBytes, _ := exec.Command("rc-update", args...).Output() + if strings.Contains(string(outBytes), service) { + return true + } + return false +} + +func (openrc OpenRCInitSystem) ServiceIsActive(service string) bool { + args := []string{service, "status"} + outBytes, _ := exec.Command("rc-service", args...).Output() + if strings.Contains(string(outBytes), "stopped") { + return false + } + return true +} + +func (openrc OpenRCInitSystem) EnableCommand(service string) string { + return fmt.Sprintf("rc-update add %s default", service) +} + +type SystemdInitSystem struct{} + +func (sysd SystemdInitSystem) EnableCommand(service string) string { + return fmt.Sprintf("systemctl enable %s.service", service) +} + +func (sysd SystemdInitSystem) reloadSystemd() error { + if err := exec.Command("systemctl", "daemon-reload").Run(); err != nil { + return fmt.Errorf("failed to reload systemd: %v", err) + } + return nil +} + +func (sysd SystemdInitSystem) ServiceStart(service string) error { + // Before we try to start any service, make sure that systemd is ready + if err := sysd.reloadSystemd(); err != nil { + return err + } + args := []string{"start", service} + return exec.Command("systemctl", args...).Run() +} + +func (sysd SystemdInitSystem) ServiceRestart(service string) error { + // Before we try to restart any service, make sure that systemd is ready + if err := sysd.reloadSystemd(); err != nil { + return err + } + args := []string{"restart", service} + return exec.Command("systemctl", args...).Run() +} + +func (sysd SystemdInitSystem) ServiceStop(service string) error { + args := []string{"stop", service} + return exec.Command("systemctl", args...).Run() +} + +func (sysd SystemdInitSystem) ServiceExists(service string) bool { + args := []string{"status", service} + outBytes, _ := exec.Command("systemctl", args...).Output() + output := string(outBytes) + if strings.Contains(output, "Loaded: not-found") { + return false + } + return true +} + +func (sysd SystemdInitSystem) ServiceIsEnabled(service string) bool { + args := []string{"is-enabled", service} + err := exec.Command("systemctl", args...).Run() + if err != nil { + return false + } + return true +} + +// ServiceIsActive will check is the service is "active". In the case of +// crash looping services (kubelet in our case) status will return as +// "activating", so we will consider this active as well. +func (sysd SystemdInitSystem) ServiceIsActive(service string) bool { + args := []string{"is-active", service} + // Ignoring error here, command returns non-0 if in "activating" status: + outBytes, _ := exec.Command("systemctl", args...).Output() + output := strings.TrimSpace(string(outBytes)) + if output == "active" || output == "activating" { + return true + } + return false +} + +// GetInitSystem returns an InitSystem for the current system, or nil +// if we cannot detect a supported init system. +// This indicates we will skip init system checks, not an error. +func GetInitSystem() (InitSystem, error) { + // Assume existence of systemctl in path implies this is a systemd system: + _, err := exec.LookPath("systemctl") + if err == nil { + return &SystemdInitSystem{}, nil + } + _, err = exec.LookPath("openrc") + if err == nil { + return &OpenRCInitSystem{}, nil + } + + return nil, fmt.Errorf("no supported init system detected, skipping checking for services") +} diff --git a/pkg/util/initsystem/initsystem_windows.go b/pkg/util/initsystem/initsystem_windows.go new file mode 100644 index 00000000000..ca819450ad9 --- /dev/null +++ b/pkg/util/initsystem/initsystem_windows.go @@ -0,0 +1,238 @@ +// +build windows + +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package initsystem + +import ( + "fmt" + "time" + + "golang.org/x/sys/windows/svc" + "golang.org/x/sys/windows/svc/mgr" +) + +// WindowsInitSystem is the windows implementation of InitSystem +type WindowsInitSystem struct{} + +func (sysd WindowsInitSystem) EnableCommand(service string) string { + return fmt.Sprintf("Set-Service '%s' -StartupType Automatic", service) +} + +// Following Windows documentation: https://docs.microsoft.com/en-us/windows/desktop/Services/starting-a-service +func (sysd WindowsInitSystem) ServiceStart(service string) error { + m, err := mgr.Connect() + if err != nil { + return err + } + defer m.Disconnect() + + s, err := m.OpenService(service) + if err != nil { + return fmt.Errorf("could not access service %s: %v", service, err) + } + defer s.Close() + + // Check if service is already started + status, err := s.Query() + if err != nil { + return fmt.Errorf("could not query service %s: %v", service, err) + } + + if status.State != svc.Stopped && status.State != svc.StopPending { + return nil + } + + timeout := time.Now().Add(10 * time.Second) + for status.State != svc.Stopped { + if timeout.Before(time.Now()) { + return fmt.Errorf("timeout waiting for %s service to stop", service) + } + time.Sleep(300 * time.Millisecond) + status, err = s.Query() + if err != nil { + return fmt.Errorf("could not retrieve %s service status: %v", service, err) + } + } + + // Start the service + err = s.Start("is", "manual-started") + if err != nil { + return fmt.Errorf("could not start service %s: %v", service, err) + } + + // Check that the start was successful + status, err = s.Query() + if err != nil { + return fmt.Errorf("could not query service %s: %v", service, err) + } + timeout = time.Now().Add(10 * time.Second) + for status.State != svc.Running { + if timeout.Before(time.Now()) { + return fmt.Errorf("timeout waiting for %s service to start", service) + } + time.Sleep(300 * time.Millisecond) + status, err = s.Query() + if err != nil { + return fmt.Errorf("could not retrieve %s service status: %v", service, err) + } + } + return nil +} + +func (sysd WindowsInitSystem) ServiceRestart(service string) error { + if err := sysd.ServiceStop(service); err != nil { + return fmt.Errorf("couldn't stop service %s: %v", service, err) + } + if err := sysd.ServiceStart(service); err != nil { + return fmt.Errorf("couldn't start service %s: %v", service, err) + } + + return nil +} + +// Following Windows documentation: https://docs.microsoft.com/en-us/windows/desktop/Services/stopping-a-service +func (sysd WindowsInitSystem) ServiceStop(service string) error { + m, err := mgr.Connect() + if err != nil { + return err + } + defer m.Disconnect() + + s, err := m.OpenService(service) + if err != nil { + return fmt.Errorf("could not access service %s: %v", service, err) + } + defer s.Close() + + // Check if service is already stopped + status, err := s.Query() + if err != nil { + return fmt.Errorf("could not query service %s: %v", service, err) + } + + if status.State == svc.Stopped { + return nil + } + + // If StopPending, check that service eventually stops + if status.State == svc.StopPending { + timeout := time.Now().Add(10 * time.Second) + for status.State != svc.Stopped { + if timeout.Before(time.Now()) { + return fmt.Errorf("timeout waiting for %s service to stop", service) + } + time.Sleep(300 * time.Millisecond) + status, err = s.Query() + if err != nil { + return fmt.Errorf("could not retrieve %s service status: %v", service, err) + } + } + return nil + } + + // Stop the service + status, err = s.Control(svc.Stop) + if err != nil { + return fmt.Errorf("could not stop service %s: %v", service, err) + } + + // Check that the stop was successful + status, err = s.Query() + if err != nil { + return fmt.Errorf("could not query service %s: %v", service, err) + } + timeout := time.Now().Add(10 * time.Second) + for status.State != svc.Stopped { + if timeout.Before(time.Now()) { + return fmt.Errorf("timeout waiting for %s service to stop", service) + } + time.Sleep(300 * time.Millisecond) + status, err = s.Query() + if err != nil { + return fmt.Errorf("could not retrieve %s service status: %v", service, err) + } + } + return nil +} + +func (sysd WindowsInitSystem) ServiceExists(service string) bool { + m, err := mgr.Connect() + if err != nil { + return false + } + defer m.Disconnect() + s, err := m.OpenService(service) + if err != nil { + return false + } + defer s.Close() + + return true +} + +func (sysd WindowsInitSystem) ServiceIsEnabled(service string) bool { + m, err := mgr.Connect() + if err != nil { + return false + } + defer m.Disconnect() + + s, err := m.OpenService(service) + if err != nil { + return false + } + defer s.Close() + + c, err := s.Config() + if err != nil { + return false + } + + return c.StartType != mgr.StartDisabled +} + +func (sysd WindowsInitSystem) ServiceIsActive(service string) bool { + m, err := mgr.Connect() + if err != nil { + return false + } + defer m.Disconnect() + s, err := m.OpenService(service) + if err != nil { + return false + } + defer s.Close() + + status, err := s.Query() + if err != nil { + return false + } + return status.State == svc.Running +} + +// GetInitSystem returns an InitSystem for the current system, or nil +// if we cannot detect a supported init system. +// This indicates we will skip init system checks, not an error. +func GetInitSystem() (InitSystem, error) { + m, err := mgr.Connect() + if err != nil { + return nil, fmt.Errorf("no supported init system detected: %v", err) + } + defer m.Disconnect() + return &WindowsInitSystem{}, nil +}