This repository has been archived by the owner. It is now read-only.
Permalink
Browse files

cli: Make 'kelda stop' prompt the user for confirmation

Previously, the stop command would immediately tell the daemon to halt
all of the machines or containers without confirming with the user that
this is the intended action. Now, the stop command warns the user if
running 'kelda stop' will terminate any containers or machines.

Some logic was slightly simplified as well, which caused 'stop' to not
bother deploying an empty blueprint if it was already empty, which had
the added effect of requiring some of the previously existing tests to
be slightly changed.
  • Loading branch information...
aegamesi authored and luise committed Nov 21, 2017
1 parent 8ce5772 commit 88e62dd1d0264fa451edbddd591f22a11c06dc7d
Showing with 117 additions and 25 deletions.
  1. +60 −16 cli/command/stop.go
  2. +57 −9 cli/command/stop_test.go
View
@@ -5,14 +5,17 @@ import (
log "github.com/sirupsen/logrus"
"fmt"
"github.com/kelda/kelda/blueprint"
"github.com/kelda/kelda/util"
"os"
)
// Stop contains the options for stopping namespaces.
type Stop struct {
namespace string
onlyContainers bool
force bool
connectionHelper
}
@@ -29,7 +32,9 @@ var stopExplanation = `Stop a deployment.
This will free all resources (e.g. VMs) associated with the deployment.
If no namespace is specified, stop the deployment running in the namespace that is
currently tracked by the daemon.`
currently tracked by the daemon.
Confirmation is required, but can be skipped with the -f flag.`
// InstallFlags sets up parsing for command line flags.
func (sCmd *Stop) InstallFlags(flags *flag.FlagSet) {
@@ -38,6 +43,7 @@ func (sCmd *Stop) InstallFlags(flags *flag.FlagSet) {
flags.StringVar(&sCmd.namespace, "namespace", "", "the namespace to stop")
flags.BoolVar(&sCmd.onlyContainers, "containers", false,
"only destroy containers")
flags.BoolVar(&sCmd.force, "f", false, "stop without confirming")
flags.Usage = func() {
util.PrintUsageString(stopCommands, stopExplanation, flags)
@@ -58,24 +64,55 @@ func (sCmd *Stop) Run() int {
newCluster := blueprint.Blueprint{
Namespace: sCmd.namespace,
}
if sCmd.namespace == "" || sCmd.onlyContainers {
currDepl, err := getCurrentDeployment(sCmd.client)
if err != nil {
log.WithError(err).
Error("Failed to get current cluster")
currDepl, err := getCurrentDeployment(sCmd.client)
if err != nil && err != errNoBlueprint {
log.WithError(err).Error("Failed to get current cluster")
return 1
}
if sCmd.namespace == "" {
newCluster.Namespace = currDepl.Namespace
}
if sCmd.onlyContainers {
if newCluster.Namespace != currDepl.Namespace {
log.Error("Stopping only containers for a namespace " +
"not tracked by the remote daemon is not " +
"currently supported")
return 1
}
if sCmd.namespace == "" {
newCluster.Namespace = currDepl.Namespace
newCluster.Machines = currDepl.Machines
}
// If the user is stopping the currently tracked namespace, inform the user of
// the changes that will be made.
if currDepl.Namespace == newCluster.Namespace {
// This equality comparison works because any two blueprints that have
// the exact same data will have exactly the same string representation.
if currDepl.String() == newCluster.String() {
fmt.Println("Nothing to stop.")
return 0
}
containersDelta := len(currDepl.Containers) - len(newCluster.Containers)
machinesDelta := len(currDepl.Machines) - len(newCluster.Machines)
fmt.Printf("This will stop %s and %s.\n",
pluralize(containersDelta, "container"),
pluralize(machinesDelta, "machine"))
} else {
fmt.Println("This will stop an unknown number of machines and " +
"containers.")
}
if !sCmd.force {
shouldStop, err := confirm(os.Stdin, "Continue stopping deployment?")
if err != nil {
log.WithError(err).Error("Unable to get user response.")
return 1
}
if sCmd.onlyContainers {
if newCluster.Namespace != currDepl.Namespace {
log.Error("Stopping only containers for a namespace " +
"not tracked by the remote daemon is not " +
"currently supported")
return 1
}
newCluster.Machines = currDepl.Machines
if !shouldStop {
fmt.Println("Stop aborted by user.")
return 0
}
}
@@ -87,3 +124,10 @@ func (sCmd *Stop) Run() int {
log.WithField("namespace", sCmd.namespace).Debug("Stopping namespace")
return 0
}
func pluralize(count int, singular string) string {
if count == 1 {
return fmt.Sprintf("%d %s", count, singular)
}
return fmt.Sprintf("%d %ss", count, singular)
}
View
@@ -8,25 +8,28 @@ import (
"github.com/kelda/kelda/db"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"io"
)
func TestStopNamespaceDefault(t *testing.T) {
t.Parallel()
c := new(clientMock.Client)
c.On("QueryBlueprints").Once().Return([]db.Blueprint{{
Blueprint: blueprint.Blueprint{Namespace: "testSpace"}}}, nil)
Blueprint: blueprint.Blueprint{Namespace: "testSpace",
Machines: []blueprint.Machine{{}}}}}, nil)
c.On("Deploy", mock.Anything).Return(nil)
stopCmd := NewStopCommand()
stopCmd.force = true
stopCmd.client = c
stopCmd.Run()
c.AssertCalled(t, "Deploy", blueprint.Blueprint{Namespace: "testSpace"}.String())
c.On("QueryBlueprints").Return(nil, nil)
assert.Equal(t, 1, stopCmd.Run(),
"can't retrieve namespace if no cluster is deployed")
stopCmd.Run()
c.AssertNumberOfCalls(t, "Deploy", 1)
}
func TestStopNamespace(t *testing.T) {
@@ -38,6 +41,7 @@ func TestStopNamespace(t *testing.T) {
stopCmd := NewStopCommand()
stopCmd.client = c
stopCmd.force = true
stopCmd.namespace = "namespace"
stopCmd.Run()
@@ -53,14 +57,16 @@ func TestStopContainers(t *testing.T) {
Namespace: "testSpace",
Machines: []blueprint.Machine{
{Provider: "Amazon"},
{Provider: "Google"}}},
{Provider: "Google"}},
Containers: []blueprint.Container{{}, {}}},
}}, nil)
c.On("Deploy", mock.Anything).Return(nil)
stopCmd := NewStopCommand()
stopCmd.client = c
stopCmd.onlyContainers = true
stopCmd.force = true
stopCmd.Run()
c.AssertCalled(t, "Deploy", blueprint.Blueprint{
@@ -77,15 +83,57 @@ func TestStopFlags(t *testing.T) {
t.Parallel()
expNamespace := "namespace"
checkStopParsing(t, []string{"-namespace", expNamespace}, expNamespace, nil)
checkStopParsing(t, []string{expNamespace}, expNamespace, nil)
checkStopParsing(t, []string{}, "", nil)
checkStopParsing(t, []string{"-namespace", expNamespace},
Stop{namespace: expNamespace}, nil)
checkStopParsing(t, []string{"-f"}, Stop{force: true}, nil)
checkStopParsing(t, []string{"-f", expNamespace},
Stop{force: true, namespace: expNamespace}, nil)
checkStopParsing(t, []string{expNamespace}, Stop{namespace: expNamespace}, nil)
checkStopParsing(t, []string{}, Stop{}, nil)
}
func checkStopParsing(t *testing.T, args []string, expNamespace string, expErr error) {
func checkStopParsing(t *testing.T, args []string, expFlags Stop, expErr error) {
stopCmd := NewStopCommand()
err := parseHelper(stopCmd, args)
assert.Equal(t, expErr, err)
assert.Equal(t, expNamespace, stopCmd.namespace)
assert.Equal(t, expFlags.namespace, stopCmd.namespace)
assert.Equal(t, expFlags.force, stopCmd.force)
}
func TestStopPromptsUser(t *testing.T) {
oldConfirm := confirm
defer func() {
confirm = oldConfirm
}()
compile = func(path string, args []string) (blueprint.Blueprint, error) {
return blueprint.Blueprint{}, nil
}
for _, confirmResp := range []bool{true, false} {
confirm = func(in io.Reader, prompt string) (bool, error) {
return confirmResp, nil
}
c := new(clientMock.Client)
c.On("QueryBlueprints").Return([]db.Blueprint{{
Blueprint: blueprint.Blueprint{
Namespace: "ns",
Machines: []blueprint.Machine{{}, {}},
Containers: []blueprint.Container{{}},
},
}}, nil)
c.On("Deploy", blueprint.Blueprint{Namespace: "ns"}.String()).Return(nil)
stopCmd := NewStopCommand()
stopCmd.client = c
stopCmd.Run()
if confirmResp {
c.AssertCalled(t, "Deploy", mock.Anything)
} else {
c.AssertNotCalled(t, "Deploy", mock.Anything)
}
}
}

0 comments on commit 88e62dd

Please sign in to comment.