Fix status update in case of connection errors

This commit is contained in:
Manuel Alejandro de Brito Fontes 2018-10-29 13:01:41 -03:00
parent 468872b7e9
commit fed013ab6f
11 changed files with 326 additions and 107 deletions

View file

@ -183,8 +183,7 @@ func execInfluxDBCommand(pod *corev1.Pod, command string) (string, error) {
execErr bytes.Buffer
)
args := fmt.Sprintf("kubectl exec --namespace %v %v -- %v", pod.Namespace, pod.Name, command)
cmd := exec.Command("/bin/bash", "-c", args)
cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("%v exec --namespace %s %s -- %s", framework.KubectlPath, pod.Namespace, pod.Name, command))
cmd.Stdout = &execOut
cmd.Stderr = &execErr
@ -195,7 +194,7 @@ func execInfluxDBCommand(pod *corev1.Pod, command string) (string, error) {
}
if err != nil {
return "", fmt.Errorf("could not execute: %v", err)
return "", fmt.Errorf("could not execute '%s %s': %v", cmd.Path, cmd.Args, err)
}
return execOut.String(), nil

View file

@ -17,13 +17,14 @@ limitations under the License.
package e2e
import (
"os"
"testing"
"github.com/golang/glog"
"github.com/onsi/ginkgo"
"github.com/onsi/ginkgo/config"
"github.com/onsi/gomega"
"k8s.io/apiserver/pkg/util/logs"
// required
_ "k8s.io/client-go/plugin/pkg/client/auth"
@ -36,6 +37,7 @@ import (
_ "k8s.io/ingress-nginx/test/e2e/servicebackend"
_ "k8s.io/ingress-nginx/test/e2e/settings"
_ "k8s.io/ingress-nginx/test/e2e/ssl"
_ "k8s.io/ingress-nginx/test/e2e/status"
)
// RunE2ETests checks configuration parameters (specified through flags) and then runs
@ -50,7 +52,12 @@ func RunE2ETests(t *testing.T) {
config.GinkgoConfig.SkipString = `\[Flaky\]|\[Feature:.+\]`
}
glog.Infof("Starting e2e run %q on Ginkgo node %d", framework.RunID, config.GinkgoConfig.ParallelNode)
if os.Getenv("KUBECTL_PATH") != "" {
framework.KubectlPath = os.Getenv("KUBECTL_PATH")
framework.Logf("Using kubectl path '%s'", framework.KubectlPath)
}
framework.Logf("Starting e2e run %q on Ginkgo node %d", framework.RunID, config.GinkgoConfig.ParallelNode)
ginkgo.RunSpecs(t, "nginx-ingress-controller e2e suite")
}

View file

@ -19,7 +19,11 @@ package framework
import (
"bytes"
"fmt"
"io"
"os/exec"
"regexp"
"strconv"
"strings"
"k8s.io/api/core/v1"
)
@ -31,14 +35,14 @@ func (f *Framework) ExecCommand(pod *v1.Pod, command string) (string, error) {
execErr bytes.Buffer
)
args := fmt.Sprintf("kubectl exec --namespace %v %v --container nginx-ingress-controller -- %v", pod.Namespace, pod.Name, command)
cmd := exec.Command("/bin/bash", "-c", args)
cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("%v exec --namespace %s %s --container nginx-ingress-controller -- %s", KubectlPath, pod.Namespace, pod.Name, command))
cmd.Stdout = &execOut
cmd.Stderr = &execErr
err := cmd.Run()
if err != nil {
return "", fmt.Errorf("could not execute: %v", err)
return "", fmt.Errorf("could not execute '%s %s': %v", cmd.Path, cmd.Args, err)
}
if execErr.Len() > 0 {
@ -59,3 +63,51 @@ func (f *Framework) NewIngressController(namespace string) error {
return nil
}
var (
proxyRegexp = regexp.MustCompile("Starting to serve on .*:([0-9]+)")
)
// KubectlProxy creates a proxy to kubernetes apiserver
func (f *Framework) KubectlProxy(port int) (int, *exec.Cmd, error) {
cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("%s proxy --accept-hosts=.* --address=0.0.0.0 --port=%d", KubectlPath, port))
stdout, stderr, err := startCmdAndStreamOutput(cmd)
if err != nil {
return -1, nil, err
}
defer stdout.Close()
defer stderr.Close()
buf := make([]byte, 128)
var n int
if n, err = stdout.Read(buf); err != nil {
return -1, cmd, fmt.Errorf("Failed to read from kubectl proxy stdout: %v", err)
}
output := string(buf[:n])
match := proxyRegexp.FindStringSubmatch(output)
if len(match) == 2 {
if port, err := strconv.Atoi(match[1]); err == nil {
return port, cmd, nil
}
}
return -1, cmd, fmt.Errorf("Failed to parse port from proxy stdout: %s", output)
}
func startCmdAndStreamOutput(cmd *exec.Cmd) (stdout, stderr io.ReadCloser, err error) {
stdout, err = cmd.StdoutPipe()
if err != nil {
return
}
stderr, err = cmd.StderrPipe()
if err != nil {
return
}
Logf("Asynchronously running '%s'", strings.Join(cmd.Args, " "))
err = cmd.Start()
return
}

