Skip to content
This repository has been archived by the owner on Jul 11, 2023. It is now read-only.

Commit

Permalink
Create e2e test for retry, tests fields retryOn and numRetries
Browse files Browse the repository at this point in the history
Signed-off-by: Shalier Xia <shalierxia@microsoft.com>
  • Loading branch information
shalier committed May 9, 2022
1 parent a6d71d2 commit 05fe1ce
Show file tree
Hide file tree
Showing 7 changed files with 348 additions and 2 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ kind-reset:

.PHONY: test-e2e
test-e2e: DOCKER_BUILDX_OUTPUT=type=docker
test-e2e: docker-build-osm build-osm docker-build-tcp-echo-server
test-e2e: docker-build-osm build-osm docker-build-tcp-echo-server docker-build-retry
go test ./tests/e2e $(E2E_FLAGS_DEFAULT) $(E2E_FLAGS)

.env:
Expand All @@ -143,7 +143,7 @@ kind-demo: .env kind-up clean-osm
build-bookwatcher:
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o ./demo/bin/bookwatcher/bookwatcher ./demo/cmd/bookwatcher

DEMO_TARGETS = bookbuyer bookthief bookstore bookwarehouse tcp-echo-server tcp-client
DEMO_TARGETS = bookbuyer bookthief bookstore bookwarehouse tcp-echo-server tcp-client retry
# docker-build-bookbuyer, etc
DOCKER_DEMO_TARGETS = $(addprefix docker-build-, $(DEMO_TARGETS))
.PHONY: $(DOCKER_DEMO_TARGETS)
Expand Down
41 changes: 41 additions & 0 deletions demo/cmd/retry/retry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package main

import (
_ "embed"
"fmt"
"net/http"
"sync"

"github.com/openservicemesh/osm/pkg/logger"
"github.com/openservicemesh/osm/tests/e2e"
)

var log = logger.NewPretty("retry")

var mu sync.Mutex
var httpRequests uint32

const retryOn = 555

func retryHandler(w http.ResponseWriter, r *http.Request) {
mu.Lock()
// Count number of http requests received
httpRequests++
mu.Unlock()

// Return status code that causes retry policy
if httpRequests <= e2e.NumRetries {
w.WriteHeader(retryOn)
}
_, err := w.Write([]byte(fmt.Sprintf("RequestCount:%v\n", httpRequests)))
if err != nil {
log.Error().Err(err).Msgf("Couldn't write number of requests recevied")
}
}

