github.com/lalkh/containerd@v1.4.3/container_checkpoint_test.go (about)

     1  // +build linux
     2  
     3  /*
     4     Copyright The containerd Authors.
     5  
     6     Licensed under the Apache License, Version 2.0 (the "License");
     7     you may not use this file except in compliance with the License.
     8     You may obtain a copy of the License at
     9  
    10         http://www.apache.org/licenses/LICENSE-2.0
    11  
    12     Unless required by applicable law or agreed to in writing, software
    13     distributed under the License is distributed on an "AS IS" BASIS,
    14     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15     See the License for the specific language governing permissions and
    16     limitations under the License.
    17  */
    18  
    19  package containerd
    20  
    21  import (
    22  	"bytes"
    23  	"fmt"
    24  	"io"
    25  	"io/ioutil"
    26  	"os"
    27  	"path/filepath"
    28  	"strings"
    29  	"sync"
    30  	"syscall"
    31  	"testing"
    32  
    33  	"github.com/containerd/containerd/cio"
    34  	"github.com/containerd/containerd/oci"
    35  	"github.com/containerd/containerd/plugin"
    36  )
    37  
    38  const (
    39  	testCheckpointName = "checkpoint-test:latest"
    40  )
    41  
    42  func TestCheckpointRestorePTY(t *testing.T) {
    43  	if !supportsCriu {
    44  		t.Skip("system does not have criu installed")
    45  	}
    46  	client, err := newClient(t, address)
    47  	if err != nil {
    48  		t.Fatal(err)
    49  	}
    50  	defer client.Close()
    51  	if client.runtime == plugin.RuntimeLinuxV1 {
    52  		t.Skip()
    53  	}
    54  
    55  	var (
    56  		ctx, cancel = testContext(t)
    57  		id          = t.Name()
    58  	)
    59  	defer cancel()
    60  
    61  	image, err := client.GetImage(ctx, testImage)
    62  	if err != nil {
    63  		t.Fatal(err)
    64  	}
    65  	container, err := client.NewContainer(ctx, id,
    66  		WithNewSnapshot(id, image),
    67  		WithNewSpec(oci.WithImageConfig(image),
    68  			oci.WithProcessArgs("sh", "-c", "read A; echo z${A}z"),
    69  			oci.WithTTY),
    70  	)
    71  	if err != nil {
    72  		t.Fatal(err)
    73  	}
    74  	defer container.Delete(ctx, WithSnapshotCleanup)
    75  
    76  	direct, err := newDirectIO(ctx, true)
    77  	if err != nil {
    78  		t.Fatal(err)
    79  	}
    80  	defer direct.Delete()
    81  
    82  	task, err := container.NewTask(ctx, direct.IOCreate)
    83  	if err != nil {
    84  		t.Fatal(err)
    85  	}
    86  	defer task.Delete(ctx)
    87  
    88  	statusC, err := task.Wait(ctx)
    89  	if err != nil {
    90  		t.Fatal(err)
    91  	}
    92  
    93  	if err := task.Start(ctx); err != nil {
    94  		t.Fatal(err)
    95  	}
    96  
    97  	checkpoint, err := container.Checkpoint(ctx, testCheckpointName+"withpty", []CheckpointOpts{
    98  		WithCheckpointRuntime,
    99  		WithCheckpointRW,
   100  		WithCheckpointTaskExit,
   101  		WithCheckpointTask,
   102  	}...)
   103  	if err != nil {
   104  		t.Fatal(err)
   105  	}
   106  
   107  	<-statusC
   108  
   109  	if _, err := task.Delete(ctx); err != nil {
   110  		t.Fatal(err)
   111  	}
   112  	direct.Delete()
   113  	if err := container.Delete(ctx, WithSnapshotCleanup); err != nil {
   114  		t.Fatal(err)
   115  	}
   116  
   117  	direct, err = newDirectIO(ctx, true)
   118  	if err != nil {
   119  		t.Fatal(err)
   120  	}
   121  
   122  	var (
   123  		wg  sync.WaitGroup
   124  		buf = bytes.NewBuffer(nil)
   125  	)
   126  	wg.Add(1)
   127  	go func() {
   128  		defer wg.Done()
   129  		io.Copy(buf, direct.Stdout)
   130  	}()
   131  
   132  	if container, err = client.Restore(ctx, id, checkpoint, []RestoreOpts{
   133  		WithRestoreImage,
   134  		WithRestoreSpec,
   135  		WithRestoreRuntime,
   136  		WithRestoreRW,
   137  	}...); err != nil {
   138  		t.Fatal(err)
   139  	}
   140  	if task, err = container.NewTask(ctx, direct.IOCreate,
   141  		WithTaskCheckpoint(checkpoint)); err != nil {
   142  		t.Fatal(err)
   143  	}
   144  
   145  	statusC, err = task.Wait(ctx)
   146  	if err != nil {
   147  		t.Fatal(err)
   148  	}
   149  
   150  	if err := task.Start(ctx); err != nil {
   151  		t.Fatal(err)
   152  	}
   153  
   154  	direct.Stdin.Write([]byte("hello\n"))
   155  	<-statusC
   156  	wg.Wait()
   157  
   158  	if err := direct.Close(); err != nil {
   159  		t.Error(err)
   160  	}
   161  
   162  	out := buf.String()
   163  	if !strings.Contains(fmt.Sprintf("%#q", out), `zhelloz`) {
   164  		t.Fatalf(`expected \x00 in output: %s`, out)
   165  	}
   166  }
   167  
   168  func TestCheckpointRestore(t *testing.T) {
   169  	if !supportsCriu {
   170  		t.Skip("system does not have criu installed")
   171  	}
   172  	client, err := newClient(t, address)
   173  	if err != nil {
   174  		t.Fatal(err)
   175  	}
   176  	defer client.Close()
   177  	if client.runtime == plugin.RuntimeLinuxV1 {
   178  		t.Skip()
   179  	}
   180  
   181  	var (
   182  		ctx, cancel = testContext(t)
   183  		id          = t.Name()
   184  	)
   185  	defer cancel()
   186  
   187  	image, err := client.GetImage(ctx, testImage)
   188  	if err != nil {
   189  		t.Fatal(err)
   190  	}
   191  	container, err := client.NewContainer(ctx, id, WithNewSnapshot(id, image), WithNewSpec(oci.WithImageConfig(image), oci.WithProcessArgs("sleep", "10")))
   192  	if err != nil {
   193  		t.Fatal(err)
   194  	}
   195  	defer container.Delete(ctx, WithSnapshotCleanup)
   196  
   197  	task, err := container.NewTask(ctx, empty())
   198  	if err != nil {
   199  		t.Fatal(err)
   200  	}
   201  	defer task.Delete(ctx)
   202  
   203  	statusC, err := task.Wait(ctx)
   204  	if err != nil {
   205  		t.Fatal(err)
   206  	}
   207  
   208  	if err := task.Start(ctx); err != nil {
   209  		t.Fatal(err)
   210  	}
   211  
   212  	checkpoint, err := container.Checkpoint(ctx, testCheckpointName+"restore", []CheckpointOpts{
   213  		WithCheckpointRuntime,
   214  		WithCheckpointRW,
   215  		WithCheckpointTask,
   216  	}...)
   217  	if err != nil {
   218  		t.Fatal(err)
   219  	}
   220  
   221  	<-statusC
   222  
   223  	if _, err := task.Delete(ctx); err != nil {
   224  		t.Fatal(err)
   225  	}
   226  	if err := container.Delete(ctx, WithSnapshotCleanup); err != nil {
   227  		t.Fatal(err)
   228  	}
   229  
   230  	if container, err = client.Restore(ctx, id, checkpoint, []RestoreOpts{
   231  		WithRestoreImage,
   232  		WithRestoreSpec,
   233  		WithRestoreRuntime,
   234  		WithRestoreRW,
   235  	}...); err != nil {
   236  		t.Fatal(err)
   237  	}
   238  	if task, err = container.NewTask(ctx, empty(), WithTaskCheckpoint(checkpoint)); err != nil {
   239  		t.Fatal(err)
   240  	}
   241  	defer task.Delete(ctx)
   242  
   243  	statusC, err = task.Wait(ctx)
   244  	if err != nil {
   245  		t.Fatal(err)
   246  	}
   247  
   248  	if err := task.Start(ctx); err != nil {
   249  		t.Fatal(err)
   250  	}
   251  
   252  	if err := task.Kill(ctx, syscall.SIGKILL); err != nil {
   253  		t.Fatal(err)
   254  	}
   255  	<-statusC
   256  }
   257  
   258  func TestCheckpointRestoreNewContainer(t *testing.T) {
   259  	if !supportsCriu {
   260  		t.Skip("system does not have criu installed")
   261  	}
   262  	client, err := newClient(t, address)
   263  	if err != nil {
   264  		t.Fatal(err)
   265  	}
   266  	defer client.Close()
   267  	if client.runtime == plugin.RuntimeLinuxV1 {
   268  		t.Skip()
   269  	}
   270  
   271  	id := t.Name()
   272  	ctx, cancel := testContext(t)
   273  	defer cancel()
   274  
   275  	image, err := client.GetImage(ctx, testImage)
   276  	if err != nil {
   277  		t.Fatal(err)
   278  	}
   279  	container, err := client.NewContainer(ctx, id, WithNewSnapshot(id, image), WithNewSpec(oci.WithImageConfig(image), oci.WithProcessArgs("sleep", "5")))
   280  	if err != nil {
   281  		t.Fatal(err)
   282  	}
   283  	defer container.Delete(ctx, WithSnapshotCleanup)
   284  
   285  	task, err := container.NewTask(ctx, empty())
   286  	if err != nil {
   287  		t.Fatal(err)
   288  	}
   289  	defer task.Delete(ctx)
   290  
   291  	statusC, err := task.Wait(ctx)
   292  	if err != nil {
   293  		t.Fatal(err)
   294  	}
   295  
   296  	if err := task.Start(ctx); err != nil {
   297  		t.Fatal(err)
   298  	}
   299  
   300  	checkpoint, err := container.Checkpoint(ctx, testCheckpointName+"newcontainer", []CheckpointOpts{
   301  		WithCheckpointRuntime,
   302  		WithCheckpointRW,
   303  		WithCheckpointTask,
   304  	}...)
   305  	if err != nil {
   306  		t.Fatal(err)
   307  	}
   308  
   309  	<-statusC
   310  
   311  	if _, err := task.Delete(ctx); err != nil {
   312  		t.Fatal(err)
   313  	}
   314  	if err := container.Delete(ctx, WithSnapshotCleanup); err != nil {
   315  		t.Fatal(err)
   316  	}
   317  	if container, err = client.Restore(ctx, id, checkpoint, []RestoreOpts{
   318  		WithRestoreImage,
   319  		WithRestoreSpec,
   320  		WithRestoreRuntime,
   321  		WithRestoreRW,
   322  	}...); err != nil {
   323  		t.Fatal(err)
   324  	}
   325  	if task, err = container.NewTask(ctx, empty(), WithTaskCheckpoint(checkpoint)); err != nil {
   326  		t.Fatal(err)
   327  	}
   328  	defer task.Delete(ctx)
   329  
   330  	statusC, err = task.Wait(ctx)
   331  	if err != nil {
   332  		t.Fatal(err)
   333  	}
   334  
   335  	if err := task.Start(ctx); err != nil {
   336  		t.Fatal(err)
   337  	}
   338  
   339  	if err := task.Kill(ctx, syscall.SIGKILL); err != nil {
   340  		t.Fatal(err)
   341  	}
   342  	<-statusC
   343  }
   344  
   345  func TestCheckpointLeaveRunning(t *testing.T) {
   346  	if testing.Short() {
   347  		t.Skip()
   348  	}
   349  	if !supportsCriu {
   350  		t.Skip("system does not have criu installed")
   351  	}
   352  	client, err := newClient(t, address)
   353  	if err != nil {
   354  		t.Fatal(err)
   355  	}
   356  	defer client.Close()
   357  	if client.runtime == plugin.RuntimeLinuxV1 {
   358  		t.Skip()
   359  	}
   360  
   361  	var (
   362  		ctx, cancel = testContext(t)
   363  		id          = t.Name()
   364  	)
   365  	defer cancel()
   366  
   367  	image, err := client.GetImage(ctx, testImage)
   368  	if err != nil {
   369  		t.Fatal(err)
   370  	}
   371  	container, err := client.NewContainer(ctx, id, WithNewSnapshot(id, image), WithNewSpec(oci.WithImageConfig(image), oci.WithProcessArgs("sleep", "100")))
   372  	if err != nil {
   373  		t.Fatal(err)
   374  	}
   375  	defer container.Delete(ctx, WithSnapshotCleanup)
   376  
   377  	task, err := container.NewTask(ctx, empty())
   378  	if err != nil {
   379  		t.Fatal(err)
   380  	}
   381  	defer task.Delete(ctx)
   382  
   383  	statusC, err := task.Wait(ctx)
   384  	if err != nil {
   385  		t.Fatal(err)
   386  	}
   387  
   388  	if err := task.Start(ctx); err != nil {
   389  		t.Fatal(err)
   390  	}
   391  
   392  	// checkpoint
   393  	if _, err := container.Checkpoint(ctx, testCheckpointName+"leaverunning", []CheckpointOpts{
   394  		WithCheckpointRuntime,
   395  		WithCheckpointRW,
   396  		WithCheckpointTask,
   397  	}...); err != nil {
   398  		t.Fatal(err)
   399  	}
   400  
   401  	status, err := task.Status(ctx)
   402  	if err != nil {
   403  		t.Fatal(err)
   404  	}
   405  	if status.Status != Running {
   406  		t.Fatalf("expected status %q but received %q", Running, status)
   407  	}
   408  
   409  	if err := task.Kill(ctx, syscall.SIGKILL); err != nil {
   410  		t.Fatal(err)
   411  	}
   412  
   413  	<-statusC
   414  }
   415  
   416  func TestCRWithImagePath(t *testing.T) {
   417  	if !supportsCriu {
   418  		t.Skip("system does not have criu installed")
   419  	}
   420  
   421  	client, err := newClient(t, address)
   422  	if err != nil {
   423  		t.Fatal(err)
   424  	}
   425  	defer client.Close()
   426  
   427  	var (
   428  		ctx, cancel = testContext(t)
   429  		id          = t.Name() + "-checkpoint"
   430  	)
   431  	defer cancel()
   432  
   433  	image, err := client.GetImage(ctx, testImage)
   434  	if err != nil {
   435  		t.Fatal(err)
   436  	}
   437  
   438  	container, err := client.NewContainer(ctx, id, WithNewSnapshot(id, image), WithNewSpec(oci.WithImageConfig(image), oci.WithProcessArgs("top")))
   439  	if err != nil {
   440  		t.Fatal(err)
   441  	}
   442  	defer container.Delete(ctx, WithSnapshotCleanup)
   443  
   444  	task, err := container.NewTask(ctx, empty())
   445  	if err != nil {
   446  		t.Fatal(err)
   447  	}
   448  	statusC, err := task.Wait(ctx)
   449  	if err != nil {
   450  		t.Fatal(err)
   451  	}
   452  	if err := task.Start(ctx); err != nil {
   453  		t.Fatal(err)
   454  	}
   455  
   456  	// create image path store criu image files
   457  	crDir, err := ioutil.TempDir("", "test-cr")
   458  	if err != nil {
   459  		t.Fatal(err)
   460  	}
   461  	defer os.RemoveAll(crDir)
   462  	imagePath := filepath.Join(crDir, "cr")
   463  	// checkpoint task
   464  	if _, err := task.Checkpoint(ctx, WithCheckpointImagePath(imagePath)); err != nil {
   465  		t.Fatal(err)
   466  	}
   467  
   468  	if err := task.Kill(ctx, syscall.SIGKILL); err != nil {
   469  		t.Fatal(err)
   470  	}
   471  	<-statusC
   472  	task.Delete(ctx)
   473  
   474  	// check image files have been dumped into image path
   475  	if files, err := ioutil.ReadDir(imagePath); err != nil || len(files) == 0 {
   476  		t.Fatal("failed to checkpoint with image path set")
   477  	}
   478  
   479  	// restore task with same container image and checkpoint directory,
   480  	// the restore process should finish in millisecond level
   481  	id = t.Name() + "-restore"
   482  	ncontainer, err := client.NewContainer(ctx, id, WithNewSnapshot(id, image), WithNewSpec(oci.WithImageConfig(image)))
   483  	if err != nil {
   484  		t.Fatal(err)
   485  	}
   486  	defer ncontainer.Delete(ctx, WithSnapshotCleanup)
   487  
   488  	ntask, err := ncontainer.NewTask(ctx, empty(), WithRestoreImagePath(imagePath))
   489  	if err != nil {
   490  		t.Fatal(err)
   491  	}
   492  	statusC, err = ntask.Wait(ctx)
   493  	if err != nil {
   494  		t.Fatal(err)
   495  	}
   496  	if err := ntask.Start(ctx); err != nil {
   497  		t.Fatal(err)
   498  	}
   499  
   500  	// check top process is existed in restored container
   501  	spec, err := container.Spec(ctx)
   502  	if err != nil {
   503  		t.Fatal(err)
   504  	}
   505  
   506  	stdout := bytes.NewBuffer(nil)
   507  	spec.Process.Args = []string{"ps", "-ef"}
   508  	process, err := ntask.Exec(ctx, t.Name()+"_exec", spec.Process, cio.NewCreator(withByteBuffers(stdout)))
   509  	if err != nil {
   510  		t.Fatal(err)
   511  	}
   512  	processStatusC, err := process.Wait(ctx)
   513  	if err != nil {
   514  		t.Fatal(err)
   515  	}
   516  	if err := process.Start(ctx); err != nil {
   517  		t.Fatal(err)
   518  	}
   519  	<-processStatusC
   520  	if _, err := process.Delete(ctx); err != nil {
   521  		t.Fatal(err)
   522  	}
   523  
   524  	if !strings.Contains(stdout.String(), "top") {
   525  		t.Errorf("except top process exists in restored container but not, got output %s", stdout.String())
   526  	}
   527  
   528  	// we wrote the same thing after attach
   529  	if err := ntask.Kill(ctx, syscall.SIGKILL); err != nil {
   530  		t.Fatal(err)
   531  	}
   532  	<-statusC
   533  	ntask.Delete(ctx)
   534  }