package staticpodscheduler_test import ( "context" "embed" "io/fs" "os" "path/filepath" "strings" "testing" "github.com/go-logr/logr/testr" "github.com/spf13/afero" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kruntime "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" calico "edge-infra.dev/pkg/k8s/net/calico" v1ienode "edge-infra.dev/pkg/sds/ien/k8s/apis/v1" "edge-infra.dev/pkg/sds/ien/k8s/controllers/nodeagent/config" "edge-infra.dev/pkg/sds/ien/k8s/controllers/nodeagent/plugins/staticpodscheduler" "edge-infra.dev/pkg/sds/ien/k8s/controllers/nodeagent/plugins/staticpodscheduler/pkg/render" nodemeta "edge-infra.dev/pkg/sds/ien/node" "edge-infra.dev/pkg/sds/ien/topology" ) //go:embed testdata/embedfs var embedFS embed.FS var ( manifestDir = "/etc/kubernetes/manifests" ) var ( lanOutageDetectorManifestFilename = "lan-outage-detector.yaml" lanOutageDetectorManifestFilepath = filepath.Join(manifestDir, lanOutageDetectorManifestFilename) //go:embed testdata/expected-lan-outage-detector.yaml expectedLANOutageDetectorManifest []byte ) var ( etcdManagerManifestFilename = "etcd-manager.yaml" etcdManagerManifestFilepath = filepath.Join(manifestDir, etcdManagerManifestFilename) //go:embed testdata/expected-etcd-manager.yaml expectedEtcdManagerManifest []byte ) var ( deviceAgentManifestFilename = "device-agent.yaml" deviceAgentManifestFilepath = filepath.Join(manifestDir, deviceAgentManifestFilename) //go:embed testdata/expected-device-agent.yaml expectedDeviceAgentsManifest []byte ) var ( cpGuardianManifestFilename = "control-plane-guardian.yaml" cpGuardianManifestFilepath = filepath.Join(manifestDir, cpGuardianManifestFilename) //go:embed testdata/expected-control-plane-guardian.yaml expectedCPGuardianManifest []byte ) var ( hostname = "desired-host" lanOutageDetectorImage = "us-east1-docker.pkg.dev/ret-edge-pltf-infra/workloads/lanoutagedetector" lanOutageSchedulerImage = "us-east1-docker.pkg.dev/ret-edge-pltf-infra/workloads/lanoutagescheduler" admissionImage = "us-east1-docker.pkg.dev/ret-edge-pltf-infra/workloads/k8s-admission" etcdManagerImage = "us-east1-docker.pkg.dev/ret-edge-pltf-infra/workloads/etcdmanager" deviceAgentImage = "us-east1-docker.pkg.dev/ret-edge-pltf-infra/workloads/deviceagent" cpGuardianImage = "us-east1-docker.pkg.dev/ret-edge-pltf-infra/workloads/controlplaneguardian" cpPromoterImage = "us-east1-docker.pkg.dev/ret-edge-pltf-infra/workloads/controlplanepromoter" controlPlaneIP = "1.2.3.4/32" macAddress = "AB:CD:EF:GH:IJ:KL" thickPOS = true webhookName = "admission" ) func TestMain(m *testing.M) { os.Exit(m.Run()) } func setupTestCtx(t *testing.T) context.Context { logOptions := testr.Options{ LogTimestamp: true, Verbosity: -1, } return ctrl.LoggerInto(context.Background(), testr.NewWithOptions(t, logOptions)) } func TestStaticPodScheduler(t *testing.T) { ctx := setupTestCtx(t) scheduler := staticpodscheduler.Plugin{} os.Setenv(render.NodeNameEnvVar, hostname) os.Setenv(render.LANOutageDetectorImageKey, lanOutageDetectorImage) os.Setenv(render.LANOutageSchedulerImageKey, lanOutageSchedulerImage) os.Setenv(render.AdmissionImageKey, admissionImage) os.Setenv(render.EtcdManagerImageKey, etcdManagerImage) os.Setenv(render.DeviceAgentImageKey, deviceAgentImage) os.Setenv(render.AdmissionWebhookEnvVar, webhookName) os.Setenv(render.CPGuardianImageKey, cpGuardianImage) os.Setenv(render.CPPromoterImageKey, cpPromoterImage) tests := map[string]struct { nodeRole v1ienode.Role filepath string expectWritten bool expectedContent []byte }{ "EtcdManagerManifest_Worker": { nodeRole: v1ienode.Worker, filepath: etcdManagerManifestFilepath, expectWritten: false, expectedContent: nil, }, "LANOutageDetectorManifest_Worker": { nodeRole: v1ienode.Worker, filepath: lanOutageDetectorManifestFilepath, expectWritten: true, expectedContent: expectedLANOutageDetectorManifest, }, "DeviceAgentManifest_Worker": { nodeRole: v1ienode.Worker, filepath: deviceAgentManifestFilepath, expectWritten: true, expectedContent: expectedDeviceAgentsManifest, }, "CPGuardianManifest_Worker": { nodeRole: v1ienode.Worker, filepath: cpGuardianManifestFilepath, expectWritten: true, expectedContent: expectedCPGuardianManifest, }, "EtcdManagerManifest_Controlplane": { nodeRole: v1ienode.ControlPlane, filepath: etcdManagerManifestFilepath, expectWritten: true, expectedContent: expectedEtcdManagerManifest, }, "LANOutageDetectorManifest_Controlplane": { nodeRole: v1ienode.ControlPlane, filepath: lanOutageDetectorManifestFilepath, expectWritten: true, expectedContent: expectedLANOutageDetectorManifest, }, "DeviceAgentManifest_Controlplane": { nodeRole: v1ienode.ControlPlane, filepath: deviceAgentManifestFilepath, expectWritten: true, expectedContent: expectedDeviceAgentsManifest, }, "CPGuardianManifest_Controlplane": { nodeRole: v1ienode.ControlPlane, filepath: cpGuardianManifestFilepath, expectWritten: true, expectedContent: expectedCPGuardianManifest, }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { ienode := getIENode(hostname, tc.nodeRole) var controlPlaneNode, workerNode *corev1.Node switch tc.nodeRole { case v1ienode.ControlPlane: controlPlaneNode = getControlPlane(hostname) workerNode = getWorkerNode("worker") case v1ienode.Worker: workerNode = getWorkerNode(hostname) controlPlaneNode = getControlPlane("controlplane") } c := getMockKubeClient(ienode, controlPlaneNode, getTopologyConfigMap(), workerNode) cfg := config.NewConfig(c, getMockKubeReader(), nil, config.Flags{}) memFs := afero.NewMemMapFs() require.NoError(t, embedFiles(memFs)) cfg = cfg.WithFs(memFs) _, err := scheduler.Reconcile(ctx, ienode, cfg) require.NoError(t, err) content, err := afero.ReadFile(memFs, tc.filepath) switch tc.expectWritten { case true: require.NoError(t, err) require.Equal(t, string(tc.expectedContent), string(content)) case false: require.ErrorIs(t, err, os.ErrNotExist) } }) } } func getMockKubeClient(initObjs ...client.Object) client.Client { return fake.NewClientBuilder().WithScheme(createScheme()).WithObjects(initObjs...).Build() } func getMockKubeReader(initObjs ...client.Object) client.Reader { return fake.NewClientBuilder().WithScheme(createScheme()).WithObjects(initObjs...).Build() } func createScheme() *kruntime.Scheme { scheme := kruntime.NewScheme() utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(v1ienode.AddToScheme(scheme)) return scheme } func getControlPlane(name string) *corev1.Node { return &corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: name, Labels: map[string]string{ nodemeta.RoleLabel: string(v1ienode.ControlPlane), }, Annotations: map[string]string{ calico.IPAnnotation: controlPlaneIP, }, }, } } func getWorkerNode(name string) *corev1.Node { return &corev1.Node{ ObjectMeta: metav1.ObjectMeta{ Name: name, Labels: map[string]string{ nodemeta.RoleLabel: string(v1ienode.Worker), }, Annotations: map[string]string{ calico.IPAnnotation: controlPlaneIP, }, }, } } func getIENode(name string, role v1ienode.Role) *v1ienode.IENode { return &v1ienode.IENode{ ObjectMeta: metav1.ObjectMeta{ Name: name, }, Spec: v1ienode.IENodeSpec{ Network: []v1ienode.Network{ { MacAddress: macAddress, }, }, Role: role, }, } } func getTopologyConfigMap() *corev1.ConfigMap { tpInfo := topology.New() tpInfo.ThickPOS = thickPOS return tpInfo.ToConfigMap() } func embedFiles(memFS afero.Fs) error { return fs.WalkDir(embedFS, "testdata/embedfs", func(path string, dir os.DirEntry, err error) error { if err != nil { return err } if dir.IsDir() && (dir.Name() == "testdata" || strings.Contains(path, "embedfs")) { return nil } if dir.IsDir() { return memFS.Mkdir(path, 0644) } file, err := embedFS.ReadFile(path) if err != nil { return err } path = strings.TrimPrefix(path, "testdata/embedfs") return afero.WriteFile(memFS, path, file, 0644) }) }