github.com/cloudfoundry/postgres-release/src/acceptance-tests@v0.0.0-20240511030151-872bdd2e0dba/testing/helpers/validator.go (about) 1 package helpers 2 3 import ( 4 "errors" 5 "fmt" 6 "sort" 7 "strconv" 8 "strings" 9 ) 10 11 type Validator struct { 12 ManifestProps Properties 13 PostgresData PGOutputData 14 PG PGData 15 PostgreSQLVersion string 16 } 17 18 const WrongPostreSQLVersionError = "Actual PostgreSQL version %s should be %s" 19 const MissingDatabaseValidationError = "Database %s has not been created" 20 const ExtraDatabaseValidationError = "Extra database %s has been created" 21 const MissingExtensionValidationError = "Extension %s for database %s has not been created" 22 const ExtraExtensionValidationError = "Extra extension %s for database %s has been created" 23 const MissingRoleValidationError = "Role %s has not been created" 24 const ExtraRoleValidationError = "Extra role %s has been created" 25 const IncorrectRolePrmissionValidationError = "Incorrect permissions for role %s" 26 const IncorrectSettingValidationError = "Incorrect value %v instead of %v for setting %s" 27 const MissingSettingValidationError = "Missing setting %s" 28 29 type PGDBSorter []PGDatabase 30 31 func (a PGDBSorter) Len() int { return len(a) } 32 func (a PGDBSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 33 func (a PGDBSorter) Less(i, j int) bool { 34 return a[j].Name == DefaultDB || (a[i].Name != DefaultDB && a[i].Name < a[j].Name) 35 } 36 37 type PgDBPropsSorter []PgDBProperties 38 39 func (a PgDBPropsSorter) Len() int { return len(a) } 40 func (a PgDBPropsSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 41 func (a PgDBPropsSorter) Less(i, j int) bool { return a[i].Name < a[j].Name } 42 43 type PGTableSorter []PGTable 44 45 func (a PGTableSorter) Len() int { return len(a) } 46 func (a PGTableSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 47 func (a PGTableSorter) Less(i, j int) bool { 48 return a[i].SchemaName < a[j].SchemaName || (a[i].SchemaName == a[j].SchemaName && a[i].TableName < a[j].TableName) 49 } 50 51 type PGColumnSorter []PGTableColumn 52 53 func (a PGColumnSorter) Len() int { return len(a) } 54 func (a PGColumnSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 55 func (a PGColumnSorter) Less(i, j int) bool { return a[i].Position < a[j].Position } 56 57 func NewValidator(props Properties, pgData PGOutputData, pg PGData, postgresqlVersion string) Validator { 58 return Validator{ 59 PostgreSQLVersion: postgresqlVersion, 60 ManifestProps: props, 61 PostgresData: pgData, 62 PG: pg, 63 } 64 } 65 66 func (v Validator) ValidatePostgreSQLVersion() error { 67 if !strings.HasPrefix(v.PostgresData.Version.Version, v.PostgreSQLVersion) { 68 return errors.New(fmt.Sprintf(WrongPostreSQLVersionError, v.PostgresData.Version.Version, v.PostgreSQLVersion)) 69 } 70 return nil 71 } 72 73 func (v Validator) ValidateDatabases() error { 74 actual := v.PostgresData.Databases 75 expected := v.ManifestProps.Databases.Databases 76 sort.Sort(PGDBSorter(actual)) 77 sort.Sort(PgDBPropsSorter(expected)) 78 for idx, actualDB := range actual { 79 if actualDB.Name == DefaultDB && idx > len(expected)-1 { 80 break 81 } 82 if idx > len(expected)-1 || actualDB.Name < expected[idx].Name { 83 if actualDB.Name != DefaultDB { 84 return errors.New(fmt.Sprintf(ExtraDatabaseValidationError, actualDB.Name)) 85 } 86 } 87 if actualDB.Name == DefaultDB || actualDB.Name > expected[idx].Name { 88 return errors.New(fmt.Sprintf(MissingDatabaseValidationError, expected[idx].Name)) 89 } 90 extMap := map[string]bool{ 91 "plpgsql": false, 92 "pgcrypto": false, 93 "citext": false, 94 "pg_stat_statements": false, 95 } 96 for _, dbExt := range actualDB.DBExts { 97 if _, ok := extMap[dbExt.Name]; !ok { 98 return errors.New(fmt.Sprintf(ExtraExtensionValidationError, dbExt.Name, expected[idx].Name)) 99 } 100 extMap[dbExt.Name] = true 101 } 102 if !extMap["pgcrypto"] { 103 return errors.New(fmt.Sprintf(MissingExtensionValidationError, "pgcrypto", expected[idx].Name)) 104 } 105 if expected[idx].CITExt && !extMap["citext"] { 106 return errors.New(fmt.Sprintf(MissingExtensionValidationError, "citext", expected[idx].Name)) 107 } else if !expected[idx].CITExt && extMap["citext"] { 108 return errors.New(fmt.Sprintf(ExtraExtensionValidationError, "citext", expected[idx].Name)) 109 } 110 if v.ManifestProps.Databases.CollectStatementStats && !extMap["pg_stat_statements"] { 111 return errors.New(fmt.Sprintf(MissingExtensionValidationError, "pg_stat_statements", expected[idx].Name)) 112 } else if !v.ManifestProps.Databases.CollectStatementStats && extMap["pg_stat_statements"] { 113 return errors.New(fmt.Sprintf(ExtraExtensionValidationError, "pg_stat_statements", expected[idx].Name)) 114 } 115 } 116 return nil 117 } 118 func (v Validator) ValidateRoles() error { 119 var err error 120 actual := v.PostgresData.Roles 121 expected := v.ManifestProps.Databases.Roles 122 123 for _, expectedRole := range expected { 124 actualRole, ok := actual[expectedRole.Name] 125 if !ok { 126 return errors.New(fmt.Sprintf(MissingRoleValidationError, expectedRole.Name)) 127 } 128 129 defaultRole := PGRole{ 130 Name: actualRole.Name, 131 Super: false, 132 Inherit: true, 133 CreateRole: false, 134 CreateDb: false, 135 CanLogin: true, 136 Replication: false, 137 ConnLimit: -1, 138 ValidUntil: "", 139 } 140 for _, elem := range expectedRole.Permissions { 141 switch { 142 case elem == "SUPERUSER": 143 defaultRole.Super = true 144 case elem == "CREATEDB": 145 defaultRole.CreateDb = true 146 case elem == "CREATEROLE": 147 defaultRole.CreateRole = true 148 case elem == "NOINHERIT": 149 defaultRole.Inherit = false 150 case elem == "NOLOGIN": 151 defaultRole.CanLogin = false 152 case elem == "REPLICATION": 153 defaultRole.Replication = true 154 case strings.Contains(elem, "CONNECTION LIMIT"): 155 value, err := strconv.Atoi(strings.SplitAfter(elem, "CONNECTION LIMIT ")[1]) 156 if err != nil { 157 return err 158 } 159 defaultRole.ConnLimit = value 160 case strings.Contains(elem, "VALID UNTIL"): 161 defaultRole.ValidUntil, err = v.PG.ConvertToPostgresDate(strings.SplitAfter(elem, "VALID UNTIL ")[1]) 162 if err != nil { 163 return err 164 } 165 default: 166 } 167 } 168 if defaultRole != actualRole { 169 return errors.New(fmt.Sprintf(IncorrectRolePrmissionValidationError, actualRole.Name)) 170 } 171 } 172 return nil 173 } 174 175 // TODO cover all setting types 176 // PostgreSQL stores setting as formatted strings 177 // the value in the postgresql.conf may not match the value from pg_settings view 178 // e.g. the shared_buffer property is stored as an int but can be specified as 128MB 179 func (v Validator) MatchSetting(key string, value interface{}) error { 180 settings := v.PostgresData.Settings 181 stringValue := fmt.Sprintf("%v", value) 182 expected, ok := settings[key] 183 if !ok { 184 return errors.New(fmt.Sprintf(MissingSettingValidationError, key)) 185 } else if expected != stringValue { 186 return errors.New(fmt.Sprintf(IncorrectSettingValidationError, stringValue, expected, key)) 187 } 188 return nil 189 } 190 func (v Validator) ValidateSettings() error { 191 var err error 192 props := v.ManifestProps.Databases 193 for key, value := range props.AdditionalConfig { 194 err = v.MatchSetting(key, value) 195 if err != nil { 196 return err 197 } 198 } 199 err = v.MatchSetting("port", props.Port) 200 if err != nil { 201 return err 202 } 203 err = v.MatchSetting("max_connections", props.MaxConnections) 204 if err != nil { 205 return err 206 } 207 err = v.MatchSetting("log_line_prefix", props.LogLinePrefix) 208 if err != nil { 209 return err 210 } 211 return nil 212 } 213 func (v Validator) ValidateAll() error { 214 var err error 215 err = v.ValidateDatabases() 216 if err != nil { 217 return err 218 } 219 err = v.ValidateRoles() 220 if err != nil { 221 return err 222 } 223 err = v.ValidateSettings() 224 if err != nil { 225 return err 226 } 227 err = v.ValidatePostgreSQLVersion() 228 if err != nil { 229 return err 230 } 231 return err 232 } 233 func (v Validator) CompareTablesTo(data PGOutputData) bool { 234 235 aDBs := v.PostgresData.Databases 236 eDBs := data.Databases 237 238 result := (len(aDBs) == len(eDBs)) 239 if result { 240 sort.Sort(PGDBSorter(aDBs)) 241 sort.Sort(PGDBSorter(eDBs)) 242 for i, db := range aDBs { 243 if db.Name != eDBs[i].Name || 244 (len(db.Tables) != len(eDBs[i].Tables)) { 245 result = false 246 break 247 } else if len(db.Tables) > 0 { 248 sort.Sort(PGTableSorter(db.Tables)) 249 sort.Sort(PGTableSorter(eDBs[i].Tables)) 250 for j, table := range db.Tables { 251 vs := eDBs[i].Tables[j] 252 if vs.SchemaName != table.SchemaName || 253 vs.TableName != table.TableName || 254 vs.TableOwner != table.TableOwner || 255 vs.TableRowsCount.Num != table.TableRowsCount.Num || 256 len(vs.TableColumns) != len(table.TableColumns) { 257 result = false 258 break 259 } 260 sort.Sort(PGColumnSorter(vs.TableColumns)) 261 sort.Sort(PGColumnSorter(table.TableColumns)) 262 for k, col := range table.TableColumns { 263 vsc := eDBs[i].Tables[j].TableColumns[k] 264 if vsc.ColumnName != col.ColumnName || 265 vsc.DataType != col.DataType || 266 vsc.Position != col.Position { 267 result = false 268 break 269 } 270 } 271 } 272 } 273 } 274 } 275 return result 276 }