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  }