Using matrix strategy for e2e on multiple k8s versions (#111)

* Using matrix strategy for e2e on multiple k8s versions

* EndpointSlice version support according to Kubernetes release

* Utility helper for testing various Kubernetes versions
This commit is contained in:
Dario Tranchitella
2020-10-29 09:39:22 +01:00
committed by GitHub
parent a7f7c00558
commit ee6e3aa0df
7 changed files with 127 additions and 92 deletions
+7 -4
View File
@@ -28,6 +28,9 @@ jobs:
args: --timeout 2m
kind:
name: e2e
strategy:
matrix:
k8s-version: ['v1.16.15', 'v1.17.11', 'v1.18.8', 'v1.19.1']
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@master
@@ -40,10 +43,10 @@ jobs:
~/go/pkg/mod
/var/lib/docker
/home/runner/work/capsule/capsule
key: ${{ runner.os }}-build-${{ env.cache-name }}
key: ${{ matrix.k8s-version }}-build-${{ env.cache-name }}
restore-keys: |
${{ runner.os }}-build-
${{ runner.os }}-
${{ matrix.k8s-version }}-build-
${{ matrix.k8s-version }}-
- name: Removing kustomize
run: sudo snap remove kustomize && sudo rm -rf $(which kustomize)
- name: Installing Ginkgo
@@ -55,4 +58,4 @@ jobs:
with:
skipClusterCreation: true
- name: e2e testing
run: make e2e
run: make e2e/${{ matrix.k8s-version }}
+2 -2
View File
@@ -135,8 +135,8 @@ golint:
# Running e2e tests in a KinD instance
.PHONY: e2e
e2e:
kind create cluster --name capsule --image=kindest/node:v1.18.0
e2e/%:
kind create cluster --name capsule --image=kindest/node:$*
make docker-build
kind load docker-image --nodes capsule-control-plane --name capsule quay.io/clastix/capsule:latest
make deploy
+31 -44
View File
@@ -17,16 +17,14 @@ package e2e
import (
"context"
"strconv"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
v1beta12 "k8s.io/api/extensions/v1beta1"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes"
"k8s.io/utils/pointer"
"github.com/clastix/capsule/api/v1alpha1"
@@ -75,12 +73,12 @@ var _ = Describe("when Tenant handles Ingress classes", func() {
By("non-specifying the class", func() {
Eventually(func() (err error) {
i := &v1beta12.Ingress{
i := &extensionsv1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "denied-ingress",
},
Spec: v1beta12.IngressSpec{
Backend: &v1beta12.IngressBackend{
Spec: extensionsv1beta1.IngressSpec{
Backend: &extensionsv1beta1.IngressBackend{
ServiceName: "foo",
ServicePort: intstr.FromInt(8080),
},
@@ -92,15 +90,15 @@ var _ = Describe("when Tenant handles Ingress classes", func() {
})
By("using a forbidden class as Annotation", func() {
Eventually(func() (err error) {
i := &v1beta12.Ingress{
i := &extensionsv1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "denied-ingress",
Annotations: map[string]string{
"kubernetes.io/ingress.class": "the-worst-ingress-available",
},
},
Spec: v1beta12.IngressSpec{
Backend: &v1beta12.IngressBackend{
Spec: extensionsv1beta1.IngressSpec{
Backend: &extensionsv1beta1.IngressBackend{
ServiceName: "foo",
ServicePort: intstr.FromInt(8080),
},
@@ -112,13 +110,13 @@ var _ = Describe("when Tenant handles Ingress classes", func() {
})
By("specifying a forbidden class", func() {
Eventually(func() (err error) {
i := &v1beta12.Ingress{
i := &extensionsv1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "denied-ingress",
},
Spec: v1beta12.IngressSpec{
Spec: extensionsv1beta1.IngressSpec{
IngressClassName: pointer.StringPtr("the-worst-ingress-available"),
Backend: &v1beta12.IngressBackend{
Backend: &extensionsv1beta1.IngressBackend{
ServiceName: "foo",
ServicePort: intstr.FromInt(8080),
},
@@ -138,15 +136,15 @@ var _ = Describe("when Tenant handles Ingress classes", func() {
for _, c := range tnt.Spec.IngressClasses.Allowed {
Eventually(func() (err error) {
i := &v1beta12.Ingress{
i := &extensionsv1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: c,
Annotations: map[string]string{
"kubernetes.io/ingress.class": c,
},
},
Spec: v1beta12.IngressSpec{
Backend: &v1beta12.IngressBackend{
Spec: extensionsv1beta1.IngressSpec{
Backend: &extensionsv1beta1.IngressBackend{
ServiceName: "foo",
ServicePort: intstr.FromInt(8080),
},
@@ -161,16 +159,9 @@ var _ = Describe("when Tenant handles Ingress classes", func() {
ns := NewNamespace("ingress-class-allowed-annotation")
cs := ownerClient(tnt)
cs, err := kubernetes.NewForConfig(cfg)
Expect(err).ToNot(HaveOccurred())
v, err := cs.Discovery().ServerVersion()
Expect(err).ToNot(HaveOccurred())
major, err := strconv.Atoi(v.Major)
Expect(err).ToNot(HaveOccurred())
minor, err := strconv.Atoi(v.Minor)
Expect(err).ToNot(HaveOccurred())
if major == 1 && minor < 18 {
Skip("Running test on Kubernetes " + v.String() + ", doesn't provide .spec.ingressClassName")
maj, min, v := GetKubernetesSemVer()
if maj == 1 && min < 18 {
Skip("Running test on Kubernetes " + v + ", doesn't provide .spec.ingressClassName")
}
NamespaceCreationShouldSucceed(ns, tnt, defaultTimeoutInterval)
@@ -178,13 +169,13 @@ var _ = Describe("when Tenant handles Ingress classes", func() {
for _, c := range tnt.Spec.IngressClasses.Allowed {
Eventually(func() (err error) {
i := &v1beta12.Ingress{
i := &extensionsv1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: c,
},
Spec: v1beta12.IngressSpec{
Spec: extensionsv1beta1.IngressSpec{
IngressClassName: &c,
Backend: &v1beta12.IngressBackend{
Backend: &extensionsv1beta1.IngressBackend{
ServiceName: "foo",
ServicePort: intstr.FromInt(8080),
},
@@ -204,15 +195,15 @@ var _ = Describe("when Tenant handles Ingress classes", func() {
NamespaceShouldBeManagedByTenant(ns, tnt, defaultTimeoutInterval)
Eventually(func() (err error) {
i := &v1beta12.Ingress{
i := &extensionsv1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: ingressClass,
Annotations: map[string]string{
"kubernetes.io/ingress.class": ingressClass,
},
},
Spec: v1beta12.IngressSpec{
Backend: &v1beta12.IngressBackend{
Spec: extensionsv1beta1.IngressSpec{
Backend: &extensionsv1beta1.IngressBackend{
ServiceName: "foo",
ServicePort: intstr.FromInt(8080),
},
@@ -227,29 +218,25 @@ var _ = Describe("when Tenant handles Ingress classes", func() {
cs := ownerClient(tnt)
ingressClass := "oil-haproxy"
cs, err := kubernetes.NewForConfig(cfg)
Expect(err).ToNot(HaveOccurred())
v, err := cs.Discovery().ServerVersion()
Expect(err).ToNot(HaveOccurred())
major, err := strconv.Atoi(v.Major)
Expect(err).ToNot(HaveOccurred())
minor, err := strconv.Atoi(v.Minor)
Expect(err).ToNot(HaveOccurred())
if major == 1 && minor < 18 {
Skip("Running test ont Kubernetes " + v.String() + ", doesn't provide .spec.ingressClassName")
maj, min, v := GetKubernetesSemVer()
if maj == 1 && min < 18 {
Skip("Running test on Kubernetes " + v + ", doesn't provide .spec.ingressClassName")
}
if maj == 1 && min < 18 {
Skip("Running test ont Kubernetes " + v + ", doesn't provide .spec.ingressClassName")
}
NamespaceCreationShouldSucceed(ns, tnt, defaultTimeoutInterval)
NamespaceShouldBeManagedByTenant(ns, tnt, defaultTimeoutInterval)
Eventually(func() (err error) {
i := &v1beta12.Ingress{
i := &extensionsv1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: ingressClass,
},
Spec: v1beta12.IngressSpec{
Spec: extensionsv1beta1.IngressSpec{
IngressClassName: &ingressClass,
Backend: &v1beta12.IngressBackend{
Backend: &extensionsv1beta1.IngressBackend{
ServiceName: "foo",
ServicePort: intstr.FromInt(8080),
},
+42 -34
View File
@@ -18,15 +18,15 @@ package e2e
import (
"context"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
discoveryv1beta1 "k8s.io/api/discovery/v1beta1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/clastix/capsule/api/v1alpha1"
)
@@ -132,24 +132,8 @@ var _ = Describe("creating a Service/Endpoint/EndpointSlice for a Tenant with ad
},
}
epsName := "foo"
epsPort := int32(9999)
eps := &discoveryv1beta1.EndpointSlice{
ObjectMeta: meta,
AddressType: discoveryv1beta1.AddressTypeIPv4,
Endpoints: []discoveryv1beta1.Endpoint{
{
Addresses: []string{"10.10.1.1"},
},
},
Ports: []discoveryv1beta1.EndpointPort{
{
Name: &epsName,
Port: &epsPort,
},
},
}
cs := ownerClient(tnt)
Eventually(func() (err error) {
_, err = cs.CoreV1().Services(ns.GetName()).Create(context.TODO(), svc, metav1.CreateOptions{})
return
@@ -158,13 +142,9 @@ var _ = Describe("creating a Service/Endpoint/EndpointSlice for a Tenant with ad
_, err = cs.CoreV1().Endpoints(ns.GetName()).Create(context.TODO(), ep, metav1.CreateOptions{})
return
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
Eventually(func() (err error) {
_, err = cs.DiscoveryV1beta1().EndpointSlices(ns.GetName()).Create(context.TODO(), eps, metav1.CreateOptions{})
return
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: svc.GetName(), Namespace: ns.GetName()}, svc)).Should(Succeed())
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ep.GetName(), Namespace: ns.GetName()}, ep)).Should(Succeed())
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: eps.GetName(), Namespace: ns.GetName()}, eps)).Should(Succeed())
By("checking additional labels on service", func() {
for _, l := range tnt.Spec.ServicesMetadata.AdditionalLabels {
Expect(svc.Labels).Should(ContainElement(l))
@@ -185,15 +165,43 @@ var _ = Describe("creating a Service/Endpoint/EndpointSlice for a Tenant with ad
Expect(ep.Annotations).Should(ContainElement(a))
}
})
By("checking additional labels on endpointslices", func() {
for _, l := range tnt.Spec.ServicesMetadata.AdditionalLabels {
Expect(eps.Labels).Should(ContainElement(l))
epsName := "foo"
epsPort := int32(9999)
var eps client.Object
maj, min, _ := GetKubernetesSemVer()
if maj == 1 && min > 16 {
eps = &discoveryv1beta1.EndpointSlice{
ObjectMeta: meta,
AddressType: discoveryv1beta1.AddressTypeIPv4,
Endpoints: []discoveryv1beta1.Endpoint{
{
Addresses: []string{"10.10.1.1"},
},
},
Ports: []discoveryv1beta1.EndpointPort{
{
Name: &epsName,
Port: &epsPort,
},
},
}
})
By("checking additional annotations endpointslices", func() {
for _, a := range tnt.Spec.NamespacesMetadata.AdditionalAnnotations {
Expect(eps.Annotations).Should(ContainElement(a))
}
})
Eventually(func() (err error) {
_, err = cs.DiscoveryV1beta1().EndpointSlices(ns.GetName()).Create(context.TODO(), eps.(*discoveryv1beta1.EndpointSlice), metav1.CreateOptions{})
return
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: eps.GetName(), Namespace: ns.GetName()}, eps)).Should(Succeed())
By("checking additional annotations endpointslices", func() {
for _, a := range tnt.Spec.NamespacesMetadata.AdditionalAnnotations {
Expect(eps.GetAnnotations()).Should(ContainElement(a))
}
})
By("checking additional labels on endpointslices", func() {
for _, l := range tnt.Spec.ServicesMetadata.AdditionalLabels {
Expect(eps.GetLabels()).Should(ContainElement(l))
}
})
}
})
})
+22
View File
@@ -20,6 +20,7 @@ package e2e
import (
"context"
"strconv"
"time"
. "github.com/onsi/gomega"
@@ -28,6 +29,8 @@ import (
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/clastix/capsule/api/v1alpha1"
@@ -127,3 +130,22 @@ func GroupShouldBeUsedInTenantRoleBinding(ns *corev1.Namespace, t *v1alpha1.Tena
}
}
func GetKubernetesSemVer() (major, minor int, ver string) {
var v *version.Info
var err error
var cs kubernetes.Interface
cs, err = kubernetes.NewForConfig(cfg)
Expect(err).ToNot(HaveOccurred())
v, err = cs.Discovery().ServerVersion()
Expect(err).ToNot(HaveOccurred())
major, err = strconv.Atoi(v.Major)
Expect(err).ToNot(HaveOccurred())
minor, err = strconv.Atoi(v.Minor)
Expect(err).ToNot(HaveOccurred())
ver = v.String()
return
}
+21 -6
View File
@@ -22,14 +22,18 @@ import (
"net/http"
"strings"
"github.com/clastix/capsule/api/v1alpha1"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
"gomodules.xyz/jsonpatch/v2"
corev1 "k8s.io/api/core/v1"
discoveryv1alpha1 "k8s.io/api/discovery/v1alpha1"
discoveryv1beta1 "k8s.io/api/discovery/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
"github.com/clastix/capsule/api/v1alpha1"
capsulewebhook "github.com/clastix/capsule/pkg/webhook"
)
// +kubebuilder:webhook:path=/mutate-v1-service-labels,mutating=true,failurePolicy=ignore,groups="";discovery.k8s.io,resources=services;endpoints;endpointslices,verbs=create;update,versions=v1;v1beta1,name=service.labels.capsule.clastix.io
@@ -102,11 +106,22 @@ func (h *handler) svcFromRequest(req admission.Request, decoder *admission.Decod
}
svc = Endpoints{ep}
case "EndpointSlice":
eps := &discoveryv1beta1.EndpointSlice{}
if err := decoder.Decode(req, eps); err != nil {
return nil, err
var eps runtime.Object
if v := req.Kind.Version; v == "v1beta1" {
eps = &discoveryv1beta1.EndpointSlice{}
if err := decoder.Decode(req, eps); err != nil {
return nil, err
}
} else if v == "v1alpha1" {
eps = &discoveryv1alpha1.EndpointSlice{}
if err := decoder.Decode(req, eps); err != nil {
return nil, err
}
} else {
return nil, fmt.Errorf("unsupported EndpointSlice version: %s", v)
}
svc = EndpointSlice{eps}
svc = EndpointSlice{eps.(metav1.Object)}
default:
err = fmt.Errorf("cannot recognize type %s", req.Kind.Kind)
}
+2 -2
View File
@@ -18,7 +18,7 @@ package service_labels
import (
corev1 "k8s.io/api/core/v1"
discoveryv1beta1 "k8s.io/api/discovery/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type ServiceType interface {
@@ -60,7 +60,7 @@ func (ep Endpoints) Annotations() map[string]string {
}
type EndpointSlice struct {
*discoveryv1beta1.EndpointSlice
metav1.Object
}
func (eps EndpointSlice) Namespace() string {