diff --git a/cmd/apiserver-boot/boot/run/local.go b/cmd/apiserver-boot/boot/run/local.go index 967d0b55a0..6ccbcb37da 100644 --- a/cmd/apiserver-boot/boot/run/local.go +++ b/cmd/apiserver-boot/boot/run/local.go @@ -17,6 +17,7 @@ limitations under the License. package run import ( + "context" "fmt" "log" "os" @@ -100,6 +101,10 @@ func RunLocal(cmd *cobra.Command, args []string) { WriteKubeConfig() + // parent context to indicate whether cmds quit + ctx, cancel := context.WithCancel(context.Background()) + ctx = util.CancelWhenSignaled(ctx) + r := map[string]interface{}{} for _, s := range toRun { r[s] = nil @@ -108,46 +113,38 @@ func RunLocal(cmd *cobra.Command, args []string) { // Start etcd if _, f := r["etcd"]; f { etcd = "http://localhost:2379" - etcdCmd := RunEtcd() - defer etcdCmd.Process.Kill() + RunEtcd(ctx, cancel) time.Sleep(time.Second * 2) } // Start apiserver if _, f := r["apiserver"]; f { - go RunApiserver() + RunApiserver(ctx, cancel) time.Sleep(time.Second * 2) } // Start controller manager if _, f := r["controller-manager"]; f { - go RunControllerManager() + RunControllerManager(ctx, cancel) } fmt.Printf("to test the server run `kubectl --kubeconfig %s api-versions`\n", config) - select {} // wait forever + <-ctx.Done() // wait forever } -func RunEtcd() *exec.Cmd { +func RunEtcd(ctx context.Context, cancel context.CancelFunc) *exec.Cmd { etcdCmd := exec.Command("etcd") if printetcd { etcdCmd.Stderr = os.Stderr etcdCmd.Stdout = os.Stdout } - fmt.Printf("%s\n", strings.Join(etcdCmd.Args, " ")) - go func() { - err := etcdCmd.Run() - defer etcdCmd.Process.Kill() - if err != nil { - log.Fatalf("Failed to run etcd %v", err) - os.Exit(-1) - } - }() + go runCommon(etcdCmd, ctx, cancel) + return etcdCmd } -func RunApiserver() *exec.Cmd { +func RunApiserver(ctx context.Context, cancel context.CancelFunc) *exec.Cmd { if len(server) == 0 { server = "bin/apiserver" } @@ -166,45 +163,62 @@ func RunApiserver() *exec.Cmd { apiserverCmd := exec.Command(server, flags..., ) - fmt.Printf("%s\n", strings.Join(apiserverCmd.Args, " ")) if printapiserver { apiserverCmd.Stderr = os.Stderr apiserverCmd.Stdout = os.Stdout } - err := apiserverCmd.Run() - if err != nil { - defer apiserverCmd.Process.Kill() - log.Fatalf("Failed to run apiserver %v", err) - os.Exit(-1) - } + go runCommon(apiserverCmd, ctx, cancel) return apiserverCmd } -func RunControllerManager() *exec.Cmd { +func RunControllerManager(ctx context.Context, cancel context.CancelFunc) *exec.Cmd { if len(controllermanager) == 0 { controllermanager = "bin/controller-manager" } + controllerManagerCmd := exec.Command(controllermanager, fmt.Sprintf("--kubeconfig=%s", config), ) - fmt.Printf("%s\n", strings.Join(controllerManagerCmd.Args, " ")) if printcontrollermanager { controllerManagerCmd.Stderr = os.Stderr controllerManagerCmd.Stdout = os.Stdout } - err := controllerManagerCmd.Run() - if err != nil { - defer controllerManagerCmd.Process.Kill() - log.Fatalf("Failed to run controller-manager %v", err) - os.Exit(-1) - } + go runCommon(controllerManagerCmd, ctx, cancel) return controllerManagerCmd } +// run a command via goroutine +func runCommon(cmd *exec.Cmd, ctx context.Context, cancel context.CancelFunc) { + stopCh := make(chan error) + cmdName := cmd.Args[0] + + fmt.Printf("%s\n", strings.Join(cmd.Args, " ")) + go func() { + err := cmd.Run() + if err != nil { + log.Printf("Failed to run %s, error: %v\n", cmdName, err) + } else { + log.Printf("Command %s quitted normally\n", cmdName) + } + stopCh <- err + }() + + select { + case <-stopCh: + // my command quited + cancel() + case <-ctx.Done(): + // other commands quited + if cmd.Process != nil { + cmd.Process.Kill() + } + } +} + func WriteKubeConfig() { // Write a kubeconfig dir, err := os.Getwd() diff --git a/cmd/apiserver-boot/boot/run/local_aggregated.go b/cmd/apiserver-boot/boot/run/local_aggregated.go index 4591c27f0d..5e50a702ee 100644 --- a/cmd/apiserver-boot/boot/run/local_aggregated.go +++ b/cmd/apiserver-boot/boot/run/local_aggregated.go @@ -17,6 +17,7 @@ limitations under the License. package run import ( + "context" "fmt" "log" "os" @@ -26,9 +27,10 @@ import ( "time" "github.com/spf13/cobra" + "k8s.io/client-go/util/homedir" "sigs.k8s.io/apiserver-builder-alpha/cmd/apiserver-boot/boot/build" - "k8s.io/client-go/util/homedir" + "sigs.k8s.io/apiserver-builder-alpha/cmd/apiserver-boot/boot/util" ) var localMinikubeCmd = &cobra.Command{ @@ -93,6 +95,10 @@ func RunLocalMinikube(cmd *cobra.Command, args []string) { build.RunBuildExecutables(cmd, args) } + // parent context to indicate whether cmds quit + ctx, cancel := context.WithCancel(context.Background()) + ctx = util.CancelWhenSignaled(ctx) + r := map[string]interface{}{} for _, s := range toRun { r[s] = nil @@ -112,24 +118,23 @@ func RunLocalMinikube(cmd *cobra.Command, args []string) { // Start etcd if _, f := r["etcd"]; f { etcd = "http://localhost:2379" - etcdCmd := RunEtcd() - defer etcdCmd.Process.Kill() + RunEtcd(ctx, cancel) time.Sleep(time.Second * 2) } // Start apiserver if _, f := r["apiserver"]; f { - go RunApiserverMinikube() + RunApiserver(ctx, cancel) time.Sleep(time.Second * 2) } // Start controller manager if _, f := r["controller-manager"]; f { - go RunControllerManager() + RunControllerManager(ctx, cancel) } fmt.Printf("to test the server run `kubectl api-versions`, if you specified --kubeconfig you must also provide the flag `--kubeconfig %s`\n", config) - select {} // wait forever + <-ctx.Done() // wait forever } func RunApiserverMinikube() *exec.Cmd { diff --git a/cmd/apiserver-boot/boot/util/util.go b/cmd/apiserver-boot/boot/util/util.go index b78d3f2336..04a5f48d06 100644 --- a/cmd/apiserver-boot/boot/util/util.go +++ b/cmd/apiserver-boot/boot/util/util.go @@ -17,6 +17,7 @@ limitations under the License. package util import ( + "context" "fmt" "io/ioutil" "log" @@ -28,6 +29,7 @@ import ( "text/template" "github.com/markbates/inflect" + "k8s.io/apiserver/pkg/server" ) var Domain string @@ -139,3 +141,15 @@ func CheckInstall() { strings.Join(missing, ",")) } } + +func CancelWhenSignaled(parent context.Context) context.Context { + ctx, cancel := context.WithCancel(parent) + + go func() { + signalChannel := server.SetupSignalHandler() + <-signalChannel + cancel() + }() + + return ctx +}