k8s.io/kubernetes@v1.29.3/test/e2e/node/ssh.go (about)

     1  /*
     2  Copyright 2015 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 node
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strings"
    23  
    24  	"k8s.io/kubernetes/test/e2e/framework"
    25  	e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
    26  	e2essh "k8s.io/kubernetes/test/e2e/framework/ssh"
    27  	admissionapi "k8s.io/pod-security-admission/api"
    28  
    29  	"github.com/onsi/ginkgo/v2"
    30  )
    31  
    32  const maxNodes = 100
    33  
    34  var _ = SIGDescribe("SSH", func() {
    35  
    36  	f := framework.NewDefaultFramework("ssh")
    37  	f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
    38  
    39  	ginkgo.BeforeEach(func() {
    40  		// When adding more providers here, also implement their functionality in e2essh.GetSigner(...).
    41  		e2eskipper.SkipUnlessProviderIs(framework.ProvidersWithSSH...)
    42  
    43  		// This test SSH's into the node for which it needs the $HOME/.ssh/id_rsa key to be present. So
    44  		// we should skip if the environment does not have the key (not all CI systems support this use case)
    45  		e2eskipper.SkipUnlessSSHKeyPresent()
    46  	})
    47  
    48  	ginkgo.It("should SSH to all nodes and run commands", func(ctx context.Context) {
    49  		// Get all nodes' external IPs.
    50  		ginkgo.By("Getting all nodes' SSH-able IP addresses")
    51  		hosts, err := e2essh.NodeSSHHosts(ctx, f.ClientSet)
    52  		if err != nil {
    53  			framework.Failf("Error getting node hostnames: %v", err)
    54  		}
    55  		ginkgo.By(fmt.Sprintf("Found %d SSH'able hosts", len(hosts)))
    56  
    57  		testCases := []struct {
    58  			cmd            string
    59  			checkStdout    bool
    60  			expectedStdout string
    61  			expectedStderr string
    62  			expectedCode   int
    63  			expectedError  error
    64  		}{
    65  			// Keep this test first - this variant runs on all nodes.
    66  			{`echo "Hello from $(whoami)@$(hostname)"`, false, "", "", 0, nil},
    67  			{`echo "foo" | grep "bar"`, true, "", "", 1, nil},
    68  			{`echo "stdout" && echo "stderr" >&2 && exit 7`, true, "stdout", "stderr", 7, nil},
    69  		}
    70  
    71  		for i, testCase := range testCases {
    72  			// Only run the first testcase against max 100 nodes. Run
    73  			// the rest against the first node we find only, since
    74  			// they're basically testing SSH semantics (and we don't
    75  			// need to do that against each host in the cluster).
    76  			nodes := len(hosts)
    77  			if i > 0 {
    78  				nodes = 1
    79  			} else if nodes > maxNodes {
    80  				nodes = maxNodes
    81  			}
    82  			testhosts := hosts[:nodes]
    83  			ginkgo.By(fmt.Sprintf("SSH'ing to %d nodes and running %s", len(testhosts), testCase.cmd))
    84  
    85  			for _, host := range testhosts {
    86  				ginkgo.By(fmt.Sprintf("SSH'ing host %s", host))
    87  
    88  				result, err := e2essh.SSH(ctx, testCase.cmd, host, framework.TestContext.Provider)
    89  				stdout, stderr := strings.TrimSpace(result.Stdout), strings.TrimSpace(result.Stderr)
    90  				if err != testCase.expectedError {
    91  					framework.Failf("Ran %s on %s, got error %v, expected %v", testCase.cmd, host, err, testCase.expectedError)
    92  				}
    93  				if testCase.checkStdout && stdout != testCase.expectedStdout {
    94  					framework.Failf("Ran %s on %s, got stdout '%s', expected '%s'", testCase.cmd, host, stdout, testCase.expectedStdout)
    95  				}
    96  				if stderr != testCase.expectedStderr {
    97  					framework.Failf("Ran %s on %s, got stderr '%s', expected '%s'", testCase.cmd, host, stderr, testCase.expectedStderr)
    98  				}
    99  				if result.Code != testCase.expectedCode {
   100  					framework.Failf("Ran %s on %s, got exit code %d, expected %d", testCase.cmd, host, result.Code, testCase.expectedCode)
   101  				}
   102  				// Show stdout, stderr for logging purposes.
   103  				if len(stdout) > 0 {
   104  					framework.Logf("Got stdout from %s: %s", host, strings.TrimSpace(stdout))
   105  				}
   106  				if len(stderr) > 0 {
   107  					framework.Logf("Got stderr from %s: %s", host, strings.TrimSpace(stderr))
   108  				}
   109  			}
   110  		}
   111  
   112  		// Quickly test that SSH itself errors correctly.
   113  		ginkgo.By("SSH'ing to a nonexistent host")
   114  		if _, err = e2essh.SSH(ctx, `echo "hello"`, "i.do.not.exist", framework.TestContext.Provider); err == nil {
   115  			framework.Failf("Expected error trying to SSH to nonexistent host.")
   116  		}
   117  	})
   118  })