mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-02-14 18:09:58 +00:00
fix(controller): make device and gateway class optional (#1775)
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
This commit is contained in:
10
.github/workflows/e2e.yml
vendored
10
.github/workflows/e2e.yml
vendored
@@ -45,10 +45,10 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
k8s-version:
|
||||
- '1.30.0'
|
||||
- '1.31.0'
|
||||
- '1.32.0'
|
||||
- '1.33.0'
|
||||
- 'v1.30.0'
|
||||
- 'v1.31.0'
|
||||
- 'v1.32.0'
|
||||
- 'v1.33.0'
|
||||
runs-on:
|
||||
labels: ubuntu-latest-8-cores
|
||||
steps:
|
||||
@@ -64,4 +64,4 @@ jobs:
|
||||
- uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4
|
||||
|
||||
- name: e2e (Enterprise)
|
||||
run: KUBERNETES_SUPPORTED_VERSION=${{ matrix.k8s-version }} sudo make e2e
|
||||
run: sudo KUBERNETES_SUPPORTED_VERSION=${{ matrix.k8s-version }} make e2e
|
||||
|
||||
@@ -5,11 +5,13 @@ package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
|
||||
"github.com/projectcapsule/capsule/pkg/api"
|
||||
"github.com/projectcapsule/capsule/pkg/utils"
|
||||
resources "k8s.io/api/resource/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -132,6 +134,13 @@ var _ = Describe("when Tenant handles Device classes", Label("tenant", "classes"
|
||||
return k8sClient.Create(context.TODO(), tnt)
|
||||
}).Should(Succeed())
|
||||
}
|
||||
|
||||
if err := k8sClient.List(context.Background(), &resources.DeviceClassList{}); err != nil {
|
||||
if utils.IsUnsupportedAPI(err) {
|
||||
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
for _, crd := range []*resources.DeviceClass{authorized, authorized2, unauthorized} {
|
||||
crd.ResourceVersion = ""
|
||||
EventuallyCreation(func() error {
|
||||
@@ -146,6 +155,12 @@ var _ = Describe("when Tenant handles Device classes", Label("tenant", "classes"
|
||||
}).Should(Succeed())
|
||||
}
|
||||
|
||||
if err := k8sClient.List(context.Background(), &resources.DeviceClassList{}); err != nil {
|
||||
if utils.IsUnsupportedAPI(err) {
|
||||
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
Eventually(func() (err error) {
|
||||
req, _ := labels.NewRequirement("env", selection.Exists, nil)
|
||||
|
||||
@@ -157,6 +172,12 @@ var _ = Describe("when Tenant handles Device classes", Label("tenant", "classes"
|
||||
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
|
||||
})
|
||||
It("ResourceClaims", func() {
|
||||
if err := k8sClient.List(context.Background(), &resources.DeviceClassList{}); err != nil {
|
||||
if utils.IsUnsupportedAPI(err) {
|
||||
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
By("Verify Status (Creation)", func() {
|
||||
Eventually(func() ([]string, error) {
|
||||
t := &capsulev1beta2.Tenant{}
|
||||
@@ -303,6 +324,11 @@ var _ = Describe("when Tenant handles Device classes", Label("tenant", "classes"
|
||||
})
|
||||
})
|
||||
It("ResourceClaimTemplates", func() {
|
||||
if err := k8sClient.List(context.Background(), &resources.DeviceClassList{}); err != nil {
|
||||
if utils.IsUnsupportedAPI(err) {
|
||||
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
ns := NewNamespace("")
|
||||
NamespaceCreation(ns, tntWithAuthorized.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed())
|
||||
|
||||
@@ -5,6 +5,7 @@ package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
@@ -21,6 +22,7 @@ import (
|
||||
|
||||
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
|
||||
"github.com/projectcapsule/capsule/pkg/api"
|
||||
"github.com/projectcapsule/capsule/pkg/utils"
|
||||
)
|
||||
|
||||
var _ = Describe("when Tenant handles Gateway classes", Label("tenant", "classes", "gateway"), func() {
|
||||
@@ -156,13 +158,21 @@ var _ = Describe("when Tenant handles Gateway classes", Label("tenant", "classes
|
||||
}
|
||||
|
||||
JustBeforeEach(func() {
|
||||
utilruntime.Must(gatewayv1.Install(scheme.Scheme))
|
||||
for _, tnt := range []*capsulev1beta2.Tenant{tntWithDefault, tntWithoutDefault, tntNoRestrictions} {
|
||||
tnt.ResourceVersion = ""
|
||||
EventuallyCreation(func() error {
|
||||
return k8sClient.Create(context.TODO(), tnt)
|
||||
}).Should(Succeed())
|
||||
}
|
||||
|
||||
if err := k8sClient.List(context.Background(), &gatewayv1.GatewayClassList{}); err != nil {
|
||||
if utils.IsUnsupportedAPI(err) {
|
||||
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
utilruntime.Must(gatewayv1.Install(scheme.Scheme))
|
||||
|
||||
for _, crd := range []*gatewayv1.GatewayClass{authorized, unauthorized, exact, exactU} {
|
||||
Eventually(func() error {
|
||||
crd.ResourceVersion = ""
|
||||
@@ -178,6 +188,12 @@ var _ = Describe("when Tenant handles Gateway classes", Label("tenant", "classes
|
||||
}).Should(Succeed())
|
||||
}
|
||||
|
||||
if err := k8sClient.List(context.Background(), &gatewayv1.GatewayClassList{}); err != nil {
|
||||
if utils.IsUnsupportedAPI(err) {
|
||||
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
Eventually(func() (err error) {
|
||||
req, _ := labels.NewRequirement("env", selection.Exists, nil)
|
||||
|
||||
@@ -189,6 +205,12 @@ var _ = Describe("when Tenant handles Gateway classes", Label("tenant", "classes
|
||||
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
|
||||
})
|
||||
It("should allow all classes", func() {
|
||||
if err := k8sClient.List(context.Background(), &gatewayv1.GatewayClassList{}); err != nil {
|
||||
if utils.IsUnsupportedAPI(err) {
|
||||
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
By("Verify Status (Creation)", func() {
|
||||
Eventually(func() ([]string, error) {
|
||||
t := &capsulev1beta2.Tenant{}
|
||||
@@ -281,6 +303,12 @@ var _ = Describe("when Tenant handles Gateway classes", Label("tenant", "classes
|
||||
})
|
||||
|
||||
It("should block Gateway", func() {
|
||||
if err := k8sClient.List(context.Background(), &gatewayv1.GatewayClassList{}); err != nil {
|
||||
if utils.IsUnsupportedAPI(err) {
|
||||
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
By("Verify Status (Creation)", func() {
|
||||
Eventually(func() ([]string, error) {
|
||||
t := &capsulev1beta2.Tenant{}
|
||||
@@ -391,6 +419,12 @@ var _ = Describe("when Tenant handles Gateway classes", Label("tenant", "classes
|
||||
|
||||
})
|
||||
It("should allow Gateway", func() {
|
||||
if err := k8sClient.List(context.Background(), &gatewayv1.GatewayClassList{}); err != nil {
|
||||
if utils.IsUnsupportedAPI(err) {
|
||||
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
By("Verify Status (Creation)", func() {
|
||||
Eventually(func() ([]string, error) {
|
||||
t := &capsulev1beta2.Tenant{}
|
||||
@@ -483,6 +517,12 @@ var _ = Describe("when Tenant handles Gateway classes", Label("tenant", "classes
|
||||
})
|
||||
})
|
||||
It("should fail on invalid configuration", func() {
|
||||
if err := k8sClient.List(context.Background(), &gatewayv1.GatewayClassList{}); err != nil {
|
||||
if utils.IsUnsupportedAPI(err) {
|
||||
Skip(fmt.Sprintf("Running test due to unsupported API kind: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
By("Verify Status (Creation)", func() {
|
||||
Eventually(func() ([]string, error) {
|
||||
t := &capsulev1beta2.Tenant{}
|
||||
|
||||
@@ -12,11 +12,12 @@ import (
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
nodev1 "k8s.io/api/node/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
resources "k8s.io/api/resource/v1"
|
||||
resourcesv1 "k8s.io/api/resource/v1"
|
||||
schedulingv1 "k8s.io/api/scheduling/v1"
|
||||
storagev1 "k8s.io/api/storage/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apiserver/pkg/authentication/serviceaccount"
|
||||
"k8s.io/client-go/rest"
|
||||
@@ -49,10 +50,16 @@ type Manager struct {
|
||||
Recorder record.EventRecorder
|
||||
Configuration configuration.Configuration
|
||||
RESTConfig *rest.Config
|
||||
classes supportedClasses
|
||||
}
|
||||
|
||||
type supportedClasses struct {
|
||||
device bool
|
||||
gateway bool
|
||||
}
|
||||
|
||||
func (r *Manager) SetupWithManager(mgr ctrl.Manager, ctrlConfig utils.ControllerOptions) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
ctrlBuilder := ctrl.NewControllerManagedBy(mgr).
|
||||
For(
|
||||
&capsulev1beta2.Tenant{},
|
||||
builder.WithPredicates(
|
||||
@@ -73,15 +80,6 @@ func (r *Manager) SetupWithManager(mgr ctrl.Manager, ctrlConfig utils.Controller
|
||||
&corev1.Namespace{},
|
||||
handler.EnqueueRequestForOwner(mgr.GetScheme(), mgr.GetRESTMapper(), &capsulev1beta2.Tenant{}),
|
||||
).
|
||||
Watches(
|
||||
&resources.DeviceClass{},
|
||||
r.statusOnlyHandlerClasses(
|
||||
r.reconcileClassStatus,
|
||||
r.collectAvailableDeviceClasses,
|
||||
"cannot collect device classes",
|
||||
),
|
||||
builder.WithPredicates(utils.UpdatedMetadataPredicate),
|
||||
).
|
||||
Watches(
|
||||
&storagev1.StorageClass{},
|
||||
r.statusOnlyHandlerClasses(
|
||||
@@ -91,15 +89,6 @@ func (r *Manager) SetupWithManager(mgr ctrl.Manager, ctrlConfig utils.Controller
|
||||
),
|
||||
builder.WithPredicates(utils.UpdatedMetadataPredicate),
|
||||
).
|
||||
Watches(
|
||||
&gatewayv1.GatewayClass{},
|
||||
r.statusOnlyHandlerClasses(
|
||||
r.reconcileClassStatus,
|
||||
r.collectAvailableGatewayClasses,
|
||||
"cannot collect gateway classes",
|
||||
),
|
||||
builder.WithPredicates(utils.UpdatedMetadataPredicate),
|
||||
).
|
||||
Watches(
|
||||
&schedulingv1.PriorityClass{},
|
||||
r.statusOnlyHandlerClasses(
|
||||
@@ -207,8 +196,47 @@ func (r *Manager) SetupWithManager(mgr ctrl.Manager, ctrlConfig utils.Controller
|
||||
},
|
||||
builder.WithPredicates(utils.PromotedServiceaccountPredicate),
|
||||
).
|
||||
WithOptions(controller.Options{MaxConcurrentReconciles: ctrlConfig.MaxConcurrentReconciles}).
|
||||
Complete(r)
|
||||
WithOptions(controller.Options{MaxConcurrentReconciles: ctrlConfig.MaxConcurrentReconciles})
|
||||
|
||||
// GatewayClass is Optional
|
||||
r.classes.gateway = utils.HasGVK(mgr.GetRESTMapper(), schema.GroupVersionKind{
|
||||
Group: "gateway.networking.k8s.io",
|
||||
Version: "v1",
|
||||
Kind: "GatewayClass",
|
||||
})
|
||||
|
||||
if r.classes.gateway {
|
||||
ctrlBuilder = ctrlBuilder.Watches(
|
||||
&gatewayv1.GatewayClass{},
|
||||
r.statusOnlyHandlerClasses(
|
||||
r.reconcileClassStatus,
|
||||
r.collectAvailableGatewayClasses,
|
||||
"cannot collect gateway classes",
|
||||
),
|
||||
builder.WithPredicates(utils.UpdatedMetadataPredicate),
|
||||
)
|
||||
}
|
||||
|
||||
// DeviceClass is Optional
|
||||
r.classes.device = utils.HasGVK(mgr.GetRESTMapper(), schema.GroupVersionKind{
|
||||
Group: "resource.k8s.io",
|
||||
Version: "v1",
|
||||
Kind: "DeviceClass",
|
||||
})
|
||||
|
||||
if r.classes.device {
|
||||
ctrlBuilder = ctrlBuilder.Watches(
|
||||
&resourcesv1.DeviceClass{},
|
||||
r.statusOnlyHandlerClasses(
|
||||
r.reconcileClassStatus,
|
||||
r.collectAvailableDeviceClasses,
|
||||
"cannot collect device classes",
|
||||
),
|
||||
builder.WithPredicates(utils.UpdatedMetadataPredicate),
|
||||
)
|
||||
}
|
||||
|
||||
return ctrlBuilder.Complete(r)
|
||||
}
|
||||
|
||||
func (r Manager) Reconcile(ctx context.Context, request ctrl.Request) (result ctrl.Result, err error) {
|
||||
|
||||
@@ -73,14 +73,16 @@ func (r Manager) reconcileClassStatus(
|
||||
func (r *Manager) collectAvailableResources(ctx context.Context, tnt *capsulev1beta2.Tenant) (err error) {
|
||||
log := log.FromContext(ctx)
|
||||
|
||||
log.V(5).Info("collecting available deviceclasses")
|
||||
if r.classes.device {
|
||||
log.V(5).Info("collecting available deviceclasses")
|
||||
|
||||
if err = r.collectAvailableDeviceClasses(ctx, tnt); err != nil {
|
||||
return err
|
||||
if err = r.collectAvailableDeviceClasses(ctx, tnt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.V(5).Info("collected available deviceclasses", "size", len(tnt.Status.Classes.DeviceClasses))
|
||||
}
|
||||
|
||||
log.V(5).Info("collected available deviceclasses", "size", len(tnt.Status.Classes.DeviceClasses))
|
||||
|
||||
log.V(5).Info("collecting available storageclasses")
|
||||
|
||||
if err = r.collectAvailableStorageClasses(ctx, tnt); err != nil {
|
||||
@@ -93,14 +95,16 @@ func (r *Manager) collectAvailableResources(ctx context.Context, tnt *capsulev1b
|
||||
return err
|
||||
}
|
||||
|
||||
log.V(5).Info("collected available priorityclasses", "size", len(tnt.Status.Classes.PriorityClasses))
|
||||
if r.classes.gateway {
|
||||
log.V(5).Info("collected available priorityclasses", "size", len(tnt.Status.Classes.PriorityClasses))
|
||||
|
||||
if err = r.collectAvailableGatewayClasses(ctx, tnt); err != nil {
|
||||
return err
|
||||
if err = r.collectAvailableGatewayClasses(ctx, tnt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.V(5).Info("collected available gatewayclasses", "size", len(tnt.Status.Classes.GatewayClasses))
|
||||
}
|
||||
|
||||
log.V(5).Info("collected available gatewayclasses", "size", len(tnt.Status.Classes.GatewayClasses))
|
||||
|
||||
if err = r.collectAvailableRuntimeClasses(ctx, tnt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
25
internal/controllers/utils/gvk.go
Normal file
25
internal/controllers/utils/gvk.go
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright 2020-2025 Project Capsule Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
)
|
||||
|
||||
func HasGVK(mapper meta.RESTMapper, gvk schema.GroupVersionKind) bool {
|
||||
_, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
|
||||
if err != nil {
|
||||
if meta.IsNoMatchError(err) {
|
||||
return false
|
||||
}
|
||||
|
||||
ctrl.Log.WithName("gvk-check").Error(err, "failed to check RESTMapping", "gvk", gvk.String())
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user