From 4881f5fafdd38d6a79ee10e7532f675faa8b2125 Mon Sep 17 00:00:00 2001 From: Dan Lorenc Date: Tue, 26 Apr 2016 11:28:17 -0700 Subject: [PATCH 1/2] Add stop command and tests. Fix bug in start command where stopped hosts weren't started. --- cli/cluster/cluster.go | 27 ++++++++++++++-- cli/cluster/cluster_test.go | 48 +++++++++++++++++++++++++++++ cli/cmd/stop.go | 61 +++++++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 cli/cmd/stop.go diff --git a/cli/cluster/cluster.go b/cli/cluster/cluster.go index 590462113b5a..2d9bed96a2bb 100644 --- a/cli/cluster/cluster.go +++ b/cli/cluster/cluster.go @@ -22,6 +22,7 @@ import ( "github.com/docker/machine/drivers/virtualbox" "github.com/docker/machine/libmachine" "github.com/docker/machine/libmachine/host" + "github.com/docker/machine/libmachine/state" "github.com/kubernetes/minikube/cli/constants" ) @@ -33,7 +34,16 @@ func StartHost(api libmachine.API) (*host.Host, error) { log.Println("Machine exists!") h, err := api.Load(constants.MachineName) if err != nil { - return nil, fmt.Errorf("Error loading existing host.") + return nil, fmt.Errorf("Error loading existing host: %s", err) + } + s, err := h.Driver.GetState() + if err != nil { + return nil, fmt.Errorf("Error getting state for host: %s", err) + } + if s != state.Running { + if err := h.Driver.Start(); err != nil { + return nil, fmt.Errorf("Error starting stopped host: %s", err) + } } return h, nil } else { @@ -41,6 +51,18 @@ func StartHost(api libmachine.API) (*host.Host, error) { } } +// StopHost stops the host VM. +func StopHost(api libmachine.API) error { + host, err := api.Load(constants.MachineName) + if err != nil { + return err + } + if err := host.Stop(); err != nil { + return err + } + return nil +} + type sshAble interface { RunSSHCommand(string) (string, error) } @@ -49,8 +71,7 @@ type sshAble interface { func StartCluster(h sshAble) error { for _, cmd := range []string{ // Download and install weave, if it doesn't exist. - `if [ ! -e /usr/local/bin/weave ]; - then + `if [ ! -e /usr/local/bin/weave ]; then sudo curl -L git.io/weave -o /usr/local/bin/weave sudo chmod a+x /usr/local/bin/weave; fi`, diff --git a/cli/cluster/cluster_test.go b/cli/cluster/cluster_test.go index 690a5f1d940f..76245961257d 100644 --- a/cli/cluster/cluster_test.go +++ b/cli/cluster/cluster_test.go @@ -93,6 +93,33 @@ func TestStartHostExists(t *testing.T) { if h.Name != constants.MachineName { t.Fatalf("Machine created with incorrect name: %s", h.Name) } + if s, _ := h.Driver.GetState(); s != state.Running { + t.Fatalf("Machine not started.") + } +} + +func TestStartStoppedHost(t *testing.T) { + api := &tests.MockAPI{} + // Create an initial host. + h, err := createHost(api) + if err != nil { + t.Fatalf("Error creating host: %v", err) + } + d := tests.MockDriver{} + h.Driver = &d + d.CurrentState = state.Stopped + + h, err = StartHost(api) + if err != nil { + t.Fatal("Error starting host.") + } + if h.Name != constants.MachineName { + t.Fatalf("Machine created with incorrect name: %s", h.Name) + } + + if s, _ := h.Driver.GetState(); s != state.Running { + t.Fatalf("Machine not started.") + } } func TestStartHost(t *testing.T) { @@ -108,4 +135,25 @@ func TestStartHost(t *testing.T) { if exists, _ := api.Exists(h.Name); !exists { t.Fatal("Machine not saved.") } + if s, _ := h.Driver.GetState(); s != state.Running { + t.Fatalf("Machine not started.") + } +} + +func TestStopHostError(t *testing.T) { + api := &tests.MockAPI{} + if err := StopHost(api); err == nil { + t.Fatal("An error should be thrown when stopping non-existing machine.") + } +} + +func TestStopHost(t *testing.T) { + api := &tests.MockAPI{} + h, _ := createHost(api) + if err := StopHost(api); err != nil { + t.Fatal("An error should be thrown when stopping non-existing machine.") + } + if s, _ := h.Driver.GetState(); s != state.Stopped { + t.Fatalf("Machine not stopped. Currently in state: %s", s) + } } diff --git a/cli/cmd/stop.go b/cli/cmd/stop.go new file mode 100644 index 000000000000..d37e584bf4ab --- /dev/null +++ b/cli/cmd/stop.go @@ -0,0 +1,61 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. +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 cmd + +import ( + "fmt" + + "github.com/docker/machine/libmachine" + "github.com/kubernetes/minikube/cli/cluster" + "github.com/kubernetes/minikube/cli/constants" + "github.com/spf13/cobra" +) + +// stopCmd represents the stop command +var stopCmd = &cobra.Command{ + Use: "stop", + Short: "A brief description of your command", + Long: `A longer description that spans multiple lines and likely contains examples +and usage of using your command. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Stopping local Kubernetes cluster...") + api := libmachine.NewClient(constants.Minipath, constants.MakeMiniPath("certs")) + defer api.Close() + + if err := cluster.StopHost(api); err != nil { + fmt.Println("Error stopping machine: %s", err) + os.Exit(1) + } + fmt.Println("Machine stopped.") + }, +} + +func init() { + RootCmd.AddCommand(stopCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // stopCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // stopCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + +} From dc3b8e761fda7a9952f536e77ece8d0c72aaa6f1 Mon Sep 17 00:00:00 2001 From: Dan Lorenc Date: Tue, 26 Apr 2016 12:50:38 -0700 Subject: [PATCH 2/2] Add delete command and tests. --- cli/cluster/cluster.go | 35 +++++++++++++++++ cli/cluster/cluster_test.go | 75 +++++++++++++++++++++++++++++++++++++ cli/cmd/delete.go | 48 ++++++++++++++++++++++++ cli/cmd/stop.go | 23 ++---------- cli/tests/api_mock.go | 5 +++ cli/tests/driver_mock.go | 6 +++ coverage.out | 31 --------------- 7 files changed, 173 insertions(+), 50 deletions(-) create mode 100644 cli/cmd/delete.go delete mode 100644 coverage.out diff --git a/cli/cluster/cluster.go b/cli/cluster/cluster.go index 2d9bed96a2bb..64a8b76a29ce 100644 --- a/cli/cluster/cluster.go +++ b/cli/cluster/cluster.go @@ -17,6 +17,7 @@ import ( "encoding/json" "fmt" "log" + "strings" "time" "github.com/docker/machine/drivers/virtualbox" @@ -63,6 +64,40 @@ func StopHost(api libmachine.API) error { return nil } +type multiError struct { + Errors []error +} + +func (m *multiError) Collect(err error) { + if err != nil { + m.Errors = append(m.Errors, err) + } +} + +func (m multiError) ToError() error { + if len(m.Errors) == 0 { + return nil + } + + errStrings := []string{} + for _, err := range m.Errors { + errStrings = append(errStrings, err.Error()) + } + return fmt.Errorf(strings.Join(errStrings, "\n")) +} + +// DeleteHost deletes the host VM. +func DeleteHost(api libmachine.API) error { + host, err := api.Load(constants.MachineName) + if err != nil { + return err + } + m := multiError{} + m.Collect(host.Driver.Remove()) + m.Collect(api.Remove(constants.MachineName)) + return m.ToError() +} + type sshAble interface { RunSSHCommand(string) (string, error) } diff --git a/cli/cluster/cluster_test.go b/cli/cluster/cluster_test.go index 76245961257d..9e6624d54f1c 100644 --- a/cli/cluster/cluster_test.go +++ b/cli/cluster/cluster_test.go @@ -2,6 +2,7 @@ package cluster import ( "fmt" + "strings" "testing" "github.com/docker/machine/libmachine/host" @@ -157,3 +158,77 @@ func TestStopHost(t *testing.T) { t.Fatalf("Machine not stopped. Currently in state: %s", s) } } + +func TestMultiError(t *testing.T) { + m := multiError{} + + m.Collect(fmt.Errorf("Error 1")) + m.Collect(fmt.Errorf("Error 2")) + + err := m.ToError() + expected := `Error 1 +Error 2` + if err.Error() != expected { + t.Fatalf("%s != %s", err, expected) + } + + m = multiError{} + if err := m.ToError(); err != nil { + t.Fatalf("Unexpected error: %s", err) + } +} + +func TestDeleteHost(t *testing.T) { + api := &tests.MockAPI{} + createHost(api) + + if err := DeleteHost(api); err != nil { + t.Fatalf("Unexpected error deleting host: %s", err) + } +} + +func TestDeleteHostErrorDeletingVM(t *testing.T) { + api := &tests.MockAPI{} + h, _ := createHost(api) + + d := &tests.MockDriver{RemoveError: true} + + h.Driver = d + + if err := DeleteHost(api); err == nil { + t.Fatal("Expected error deleting host.") + } +} + +func TestDeleteHostErrorDeletingFiles(t *testing.T) { + api := &tests.MockAPI{RemoveError: true} + createHost(api) + + if err := DeleteHost(api); err == nil { + t.Fatal("Expected error deleting host.") + } +} + +func TestDeleteHostMultipleErrors(t *testing.T) { + api := &tests.MockAPI{ + RemoveError: true, + } + h, _ := createHost(api) + + d := &tests.MockDriver{RemoveError: true} + + h.Driver = d + + err := DeleteHost(api) + + if err == nil { + t.Fatal("Expected error deleting host, didn't get one.") + } + + expectedErrors := []string{"Error removing minikubeVM", "Error deleting machine"} + for _, expectedError := range expectedErrors { + if !strings.Contains(err.Error(), expectedError) { + t.Fatalf("Error %s expected to contain: %s. ", err) + } + } +} diff --git a/cli/cmd/delete.go b/cli/cmd/delete.go new file mode 100644 index 000000000000..83bf76d39bac --- /dev/null +++ b/cli/cmd/delete.go @@ -0,0 +1,48 @@ +// Copyright © 2016 NAME HERE +// +// 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 cmd + +import ( + "fmt" + "os" + + "github.com/docker/machine/libmachine" + "github.com/kubernetes/minikube/cli/cluster" + "github.com/kubernetes/minikube/cli/constants" + "github.com/spf13/cobra" +) + +// deleteCmd represents the delete command +var deleteCmd = &cobra.Command{ + Use: "delete", + Short: "Deletes a local kubernetes cluster.", + Long: `Deletes a local kubernetes cluster. This command deletes the VM, and removes all +associated files.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Deleting local Kubernetes cluster...") + api := libmachine.NewClient(constants.Minipath, constants.MakeMiniPath("certs")) + defer api.Close() + + if err := cluster.DeleteHost(api); err != nil { + fmt.Println("Errors occurred deleting machine: ", err) + os.Exit(1) + } + fmt.Println("Machine deleted.") + }, +} + +func init() { + RootCmd.AddCommand(deleteCmd) +} diff --git a/cli/cmd/stop.go b/cli/cmd/stop.go index ca2c24a714eb..c6a1f2a41c30 100644 --- a/cli/cmd/stop.go +++ b/cli/cmd/stop.go @@ -26,20 +26,16 @@ import ( // stopCmd represents the stop command var stopCmd = &cobra.Command{ Use: "stop", - Short: "A brief description of your command", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, + Short: "Stops a running local kubernetes cluster.", + Long: `Stops a local kubernetes cluster running in Virtualbox. This command stops the VM +itself, leaving all files intact. The cluster can be started again with the "start" command.`, Run: func(cmd *cobra.Command, args []string) { fmt.Println("Stopping local Kubernetes cluster...") api := libmachine.NewClient(constants.Minipath, constants.MakeMiniPath("certs")) defer api.Close() if err := cluster.StopHost(api); err != nil { - fmt.Println("Error stopping machine: %s", err) + fmt.Println("Error stopping machine: ", err) os.Exit(1) } fmt.Println("Machine stopped.") @@ -48,15 +44,4 @@ to quickly create a Cobra application.`, func init() { RootCmd.AddCommand(stopCmd) - - // Here you will define your flags and configuration settings. - - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // stopCmd.PersistentFlags().String("foo", "", "A help for foo") - - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // stopCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") - } diff --git a/cli/tests/api_mock.go b/cli/tests/api_mock.go index 0f35f57161ca..4fa18ec50da6 100644 --- a/cli/tests/api_mock.go +++ b/cli/tests/api_mock.go @@ -15,6 +15,7 @@ import ( type MockAPI struct { Hosts []*host.Host CreateError bool + RemoveError bool } // Close closes the API. @@ -77,6 +78,10 @@ func (api *MockAPI) Load(name string) (*host.Host, error) { // Remove a host. func (api *MockAPI) Remove(name string) error { + if api.RemoveError { + return fmt.Errorf("Error removing %s", name) + } + newHosts := []*host.Host{} for _, host := range api.Hosts { diff --git a/cli/tests/driver_mock.go b/cli/tests/driver_mock.go index f7e904b4a999..5e7b10d615a9 100644 --- a/cli/tests/driver_mock.go +++ b/cli/tests/driver_mock.go @@ -1,6 +1,8 @@ package tests import ( + "fmt" + "github.com/docker/machine/libmachine/drivers" "github.com/docker/machine/libmachine/mcnflag" "github.com/docker/machine/libmachine/state" @@ -10,6 +12,7 @@ import ( type MockDriver struct { drivers.BaseDriver CurrentState state.State + RemoveError bool } // Create creates a MockDriver instance @@ -46,6 +49,9 @@ func (driver *MockDriver) Kill() error { // Remove removes the machine func (driver *MockDriver) Remove() error { + if driver.RemoveError { + return fmt.Errorf("Error deleting machine.") + } return nil } diff --git a/coverage.out b/coverage.out deleted file mode 100644 index 0224ac0336c7..000000000000 --- a/coverage.out +++ /dev/null @@ -1,31 +0,0 @@ -mode: set -github.com/kubernetes/minikube/cli/cluster/cluster.go:30.56,31.66 1 1 -github.com/kubernetes/minikube/cli/cluster/cluster.go:31.66,33.3 1 0 -github.com/kubernetes/minikube/cli/cluster/cluster.go:33.3,33.19 1 1 -github.com/kubernetes/minikube/cli/cluster/cluster.go:33.19,36.17 3 1 -github.com/kubernetes/minikube/cli/cluster/cluster.go:39.3,40.17 2 1 -github.com/kubernetes/minikube/cli/cluster/cluster.go:43.3,43.25 1 1 -github.com/kubernetes/minikube/cli/cluster/cluster.go:48.3,48.16 1 1 -github.com/kubernetes/minikube/cli/cluster/cluster.go:36.17,38.4 1 0 -github.com/kubernetes/minikube/cli/cluster/cluster.go:40.17,42.4 1 0 -github.com/kubernetes/minikube/cli/cluster/cluster.go:43.25,44.43 1 1 -github.com/kubernetes/minikube/cli/cluster/cluster.go:44.43,46.5 1 0 -github.com/kubernetes/minikube/cli/cluster/cluster.go:49.3,51.3 1 1 -github.com/kubernetes/minikube/cli/cluster/cluster.go:55.41,57.16 2 1 -github.com/kubernetes/minikube/cli/cluster/cluster.go:60.2,60.36 1 1 -github.com/kubernetes/minikube/cli/cluster/cluster.go:63.2,63.12 1 1 -github.com/kubernetes/minikube/cli/cluster/cluster.go:57.16,59.3 1 1 -github.com/kubernetes/minikube/cli/cluster/cluster.go:60.36,62.3 1 0 -github.com/kubernetes/minikube/cli/cluster/cluster.go:71.36,91.140 1 1 -github.com/kubernetes/minikube/cli/cluster/cluster.go:99.2,99.12 1 1 -github.com/kubernetes/minikube/cli/cluster/cluster.go:91.140,94.17 3 1 -github.com/kubernetes/minikube/cli/cluster/cluster.go:94.17,96.4 1 1 -github.com/kubernetes/minikube/cli/cluster/cluster.go:102.57,106.16 4 1 -github.com/kubernetes/minikube/cli/cluster/cluster.go:110.2,112.16 3 1 -github.com/kubernetes/minikube/cli/cluster/cluster.go:116.2,119.38 3 1 -github.com/kubernetes/minikube/cli/cluster/cluster.go:125.2,125.36 1 1 -github.com/kubernetes/minikube/cli/cluster/cluster.go:128.2,128.15 1 1 -github.com/kubernetes/minikube/cli/cluster/cluster.go:106.16,108.3 1 0 -github.com/kubernetes/minikube/cli/cluster/cluster.go:112.16,114.3 1 0 -github.com/kubernetes/minikube/cli/cluster/cluster.go:119.38,123.3 2 0 -github.com/kubernetes/minikube/cli/cluster/cluster.go:125.36,127.3 1 0