diff --git a/config/crd/bases/psmdb.percona.com_perconaservermongodbs.yaml b/config/crd/bases/psmdb.percona.com_perconaservermongodbs.yaml index 09f89e40e..8da605195 100644 --- a/config/crd/bases/psmdb.percona.com_perconaservermongodbs.yaml +++ b/config/crd/bases/psmdb.percona.com_perconaservermongodbs.yaml @@ -6206,6 +6206,10 @@ spec: type: string type: object type: object + primaryPreferTagSelector: + additionalProperties: + type: string + type: object priorityClassName: type: string readinessProbe: @@ -13792,6 +13796,10 @@ spec: type: string type: object type: object + primaryPreferTagSelector: + additionalProperties: + type: string + type: object priorityClassName: type: string readinessProbe: diff --git a/deploy/bundle.yaml b/deploy/bundle.yaml index e57ad5044..19f1fae1a 100644 --- a/deploy/bundle.yaml +++ b/deploy/bundle.yaml @@ -2217,6 +2217,10 @@ spec: - type: string x-kubernetes-int-or-string: true type: object + primaryPreferTagSelector: + additionalProperties: + type: string + type: object priorityClassName: type: string resources: @@ -4868,6 +4872,10 @@ spec: type: string type: object type: object + primaryPreferTagSelector: + additionalProperties: + type: string + type: object priorityClassName: type: string readinessProbe: diff --git a/deploy/crd.yaml b/deploy/crd.yaml index 6c2ee036b..baff21132 100644 --- a/deploy/crd.yaml +++ b/deploy/crd.yaml @@ -6879,6 +6879,10 @@ spec: type: string type: object type: object + primaryPreferTagSelector: + additionalProperties: + type: string + type: object priorityClassName: type: string readinessProbe: @@ -14465,6 +14469,10 @@ spec: type: string type: object type: object + primaryPreferTagSelector: + additionalProperties: + type: string + type: object priorityClassName: type: string readinessProbe: diff --git a/deploy/cw-bundle.yaml b/deploy/cw-bundle.yaml index db45f99da..571343cb8 100644 --- a/deploy/cw-bundle.yaml +++ b/deploy/cw-bundle.yaml @@ -2217,6 +2217,10 @@ spec: - type: string x-kubernetes-int-or-string: true type: object + primaryPreferTagSelector: + additionalProperties: + type: string + type: object priorityClassName: type: string resources: @@ -4868,6 +4872,10 @@ spec: type: string type: object type: object + primaryPreferTagSelector: + additionalProperties: + type: string + type: object priorityClassName: type: string readinessProbe: diff --git a/e2e-tests/version-service/conf/crd.yaml b/e2e-tests/version-service/conf/crd.yaml index 6c2ee036b..baff21132 100644 --- a/e2e-tests/version-service/conf/crd.yaml +++ b/e2e-tests/version-service/conf/crd.yaml @@ -6879,6 +6879,10 @@ spec: type: string type: object type: object + primaryPreferTagSelector: + additionalProperties: + type: string + type: object priorityClassName: type: string readinessProbe: @@ -14465,6 +14469,10 @@ spec: type: string type: object type: object + primaryPreferTagSelector: + additionalProperties: + type: string + type: object priorityClassName: type: string readinessProbe: diff --git a/pkg/apis/psmdb/v1/psmdb_types.go b/pkg/apis/psmdb/v1/psmdb_types.go index debbf2b79..d01da1811 100644 --- a/pkg/apis/psmdb/v1/psmdb_types.go +++ b/pkg/apis/psmdb/v1/psmdb_types.go @@ -581,25 +581,28 @@ func (conf *MongoConfiguration) SetDefaults() error { type HorizonsSpec map[string]map[string]string +type PrimaryPreferTagSelectorSpec map[string]string + type ReplsetSpec struct { MultiAZ `json:",inline"` - Name string `json:"name,omitempty"` - Size int32 `json:"size"` - ClusterRole ClusterRole `json:"clusterRole,omitempty"` - Arbiter Arbiter `json:"arbiter,omitempty"` - Expose ExposeTogglable `json:"expose,omitempty"` - VolumeSpec *VolumeSpec `json:"volumeSpec,omitempty"` - ReadinessProbe *corev1.Probe `json:"readinessProbe,omitempty"` - LivenessProbe *LivenessProbeExtended `json:"livenessProbe,omitempty"` - PodSecurityContext *corev1.PodSecurityContext `json:"podSecurityContext,omitempty"` - ContainerSecurityContext *corev1.SecurityContext `json:"containerSecurityContext,omitempty"` - Storage *MongodSpecStorage `json:"storage,omitempty"` - Configuration MongoConfiguration `json:"configuration,omitempty"` - ExternalNodes []*ExternalNode `json:"externalNodes,omitempty"` - NonVoting NonVotingSpec `json:"nonvoting,omitempty"` - HostAliases []corev1.HostAlias `json:"hostAliases,omitempty"` - Horizons HorizonsSpec `json:"splitHorizons,omitempty"` + Name string `json:"name,omitempty"` + Size int32 `json:"size"` + ClusterRole ClusterRole `json:"clusterRole,omitempty"` + Arbiter Arbiter `json:"arbiter,omitempty"` + Expose ExposeTogglable `json:"expose,omitempty"` + VolumeSpec *VolumeSpec `json:"volumeSpec,omitempty"` + ReadinessProbe *corev1.Probe `json:"readinessProbe,omitempty"` + LivenessProbe *LivenessProbeExtended `json:"livenessProbe,omitempty"` + PodSecurityContext *corev1.PodSecurityContext `json:"podSecurityContext,omitempty"` + ContainerSecurityContext *corev1.SecurityContext `json:"containerSecurityContext,omitempty"` + Storage *MongodSpecStorage `json:"storage,omitempty"` + Configuration MongoConfiguration `json:"configuration,omitempty"` + ExternalNodes []*ExternalNode `json:"externalNodes,omitempty"` + NonVoting NonVotingSpec `json:"nonvoting,omitempty"` + HostAliases []corev1.HostAlias `json:"hostAliases,omitempty"` + Horizons HorizonsSpec `json:"splitHorizons,omitempty"` + PrimaryPreferTagSelector PrimaryPreferTagSelectorSpec `json:"primaryPreferTagSelector,omitempty"` } func (r *ReplsetSpec) PodName(cr *PerconaServerMongoDB, idx int) string { diff --git a/pkg/apis/psmdb/v1/zz_generated.deepcopy.go b/pkg/apis/psmdb/v1/zz_generated.deepcopy.go index 548bc2bb8..1e86f8d46 100644 --- a/pkg/apis/psmdb/v1/zz_generated.deepcopy.go +++ b/pkg/apis/psmdb/v1/zz_generated.deepcopy.go @@ -1363,6 +1363,27 @@ func (in *PodDisruptionBudgetSpec) DeepCopy() *PodDisruptionBudgetSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in PrimaryPreferTagSelectorSpec) DeepCopyInto(out *PrimaryPreferTagSelectorSpec) { + { + in := &in + *out = make(PrimaryPreferTagSelectorSpec, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrimaryPreferTagSelectorSpec. +func (in PrimaryPreferTagSelectorSpec) DeepCopy() PrimaryPreferTagSelectorSpec { + if in == nil { + return nil + } + out := new(PrimaryPreferTagSelectorSpec) + in.DeepCopyInto(out) + return *out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ReplsetMemberStatus) DeepCopyInto(out *ReplsetMemberStatus) { *out = *in @@ -1451,6 +1472,13 @@ func (in *ReplsetSpec) DeepCopyInto(out *ReplsetSpec) { (*out)[key] = outVal } } + if in.PrimaryPreferTagSelector != nil { + in, out := &in.PrimaryPreferTagSelector, &out.PrimaryPreferTagSelector + *out = make(PrimaryPreferTagSelectorSpec, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReplsetSpec. diff --git a/pkg/controller/perconaservermongodb/mgo.go b/pkg/controller/perconaservermongodb/mgo.go index a671b280a..83d5995aa 100644 --- a/pkg/controller/perconaservermongodb/mgo.go +++ b/pkg/controller/perconaservermongodb/mgo.go @@ -291,6 +291,10 @@ func (r *ReconcilePerconaServerMongoDB) updateConfigMembers(ctx context.Context, Votes: mongo.DefaultVotes, } + if compareTags(nodeLabels, rs.PrimaryPreferTagSelector) { + member.Priority = mongo.DefaultPriority + 1 + } + if len(rs.Horizons) > 0 { horizons := make(map[string]string) for h, domain := range rs.Horizons[pod.Name] { @@ -411,7 +415,7 @@ func (r *ReconcilePerconaServerMongoDB) updateConfigMembers(ctx context.Context, } currMembers := append(mongo.ConfigMembers(nil), cnf.Members...) - cnf.Members.SetVotes(unsafePSA) + cnf.Members.SetVotes(members, unsafePSA) if !reflect.DeepEqual(currMembers, cnf.Members) { cnf.Version++ @@ -726,6 +730,19 @@ func comparePrivileges(x []mongo.RolePrivilege, y []mongo.RolePrivilege) bool { return true } +func compareTags(tags mongo.ReplsetTags, selector api.PrimaryPreferTagSelectorSpec) bool { + if len(selector) == 0 { + return false + } + for tag, v := range selector { + if val, ok := tags[tag]; ok && val == v { + continue + } + return false + } + return true +} + func (r *ReconcilePerconaServerMongoDB) createOrUpdateSystemRoles(ctx context.Context, cli mongo.Client, role string, privileges []mongo.RolePrivilege) error { roleInfo, err := cli.GetRole(ctx, role) if err != nil { diff --git a/pkg/controller/perconaservermongodb/mgo_test.go b/pkg/controller/perconaservermongodb/mgo_test.go new file mode 100644 index 000000000..2aa6e2829 --- /dev/null +++ b/pkg/controller/perconaservermongodb/mgo_test.go @@ -0,0 +1,58 @@ +package perconaservermongodb + +import ( + "testing" + + api "github.com/percona/percona-server-mongodb-operator/pkg/apis/psmdb/v1" + "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/mongo" +) + +func TestCompareTags(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + mongoTags mongo.ReplsetTags + selectorTags api.PrimaryPreferTagSelectorSpec + expected bool + }{ + { + name: "empty tags", + mongoTags: mongo.ReplsetTags{}, + selectorTags: api.PrimaryPreferTagSelectorSpec{}, + expected: false, + }, + { + name: "selector with podName", + mongoTags: mongo.ReplsetTags{}, + selectorTags: api.PrimaryPreferTagSelectorSpec{"podName": "test"}, + expected: false, + }, + { + name: "match selector with podName", + mongoTags: mongo.ReplsetTags{"podName": "test"}, + selectorTags: api.PrimaryPreferTagSelectorSpec{"podName": "test"}, + expected: true, + }, + { + name: "match selector with podName and other tags", + mongoTags: mongo.ReplsetTags{"podName": "test", "other": "tag"}, + selectorTags: api.PrimaryPreferTagSelectorSpec{"podName": "test"}, + expected: true, + }, + { + name: "match two selectors with podName and other tags", + mongoTags: mongo.ReplsetTags{"podName": "test", "other": "tag"}, + selectorTags: api.PrimaryPreferTagSelectorSpec{"podName": "test", "other": "tag"}, + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := compareTags(tt.mongoTags, tt.selectorTags); got != tt.expected { + t.Errorf("compareTags() = %v, want %v", got, tt.expected) + } + }) + } +} diff --git a/pkg/psmdb/mongo/mongo.go b/pkg/psmdb/mongo/mongo.go index 5f988d1f7..8d9f9680c 100644 --- a/pkg/psmdb/mongo/mongo.go +++ b/pkg/psmdb/mongo/mongo.go @@ -791,9 +791,20 @@ func (m *ConfigMembers) AddNew(from ConfigMembers) bool { } // SetVotes sets voting parameters for members list -func (m *ConfigMembers) SetVotes(unsafePSA bool) { +func (m *ConfigMembers) SetVotes(compareWith ConfigMembers, unsafePSA bool) { votes := 0 lastVoteIdx := -1 + + cm := make(map[string]int, len(compareWith)) + + for _, member := range compareWith { + if member.ArbiterOnly { + continue + } + + cm[member.Host] = member.Priority + } + for i, member := range *m { if member.Hidden { continue @@ -834,7 +845,12 @@ func (m *ConfigMembers) SetVotes(unsafePSA bool) { // In unsafe PSA (Primary with a Secondary and an Arbiter), // we are unable to set the votes and the priority simultaneously. // Therefore, setting only the votes. - []ConfigMember(*m)[i].Priority = DefaultPriority + priority := DefaultPriority + if c, ok := cm[member.Host]; ok && c > 0 { + priority = c + } + + []ConfigMember(*m)[i].Priority = priority } } } else if member.ArbiterOnly { diff --git a/pkg/psmdb/mongo/mongo_test.go b/pkg/psmdb/mongo/mongo_test.go index 6dcae2c65..e2d36f3d2 100644 --- a/pkg/psmdb/mongo/mongo_test.go +++ b/pkg/psmdb/mongo/mongo_test.go @@ -281,7 +281,7 @@ func TestVoting(t *testing.T) { } for _, c := range cases { - c.mset.SetVotes(false) + c.mset.SetVotes(*c.mset, false) if len(*c.mset) != len(*c.desiered) { t.Errorf("%s: missmatched members size", c.name) continue