client/pkg/serving/v1beta1/client_test.go

328 lines
13 KiB
Go

// Copyright © 2021 The Knative 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 v1beta1
import (
"context"
"fmt"
"testing"
eventingv1 "knative.dev/eventing/pkg/apis/eventing/v1"
"gotest.tools/v3/assert"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
clienttesting "k8s.io/client-go/testing"
"knative.dev/client/pkg/util"
duckv1 "knative.dev/pkg/apis/duck/v1"
servingv1beta1 "knative.dev/serving/pkg/apis/serving/v1beta1"
"knative.dev/serving/pkg/client/clientset/versioned/scheme"
servingv1beta1fake "knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1beta1/fake"
)
const (
testNamespace = "test-ns"
domainMappingResource = "domainmappings"
)
func setup() (serving servingv1beta1fake.FakeServingV1beta1, client KnServingClient) {
serving = servingv1beta1fake.FakeServingV1beta1{Fake: &clienttesting.Fake{}}
client = NewKnServingClient(&serving, testNamespace)
return
}
func TestGetDomainMapping(t *testing.T) {
serving, client := setup()
serviceName := "foo"
domainName := "foo.bar"
serving.AddReactor("get", domainMappingResource,
func(a clienttesting.Action) (bool, runtime.Object, error) {
dm := createDomainMapping(domainName, createServiceRef(serviceName, testNamespace))
name := a.(clienttesting.GetAction).GetName()
assert.Assert(t, name != "")
assert.Equal(t, testNamespace, a.GetNamespace())
if name == domainName {
return true, dm, nil
}
return true, nil, errors.NewNotFound(servingv1beta1.Resource("dm"), name)
})
t.Run("get domain mapping by name returns object", func(t *testing.T) {
domainMapping, err := client.GetDomainMapping(context.Background(), domainName)
assert.NilError(t, err)
assert.Equal(t, domainName, domainMapping.Name, "domain mapping name should be equal")
validateGroupVersionKind(t, domainMapping)
})
t.Run("get non-existing domain mapping by name returns error", func(t *testing.T) {
nonExistingName := "does-not-exist"
service, err := client.GetDomainMapping(context.Background(), nonExistingName)
assert.Assert(t, service == nil, "no domain mapping should be returned")
assert.ErrorContains(t, err, "not found")
assert.ErrorContains(t, err, nonExistingName)
})
}
func TestCreateDomainMapping(t *testing.T) {
serving, client := setup()
serviceName := "foo"
domainName := "foo.bar"
secretName := "tls-secret"
domainMapping := createDomainMapping(domainName, createServiceRef(serviceName, testNamespace))
serving.AddReactor("create", domainMappingResource,
func(a clienttesting.Action) (bool, runtime.Object, error) {
assert.Equal(t, testNamespace, a.GetNamespace())
name := a.(clienttesting.CreateAction).GetObject().(metav1.Object).GetName()
if name == domainMapping.Name {
domainMapping.Generation = 2
return true, domainMapping, nil
}
return true, nil, fmt.Errorf("error while creating service %s", name)
})
t.Run("create domain mapping without error creates a new object", func(t *testing.T) {
err := client.CreateDomainMapping(context.Background(), domainMapping)
assert.NilError(t, err)
assert.Equal(t, domainMapping.Generation, int64(2))
validateGroupVersionKind(t, domainMapping)
})
t.Run("create domain mapping with tls secret without error", func(t *testing.T) {
err := client.CreateDomainMapping(context.Background(), createDomainMappingWithTls(domainName, createServiceRef(serviceName, testNamespace), secretName))
assert.NilError(t, err)
})
t.Run("create domain mapping without tls secret without error", func(t *testing.T) {
err := client.CreateDomainMapping(context.Background(), createDomainMappingWithTls(domainName, createServiceRef(serviceName, testNamespace), ""))
assert.NilError(t, err)
})
t.Run("create domain mapping with an error returns an error object", func(t *testing.T) {
err := client.CreateDomainMapping(context.Background(), createDomainMapping("unknown", createServiceRef(serviceName, testNamespace)))
assert.ErrorContains(t, err, "unknown")
})
}
func TestUpdateDomainMapping(t *testing.T) {
serving, client := setup()
serviceName := "foo"
domainName := "foo.bar"
domainMappingUpdate := createDomainMapping(domainName, createServiceRef(serviceName, testNamespace))
domainMappingUpdate.ObjectMeta.Generation = 2
serving.AddReactor("update", domainMappingResource,
func(a clienttesting.Action) (bool, runtime.Object, error) {
assert.Equal(t, testNamespace, a.GetNamespace())
name := a.(clienttesting.UpdateAction).GetObject().(metav1.Object).GetName()
if name == domainMappingUpdate.Name {
dmResult := createDomainMapping(domainName, createServiceRef(serviceName, testNamespace))
dmResult.Generation = 3
return true, dmResult, nil
}
return true, nil, fmt.Errorf("error while updating service %s", name)
})
t.Run("update domain mapping without error", func(t *testing.T) {
err := client.UpdateDomainMapping(context.Background(), domainMappingUpdate)
assert.NilError(t, err)
validateGroupVersionKind(t, domainMappingUpdate)
})
t.Run("update domain mapping with error", func(t *testing.T) {
err := client.UpdateDomainMapping(context.Background(), createDomainMapping("unknown", createServiceRef(serviceName, testNamespace)))
assert.ErrorContains(t, err, "unknown")
})
}
func TestUpdateDomainMappingWithRetry(t *testing.T) {
serving, client := setup()
serviceName := "foo"
domainName := "foo.bar"
var attemptCount, maxAttempts = 0, 5
serving.AddReactor("get", "domainmappings",
func(a clienttesting.Action) (bool, runtime.Object, error) {
name := a.(clienttesting.GetAction).GetName()
if name == "deletedDomain" {
domain := createDomainMapping(name, createServiceRef(serviceName, testNamespace))
now := metav1.Now()
domain.DeletionTimestamp = &now
return true, domain, nil
}
if name == "getErrorDomain" {
return true, nil, errors.NewInternalError(fmt.Errorf("mock internal error"))
}
return true, createDomainMapping(name, createServiceRef(serviceName, testNamespace)), nil
})
serving.AddReactor("update", "domainmappings",
func(a clienttesting.Action) (bool, runtime.Object, error) {
createDomainMapping := a.(clienttesting.UpdateAction).GetObject()
name := createDomainMapping.(metav1.Object).GetName()
if name == domainName && attemptCount > 0 {
attemptCount--
return true, nil, errors.NewConflict(eventingv1.Resource(domainMappingResource), "errorDomain", fmt.Errorf("error updating because of conflict"))
}
if name == "errorDomain" {
return true, nil, errors.NewInternalError(fmt.Errorf("mock internal error"))
}
return true, createDomainMapping, nil
})
t.Run("Update domain mapping successfully without any retries", func(t *testing.T) {
err := client.UpdateDomainMappingWithRetry(context.Background(), domainName, func(domain *servingv1beta1.DomainMapping) (*servingv1beta1.DomainMapping, error) {
return domain, nil
}, maxAttempts)
assert.NilError(t, err, "No retries required as no conflict error occurred")
})
t.Run("Update domain mapping with retry after max retries", func(t *testing.T) {
attemptCount = maxAttempts - 1
err := client.UpdateDomainMappingWithRetry(context.Background(), domainName, func(domain *servingv1beta1.DomainMapping) (*servingv1beta1.DomainMapping, error) {
return domain, nil
}, maxAttempts)
assert.NilError(t, err, "Update retried %d times and succeeded", maxAttempts)
assert.Equal(t, attemptCount, 0)
})
t.Run("Update domain mapping with retry and fail with conflict after exhausting max retries", func(t *testing.T) {
attemptCount = maxAttempts
err := client.UpdateDomainMappingWithRetry(context.Background(), domainName, func(domain *servingv1beta1.DomainMapping) (*servingv1beta1.DomainMapping, error) {
return domain, nil
}, maxAttempts)
assert.ErrorType(t, err, errors.IsConflict, "Update retried %d times and failed", maxAttempts)
assert.Equal(t, attemptCount, 0)
})
t.Run("Update domain mapping with retry and fail with conflict after exhausting max retries", func(t *testing.T) {
attemptCount = maxAttempts
err := client.UpdateDomainMappingWithRetry(context.Background(), domainName, func(domain *servingv1beta1.DomainMapping) (*servingv1beta1.DomainMapping, error) {
return domain, nil
}, maxAttempts)
assert.ErrorType(t, err, errors.IsConflict, "Update retried %d times and failed", maxAttempts)
assert.Equal(t, attemptCount, 0)
})
t.Run("Update domain mapping with retry fails with a non conflict error", func(t *testing.T) {
err := client.UpdateDomainMappingWithRetry(context.Background(), "errorDomain", func(domain *servingv1beta1.DomainMapping) (*servingv1beta1.DomainMapping, error) {
return domain, nil
}, maxAttempts)
assert.ErrorType(t, err, errors.IsInternalError)
})
t.Run("Update domain mapping with retry fails with resource already deleted error", func(t *testing.T) {
err := client.UpdateDomainMappingWithRetry(context.Background(), "deletedDomain", func(domain *servingv1beta1.DomainMapping) (*servingv1beta1.DomainMapping, error) {
return domain, nil
}, maxAttempts)
assert.ErrorContains(t, err, "marked for deletion")
})
t.Run("Update domain mapping with retry fails with error from updateFunc", func(t *testing.T) {
err := client.UpdateDomainMappingWithRetry(context.Background(), domainName, func(domain *servingv1beta1.DomainMapping) (*servingv1beta1.DomainMapping, error) {
return domain, fmt.Errorf("error updating object")
}, maxAttempts)
assert.ErrorContains(t, err, "error updating object")
})
t.Run("Update domain mapping with retry fails with error from GetDomainMapping", func(t *testing.T) {
err := client.UpdateDomainMappingWithRetry(context.Background(), "getErrorDomain", func(domain *servingv1beta1.DomainMapping) (*servingv1beta1.DomainMapping, error) {
return domain, nil
}, maxAttempts)
assert.ErrorType(t, err, errors.IsInternalError)
})
}
func TestDeleteDomainMapping(t *testing.T) {
serving, client := setup()
domainName := "foo.bar"
serving.AddReactor("delete", domainMappingResource,
func(a clienttesting.Action) (bool, runtime.Object, error) {
name := a.(clienttesting.DeleteAction).GetName()
assert.Assert(t, name != "")
assert.Equal(t, testNamespace, a.GetNamespace())
if name == domainName {
return true, nil, nil
}
return true, nil, errors.NewNotFound(servingv1beta1.Resource(domainMappingResource), name)
})
t.Run("delete domain mapping returns no error", func(t *testing.T) {
err := client.DeleteDomainMapping(context.Background(), domainName)
assert.NilError(t, err)
})
t.Run("delete non-existing domain mapping returns error", func(t *testing.T) {
nonExistingName := "does-not-exist"
err := client.DeleteDomainMapping(context.Background(), nonExistingName)
assert.ErrorContains(t, err, "not found")
assert.ErrorContains(t, err, nonExistingName)
assert.ErrorType(t, err, &errors.StatusError{})
})
}
func TestListDomainMappings(t *testing.T) {
serving, client := setup()
t.Run("list domain mappings returns a list of objects", func(t *testing.T) {
dm1 := createDomainMapping("dm-1", createServiceRef("svc1", testNamespace))
dm2 := createDomainMapping("dm-2", createServiceRef("svc2", testNamespace))
dm3 := createDomainMapping("dm-3", createServiceRef("svc3", testNamespace))
serving.AddReactor("list", domainMappingResource,
func(a clienttesting.Action) (bool, runtime.Object, error) {
assert.Equal(t, testNamespace, a.GetNamespace())
return true, &servingv1beta1.DomainMappingList{Items: []servingv1beta1.DomainMapping{*dm1, *dm2, *dm3}}, nil
})
listServices, err := client.ListDomainMappings(context.Background())
assert.NilError(t, err)
assert.Assert(t, len(listServices.Items) == 3)
assert.Equal(t, listServices.Items[0].Name, "dm-1")
assert.Equal(t, listServices.Items[1].Name, "dm-2")
assert.Equal(t, listServices.Items[2].Name, "dm-3")
validateGroupVersionKind(t, listServices)
validateGroupVersionKind(t, &listServices.Items[0])
validateGroupVersionKind(t, &listServices.Items[1])
validateGroupVersionKind(t, &listServices.Items[2])
})
}
func validateGroupVersionKind(t *testing.T, obj runtime.Object) {
gvkExpected, err := util.GetGroupVersionKind(obj, servingv1beta1.SchemeGroupVersion, scheme.Scheme)
assert.NilError(t, err)
gvkGiven := obj.GetObjectKind().GroupVersionKind()
fmt.Println(gvkGiven.String())
assert.Equal(t, *gvkExpected, gvkGiven, "GVK should be the same")
}
func createDomainMapping(name string, ref duckv1.KReference) *servingv1beta1.DomainMapping {
return NewDomainMappingBuilder(name).Namespace("default").Reference(ref).Build()
}
func createDomainMappingWithTls(name string, ref duckv1.KReference, tls string) *servingv1beta1.DomainMapping {
return NewDomainMappingBuilder(name).Namespace("default").Reference(ref).TLS(tls).Build()
}
func createServiceRef(service, namespace string) duckv1.KReference {
return duckv1.KReference{Name: service,
Kind: "Service",
APIVersion: "serving.knative.dev/v1",
Namespace: namespace,
}
}