func main() {
// mux := http.NewServeMux()
http.HandleFunc("/", retryHandler)
err := http.ListenAndServe(":9091", nil)
log.Fatal().Err(err).Msgf("Failed to start HTTP server on port 9091")
}
2 changes: 2 additions & 0 deletions tests/e2e/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ const (

var (
fortioSingleCallSpec = framework.FortioLoadTestSpec{Calls: 1}
// NumRetries is the number of retries for retry e2e
NumRetries uint32 = 5
)
239 changes: 239 additions & 0 deletions tests/e2e/e2e_retry_policy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
package e2e

import (
"context"
"fmt"
"time"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/openservicemesh/osm/pkg/apis/policy/v1alpha1"
. "github.com/openservicemesh/osm/tests/framework"
)

const client = "client"
const server = "server"

var meshNs = []string{client, server}

var _ = OSMDescribe("Test Retry Policy",
OSMDescribeInfo{
Tier: 2,
Bucket: 8,
},
func() {
Context("Retry policy enabled", func() {
It("tests retryOn and numRetries field for retry policy",
func() {
// Install OSM
installOpts := Td.GetOSMInstallOpts()
installOpts.EnablePermissiveMode = true
installOpts.EnableRetryPolicy = true
Expect(Td.InstallOSM(installOpts)).To(Succeed())

// Create test NS in mesh
for _, n := range meshNs {
Expect(Td.CreateNs(n, nil)).To(Succeed())
Expect(Td.AddNsToMesh(true, n)).To(Succeed())
}

// Load retry image
retryImage := fmt.Sprintf("%s/retry:%s", installOpts.ContainerRegistryLoc, installOpts.OsmImagetag)
Expect(Td.LoadImagesToKind([]string{"retry"})).To(Succeed())

svcAccDef, podDef, svcDef, err := Td.SimplePodApp(
SimplePodAppDef{
PodName: server,
Namespace: server,
ServiceAccountName: server,
Command: []string{"/retry"},
Image: retryImage,
Ports: []int{9091},
OS: Td.ClusterOS,
})
Expect(err).NotTo(HaveOccurred())

_, err = Td.CreateServiceAccount(server, &svcAccDef)
Expect(err).NotTo(HaveOccurred())
_, err = Td.CreatePod(server, podDef)
Expect(err).NotTo(HaveOccurred())
serverSvc, err := Td.CreateService(server, svcDef)
Expect(err).NotTo(HaveOccurred())

Expect(Td.WaitForPodsRunningReady(server, 90*time.Second, 1, nil)).To(Succeed())

// Get simple Pod definitions for the source/client
svcAccDef, podDef, svcDef, err = Td.SimplePodApp(SimplePodAppDef{
PodName: client,
Namespace: client,
Command: []string{"sleep", "365d"},
Image: "curlimages/curl",
Ports: []int{80},
OS: Td.ClusterOS,
})
Expect(err).NotTo(HaveOccurred())

clientSvcAcct, err := Td.CreateServiceAccount(client, &svcAccDef)
Expect(err).NotTo(HaveOccurred())
clientPod, err := Td.CreatePod(client, podDef)
Expect(err).NotTo(HaveOccurred())
_, err = Td.CreateService(client, svcDef)
Expect(err).NotTo(HaveOccurred())

Expect(Td.WaitForPodsRunningReady(client, 90*time.Second, 1, nil)).To(Succeed())

retry := &v1alpha1.Retry{
ObjectMeta: metav1.ObjectMeta{
Name: "retrypolicy",
Namespace: client,
},
Spec: v1alpha1.RetrySpec{
Source: v1alpha1.RetrySrcDstSpec{
Kind: "ServiceAccount",
Name: clientSvcAcct.Name,
Namespace: client,
},
Destinations: []v1alpha1.RetrySrcDstSpec{
{
Kind: "Service",
Name: serverSvc.Name,
Namespace: server,
},
},
RetryPolicy: v1alpha1.RetryPolicySpec{
RetryOn: "5xx",
PerTryTimeout: &metav1.Duration{Duration: time.Duration(1 * time.Second)},
NumRetries: &NumRetries,
RetryBackoffBaseInterval: &metav1.Duration{Duration: time.Duration(5 * time.Second)},
},
},
}
_, err = Td.PolicyClient.PolicyV1alpha1().Retries(client).Create(context.TODO(), retry, metav1.CreateOptions{})
Expect(err).ToNot((HaveOccurred()))

req := HTTPRequestDef{
SourceNs: client,
SourcePod: clientPod.Name,
SourceContainer: podDef.GetName(),
Destination: fmt.Sprintf("%s.%s.svc.cluster.local:9091", serverSvc.Name, server),
}

By("A request that will be retried NumRetries times then succeed")
// wait for server
time.Sleep(3 * time.Second)
result := Td.RetryHTTPRequest(req)
// One count is the initial http request that returns a retriable status code
// followed by numRetries retries
Expect(result.RequestCount).To(Equal(int(NumRetries) + 1))
Expect(result.StatusCode).To(Equal(200))
Expect(result.Err).To(BeNil())
})
})
Context("Retry policy disabled", func() {
It("tests retry does not occur",
func() {
// Install OSM
installOpts := Td.GetOSMInstallOpts()
installOpts.EnablePermissiveMode = true
installOpts.EnableRetryPolicy = false
Expect(Td.InstallOSM(installOpts)).To(Succeed())

// Create test NS in mesh
for _, n := range meshNs {
Expect(Td.CreateNs(n, nil)).To(Succeed())
Expect(Td.AddNsToMesh(true, n)).To(Succeed())
}
// Load retry image
retryImage := fmt.Sprintf("%s/retry:%s", installOpts.ContainerRegistryLoc, installOpts.OsmImagetag)
Expect(Td.LoadImagesToKind([]string{"retry"})).To(Succeed())

svcAccDef, podDef, svcDef, err := Td.SimplePodApp(
SimplePodAppDef{
PodName: server,
Namespace: server,
ServiceAccountName: server,
Command: []string{"/retry"},
Image: retryImage,
Ports: []int{9091},
OS: Td.ClusterOS,
})
Expect(err).NotTo(HaveOccurred())

_, err = Td.CreateServiceAccount(server, &svcAccDef)
Expect(err).NotTo(HaveOccurred())
_, err = Td.CreatePod(server, podDef)
Expect(err).NotTo(HaveOccurred())
serverSvc, err := Td.CreateService(server, svcDef)
Expect(err).NotTo(HaveOccurred())

Expect(Td.WaitForPodsRunningReady(server, 90*time.Second, 1, nil)).To(Succeed())

// Get simple Pod definitions for the source/client
svcAccDef, podDef, svcDef, err = Td.SimplePodApp(SimplePodAppDef{
PodName: client,
Namespace: client,
Command: []string{"sleep", "365d"},
Image: "curlimages/curl",
Ports: []int{80},
OS: Td.ClusterOS,
})
Expect(err).NotTo(HaveOccurred())

clientSvcAcct, err := Td.CreateServiceAccount(client, &svcAccDef)
Expect(err).NotTo(HaveOccurred())
clientPod, err := Td.CreatePod(client, podDef)
Expect(err).NotTo(HaveOccurred())
_, err = Td.CreateService(client, svcDef)
Expect(err).NotTo(HaveOccurred())

Expect(Td.WaitForPodsRunningReady(client, 90*time.Second, 1, nil)).To(Succeed())

retry := &v1alpha1.Retry{
ObjectMeta: metav1.ObjectMeta{
Name: "retrypolicy",
Namespace: client,
},
Spec: v1alpha1.RetrySpec{
Source: v1alpha1.RetrySrcDstSpec{
Kind: "ServiceAccount",
Name: clientSvcAcct.Name,
Namespace: client,
},
Destinations: []v1alpha1.RetrySrcDstSpec{
{
Kind: "Service",
Name: serverSvc.Name,
Namespace: server,
},
},
RetryPolicy: v1alpha1.RetryPolicySpec{
RetryOn: "5xx",
PerTryTimeout: &metav1.Duration{Duration: time.Duration(1 * time.Second)},
NumRetries: &NumRetries,
RetryBackoffBaseInterval: &metav1.Duration{Duration: time.Duration(5 * time.Second)},
},
},
}
_, err = Td.PolicyClient.PolicyV1alpha1().Retries(client).Create(context.TODO(), retry, metav1.CreateOptions{})
Expect(err).ToNot((HaveOccurred()))

req := HTTPRequestDef{
SourceNs: client,
SourcePod: clientPod.Name,
SourceContainer: podDef.GetName(),
Destination: fmt.Sprintf("%s.%s.svc.cluster.local:9091", serverSvc.Name, server),
}

By("A request that will not be retried on")
// wait for server
time.Sleep(3 * time.Second)
result := Td.RetryHTTPRequest(req)
// One count is the initial http request that is not retried on
Expect(result.RequestCount).To(Equal(1))
Expect(result.StatusCode).To(Equal(555))
Expect(result.Err).To(BeNil())
})
})
})
2 changes: 2 additions & 0 deletions tests/framework/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ func setMeshConfigToDefault(instOpts InstallOSMOpts, meshConfig *configv1alpha2.
meshConfig.Spec.Certificate.CertKeyBitSize = instOpts.CertKeyBitSize

meshConfig.Spec.FeatureFlags.EnableIngressBackendPolicy = instOpts.EnableIngressBackendPolicy
meshConfig.Spec.FeatureFlags.EnableRetryPolicy = instOpts.EnableRetryPolicy

return meshConfig
}
Expand Down Expand Up @@ -503,6 +504,7 @@ func (td *OsmTestData) InstallOSM(instOpts InstallOSMOpts) error {
fmt.Sprintf("osm.enableFluentbit=%v", instOpts.DeployFluentbit),
fmt.Sprintf("osm.enablePrivilegedInitContainer=%v", instOpts.EnablePrivilegedInitContainer),
fmt.Sprintf("osm.featureFlags.enableIngressBackendPolicy=%v", instOpts.EnableIngressBackendPolicy),
fmt.Sprintf("osm.featureFlags.enableRetryPolicy=%v", instOpts.EnableRetryPolicy),
fmt.Sprintf("osm.enableReconciler=%v", instOpts.EnableReconciler),
)

Expand Down
61 changes: 61 additions & 0 deletions tests/framework/common_traffic.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ type HTTPRequestResult struct {
Err error
}

// RetryRequestResult represents the result of a GRPCRequest call
type RetryRequestResult struct {
RequestCount int
StatusCode int
Err error
}

// TCPRequestResult represents the result of a TCPRequest call
type TCPRequestResult struct {
Response string
Expand Down Expand Up @@ -132,6 +139,60 @@ func (td *OsmTestData) HTTPRequest(ht HTTPRequestDef) HTTPRequestResult {
}
}

// RetryHTTPRequest runs a synchronous call to run the HTTPRequestDef and returns the HTTP Response (string)
func (td *OsmTestData) RetryHTTPRequest(ht HTTPRequestDef) RetryRequestResult {
// -s silent progress, -L follow redirects
var commandStr string
if td.ClusterOS == constants.OSWindows {
commandStr = fmt.Sprintf("curl.exe -s -w %s:%%{http_code} -L %s", StatusCodeWord, ht.Destination)
} else {
commandStr = fmt.Sprintf("/usr/bin/curl -s -w %s:%%{http_code} -L %s", StatusCodeWord, ht.Destination)
}
command := strings.Fields(commandStr)

stdout, stderr, err := td.RunRemote(ht.SourceNs, ht.SourcePod, ht.SourceContainer, command)
if err != nil {
// Error codes from the execution come through err
// Curl 'Connection refused' err code = 7
return RetryRequestResult{
0,
0,
fmt.Errorf("Remote exec err: %v | stderr: %s", err, stderr),
}
}
if len(stderr) > 0 {
// no error from execution and proper exit code, we got some stderr though
td.T.Logf("[warn] Stderr: %v", stderr)
}
split := strings.Split(stdout, "\n")
var fields [][]string
for _, s := range split {
fields = append(fields, strings.Split(s, ":"))
}
rqCount, err := strconv.Atoi(fields[0][1])
if err != nil {
return RetryRequestResult{
0,
0,
fmt.Errorf("Could not read request count as integer: %v", err),
}
}
statusCode, err := strconv.Atoi(fields[1][1])
if err != nil {
return RetryRequestResult{
0,
0,
fmt.Errorf("Could not read status code as integer: %v", err),
}
}

return RetryRequestResult{
rqCount,
statusCode,
nil,
}
}

// TCPRequest runs a synchronous TCP request to run the TCPRequestDef and return a TCPRequestResult
func (td *OsmTestData) TCPRequest(req TCPRequestDef) TCPRequestResult {
var command []string
Expand Down
Loading

0 comments on commit 05fe1ce

Please sign in to comment.