virtcontainers: add pause and resume container to the API

Pause and resume container functions allow us to just pause/resume a
specific container not all the sanbox, in that way different containers
can be paused or running in the same sanbox.

Signed-off-by: Julio Montes <julio.montes@intel.com>
This commit is contained in:
Julio Montes 2018-05-21 12:59:46 -05:00
parent 44b65e1d52
commit b99cadb553
16 changed files with 287 additions and 7 deletions

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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)

View File

@ -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
}

View File

@ -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)
}

View File

@ -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

View File

@ -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) {

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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 ""
}

View File

@ -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) {

View File

@ -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)
}

View File

@ -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))
}

View File

@ -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
}