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 e2e tests for fromHosts sync - configmaps #2473

Merged
merged 10 commits into from
Feb 14, 2025
5 changes: 2 additions & 3 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,7 @@ e2e distribution="k3s" path="./test/e2e" multinamespace="false": create-kind &&
echo "Execute test suites ({{ distribution }}, {{ path }}, {{ multinamespace }})"

TELEMETRY_PRIVATE_KEY="" goreleaser build --snapshot --clean

cp dist/vcluster_linux_$(go env GOARCH | sed s/amd64/amd64_v1/g)/vcluster ./vcluster
cp dist/vcluster_linux_$(go env GOARCH | sed s/amd64/amd64_v1/g | sed s/arm64/arm64_v8.0/g)/vcluster ./vcluster
docker build -t vcluster:e2e-latest -f Dockerfile.release --build-arg TARGETARCH=$(uname -m) --build-arg TARGETOS=linux .
rm ./vcluster

Expand All @@ -121,7 +120,7 @@ e2e distribution="k3s" path="./test/e2e" multinamespace="false": create-kind &&

kubectl create namespace from-host-sync-test
kubectl create namespace from-host-sync-test-2
./dist/vcluster-cli_$(go env GOOS)_$(go env GOARCH | sed s/amd64/amd64_v1/g)/vcluster \
./dist/vcluster-cli_$(go env GOOS)_$(go env GOARCH | sed s/amd64/amd64_v1/g | sed s/arm64/arm64_v8.0/g)/vcluster \
create vcluster -n vcluster \
--create-namespace \
--debug \
Expand Down
28 changes: 14 additions & 14 deletions pkg/syncer/from_host_syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ func (s *genericFromHostSyncer) Sync(ctx *synccontext.SyncContext, event *syncco

func (s *genericFromHostSyncer) SyncToVirtual(ctx *synccontext.SyncContext, event *synccontext.SyncToVirtualEvent[client.Object]) (ctrl.Result, error) {
klog.FromContext(ctx).V(1).Info("SyncToVirtual called")
if event.VirtualOld != nil || event.Host.GetDeletionTimestamp() != nil {
return patcher.DeleteHostObject(ctx, event.Host, event.VirtualOld, "virtual object was deleted")
if event.VirtualOld != nil && event.Host.GetDeletionTimestamp() != nil {
return patcher.DeleteVirtualObject(ctx, event.VirtualOld, event.Host, "host object was deleted")
}

vObj := translate.VirtualMetadata(event.Host, s.HostToVirtual(ctx, types.NamespacedName{Name: event.Host.GetName(), Namespace: event.Host.GetNamespace()}, event.Host))
Expand All @@ -107,11 +107,11 @@ func (s *genericFromHostSyncer) SyncToVirtual(ctx *synccontext.SyncContext, even
if err != nil {
if kerrors.IsNotFound(err) {
return ctrl.Result{Requeue: true},
ctx.VirtualClient.Create(
client.IgnoreAlreadyExists(ctx.VirtualClient.Create(
ctx, &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{Name: vObj.GetNamespace()},
},
)
))
}
return ctrl.Result{}, err
} else if namespace.DeletionTimestamp != nil {
Expand Down Expand Up @@ -152,27 +152,28 @@ func (s *genericFromHostSyncer) ConfigureAndStartManager(ctx *synccontext.Regist
if !createCustomManager {
return ctx, nil
}
newCtx := *ctx

logNs := make([]string, 0, len(multiNsCacheConfig.DefaultNamespaces))
for k := range multiNsCacheConfig.DefaultNamespaces {
logNs = append(logNs, k)
}
klog.FromContext(ctx).Info("Setting up custom physical multi-namespace manager for", "namespaces", logNs, "syncer", s.Name())
localMultiNamespaceManager, err := ctrl.NewManager(ctx.Config.WorkloadConfig, ctrl.Options{
klog.FromContext(newCtx).Info("Setting up custom physical multi-namespace manager for", "namespaces", logNs, "syncer", s.Name())
localMultiNamespaceManager, err := ctrl.NewManager(newCtx.Config.WorkloadConfig, ctrl.Options{
Scheme: scheme.Scheme,
Metrics: metricsserver.Options{
BindAddress: "0",
},
PprofBindAddress: "0",
LeaderElection: false,
NewClient: pro.NewVirtualClient(ctx.Config),
NewClient: pro.NewVirtualClient(newCtx.Config),
WebhookServer: nil,
Cache: cache.Options{
Mapper: ctx.PhysicalManager.GetRESTMapper(),
Mapper: newCtx.PhysicalManager.GetRESTMapper(),
DefaultNamespaces: multiNsCacheConfig.DefaultNamespaces,
DefaultWatchErrorHandler: func(r *toolscache.Reflector, err error) {
if kerrors.IsForbidden(err) {
klog.FromContext(ctx).Error(err,
klog.FromContext(newCtx).Error(err,
"trying to watch on a namespace that does not exists / have no permissions. "+
"Please either re-create it or remove the namespace from mappings in the vcluster.yaml and restart vCluster.")
} else {
Expand All @@ -182,21 +183,20 @@ func (s *genericFromHostSyncer) ConfigureAndStartManager(ctx *synccontext.Regist
},
})
if err != nil {
return ctx, fmt.Errorf("unable to create custom physical manager for syncer %s: %w", s.Name(), err)
return nil, fmt.Errorf("unable to create custom physical manager for syncer %s: %w", s.Name(), err)
}

go func() {
err := localMultiNamespaceManager.Start(ctx)
err := localMultiNamespaceManager.Start(newCtx)
if err != nil {
panic(err)
}
}()

if synced := localMultiNamespaceManager.GetCache().WaitForCacheSync(ctx.Context); !synced {
klog.FromContext(ctx).Error(err, "cache was not synced")
if synced := localMultiNamespaceManager.GetCache().WaitForCacheSync(newCtx); !synced {
klog.FromContext(newCtx).Error(err, "cache was not synced")
return ctx, fmt.Errorf("cache was not synced for custom physical manager for %s syncer", s.Name())
}
newCtx := *ctx
newCtx.PhysicalManager = localMultiNamespaceManager
return &newCtx, nil
}
Expand Down
16 changes: 7 additions & 9 deletions pkg/syncer/synccontext/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,23 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
)

func NewBidirectionalObjectCache(obj client.Object) *BidirectionalObjectCache {
func NewBidirectionalObjectCache(obj client.Object, mapper Mapper) *BidirectionalObjectCache {
return &BidirectionalObjectCache{
vCache: newObjectCache(),
pCache: newObjectCache(),

obj: obj,

mapper: mapper,
}
}

type BidirectionalObjectCache struct {
vCache *ObjectCache
pCache *ObjectCache

mapper Mapper

obj client.Object
}

Expand All @@ -45,15 +49,9 @@ func (o *BidirectionalObjectCache) Start(ctx *RegisterContext) error {
return fmt.Errorf("gvk for object: %w", err)
}

mapper, err := ctx.Mappings.ByGVK(gvk)
if err != nil {
return fmt.Errorf("mapper for gvk %s couldn't be found", gvk.String())
}

go func() {
wait.Until(func() {
syncContext := ctx.ToSyncContext("bidirectional-object-cache")

// clear up host cache
o.pCache.cache.Range(func(key, _ any) bool {
// check physical object
Expand All @@ -63,7 +61,7 @@ func (o *BidirectionalObjectCache) Start(ctx *RegisterContext) error {
}

// check virtual object
vName := mapper.HostToVirtual(syncContext, pName, nil)
vName := o.mapper.HostToVirtual(syncContext, pName, nil)
if vName.Name == "" {
o.pCache.cache.Delete(key)
klog.FromContext(syncContext).V(1).Info("Delete from host cache", "gvk", gvk.String(), "key", pName.String())
Expand All @@ -88,7 +86,7 @@ func (o *BidirectionalObjectCache) Start(ctx *RegisterContext) error {
}

// check host object
pName := mapper.VirtualToHost(syncContext, vName, nil)
pName := o.mapper.VirtualToHost(syncContext, vName, nil)
if pName.Name == "" {
o.vCache.cache.Delete(key)
klog.FromContext(syncContext).V(1).Info("Delete from virtual cache", "gvk", gvk.String(), "key", vName.String())
Expand Down
2 changes: 1 addition & 1 deletion pkg/syncer/syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func NewSyncController(ctx *synccontext.RegisterContext, syncer syncertypes.Sync

var objectCache *synccontext.BidirectionalObjectCache
if options.ObjectCaching {
objectCache = synccontext.NewBidirectionalObjectCache(syncer.Resource().DeepCopyObject().(client.Object))
objectCache = synccontext.NewBidirectionalObjectCache(syncer.Resource().DeepCopyObject().(client.Object), syncer)
}

return &SyncController{
Expand Down
2 changes: 1 addition & 1 deletion pkg/syncer/syncer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,7 @@ func TestReconcile(t *testing.T) {
virtualClient: vClient,
options: options,

objectCache: synccontext.NewBidirectionalObjectCache(syncer.Resource()),
objectCache: synccontext.NewBidirectionalObjectCache(syncer.Resource(), syncer),
}

// create objects
Expand Down
2 changes: 1 addition & 1 deletion pkg/syncer/testing/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func FakeStartSyncer(t *testing.T, ctx *synccontext.RegisterContext, create func
// check if object cache is needed
optionsProvider, ok := object.(syncer.OptionsProvider)
if ok && optionsProvider.Options().ObjectCaching {
syncCtx.ObjectCache = synccontext.NewBidirectionalObjectCache(object.Resource())
syncCtx.ObjectCache = synccontext.NewBidirectionalObjectCache(object.Resource(), mapper)
}

syncCtx.Log = loghelper.NewFromExisting(log.NewLog(0), object.Name())
Expand Down
72 changes: 72 additions & 0 deletions test/e2e/syncer/fromhost/from_host.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@ var _ = ginkgo.Describe("ConfigMaps are synced to host and can be used in Pods",
f *framework.Framework
configMap1 *corev1.ConfigMap
configMap2 *corev1.ConfigMap
configMap3 *corev1.ConfigMap
cm1Name = "dummy"
cm1HostNamespace = "from-host-sync-test"
cmsVirtualNamespace = "barfoo"
cm2HostNamespace = "default"
cm2HostName = "my-cm"
cm2VirtualName = "cm-my"
cm3Name = "from-vcluster-ns"
cm3HostNamespace = "vcluster"
cm3virtualNamespace = "my-new-ns"
podName = "my-pod"
)

Expand All @@ -48,6 +52,16 @@ var _ = ginkgo.Describe("ConfigMaps are synced to host and can be used in Pods",
"ANOTHER_ENV_FROM_DEFAULT_NS": "two",
},
}
configMap3 = &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: cm3Name,
Namespace: cm3HostNamespace,
},
Data: map[string]string{
"VAL1": "abcdef",
"VAL2": "defgh",
},
}

})

Expand Down Expand Up @@ -189,4 +203,62 @@ var _ = ginkgo.Describe("ConfigMaps are synced to host and can be used in Pods",
gomega.Expect(envs["ENV_FROM_DEFAULT_NS"]).To(gomega.Equal("one"))
})

ginkgo.It("Delete configmap in vCluster should be sync again by host", func() {
oldConfigMap, _ := f.VClusterClient.CoreV1().ConfigMaps(cmsVirtualNamespace).Get(f.Context, cm2VirtualName, metav1.GetOptions{})

uidBeforeDeletion := oldConfigMap.UID

framework.ExpectNoError(f.VClusterClient.CoreV1().ConfigMaps(cmsVirtualNamespace).Delete(f.Context, cm2VirtualName, metav1.DeleteOptions{}))

gomega.Eventually(func() bool {
newConfigMap, err := f.VClusterClient.CoreV1().ConfigMaps(cmsVirtualNamespace).Get(f.Context, cm2VirtualName, metav1.GetOptions{})
if err != nil {
return false
}
return newConfigMap.Data["ENV_FROM_DEFAULT_NS"] == "one" && newConfigMap.Data["ANOTHER_ENV_FROM_DEFAULT_NS"] == "two" && uidBeforeDeletion != newConfigMap.UID
}).
WithPolling(time.Second).
WithTimeout(framework.PollTimeout).
Should(gomega.BeTrue())
})

ginkgo.It("confimap edited in vCluster should be overwritten by host", func() {
vClusterMap, err := f.VClusterClient.CoreV1().ConfigMaps(cmsVirtualNamespace).Get(f.Context, cm2VirtualName, metav1.GetOptions{})
framework.ExpectNoError(err)

vClusterMap.Data = make(map[string]string, 1)

vClusterMap.Data["new-value"] = "will-be-overwritten"

_, err = f.VClusterClient.CoreV1().ConfigMaps(cmsVirtualNamespace).Update(f.Context, vClusterMap, metav1.UpdateOptions{})
framework.ExpectNoError(err)

gomega.Eventually(func() bool {
defaultCmValues, err := f.VClusterClient.CoreV1().ConfigMaps(cmsVirtualNamespace).Get(f.Context, cm2VirtualName, metav1.GetOptions{})
if err != nil {
return false
}
_, updatedExist := defaultCmValues.Data["new-value"]
return defaultCmValues.Data["ENV_FROM_DEFAULT_NS"] == "one" && defaultCmValues.Data["ANOTHER_ENV_FROM_DEFAULT_NS"] == "two" && !updatedExist
}).
WithPolling(time.Second).
WithTimeout(framework.PollTimeout).
Should(gomega.BeTrue())
})

ginkgo.It("Syncs all ConfigMaps from vCluster's host namespace to my-new-ns in vCluster", func() {
_, err := f.HostClient.CoreV1().ConfigMaps(configMap3.GetNamespace()).Create(f.Context, configMap3, metav1.CreateOptions{})
framework.ExpectNoError(err)

gomega.Eventually(func() bool {
defaultCmValues, err := f.VClusterClient.CoreV1().ConfigMaps(cm3virtualNamespace).Get(f.Context, cm3Name, metav1.GetOptions{})
if err != nil {
return false
}
return defaultCmValues.Data["VAL1"] == "abcdef" && defaultCmValues.Data["VAL2"] == "defgh"
}).
WithPolling(time.Second).
WithTimeout(framework.PollTimeout).
Should(gomega.BeTrue())
})
})
Loading