Skip to content

Commit ce8b009

Browse files
authored
fix team member deprecation (#2072)
1 parent 84fe38a commit ce8b009

File tree

6 files changed

+90
-29
lines changed

6 files changed

+90
-29
lines changed

.github/workflows/run_e2e.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ jobs:
1717
go-version: "^1.17.4"
1818
- name: Make dependencies
1919
run: make deps mocks
20+
- name: Code generation
21+
run: make codegen
2022
- name: Compile
2123
run: make linux
2224
- name: Run unit tests

e2e/tests/test_e2e.py

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,8 @@ def test_additional_teams_and_members(self):
250250
}
251251
k8s.update_config(enable_postgres_team_crd)
252252

253+
# add team and member to custom-team-membership
254+
# contains already elephant user
253255
k8s.api.custom_objects_api.patch_namespaced_custom_object(
254256
'acid.zalan.do', 'v1', 'default',
255257
'postgresteams', 'custom-team-membership',
@@ -300,6 +302,13 @@ def test_additional_teams_and_members(self):
300302
self.eventuallyEqual(lambda: len(self.query_database(leader.metadata.name, "postgres", user_query)), 2,
301303
"Database role of replaced member in PostgresTeam not renamed", 10, 5)
302304

305+
# create fake deletion user so operator fails renaming
306+
# but altering role to NOLOGIN will succeed
307+
create_fake_deletion_user = """
308+
CREATE USER tester_delete_me NOLOGIN;
309+
"""
310+
self.query_database(leader.metadata.name, "postgres", create_fake_deletion_user)
311+
303312
# re-add additional member and check if the role is renamed back
304313
k8s.api.custom_objects_api.patch_namespaced_custom_object(
305314
'acid.zalan.do', 'v1', 'default',
@@ -317,12 +326,45 @@ def test_additional_teams_and_members(self):
317326
user_query = """
318327
SELECT rolname
319328
FROM pg_catalog.pg_roles
320-
WHERE (rolname = 'kind' AND rolcanlogin)
321-
OR (rolname = 'tester_delete_me' AND NOT rolcanlogin);
329+
WHERE rolname = 'kind' AND rolcanlogin;
322330
"""
323-
self.eventuallyEqual(lambda: len(self.query_database(leader.metadata.name, "postgres", user_query)), 2,
331+
self.eventuallyEqual(lambda: len(self.query_database(leader.metadata.name, "postgres", user_query)), 1,
324332
"Database role of recreated member in PostgresTeam not renamed back to original name", 10, 5)
325333

334+
user_query = """
335+
SELECT rolname
336+
FROM pg_catalog.pg_roles
337+
WHERE rolname IN ('tester','tester_delete_me') AND NOT rolcanlogin;
338+
"""
339+
self.eventuallyEqual(lambda: len(self.query_database(leader.metadata.name, "postgres", user_query)), 2,
340+
"Database role of replaced member in PostgresTeam not denied from login", 10, 5)
341+
342+
# re-add other additional member, operator should grant LOGIN back to tester
343+
# but nothing happens to deleted role
344+
k8s.api.custom_objects_api.patch_namespaced_custom_object(
345+
'acid.zalan.do', 'v1', 'default',
346+
'postgresteams', 'custom-team-membership',
347+
{
348+
'spec': {
349+
'additionalMembers': {
350+
'e2e': [
351+
'kind',
352+
'tester'
353+
]
354+
},
355+
}
356+
})
357+
358+
user_query = """
359+
SELECT rolname
360+
FROM pg_catalog.pg_roles
361+
WHERE (rolname IN ('tester', 'kind')
362+
AND rolcanlogin)
363+
OR (rolname = 'tester_delete_me' AND NOT rolcanlogin);
364+
"""
365+
self.eventuallyEqual(lambda: len(self.query_database(leader.metadata.name, "postgres", user_query)), 3,
366+
"Database role of deleted member in PostgresTeam not removed when recreated manually", 10, 5)
367+
326368
# revert config change
327369
revert_resync = {
328370
"data": {
@@ -1204,8 +1246,9 @@ def test_node_affinity(self):
12041246
self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, "Operator does not get in sync")
12051247

12061248
# node affinity change should cause another rolling update and relocation of replica
1207-
k8s.wait_for_pod_start('spilo-role=replica,' + cluster_label)
1249+
k8s.wait_for_pod_failover(master_nodes, 'spilo-role=replica,' + cluster_label)
12081250
k8s.wait_for_pod_start('spilo-role=master,' + cluster_label)
1251+
k8s.wait_for_pod_start('spilo-role=replica,' + cluster_label)
12091252

12101253
except timeout_decorator.TimeoutError:
12111254
print('Operator log: {}'.format(k8s.get_operator_log()))
@@ -1956,4 +1999,4 @@ def query_database(self, pod_name, db_name, query):
19561999
return result_set
19572000

19582001
if __name__ == '__main__':
1959-
unittest.main()
2002+
unittest.main()

pkg/cluster/database.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,8 @@ func (c *Cluster) readPgUsersFromDatabase(userNames []string) (users spec.PgUser
231231
parameters[fields[0]] = fields[1]
232232
}
233233

234-
if strings.HasSuffix(rolname, c.OpConfig.RoleDeletionSuffix) {
234+
// consider NOLOGIN roles with deleted suffix as deprecated users
235+
if strings.HasSuffix(rolname, c.OpConfig.RoleDeletionSuffix) && !rolcanlogin {
235236
roldeleted = true
236237
}
237238

pkg/cluster/sync.go

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -104,18 +104,19 @@ func (c *Cluster) Sync(newSpec *acidv1.Postgresql) error {
104104
if !(c.databaseAccessDisabled() || c.getNumberOfInstances(&newSpec.Spec) <= 0 || c.Spec.StandbyCluster != nil) {
105105
c.logger.Debug("syncing roles")
106106
if err = c.syncRoles(); err != nil {
107-
err = fmt.Errorf("could not sync roles: %v", err)
108-
return err
107+
// remember all cached users in c.pgUsers
108+
for cachedUserName, cachedUser := range c.pgUsersCache {
109+
c.pgUsers[cachedUserName] = cachedUser
110+
}
111+
c.logger.Errorf("could not sync roles: %v", err)
109112
}
110113
c.logger.Debug("syncing databases")
111114
if err = c.syncDatabases(); err != nil {
112-
err = fmt.Errorf("could not sync databases: %v", err)
113-
return err
115+
c.logger.Errorf("could not sync databases: %v", err)
114116
}
115117
c.logger.Debug("syncing prepared databases with schemas")
116118
if err = c.syncPreparedDatabases(); err != nil {
117-
err = fmt.Errorf("could not sync prepared database: %v", err)
118-
return err
119+
c.logger.Errorf("could not sync prepared database: %v", err)
119120
}
120121
}
121122

@@ -933,10 +934,7 @@ func (c *Cluster) syncRoles() (err error) {
933934
}
934935
}
935936

936-
// copy map for ProduceSyncRequests to include also system users
937-
for userName, pgUser := range c.pgUsers {
938-
newUsers[userName] = pgUser
939-
}
937+
// search also for system users
940938
for _, systemUser := range c.systemUsers {
941939
userNames = append(userNames, systemUser.Name)
942940
newUsers[systemUser.Name] = systemUser
@@ -950,13 +948,21 @@ func (c *Cluster) syncRoles() (err error) {
950948
// update pgUsers where a deleted role was found
951949
// so that they are skipped in ProduceSyncRequests
952950
for _, dbUser := range dbUsers {
953-
if originalUser, exists := deletedUsers[dbUser.Name]; exists {
954-
recreatedUser := c.pgUsers[originalUser]
951+
originalUsername, foundDeletedUser := deletedUsers[dbUser.Name]
952+
// check if original user does not exist in dbUsers
953+
_, originalUserAlreadyExists := dbUsers[originalUsername]
954+
if foundDeletedUser && !originalUserAlreadyExists {
955+
recreatedUser := c.pgUsers[originalUsername]
955956
recreatedUser.Deleted = true
956-
c.pgUsers[originalUser] = recreatedUser
957+
c.pgUsers[originalUsername] = recreatedUser
957958
}
958959
}
959960

961+
// last but not least copy pgUsers to newUsers to send to ProduceSyncRequests
962+
for _, pgUser := range c.pgUsers {
963+
newUsers[pgUser.Name] = pgUser
964+
}
965+
960966
pgSyncRequests := c.userSyncStrategy.ProduceSyncRequests(dbUsers, newUsers)
961967
if err = c.userSyncStrategy.ExecuteSyncRequests(pgSyncRequests, c.pgDb); err != nil {
962968
return fmt.Errorf("error executing sync statements: %v", err)

pkg/util/users/users.go

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ func (strategy DefaultUserSyncStrategy) ProduceSyncRequests(dbUsers spec.PgUserM
4343

4444
var reqs []spec.PgSyncUserRequest
4545
for name, newUser := range newUsers {
46-
// do not create user that exists in DB with deletion suffix
46+
// do not create user when there exists a user with the same name plus deletion suffix
47+
// instead request a renaming of the deleted user back to the original name (see * below)
4748
if newUser.Deleted {
4849
continue
4950
}
@@ -82,22 +83,28 @@ func (strategy DefaultUserSyncStrategy) ProduceSyncRequests(dbUsers spec.PgUserM
8283
}
8384
}
8485

85-
// No existing roles are deleted or stripped of role membership/flags
86+
// no existing roles are deleted or stripped of role membership/flags
8687
// but team roles will be renamed and denied from LOGIN
8788
for name, dbUser := range dbUsers {
8889
if _, exists := newUsers[name]; !exists {
89-
// toggle LOGIN flag based on role deletion
90-
userFlags := make([]string, len(dbUser.Flags))
91-
userFlags = append(userFlags, dbUser.Flags...)
9290
if dbUser.Deleted {
93-
dbUser.Flags = util.StringSliceReplaceElement(dbUser.Flags, constants.RoleFlagNoLogin, constants.RoleFlagLogin)
91+
// * user with deletion suffix and NOLOGIN found in database
92+
// grant back LOGIN and rename only if original user is wanted and does not exist in database
93+
originalName := strings.TrimSuffix(name, strategy.RoleDeletionSuffix)
94+
_, originalUserWanted := newUsers[originalName]
95+
_, originalUserAlreadyExists := dbUsers[originalName]
96+
if !originalUserWanted || originalUserAlreadyExists {
97+
continue
98+
}
99+
// a deleted dbUser has no NOLOGIN flag, so we can add the LOGIN flag
100+
dbUser.Flags = append(dbUser.Flags, constants.RoleFlagLogin)
94101
} else {
102+
// user found in database and not wanted in newUsers - replace LOGIN flag with NOLOGIN
95103
dbUser.Flags = util.StringSliceReplaceElement(dbUser.Flags, constants.RoleFlagLogin, constants.RoleFlagNoLogin)
96104
}
97-
if !util.IsEqualIgnoreOrder(userFlags, dbUser.Flags) {
98-
reqs = append(reqs, spec.PgSyncUserRequest{Kind: spec.PGsyncUserAlter, User: dbUser})
99-
}
100-
105+
// request ALTER ROLE to grant or revoke LOGIN
106+
reqs = append(reqs, spec.PgSyncUserRequest{Kind: spec.PGsyncUserAlter, User: dbUser})
107+
// request RENAME which will happen on behalf of the pgUser.Deleted field
101108
reqs = append(reqs, spec.PgSyncUserRequest{Kind: spec.PGSyncUserRename, User: dbUser})
102109
}
103110
}

pkg/util/util_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ var substractTest = []struct {
6262
}{
6363
{[]string{"a", "b", "c", "d"}, []string{"a", "b", "c", "d"}, []string{}, true},
6464
{[]string{"a", "b", "c", "d"}, []string{"a", "bb", "c", "d"}, []string{"b"}, false},
65+
{[]string{""}, []string{"b"}, []string{""}, false},
66+
{[]string{"a"}, []string{""}, []string{"a"}, false},
6567
}
6668

6769
var sliceContaintsTest = []struct {

0 commit comments

Comments
 (0)