github.com/qsunny/k8s@v0.0.0-20220101153623-e6dca256d5bf/heapster-1.3.0/integration/framework.go (about)

     1  // Copyright 2014 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package integration
    16  
    17  import (
    18  	"flag"
    19  	"fmt"
    20  	"io/ioutil"
    21  	"os"
    22  	"os/exec"
    23  	"path/filepath"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/golang/glog"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/labels"
    30  	"k8s.io/apimachinery/pkg/runtime"
    31  	kclient "k8s.io/client-go/kubernetes"
    32  	"k8s.io/client-go/pkg/api"
    33  	"k8s.io/client-go/pkg/api/v1"
    34  	kclientcmd "k8s.io/client-go/tools/clientcmd"
    35  	kclientcmdapi "k8s.io/client-go/tools/clientcmd/api"
    36  )
    37  
    38  type kubeFramework interface {
    39  	// Kube client
    40  	Client() *kclient.Clientset
    41  
    42  	// Parses and Returns a replication Controller object contained in 'filePath'
    43  	ParseRC(filePath string) (*v1.ReplicationController, error)
    44  
    45  	// Parses and Returns a service object contained in 'filePath'
    46  	ParseService(filePath string) (*v1.Service, error)
    47  
    48  	// Creates a kube service.
    49  	CreateService(ns string, service *v1.Service) (*v1.Service, error)
    50  
    51  	// Creates a namespace.
    52  	CreateNs(ns *v1.Namespace) (*v1.Namespace, error)
    53  
    54  	// Creates a kube replication controller.
    55  	CreateRC(ns string, rc *v1.ReplicationController) (*v1.ReplicationController, error)
    56  
    57  	// Deletes a namespace
    58  	DeleteNs(ns string) error
    59  
    60  	// Destroy cluster
    61  	DestroyCluster()
    62  
    63  	// Returns a url that provides access to a kubernetes service via the proxy on the apiserver.
    64  	// This url requires master auth.
    65  	GetProxyUrlForService(service *v1.Service) string
    66  
    67  	// Returns the node hostnames.
    68  	GetNodeNames() ([]string, error)
    69  
    70  	// Returns the nodes.
    71  	GetNodes() (*v1.NodeList, error)
    72  
    73  	// Returns pod names in the cluster.
    74  	// TODO: Remove, or mix with namespace
    75  	GetRunningPodNames() ([]string, error)
    76  
    77  	// Returns pods in the cluster running outside kubernetes-master.
    78  	GetPodsRunningOnNodes() ([]v1.Pod, error)
    79  
    80  	// Returns pods in the cluster.
    81  	GetAllRunningPods() ([]v1.Pod, error)
    82  
    83  	WaitUntilPodRunning(ns string, podLabels map[string]string, timeout time.Duration) error
    84  	WaitUntilServiceActive(svc *v1.Service, timeout time.Duration) error
    85  }
    86  
    87  type realKubeFramework struct {
    88  	// Kube client.
    89  	kubeClient *kclient.Clientset
    90  
    91  	// The version of the kube cluster
    92  	version string
    93  
    94  	// Master IP for this framework
    95  	masterIP string
    96  
    97  	// The base directory of current kubernetes release.
    98  	baseDir string
    99  }
   100  
   101  const imageUrlTemplate = "https://github.com/kubernetes/kubernetes/releases/download/v%s/kubernetes.tar.gz"
   102  
   103  var (
   104  	kubeConfig = flag.String("kube_config", os.Getenv("HOME")+"/.kube/config", "Path to cluster info file.")
   105  	workDir    = flag.String("work_dir", "/tmp/heapster_test", "Filesystem path where test files will be stored. Files will persist across runs to speed up tests.")
   106  )
   107  
   108  func exists(path string) bool {
   109  	if _, err := os.Stat(path); err != nil {
   110  		glog.V(2).Infof("%q does not exist", path)
   111  		return false
   112  	}
   113  	return true
   114  }
   115  
   116  const pathToGCEConfig = "cluster/gce/config-default.sh"
   117  
   118  func disableClusterMonitoring(kubeBaseDir string) error {
   119  	kubeConfigFilePath := filepath.Join(kubeBaseDir, pathToGCEConfig)
   120  	input, err := ioutil.ReadFile(kubeConfigFilePath)
   121  	if err != nil {
   122  		return err
   123  	}
   124  
   125  	lines := strings.Split(string(input), "\n")
   126  
   127  	for i, line := range lines {
   128  		if strings.Contains(line, "ENABLE_CLUSTER_MONITORING") {
   129  			lines[i] = "ENABLE_CLUSTER_MONITORING=false"
   130  		} else if strings.Contains(line, "NUM_MINIONS=") {
   131  			lines[i] = "NUM_MINIONS=2"
   132  		}
   133  	}
   134  	output := strings.Join(lines, "\n")
   135  	return ioutil.WriteFile(kubeConfigFilePath, []byte(output), 0644)
   136  }
   137  
   138  func runKubeClusterCommand(kubeBaseDir, command string) ([]byte, error) {
   139  	cmd := exec.Command(filepath.Join(kubeBaseDir, "cluster", command))
   140  	glog.V(2).Infof("about to run %v", cmd)
   141  	return cmd.CombinedOutput()
   142  }
   143  
   144  func setupNewCluster(kubeBaseDir string) error {
   145  	cmd := "kube-up.sh"
   146  	destroyCluster(kubeBaseDir)
   147  	out, err := runKubeClusterCommand(kubeBaseDir, cmd)
   148  	if err != nil {
   149  		glog.Errorf("failed to bring up cluster - %q\n%s", err, out)
   150  		return fmt.Errorf("failed to bring up cluster - %q", err)
   151  	}
   152  	glog.V(2).Info(string(out))
   153  	glog.V(2).Infof("Giving the cluster 30 sec to stabilize")
   154  	time.Sleep(30 * time.Second)
   155  	return nil
   156  }
   157  
   158  func destroyCluster(kubeBaseDir string) error {
   159  	if kubeBaseDir == "" {
   160  		glog.Infof("Skipping cluster tear down since kubernetes repo base path is not set.")
   161  		return nil
   162  	}
   163  	glog.V(1).Info("Bringing down any existing kube cluster")
   164  	out, err := runKubeClusterCommand(kubeBaseDir, "kube-down.sh")
   165  	if err != nil {
   166  		glog.Errorf("failed to tear down cluster - %q\n%s", err, out)
   167  		return fmt.Errorf("failed to tear down kube cluster - %q", err)
   168  	}
   169  
   170  	return nil
   171  }
   172  
   173  func downloadRelease(workDir, version string) error {
   174  	// Temporary download path.
   175  	downloadPath := filepath.Join(workDir, "kube")
   176  	// Format url.
   177  	downloadUrl := fmt.Sprintf(imageUrlTemplate, version)
   178  	glog.V(1).Infof("About to download kube release using url: %q", downloadUrl)
   179  
   180  	// Download kube code and store it in a temp dir.
   181  	if err := exec.Command("wget", downloadUrl, "-O", downloadPath).Run(); err != nil {
   182  		return fmt.Errorf("failed to wget kubernetes release @ %q - %v", downloadUrl, err)
   183  	}
   184  
   185  	// Un-tar kube release.
   186  	if err := exec.Command("tar", "-xf", downloadPath, "-C", workDir).Run(); err != nil {
   187  		return fmt.Errorf("failed to un-tar kubernetes release at %q - %v", downloadPath, err)
   188  	}
   189  	return nil
   190  }
   191  
   192  func getKubeClient() (string, *kclient.Clientset, error) {
   193  	c, err := kclientcmd.LoadFromFile(*kubeConfig)
   194  	if err != nil {
   195  		return "", nil, fmt.Errorf("error loading kubeConfig: %v", err.Error())
   196  	}
   197  	if c.CurrentContext == "" || len(c.Clusters) == 0 {
   198  		return "", nil, fmt.Errorf("invalid kubeConfig: %+v", *c)
   199  	}
   200  	config, err := kclientcmd.NewDefaultClientConfig(
   201  		*c,
   202  		&kclientcmd.ConfigOverrides{
   203  			ClusterInfo: kclientcmdapi.Cluster{
   204  				APIVersion: "v1",
   205  			},
   206  		}).ClientConfig()
   207  	if err != nil {
   208  		return "", nil, fmt.Errorf("error parsing kubeConfig: %v", err.Error())
   209  	}
   210  	kubeClient, err := kclient.NewForConfig(config)
   211  	if err != nil {
   212  		return "", nil, fmt.Errorf("error creating client - %q", err)
   213  	}
   214  
   215  	return c.Clusters[c.CurrentContext].Server, kubeClient, nil
   216  }
   217  
   218  func validateCluster(baseDir string) bool {
   219  	glog.V(1).Info("validating existing cluster")
   220  	out, err := runKubeClusterCommand(baseDir, "validate-cluster.sh")
   221  	if err != nil {
   222  		glog.V(1).Infof("cluster validation failed - %q\n %s", err, out)
   223  		return false
   224  	}
   225  	return true
   226  }
   227  
   228  func requireNewCluster(baseDir, version string) bool {
   229  	// Setup kube client
   230  	_, kubeClient, err := getKubeClient()
   231  	if err != nil {
   232  		glog.V(1).Infof("kube client creation failed - %q", err)
   233  		return true
   234  	}
   235  	glog.V(1).Infof("checking if existing cluster can be used")
   236  	versionInfo, err := kubeClient.ServerVersion()
   237  	if err != nil {
   238  		glog.V(1).Infof("failed to get kube version info - %q", err)
   239  		return true
   240  	}
   241  	return !strings.Contains(versionInfo.GitVersion, version)
   242  }
   243  
   244  func requireDownload(baseDir string) bool {
   245  	// Check that cluster scripts are present.
   246  	return !exists(filepath.Join(baseDir, "cluster", "kube-up.sh")) ||
   247  		!exists(filepath.Join(baseDir, "cluster", "kube-down.sh")) ||
   248  		!exists(filepath.Join(baseDir, "cluster", "validate-cluster.sh"))
   249  }
   250  
   251  func downloadAndSetupCluster(version string) (baseDir string, err error) {
   252  	// Create a temp dir to store the kube release files.
   253  	tempDir := filepath.Join(*workDir, version)
   254  	if !exists(tempDir) {
   255  		if err := os.MkdirAll(tempDir, 0700); err != nil {
   256  			return "", fmt.Errorf("failed to create a temp dir at %s - %q", tempDir, err)
   257  		}
   258  		glog.V(1).Infof("Successfully setup work dir at %s", tempDir)
   259  	}
   260  
   261  	kubeBaseDir := filepath.Join(tempDir, "kubernetes")
   262  
   263  	if requireDownload(kubeBaseDir) {
   264  		if exists(kubeBaseDir) {
   265  			os.RemoveAll(kubeBaseDir)
   266  		}
   267  		if err := downloadRelease(tempDir, version); err != nil {
   268  			return "", err
   269  		}
   270  		glog.V(1).Infof("Successfully downloaded kubernetes release at %s", tempDir)
   271  	}
   272  
   273  	// Disable monitoring
   274  	if err := disableClusterMonitoring(kubeBaseDir); err != nil {
   275  		return "", fmt.Errorf("failed to disable cluster monitoring in kube cluster config - %q", err)
   276  	}
   277  	glog.V(1).Info("Disabled cluster monitoring")
   278  	if !requireNewCluster(kubeBaseDir, version) {
   279  		glog.V(1).Infof("skipping cluster setup since a cluster with required version already exists")
   280  		return kubeBaseDir, nil
   281  	}
   282  
   283  	// Setup kube cluster
   284  	glog.V(1).Infof("Setting up new kubernetes cluster version: %s", version)
   285  	if err := os.Setenv("KUBERNETES_SKIP_CONFIRM", "y"); err != nil {
   286  		return "", err
   287  	}
   288  	if err := setupNewCluster(kubeBaseDir); err != nil {
   289  		// Cluster setup failed for some reason.
   290  		// Attempting to validate the cluster to see if it failed in the validate phase.
   291  		sleepDuration := 10 * time.Second
   292  		clusterReady := false
   293  		for i := 0; i < int(time.Minute/sleepDuration); i++ {
   294  			if !validateCluster(kubeBaseDir) {
   295  				glog.Infof("Retry validation after %v seconds.", sleepDuration/time.Second)
   296  				time.Sleep(sleepDuration)
   297  			} else {
   298  				clusterReady = true
   299  				break
   300  			}
   301  		}
   302  		if !clusterReady {
   303  			return "", fmt.Errorf("failed to setup cluster - %q", err)
   304  		}
   305  	}
   306  	glog.V(1).Infof("Successfully setup new kubernetes cluster version %s", version)
   307  
   308  	return kubeBaseDir, nil
   309  }
   310  
   311  func newKubeFramework(version string) (kubeFramework, error) {
   312  	var err error
   313  	kubeBaseDir := ""
   314  	if version != "" {
   315  		if len(strings.Split(version, ".")) != 3 {
   316  			return nil, fmt.Errorf("invalid kubernetes version specified - %q", version)
   317  		}
   318  		kubeBaseDir, err = downloadAndSetupCluster(version)
   319  		if err != nil {
   320  			return nil, err
   321  		}
   322  	}
   323  
   324  	// Setup kube client
   325  	masterIP, kubeClient, err := getKubeClient()
   326  	if err != nil {
   327  		return nil, err
   328  	}
   329  	return &realKubeFramework{
   330  		kubeClient: kubeClient,
   331  		baseDir:    kubeBaseDir,
   332  		version:    version,
   333  		masterIP:   masterIP,
   334  	}, nil
   335  }
   336  
   337  func (self *realKubeFramework) Client() *kclient.Clientset {
   338  	return self.kubeClient
   339  }
   340  
   341  func (self *realKubeFramework) loadObject(filePath string) (runtime.Object, error) {
   342  	data, err := ioutil.ReadFile(filePath)
   343  	if err != nil {
   344  		return nil, fmt.Errorf("failed to read object: %v", err)
   345  	}
   346  	obj, _, err := api.Codecs.UniversalDecoder(v1.SchemeGroupVersion).Decode(data, nil, nil)
   347  	return obj, err
   348  }
   349  
   350  func (self *realKubeFramework) ParseRC(filePath string) (*v1.ReplicationController, error) {
   351  	obj, err := self.loadObject(filePath)
   352  	if err != nil {
   353  		return nil, err
   354  	}
   355  
   356  	rc, ok := obj.(*v1.ReplicationController)
   357  	if !ok {
   358  		return nil, fmt.Errorf("Failed to cast replicationController: %#v", obj)
   359  	}
   360  	return rc, nil
   361  }
   362  
   363  func (self *realKubeFramework) ParseService(filePath string) (*v1.Service, error) {
   364  	obj, err := self.loadObject(filePath)
   365  	if err != nil {
   366  		return nil, err
   367  	}
   368  	service, ok := obj.(*v1.Service)
   369  	if !ok {
   370  		return nil, fmt.Errorf("Failed to cast service: %v", obj)
   371  	}
   372  	return service, nil
   373  }
   374  
   375  func (self *realKubeFramework) CreateService(ns string, service *v1.Service) (*v1.Service, error) {
   376  	service.Namespace = ns
   377  	newSvc, err := self.kubeClient.Services(ns).Create(service)
   378  	return newSvc, err
   379  }
   380  
   381  func (self *realKubeFramework) DeleteNs(ns string) error {
   382  
   383  	_, err := self.kubeClient.Namespaces().Get(ns, metav1.GetOptions{})
   384  	if err != nil {
   385  		glog.V(0).Infof("Cannot get namespace %q. Skipping deletion: %s", ns, err)
   386  		return nil
   387  	}
   388  	glog.V(0).Infof("Deleting namespace %s", ns)
   389  	self.kubeClient.Namespaces().Delete(ns, nil)
   390  
   391  	for i := 0; i < 5; i++ {
   392  		glog.V(0).Infof("Checking for namespace %s", ns)
   393  		_, err := self.kubeClient.Namespaces().Get(ns, metav1.GetOptions{})
   394  		if err != nil {
   395  			glog.V(0).Infof("%s doesn't exist", ns)
   396  			return nil
   397  		}
   398  		time.Sleep(10 * time.Second)
   399  	}
   400  	return fmt.Errorf("Namespace %s still exists", ns)
   401  }
   402  
   403  func (self *realKubeFramework) CreateNs(ns *v1.Namespace) (*v1.Namespace, error) {
   404  	return self.kubeClient.Namespaces().Create(ns)
   405  }
   406  
   407  func (self *realKubeFramework) CreateRC(ns string, rc *v1.ReplicationController) (*v1.ReplicationController, error) {
   408  	rc.Namespace = ns
   409  	return self.kubeClient.ReplicationControllers(ns).Create(rc)
   410  }
   411  
   412  func (self *realKubeFramework) DestroyCluster() {
   413  	destroyCluster(self.baseDir)
   414  }
   415  
   416  func (self *realKubeFramework) GetProxyUrlForService(service *v1.Service) string {
   417  	return fmt.Sprintf("%s/api/v1/proxy/namespaces/default/services/%s/", self.masterIP, service.Name)
   418  }
   419  
   420  func (self *realKubeFramework) GetNodeNames() ([]string, error) {
   421  	var nodes []string
   422  	nodeList, err := self.GetNodes()
   423  	if err != nil {
   424  		return nodes, err
   425  	}
   426  	for _, node := range nodeList.Items {
   427  		nodes = append(nodes, node.Name)
   428  	}
   429  	return nodes, nil
   430  }
   431  
   432  func (self *realKubeFramework) GetNodes() (*v1.NodeList, error) {
   433  	return self.kubeClient.Nodes().List(metav1.ListOptions{})
   434  }
   435  
   436  func (self *realKubeFramework) GetAllRunningPods() ([]v1.Pod, error) {
   437  	return getRunningPods(true, self.kubeClient)
   438  }
   439  
   440  func (self *realKubeFramework) GetPodsRunningOnNodes() ([]v1.Pod, error) {
   441  	return getRunningPods(false, self.kubeClient)
   442  }
   443  
   444  func getRunningPods(includeMaster bool, kubeClient *kclient.Clientset) ([]v1.Pod, error) {
   445  	glog.V(0).Infof("Getting running pods")
   446  	podList, err := kubeClient.Pods(v1.NamespaceAll).List(metav1.ListOptions{})
   447  	if err != nil {
   448  		return nil, err
   449  	}
   450  	pods := []v1.Pod{}
   451  	for _, pod := range podList.Items {
   452  		if pod.Status.Phase == v1.PodRunning {
   453  			if includeMaster || !isMasterNode(pod.Spec.NodeName) {
   454  				pods = append(pods, pod)
   455  			}
   456  		}
   457  	}
   458  	return pods, nil
   459  }
   460  
   461  func isMasterNode(nodeName string) bool {
   462  	return strings.Contains(nodeName, "kubernetes-master")
   463  }
   464  
   465  func (self *realKubeFramework) GetRunningPodNames() ([]string, error) {
   466  	var pods []string
   467  	podList, err := self.GetAllRunningPods()
   468  	if err != nil {
   469  		return pods, err
   470  	}
   471  	for _, pod := range podList {
   472  		pods = append(pods, string(pod.Name))
   473  	}
   474  	return pods, nil
   475  }
   476  
   477  func (rkf *realKubeFramework) WaitUntilPodRunning(ns string, podLabels map[string]string, timeout time.Duration) error {
   478  	glog.V(2).Infof("Waiting for pod %v in %s...", podLabels, ns)
   479  	podsInterface := rkf.Client().Pods(ns)
   480  	for i := 0; i < int(timeout/time.Second); i++ {
   481  		podList, err := podsInterface.List(metav1.ListOptions{
   482  			LabelSelector: labels.Set(podLabels).AsSelector().String(),
   483  		})
   484  		if err != nil {
   485  			glog.V(1).Info(err)
   486  			return err
   487  		}
   488  		if len(podList.Items) > 0 {
   489  			podSpec := podList.Items[0]
   490  			if podSpec.Status.Phase == v1.PodRunning {
   491  				return nil
   492  			}
   493  		}
   494  		time.Sleep(time.Second)
   495  	}
   496  	return fmt.Errorf("pod not in running state after %d", timeout/time.Second)
   497  }
   498  
   499  func (rkf *realKubeFramework) WaitUntilServiceActive(svc *v1.Service, timeout time.Duration) error {
   500  	glog.V(2).Infof("Waiting for endpoints in service %s/%s", svc.Namespace, svc.Name)
   501  	for i := 0; i < int(timeout/time.Second); i++ {
   502  		e, err := rkf.Client().Endpoints(svc.Namespace).Get(svc.Name, metav1.GetOptions{})
   503  		if err != nil {
   504  			return err
   505  		}
   506  		if len(e.Subsets) > 0 {
   507  			return nil
   508  		}
   509  		time.Sleep(time.Second)
   510  	}
   511  
   512  	return fmt.Errorf("Service %q not active after %d seconds - no endpoints found", svc.Name, timeout/time.Second)
   513  
   514  }