Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions client/stop_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,34 @@ import (
cpb "github.com/openconfig/gnoi/containerz"
)

// StopContainer stops the requested instance. Stop can also force termination.
func (c *Client) StopContainer(ctx context.Context, instance string, force bool) error {
// stopOptions contains the optional parameters of a stop container call.
type stopOptions struct {
restart bool
}

// StopOption is an option passed to a stop container call.
type StopOption func(*stopOptions)

// WithRestart requests that the target starts the container again immediately
// after stopping it.
func WithRestart() StopOption {
return func(opt *stopOptions) {
opt.restart = true
}
}

// StopContainer stops the requested instance. Stop can also force termination
// and restart the container after it has stopped.
func (c *Client) StopContainer(ctx context.Context, instance string, force bool, opts ...StopOption) error {
optionz := &stopOptions{}
for _, opt := range opts {
opt(optionz)
}

if _, err := c.cli.StopContainer(ctx, &cpb.StopContainerRequest{
InstanceName: instance,
Force: force,
Restart: optionz.restart,
}); err != nil {
return err
}
Expand Down
30 changes: 29 additions & 1 deletion client/stop_container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func TestStop(t *testing.T) {
name string
inInstance string
inForce bool
inOpts []StopOption

wantMsg *cpb.StopContainerRequest
}{
Expand All @@ -52,6 +53,33 @@ func TestStop(t *testing.T) {
Force: true,
},
},
{
name: "no-force",
inInstance: "some-instance",
wantMsg: &cpb.StopContainerRequest{
InstanceName: "some-instance",
},
},
{
name: "restart",
inInstance: "some-instance",
inOpts: []StopOption{WithRestart()},
wantMsg: &cpb.StopContainerRequest{
InstanceName: "some-instance",
Restart: true,
},
},
{
name: "force-and-restart",
inInstance: "some-instance",
inForce: true,
inOpts: []StopOption{WithRestart()},
wantMsg: &cpb.StopContainerRequest{
InstanceName: "some-instance",
Force: true,
Restart: true,
},
},
}

ctx := context.Background()
Expand All @@ -65,7 +93,7 @@ func TestStop(t *testing.T) {
t.Fatalf("NewClient(%v) returned an unexpected error: %v", addr, err)
}

if err := cli.StopContainer(ctx, tc.inInstance, tc.inForce); err != nil {
if err := cli.StopContainer(ctx, tc.inInstance, tc.inForce, tc.inOpts...); err != nil {
t.Fatalf("Stop(%q, %t) returned an unexpected error: %v", tc.inInstance, tc.inForce, err)
}

Expand Down
16 changes: 14 additions & 2 deletions cmd/container_stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ package cmd
import (
"fmt"

"github.com/openconfig/containerz/client"
"github.com/spf13/cobra"
)

var (
force bool
force bool
restart bool
)

var cntStopCmd = &cobra.Command{
Expand All @@ -32,10 +34,19 @@ var cntStopCmd = &cobra.Command{
fmt.Println("--instance must be provided")
}

if err := containerzClient.StopContainer(command.Context(), instance, force); err != nil {
var opts []client.StopOption
if restart {
opts = append(opts, client.WithRestart())
}

if err := containerzClient.StopContainer(command.Context(), instance, force, opts...); err != nil {
return err
}

if restart {
fmt.Printf("Successfully restarted %s\n", instance)
return nil
}
fmt.Printf("Successfully stopped %s\n", instance)
return nil
},
Expand All @@ -46,4 +57,5 @@ func init() {

cntStopCmd.PersistentFlags().StringVar(&instance, "instance", "", "Container instance to stop.")
cntStopCmd.PersistentFlags().BoolVar(&force, "force", false, "Forcefully stop the container.")
cntStopCmd.PersistentFlags().BoolVar(&restart, "restart", false, "Start the container again after stopping it.")
}
14 changes: 14 additions & 0 deletions containers/docker/container_stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ const maximumStopTimeout = 10
// If the Force option is set but no timeout is provided the container's StopTimeout
// value is used, if set, otherwise the engine default.
// If the Force option is not set, no forceful termination is performed.
// If the Restart option is set, the container is started again after it has
// stopped, using the same termination semantics.
func (m *Manager) ContainerStop(ctx context.Context, instance string, opts ...options.Option) error {
optionz := options.ApplyOptions(opts...)

Expand Down Expand Up @@ -57,6 +59,18 @@ func (m *Manager) ContainerStop(ctx context.Context, instance string, opts ...op
pDuration = nil
}

if optionz.Restart {
// ContainerRestart stops the container using the same timeout semantics
// as ContainerStop and then starts it again. The container keeps its ID,
// configuration and restart policy.
if err := m.client.ContainerRestart(ctx, instance, container.StopOptions{Timeout: pDuration}); err != nil {
klog.Warningf("container %s failed to restart", instance)
return status.Errorf(codes.Unknown, "failed to restart container %s with error %s",
instance, err)
}
return nil
}

if err := m.client.ContainerStop(ctx, instance, container.StopOptions{Timeout: pDuration}); err != nil {
klog.Warningf("container %s failed to stop", instance)
return status.Errorf(codes.Unknown, "failed to stop container %s with error %s",
Expand Down
64 changes: 61 additions & 3 deletions containers/docker/container_stop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ type fakeStoppingDocker struct {
fakeDocker
cnts []types.Container

Instance string
Duration int
RemoveInstance string
Instance string
Duration int
RemoveInstance string
RestartInstance string
}

func (f *fakeStoppingDocker) ContainerStop(ctx context.Context, container string, options container.StopOptions) error {
Expand All @@ -32,6 +33,15 @@ func (f *fakeStoppingDocker) ContainerStop(ctx context.Context, container string
return nil
}

func (f *fakeStoppingDocker) ContainerRestart(ctx context.Context, container string, options container.StopOptions) error {
f.RestartInstance = container

if options.Timeout != nil {
f.Duration = *options.Timeout
}
return nil
}

func (f fakeStoppingDocker) ContainerList(ctx context.Context, options container.ListOptions) ([]types.Container, error) {
return f.cnts, nil
}
Expand Down Expand Up @@ -97,6 +107,54 @@ func TestContainerStop(t *testing.T) {
Duration: maximumStopTimeout,
},
},
{
name: "restart-no-force",
inInstance: "restart-no-force",
inOpts: []options.Option{options.Restart()},
inCnts: []types.Container{
types.Container{
Names: []string{"restart-no-force"},
},
},
wantState: &fakeStoppingDocker{
RestartInstance: "restart-no-force",
Duration: -1,
},
},
{
name: "restart-with-force-no-duration",
inInstance: "restart-with-force-no-duration",
inOpts: []options.Option{options.Force(), options.Restart()},
inCnts: []types.Container{
types.Container{
Names: []string{"restart-with-force-no-duration"},
},
},
wantState: &fakeStoppingDocker{
RestartInstance: "restart-with-force-no-duration",
},
},
{
name: "restart-with-force-and-duration",
inInstance: "restart-with-force-and-duration",
inTimeout: 1 * time.Minute,
inOpts: []options.Option{options.Force(), options.Restart()},
inCnts: []types.Container{
types.Container{
Names: []string{"restart-with-force-and-duration"},
},
},
wantState: &fakeStoppingDocker{
RestartInstance: "restart-with-force-and-duration",
Duration: maximumStopTimeout,
},
},
{
name: "restart-no-such-instance",
inInstance: "restart-no-such-instance",
inOpts: []options.Option{options.Restart()},
wantErr: status.Errorf(codes.NotFound, "container restart-no-such-instance was not found"),
},
}

for _, tc := range tests {
Expand Down
1 change: 1 addition & 0 deletions containers/docker/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type docker interface {
ContainerList(ctx context.Context, options container.ListOptions) ([]types.Container, error)
ContainerLogs(ctx context.Context, container string, options container.LogsOptions) (io.ReadCloser, error)
ContainerRemove(ctx context.Context, container string, options container.RemoveOptions) error
ContainerRestart(ctx context.Context, container string, options container.StopOptions) error
ContainerStart(ctx context.Context, container string, options container.StartOptions) error
ContainerStop(ctx context.Context, container string, options container.StopOptions) error
ImageList(ctx context.Context, options image.ListOptions) ([]image.Summary, error)
Expand Down
4 changes: 4 additions & 0 deletions containers/docker/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ func (fakeDocker) ContainerRemove(ctx context.Context, container string, options
return fmt.Errorf("not implemented")
}

func (fakeDocker) ContainerRestart(ctx context.Context, container string, _ container.StopOptions) error {
return fmt.Errorf("not implemented")
}

func (fakeDocker) ContainerStart(ctx context.Context, container string, options container.StartOptions) error {
return fmt.Errorf("not implemented")
}
Expand Down
12 changes: 12 additions & 0 deletions containers/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ type options struct {
// Force indicates that the container implementation should attempt to force the operation.
Force bool

// Restart indicates that a stopped container should be started again immediately.
Restart bool

// InstanceName is the name of a running container instance.
InstanceName string

Expand Down Expand Up @@ -207,6 +210,15 @@ func Force() Option {
}
}

// Restart requests that the container is started again immediately after it
// has been stopped.
// Supported by: ContainerStop
func Restart() Option {
return func(p *options) {
p.Restart = true
}
}

// WithInstanceName sets the name of the instance of a container.
// Supported by: ContainerStart
func WithInstanceName(instance string) Option {
Expand Down
10 changes: 10 additions & 0 deletions containers/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ func TestForce(t *testing.T) {
}
}

func TestRestart(t *testing.T) {
p := &options{}

Restart()(p)

if !p.Restart {
t.Errorf("Restart() did not set the restart flag")
}
}

func TestFollow(t *testing.T) {
p := &options{}

Expand Down
2 changes: 2 additions & 0 deletions server/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type fakeContainerManager struct {
Ports map[uint32]uint32
Envs map[string]string
Force bool
Restart bool
Follow bool
All bool
Async bool
Expand Down Expand Up @@ -133,6 +134,7 @@ func (f *fakeContainerManager) ContainerStop(_ context.Context, instance string,
optionz := options.ApplyOptions(opts...)
f.Instance = instance
f.Force = optionz.Force
f.Restart = optionz.Restart
return nil
}

Expand Down
6 changes: 5 additions & 1 deletion server/stop_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (

// StopContainer stops a container. If the container does not exist or is not running
// this operation returns an error. This operation can, optionally, force
// (i.e. kill) a container.
// (i.e. kill) a container and restart it immediately after it has stopped.
func (s *Server) StopContainer(ctx context.Context, request *cpb.StopContainerRequest) (*cpb.StopContainerResponse, error) {
// TODO (alshabib): Consider adding a timeout to the request or use a containerz default.s
opts := []options.Option{}
Expand All @@ -32,6 +32,10 @@ func (s *Server) StopContainer(ctx context.Context, request *cpb.StopContainerRe
opts = append(opts, options.Force())
}

if request.GetRestart() {
opts = append(opts, options.Restart())
}

if err := s.mgr.ContainerStop(ctx, request.GetInstanceName(), opts...); err != nil {
return nil, err
}
Expand Down
26 changes: 26 additions & 0 deletions server/stop_container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,32 @@ func TestStop(t *testing.T) {
Force: true,
},
},
{
name: "restart",
inReq: &cpb.StopContainerRequest{
InstanceName: "some-name",
Restart: true,
},
wantResp: &cpb.StopContainerResponse{},
wantState: &fakeContainerManager{
Instance: "some-name",
Restart: true,
},
},
{
name: "force-and-restart",
inReq: &cpb.StopContainerRequest{
InstanceName: "some-name",
Force: true,
Restart: true,
},
wantResp: &cpb.StopContainerResponse{},
wantState: &fakeContainerManager{
Instance: "some-name",
Force: true,
Restart: true,
},
},
}

for _, tc := range tests {
Expand Down
Loading