2014-10-23 00:49:40 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2014 The Kubernetes Authors .
2014-10-23 00:49:40 +00:00
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 .
* /
// e2e.go runs the e2e test suite. No non-standard package dependencies; call with "go run".
package main
import (
2016-11-17 19:00:04 +00:00
"bytes"
2016-09-23 01:40:53 +00:00
"encoding/xml"
2014-10-23 00:49:40 +00:00
"flag"
2016-07-07 03:23:22 +00:00
"fmt"
"io/ioutil"
2014-10-23 00:49:40 +00:00
"log"
"os"
"os/exec"
2016-09-06 20:41:02 +00:00
"os/signal"
2016-10-04 00:18:29 +00:00
"os/user"
2014-10-23 00:49:40 +00:00
"path/filepath"
2016-12-05 23:38:48 +00:00
"regexp"
2016-09-26 22:01:43 +00:00
"strconv"
2014-10-23 00:49:40 +00:00
"strings"
2016-11-17 19:00:04 +00:00
"text/template"
2015-09-15 22:03:34 +00:00
"time"
2014-10-23 00:49:40 +00:00
)
var (
2016-12-01 23:23:35 +00:00
interrupt = time . NewTimer ( time . Duration ( 0 ) ) // interrupt testing at this time.
terminate = time . NewTimer ( time . Duration ( 0 ) ) // terminate testing at this time.
2016-08-09 23:22:47 +00:00
// TODO(fejta): change all these _ flags to -
2016-09-23 01:40:53 +00:00
build = flag . Bool ( "build" , false , "If true, build a new release. Otherwise, use whatever is there." )
2016-07-07 03:23:22 +00:00
checkVersionSkew = flag . Bool ( "check_version_skew" , true , "" +
"By default, verify that client and server have exact version match. " +
"You can explicitly set to false if you're, e.g., testing client changes " +
"for which the server version doesn't make a difference." )
checkLeakedResources = flag . Bool ( "check_leaked_resources" , false , "Ensure project ends with the same resources" )
2016-11-17 19:00:04 +00:00
deployment = flag . String ( "deployment" , "bash" , "up/down mechanism (defaults to cluster/kube-{up,down}.sh) (choices: bash/kops/kubernetes-anywhere)" )
2016-07-07 03:23:22 +00:00
down = flag . Bool ( "down" , false , "If true, tear down the cluster before exiting." )
2016-08-10 22:09:57 +00:00
dump = flag . String ( "dump" , "" , "If set, dump cluster logs to this location on test or cluster-up failure" )
2016-07-07 03:23:22 +00:00
kubemark = flag . Bool ( "kubemark" , false , "If true, run kubemark tests." )
skewTests = flag . Bool ( "skew" , false , "If true, run tests in another version at ../kubernetes/hack/e2e.go" )
testArgs = flag . String ( "test_args" , "" , "Space-separated list of arguments to pass to Ginkgo test runner." )
test = flag . Bool ( "test" , false , "Run Ginkgo tests." )
2016-12-01 23:23:35 +00:00
timeout = flag . Duration ( "timeout" , time . Duration ( 0 ) , "Terminate testing after the timeout duration (s/m/h)" )
2016-07-07 03:23:22 +00:00
up = flag . Bool ( "up" , false , "If true, start the the e2e cluster. If cluster is already up, recreate it." )
upgradeArgs = flag . String ( "upgrade_args" , "" , "If set, run upgrade tests before other tests" )
verbose = flag . Bool ( "v" , false , "If true, print all command output." )
2014-10-23 00:49:40 +00:00
2016-09-26 22:01:43 +00:00
// kops specific flags.
2016-10-04 20:51:02 +00:00
kopsPath = flag . String ( "kops" , "" , "(kops only) Path to the kops binary. Must be set for kops." )
kopsCluster = flag . String ( "kops-cluster" , "" , "(kops only) Cluster name. Must be set for kops." )
kopsState = flag . String ( "kops-state" , os . Getenv ( "KOPS_STATE_STORE" ) , "(kops only) s3:// path to kops state store. Must be set. (This flag defaults to $KOPS_STATE_STORE, and overrides it if set.)" )
kopsSSHKey = flag . String ( "kops-ssh-key" , os . Getenv ( "AWS_SSH_KEY" ) , "(kops only) Path to ssh key-pair for each node. (Defaults to $AWS_SSH_KEY or '~/.ssh/kube_aws_rsa'.)" )
kopsKubeVersion = flag . String ( "kops-kubernetes-version" , "" , "(kops only) If set, the version of Kubernetes to deploy (can be a URL to a GCS path where the release is stored) (Defaults to kops default, latest stable release.)." )
kopsZones = flag . String ( "kops-zones" , "us-west-2a" , "(kops AWS only) AWS zones for kops deployment, comma delimited." )
kopsNodes = flag . Int ( "kops-nodes" , 2 , "(kops only) Number of nodes to create." )
2016-11-01 20:57:24 +00:00
kopsUpTimeout = flag . Duration ( "kops-up-timeout" , 20 * time . Minute , "(kops only) Time limit between 'kops config / kops update' and a response from the Kubernetes API." )
2016-09-26 22:01:43 +00:00
2016-11-17 19:00:04 +00:00
// kubernetes-anywhere specific flags.
kubernetesAnywherePath = flag . String ( "kubernetes-anywhere-path" , "" , "(kubernetes-anywhere only) Path to the kubernetes-anywhere directory. Must be set for kubernetes-anywhere." )
kubernetesAnywherePhase2Provider = flag . String ( "kubernetes-anywhere-phase2-provider" , "ignition" , "(kubernetes-anywhere only) Provider for phase2 bootstrapping. (Defaults to ignition)." )
kubernetesAnywhereCluster = flag . String ( "kubernetes-anywhere-cluster" , "" , "(kubernetes-anywhere only) Cluster name. Must be set for kubernetes-anywhere." )
kubernetesAnywhereUpTimeout = flag . Duration ( "kubernetes-anywhere-up-timeout" , 20 * time . Minute , "(kubernetes-anywhere only) Time limit between starting a cluster and making a successful call to the Kubernetes API." )
2016-09-26 22:01:43 +00:00
// Deprecated flags.
2016-09-23 01:40:53 +00:00
deprecatedPush = flag . Bool ( "push" , false , "Deprecated. Does nothing." )
deprecatedPushup = flag . Bool ( "pushup" , false , "Deprecated. Does nothing." )
deprecatedCtlCmd = flag . String ( "ctl" , "" , "Deprecated. Does nothing." )
2014-12-16 00:03:11 +00:00
)
2014-10-23 00:49:40 +00:00
2016-11-17 19:00:04 +00:00
const kubernetesAnywhereConfigTemplate = `
. phase1 . num_nodes = 4
. phase1 . cluster_name = "{{.Cluster}}"
. phase1 . cloud_provider = "gce"
. phase1 . gce . os_image = "ubuntu-1604-xenial-v20160420c"
. phase1 . gce . instance_type = "n1-standard-2"
. phase1 . gce . project = "{{.Project}}"
. phase1 . gce . region = "us-central1"
. phase1 . gce . zone = "us-central1-b"
. phase1 . gce . network = "default"
. phase2 . installer_container = "docker.io/colemickens/k8s-ignition:latest"
. phase2 . docker_registry = "gcr.io/google-containers"
. phase2 . kubernetes_version = "v1.4.1"
. phase2 . provider = "{{.Phase2Provider}}"
. phase3 . run_addons = y
. phase3 . kube_proxy = y
. phase3 . dashboard = y
. phase3 . heapster = y
. phase3 . kube_dns = y
`
2016-08-09 23:22:47 +00:00
func appendError ( errs [ ] error , err error ) [ ] error {
if err != nil {
return append ( errs , err )
}
return errs
}
2016-09-23 01:40:53 +00:00
func validWorkingDirectory ( ) error {
2016-07-07 03:23:22 +00:00
cwd , err := os . Getwd ( )
if err != nil {
2016-09-23 01:40:53 +00:00
return fmt . Errorf ( "could not get pwd: %v" , err )
2016-07-07 03:23:22 +00:00
}
acwd , err := filepath . Abs ( cwd )
if err != nil {
2016-09-23 01:40:53 +00:00
return fmt . Errorf ( "failed to convert %s to an absolute path: %v" , cwd , err )
2016-07-07 03:23:22 +00:00
}
2016-09-23 01:40:53 +00:00
// This also matches "kubernetes_skew" for upgrades.
2016-07-07 03:23:22 +00:00
if ! strings . Contains ( filepath . Base ( acwd ) , "kubernetes" ) {
2016-09-23 01:40:53 +00:00
return fmt . Errorf ( "must run from kubernetes directory root: %v" , acwd )
}
return nil
}
type TestCase struct {
XMLName xml . Name ` xml:"testcase" `
ClassName string ` xml:"classname,attr" `
Name string ` xml:"name,attr" `
Time float64 ` xml:"time,attr" `
Failure string ` xml:"failure,omitempty" `
}
type TestSuite struct {
XMLName xml . Name ` xml:"testsuite" `
Failures int ` xml:"failures,attr" `
Tests int ` xml:"tests,attr" `
Time float64 ` xml:"time,attr" `
Cases [ ] TestCase
}
var suite TestSuite
func xmlWrap ( name string , f func ( ) error ) error {
start := time . Now ( )
err := f ( )
duration := time . Since ( start )
c := TestCase {
Name : name ,
ClassName : "e2e.go" ,
Time : duration . Seconds ( ) ,
}
if err != nil {
c . Failure = err . Error ( )
suite . Failures ++
}
suite . Cases = append ( suite . Cases , c )
suite . Tests ++
return err
}
func writeXML ( start time . Time ) {
suite . Time = time . Since ( start ) . Seconds ( )
out , err := xml . MarshalIndent ( & suite , "" , " " )
if err != nil {
log . Fatalf ( "Could not marshal XML: %s" , err )
}
path := filepath . Join ( * dump , "junit_runner.xml" )
f , err := os . Create ( path )
if err != nil {
log . Fatalf ( "Could not create file: %s" , err )
}
defer f . Close ( )
if _ , err := f . WriteString ( xml . Header ) ; err != nil {
log . Fatalf ( "Error writing XML header: %s" , err )
}
if _ , err := f . Write ( out ) ; err != nil {
log . Fatalf ( "Error writing XML data: %s" , err )
}
log . Printf ( "Saved XML output to %s." , path )
}
func main ( ) {
log . SetFlags ( log . LstdFlags | log . Lshortfile )
flag . Parse ( )
2016-12-01 23:23:35 +00:00
if ! terminate . Stop ( ) {
<- terminate . C // Drain the value if necessary.
}
if ! interrupt . Stop ( ) {
<- interrupt . C // Drain value
}
if * timeout > 0 {
log . Printf ( "Limiting testing to %s" , * timeout )
interrupt . Reset ( * timeout )
}
2016-09-23 01:40:53 +00:00
if err := validWorkingDirectory ( ) ; err != nil {
log . Fatalf ( "Called from invalid working directory: %v" , err )
}
2016-09-26 22:01:43 +00:00
deploy , err := getDeployer ( )
if err != nil {
log . Fatalf ( "Error creating deployer: %v" , err )
}
2016-09-06 20:41:02 +00:00
if * down {
// listen for signals such as ^C and gracefully attempt to clean up
c := make ( chan os . Signal , 1 )
signal . Notify ( c , os . Interrupt )
go func ( ) {
for range c {
log . Print ( "Captured ^C, gracefully attempting to cleanup resources.." )
if err := deploy . Down ( ) ; err != nil {
log . Printf ( "Tearing down deployment failed: %v" , err )
os . Exit ( 1 )
}
}
} ( )
}
2016-09-26 22:01:43 +00:00
if err := run ( deploy ) ; err != nil {
2016-09-23 01:40:53 +00:00
log . Fatalf ( "Something went wrong: %s" , err )
}
}
2016-09-26 22:01:43 +00:00
func run ( deploy deployer ) error {
2016-09-23 01:40:53 +00:00
if * dump != "" {
defer writeXML ( time . Now ( ) )
2016-07-07 03:23:22 +00:00
}
2016-07-14 14:20:30 +00:00
2014-10-23 00:49:40 +00:00
if * build {
2016-09-23 01:40:53 +00:00
if err := xmlWrap ( "Build" , Build ) ; err != nil {
return fmt . Errorf ( "error building: %s" , err )
2014-10-23 00:49:40 +00:00
}
}
2016-09-23 01:40:53 +00:00
if * checkVersionSkew {
os . Setenv ( "KUBECTL" , "./cluster/kubectl.sh --match-server-version" )
} else {
os . Setenv ( "KUBECTL" , "./cluster/kubectl.sh" )
}
os . Setenv ( "KUBE_CONFIG_FILE" , "config-test.sh" )
// force having batch/v2alpha1 always on for e2e tests
os . Setenv ( "KUBE_RUNTIME_CONFIG" , "batch/v2alpha1=true" )
2016-09-12 20:53:35 +00:00
if * up {
2016-09-26 22:01:43 +00:00
if err := xmlWrap ( "TearDown" , deploy . Down ) ; err != nil {
2016-09-23 01:40:53 +00:00
return fmt . Errorf ( "error tearing down previous cluster: %s" , err )
2016-08-09 23:22:47 +00:00
}
2014-12-08 22:55:47 +00:00
}
2016-07-07 03:23:22 +00:00
2016-09-23 01:40:53 +00:00
var err error
2016-08-09 23:22:47 +00:00
var errs [ ] error
2016-09-23 01:40:53 +00:00
var (
beforeResources [ ] byte
upResources [ ] byte
2016-11-11 01:34:07 +00:00
downResources [ ] byte
2016-09-23 01:40:53 +00:00
afterResources [ ] byte
)
2016-07-07 03:23:22 +00:00
if * checkLeakedResources {
2016-09-23 01:40:53 +00:00
errs = appendError ( errs , xmlWrap ( "ListResources Before" , func ( ) error {
beforeResources , err = ListResources ( )
return err
} ) )
2016-07-07 03:23:22 +00:00
}
2016-09-23 01:40:53 +00:00
if * up {
2016-10-10 10:21:57 +00:00
// If we tried to bring the cluster up, make a courtesy
// attempt to bring it down so we're not leaving resources around.
//
// TODO: We should try calling deploy.Down exactly once. Though to
// stop the leaking resources for now, we want to be on the safe side
// and call it explictly in defer if the other one is not called.
if * down {
defer xmlWrap ( "Deferred TearDown" , deploy . Down )
}
2016-09-23 01:40:53 +00:00
// Start the cluster using this version.
2016-09-26 22:01:43 +00:00
if err := xmlWrap ( "Up" , deploy . Up ) ; err != nil {
2016-12-01 00:18:53 +00:00
if * dump != "" {
xmlWrap ( "DumpClusterLogs" , func ( ) error {
return DumpClusterLogs ( * dump )
} )
}
2016-09-23 01:40:53 +00:00
return fmt . Errorf ( "starting e2e cluster: %s" , err )
2016-07-07 03:23:22 +00:00
}
2016-10-05 17:47:25 +00:00
if * dump != "" {
cmd := exec . Command ( "./cluster/kubectl.sh" , "--match-server-version=false" , "get" , "nodes" , "-oyaml" )
b , err := cmd . CombinedOutput ( )
if * verbose {
log . Printf ( "kubectl get nodes:\n%s" , string ( b ) )
}
if err == nil {
if err := ioutil . WriteFile ( filepath . Join ( * dump , "nodes.yaml" ) , b , 0644 ) ; err != nil {
errs = appendError ( errs , fmt . Errorf ( "error writing nodes.yaml: %v" , err ) )
}
} else {
errs = appendError ( errs , fmt . Errorf ( "error running get nodes: %v" , err ) )
}
}
2016-07-07 03:23:22 +00:00
}
if * checkLeakedResources {
2016-09-23 01:40:53 +00:00
errs = appendError ( errs , xmlWrap ( "ListResources Up" , func ( ) error {
upResources , err = ListResources ( )
return err
} ) )
2016-07-07 03:23:22 +00:00
}
if * upgradeArgs != "" {
2016-09-23 01:40:53 +00:00
errs = appendError ( errs , xmlWrap ( "UpgradeTest" , func ( ) error {
return UpgradeTest ( * upgradeArgs )
} ) )
2016-07-07 03:23:22 +00:00
}
if * test {
2016-09-26 22:01:43 +00:00
errs = appendError ( errs , xmlWrap ( "get kubeconfig" , deploy . SetupKubecfg ) )
2016-09-23 01:40:53 +00:00
errs = appendError ( errs , xmlWrap ( "kubectl version" , func ( ) error {
return finishRunning ( "kubectl version" , exec . Command ( "./cluster/kubectl.sh" , "version" , "--match-server-version=false" ) )
} ) )
2016-09-26 17:59:38 +00:00
// Individual tests will create their own JUnit, so don't xmlWrap.
2016-07-07 03:23:22 +00:00
if * skewTests {
2016-09-26 17:59:38 +00:00
errs = appendError ( errs , SkewTest ( ) )
2016-07-07 03:23:22 +00:00
} else {
2016-09-26 22:01:43 +00:00
if err := xmlWrap ( "IsUp" , deploy . IsUp ) ; err != nil {
2016-09-26 17:59:38 +00:00
errs = appendError ( errs , err )
} else {
errs = appendError ( errs , Test ( ) )
}
2016-07-07 03:23:22 +00:00
}
}
if * kubemark {
2016-09-27 23:22:23 +00:00
errs = appendError ( errs , KubemarkTest ( ) )
2016-09-23 01:40:53 +00:00
}
if len ( errs ) > 0 && * dump != "" {
errs = appendError ( errs , xmlWrap ( "DumpClusterLogs" , func ( ) error {
return DumpClusterLogs ( * dump )
} ) )
2016-08-10 22:09:57 +00:00
}
2016-11-11 01:34:07 +00:00
if * checkLeakedResources {
errs = appendError ( errs , xmlWrap ( "ListResources Down" , func ( ) error {
downResources , err = ListResources ( )
return err
} ) )
}
2014-10-23 00:49:40 +00:00
if * down {
2016-09-26 22:01:43 +00:00
errs = appendError ( errs , xmlWrap ( "TearDown" , deploy . Down ) )
2016-07-07 03:23:22 +00:00
}
if * checkLeakedResources {
log . Print ( "Sleeping for 30 seconds..." ) // Wait for eventually consistent listing
time . Sleep ( 30 * time . Second )
2016-09-23 01:40:53 +00:00
if err := xmlWrap ( "ListResources After" , func ( ) error {
afterResources , err = ListResources ( )
return err
} ) ; err != nil {
2016-08-09 23:22:47 +00:00
errs = append ( errs , err )
} else {
2016-09-23 01:40:53 +00:00
errs = appendError ( errs , xmlWrap ( "DiffResources" , func ( ) error {
2016-11-11 01:34:07 +00:00
return DiffResources ( beforeResources , upResources , downResources , afterResources , * dump )
2016-09-23 01:40:53 +00:00
} ) )
2016-08-09 23:22:47 +00:00
}
2014-10-23 00:49:40 +00:00
}
2016-08-09 23:22:47 +00:00
if len ( errs ) != 0 {
2016-09-23 01:40:53 +00:00
return fmt . Errorf ( "encountered %d errors: %v" , len ( errs ) , errs )
2016-07-07 03:23:22 +00:00
}
2016-09-23 01:40:53 +00:00
return nil
2016-07-07 03:23:22 +00:00
}
2016-11-11 01:34:07 +00:00
func DiffResources ( before , clusterUp , clusterDown , after [ ] byte , location string ) error {
2016-07-07 03:23:22 +00:00
if location == "" {
var err error
location , err = ioutil . TempDir ( "" , "e2e-check-resources" )
if err != nil {
2016-08-09 23:22:47 +00:00
return fmt . Errorf ( "Could not create e2e-check-resources temp dir: %s" , err )
2016-07-07 03:23:22 +00:00
}
}
2016-08-09 23:22:47 +00:00
var mode os . FileMode = 0664
bp := filepath . Join ( location , "gcp-resources-before.txt" )
up := filepath . Join ( location , "gcp-resources-cluster-up.txt" )
2016-11-11 01:34:07 +00:00
cdp := filepath . Join ( location , "gcp-resources-cluster-down.txt" )
2016-08-09 23:22:47 +00:00
ap := filepath . Join ( location , "gcp-resources-after.txt" )
dp := filepath . Join ( location , "gcp-resources-diff.txt" )
if err := ioutil . WriteFile ( bp , before , mode ) ; err != nil {
return err
}
if err := ioutil . WriteFile ( up , clusterUp , mode ) ; err != nil {
return err
}
2016-11-11 01:34:07 +00:00
if err := ioutil . WriteFile ( cdp , clusterDown , mode ) ; err != nil {
return err
}
2016-08-09 23:22:47 +00:00
if err := ioutil . WriteFile ( ap , after , mode ) ; err != nil {
return err
}
2016-07-07 03:23:22 +00:00
cmd := exec . Command ( "diff" , "-sw" , "-U0" , "-F^\\[.*\\]$" , bp , ap )
if * verbose {
cmd . Stderr = os . Stderr
}
2016-08-09 23:22:47 +00:00
stdout , cerr := cmd . Output ( )
if err := ioutil . WriteFile ( dp , stdout , mode ) ; err != nil {
return err
}
if cerr == nil { // No diffs
return nil
2016-07-07 03:23:22 +00:00
}
2016-08-09 23:22:47 +00:00
lines := strings . Split ( string ( stdout ) , "\n" )
2016-07-07 03:23:22 +00:00
if len ( lines ) < 3 { // Ignore the +++ and --- header lines
2016-08-09 23:22:47 +00:00
return nil
2016-07-07 03:23:22 +00:00
}
2016-08-16 15:14:30 +00:00
lines = lines [ 2 : ]
2016-07-07 03:23:22 +00:00
2016-12-05 23:38:48 +00:00
var added , report [ ] string
resourceTypeRE := regexp . MustCompile ( ` ^@@.+\s(\[\s\S+\s\])$ ` )
2016-07-07 03:23:22 +00:00
for _ , l := range lines {
2016-12-05 23:38:48 +00:00
if matches := resourceTypeRE . FindStringSubmatch ( l ) ; matches != nil {
report = append ( report , matches [ 1 ] )
}
2016-08-16 15:14:30 +00:00
if strings . HasPrefix ( l , "+" ) && len ( strings . TrimPrefix ( l , "+" ) ) > 0 {
2016-07-07 03:23:22 +00:00
added = append ( added , l )
2016-12-05 23:38:48 +00:00
report = append ( report , l )
2016-07-07 03:23:22 +00:00
}
}
if len ( added ) > 0 {
2016-12-05 23:38:48 +00:00
return fmt . Errorf ( "Error: %d leaked resources\n%v" , len ( added ) , strings . Join ( report , "\n" ) )
2016-07-07 03:23:22 +00:00
}
2016-08-09 23:22:47 +00:00
return nil
2016-07-07 03:23:22 +00:00
}
2016-08-09 23:22:47 +00:00
func ListResources ( ) ( [ ] byte , error ) {
2016-07-07 03:23:22 +00:00
log . Printf ( "Listing resources..." )
cmd := exec . Command ( "./cluster/gce/list-resources.sh" )
if * verbose {
cmd . Stderr = os . Stderr
}
stdout , err := cmd . Output ( )
if err != nil {
2016-08-09 23:22:47 +00:00
return nil , fmt . Errorf ( "Failed to list resources (%s):\n%s" , err , string ( stdout ) )
2016-07-07 03:23:22 +00:00
}
2016-08-09 23:22:47 +00:00
return stdout , nil
2016-07-07 03:23:22 +00:00
}
2016-09-23 01:40:53 +00:00
func Build ( ) error {
// The build-release script needs stdin to ask the user whether
// it's OK to download the docker image.
cmd := exec . Command ( "make" , "quick-release" )
cmd . Stdin = os . Stdin
if err := finishRunning ( "build-release" , cmd ) ; err != nil {
return fmt . Errorf ( "error building kubernetes: %v" , err )
}
return nil
}
2016-09-26 22:01:43 +00:00
type deployer interface {
Up ( ) error
IsUp ( ) error
SetupKubecfg ( ) error
Down ( ) error
}
func getDeployer ( ) ( deployer , error ) {
switch * deployment {
case "bash" :
return bash { } , nil
case "kops" :
return NewKops ( )
2016-11-17 19:00:04 +00:00
case "kubernetes-anywhere" :
return NewKubernetesAnywhere ( )
2016-09-26 22:01:43 +00:00
default :
return nil , fmt . Errorf ( "Unknown deployment strategy %q" , * deployment )
}
2014-10-23 00:49:40 +00:00
}
2016-09-26 22:01:43 +00:00
type bash struct { }
func ( b bash ) Up ( ) error {
2016-07-07 03:23:22 +00:00
return finishRunning ( "up" , exec . Command ( "./hack/e2e-internal/e2e-up.sh" ) )
2014-10-23 00:49:40 +00:00
}
2016-09-26 22:01:43 +00:00
func ( b bash ) IsUp ( ) error {
2016-09-26 17:59:38 +00:00
return finishRunning ( "get status" , exec . Command ( "./hack/e2e-internal/e2e-status.sh" ) )
2016-07-07 03:23:22 +00:00
}
2016-09-26 22:01:43 +00:00
func ( b bash ) SetupKubecfg ( ) error {
return nil
}
func ( b bash ) Down ( ) error {
return finishRunning ( "teardown" , exec . Command ( "./hack/e2e-internal/e2e-down.sh" ) )
}
type kops struct {
2016-10-04 20:51:02 +00:00
path string
kubeVersion string
sshKey string
zones [ ] string
nodes int
cluster string
kubecfg string
2016-09-26 22:01:43 +00:00
}
func NewKops ( ) ( * kops , error ) {
if * kopsPath == "" {
return nil , fmt . Errorf ( "--kops must be set to a valid binary path for kops deployment." )
}
if * kopsCluster == "" {
return nil , fmt . Errorf ( "--kops-cluster must be set to a valid cluster name for kops deployment." )
}
if * kopsState == "" {
return nil , fmt . Errorf ( "--kops-state must be set to a valid S3 path for kops deployment." )
}
2016-10-04 00:18:29 +00:00
sshKey := * kopsSSHKey
if sshKey == "" {
usr , err := user . Current ( )
if err != nil {
return nil , err
}
sshKey = filepath . Join ( usr . HomeDir , ".ssh/kube_aws_rsa" )
}
2016-09-26 22:01:43 +00:00
if err := os . Setenv ( "KOPS_STATE_STORE" , * kopsState ) ; err != nil {
return nil , err
}
f , err := ioutil . TempFile ( "" , "kops-kubecfg" )
if err != nil {
return nil , err
}
defer f . Close ( )
kubecfg := f . Name ( )
if err := f . Chmod ( 0600 ) ; err != nil {
return nil , err
}
if err := os . Setenv ( "KUBECONFIG" , kubecfg ) ; err != nil {
return nil , err
}
// Set KUBERNETES_CONFORMANCE_TEST so the auth info is picked up
// from kubectl instead of bash inference.
if err := os . Setenv ( "KUBERNETES_CONFORMANCE_TEST" , "yes" ) ; err != nil {
return nil , err
}
// Set KUBERNETES_CONFORMANCE_PROVIDER to override the
// cloudprovider for KUBERNETES_CONFORMANCE_TEST.
if err := os . Setenv ( "KUBERNETES_CONFORMANCE_PROVIDER" , "aws" ) ; err != nil {
return nil , err
}
2016-10-04 00:18:29 +00:00
// AWS_SSH_KEY is required by the AWS e2e tests.
if err := os . Setenv ( "AWS_SSH_KEY" , sshKey ) ; err != nil {
return nil , err
}
// ZONE is required by the AWS e2e tests.
2016-09-26 22:01:43 +00:00
zones := strings . Split ( * kopsZones , "," )
if err := os . Setenv ( "ZONE" , zones [ 0 ] ) ; err != nil {
return nil , err
}
return & kops {
2016-10-04 20:51:02 +00:00
path : * kopsPath ,
kubeVersion : * kopsKubeVersion ,
sshKey : sshKey + ".pub" , // kops only needs the public key, e2es need the private key.
zones : zones ,
nodes : * kopsNodes ,
cluster : * kopsCluster ,
kubecfg : kubecfg ,
2016-09-26 22:01:43 +00:00
} , nil
}
func ( k kops ) Up ( ) error {
2016-10-04 20:51:02 +00:00
createArgs := [ ] string {
"create" , "cluster" ,
2016-09-26 22:01:43 +00:00
"--name" , k . cluster ,
2016-10-04 00:18:29 +00:00
"--ssh-public-key" , k . sshKey ,
2016-09-26 22:01:43 +00:00
"--node-count" , strconv . Itoa ( k . nodes ) ,
2016-10-04 20:51:02 +00:00
"--zones" , strings . Join ( k . zones , "," ) ,
}
if k . kubeVersion != "" {
createArgs = append ( createArgs , "--kubernetes-version" , k . kubeVersion )
}
if err := finishRunning ( "kops config" , exec . Command ( k . path , createArgs ... ) ) ; err != nil {
2016-09-26 22:01:43 +00:00
return fmt . Errorf ( "kops configuration failed: %v" , err )
}
if err := finishRunning ( "kops update" , exec . Command ( k . path , "update" , "cluster" , k . cluster , "--yes" ) ) ; err != nil {
return fmt . Errorf ( "kops bringup failed: %v" , err )
}
// TODO(zmerlynn): More cluster validation. This should perhaps be
// added to kops and not here, but this is a fine place to loop
// for now.
2016-11-17 19:00:04 +00:00
return waitForNodes ( k , k . nodes + 1 , * kopsUpTimeout )
2016-09-26 22:01:43 +00:00
}
func ( k kops ) IsUp ( ) error {
2016-11-17 19:00:04 +00:00
return isUp ( k )
2016-09-26 22:01:43 +00:00
}
func ( k kops ) SetupKubecfg ( ) error {
info , err := os . Stat ( k . kubecfg )
if err != nil {
return err
}
if info . Size ( ) > 0 {
// Assume that if we already have it, it's good.
return nil
}
if err := finishRunning ( "kops export" , exec . Command ( k . path , "export" , "kubecfg" , k . cluster ) ) ; err != nil {
return fmt . Errorf ( "Failure exporting kops kubecfg: %v" , err )
}
return nil
}
func ( k kops ) Down ( ) error {
// We do a "kops get" first so the exit status of "kops delete" is
// more sensical in the case of a non-existant cluster. ("kops
// delete" will exit with status 1 on a non-existant cluster)
err := finishRunning ( "kops get" , exec . Command ( k . path , "get" , "clusters" , k . cluster ) )
if err != nil {
// This is expected if the cluster doesn't exist.
return nil
}
return finishRunning ( "kops delete" , exec . Command ( k . path , "delete" , "cluster" , k . cluster , "--yes" ) )
}
2016-11-17 19:00:04 +00:00
type kubernetesAnywhere struct {
path string
// These are exported only because their use in the config template requires it.
Phase2Provider string
Project string
Cluster string
}
func NewKubernetesAnywhere ( ) ( * kubernetesAnywhere , error ) {
if * kubernetesAnywherePath == "" {
return nil , fmt . Errorf ( "--kubernetes-anywhere-path is required" )
}
if * kubernetesAnywhereCluster == "" {
return nil , fmt . Errorf ( "--kubernetes-anywhere-cluster is required" )
}
project , ok := os . LookupEnv ( "PROJECT" )
if ! ok {
return nil , fmt . Errorf ( "The PROJECT environment variable is required to be set for kubernetes-anywhere" )
}
// Set KUBERNETES_CONFORMANCE_TEST so the auth info is picked up
// from kubectl instead of bash inference.
if err := os . Setenv ( "KUBERNETES_CONFORMANCE_TEST" , "yes" ) ; err != nil {
return nil , err
}
k := & kubernetesAnywhere {
path : * kubernetesAnywherePath ,
Phase2Provider : * kubernetesAnywherePhase2Provider ,
Project : project ,
Cluster : * kubernetesAnywhereCluster ,
}
if err := k . writeConfig ( ) ; err != nil {
return nil , err
}
return k , nil
}
func ( k kubernetesAnywhere ) getConfig ( ) ( string , error ) {
// As needed, plumb through more CLI options to replace these defaults
tmpl , err := template . New ( "kubernetes-anywhere-config" ) . Parse ( kubernetesAnywhereConfigTemplate )
if err != nil {
return "" , fmt . Errorf ( "Error creating template for KubernetesAnywhere config: %v" , err )
}
var buf bytes . Buffer
if err = tmpl . Execute ( & buf , k ) ; err != nil {
return "" , fmt . Errorf ( "Error executing template for KubernetesAnywhere config: %v" , err )
}
return buf . String ( ) , nil
}
func ( k kubernetesAnywhere ) writeConfig ( ) error {
config , err := k . getConfig ( )
if err != nil {
return fmt . Errorf ( "Could not generate config: %v" , err )
}
f , err := os . Create ( k . path + "/.config" )
if err != nil {
return fmt . Errorf ( "Could not create file: %v" , err )
}
defer f . Close ( )
fmt . Fprint ( f , config )
return nil
}
func ( k kubernetesAnywhere ) Up ( ) error {
cmd := exec . Command ( "make" , "-C" , k . path , "WAIT_FOR_KUBECONFIG=y" , "deploy-cluster" )
if err := finishRunning ( "deploy-cluster" , cmd ) ; err != nil {
return err
}
nodes := 4 // For now, this is hardcoded in the config
return waitForNodes ( k , nodes + 1 , * kubernetesAnywhereUpTimeout )
}
func ( k kubernetesAnywhere ) IsUp ( ) error {
return isUp ( k )
}
func ( k kubernetesAnywhere ) SetupKubecfg ( ) error {
output , err := exec . Command ( "make" , "--silent" , "-C" , k . path , "kubeconfig-path" ) . Output ( )
if err != nil {
return fmt . Errorf ( "Could not get kubeconfig-path: %v" , err )
}
kubecfg := strings . TrimSuffix ( string ( output ) , "\n" )
if err = os . Setenv ( "KUBECONFIG" , kubecfg ) ; err != nil {
return err
}
return nil
}
func ( k kubernetesAnywhere ) Down ( ) error {
err := finishRunning ( "get kubeconfig-path" , exec . Command ( "make" , "-C" , k . path , "kubeconfig-path" ) )
if err != nil {
// This is expected if the cluster doesn't exist.
return nil
}
return finishRunning ( "destroy-cluster" , exec . Command ( "make" , "-C" , k . path , "FORCE_DESTROY=y" , "destroy-cluster" ) )
}
2016-09-26 22:01:43 +00:00
func clusterSize ( deploy deployer ) ( int , error ) {
if err := deploy . SetupKubecfg ( ) ; err != nil {
return - 1 , err
}
o , err := exec . Command ( "kubectl" , "get" , "nodes" , "--no-headers" ) . Output ( )
if err != nil {
2016-11-29 13:00:09 +00:00
log . Printf ( "kubectl get nodes failed: %s\n%s" , WrapError ( err ) . Error ( ) , string ( o ) )
2016-09-26 22:01:43 +00:00
return - 1 , err
}
stdout := strings . TrimSpace ( string ( o ) )
log . Printf ( "Cluster nodes:\n%s" , stdout )
return len ( strings . Split ( stdout , "\n" ) ) , nil
}
2016-11-29 13:00:09 +00:00
// CommandError will provide stderr output (if available) from structured
// exit errors
type CommandError struct {
err error
}
func WrapError ( err error ) * CommandError {
if err == nil {
return nil
}
return & CommandError { err : err }
}
func ( e * CommandError ) Error ( ) string {
if e == nil {
return ""
}
exitErr , ok := e . err . ( * exec . ExitError )
if ! ok {
return e . err . Error ( )
}
stderr := ""
if exitErr . Stderr != nil {
stderr = string ( stderr )
}
return fmt . Sprintf ( "%q: %q" , exitErr . Error ( ) , stderr )
}
2016-11-17 19:00:04 +00:00
func isUp ( d deployer ) error {
n , err := clusterSize ( d )
if err != nil {
return err
}
if n <= 0 {
return fmt . Errorf ( "cluster found, but %d nodes reported" , n )
}
return nil
}
func waitForNodes ( d deployer , nodes int , timeout time . Duration ) error {
for stop := time . Now ( ) . Add ( timeout ) ; time . Now ( ) . Before ( stop ) ; time . Sleep ( 30 * time . Second ) {
n , err := clusterSize ( d )
if err != nil {
log . Printf ( "Can't get cluster size, sleeping: %v" , err )
continue
}
if n < nodes {
log . Printf ( "%d (current nodes) < %d (requested instances), sleeping" , n , nodes )
continue
}
return nil
}
return fmt . Errorf ( "waiting for nodes timed out" )
}
2016-08-09 23:22:47 +00:00
func DumpClusterLogs ( location string ) error {
2016-07-07 03:23:22 +00:00
log . Printf ( "Dumping cluster logs to: %v" , location )
2016-08-09 23:22:47 +00:00
return finishRunning ( "dump cluster logs" , exec . Command ( "./cluster/log-dump.sh" , location ) )
2016-07-07 03:23:22 +00:00
}
2016-08-09 23:22:47 +00:00
func KubemarkTest ( ) error {
2016-07-07 03:23:22 +00:00
// Stop previous run
2016-08-09 23:22:47 +00:00
err := finishRunning ( "Stop kubemark" , exec . Command ( "./test/kubemark/stop-kubemark.sh" ) )
if err != nil {
return err
2016-07-07 03:23:22 +00:00
}
2016-10-10 10:21:57 +00:00
// If we tried to bring the Kubemark cluster up, make a courtesy
// attempt to bring it down so we're not leaving resources around.
//
// TODO: We should try calling stop-kubemark exactly once. Though to
// stop the leaking resources for now, we want to be on the safe side
// and call it explictly in defer if the other one is not called.
defer xmlWrap ( "Deferred Stop kubemark" , func ( ) error {
return finishRunning ( "Stop kubemark" , exec . Command ( "./test/kubemark/stop-kubemark.sh" ) )
} )
2016-07-07 03:23:22 +00:00
// Start new run
backups := [ ] string { "NUM_NODES" , "MASTER_SIZE" }
for _ , item := range backups {
old , present := os . LookupEnv ( item )
if present {
defer os . Setenv ( item , old )
} else {
defer os . Unsetenv ( item )
}
}
os . Setenv ( "NUM_NODES" , os . Getenv ( "KUBEMARK_NUM_NODES" ) )
os . Setenv ( "MASTER_SIZE" , os . Getenv ( "KUBEMARK_MASTER_SIZE" ) )
2016-10-10 10:21:57 +00:00
err = xmlWrap ( "Start kubemark" , func ( ) error {
return finishRunning ( "Start kubemark" , exec . Command ( "./test/kubemark/start-kubemark.sh" ) )
} )
2016-08-09 23:22:47 +00:00
if err != nil {
return err
2016-07-07 03:23:22 +00:00
}
// Run kubemark tests
focus , present := os . LookupEnv ( "KUBEMARK_TESTS" )
if ! present {
focus = "starting\\s30\\pods"
}
test_args := os . Getenv ( "KUBEMARK_TEST_ARGS" )
2016-08-09 23:22:47 +00:00
err = finishRunning ( "Run kubemark tests" , exec . Command ( "./test/kubemark/run-e2e-tests.sh" , "--ginkgo.focus=" + focus , test_args ) )
if err != nil {
return err
2016-07-07 03:23:22 +00:00
}
2016-10-10 10:21:57 +00:00
err = xmlWrap ( "Stop kubemark" , func ( ) error {
return finishRunning ( "Stop kubemark" , exec . Command ( "./test/kubemark/stop-kubemark.sh" ) )
} )
2016-08-09 23:22:47 +00:00
if err != nil {
return err
2016-07-07 03:23:22 +00:00
}
2016-08-09 23:22:47 +00:00
return nil
2016-07-07 03:23:22 +00:00
}
2016-08-09 23:22:47 +00:00
func chdirSkew ( ) ( string , error ) {
2016-07-07 03:23:22 +00:00
old , err := os . Getwd ( )
if err != nil {
2016-09-23 01:40:53 +00:00
return "" , fmt . Errorf ( "failed to os.Getwd(): %v" , err )
2016-07-07 03:23:22 +00:00
}
err = os . Chdir ( "../kubernetes_skew" )
if err != nil {
2016-09-23 01:40:53 +00:00
return "" , fmt . Errorf ( "failed to cd ../kubernetes_skew: %v" , err )
2016-07-07 03:23:22 +00:00
}
2016-08-09 23:22:47 +00:00
return old , nil
}
func UpgradeTest ( args string ) error {
old , err := chdirSkew ( )
2016-07-07 03:23:22 +00:00
if err != nil {
2016-08-09 23:22:47 +00:00
return err
2016-07-07 03:23:22 +00:00
}
defer os . Chdir ( old )
previous , present := os . LookupEnv ( "E2E_REPORT_PREFIX" )
if present {
defer os . Setenv ( "E2E_REPORT_PREFIX" , previous )
} else {
defer os . Unsetenv ( "E2E_REPORT_PREFIX" )
}
os . Setenv ( "E2E_REPORT_PREFIX" , "upgrade" )
return finishRunning ( "Upgrade Ginkgo tests" ,
exec . Command (
"go" , "run" , "./hack/e2e.go" ,
"--test" ,
"--test_args=" + args ,
fmt . Sprintf ( "--v=%t" , * verbose ) ,
fmt . Sprintf ( "--check_version_skew=%t" , * checkVersionSkew ) ) )
}
2016-08-09 23:22:47 +00:00
func SkewTest ( ) error {
old , err := chdirSkew ( )
2016-07-07 03:23:22 +00:00
if err != nil {
2016-08-09 23:22:47 +00:00
return err
2016-07-07 03:23:22 +00:00
}
defer os . Chdir ( old )
return finishRunning ( "Skewed Ginkgo tests" ,
exec . Command (
"go" , "run" , "./hack/e2e.go" ,
"--test" ,
"--test_args=" + * testArgs ,
fmt . Sprintf ( "--v=%t" , * verbose ) ,
fmt . Sprintf ( "--check_version_skew=%t" , * checkVersionSkew ) ) )
2014-12-07 23:29:30 +00:00
}
2016-08-09 23:22:47 +00:00
func Test ( ) error {
2016-07-07 03:23:22 +00:00
// TODO(fejta): add a --federated or something similar
2016-08-23 20:14:58 +00:00
if os . Getenv ( "FEDERATION" ) != "true" {
2016-07-07 03:23:22 +00:00
return finishRunning ( "Ginkgo tests" , exec . Command ( "./hack/ginkgo-e2e.sh" , strings . Fields ( * testArgs ) ... ) )
2016-05-17 16:11:31 +00:00
}
2015-01-16 00:43:03 +00:00
2016-07-07 03:23:22 +00:00
if * testArgs == "" {
* testArgs = "--ginkgo.focus=\\[Feature:Federation\\]"
2016-05-10 21:44:45 +00:00
}
2016-07-07 03:23:22 +00:00
return finishRunning ( "Federated Ginkgo tests" , exec . Command ( "./hack/federated-ginkgo-e2e.sh" , strings . Fields ( * testArgs ) ... ) )
2014-10-23 00:49:40 +00:00
}
2016-08-09 23:22:47 +00:00
func finishRunning ( stepName string , cmd * exec . Cmd ) error {
2014-10-23 00:49:40 +00:00
if * verbose {
2015-02-13 21:53:27 +00:00
cmd . Stdout = os . Stdout
cmd . Stderr = os . Stderr
2014-10-23 00:49:40 +00:00
}
2015-02-13 21:53:27 +00:00
log . Printf ( "Running: %v" , stepName )
2015-09-15 22:03:34 +00:00
defer func ( start time . Time ) {
log . Printf ( "Step '%s' finished in %s" , stepName , time . Since ( start ) )
} ( time . Now ( ) )
2016-12-01 23:23:35 +00:00
if err := cmd . Start ( ) ; err != nil {
return fmt . Errorf ( "error starting %v: %v" , stepName , err )
}
finished := make ( chan error )
go func ( ) {
finished <- cmd . Wait ( )
} ( )
for {
select {
case <- terminate . C :
terminate . Reset ( time . Duration ( 0 ) ) // Kill subsequent processes immediately.
cmd . Process . Kill ( )
return fmt . Errorf ( "Terminate testing after 15m after %s timeout during %s" , * timeout , stepName )
case <- interrupt . C :
log . Printf ( "Interrupt testing after %s timeout. Will terminate in another 15m" , * timeout )
terminate . Reset ( 15 * time . Minute )
cmd . Process . Signal ( os . Interrupt )
case err := <- finished :
return err
}
2014-10-23 00:49:40 +00:00
}
2015-02-06 22:33:57 +00:00
}