Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Turn ingress TestCheck into an E2e test #32

Merged
merged 2 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 62 additions & 2 deletions cmd/ingress/ingress.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ingress
import (
"context"
"fmt"
"net/http"
"os"
"time"

Expand All @@ -28,9 +29,15 @@ type Ingress struct {
Namespace string
Clientset *kubernetes.Clientset
NoDnsCheck bool
NoHTTPCheck bool
IngressClassName string
ResourceName string
ExternalHostname string
// HTTPCheckEndpoint is the HTTP endpoint that is requested to check the service.
// If not set, it defaults to http://<ExternalHostname>/
// This is usually set to the LoadBalancer IP of the Ingress Controller Service,
// in case the external hostname is not resolvable.
HTTPCheckEndpoint string
}

func NewIngress(checker *cmd.Checker, noDnsCheck bool) (*Ingress, error) {
Expand Down Expand Up @@ -98,6 +105,16 @@ func (i *Ingress) Check() error {
}
}

if i.NoHTTPCheck {
i.Chatwork.AddMessage("Skip HTTP Check\n")
i.Logger().Info("Skip HTTP Check")
} else {
if err := i.checkHTTP(); err != nil {
return err
}

}

i.Chatwork.AddMessage("Ingress check finished\n")
return nil
}
Expand Down Expand Up @@ -187,7 +204,7 @@ func (i *Ingress) createDeploymentObject() *appsv1.Deployment {
{
Name: "http",
Protocol: apiv1.ProtocolTCP,
ContainerPort: 8080,
ContainerPort: 80,
},
},
},
Expand All @@ -213,7 +230,7 @@ func (i *Ingress) createServiceObject() *apiv1.Service {
{
Protocol: apiv1.ProtocolTCP,
Port: 80,
TargetPort: intstr.FromInt(8080),
TargetPort: intstr.FromInt(80),
},
},
},
Expand Down Expand Up @@ -308,3 +325,46 @@ func (i *Ingress) checkDNSRecord() error {

return nil
}

// Tries to access the service endpoint via HTTP
// and see if it returns 200 OK.
func (i *Ingress) checkHTTP() error {
var endpoint string
if i.HTTPCheckEndpoint != "" {
endpoint = i.HTTPCheckEndpoint
} else {
endpoint = fmt.Sprintf("http://%s/", i.ExternalHostname)
}

i.Logger().Infof("Check HTTP for: %s", endpoint)
err := wait.PollUntilContextTimeout(i.Ctx, 10*time.Second, i.Timeout, false, func(ctx context.Context) (bool, error) {
req, err := http.NewRequest("GET", endpoint, nil)
if err != nil {
return false, err
}
req.Host = i.ExternalHostname

i.Logger().Infof("Requesting %s with headers %v", endpoint, req.Header)

resp, err := http.DefaultClient.Do(req)
if err != nil {
i.Logger().Warn(err)
return false, nil
}

if resp.StatusCode != 200 {
i.Logger().Infof("HTTP Status Code is not 200: %d", resp.StatusCode)
return false, nil
}

i.Logger().Info("HTTP Status Code is 200")
i.Chatwork.AddMessage("HTTP Status Code is 200\n")
return true, nil
})

if err != nil {
return fmt.Errorf("waiting for HTTP service to be ready: %w", err)
}

return nil
}
87 changes: 77 additions & 10 deletions cmd/ingress/ingress_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ import (
"context"
"fmt"
"os"
"os/exec"
"syscall"
"testing"
"time"

"github.com/chatwork/kibertas/cmd"
"github.com/chatwork/kibertas/config"
"github.com/chatwork/kibertas/util"
"github.com/chatwork/kibertas/util/notify"
"github.com/mumoshu/testkit"
"github.com/stretchr/testify/require"

"github.com/sirupsen/logrus"
)
Expand All @@ -34,6 +36,10 @@ func TestNewIngress(t *testing.T) {
}

