Skip to content

Commit

Permalink
added runtime classes sync
Browse files Browse the repository at this point in the history
  • Loading branch information
facchettos committed Aug 7, 2024
1 parent 6260c64 commit 46d919f
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 0 deletions.
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": "RuntimeClasse 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
# RuntimeClasse 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"`

// RuntimeClasse 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)
},
},
})
}

0 comments on commit 46d919f

Please sign in to comment.