k8s.io/kubernetes@v1.29.3/test/e2e_node/checkpoint_container.go (about)

     1  /*
     2  Copyright 2022 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package e2enode
    18  
    19  import (
    20  	"archive/tar"
    21  	"context"
    22  	"encoding/json"
    23  	"fmt"
    24  	"io"
    25  	"net/http"
    26  	"os"
    27  	"strings"
    28  	"time"
    29  
    30  	"github.com/onsi/ginkgo/v2"
    31  	v1 "k8s.io/api/core/v1"
    32  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    33  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    34  	clientset "k8s.io/client-go/kubernetes"
    35  	restclient "k8s.io/client-go/rest"
    36  	"k8s.io/kubernetes/test/e2e/framework"
    37  	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
    38  	"k8s.io/kubernetes/test/e2e/nodefeature"
    39  	testutils "k8s.io/kubernetes/test/utils"
    40  	imageutils "k8s.io/kubernetes/test/utils/image"
    41  	admissionapi "k8s.io/pod-security-admission/api"
    42  )
    43  
    44  const (
    45  	// timeout for proxy requests.
    46  	proxyTimeout = 2 * time.Minute
    47  )
    48  
    49  type checkpointResult struct {
    50  	Items []string `json:"items"`
    51  }
    52  
    53  // proxyPostRequest performs a post on a node proxy endpoint given the nodename and rest client.
    54  func proxyPostRequest(ctx context.Context, c clientset.Interface, node, endpoint string, port int) (restclient.Result, error) {
    55  	// proxy tends to hang in some cases when Node is not ready. Add an artificial timeout for this call. #22165
    56  	var result restclient.Result
    57  	finished := make(chan struct{}, 1)
    58  	go func() {
    59  		result = c.CoreV1().RESTClient().Post().
    60  			Resource("nodes").
    61  			SubResource("proxy").
    62  			Name(fmt.Sprintf("%v:%v", node, port)).
    63  			Suffix(endpoint).
    64  			Do(ctx)
    65  
    66  		finished <- struct{}{}
    67  	}()
    68  	select {
    69  	case <-finished:
    70  		return result, nil
    71  	case <-ctx.Done():
    72  		return restclient.Result{}, nil
    73  	case <-time.After(proxyTimeout):
    74  		return restclient.Result{}, nil
    75  	}
    76  }
    77  
    78  var _ = SIGDescribe("Checkpoint Container", nodefeature.CheckpointContainer, func() {
    79  	f := framework.NewDefaultFramework("checkpoint-container-test")
    80  	f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
    81  	ginkgo.It("will checkpoint a container out of a pod", func(ctx context.Context) {
    82  		ginkgo.By("creating a target pod")
    83  		podClient := e2epod.NewPodClient(f)
    84  		pod := podClient.CreateSync(ctx, &v1.Pod{
    85  			ObjectMeta: metav1.ObjectMeta{Name: "checkpoint-container-pod"},
    86  			Spec: v1.PodSpec{
    87  				Containers: []v1.Container{
    88  					{
    89  						Name:    "test-container-1",
    90  						Image:   imageutils.GetE2EImage(imageutils.BusyBox),
    91  						Command: []string{"/bin/sleep"},
    92  						Args:    []string{"10000"},
    93  					},
    94  				},
    95  			},
    96  		})
    97  
    98  		p, err := podClient.Get(
    99  			ctx,
   100  			pod.Name,
   101  			metav1.GetOptions{},
   102  		)
   103  
   104  		framework.ExpectNoError(err)
   105  		isReady, err := testutils.PodRunningReady(p)
   106  		framework.ExpectNoError(err)
   107  		if !isReady {
   108  			framework.Failf("pod %q should be ready", p.Name)
   109  		}
   110  
   111  		framework.Logf(
   112  			"About to checkpoint container %q on %q",
   113  			pod.Spec.Containers[0].Name,
   114  			pod.Spec.NodeName,
   115  		)
   116  		result, err := proxyPostRequest(
   117  			ctx,
   118  			f.ClientSet,
   119  			pod.Spec.NodeName,
   120  			fmt.Sprintf(
   121  				"checkpoint/%s/%s/%s",
   122  				f.Namespace.Name,
   123  				pod.Name,
   124  				pod.Spec.Containers[0].Name,
   125  			),
   126  			framework.KubeletPort,
   127  		)
   128  
   129  		framework.ExpectNoError(err)
   130  
   131  		err = result.Error()
   132  		if err != nil {
   133  			statusError, ok := err.(*apierrors.StatusError)
   134  			if !ok {
   135  				framework.Failf("got error %#v, expected StatusError", err)
   136  			}
   137  			// If we are testing against a kubelet with ContainerCheckpoint == false
   138  			// we should get a 404. So a 404 is (also) a good sign.
   139  			if (int(statusError.ErrStatus.Code)) == http.StatusNotFound {
   140  				ginkgo.Skip("Feature 'ContainerCheckpoint' is not enabled and not available")
   141  				return
   142  			}
   143  
   144  			// If the container engine has not implemented the Checkpoint CRI API
   145  			// we will get 500 and a message with
   146  			// '(rpc error: code = Unimplemented desc = unknown method CheckpointContainer'
   147  			if (int(statusError.ErrStatus.Code)) == http.StatusInternalServerError {
   148  				if strings.Contains(
   149  					statusError.ErrStatus.Message,
   150  					"(rpc error: code = Unimplemented desc = unknown method CheckpointContainer",
   151  				) {
   152  					ginkgo.Skip("Container engine does not implement 'CheckpointContainer'")
   153  					return
   154  				}
   155  			}
   156  			framework.Failf("Unexpected status code (%d) during 'CheckpointContainer'", statusError.ErrStatus.Code)
   157  		}
   158  
   159  		framework.ExpectNoError(err)
   160  
   161  		// Checkpointing actually worked. Verify that the checkpoint exists and that
   162  		// it is a checkpoint.
   163  
   164  		raw, err := result.Raw()
   165  		framework.ExpectNoError(err)
   166  		answer := checkpointResult{}
   167  		err = json.Unmarshal(raw, &answer)
   168  		framework.ExpectNoError(err)
   169  
   170  		for _, item := range answer.Items {
   171  			// Check that the file exists
   172  			_, err := os.Stat(item)
   173  			framework.ExpectNoError(err)
   174  			// Check the content of the tar file
   175  			// At least looking for the following files
   176  			//  * spec.dump
   177  			//  * config.dump
   178  			//  * checkpoint/inventory.img
   179  			// If these files exist in the checkpoint archive it is
   180  			// probably a complete checkpoint.
   181  			checkForFiles := map[string]bool{
   182  				"spec.dump":                false,
   183  				"config.dump":              false,
   184  				"checkpoint/inventory.img": false,
   185  			}
   186  			fileReader, err := os.Open(item)
   187  			framework.ExpectNoError(err)
   188  			tr := tar.NewReader(fileReader)
   189  			for {
   190  				hdr, err := tr.Next()
   191  				if err == io.EOF {
   192  					// End of archive
   193  					break
   194  				}
   195  				framework.ExpectNoError(err)
   196  				if _, key := checkForFiles[hdr.Name]; key {
   197  					checkForFiles[hdr.Name] = true
   198  				}
   199  			}
   200  			for fileName := range checkForFiles {
   201  				if !checkForFiles[fileName] {
   202  					framework.Failf("File %q not found in checkpoint archive %q", fileName, item)
   203  				}
   204  			}
   205  			// cleanup checkpoint archive
   206  			os.RemoveAll(item)
   207  		}
   208  	})
   209  })