diff --git a/virtcontainers/agent.go b/virtcontainers/agent.go index e1e0b406cd..9327786e49 100644 --- a/virtcontainers/agent.go +++ b/virtcontainers/agent.go @@ -197,4 +197,10 @@ type agent interface { // statsContainer will tell the agent to get stats from a container related to a Sandbox statsContainer(sandbox *Sandbox, c Container) (*ContainerStats, error) + + // pauseContainer will pause a container + pauseContainer(sandbox *Sandbox, c Container) error + + // resumeContainer will resume a paused container + resumeContainer(sandbox *Sandbox, c Container) error } diff --git a/virtcontainers/api.go b/virtcontainers/api.go index 82eea17c0e..b91c8812f2 100644 --- a/virtcontainers/api.go +++ b/virtcontainers/api.go @@ -653,3 +653,46 @@ func StatsContainer(sandboxID, containerID string) (ContainerStats, error) { return s.StatsContainer(containerID) } + +func togglePauseContainer(sandboxID, containerID string, pause bool) error { + if sandboxID == "" { + return errNeedSandboxID + } + + if containerID == "" { + return errNeedContainerID + } + + lockFile, err := rwLockSandbox(sandboxID) + if err != nil { + return err + } + defer unlockSandbox(lockFile) + + s, err := fetchSandbox(sandboxID) + if err != nil { + return err + } + + // Fetch the container. + c, err := s.findContainer(containerID) + if err != nil { + return err + } + + if pause { + return c.pause() + } + + return c.resume() +} + +// PauseContainer is the virtcontainers container pause entry point. +func PauseContainer(sandboxID, containerID string) error { + return togglePauseContainer(sandboxID, containerID, true) +} + +// ResumeContainer is the virtcontainers container resume entry point. +func ResumeContainer(sandboxID, containerID string) error { + return togglePauseContainer(sandboxID, containerID, false) +} diff --git a/virtcontainers/api_test.go b/virtcontainers/api_test.go index 502302ab9c..a7dc007033 100644 --- a/virtcontainers/api_test.go +++ b/virtcontainers/api_test.go @@ -2400,3 +2400,43 @@ func TestUpdateContainer(t *testing.T) { err = UpdateContainer(s.ID(), contID, resources) assert.NoError(err) } + +func TestPauseResumeContainer(t *testing.T) { + if os.Geteuid() != 0 { + t.Skip(testDisabledAsNonRoot) + } + + cleanUp() + + assert := assert.New(t) + err := PauseContainer("", "") + assert.Error(err) + + err = PauseContainer("abc", "") + assert.Error(err) + + contID := "100" + config := newTestSandboxConfigNoop() + + s, sandboxDir, err := createAndStartSandbox(config) + assert.NoError(err) + assert.NotNil(s) + + contConfig := newTestContainerConfigNoop(contID) + _, c, err := CreateContainer(s.ID(), contConfig) + assert.NoError(err) + assert.NotNil(c) + + contDir := filepath.Join(sandboxDir, contID) + _, err = os.Stat(contDir) + assert.NoError(err) + + _, err = StartContainer(s.ID(), contID) + assert.NoError(err) + + err = PauseContainer(s.ID(), contID) + assert.NoError(err) + + err = ResumeContainer(s.ID(), contID) + assert.NoError(err) +} diff --git a/virtcontainers/container.go b/virtcontainers/container.go index af9b459953..b3a10adc8b 100644 --- a/virtcontainers/container.go +++ b/virtcontainers/container.go @@ -868,8 +868,8 @@ func (c *Container) signalProcess(processID string, signal syscall.Signal, all b return fmt.Errorf("Sandbox not ready or running, impossible to signal the container") } - if c.state.State != StateReady && c.state.State != StateRunning { - return fmt.Errorf("Container not ready or running, impossible to signal the container") + if c.state.State != StateReady && c.state.State != StateRunning && c.state.State != StatePaused { + return fmt.Errorf("Container not ready, running or paused, impossible to signal the container") } return c.sandbox.agent.signalProcess(c, processID, signal, all) @@ -938,6 +938,38 @@ func (c *Container) update(resources specs.LinuxResources) error { return c.sandbox.agent.updateContainer(c.sandbox, *c, resources) } +func (c *Container) pause() error { + if err := c.checkSandboxRunning("pause"); err != nil { + return err + } + + if c.state.State != StateRunning && c.state.State != StateReady { + return fmt.Errorf("Container not running or ready, impossible to pause") + } + + if err := c.sandbox.agent.pauseContainer(c.sandbox, *c); err != nil { + return err + } + + return c.setContainerState(StatePaused) +} + +func (c *Container) resume() error { + if err := c.checkSandboxRunning("resume"); err != nil { + return err + } + + if c.state.State != StatePaused { + return fmt.Errorf("Container not paused, impossible to resume") + } + + if err := c.sandbox.agent.resumeContainer(c.sandbox, *c); err != nil { + return err + } + + return c.setContainerState(StateRunning) +} + func (c *Container) hotplugDrive() error { dev, err := getDeviceForPath(c.rootFs) diff --git a/virtcontainers/container_test.go b/virtcontainers/container_test.go index cf26686527..68bfd699e1 100644 --- a/virtcontainers/container_test.go +++ b/virtcontainers/container_test.go @@ -418,11 +418,6 @@ func TestKillContainerErrorState(t *testing.T) { err := c.kill(syscall.SIGKILL, true) assert.Error(err) - // Container paused - c.state.State = StatePaused - err = c.kill(syscall.SIGKILL, false) - assert.Error(err) - // Container stopped c.state.State = StateStopped err = c.kill(syscall.SIGKILL, true) diff --git a/virtcontainers/hyperstart_agent.go b/virtcontainers/hyperstart_agent.go index 7a13b47fc2..230f2393e4 100644 --- a/virtcontainers/hyperstart_agent.go +++ b/virtcontainers/hyperstart_agent.go @@ -844,3 +844,13 @@ func (h *hyper) readProcessStderr(c *Container, processID string, data []byte) ( // hyperstart-agent does not support stderr read request return 0, nil } + +func (h *hyper) pauseContainer(sandbox *Sandbox, c Container) error { + // hyperstart-agent does not support pause container + return nil +} + +func (h *hyper) resumeContainer(sandbox *Sandbox, c Container) error { + // hyperstart-agent does not support resume container + return nil +} diff --git a/virtcontainers/implementation.go b/virtcontainers/implementation.go index 81ed6db32f..f75348fa9e 100644 --- a/virtcontainers/implementation.go +++ b/virtcontainers/implementation.go @@ -125,3 +125,13 @@ func (impl *VCImpl) ProcessListContainer(sandboxID, containerID string, options func (impl *VCImpl) UpdateContainer(sandboxID, containerID string, resources specs.LinuxResources) error { return UpdateContainer(sandboxID, containerID, resources) } + +// PauseContainer implements the VC function of the same name. +func (impl *VCImpl) PauseContainer(sandboxID, containerID string) error { + return PauseContainer(sandboxID, containerID) +} + +// ResumeContainer implements the VC function of the same name. +func (impl *VCImpl) ResumeContainer(sandboxID, containerID string) error { + return ResumeContainer(sandboxID, containerID) +} diff --git a/virtcontainers/interfaces.go b/virtcontainers/interfaces.go index f61fd1de07..4a9f2a239d 100644 --- a/virtcontainers/interfaces.go +++ b/virtcontainers/interfaces.go @@ -38,6 +38,8 @@ type VC interface { StopContainer(sandboxID, containerID string) (VCContainer, error) ProcessListContainer(sandboxID, containerID string, options ProcessListOptions) (ProcessList, error) UpdateContainer(sandboxID, containerID string, resources specs.LinuxResources) error + PauseContainer(sandboxID, containerID string) error + ResumeContainer(sandboxID, containerID string) error } // VCSandbox is the Sandbox interface diff --git a/virtcontainers/kata_agent.go b/virtcontainers/kata_agent.go index de1d0daa21..6ef55bbeb3 100644 --- a/virtcontainers/kata_agent.go +++ b/virtcontainers/kata_agent.go @@ -982,6 +982,24 @@ func (k *kataAgent) updateContainer(sandbox *Sandbox, c Container, resources spe return err } +func (k *kataAgent) pauseContainer(sandbox *Sandbox, c Container) error { + req := &grpc.PauseContainerRequest{ + ContainerId: c.id, + } + + _, err := k.sendReq(req) + return err +} + +func (k *kataAgent) resumeContainer(sandbox *Sandbox, c Container) error { + req := &grpc.ResumeContainerRequest{ + ContainerId: c.id, + } + + _, err := k.sendReq(req) + return err +} + func (k *kataAgent) onlineCPUMem(cpus uint32) error { req := &grpc.OnlineCPUMemRequest{ Wait: false, @@ -1155,6 +1173,12 @@ func (k *kataAgent) installReqFunc(c *kataclient.AgentClient) { k.reqHandlers["grpc.StatsContainerRequest"] = func(ctx context.Context, req interface{}, opts ...golangGrpc.CallOption) (interface{}, error) { return k.client.StatsContainer(ctx, req.(*grpc.StatsContainerRequest), opts...) } + k.reqHandlers["grpc.PauseContainerRequest"] = func(ctx context.Context, req interface{}, opts ...golangGrpc.CallOption) (interface{}, error) { + return k.client.PauseContainer(ctx, req.(*grpc.PauseContainerRequest), opts...) + } + k.reqHandlers["grpc.ResumeContainerRequest"] = func(ctx context.Context, req interface{}, opts ...golangGrpc.CallOption) (interface{}, error) { + return k.client.ResumeContainer(ctx, req.(*grpc.ResumeContainerRequest), opts...) + } } func (k *kataAgent) sendReq(request interface{}) (interface{}, error) { diff --git a/virtcontainers/noop_agent.go b/virtcontainers/noop_agent.go index 07356f02e9..016eda65a7 100644 --- a/virtcontainers/noop_agent.go +++ b/virtcontainers/noop_agent.go @@ -125,3 +125,13 @@ func (n *noopAgent) readProcessStdout(c *Container, processID string, data []byt func (n *noopAgent) readProcessStderr(c *Container, processID string, data []byte) (int, error) { return 0, nil } + +// pauseContainer is the Noop agent Container pause implementation. It does nothing. +func (n *noopAgent) pauseContainer(sandbox *Sandbox, c Container) error { + return nil +} + +// resumeContainer is the Noop agent Container resume implementation. It does nothing. +func (n *noopAgent) resumeContainer(sandbox *Sandbox, c Container) error { + return nil +} diff --git a/virtcontainers/noop_agent_test.go b/virtcontainers/noop_agent_test.go index cd158cbf10..f4f076071c 100644 --- a/virtcontainers/noop_agent_test.go +++ b/virtcontainers/noop_agent_test.go @@ -130,3 +130,29 @@ func TestNoopAgentStatsContainer(t *testing.T) { t.Fatal(err) } } + +func TestNoopAgentPauseContainer(t *testing.T) { + n := &noopAgent{} + sandbox, container, err := testCreateNoopContainer() + if err != nil { + t.Fatal(err) + } + defer cleanUp() + err = n.pauseContainer(sandbox, *container) + if err != nil { + t.Fatal(err) + } +} + +func TestNoopAgentResumeContainer(t *testing.T) { + n := &noopAgent{} + sandbox, container, err := testCreateNoopContainer() + if err != nil { + t.Fatal(err) + } + defer cleanUp() + err = n.resumeContainer(sandbox, *container) + if err != nil { + t.Fatal(err) + } +} diff --git a/virtcontainers/pkg/oci/utils.go b/virtcontainers/pkg/oci/utils.go index c886befbda..60fa7b5709 100644 --- a/virtcontainers/pkg/oci/utils.go +++ b/virtcontainers/pkg/oci/utils.go @@ -65,6 +65,9 @@ const ( // StateStopped represents a container that has been stopped. StateStopped = "stopped" + + // StatePaused represents a container that has been paused. + StatePaused = "paused" ) // CompatOCIProcess is a structure inheriting from spec.Process defined @@ -612,6 +615,8 @@ func StateToOCIState(state vc.State) string { return StateRunning case vc.StateStopped: return StateStopped + case vc.StatePaused: + return StatePaused default: return "" } diff --git a/virtcontainers/pkg/oci/utils_test.go b/virtcontainers/pkg/oci/utils_test.go index 37385ff2f4..12e5c178ef 100644 --- a/virtcontainers/pkg/oci/utils_test.go +++ b/virtcontainers/pkg/oci/utils_test.go @@ -437,6 +437,11 @@ func TestStateToOCIState(t *testing.T) { if ociState := StateToOCIState(state); ociState != "stopped" { t.Fatalf("Expecting \"created\" state, got \"%s\"", ociState) } + + state.State = vc.StatePaused + if ociState := StateToOCIState(state); ociState != "paused" { + t.Fatalf("Expecting \"paused\" state, got \"%s\"", ociState) + } } func TestEnvVars(t *testing.T) { diff --git a/virtcontainers/pkg/vcmock/mock.go b/virtcontainers/pkg/vcmock/mock.go index bda162e381..a4e0bcab3b 100644 --- a/virtcontainers/pkg/vcmock/mock.go +++ b/virtcontainers/pkg/vcmock/mock.go @@ -214,3 +214,21 @@ func (m *VCMock) UpdateContainer(sandboxID, containerID string, resources specs. return fmt.Errorf("%s: %s (%+v): sandboxID: %v, containerID: %v", mockErrorPrefix, getSelf(), m, sandboxID, containerID) } + +// PauseContainer implements the VC function of the same name. +func (m *VCMock) PauseContainer(sandboxID, containerID string) error { + if m.PauseContainerFunc != nil { + return m.PauseContainerFunc(sandboxID, containerID) + } + + return fmt.Errorf("%s: %s (%+v): sandboxID: %v, containerID: %v", mockErrorPrefix, getSelf(), m, sandboxID, containerID) +} + +// ResumeContainer implements the VC function of the same name. +func (m *VCMock) ResumeContainer(sandboxID, containerID string) error { + if m.ResumeContainerFunc != nil { + return m.ResumeContainerFunc(sandboxID, containerID) + } + + return fmt.Errorf("%s: %s (%+v): sandboxID: %v, containerID: %v", mockErrorPrefix, getSelf(), m, sandboxID, containerID) +} diff --git a/virtcontainers/pkg/vcmock/mock_test.go b/virtcontainers/pkg/vcmock/mock_test.go index 77537432fe..22ae00b584 100644 --- a/virtcontainers/pkg/vcmock/mock_test.go +++ b/virtcontainers/pkg/vcmock/mock_test.go @@ -623,3 +623,55 @@ func TestVCMockFetchSandbox(t *testing.T) { assert.True(IsMockError(err)) } + +func TestVCMockPauseContainer(t *testing.T) { + assert := assert.New(t) + + m := &VCMock{} + config := &vc.SandboxConfig{} + assert.Nil(m.PauseContainerFunc) + + err := m.PauseContainer(config.ID, config.ID) + assert.Error(err) + assert.True(IsMockError(err)) + + m.PauseContainerFunc = func(sid, cid string) error { + return nil + } + + err = m.PauseContainer(config.ID, config.ID) + assert.NoError(err) + + // reset + m.PauseContainerFunc = nil + + err = m.PauseContainer(config.ID, config.ID) + assert.Error(err) + assert.True(IsMockError(err)) +} + +func TestVCMockResumeContainer(t *testing.T) { + assert := assert.New(t) + + m := &VCMock{} + config := &vc.SandboxConfig{} + assert.Nil(m.ResumeContainerFunc) + + err := m.ResumeContainer(config.ID, config.ID) + assert.Error(err) + assert.True(IsMockError(err)) + + m.ResumeContainerFunc = func(sid, cid string) error { + return nil + } + + err = m.ResumeContainer(config.ID, config.ID) + assert.NoError(err) + + // reset + m.ResumeContainerFunc = nil + + err = m.ResumeContainer(config.ID, config.ID) + assert.Error(err) + assert.True(IsMockError(err)) +} diff --git a/virtcontainers/pkg/vcmock/types.go b/virtcontainers/pkg/vcmock/types.go index 73226c50f8..c223526034 100644 --- a/virtcontainers/pkg/vcmock/types.go +++ b/virtcontainers/pkg/vcmock/types.go @@ -58,4 +58,6 @@ type VCMock struct { StopContainerFunc func(sandboxID, containerID string) (vc.VCContainer, error) ProcessListContainerFunc func(sandboxID, containerID string, options vc.ProcessListOptions) (vc.ProcessList, error) UpdateContainerFunc func(sandboxID, containerID string, resources specs.LinuxResources) error + PauseContainerFunc func(sandboxID, containerID string) error + ResumeContainerFunc func(sandboxID, containerID string) error }