1 //go:build windows 2 // +build windows 3 4 /* 5 Copyright 2018 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 service 21 22 import ( 23 "os" 24 "time" 25 26 "k8s.io/apiserver/pkg/server" 27 "k8s.io/klog/v2" 28 29 "golang.org/x/sys/windows" 30 "golang.org/x/sys/windows/svc" 31 ) 32 33 type handler struct { 34 tosvc chan bool 35 fromsvc chan error 36 } 37 38 // InitService is the entry point for running the daemon as a Windows 39 // service. It returns an indication of whether it is running as a service; 40 // and an error. 41 func InitService(serviceName string) error { 42 h := &handler{ 43 tosvc: make(chan bool), 44 fromsvc: make(chan error), 45 } 46 47 var err error 48 go func() { 49 err = svc.Run(serviceName, h) 50 h.fromsvc <- err 51 }() 52 53 // Wait for the first signal from the service handler. 54 err = <-h.fromsvc 55 if err != nil { 56 return err 57 } 58 klog.Infof("Running %s as a Windows service!", serviceName) 59 return nil 60 } 61 62 func (h *handler) Execute(_ []string, r <-chan svc.ChangeRequest, s chan<- svc.Status) (bool, uint32) { 63 s <- svc.Status{State: svc.StartPending, Accepts: 0} 64 // Unblock initService() 65 h.fromsvc <- nil 66 67 s <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown | svc.Accepted(windows.SERVICE_ACCEPT_PARAMCHANGE)} 68 klog.Infof("Service running") 69 Loop: 70 for { 71 select { 72 case <-h.tosvc: 73 break Loop 74 case c := <-r: 75 switch c.Cmd { 76 case svc.Cmd(windows.SERVICE_CONTROL_PARAMCHANGE): 77 s <- c.CurrentStatus 78 case svc.Interrogate: 79 s <- c.CurrentStatus 80 case svc.Stop, svc.Shutdown: 81 klog.Infof("Service stopping") 82 // We need to translate this request into a signal that can be handled by the signal handler 83 // handling shutdowns normally (currently apiserver/pkg/server/signal.go). 84 // If we do not do this, our main threads won't be notified of the upcoming shutdown. 85 // Since Windows services do not use any console, we cannot simply generate a CTRL_BREAK_EVENT 86 // but need a dedicated notification mechanism. 87 graceful := server.RequestShutdown() 88 89 // Free up the control handler and let us terminate as gracefully as possible. 90 // If that takes too long, the service controller will kill the remaining threads. 91 // As per https://docs.microsoft.com/en-us/windows/desktop/services/service-control-handler-function 92 s <- svc.Status{State: svc.StopPending} 93 94 // If we cannot exit gracefully, we really only can exit our process, so at least the 95 // service manager will think that we gracefully exited. At the time of writing this comment this is 96 // needed for applications that do not use signals (e.g. kube-proxy) 97 if !graceful { 98 go func() { 99 // Ensure the SCM was notified (The operation above (send to s) was received and communicated to the 100 // service control manager - so it doesn't look like the service crashes) 101 time.Sleep(1 * time.Second) 102 os.Exit(0) 103 }() 104 } 105 break Loop 106 } 107 } 108 } 109 110 return false, 0 111 } 112