github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/kubetest/anywhere.go (about) 1 /* 2 Copyright 2017 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 main 18 19 import ( 20 "bytes" 21 "flag" 22 "fmt" 23 "io/ioutil" 24 "log" 25 "os" 26 "os/exec" 27 "regexp" 28 "strings" 29 "text/template" 30 "time" 31 ) 32 33 var ( 34 // kubernetes-anywhere specific flags. 35 kubernetesAnywherePath = flag.String("kubernetes-anywhere-path", "", 36 "(kubernetes-anywhere only) Path to the kubernetes-anywhere directory. Must be set for kubernetes-anywhere.") 37 kubernetesAnywherePhase2Provider = flag.String("kubernetes-anywhere-phase2-provider", "ignition", 38 "(kubernetes-anywhere only) Provider for phase2 bootstrapping. (Defaults to ignition).") 39 kubernetesAnywhereKubeadmVersion = flag.String("kubernetes-anywhere-kubeadm-version", "stable", 40 "(kubernetes-anywhere only) Version of kubeadm to use, if phase2-provider is kubeadm. May be \"stable\" or a gs:// link to a custom build.") 41 kubernetesAnywhereKubernetesVersion = flag.String("kubernetes-anywhere-kubernetes-version", "", 42 "(kubernetes-anywhere only) Version of Kubernetes to use (e.g. latest, stable, latest-1.6, 1.6.3, etc).") 43 kubernetesAnywhereKubeletVersion = flag.String("kubernetes-anywhere-kubelet-version", "stable", 44 "(kubernetes-anywhere only) Version of Kubelet to use, if phase2-provider is kubeadm. May be \"stable\" or a gs:// link to a custom build.") 45 kubernetesAnywhereKubeletCIVersion = flag.String("kubernetes-anywhere-kubelet-ci-version", "", 46 "(kubernetes-anywhere only) If specified, the ci version for the kubelt to use. Overrides kubernetes-anywhere-kubelet-version.") 47 kubernetesAnywhereCluster = flag.String("kubernetes-anywhere-cluster", "", 48 "(kubernetes-anywhere only) Cluster name. Must be set for kubernetes-anywhere.") 49 kubernetesAnywhereUpTimeout = flag.Duration("kubernetes-anywhere-up-timeout", 20*time.Minute, 50 "(kubernetes-anywhere only) Time limit between starting a cluster and making a successful call to the Kubernetes API.") 51 kubernetesAnywhereNumNodes = flag.Int("kubernetes-anywhere-num-nodes", 4, 52 "(kubernetes-anywhere only) Number of nodes to be deployed in the cluster.") 53 kubernetesAnywhereUpgradeMethod = flag.String("kubernetes-anywhere-upgrade-method", "upgrade", 54 "(kubernetes-anywhere only) Indicates whether to do the control plane upgrade with kubeadm method \"init\" or \"upgrade\"") 55 ) 56 57 const kubernetesAnywhereConfigTemplate = ` 58 .phase1.num_nodes={{.NumNodes}} 59 .phase1.cluster_name="{{.Cluster}}" 60 .phase1.ssh_user="" 61 .phase1.cloud_provider="gce" 62 63 .phase1.gce.os_image="ubuntu-1604-xenial-v20160420c" 64 .phase1.gce.instance_type="n1-standard-1" 65 .phase1.gce.project="{{.Project}}" 66 .phase1.gce.region="{{.Region}}" 67 .phase1.gce.zone="{{.Zone}}" 68 .phase1.gce.network="default" 69 70 .phase2.installer_container="docker.io/colemickens/k8s-ignition:latest" 71 .phase2.docker_registry="gcr.io/google-containers" 72 .phase2.kubernetes_version="{{.KubernetesVersion}}" 73 .phase2.provider="{{.Phase2Provider}}" 74 .phase2.kubelet_version="{{.KubeletVersion}}" 75 .phase2.kubeadm.version="{{.KubeadmVersion}}" 76 .phase2.kube_context_name="{{.KubeContext}}" 77 .phase2.kubeadm.master_upgrade.method="{{.UpgradeMethod}}" 78 79 .phase3.run_addons=y 80 .phase3.weave_net={{if eq .Phase2Provider "kubeadm" -}} y {{- else -}} n {{- end}} 81 .phase3.kube_proxy=n 82 .phase3.dashboard=n 83 .phase3.heapster=n 84 .phase3.kube_dns=n 85 ` 86 87 type kubernetesAnywhere struct { 88 path string 89 // These are exported only because their use in the config template requires it. 90 Phase2Provider string 91 KubeadmVersion string 92 KubeletVersion string 93 UpgradeMethod string 94 KubernetesVersion string 95 NumNodes int 96 Project string 97 Cluster string 98 Zone string 99 Region string 100 KubeContext string 101 } 102 103 func newKubernetesAnywhere(project, zone string) (deployer, error) { 104 if *kubernetesAnywherePath == "" { 105 return nil, fmt.Errorf("--kubernetes-anywhere-path is required") 106 } 107 108 if *kubernetesAnywhereCluster == "" { 109 return nil, fmt.Errorf("--kubernetes-anywhere-cluster is required") 110 } 111 112 if project == "" { 113 return nil, fmt.Errorf("--provider=kubernetes-anywhere requires --gcp-project") 114 } 115 116 if zone == "" { 117 zone = "us-central1-c" 118 } 119 120 kubeletVersion := *kubernetesAnywhereKubeletVersion 121 if *kubernetesAnywhereKubeletCIVersion != "" { 122 resolvedVersion, err := resolveCIVersion(*kubernetesAnywhereKubeletCIVersion) 123 if err != nil { 124 return nil, err 125 } 126 kubeletVersion = bazelBuildPath(resolvedVersion) 127 } 128 129 // Set KUBERNETES_CONFORMANCE_TEST so the auth info is picked up 130 // from kubectl instead of bash inference. 131 if err := os.Setenv("KUBERNETES_CONFORMANCE_TEST", "yes"); err != nil { 132 return nil, err 133 } 134 135 // Set KUBERNETES_CONFORMANCE_PROVIDER since KUBERNETES_CONFORMANCE_TEST is set 136 // to ensure the right provider is passed onto the test. 137 if err := os.Setenv("KUBERNETES_CONFORMANCE_PROVIDER", "kubernetes-anywhere"); err != nil { 138 return nil, err 139 } 140 141 k := &kubernetesAnywhere{ 142 path: *kubernetesAnywherePath, 143 Phase2Provider: *kubernetesAnywherePhase2Provider, 144 KubeadmVersion: *kubernetesAnywhereKubeadmVersion, 145 KubeletVersion: kubeletVersion, 146 UpgradeMethod: *kubernetesAnywhereUpgradeMethod, 147 KubernetesVersion: *kubernetesAnywhereKubernetesVersion, 148 NumNodes: *kubernetesAnywhereNumNodes, 149 Project: project, 150 Cluster: *kubernetesAnywhereCluster, 151 Zone: zone, 152 Region: regexp.MustCompile(`-[^-]+$`).ReplaceAllString(zone, ""), 153 } 154 155 if err := k.writeConfig(); err != nil { 156 return nil, err 157 } 158 return k, nil 159 } 160 161 func resolveCIVersion(version string) (string, error) { 162 if strings.HasPrefix(version, "v") { 163 return version, nil 164 } 165 file := fmt.Sprintf("gs://kubernetes-release-dev/ci/%v.txt", version) 166 return readGSFile(file) 167 } 168 169 func bazelBuildPath(version string) string { 170 // This replicates the logic from scenarios/kubernetes_e2e.py, to 171 // accommodate the fact that bazel artifacts are stored in a different 172 // location for 1.6 builds. 173 if strings.HasPrefix(version, "v1.6.") { 174 return fmt.Sprintf("gs://kubernetes-release-dev/bazel/%v/build/debs/", version) 175 } else { 176 return fmt.Sprintf("gs://kubernetes-release-dev/bazel/%v/bin/linux/amd64/", version) 177 } 178 } 179 180 // Implemented as a function var for testing. 181 var readGSFile = readGSFileImpl 182 183 func readGSFileImpl(filepath string) (string, error) { 184 contents, err := output(exec.Command("gsutil", "cat", filepath)) 185 if err != nil { 186 return "", err 187 } 188 return strings.TrimSpace(string(contents)), nil 189 } 190 191 func (k *kubernetesAnywhere) getConfig() ([]byte, error) { 192 // As needed, plumb through more CLI options to replace these defaults 193 tmpl, err := template.New("kubernetes-anywhere-config").Parse(kubernetesAnywhereConfigTemplate) 194 if err != nil { 195 return nil, fmt.Errorf("Error creating template for KubernetesAnywhere config: %v", err) 196 } 197 198 var buf bytes.Buffer 199 if err = tmpl.Execute(&buf, k); err != nil { 200 return nil, fmt.Errorf("Error executing template for KubernetesAnywhere config: %v", err) 201 } 202 203 return buf.Bytes(), nil 204 } 205 206 func (k *kubernetesAnywhere) writeConfig() error { 207 config, err := k.getConfig() 208 if err != nil { 209 return fmt.Errorf("Could not generate config: %v", err) 210 } 211 return ioutil.WriteFile(k.path+"/.config", config, 0644) 212 } 213 214 func (k *kubernetesAnywhere) Up() error { 215 cmd := exec.Command("make", "-C", k.path, "WAIT_FOR_KUBECONFIG=y", "deploy") 216 if err := finishRunning(cmd); err != nil { 217 return err 218 } 219 220 nodes := k.NumNodes 221 return waitForNodes(k, nodes+1, *kubernetesAnywhereUpTimeout) 222 } 223 224 func (k *kubernetesAnywhere) IsUp() error { 225 return isUp(k) 226 } 227 228 func (k *kubernetesAnywhere) DumpClusterLogs(localPath, gcsPath string) error { 229 // TODO(pipejakob): the default implementation (log-dump.sh) doesn't work for 230 // kubernetes-anywhere yet, so just skip attempting to dump logs. 231 // https://github.com/kubernetes/kubeadm/issues/256 232 log.Print("DumpClusterLogs is a no-op for kubernetes-anywhere deployments. Not doing anything.") 233 log.Print("If you care about enabling this feature, follow this issue for progress:") 234 log.Print(" https://github.com/kubernetes/kubeadm/issues/256") 235 return nil 236 } 237 238 func (k *kubernetesAnywhere) TestSetup() error { 239 o, err := output(exec.Command("make", "--silent", "-C", k.path, "kubeconfig-path")) 240 if err != nil { 241 return fmt.Errorf("Could not get kubeconfig-path: %v", err) 242 } 243 kubecfg := strings.TrimSuffix(string(o), "\n") 244 245 if err = os.Setenv("KUBECONFIG", kubecfg); err != nil { 246 return err 247 } 248 return nil 249 } 250 251 func (k *kubernetesAnywhere) Down() error { 252 err := finishRunning(exec.Command("make", "-C", k.path, "kubeconfig-path")) 253 if err != nil { 254 // This is expected if the cluster doesn't exist. 255 return nil 256 } 257 return finishRunning(exec.Command("make", "-C", k.path, "FORCE_DESTROY=y", "destroy")) 258 } 259 260 const defaultConfigFile = ".config" 261 262 type kubernetesAnywhereMultiCluster struct { 263 *kubernetesAnywhere 264 multiClusters multiClusterDeployment 265 configFile map[string]string 266 } 267 268 // newKubernetesAnywhereMultiCluster returns the deployer based on kubernetes-anywhere 269 // which can be used to deploy multiple clusters simultaneously. 270 func newKubernetesAnywhereMultiCluster(project, zone string, multiClusters multiClusterDeployment) (deployer, error) { 271 if len(multiClusters.clusters) < 1 { 272 return nil, fmt.Errorf("invalid --multi-clusters flag passed") 273 } 274 k, err := newKubernetesAnywhere(project, zone) 275 if err != nil { 276 return nil, err 277 } 278 mk := &kubernetesAnywhereMultiCluster{k.(*kubernetesAnywhere), multiClusters, make(map[string]string)} 279 280 for _, cluster := range mk.multiClusters.clusters { 281 specificZone, specified := mk.multiClusters.zones[cluster] 282 if specified { 283 mk.Zone = specificZone 284 } 285 mk.Cluster = cluster 286 mk.KubeContext = mk.Zone + "-" + mk.Cluster 287 mk.configFile[cluster] = defaultConfigFile + "-" + mk.Cluster 288 if err := mk.writeConfig(); err != nil { 289 return nil, err 290 } 291 } 292 return mk, nil 293 } 294 295 // writeConfig writes the kubernetes-anywhere config file to file system after 296 // rendering the template file with configuration in deployer. 297 func (k *kubernetesAnywhereMultiCluster) writeConfig() error { 298 config, err := k.getConfig() 299 if err != nil { 300 return fmt.Errorf("could not generate config: %v", err) 301 } 302 303 return ioutil.WriteFile(k.path+"/"+k.configFile[k.Cluster], config, 0644) 304 } 305 306 // Up brings up multiple k8s clusters in parallel. 307 func (k *kubernetesAnywhereMultiCluster) Up() error { 308 var cmds []*exec.Cmd 309 for _, cluster := range k.multiClusters.clusters { 310 cmd := exec.Command("make", "-C", k.path, "CONFIG_FILE="+k.configFile[cluster], "deploy") 311 cmds = append(cmds, cmd) 312 } 313 314 if err := finishRunningParallel(cmds...); err != nil { 315 return err 316 } 317 318 return k.TestSetup() 319 } 320 321 // TestSetup sets up test environment by merging kubeconfig of multiple deployments. 322 func (k *kubernetesAnywhereMultiCluster) TestSetup() error { 323 var kubecfg string 324 for _, cluster := range k.multiClusters.clusters { 325 o, err := output(exec.Command("make", "--silent", "-C", k.path, "CONFIG_FILE="+k.configFile[cluster], "kubeconfig-path")) 326 if err != nil { 327 return fmt.Errorf("could not get kubeconfig-path: %v", err) 328 } 329 if len(kubecfg) != 0 { 330 kubecfg += ":" 331 } 332 kubecfg += strings.TrimSuffix(string(o), "\n") 333 } 334 335 if err := os.Setenv("KUBECONFIG", kubecfg); err != nil { 336 return err 337 } 338 return nil 339 } 340 341 // IsUp checks if all the clusters in the deployer are up. 342 func (k *kubernetesAnywhereMultiCluster) IsUp() error { 343 if err := k.TestSetup(); err != nil { 344 return err 345 } 346 347 for _, cluster := range k.multiClusters.clusters { 348 zone := k.multiClusters.zones[cluster] 349 kubeContext := zone + "-" + cluster 350 o, err := output(exec.Command("kubectl", "--context="+kubeContext, "get", "nodes", "--no-headers")) 351 if err != nil { 352 log.Printf("kubectl get nodes failed for cluster %s: %s\n%s", cluster, wrapError(err).Error(), string(o)) 353 return err 354 } 355 stdout := strings.TrimSpace(string(o)) 356 log.Printf("Cluster nodes of cluster %s:\n%s", cluster, stdout) 357 358 n := len(strings.Split(stdout, "\n")) 359 if n < k.NumNodes { 360 return fmt.Errorf("cluster %s found, but %d nodes reported", cluster, n) 361 } 362 } 363 return nil 364 } 365 366 // Down brings down multiple k8s clusters in parallel. 367 func (k *kubernetesAnywhereMultiCluster) Down() error { 368 if err := k.TestSetup(); err != nil { 369 // This is expected if the clusters doesn't exist. 370 return nil 371 } 372 373 var cmds []*exec.Cmd 374 for _, cluster := range k.multiClusters.clusters { 375 cmd := exec.Command("make", "-C", k.path, "CONFIG_FILE="+k.configFile[cluster], "FORCE_DESTROY=y", "destroy") 376 cmds = append(cmds, cmd) 377 } 378 return finishRunningParallel(cmds...) 379 }