View file

@ -46,6 +46,11 @@ const (
HTTPS RequestScheme = "https"
)
var (
// KubectlPath defines the full path of the kubectl binary
KubectlPath = "/usr/local/bin/kubectl"
)
// Framework supports common operations used by e2e tests; it will keep a client & a namespace for you.
type Framework struct {
BaseName string
@ -197,9 +202,8 @@ func (f *Framework) WaitForNginxConfiguration(matcher func(cfg string) bool) err
return wait.Poll(Poll, time.Minute*5, f.matchNginxConditions("", matcher))
}
// NginxLogs returns the logs of the nginx ingress controller pod running
func (f *Framework) NginxLogs() (string, error) {
l, err := f.KubeClientSet.CoreV1().Pods(f.IngressController.Namespace).List(metav1.ListOptions{
func nginxLogs(client kubernetes.Interface, namespace string) (string, error) {
l, err := client.CoreV1().Pods(namespace).List(metav1.ListOptions{
LabelSelector: "app.kubernetes.io/name=ingress-nginx",
})
if err != nil {
@ -209,7 +213,7 @@ func (f *Framework) NginxLogs() (string, error) {
for _, pod := range l.Items {
if strings.HasPrefix(pod.GetName(), "nginx-ingress-controller") {
if isRunning, err := podRunningReady(&pod); err == nil && isRunning {
return f.Logs(&pod)
return Logs(&pod)
}
}
}
@ -217,6 +221,11 @@ func (f *Framework) NginxLogs() (string, error) {
return "", fmt.Errorf("no nginx ingress controller pod is running (logs)")
}
// NginxLogs returns the logs of the nginx ingress controller pod running
func (f *Framework) NginxLogs() (string, error) {
return nginxLogs(f.KubeClientSet, f.IngressController.Namespace)
}
func (f *Framework) matchNginxConditions(name string, matcher func(cfg string) bool) wait.ConditionFunc {
return func() (bool, error) {
l, err := f.KubeClientSet.CoreV1().Pods(f.IngressController.Namespace).List(metav1.ListOptions{
@ -380,7 +389,7 @@ func UpdateDeployment(kubeClientSet kubernetes.Interface, namespace string, name
LabelSelector: fields.SelectorFromSet(fields.Set(deployment.Spec.Template.ObjectMeta.Labels)).String(),
})
if err != nil {
return errors.Wrapf(err, "failed to wait for nginx-ingress-controller replica count to be %v", replicas)
return errors.Wrapf(err, "waiting for nginx-ingress-controller replica count to be %v", replicas)
}
return nil

View file

@ -25,7 +25,7 @@ import (
)
// Logs returns the log entries of a given Pod.
func (f *Framework) Logs(pod *v1.Pod) (string, error) {
func Logs(pod *v1.Pod) (string, error) {
var (
execOut bytes.Buffer
execErr bytes.Buffer
@ -35,14 +35,13 @@ func (f *Framework) Logs(pod *v1.Pod) (string, error) {
return "", fmt.Errorf("could not determine which container to use")
}
args := fmt.Sprintf("kubectl logs -n %v %v", pod.Namespace, pod.Name)
cmd := exec.Command("/bin/bash", "-c", args)
cmd := exec.Command("/bin/bash", "-c", fmt.Sprintf("%v logs --namespace %s %s", KubectlPath, pod.Namespace, pod.Name))
cmd.Stdout = &execOut
cmd.Stderr = &execErr
err := cmd.Run()
if err != nil {
return "", fmt.Errorf("could not execute: %v", err)
return "", fmt.Errorf("could not execute '%s %s': %v", cmd.Path, cmd.Args, err)
}
if execErr.Len() > 0 {

145
test/e2e/status/update.go Normal file
View file

@ -0,0 +1,145 @@
/*
Copyright 2018 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 settings
import (
"fmt"
"log"
"net"
"strings"
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
appsv1beta1 "k8s.io/api/apps/v1beta1"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/ingress-nginx/test/e2e/framework"
)
var _ = framework.IngressNginxDescribe("Status Update [Status]", func() {
f := framework.NewDefaultFramework("status-update")
host := "status-update"
address := getHostIP()
BeforeEach(func() {
})
AfterEach(func() {
})
It("should update status field after client-go reconnection", func() {
port, cmd, err := f.KubectlProxy(0)
Expect(err).NotTo(HaveOccurred(), "starting kubectl proxy")
err = framework.UpdateDeployment(f.KubeClientSet, f.IngressController.Namespace, "nginx-ingress-controller", 1,
func(deployment *appsv1beta1.Deployment) error {
args := deployment.Spec.Template.Spec.Containers[0].Args
args = append(args, fmt.Sprintf("--apiserver-host=http://%s:%d", address.String(), port))
args = append(args, "--publish-status-address=1.1.0.0")
// flags --publish-service and --publish-status-address are mutually exclusive
var index int
for k, v := range args {
if strings.Index(v, "--publish-service") != -1 {
index = k
break
}
}
if index > -1 {
args[index] = ""
}
deployment.Spec.Template.Spec.Containers[0].Args = args
_, err := f.KubeClientSet.AppsV1beta1().Deployments(f.IngressController.Namespace).Update(deployment)
return err
})
Expect(err).NotTo(HaveOccurred(), "updating ingress controller deployment flags")
err = f.NewEchoDeploymentWithReplicas(1)
Expect(err).NotTo(HaveOccurred(), "waiting one replicaset in echoserver deployment")
ing, err := f.EnsureIngress(framework.NewSingleIngress(host, "/", host, f.IngressController.Namespace, "http-svc", 80, nil))
Expect(err).NotTo(HaveOccurred(), "waiting Ingress creation for hostname %v", host)
Expect(ing).NotTo(BeNil())
err = f.WaitForNginxConfiguration(
func(cfg string) bool {
return strings.Contains(cfg, fmt.Sprintf("server_name %s", host))
})
Expect(err).NotTo(HaveOccurred(), "waiting for nginx server section with server_name %v", host)
framework.Logf("waiting for leader election and initial status update")
time.Sleep(30 * time.Second)
err = cmd.Process.Kill()
Expect(err).NotTo(HaveOccurred(), "terminating kubectl proxy")
ing, err = f.KubeClientSet.Extensions().Ingresses(f.IngressController.Namespace).Get(host, metav1.GetOptions{})
Expect(err).NotTo(HaveOccurred(), "getting %s/%v Ingress", f.IngressController.Namespace, host)
ing.Status.LoadBalancer.Ingress = []apiv1.LoadBalancerIngress{}
_, err = f.KubeClientSet.Extensions().Ingresses(f.IngressController.Namespace).UpdateStatus(ing)
Expect(err).NotTo(HaveOccurred(), "cleaning Ingress status")
time.Sleep(10 * time.Second)
err = f.KubeClientSet.CoreV1().
ConfigMaps(f.IngressController.Namespace).
Delete("ingress-controller-leader-nginx", &metav1.DeleteOptions{})
Expect(err).NotTo(HaveOccurred(), "deleting leader election configmap")
_, cmd, err = f.KubectlProxy(port)
Expect(err).NotTo(HaveOccurred(), "starting kubectl proxy")
defer func() {
if cmd != nil {
err := cmd.Process.Kill()
Expect(err).NotTo(HaveOccurred(), "terminating kubectl proxy")
}
}()
err = wait.Poll(10*time.Second, time.Minute*3, func() (done bool, err error) {
ing, err = f.KubeClientSet.Extensions().Ingresses(f.IngressController.Namespace).Get(host, metav1.GetOptions{})
if err != nil {
return false, err
}
if len(ing.Status.LoadBalancer.Ingress) != 1 {
return false, nil
}
return true, nil
})
Expect(err).NotTo(HaveOccurred(), "waiting for ingress status")
Expect(ing.Status.LoadBalancer.Ingress).Should(Equal([]apiv1.LoadBalancerIngress{
{IP: "1.1.0.0"},
}))
})
})
func getHostIP() net.IP {
conn, err := net.Dial("udp", "8.8.8.8:80")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
localAddr := conn.LocalAddr().(*net.UDPAddr)
return localAddr.IP
}