From 4d7ee1cd5a6594dd3d22fdb4a94ab84213f4be3e Mon Sep 17 00:00:00 2001 From: ksubrmnn Date: Tue, 21 May 2019 12:38:10 -0700 Subject: [PATCH] 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 +}