k3s/test/e2e/upgrades/etcd.go

209 lines
6.1 KiB
Go
Raw Normal View History

2017-06-30 00:03:25 +00:00
/*
Copyright 2017 The Kubernetes Authors.
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 upgrades
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"path/filepath"
"sync"
"time"
"github.com/onsi/ginkgo"
"github.com/onsi/gomega"
2017-06-30 00:03:25 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2018-09-21 05:06:51 +00:00
"k8s.io/apimachinery/pkg/util/version"
2018-09-21 06:03:05 +00:00
"k8s.io/apimachinery/pkg/util/wait"
2017-06-30 00:03:25 +00:00
"k8s.io/kubernetes/test/e2e/framework"
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
e2e: abstract access to additional files The new test/e2e/framework/testfiles package makes it possible to write tests that do not depend on a specific way of providing additional test files at runtime. Such tests and the framework are then more easily reused in other test suites. In the test/e2e suite file access is enabled based on the existing "repo-root" command line parameter and the built-in bindata. Tests using the new API will first check for files under "repo-root" and then fall back to the builtin data. This way, users of a test binary can modify those files without having to rebuild the binary. "repo-root" is still needed because at least some tests check for additional files (secret.yaml, via ingress_utils.go) that are not part of the upstream source code and thus may or may not be built into a test binary. Tests using bindata or repo-root directly get modified to use the new API, or removed when they are obsolete: test/e2e/examples.go depended on files that were removed in https://github.com/kubernetes/kubernetes/pull/61246 and thus can no longer be run in Kubernetes. Moving the tests to kubernetes/examples is tracked in https://github.com/kubernetes/examples/issues/214. The file removal did not break the automated E2E testing probably because the tests are under the Feature:Example tag and thus not enabled during normal CI runs. Removing also the obsolete tests makes it simpler to rework the "repo-root" setting because less code uses it. Related-to: #66649 and #23987
2018-08-31 14:43:15 +00:00
"k8s.io/kubernetes/test/e2e/framework/testfiles"
2017-06-30 00:03:25 +00:00
)
const manifestPath = "test/e2e/testing-manifests/statefulset/etcd"
// EtcdUpgradeTest tests that etcd is writable before and after a cluster upgrade.
2017-06-30 00:03:25 +00:00
type EtcdUpgradeTest struct {
ip string
successfulWrites int
ssTester *framework.StatefulSetTester
}
// Name returns the tracking name of the test.
2017-06-30 00:03:25 +00:00
func (EtcdUpgradeTest) Name() string { return "etcd-upgrade" }
// Skip returns true when this test can be skipped.
2017-06-30 00:03:25 +00:00
func (EtcdUpgradeTest) Skip(upgCtx UpgradeContext) bool {
minVersion := version.MustParseSemantic("1.6.0")
for _, vCtx := range upgCtx.Versions {
if vCtx.Version.LessThan(minVersion) {
return true
}
}
return false
}
func kubectlCreate(ns, file string) {
input := string(testfiles.ReadOrDie(filepath.Join(manifestPath, file), ginkgo.Fail))
e2e: abstract access to additional files The new test/e2e/framework/testfiles package makes it possible to write tests that do not depend on a specific way of providing additional test files at runtime. Such tests and the framework are then more easily reused in other test suites. In the test/e2e suite file access is enabled based on the existing "repo-root" command line parameter and the built-in bindata. Tests using the new API will first check for files under "repo-root" and then fall back to the builtin data. This way, users of a test binary can modify those files without having to rebuild the binary. "repo-root" is still needed because at least some tests check for additional files (secret.yaml, via ingress_utils.go) that are not part of the upstream source code and thus may or may not be built into a test binary. Tests using bindata or repo-root directly get modified to use the new API, or removed when they are obsolete: test/e2e/examples.go depended on files that were removed in https://github.com/kubernetes/kubernetes/pull/61246 and thus can no longer be run in Kubernetes. Moving the tests to kubernetes/examples is tracked in https://github.com/kubernetes/examples/issues/214. The file removal did not break the automated E2E testing probably because the tests are under the Feature:Example tag and thus not enabled during normal CI runs. Removing also the obsolete tests makes it simpler to rework the "repo-root" setting because less code uses it. Related-to: #66649 and #23987
2018-08-31 14:43:15 +00:00
framework.RunKubectlOrDieInput(input, "create", "-f", "-", fmt.Sprintf("--namespace=%s", ns))
2017-06-30 00:03:25 +00:00
}
// Setup creates etcd statefulset and then verifies that the etcd is writable.
2017-06-30 00:03:25 +00:00
func (t *EtcdUpgradeTest) Setup(f *framework.Framework) {
ns := f.Namespace.Name
statefulsetPoll := 30 * time.Second
statefulsetTimeout := 10 * time.Minute
t.ssTester = framework.NewStatefulSetTester(f.ClientSet)
ginkgo.By("Creating a PDB")
2017-06-30 00:03:25 +00:00
kubectlCreate(ns, "pdb.yaml")
ginkgo.By("Creating an etcd StatefulSet")
2017-06-30 00:03:25 +00:00
t.ssTester.CreateStatefulSet(manifestPath, ns)
ginkgo.By("Creating an etcd--test-server deployment")
2017-06-30 00:03:25 +00:00
kubectlCreate(ns, "tester.yaml")
ginkgo.By("Getting the ingress IPs from the services")
2017-06-30 00:03:25 +00:00
err := wait.PollImmediate(statefulsetPoll, statefulsetTimeout, func() (bool, error) {
if t.ip = t.getServiceIP(f, ns, "test-server"); t.ip == "" {
return false, nil
}
if _, err := t.listUsers(); err != nil {
e2elog.Logf("Service endpoint is up but isn't responding")
2017-06-30 00:03:25 +00:00
return false, nil
}
return true, nil
})
framework.ExpectNoError(err)
e2elog.Logf("Service endpoint is up")
2017-06-30 00:03:25 +00:00
ginkgo.By("Adding 2 dummy users")
err = t.addUser("Alice")
framework.ExpectNoError(err)
err = t.addUser("Bob")
framework.ExpectNoError(err)
2017-06-30 00:03:25 +00:00
t.successfulWrites = 2
ginkgo.By("Verifying that the users exist")
2017-06-30 00:03:25 +00:00
users, err := t.listUsers()
framework.ExpectNoError(err)
gomega.Expect(len(users)).To(gomega.Equal(2))
2017-06-30 00:03:25 +00:00
}
func (t *EtcdUpgradeTest) listUsers() ([]string, error) {
r, err := http.Get(fmt.Sprintf("http://%s:8080/list", t.ip))
if err != nil {
return nil, err
}
defer r.Body.Close()
if r.StatusCode != http.StatusOK {
b, err := ioutil.ReadAll(r.Body)
if err != nil {
return nil, err
}
return nil, fmt.Errorf(string(b))
}
var names []string
if err := json.NewDecoder(r.Body).Decode(&names); err != nil {
return nil, err
}
return names, nil
}
func (t *EtcdUpgradeTest) addUser(name string) error {
val := map[string][]string{"name": {name}}
r, err := http.PostForm(fmt.Sprintf("http://%s:8080/add", t.ip), val)
if err != nil {
return err
}
defer r.Body.Close()
if r.StatusCode != http.StatusOK {
b, err := ioutil.ReadAll(r.Body)
if err != nil {
return err
}
return fmt.Errorf(string(b))
}
return nil
}
func (t *EtcdUpgradeTest) getServiceIP(f *framework.Framework, ns, svcName string) string {
svc, err := f.ClientSet.CoreV1().Services(ns).Get(svcName, metav1.GetOptions{})
framework.ExpectNoError(err)
2017-06-30 00:03:25 +00:00
ingress := svc.Status.LoadBalancer.Ingress
if len(ingress) == 0 {
return ""
}
return ingress[0].IP
}
// Test waits for upgrade to complete and verifies if etcd is writable.
2017-06-30 00:03:25 +00:00
func (t *EtcdUpgradeTest) Test(f *framework.Framework, done <-chan struct{}, upgrade UpgradeType) {
ginkgo.By("Continuously polling the database during upgrade.")
2017-06-30 00:03:25 +00:00
var (
success, failures, writeAttempts, lastUserCount int
mu sync.Mutex
errors = map[string]int{}
)
// Write loop.
go wait.Until(func() {
writeAttempts++
if err := t.addUser(fmt.Sprintf("user-%d", writeAttempts)); err != nil {
e2elog.Logf("Unable to add user: %v", err)
2017-06-30 00:03:25 +00:00
mu.Lock()
errors[err.Error()]++
mu.Unlock()
return
}
t.successfulWrites++
}, 10*time.Millisecond, done)
// Read loop.
wait.Until(func() {
users, err := t.listUsers()
if err != nil {
e2elog.Logf("Could not retrieve users: %v", err)
2017-06-30 00:03:25 +00:00
failures++
mu.Lock()
errors[err.Error()]++
mu.Unlock()
return
}
success++
lastUserCount = len(users)
}, 10*time.Millisecond, done)
e2elog.Logf("got %d users; want >=%d", lastUserCount, t.successfulWrites)
2017-06-30 00:03:25 +00:00
gomega.Expect(lastUserCount >= t.successfulWrites).To(gomega.BeTrue())
2017-06-30 00:03:25 +00:00
ratio := float64(success) / float64(success+failures)
e2elog.Logf("Successful gets %d/%d=%v", success, success+failures, ratio)
2017-06-30 00:03:25 +00:00
ratio = float64(t.successfulWrites) / float64(writeAttempts)
e2elog.Logf("Successful writes %d/%d=%v", t.successfulWrites, writeAttempts, ratio)
e2elog.Logf("Errors: %v", errors)
2017-06-30 00:03:25 +00:00
// TODO(maisem): tweak this value once we have a few test runs.
gomega.Expect(ratio > 0.75).To(gomega.BeTrue())
2017-06-30 00:03:25 +00:00
}
// Teardown does one final check of the data's availability.
func (t *EtcdUpgradeTest) Teardown(f *framework.Framework) {
users, err := t.listUsers()
framework.ExpectNoError(err)
gomega.Expect(len(users) >= t.successfulWrites).To(gomega.BeTrue())
2017-06-30 00:03:25 +00:00
}