2015-02-20 17:35:42 +00:00
|
|
|
/*
|
|
|
|
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 (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2015-03-05 04:35:51 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
|
|
|
|
2015-02-20 17:35:42 +00:00
|
|
|
. "github.com/onsi/ginkgo"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
nautilusImage = "kubernetes/update-demo:nautilus"
|
|
|
|
kittenImage = "kubernetes/update-demo:kitten"
|
|
|
|
updateDemoSelector = "name=update-demo"
|
|
|
|
updateDemoContainer = "update-demo"
|
2015-03-02 18:15:34 +00:00
|
|
|
kubectlProxyPort = 8011
|
2015-02-20 17:35:42 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var _ = Describe("kubectl", func() {
|
|
|
|
|
2015-03-02 18:15:34 +00:00
|
|
|
// Constants.
|
|
|
|
var (
|
2015-03-04 21:02:47 +00:00
|
|
|
updateDemoRoot = filepath.Join(testContext.repoRoot, "examples/update-demo/v1beta1")
|
2015-03-02 18:15:34 +00:00
|
|
|
nautilusPath = filepath.Join(updateDemoRoot, "nautilus-rc.yaml")
|
|
|
|
kittenPath = filepath.Join(updateDemoRoot, "kitten-rc.yaml")
|
|
|
|
)
|
|
|
|
|
2015-03-05 04:35:51 +00:00
|
|
|
var c *client.Client
|
2015-03-02 18:15:34 +00:00
|
|
|
|
|
|
|
BeforeEach(func() {
|
2015-03-05 04:35:51 +00:00
|
|
|
var err error
|
|
|
|
c, err = loadClient()
|
|
|
|
expectNoError(err)
|
2015-03-02 18:15:34 +00:00
|
|
|
})
|
2015-02-20 17:35:42 +00:00
|
|
|
|
|
|
|
It("should create and stop a replication controller", func() {
|
|
|
|
defer cleanup(nautilusPath)
|
|
|
|
|
|
|
|
By("creating a replication controller")
|
|
|
|
runKubectl("create", "-f", nautilusPath)
|
2015-03-05 04:35:51 +00:00
|
|
|
validateController(c, nautilusImage, 2)
|
2015-02-20 17:35:42 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
It("should scale a replication controller", func() {
|
|
|
|
defer cleanup(nautilusPath)
|
|
|
|
|
|
|
|
By("creating a replication controller")
|
|
|
|
runKubectl("create", "-f", nautilusPath)
|
2015-03-05 04:35:51 +00:00
|
|
|
validateController(c, nautilusImage, 2)
|
2015-02-20 17:35:42 +00:00
|
|
|
By("scaling down the replication controller")
|
|
|
|
runKubectl("resize", "rc", "update-demo-nautilus", "--replicas=1")
|
2015-03-05 04:35:51 +00:00
|
|
|
validateController(c, nautilusImage, 1)
|
2015-02-20 17:35:42 +00:00
|
|
|
By("scaling up the replication controller")
|
|
|
|
runKubectl("resize", "rc", "update-demo-nautilus", "--replicas=2")
|
2015-03-05 04:35:51 +00:00
|
|
|
validateController(c, nautilusImage, 2)
|
2015-02-20 17:35:42 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
It("should do a rolling update of a replication controller", func() {
|
|
|
|
// Cleanup all resources in case we fail somewhere in the middle
|
|
|
|
defer cleanup(updateDemoRoot)
|
|
|
|
|
|
|
|
By("creating the initial replication controller")
|
|
|
|
runKubectl("create", "-f", nautilusPath)
|
2015-03-05 04:35:51 +00:00
|
|
|
validateController(c, nautilusImage, 2)
|
2015-02-20 17:35:42 +00:00
|
|
|
By("rollingupdate to new replication controller")
|
|
|
|
runKubectl("rollingupdate", "update-demo-nautilus", "--update-period=1s", "-f", kittenPath)
|
2015-03-05 04:35:51 +00:00
|
|
|
validateController(c, kittenImage, 2)
|
2015-02-20 17:35:42 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
func cleanup(filePath string) {
|
|
|
|
By("using stop to clean up resources")
|
|
|
|
runKubectl("stop", "-f", filePath)
|
|
|
|
|
|
|
|
resources := runKubectl("get", "pods,rc", "-l", updateDemoSelector, "--no-headers")
|
|
|
|
if resources != "" {
|
|
|
|
Failf("Resources left running after stop:\n%s", resources)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-05 04:35:51 +00:00
|
|
|
func validateController(c *client.Client, image string, replicas int) {
|
2015-02-20 17:35:42 +00:00
|
|
|
|
|
|
|
getPodsTemplate := "--template={{range.items}}{{.id}} {{end}}"
|
|
|
|
|
|
|
|
// NB: kubectl adds the "exists" function to the standard template functions.
|
|
|
|
// This lets us check to see if the "running" entry exists for each of the containers
|
|
|
|
// we care about. Exists will never return an error and it's safe to check a chain of
|
|
|
|
// things, any one of which may not exist. In the below template, all of info,
|
|
|
|
// containername, and running might be nil, so the normal index function isn't very
|
|
|
|
// helpful.
|
|
|
|
// This template is unit-tested in kubectl, so if you change it, update the unit test.
|
|
|
|
//
|
|
|
|
// You can read about the syntax here: http://golang.org/pkg/text/template/
|
|
|
|
getContainerStateTemplate := fmt.Sprintf(`--template={{and (exists . "currentState" "info" "%s" "state" "running")}}`, updateDemoContainer)
|
|
|
|
|
|
|
|
getImageTemplate := fmt.Sprintf(`--template={{(index .currentState.info "%s").image}}`, updateDemoContainer)
|
|
|
|
|
|
|
|
By(fmt.Sprintf("waiting for all containers in %s pods to come up.", updateDemoSelector))
|
2015-03-05 20:04:00 +00:00
|
|
|
for start := time.Now(); time.Since(start) < podStartTimeout; time.Sleep(5 * time.Second) {
|
2015-02-20 17:35:42 +00:00
|
|
|
getPodsOutput := runKubectl("get", "pods", "-o", "template", getPodsTemplate, "-l", updateDemoSelector)
|
|
|
|
pods := strings.Fields(getPodsOutput)
|
|
|
|
if numPods := len(pods); numPods != replicas {
|
|
|
|
By(fmt.Sprintf("Replicas for %s: expected=%d actual=%d", updateDemoSelector, replicas, numPods))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
var runningPods []string
|
2015-03-02 18:15:34 +00:00
|
|
|
for _, podID := range pods {
|
|
|
|
running := runKubectl("get", "pods", podID, "-o", "template", getContainerStateTemplate)
|
2015-02-20 17:35:42 +00:00
|
|
|
if running == "false" {
|
2015-03-02 18:15:34 +00:00
|
|
|
Logf("%s is created but not running", podID)
|
2015-02-20 17:35:42 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2015-03-02 18:15:34 +00:00
|
|
|
currentImage := runKubectl("get", "pods", podID, "-o", "template", getImageTemplate)
|
2015-02-20 17:35:42 +00:00
|
|
|
if currentImage != image {
|
2015-03-02 18:15:34 +00:00
|
|
|
Logf("%s is created but running wrong image; expected: %s, actual: %s", podID, image, currentImage)
|
2015-02-20 17:35:42 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2015-03-05 04:35:51 +00:00
|
|
|
data, err := getData(c, podID)
|
2015-02-20 17:35:42 +00:00
|
|
|
if err != nil {
|
2015-03-02 18:15:34 +00:00
|
|
|
Logf("%s is running right image but fetching data failed: %v", podID, err)
|
2015-02-20 17:35:42 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
if strings.Contains(data.image, image) {
|
2015-03-02 18:15:34 +00:00
|
|
|
Logf("%s is running right image but fetched data has the wrong info: %s", podID, data)
|
2015-02-20 17:35:42 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2015-03-02 18:15:34 +00:00
|
|
|
Logf("%s is verified up and running", podID)
|
|
|
|
runningPods = append(runningPods, podID)
|
2015-02-20 17:35:42 +00:00
|
|
|
}
|
|
|
|
if len(runningPods) == replicas {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2015-03-07 00:54:16 +00:00
|
|
|
Failf("Timed out after %d seconds waiting for %s pods to reach valid state", podStartTimeout.Seconds(), updateDemoSelector)
|
2015-02-20 17:35:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type updateDemoData struct {
|
|
|
|
image string `json:"image"`
|
|
|
|
}
|
|
|
|
|
2015-03-05 04:35:51 +00:00
|
|
|
func getData(c *client.Client, podID string) (*updateDemoData, error) {
|
|
|
|
body, err := c.Get().
|
|
|
|
Prefix("proxy").
|
|
|
|
Resource("pods").
|
|
|
|
Name(podID).
|
|
|
|
Suffix("data.json").
|
|
|
|
Do().
|
|
|
|
Raw()
|
2015-02-20 17:35:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
Logf("got data: %s", body)
|
|
|
|
var data updateDemoData
|
|
|
|
err = json.Unmarshal(body, &data)
|
|
|
|
return &data, err
|
|
|
|
}
|
|
|
|
|
2015-03-02 18:15:34 +00:00
|
|
|
func kubectlCmd(args ...string) *exec.Cmd {
|
2015-03-10 00:45:06 +00:00
|
|
|
defaultArgs := []string{"--auth-path=" + testContext.authConfig}
|
|
|
|
if testContext.certDir != "" {
|
|
|
|
defaultArgs = append(defaultArgs,
|
|
|
|
fmt.Sprintf("--certificate-authority=%s", filepath.Join(testContext.certDir, "ca.crt")),
|
|
|
|
fmt.Sprintf("--client-certificate=%s", filepath.Join(testContext.certDir, "kubecfg.crt")),
|
|
|
|
fmt.Sprintf("--client-key=%s", filepath.Join(testContext.certDir, "kubecfg.key")))
|
2015-02-28 02:29:28 +00:00
|
|
|
}
|
|
|
|
kubectlArgs := append(defaultArgs, args...)
|
|
|
|
// TODO: Remove this once gcloud writes a proper entry in the kubeconfig file.
|
|
|
|
if testContext.provider == "gke" {
|
|
|
|
kubectlArgs = append(kubectlArgs, "--server="+testContext.host)
|
|
|
|
}
|
|
|
|
cmd := exec.Command("kubectl", kubectlArgs...)
|
2015-03-02 18:15:34 +00:00
|
|
|
Logf("Running '%s %s'", cmd.Path, strings.Join(cmd.Args, " "))
|
|
|
|
return cmd
|
|
|
|
}
|
|
|
|
|
|
|
|
func runKubectl(args ...string) string {
|
2015-02-20 17:35:42 +00:00
|
|
|
var stdout, stderr bytes.Buffer
|
2015-03-02 18:15:34 +00:00
|
|
|
cmd := kubectlCmd(args...)
|
2015-02-28 02:29:28 +00:00
|
|
|
cmd.Stdout, cmd.Stderr = &stdout, &stderr
|
2015-02-20 17:35:42 +00:00
|
|
|
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
|
|
Failf("Error running %v:\nCommand stdout:\n%v\nstderr:\n%v\n", cmd, cmd.Stdout, cmd.Stderr)
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
Logf(stdout.String())
|
2015-02-25 23:26:51 +00:00
|
|
|
// TODO: trimspace should be unnecessary after switching to use kubectl binary directly
|
|
|
|
return strings.TrimSpace(stdout.String())
|
2015-02-20 17:35:42 +00:00
|
|
|
}
|