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

Bug fix: don't constantly update APIRule in tests while getting the status #1581

Merged
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
13 changes: 2 additions & 11 deletions tests/integration/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,18 +99,9 @@ func TestV2alpha1(t *testing.T) {
func runTestsuite(t *testing.T, testsuite testcontext.Testsuite) {
opts := createGoDogOpts(t, testsuite.FeaturePath(), testsuite.TestConcurrency())
suite := godog.TestSuite{
Name: testsuite.Name(),
ScenarioInitializer: func() func(*godog.ScenarioContext) {
if testsuite.Name() == "v2alpha1" {
return testsuite.InitScenarios
}
return nil
}(),
Name: testsuite.Name(),
ScenarioInitializer: testsuite.InitScenarios,
TestSuiteInitializer: func(ctx *godog.TestSuiteContext) {
if testsuite.Name() != "v2alpha1" {
testsuite.InitScenarios(ctx.ScenarioContext())
}

ctx.BeforeSuite(func() {
log.Printf("Executing before suite hooks")
for _, hook := range testsuite.BeforeSuiteHooks() {
Expand Down
112 changes: 76 additions & 36 deletions tests/integration/pkg/helpers/api_rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,10 @@ const (
notReconciledCode = ""
)

// RetryableApiRule wraps any function that modifies or creates an APIRule
type RetryableApiRule func(k8sClient dynamic.Interface, resources ...unstructured.Unstructured) (*unstructured.Unstructured, error)

func getAPIRuleStatus(res *unstructured.Unstructured) (string, string, error) {
apiRuleName := res.GetName()
if res.Object == nil || res.Object["apiVersion"] == nil {
return "", "", errors.New("apiVersion not found in the APIRule object")
return "", "", fmt.Errorf("apiVersion not found in the APIRule %s object", apiRuleName)
}

apiVersion := strings.Split(res.Object["apiVersion"].(string), "/")
Expand All @@ -63,47 +61,55 @@ func getAPIRuleStatus(res *unstructured.Unstructured) (string, string, error) {
code = arStatus.Status.State
description = arStatus.Status.Description
default:
return "", "", errors.New("unsupported APIRule version")
return "", "", fmt.Errorf("APIRule %s has unsupported version", apiRuleName)
}

return code, description, nil
}

// ApplyApiRule tries toExecute function and retries with onRetry if APIRule status is in error status or has no status
// code. This function works for both v1beta1 and v2alpha1 versions of APIRule.
func ApplyApiRule(toExecute RetryableApiRule, onRetry RetryableApiRule, k8sClient dynamic.Interface, retryOpts []retry.Option, resources []unstructured.Unstructured) error {
res, err := toExecute(k8sClient, resources...)
// CreateApiRule creates APIRule and waits for its status
// This function works for both v1beta1 and v2alpha1 versions of APIRule.
func CreateApiRule(resourceMgr *resource.Manager, k8sClient dynamic.Interface, retryOpts []retry.Option, apiRuleResource unstructured.Unstructured) error {
if apiRuleResource.GetObjectKind().GroupVersionKind().Kind != "APIRule" {
return fmt.Errorf("object with name %s is not an APIRule unintended usage of the function", apiRuleResource.GetName())
}

resourceSchema, ns, _ := resourceMgr.GetResourceSchemaAndNamespace(apiRuleResource)
apiRuleName := apiRuleResource.GetName()

err := resourceMgr.CreateResource(k8sClient, resourceSchema, ns, apiRuleResource)
if err != nil {
return err
}

if res.GetObjectKind().GroupVersionKind().Kind != "APIRule" {
return errors.New("object is not an APIRule unintended usage of the function")
currentApiRule, err := resourceMgr.GetResource(k8sClient, resourceSchema, ns, apiRuleName)
if err != nil {
return err
}

code, _, err := getAPIRuleStatus(res)
code, _, err := getAPIRuleStatus(currentApiRule)
if err != nil {
return err
}

if code == errorV1beta1 || code == errorV2alpha1 || code == notReconciledCode {
return retry.Do(func() error {
res, err := onRetry(k8sClient, resources...)
currentApiRule, err = resourceMgr.GetResource(k8sClient, resourceSchema, ns, apiRuleName)
if err != nil {
return err
}

code, description, err := getAPIRuleStatus(res)
code, description, err := getAPIRuleStatus(currentApiRule)
if err != nil {
return err
}

switch code {
case notReconciledCode:
return errors.New("apirule not reconciled")
return fmt.Errorf("APIRule %s not reconciled", apiRuleName)
case errorV1beta1, errorV2alpha1:
log.Printf("APIRule status code is '%s' with description '%s'", code, description)
return errors.New("apirule in error state")
log.Printf("APIRule %s status code is '%s' with description '%s'", apiRuleName, code, description)
return fmt.Errorf("APIRule %s in error state", apiRuleName)
default:
return nil
}
Expand All @@ -112,22 +118,35 @@ func ApplyApiRule(toExecute RetryableApiRule, onRetry RetryableApiRule, k8sClien
return nil
}

func ApplyApiRuleV2Alpha1(toExecute RetryableApiRule, onRetry RetryableApiRule, k8sClient dynamic.Interface, retryOpts []retry.Option, resources []unstructured.Unstructured) error {
res, err := toExecute(k8sClient, resources...)
func CreateApiRuleV2Alpha1(resourceMgr *resource.Manager, k8sClient dynamic.Interface, retryOpts []retry.Option, apiRuleResource unstructured.Unstructured) error {
if apiRuleResource.GetObjectKind().GroupVersionKind().Kind != "APIRule" {
return fmt.Errorf("object with name %s is not an APIRule unintended usage of the function", apiRuleResource.GetName())
}

resourceSchema, ns, _ := resourceMgr.GetResourceSchemaAndNamespace(apiRuleResource)
apiRuleName := apiRuleResource.GetName()

err := resourceMgr.CreateResource(k8sClient, resourceSchema, ns, apiRuleResource)
if err != nil {
return err
}
apiStatus, err := GetAPIRuleStatusV2Alpha1(res)

currentApiRule, err := resourceMgr.GetResource(k8sClient, resourceSchema, ns, apiRuleName)
if err != nil {
return err
}
apiStatus, err := GetAPIRuleStatusV2Alpha1(currentApiRule)
if err != nil {
return err
}
if apiStatus.Status.State != "Ready" {
return retry.Do(func() error {
res, err := onRetry(k8sClient, resources...)
currentApiRule, err = resourceMgr.GetResource(k8sClient, resourceSchema, ns, apiRuleName)
if err != nil {
return err
}
js, err := json.Marshal(res)

js, err := json.Marshal(currentApiRule)
if err != nil {
return err
}
Expand All @@ -136,31 +155,44 @@ func ApplyApiRuleV2Alpha1(toExecute RetryableApiRule, onRetry RetryableApiRule,
return err
}
if apiStatus.Status.State != "Ready" {
log.Println("APIRule status not Ready: " + apiStatus.Status.Description)
return errors.New("APIRule status not Ready: " + apiStatus.Status.Description)
log.Printf("APIRule %s status not Ready, but is: %s\n", apiRuleName, apiStatus.Status.Description)
return fmt.Errorf("APIRule %s status not Ready, but is: %s", apiRuleName, apiStatus.Status.Description)
}
return nil
}, retryOpts...)
}
return nil
}

func ApplyApiRuleV2Alpha1ExpectError(toExecute RetryableApiRule, onRetry RetryableApiRule, k8sClient dynamic.Interface, retryOpts []retry.Option, resources []unstructured.Unstructured, errorMessage string) error {
res, err := toExecute(k8sClient, resources...)
func CreateApiRuleV2Alpha1ExpectError(resourceMgr *resource.Manager, k8sClient dynamic.Interface, retryOpts []retry.Option, apiRuleResource unstructured.Unstructured, errorMessage string) error {
if apiRuleResource.GetObjectKind().GroupVersionKind().Kind != "APIRule" {
return errors.New("object is not an APIRule unintended usage of the function")
}

resourceSchema, ns, _ := resourceMgr.GetResourceSchemaAndNamespace(apiRuleResource)
apiRuleName := apiRuleResource.GetName()

err := resourceMgr.CreateResource(k8sClient, resourceSchema, ns, apiRuleResource)
if err != nil {
return err
}

currentApiRule, err := resourceMgr.GetResource(k8sClient, resourceSchema, ns, apiRuleName)
if err != nil {
return err
}
apiStatus, err := GetAPIRuleStatusV2Alpha1(res)

apiStatus, err := GetAPIRuleStatusV2Alpha1(currentApiRule)
if err != nil {
return err
}
if apiStatus.Status.State != "Error" {
return retry.Do(func() error {
res, err := onRetry(k8sClient, resources...)
currentApiRule, err = resourceMgr.GetResource(k8sClient, resourceSchema, ns, apiRuleName)
if err != nil {
return err
}
js, err := json.Marshal(res)
js, err := json.Marshal(currentApiRule)
if err != nil {
return err
}
Expand All @@ -169,28 +201,36 @@ func ApplyApiRuleV2Alpha1ExpectError(toExecute RetryableApiRule, onRetry Retryab
return err
}
if apiStatus.Status.State != "Error" {
log.Printf("expected but APIRule status to be Error, got %s with desc: %s", apiStatus.Status.State, apiStatus.Status.Description)
return fmt.Errorf("expected but APIRule status to be Error, got %s with desc: %s", apiStatus.Status.State, apiStatus.Status.Description)
log.Printf("expected APIRule %s status to be Error, got %s with desc: %s", apiRuleName, apiStatus.Status.State, apiStatus.Status.Description)
return fmt.Errorf("expected APIRule %s status to be Error, got %s with desc: %s", apiRuleName, apiStatus.Status.State, apiStatus.Status.Description)
}
if !strings.Contains(apiStatus.Status.Description, errorMessage) {
log.Printf("expected error description of the APIRule to be %s, got %s", errorMessage, apiStatus.Status.Description)
return fmt.Errorf("expected error description of the APIRule to be %s, got %s", errorMessage, apiStatus.Status.Description)
log.Printf("expected error description of the APIRule %s to be %s, got %s", apiRuleName, errorMessage, apiStatus.Status.Description)
return fmt.Errorf("expected error description of the APIRule %s to be %s, got %s", apiRuleName, errorMessage, apiStatus.Status.Description)
}
return nil
}, retryOpts...)
}
return nil
}

func UpdateApiRule(resourceManager *resource.Manager, k8sClient dynamic.Interface, retryOpts []retry.Option, resources []unstructured.Unstructured) error {
func UpdateApiRule(resourceMgr *resource.Manager, k8sClient dynamic.Interface, retryOpts []retry.Option, apiRuleResource unstructured.Unstructured) error {
resourceSchema, ns, _ := resourceMgr.GetResourceSchemaAndNamespace(apiRuleResource)
apiRuleName := apiRuleResource.GetName()

status := ApiRuleStatusV1beta1{}

res, err := resourceManager.UpdateResources(k8sClient, resources...)
err := resourceMgr.UpdateResource(k8sClient, resourceSchema, ns, apiRuleName, apiRuleResource)
if err != nil {
return err
}

currentApiRule, err := resourceMgr.GetResource(k8sClient, resourceSchema, ns, apiRuleName)
if err != nil {
return err
}

js, err := json.Marshal(res)
js, err := json.Marshal(currentApiRule)
if err != nil {
return err
}
Expand Down
22 changes: 20 additions & 2 deletions tests/integration/pkg/helpers/loadbalancer.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package helpers

import (
"context"
"fmt"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"log"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"net"
)

Expand All @@ -14,7 +17,6 @@ func GetLoadBalancerIp(loadBalancerIngress map[string]interface{}) (net.IP, erro
if err == nil {
return loadBalancerIP, nil
} else {
log.Printf("Falling back to reading DNS based load balancer IP, because of: %s\n", err)
return getDnsBasedLoadBalancerIp(loadBalancerIngress)
}
}
Expand Down Expand Up @@ -48,3 +50,19 @@ func getDnsBasedLoadBalancerIp(lbIngress map[string]interface{}) (net.IP, error)

return ips[0], nil
}

func GetLoadBalancerIngress(k8sClient dynamic.Interface, svcName string, svcNamespace string) (map[string]interface{}, error) {
res := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "services"}
svc, err := k8sClient.Resource(res).Namespace(svcNamespace).Get(context.Background(), svcName, v1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("service %s was not found in namespace %s: %w", svcName, svcNamespace, err)
}

ingress, found, err := unstructured.NestedSlice(svc.Object, "status", "loadBalancer", "ingress")
if err != nil || !found {
return nil, fmt.Errorf("could not get load balancer status from the service %s: %w", svcName, err)
}
loadBalancerIngress, _ := ingress[0].(map[string]interface{})

return loadBalancerIngress, nil
}
6 changes: 3 additions & 3 deletions tests/integration/pkg/helpers/response_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func (s *StatusPredicate) Assert(response http.Response) (bool, string) {
return true, ""
}

return false, fmt.Sprintf("Status code %d is not between %d and %d", response.StatusCode, s.LowerStatusBound, s.UpperStatusBound)
return false, fmt.Sprintf("Status code %d on url %s is not between %d and %d", response.StatusCode, response.Request.URL, s.LowerStatusBound, s.UpperStatusBound)
}

// BodyContainsPredicate is a struct representing desired HTTP response body containing expected strings
Expand All @@ -35,7 +35,7 @@ type BodyContainsPredicate struct {
func (s *BodyContainsPredicate) Assert(response http.Response) (bool, string) {
bodyBytes, err := io.ReadAll(response.Body)
if err != nil {
return false, "Failed to read response body"
return false, fmt.Sprintf("Failed to read response body from url %s", response.Request.URL)
}

bodyString := string(bodyBytes)
Expand All @@ -50,7 +50,7 @@ func (s *BodyContainsPredicate) Assert(response http.Response) (bool, string) {
if len(notContained) == 0 {
return true, ""
} else {
return false, fmt.Sprintf("Body didn't contain '%s'", strings.Join(notContained, "', '"))
return false, fmt.Sprintf("Body got from url %s didn't contain '%s'", response.Request.URL, strings.Join(notContained, "', '"))
}

}
12 changes: 12 additions & 0 deletions tests/integration/pkg/manifestprocessor/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@ func ParseFromFileWithTemplate(fileName string, directory string, templateData i
return ParseWithTemplate(rawData, templateData)
}

func ParseSingleEntryFromFileWithTemplate(fileName string, directory string, templateData interface{}) (unstructured.Unstructured, error) {
result, err := ParseFromFileWithTemplate(fileName, directory, templateData)
if err != nil {
return unstructured.Unstructured{}, err
}

if len(result) > 1 {
return unstructured.Unstructured{}, fmt.Errorf("Template in file %s contains more than one entry", fileName)
}
return result[0], nil
}

func ParseWithTemplate(manifest []byte, templateData interface{}) ([]unstructured.Unstructured, error) {
man, err := parseTemplateWithData(string(manifest), templateData)
if err != nil {
Expand Down
37 changes: 37 additions & 0 deletions tests/integration/pkg/network/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ package network
import (
"context"
_ "embed"
"fmt"
"github.com/avast/retry-go/v4"
k8sclient "github.com/kyma-project/api-gateway/tests/integration/pkg/client"
"github.com/kyma-project/api-gateway/tests/integration/pkg/helpers"
"github.com/kyma-project/api-gateway/tests/integration/pkg/manifestprocessor"
"github.com/kyma-project/api-gateway/tests/integration/pkg/resource"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/client-go/dynamic"
"log"
"net"
"sigs.k8s.io/controller-runtime/pkg/client"
"time"
)
Expand Down Expand Up @@ -66,3 +70,36 @@ func restartCoreDnsPods() error {

return c.Patch(context.Background(), dep, patch)
}

// function waits until the given domain points to the given IP
// the domain must be a wildcard one because the function probes the <randomHost>.<domain>
func WaitUntilDNSReady(domain string, expectedIP net.IP, retryOpts []retry.Option) error {
return retry.Do(func() error {
ready, err := isDNSReady(domain, expectedIP)
if err != nil {
return fmt.Errorf("error while checking if domain %s is ready: %w", domain, err)
}
if !ready {
return fmt.Errorf("domain %s is not ready yet", domain)
}
return nil
}, retryOpts...)
}

// function checks whether the given domain points to the given IP
// the domain must be a wildcard one because the function probes the <randomHost>.<domain>
func isDNSReady(domain string, expectedIP net.IP) (bool, error) {
randomHost := helpers.GenerateRandomString(3)
ips, err := net.LookupIP(fmt.Sprintf("%s.%s", randomHost, domain))
if err != nil {
return false, nil
}
if len(ips) != 0 {
for _, ip := range ips {
if ip.Equal(expectedIP) {
return true, nil
}
}
}
return false, err
}
Loading
Loading