func TestCheck(t *testing.T) {
if testing.Short() {
t.Skip("Skipping test in short mode.")
}

t.Parallel()
logger := func() *logrus.Entry {
return logrus.NewEntry(logrus.New())
Expand All @@ -53,18 +59,43 @@ func TestCheck(t *testing.T) {

kc := h.KubernetesCluster(t)

// Start cloud-provider-kind to manage service type=LoadBalancer
//
// This requiers cloud-provider-kind to be installed in the PATH.
// Follow https://github.com/kubernetes-sigs/cloud-provider-kind?tab=readme-ov-file#install to install it.
bin, err := exec.LookPath("cloud-provider-kind")
if bin == "" {
t.Fatalf("cloud-provider-kind not found in PATH: %s", os.Getenv("PATH"))
}
require.NoError(t, err)

handle := StartProcess(t, bin)
defer handle.Stop(t)

helm := testkit.NewHelm(kc.KubeconfigPath)
// See https://github.com/kubernetes/autoscaler/tree/master/charts/cluster-autoscaler#tldr
helm.AddRepo(t, "ingress-nginx", "https://kubernetes.github.io/ingress-nginx")

ingressNginxNs := "default"
helm.UpgradeOrInstall(t, "my-ingress-nginx", "ingress-nginx/ingress-nginx", func(hc *testkit.HelmConfig) {
hc.Values = map[string]interface{}{}
hc.Values = map[string]interface{}{
"rbac": map[string]interface{}{
"create": true,
},
}

hc.Namespace = ingressNginxNs
})

namespace := fmt.Sprintf("ingress-test-%d%02d%02d-%s", now.Year(), now.Month(), now.Day(), util.GenerateRandomString(5))
kctl := testkit.NewKubectl(kc.KubeconfigPath)

// Get the external IP of the ingress-nginx service
ingressNginxSvcLBIP := kctl.Capture(t, "get", "svc", "-n", ingressNginxNs, "my-ingress-nginx-controller", "-o", "jsonpath={.status.loadBalancer.ingress[0].ip}")
t.Logf("ingress-nginx service LB IP: %s", ingressNginxSvcLBIP)

// We intentionally make the test namespace deterministic to avoid ingress path
// conflicts among test namespaces across test runs
namespace := fmt.Sprintf("ingress-test-%d%02d%02d", now.Year(), now.Month(), now.Day())

os.Setenv("KUBECONFIG", kc.KubeconfigPath)

Expand All @@ -76,13 +107,14 @@ func TestCheck(t *testing.T) {
// kindとingress-nginxがある前提
// レコードは作れないのでNoDnsCheckをtrueにする
ingress := &Ingress{
Checker: cmd.NewChecker(context.Background(), true, logger, chatwork, "test", 1*time.Minute),
Namespace: namespace,
Clientset: k8sclient,
NoDnsCheck: true,
IngressClassName: "nginx",
ResourceName: "sample",
ExternalHostname: "sample.example.com",
Checker: cmd.NewChecker(context.Background(), true, logger, chatwork, "test", 1*time.Minute),
Namespace: namespace,
Clientset: k8sclient,
NoDnsCheck: true,
IngressClassName: "nginx",
ResourceName: "sample",
ExternalHostname: "sample.example.com",
HTTPCheckEndpoint: "http://" + ingressNginxSvcLBIP + "/",
}

err = ingress.Check()
Expand All @@ -91,6 +123,41 @@ func TestCheck(t *testing.T) {
}
}

type ProcessHandle struct {
proc *os.Process
}

// Sends a SIGTERM to the process
func (h *ProcessHandle) Stop(t *testing.T) {
t.Helper()

if err := h.proc.Signal(syscall.SIGTERM); err != nil {
t.Errorf("Failed to send SIGTERM to the process: %s", err)
}

if _, err := h.proc.Wait(); err != nil {
t.Errorf("Failed to wait for the process to exit: %s", err)
}
}

func StartProcess(t *testing.T, name string) *ProcessHandle {
t.Helper()

handle := &ProcessHandle{}

proc, err := os.StartProcess(name, []string{}, &os.ProcAttr{
Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
})

if err != nil {
t.Fatalf("Failed to start process: %s", err)
}

handle.proc = proc

return handle
}

func TestCheckDNSRecord(t *testing.T) {
logger := func() *logrus.Entry {
return logrus.NewEntry(logrus.New())
Expand Down