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

added runtime classes sync #2037

Merged
merged 1 commit into from
Aug 7, 2024
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
1 change: 1 addition & 0 deletions chart/templates/_rbac.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
.Values.sync.toHost.volumeSnapshots.enabled
.Values.controlPlane.advanced.virtualScheduler.enabled
.Values.sync.fromHost.ingressClasses.enabled
.Values.sync.fromHost.runtimeClasses.enabled
(eq (toString .Values.sync.fromHost.storageClasses.enabled) "true")
(eq (toString .Values.sync.fromHost.csiNodes.enabled) "true")
(eq (toString .Values.sync.fromHost.csiDrivers.enabled) "true")
Expand Down
5 changes: 5 additions & 0 deletions chart/templates/clusterrole.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ rules:
resources: ["ingressclasses"]
verbs: ["get", "watch", "list"]
{{- end }}
{{- if .Values.sync.fromHost.runtimeClasses.enabled }}
- apiGroups: ["nodes.k8s.io"]
resources: ["runtimeclasses"]
verbs: ["get", "watch", "list"]
{{- end }}
{{- if .Values.sync.toHost.storageClasses.enabled }}
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
Expand Down
4 changes: 4 additions & 0 deletions chart/values.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2701,6 +2701,10 @@
"$ref": "#/$defs/EnableSwitch",
"description": "IngressClasses defines if ingress classes should get synced from the host cluster to the virtual cluster, but not back."
},
"runtimeClasses": {
"$ref": "#/$defs/EnableSwitch",
"description": "RuntimeClasses defines if runtime classes should get synced from the host cluster to the virtual cluster, but not back."
},
"storageClasses": {
"$ref": "#/$defs/EnableAutoSwitch",
"description": "StorageClasses defines if storage classes should get synced from the host cluster to the virtual cluster, but not back. If auto, is automatically enabled when the virtual scheduler is enabled."
Expand Down
3 changes: 3 additions & 0 deletions chart/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ sync:
# IngressClasses defines if ingress classes should get synced from the host cluster to the virtual cluster, but not back.
ingressClasses:
enabled: false
# RuntimeClasses defines if runtime classes should get synced from the host cluster to the virtual cluster, but not back.
runtimeClasses:
enabled: false
# Nodes defines if nodes should get synced from the host cluster to the virtual cluster, but not back.
nodes:
# Enabled specifies if syncing real nodes should be enabled. If this is disabled, vCluster will create fake nodes instead.
Expand Down
3 changes: 3 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,9 @@ type SyncFromHost struct {
// IngressClasses defines if ingress classes should get synced from the host cluster to the virtual cluster, but not back.
IngressClasses EnableSwitch `json:"ingressClasses,omitempty"`

// RuntimeClasses defines if runtime classes should get synced from the host cluster to the virtual cluster, but not back.
RuntimeClasses EnableSwitch `json:"runtimeClasses,omitempty"`

// StorageClasses defines if storage classes should get synced from the host cluster to the virtual cluster, but not back. If auto, is automatically enabled when the virtual scheduler is enabled.
StorageClasses EnableAutoSwitch `json:"storageClasses,omitempty"`

Expand Down
2 changes: 2 additions & 0 deletions config/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ sync:
enabled: auto
ingressClasses:
enabled: false
runtimeClasses:
enabled: false
nodes:
enabled: false
syncBackChanges: false
Expand Down
2 changes: 2 additions & 0 deletions pkg/controllers/resources/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/loft-sh/vcluster/pkg/controllers/resources/poddisruptionbudgets"
"github.com/loft-sh/vcluster/pkg/controllers/resources/pods"
"github.com/loft-sh/vcluster/pkg/controllers/resources/priorityclasses"
"github.com/loft-sh/vcluster/pkg/controllers/resources/runtimeclasses"
"github.com/loft-sh/vcluster/pkg/controllers/resources/secrets"
"github.com/loft-sh/vcluster/pkg/controllers/resources/serviceaccounts"
"github.com/loft-sh/vcluster/pkg/controllers/resources/services"
Expand Down Expand Up @@ -50,6 +51,7 @@ func getSyncers(ctx *synccontext.RegisterContext) []BuildController {
isEnabled(ctx.Config.Sync.ToHost.PersistentVolumeClaims.Enabled, persistentvolumeclaims.New),
isEnabled(ctx.Config.Sync.ToHost.Ingresses.Enabled, ingresses.New),
isEnabled(ctx.Config.Sync.FromHost.IngressClasses.Enabled, ingressclasses.New),
isEnabled(ctx.Config.Sync.FromHost.RuntimeClasses.Enabled, runtimeclasses.New),
isEnabled(ctx.Config.Sync.ToHost.StorageClasses.Enabled, storageclasses.New),
isEnabled(ctx.Config.Sync.FromHost.StorageClasses.Enabled == "true", storageclasses.NewHostStorageClassSyncer),
isEnabled(ctx.Config.Sync.ToHost.PriorityClasses.Enabled, priorityclasses.New),
Expand Down
76 changes: 76 additions & 0 deletions pkg/controllers/resources/runtimeclasses/syncer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package runtimeclasses

import (
"fmt"

"github.com/loft-sh/vcluster/pkg/mappings/generic"
"github.com/loft-sh/vcluster/pkg/patcher"
"github.com/loft-sh/vcluster/pkg/syncer"
"github.com/loft-sh/vcluster/pkg/syncer/synccontext"
syncertypes "github.com/loft-sh/vcluster/pkg/syncer/types"
"github.com/loft-sh/vcluster/pkg/util/translate"
nodev1 "k8s.io/api/node/v1"
"k8s.io/apimachinery/pkg/types"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func New(_ *synccontext.RegisterContext) (syncertypes.Object, error) {
mapper, err := generic.NewMirrorMapper(&nodev1.RuntimeClass{})
if err != nil {
return nil, err
}

return &runtimeClassSyncer{
Mapper: mapper,
}, nil
}

type runtimeClassSyncer struct {
synccontext.Mapper
}

func (i *runtimeClassSyncer) Name() string {
return "runtimeclass"
}

func (i *runtimeClassSyncer) Resource() client.Object {
return &nodev1.RuntimeClass{}
}

var _ syncertypes.Syncer = &runtimeClassSyncer{}

func (i *runtimeClassSyncer) Syncer() syncertypes.Sync[client.Object] {
return syncer.ToGenericSyncer[*nodev1.RuntimeClass](i)
}

func (i *runtimeClassSyncer) SyncToVirtual(ctx *synccontext.SyncContext, event *synccontext.SyncToVirtualEvent[*nodev1.RuntimeClass]) (ctrl.Result, error) {
vObj := translate.CopyObjectWithName(event.Host, types.NamespacedName{Name: event.Host.Name, Namespace: event.Host.Namespace}, false)
ctx.Log.Infof("create runtime class %s, because it does not exist in virtual cluster", vObj.Name)
return ctrl.Result{}, ctx.VirtualClient.Create(ctx, vObj)
}

func (i *runtimeClassSyncer) Sync(ctx *synccontext.SyncContext, event *synccontext.SyncEvent[*nodev1.RuntimeClass]) (_ ctrl.Result, retErr error) {
patch, err := patcher.NewSyncerPatcher(ctx, event.Host, event.Virtual)
if err != nil {
return ctrl.Result{}, fmt.Errorf("new syncer patcher: %w", err)
}
defer func() {
if err := patch.Patch(ctx, event.Host, event.Virtual); err != nil {
retErr = utilerrors.NewAggregate([]error{retErr, err})
}
}()

event.Virtual.Annotations = event.Host.Annotations
event.Virtual.Labels = event.Host.Labels
event.Virtual.Handler = event.Host.Handler
event.Virtual.Overhead = event.Host.Overhead
event.Virtual.Scheduling = event.Host.Scheduling
return ctrl.Result{}, nil
}

func (i *runtimeClassSyncer) SyncToHost(ctx *synccontext.SyncContext, event *synccontext.SyncToHostEvent[*nodev1.RuntimeClass]) (ctrl.Result, error) {
ctx.Log.Infof("delete virtual runtime class %s, because physical object is missing", event.Virtual.Name)
return ctrl.Result{}, ctx.VirtualClient.Delete(ctx, event.Virtual)
}
137 changes: 137 additions & 0 deletions pkg/controllers/resources/runtimeclasses/syncer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package runtimeclasses

import (
"testing"

"github.com/loft-sh/vcluster/pkg/syncer/synccontext"
syncertesting "github.com/loft-sh/vcluster/pkg/syncer/testing"
"github.com/loft-sh/vcluster/pkg/util/translate"
"gotest.tools/assert"
corev1 "k8s.io/api/core/v1"
nodev1 "k8s.io/api/node/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)

func TestSync(t *testing.T) {
vObjectMeta := metav1.ObjectMeta{
Name: "test-ingc",
Annotations: map[string]string{
translate.NameAnnotation: "test-runtimec",
translate.UIDAnnotation: "",
translate.KindAnnotation: nodev1.SchemeGroupVersion.WithKind("RuntimeClass").String(),
},
}

vObj := &nodev1.RuntimeClass{
ObjectMeta: vObjectMeta,
Scheduling: &nodev1.Scheduling{
NodeSelector: map[string]string{"stuff": "stuff"},
},
Handler: "somehandler",
Overhead: &nodev1.Overhead{
PodFixed: corev1.ResourceList{corev1.ResourceCPU: resource.MustParse("1")},
},
}

pObj := &nodev1.RuntimeClass{
ObjectMeta: metav1.ObjectMeta{
Name: vObjectMeta.Name,
Labels: map[string]string{
translate.MarkerLabel: translate.VClusterName,
},
Annotations: map[string]string{
translate.NameAnnotation: "test-runtimec",
translate.UIDAnnotation: "",
translate.KindAnnotation: nodev1.SchemeGroupVersion.WithKind("RuntimeClass").String(),
},
},
Scheduling: &nodev1.Scheduling{
NodeSelector: map[string]string{"stuff": "stuff"},
},
Handler: "somehandler",
Overhead: &nodev1.Overhead{
PodFixed: corev1.ResourceList{corev1.ResourceCPU: resource.MustParse("1")},
},
}

vObjUpdated := &nodev1.RuntimeClass{
ObjectMeta: vObjectMeta,
Scheduling: &nodev1.Scheduling{
NodeSelector: map[string]string{"stuff": "stuff2"},
},
Handler: "somehandler",
Overhead: &nodev1.Overhead{
PodFixed: corev1.ResourceList{corev1.ResourceCPU: resource.MustParse("1")},
},
}

pObjUpdated := &nodev1.RuntimeClass{
ObjectMeta: metav1.ObjectMeta{
Name: translate.Default.HostNameCluster(vObjectMeta.Name),
Labels: map[string]string{
translate.MarkerLabel: translate.VClusterName,
},
Annotations: map[string]string{
translate.NameAnnotation: "test-runtimec",
translate.UIDAnnotation: "",
translate.KindAnnotation: nodev1.SchemeGroupVersion.WithKind("RuntimeClass").String(),
},
},
Scheduling: &nodev1.Scheduling{
NodeSelector: map[string]string{"stuff": "stuff2"},
},
Handler: "somehandler",
Overhead: &nodev1.Overhead{
PodFixed: corev1.ResourceList{corev1.ResourceCPU: resource.MustParse("1")},
},
}

syncertesting.RunTests(t, []*syncertesting.SyncTest{
{
Name: "Import",
InitialVirtualState: []runtime.Object{},
InitialPhysicalState: []runtime.Object{pObj},
ExpectedVirtualState: map[schema.GroupVersionKind][]runtime.Object{
nodev1.SchemeGroupVersion.WithKind("RuntimeClass"): {vObj},
},
ExpectedPhysicalState: map[schema.GroupVersionKind][]runtime.Object{
nodev1.SchemeGroupVersion.WithKind("RuntimeClass"): {pObj},
},
Sync: func(ctx *synccontext.RegisterContext) {
syncCtx, syncer := syncertesting.FakeStartSyncer(t, ctx, New)
_, err := syncer.(*runtimeClassSyncer).SyncToVirtual(syncCtx, synccontext.NewSyncToVirtualEvent(pObj))
assert.NilError(t, err)
},
},
{
Name: "Delete virtual",
InitialVirtualState: []runtime.Object{vObj},
ExpectedVirtualState: map[schema.GroupVersionKind][]runtime.Object{},
ExpectedPhysicalState: map[schema.GroupVersionKind][]runtime.Object{},
Sync: func(ctx *synccontext.RegisterContext) {
syncCtx, syncer := syncertesting.FakeStartSyncer(t, ctx, New)
_, err := syncer.(*runtimeClassSyncer).SyncToHost(syncCtx, synccontext.NewSyncToHostEvent(vObj))
assert.NilError(t, err)
},
},
{
Name: "Sync",
InitialVirtualState: []runtime.Object{vObj},
InitialPhysicalState: []runtime.Object{pObjUpdated},
ExpectedVirtualState: map[schema.GroupVersionKind][]runtime.Object{
nodev1.SchemeGroupVersion.WithKind("RuntimeClass"): {vObjUpdated},
},
ExpectedPhysicalState: map[schema.GroupVersionKind][]runtime.Object{
nodev1.SchemeGroupVersion.WithKind("RuntimeClass"): {pObjUpdated},
},
Sync: func(ctx *synccontext.RegisterContext) {
syncCtx, syncer := syncertesting.FakeStartSyncer(t, ctx, New)
_, err := syncer.(*runtimeClassSyncer).Sync(syncCtx, synccontext.NewSyncEvent(pObjUpdated, vObj))
assert.NilError(t, err)
},
},
})
}
Loading