mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-05 19:21:37 +00:00
Implement Go e2e SSH utility and simple test that runs it on all nodes.
This commit is contained in:
122
test/e2e/ssh.go
Normal file
122
test/e2e/ssh.go
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2015 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package e2e
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("SSH", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
var err error
|
||||||
|
c, err = loadClient()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should SSH to all nodes and run commands", func() {
|
||||||
|
// When adding more providers here, also implement their functionality
|
||||||
|
// in util.go's getSigner(...).
|
||||||
|
provider := testContext.Provider
|
||||||
|
if !providerIs("gce", "gke") {
|
||||||
|
By(fmt.Sprintf("Skipping SSH test, which is not implemented for %s", provider))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all nodes' external IPs.
|
||||||
|
By("Getting all nodes' SSH-able IP addresses")
|
||||||
|
nodelist, err := c.Nodes().List(labels.Everything(), fields.Everything())
|
||||||
|
if err != nil {
|
||||||
|
Failf("Error getting nodes: %v", err)
|
||||||
|
}
|
||||||
|
hosts := make([]string, 0, len(nodelist.Items))
|
||||||
|
for _, n := range nodelist.Items {
|
||||||
|
for _, addr := range n.Status.Addresses {
|
||||||
|
// Use the first external IP address we find on the node, and
|
||||||
|
// use at most one per node.
|
||||||
|
// NOTE: Until #7412 is fixed this will repeatedly ssh into the
|
||||||
|
// master node and not check any of the minions.
|
||||||
|
if addr.Type == api.NodeExternalIP {
|
||||||
|
hosts = append(hosts, addr.Address+":22")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail if any node didn't have an external IP.
|
||||||
|
if len(hosts) != len(nodelist.Items) {
|
||||||
|
Failf("Only found %d external IPs on nodes, but found %d nodes. Nodelist: %v",
|
||||||
|
len(hosts), len(nodelist.Items), nodelist)
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
cmd string
|
||||||
|
checkStdout bool
|
||||||
|
expectedStdout string
|
||||||
|
expectedStderr string
|
||||||
|
expectedCode int
|
||||||
|
expectedError error
|
||||||
|
}{
|
||||||
|
{`echo "Hello"`, true, "Hello", "", 0, nil},
|
||||||
|
// Same as previous, but useful for test output diagnostics.
|
||||||
|
{`echo "Hello from $(whoami)@$(hostname)"`, false, "", "", 0, nil},
|
||||||
|
{`echo "foo" | grep "bar"`, true, "", "", 1, nil},
|
||||||
|
{`echo "Out" && echo "Error" >&2 && exit 7`, true, "Out", "Error", 7, nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run commands on all nodes via SSH.
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
By(fmt.Sprintf("SSH'ing to all nodes and running %s", testCase.cmd))
|
||||||
|
for _, host := range hosts {
|
||||||
|
stdout, stderr, code, err := SSH(testCase.cmd, host, provider)
|
||||||
|
stdout, stderr = strings.TrimSpace(stdout), strings.TrimSpace(stderr)
|
||||||
|
if err != testCase.expectedError {
|
||||||
|
Failf("Ran %s on %s, got error %v, expected %v", testCase.cmd, host, err, testCase.expectedError)
|
||||||
|
}
|
||||||
|
if testCase.checkStdout && stdout != testCase.expectedStdout {
|
||||||
|
Failf("Ran %s on %s, got stdout '%s', expected '%s'", testCase.cmd, host, stdout, testCase.expectedStdout)
|
||||||
|
}
|
||||||
|
if stderr != testCase.expectedStderr {
|
||||||
|
Failf("Ran %s on %s, got stderr '%s', expected '%s'", testCase.cmd, host, stderr, testCase.expectedStderr)
|
||||||
|
}
|
||||||
|
if code != testCase.expectedCode {
|
||||||
|
Failf("Ran %s on %s, got exit code %d, expected %d", testCase.cmd, host, code, testCase.expectedCode)
|
||||||
|
}
|
||||||
|
// Show stdout, stderr for logging purposes.
|
||||||
|
if len(stdout) > 0 {
|
||||||
|
Logf("Got stdout from %s: %s", host, strings.TrimSpace(stdout))
|
||||||
|
}
|
||||||
|
if len(stderr) > 0 {
|
||||||
|
Logf("Got stderr from %s: %s", host, strings.TrimSpace(stderr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quickly test that SSH itself errors correctly.
|
||||||
|
By("SSH'ing to a nonexistent host")
|
||||||
|
if _, _, _, err = SSH(`echo "hello"`, "i.do.not.exist", provider); err == nil {
|
||||||
|
Failf("Expected error trying to SSH to nonexistent host.")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
@@ -19,13 +19,17 @@ package e2e
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"code.google.com/p/go-uuid/uuid"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
|
||||||
@@ -34,10 +38,10 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/wait"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/wait"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
"code.google.com/p/go-uuid/uuid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -626,3 +630,85 @@ func BadEvents(events []*api.Event) int {
|
|||||||
}
|
}
|
||||||
return badEvents
|
return badEvents
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SSH synchronously SSHs to a node running on provider and runs cmd. If there
|
||||||
|
// is no error performing the SSH, the stdout, stderr, and exit code are
|
||||||
|
// returned.
|
||||||
|
func SSH(cmd, host, provider string) (string, string, int, error) {
|
||||||
|
// Get a signer for the provider.
|
||||||
|
signer, err := getSigner(provider)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", 0, fmt.Errorf("error getting signer for provider %s: '%v'", provider, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the config, dial the server, and open a session.
|
||||||
|
config := &ssh.ClientConfig{
|
||||||
|
User: os.Getenv("USER"),
|
||||||
|
Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)},
|
||||||
|
}
|
||||||
|
client, err := ssh.Dial("tcp", host, config)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", 0, fmt.Errorf("error getting SSH client to host %s: '%v'", host, err)
|
||||||
|
}
|
||||||
|
session, err := client.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", 0, fmt.Errorf("error creating session to host %s: '%v'", host, err)
|
||||||
|
}
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
// Run the command.
|
||||||
|
code := 0
|
||||||
|
var bout, berr bytes.Buffer
|
||||||
|
session.Stdout, session.Stderr = &bout, &berr
|
||||||
|
if err = session.Run(cmd); err != nil {
|
||||||
|
// Check whether the command failed to run or didn't complete.
|
||||||
|
if exiterr, ok := err.(*ssh.ExitError); ok {
|
||||||
|
// If we got an ExitError and the exit code is nonzero, we'll
|
||||||
|
// consider the SSH itself successful (just that the command run
|
||||||
|
// errored on the host).
|
||||||
|
if code = exiterr.ExitStatus(); code != 0 {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Some other kind of error happened (e.g. an IOError); consider the
|
||||||
|
// SSH unsuccessful.
|
||||||
|
err = fmt.Errorf("failed running `%s` on %s: '%v'", cmd, host, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bout.String(), berr.String(), code, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSigner returns an ssh.Signer for the provider ("gce", etc.) that can be
|
||||||
|
// used to SSH to their nodes.
|
||||||
|
func getSigner(provider string) (ssh.Signer, error) {
|
||||||
|
// Get the directory in which SSH keys are located.
|
||||||
|
keydir := filepath.Join(os.Getenv("HOME"), ".ssh")
|
||||||
|
|
||||||
|
// Select the key itself to use. When implementing more providers here,
|
||||||
|
// please also add them to any SSH tests that are disabled because of signer
|
||||||
|
// support.
|
||||||
|
keyfile := ""
|
||||||
|
switch provider {
|
||||||
|
case "gce", "gke":
|
||||||
|
keyfile = "google_compute_engine"
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("getSigner(...) not implemented for %s", provider)
|
||||||
|
}
|
||||||
|
key := filepath.Join(keydir, keyfile)
|
||||||
|
|
||||||
|
// Create an actual signer.
|
||||||
|
file, err := os.Open(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error opening SSH key %s: '%v'", key, err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
buffer, err := ioutil.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading SSH key %s: '%v'", key, err)
|
||||||
|
}
|
||||||
|
signer, err := ssh.ParsePrivateKey(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing SSH key %s: '%v'", key, err)
|
||||||
|
}
|
||||||
|
return signer, nil
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user