package couchctl import ( "testing" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "sigs.k8s.io/yaml" "edge-infra.dev/pkg/edge/apis/meta" persistenceApi "edge-infra.dev/pkg/edge/apis/persistence/v1alpha1" "edge-infra.dev/pkg/edge/controllers/envctl/pkg/nameutils" dsapi "edge-infra.dev/pkg/edge/datasync/apis/v1alpha1" "edge-infra.dev/pkg/edge/datasync/couchdb" "edge-infra.dev/pkg/k8s/unstructured" v1ien "edge-infra.dev/pkg/sds/ien/k8s/apis/v1" nodemeta "edge-infra.dev/pkg/sds/ien/node" "github.com/stretchr/testify/assert" ) var ( replSet = `apiVersion: datasync.edge.ncr.com/v1alpha1 kind: CouchDBReplicationSet metadata: name: "{server_name}" namespace: data-sync-couchdb spec: datasets: - name: "{replication_db}" config: doc_ids: - repl_doc provider: name: "" source: name: "{node_uid}" namespace: "{replication_secret_ns}" target: name: "{server_name}" namespace: data-sync-couchdb ` replSetInvalid = `apiVersion: datasync.edge.ncr.com/v1alpha1 kind: CouchDBReplicationSet metadata: name: "{anything_goes}" namespace: data-sync-couchdb ` statefulSetTemplate = `apiVersion: datasync.edge.ncr.com/v1alpha1 kind: CouchDBPersistence metadata: name: couchdb-lane spec: statefulsets: - apiVersion: apps/v1 kind: StatefulSet metadata: name: "{couchdb_sts}" namespace: data-sync-couchdb labels: platform.edge.ncr.com/component: data-sync-couchdb spec: replicas: 1 selector: matchLabels: platform.edge.ncr.com/component: data-sync-couchdb template: metadata: labels: platform.edge.ncr.com/component: data-sync-couchdb spec: containers: - name: nignx image: nginx volumes: - name: config configMap: name: "{couchdb_sts}" items: - key: inifile path: chart.ini volumeClaimTemplates: - metadata: name: database-storage labels: platform.edge.ncr.com/component: data-sync-couchdb spec: resources: requests: storage: "10Gi" accessModes: - "ReadWriteOnce" serviceName: data-sync-couchdb podManagementPolicy: Parallel ` rdb = "repl-hash-banner-id" ) func TestParseSubstitutions(t *testing.T) { substitutions, err := ParseSubstitutions(replSet) assert.NoError(t, err) assert.Equal(t, len(substitutions), 4) // unique substitutions substitutions, err = ParseSubstitutions(replSetInvalid) assert.Error(t, err) assert.Nil(t, substitutions) } func TestApplySubstitutions(t *testing.T) { suVars := map[SubstitutionVar]string{ ServerName: couchdb.StoreServerName, ServerType: string(dsapi.Store), LaneNumber: "", Suffix: "", CouchDBStatefulSet: couchdb.Namespace, ChirpName: ChirpOldName, ReplicationDB: "repl-db", ReplicationSecret: couchdb.StoreReplicationSecretName, ReplicationSecretNS: ControllerNamespace, NodeUID: "uid", } su := ToSubstitution(suVars) r := &dsapi.CouchDBReplicationSet{} assert.NoError(t, yaml.Unmarshal([]byte(replSet), r)) un, err := ApplySubstitutions(r, su) assert.NoError(t, err) assert.NotNil(t, un) assert.NoError(t, unstructured.FromUnstructured(un, r)) assert.Equal(t, r.Name, couchdb.StoreServerName) assert.Equal(t, r.Spec.Datasets[0].Name, "repl-db") assert.Equal(t, r.Spec.Source.Name, "uid") assert.Equal(t, r.Spec.Source.Namespace, ControllerNamespace) assert.Equal(t, r.Spec.Target.Name, couchdb.StoreServerName) assert.Equal(t, r.Labels[couchdb.SubstitutionLabel], "true") } func TestStoreSubstitution(t *testing.T) { // mostly static variables su := StoreSubstitution(rdb) assert.Equal(t, su.ServerName, couchdb.StoreServerName) assert.Equal(t, su.ServerType, dsapi.Store) assert.Equal(t, su.LaneNumber, "") assert.Equal(t, su.CouchDBStatefulSet, couchdb.Namespace) assert.Equal(t, su.ChirpStatefulSet, ChirpOldName) assert.Equal(t, su.ReplicationDB, rdb) assert.Equal(t, su.ReplicationSecret, couchdb.StoreReplicationSecretName) assert.Equal(t, su.ReplicationSecretNS, ControllerNamespace) } func TestLaneSubstitution(t *testing.T) { ni := &nameutils.NodeInfo{ UID: "leader-uid", Hostname: defaultHost, Lane: "", Class: v1ien.Server, Role: v1ien.ControlPlane, } su := LaneSubstitution(ni, nil, rdb, "leader-uid") assert.True(t, su.Leader) assert.Equal(t, su.NodeInfo, ni) assert.Equal(t, su.ServerName, "couchdb-leader-uid") assert.Equal(t, su.ServerType, dsapi.Store) assert.Equal(t, su.LaneNumber, "") assert.Equal(t, su.CouchDBStatefulSet, "couchdb-"+meta.Hash("leader-uid")) assert.Equal(t, su.ChirpStatefulSet, "chirp-"+meta.Hash("leader-uid")) assert.Equal(t, su.ReplicationDB, rdb) assert.Equal(t, su.ReplicationSecret, couchdb.StoreReplicationSecretName) assert.Equal(t, su.ReplicationSecretNS, ControllerNamespace) assert.Equal(t, su.NodeUID, "leader-uid") ni = &nameutils.NodeInfo{ UID: "uid", Name: "test", Hostname: "ien", Lane: "1", Class: v1ien.Touchpoint, Role: v1ien.Worker, } m := map[string]map[string]string{} m["data-sync-couchdb"] = map[string]string{ "test": "1", } m["data-sync-messaging"] = map[string]string{ "test": "1", } // old pvc prefix su = LaneSubstitution(ni, m, rdb, "leader-uid") assert.False(t, su.Leader) assert.Equal(t, su.NodeInfo, ni) assert.Equal(t, su.ServerName, "couchdb-uid") assert.Equal(t, su.ServerType, dsapi.Touchpoint) assert.Equal(t, su.LaneNumber, "1") assert.Equal(t, su.CouchDBStatefulSet, "data-sync-couchdb-1") assert.Equal(t, su.ChirpStatefulSet, "data-sync-messaging-1") assert.Equal(t, su.ReplicationDB, rdb) assert.Equal(t, su.ReplicationSecret, "couchdb-leader-uid") assert.Equal(t, su.ReplicationSecretNS, couchdb.Namespace) assert.Equal(t, su.NodeUID, "uid") } func TestStoreStatefulSetSubstitution(t *testing.T) { su := StoreSubstitution(rdb) p := &dsapi.CouchDBPersistence{} err := yaml.Unmarshal([]byte(statefulSetTemplate), p) assert.NoError(t, err) sts := &p.Spec.StatefulSets[0] // Store Server StatefulSet Substitution un, err := ApplySubstitutions(sts, su) assert.NoError(t, err) assert.NotNil(t, un) sts = &appsv1.StatefulSet{} err = unstructured.FromUnstructured(un, sts) assert.NoError(t, err) assert.Equal(t, sts.Name, su.CouchDBStatefulSet) assert.Equal(t, sts.Spec.Selector.MatchLabels[persistenceApi.InstanceLabel], sts.Name) assert.Equal(t, sts.Spec.Template.ObjectMeta.Labels[persistenceApi.InstanceLabel], sts.Name) assert.NotContains(t, sts.Spec.Template.ObjectMeta.Labels, nodemeta.ClassLabel) assert.Equal(t, sts.Spec.Template.Spec.Volumes[0].ConfigMap.Name, su.CouchDBStatefulSet) na := sts.Spec.Template.Spec.Affinity.NodeAffinity assert.Nil(t, na.RequiredDuringSchedulingIgnoredDuringExecution) assert.Len(t, na.PreferredDuringSchedulingIgnoredDuringExecution, 1) sch := na.PreferredDuringSchedulingIgnoredDuringExecution[0] assert.Equal(t, int32(100), sch.Weight) assert.Empty(t, sch.Preference.MatchFields) assert.Len(t, sch.Preference.MatchExpressions, 1) assert.Equal(t, sch.Preference.MatchExpressions[0], corev1.NodeSelectorRequirement{ Key: nodemeta.RoleLabel, Operator: corev1.NodeSelectorOpIn, Values: []string{string(v1ien.ControlPlane)}, }) } func TestLaneStatefulSetSubstitution(t *testing.T) { ni := &nameutils.NodeInfo{ UID: "uid", Hostname: defaultHost, Lane: "1", Class: v1ien.Touchpoint, Role: v1ien.Worker, } su := LaneSubstitution(ni, nil, rdb, "leader-uid") p := &dsapi.CouchDBPersistence{} err := yaml.Unmarshal([]byte(statefulSetTemplate), p) assert.NoError(t, err) sts := &p.Spec.StatefulSets[0] // Lane StatefulSet Substitution un, err := ApplySubstitutions(sts, su) assert.NoError(t, err) assert.NotNil(t, un) sts = &appsv1.StatefulSet{} err = unstructured.FromUnstructured(un, sts) assert.NoError(t, err) assert.Equal(t, sts.Name, su.CouchDBStatefulSet) assert.Equal(t, sts.Labels[couchdb.NodeUIDLabel], "uid") assert.Equal(t, sts.Spec.Selector.MatchLabels[persistenceApi.InstanceLabel], sts.Name) assert.Equal(t, sts.Spec.Template.ObjectMeta.Labels[persistenceApi.InstanceLabel], sts.Name) assert.Equal(t, sts.Spec.Template.Spec.Volumes[0].ConfigMap.Name, su.CouchDBStatefulSet) na := sts.Spec.Template.Spec.Affinity.NodeAffinity assert.Nil(t, na.PreferredDuringSchedulingIgnoredDuringExecution) assert.NotNil(t, na.RequiredDuringSchedulingIgnoredDuringExecution) nodeSelectors := na.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms assert.Len(t, nodeSelectors, 1) nodeSelector := nodeSelectors[0] assert.Empty(t, nodeSelector.MatchFields) assert.Len(t, nodeSelector.MatchExpressions, 1) assert.Equal(t, nodeSelector.MatchExpressions[0], corev1.NodeSelectorRequirement{ Key: couchdb.NodeUIDLabel, Operator: corev1.NodeSelectorOpIn, Values: []string{su.NodeUID}, }) }