github.com/jenkins-x/test-infra@v0.0.7/kubetest/eks/eks.go (about)

     1  /*
     2  Copyright 2018 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 eks implements 'kubetest' deployer interface.
    18  // It uses 'aws-k8s-tester' and 'kubectl' binaries, rather than importing internal packages.
    19  // All underlying implementation and external dependencies are compiled into one binary.
    20  package eks
    21  
    22  import (
    23  	"fmt"
    24  	"io"
    25  	"io/ioutil"
    26  	"log"
    27  	"net/http"
    28  	"os"
    29  	osexec "os/exec"
    30  	"path/filepath"
    31  	"syscall"
    32  	"time"
    33  
    34  	"github.com/aws/aws-k8s-tester/eksconfig"
    35  	"github.com/aws/aws-k8s-tester/ekstester"
    36  	"k8s.io/test-infra/kubetest/process"
    37  	"k8s.io/test-infra/kubetest/util"
    38  )
    39  
    40  // deployer implements EKS deployer interface using "aws-k8s-tester" binary.
    41  // Satisfies "k8s.io/test-infra/kubetest/main.go" 'deployer' and 'publisher" interfaces.
    42  // Reference https://github.com/kubernetes/test-infra/blob/master/kubetest/main.go.
    43  type deployer struct {
    44  	stopc chan struct{}
    45  	cfg   *eksconfig.Config
    46  	ctrl  *process.Control
    47  }
    48  
    49  // NewDeployer creates a new EKS deployer.
    50  func NewDeployer(timeout time.Duration, verbose bool) (ekstester.Deployer, error) {
    51  	cfg := eksconfig.NewDefault()
    52  	err := cfg.UpdateFromEnvs()
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  	var f *os.File
    57  	f, err = ioutil.TempFile(os.TempDir(), "aws-k8s-tester-config")
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  	cfg.ConfigPath = f.Name()
    62  	if err = f.Close(); err != nil {
    63  		return nil, fmt.Errorf("failed to close aws-k8s-tester-config file %v", err)
    64  	}
    65  	if err = cfg.Sync(); err != nil {
    66  		return nil, err
    67  	}
    68  
    69  	dp := &deployer{
    70  		stopc: make(chan struct{}),
    71  		cfg:   cfg,
    72  		ctrl: process.NewControl(
    73  			timeout,
    74  			time.NewTimer(timeout),
    75  			time.NewTimer(timeout),
    76  			verbose,
    77  		),
    78  	}
    79  
    80  	if err = os.RemoveAll(cfg.AWSK8sTesterPath); err != nil {
    81  		return nil, err
    82  	}
    83  	if err = os.MkdirAll(filepath.Dir(cfg.AWSK8sTesterPath), 0700); err != nil {
    84  		return nil, err
    85  	}
    86  	f, err = os.Create(cfg.AWSK8sTesterPath)
    87  	if err != nil {
    88  		return nil, fmt.Errorf("failed to create %q (%v)", cfg.AWSK8sTesterPath, err)
    89  	}
    90  	cfg.AWSK8sTesterPath = f.Name()
    91  	if err = httpRead(cfg.AWSK8sTesterDownloadURL, f); err != nil {
    92  		return nil, err
    93  	}
    94  	if err = f.Close(); err != nil {
    95  		return nil, fmt.Errorf("failed to close aws-k8s-tester file %v", err)
    96  	}
    97  	if err = util.EnsureExecutable(cfg.AWSK8sTesterPath); err != nil {
    98  		return nil, err
    99  	}
   100  	return dp, nil
   101  }
   102  
   103  // Up creates a new EKS cluster.
   104  func (dp *deployer) Up() (err error) {
   105  	// "create cluster" command outputs cluster information
   106  	// in the configuration file (e.g. VPC ID, ALB DNS names, etc.)
   107  	// this needs be reloaded for other deployer method calls
   108  	createCmd := osexec.Command(
   109  		dp.cfg.AWSK8sTesterPath,
   110  		"eks",
   111  		"--path="+dp.cfg.ConfigPath,
   112  		"create",
   113  		"cluster",
   114  	)
   115  	errc := make(chan error)
   116  	go func() {
   117  		_, oerr := dp.ctrl.Output(createCmd)
   118  		errc <- oerr
   119  	}()
   120  	select {
   121  	case <-dp.stopc:
   122  		fmt.Fprintln(os.Stderr, "received stop signal, interrupting 'create cluster' command...")
   123  		ierr := createCmd.Process.Signal(syscall.SIGINT)
   124  		err = fmt.Errorf("'create cluster' command interrupted (interrupt error %v)", ierr)
   125  	case err = <-errc:
   126  	}
   127  	return err
   128  }
   129  
   130  // Down tears down the existing EKS cluster.
   131  func (dp *deployer) Down() (err error) {
   132  	// reload configuration from disk to read the latest configuration
   133  	if _, err = dp.LoadConfig(); err != nil {
   134  		return err
   135  	}
   136  	_, err = dp.ctrl.Output(osexec.Command(
   137  		dp.cfg.AWSK8sTesterPath,
   138  		"eks",
   139  		"--path="+dp.cfg.ConfigPath,
   140  		"delete",
   141  		"cluster",
   142  	))
   143  	return err
   144  }
   145  
   146  // IsUp returns an error if the cluster is not up and running.
   147  func (dp *deployer) IsUp() (err error) {
   148  	// reload configuration from disk to read the latest configuration
   149  	if _, err = dp.LoadConfig(); err != nil {
   150  		return err
   151  	}
   152  	_, err = dp.ctrl.Output(osexec.Command(
   153  		dp.cfg.AWSK8sTesterPath,
   154  		"eks",
   155  		"--path="+dp.cfg.ConfigPath,
   156  		"check",
   157  		"cluster",
   158  	))
   159  	if err != nil {
   160  		return err
   161  	}
   162  	if _, err = dp.LoadConfig(); err != nil {
   163  		return err
   164  	}
   165  	if dp.cfg.ClusterState.Status != "ACTIVE" {
   166  		return fmt.Errorf("cluster %q status is %q",
   167  			dp.cfg.ClusterName,
   168  			dp.cfg.ClusterState.Status,
   169  		)
   170  	}
   171  	return nil
   172  }
   173  
   174  // TestSetup checks if EKS testing cluster has been set up or not.
   175  func (dp *deployer) TestSetup() error {
   176  	return dp.IsUp()
   177  }
   178  
   179  // GetClusterCreated returns EKS cluster creation time and error (if any).
   180  func (dp *deployer) GetClusterCreated(v string) (time.Time, error) {
   181  	err := dp.IsUp()
   182  	if err != nil {
   183  		return time.Time{}, err
   184  	}
   185  	return dp.cfg.ClusterState.Created, nil
   186  }
   187  
   188  func (dp *deployer) GetWorkerNodeLogs() (err error) {
   189  	// reload configuration from disk to read the latest configuration
   190  	if _, err = dp.LoadConfig(); err != nil {
   191  		return err
   192  	}
   193  	_, err = dp.ctrl.Output(osexec.Command(
   194  		dp.cfg.AWSK8sTesterPath,
   195  		"eks",
   196  		"--path="+dp.cfg.ConfigPath,
   197  		"test", "get-worker-node-logs",
   198  	))
   199  	return err
   200  }
   201  
   202  // DumpClusterLogs dumps all logs to artifact directory.
   203  // Let default kubetest log dumper handle all artifact uploads.
   204  // See https://github.com/kubernetes/test-infra/pull/9811/files#r225776067.
   205  func (dp *deployer) DumpClusterLogs(artifactDir, _ string) (err error) {
   206  	// reload configuration from disk to read the latest configuration
   207  	if _, err = dp.LoadConfig(); err != nil {
   208  		return err
   209  	}
   210  	_, err = dp.ctrl.Output(osexec.Command(
   211  		dp.cfg.AWSK8sTesterPath,
   212  		"eks",
   213  		"--path="+dp.cfg.ConfigPath,
   214  		"test", "get-worker-node-logs",
   215  	))
   216  	if err != nil {
   217  		return err
   218  	}
   219  	_, err = dp.ctrl.Output(osexec.Command(
   220  		dp.cfg.AWSK8sTesterPath,
   221  		"eks",
   222  		"--path="+dp.cfg.ConfigPath,
   223  		"test", "dump-cluster-logs",
   224  		artifactDir,
   225  	))
   226  	return err
   227  }
   228  
   229  // KubectlCommand returns "kubectl" command object for API reachability tests.
   230  func (dp *deployer) KubectlCommand() (*osexec.Cmd, error) {
   231  	// reload configuration from disk to read the latest configuration
   232  	if _, err := dp.LoadConfig(); err != nil {
   233  		return nil, err
   234  	}
   235  	return osexec.Command(dp.cfg.KubectlPath, "--kubeconfig="+dp.cfg.KubeConfigPath), nil
   236  }
   237  
   238  // Stop stops ongoing operations.
   239  // This is useful for local development.
   240  // For example, one may run "Up" but have to cancel ongoing "Up"
   241  // operation. Then, it can just send syscall.SIGINT to trigger "Stop".
   242  func (dp *deployer) Stop() {
   243  	close(dp.stopc)
   244  }
   245  
   246  // LoadConfig reloads configuration from disk to read the latest
   247  // cluster configuration and its states.
   248  func (dp *deployer) LoadConfig() (eksconfig.Config, error) {
   249  	var err error
   250  	dp.cfg, err = eksconfig.Load(dp.cfg.ConfigPath)
   251  	return *dp.cfg, err
   252  }
   253  
   254  var httpTransport *http.Transport
   255  
   256  func init() {
   257  	httpTransport = new(http.Transport)
   258  	httpTransport.RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))
   259  }
   260  
   261  // curl -L [URL] | writer
   262  func httpRead(u string, wr io.Writer) error {
   263  	log.Printf("curl %s", u)
   264  	cli := &http.Client{Transport: httpTransport}
   265  	r, err := cli.Get(u)
   266  	if err != nil {
   267  		return err
   268  	}
   269  	defer r.Body.Close()
   270  	if r.StatusCode >= 400 {
   271  		return fmt.Errorf("%v returned %d", u, r.StatusCode)
   272  	}
   273  	_, err = io.Copy(wr, r.Body)
   274  	return err
   275  }