Files
cozystack/internal/migrationcontroller/host_prune_test.go
Aleksei Sviridkin 047c715775 chore: clean up stray console file and test fixtures
Drop a local kubeconfig that was committed by accident alongside the
console sources and ignore the filename so it cannot come back. Replace
environment-specific values in the vSphere migration fixtures with
synthetic ones: the tests only care about the shape of the inventory
record, not where it was captured.

Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
2026-09-24 20:37:49 +03:00

112 lines
4.5 KiB
Go

// SPDX-License-Identifier: Apache-2.0
package migrationcontroller
import (
"context"
"testing"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record"
clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
migrationv1alpha1 "github.com/cozystack/cozystack/api/migration/v1alpha1"
)
// hostOverride builds the Host and Secret pair the controller creates for one
// entry, as an earlier reconcile would have left them.
func hostOverride(src *migrationv1alpha1.VMImportSource, id string) (*unstructured.Unstructured, *corev1.Secret) {
name := hostOverrideName(src.Name, id)
owner := ownerRef(migrationv1alpha1.GroupVersion.WithKind("VMImportSource"), src.Name, src.UID)
host := newObject(hostGVK)
host.SetName(name)
host.SetNamespace(src.Namespace)
host.SetLabels(map[string]string{migrationv1alpha1.ManagedByLabel: migrationv1alpha1.ManagedByValue})
host.SetOwnerReferences([]metav1.OwnerReference{owner})
secret := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: src.Namespace,
Labels: map[string]string{migrationv1alpha1.ManagedByLabel: migrationv1alpha1.ManagedByValue},
OwnerReferences: []metav1.OwnerReference{owner},
}}
return host, secret
}
// A Source is editable, so an override can be dropped after it was created. A
// Host left behind is not inert — Forklift keeps routing that ESXi host's
// transfers through the address it names — and its credentials would otherwise
// outlive the decision to stop using them.
func TestEditingASourceRemovesTheHostsItNoLongerLists(t *testing.T) {
s := testScheme(t)
src := vsphereSource("vcenter", "tenant-foo")
src.Spec.Hosts = []migrationv1alpha1.HostOverride{{
ID: "host-10",
Address: "192.0.2.29",
Credentials: migrationv1alpha1.ProviderCredentials{
Username: "root", Password: "x", InsecureSkipVerify: true,
},
}}
keptHost, keptSecret := hostOverride(src, "host-10")
goneHost, goneSecret := hostOverride(src, "host-99")
c := clientfake.NewClientBuilder().WithScheme(s).
WithObjects(src, keptHost, keptSecret, goneHost, goneSecret).Build()
r := &VMImportSourceReconciler{Client: c, Scheme: s, Recorder: record.NewFakeRecorder(10)}
if err := r.pruneHosts(context.Background(), src); err != nil {
t.Fatalf("pruneHosts: %v", err)
}
gone := types.NamespacedName{Namespace: "tenant-foo", Name: hostOverrideName("vcenter", "host-99")}
if err := c.Get(context.Background(), gone, newObject(hostGVK)); !apierrors.IsNotFound(err) {
t.Errorf("the dropped Host survived (err %v); it would keep redirecting transfers", err)
}
if err := c.Get(context.Background(), gone, &corev1.Secret{}); !apierrors.IsNotFound(err) {
t.Errorf("the dropped Host's credentials survived (err %v)", err)
}
kept := types.NamespacedName{Namespace: "tenant-foo", Name: hostOverrideName("vcenter", "host-10")}
if err := c.Get(context.Background(), kept, newObject(hostGVK)); err != nil {
t.Errorf("the still-listed Host was removed: %v", err)
}
if err := c.Get(context.Background(), kept, &corev1.Secret{}); err != nil {
t.Errorf("the still-listed Host's credentials were removed: %v", err)
}
}
// The label says this controller made the object, not that it made it for this
// Source. Two Sources in one namespace must not delete each other's Hosts.
func TestPruningLeavesAnotherSourcesHostsAlone(t *testing.T) {
s := testScheme(t)
mine := vsphereSource("vcenter", "tenant-foo")
theirs := vsphereSource("other", "tenant-foo")
theirs.UID = types.UID("other-uid")
theirHost, theirSecret := hostOverride(theirs, "host-10")
c := clientfake.NewClientBuilder().WithScheme(s).
WithObjects(mine, theirs, theirHost, theirSecret).Build()
r := &VMImportSourceReconciler{Client: c, Scheme: s, Recorder: record.NewFakeRecorder(10)}
// `mine` lists no hosts at all, so an ownership-blind prune would take the
// other Source's Host with it.
if err := r.pruneHosts(context.Background(), mine); err != nil {
t.Fatalf("pruneHosts: %v", err)
}
name := types.NamespacedName{Namespace: "tenant-foo", Name: hostOverrideName("other", "host-10")}
if err := c.Get(context.Background(), name, newObject(hostGVK)); err != nil {
t.Errorf("another Source's Host was deleted: %v", err)
}
if err := c.Get(context.Background(), name, &corev1.Secret{}); err != nil {
t.Errorf("another Source's credentials were deleted: %v", err)
}
}