From 94e175eb12beeea3a620b2d56e8c9f77f14ad6e8 Mon Sep 17 00:00:00 2001 From: Murat Kabilov Date: Wed, 24 May 2017 18:37:36 +0200 Subject: [PATCH 01/14] fix NamespacedName decoder --- pkg/spec/types.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pkg/spec/types.go b/pkg/spec/types.go index 864f51928..7da8eb497 100644 --- a/pkg/spec/types.go +++ b/pkg/spec/types.go @@ -1,6 +1,8 @@ package spec import ( + "fmt" + "strings" "database/sql" "k8s.io/client-go/pkg/api/v1" @@ -62,10 +64,6 @@ type UserSyncer interface { } func (n NamespacedName) String() string { - if n.Namespace == "" && n.Name == "" { - return "" - } - return types.NamespacedName(n).String() } @@ -75,9 +73,16 @@ func (n NamespacedName) MarshalJSON() ([]byte, error) { func (n *NamespacedName) Decode(value string) error { name := types.NewNamespacedNameFromString(value) - if value != "" && name == (types.NamespacedName{}) { + + if strings.Trim(value, string(types.Separator)) != "" && name == (types.NamespacedName{}) { name.Name = value name.Namespace = v1.NamespaceDefault + } else if name.Namespace == "" { + name.Namespace = v1.NamespaceDefault + } + + if name.Name == "" { + return fmt.Errorf("Incorrect namespaced name") } *n = NamespacedName(name) From 86d9b8c71cc55abe128ec4b70ec3ea19db215f65 Mon Sep 17 00:00:00 2001 From: Murat Kabilov Date: Wed, 24 May 2017 18:38:03 +0200 Subject: [PATCH 02/14] fix weekday parsing in MaintenanceWindow --- pkg/spec/postgresql.go | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/pkg/spec/postgresql.go b/pkg/spec/postgresql.go index e00625c7d..d673529f0 100644 --- a/pkg/spec/postgresql.go +++ b/pkg/spec/postgresql.go @@ -12,8 +12,6 @@ import ( "k8s.io/client-go/pkg/api/v1" ) -var alphaRegexp = regexp.MustCompile("^[a-zA-Z]*$") - type MaintenanceWindow struct { StartTime time.Time // Start time StartWeekday time.Weekday // Start weekday @@ -96,7 +94,10 @@ type PostgresqlList struct { Items []Postgresql `json:"items"` } -func parseTime(s string) (t time.Time, wd time.Weekday, wdProvided bool, err error) { +var alphaRegexp = regexp.MustCompile("^[a-zA-Z]*$") +var weekdays = map[string]int{"Sun":0, "Mon":1, "Tue":2, "Wed":3, "Thu":4, "Fri":5, "Sat":6} + +func ParseTime(s string) (t time.Time, wd time.Weekday, wdProvided bool, err error) { var timeLayout string parts := strings.Split(s, ":") @@ -107,6 +108,12 @@ func parseTime(s string) (t time.Time, wd time.Weekday, wdProvided bool, err err } timeLayout = "Mon:15:04" wdProvided = true + weekday, ok := weekdays[parts[0]] + if !ok { + err = fmt.Errorf("incorrect weekday") + return + } + wd = time.Weekday(weekday) } else { wdProvided = false timeLayout = "15:04" @@ -116,8 +123,6 @@ func parseTime(s string) (t time.Time, wd time.Weekday, wdProvided bool, err err if err != nil { return } - - wd = tp.Weekday() t = tp.UTC() return @@ -125,7 +130,7 @@ func parseTime(s string) (t time.Time, wd time.Weekday, wdProvided bool, err err func (m *MaintenanceWindow) MarshalJSON() ([]byte, error) { var startWd, endWd string - if m.StartWeekday == time.Sunday && m.EndWeekday == time.Saturday { + if m.StartWeekday == time.Monday && m.EndWeekday == time.Sunday { startWd = "" endWd = "" } else { @@ -151,12 +156,12 @@ func (m *MaintenanceWindow) UnmarshalJSON(data []byte) error { return fmt.Errorf("incorrect maintenance window format") } - got.StartTime, got.StartWeekday, weekdayProvidedFrom, err = parseTime(parts[0]) + got.StartTime, got.StartWeekday, weekdayProvidedFrom, err = ParseTime(parts[0]) if err != nil { return err } - got.EndTime, got.EndWeekday, weekdayProvidedTo, err = parseTime(parts[1]) + got.EndTime, got.EndWeekday, weekdayProvidedTo, err = ParseTime(parts[1]) if err != nil { return err } @@ -165,9 +170,13 @@ func (m *MaintenanceWindow) UnmarshalJSON(data []byte) error { return fmt.Errorf("'From' time must be prior to the 'To' time") } + if (int(got.StartWeekday) + 6) % 7 > (int(got.EndWeekday) + 6) % 7 { + return fmt.Errorf("'From' weekday must be prior to the 'To' weekday") + } + if !weekdayProvidedFrom || !weekdayProvidedTo { - got.StartWeekday = time.Sunday - got.EndWeekday = time.Saturday + got.StartWeekday = time.Monday + got.EndWeekday = time.Sunday } *m = got @@ -191,11 +200,17 @@ func (pl *PostgresqlList) GetListMeta() unversioned.List { return &pl.Metadata } -func clusterName(clusterName string, teamName string) (string, error) { +func extractClusterName(clusterName string, teamName string) (string, error) { teamNameLen := len(teamName) + if len(clusterName) < teamNameLen+2 { return "", fmt.Errorf("name is too short") } + + if teamNameLen == 0 { + return "", fmt.Errorf("Team name is empty") + } + if strings.ToLower(clusterName[:teamNameLen+1]) != strings.ToLower(teamName)+"-" { return "", fmt.Errorf("name must match {TEAM}-{NAME} format") } @@ -228,7 +243,7 @@ func (p *Postgresql) UnmarshalJSON(data []byte) error { } tmp2 := Postgresql(tmp) - clusterName, err := clusterName(tmp2.Metadata.Name, tmp2.Spec.TeamID) + clusterName, err := extractClusterName(tmp2.Metadata.Name, tmp2.Spec.TeamID) if err == nil { tmp2.Spec.ClusterName = clusterName } else { From 295b00b3796d3349c8ca43e0700db02a14920c3b Mon Sep 17 00:00:00 2001 From: Murat Kabilov Date: Wed, 24 May 2017 18:38:15 +0200 Subject: [PATCH 03/14] postgresql spec tests --- pkg/spec/postgresql_test.go | 347 ++++++++++++++++++++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100644 pkg/spec/postgresql_test.go diff --git a/pkg/spec/postgresql_test.go b/pkg/spec/postgresql_test.go new file mode 100644 index 000000000..0ec47821d --- /dev/null +++ b/pkg/spec/postgresql_test.go @@ -0,0 +1,347 @@ +package spec + +import ( + "reflect" + "testing" + "time" + "encoding/json" + "bytes" + + "k8s.io/client-go/pkg/api/unversioned" + "k8s.io/client-go/pkg/api/v1" +) + +var pTests = []struct { + s string + time time.Time + weekday time.Weekday + weekdayProvided bool +}{ + {"Mon:16:08", mustParseTime("16:08"), time.Monday, true}, + {"Sun:11:00", mustParseTime("11:00"), time.Sunday, true}, + {"23:59", mustParseTime("23:59"), time.Weekday(0), false}, +} + +var pErr = []string{"Thr:00:12", "26:09", "Std:26:09", "Saturday:00:00"} + +var clusterNames = []struct { + s string + team string + clusterName string +}{ + {"acid-test", "acid", "test"}, + {"test-my-name", "test", "my-name"}, + {"my-team-another-test", "my-team", "another-test"}, + {"------strange-team-cluster", "-----", "strange-team-cluster"}, +} + +var wrongClusterNames = []struct { + s string + team string +}{ + {"acid-test", "test"}, + {"-test", ""}, + {"-test", "-"}, + {"", "-"}, + {"-", "-"}, +} + +var maintenanceWindows = []struct { + s string + m MaintenanceWindow +}{{`"10:00-20:00"`, + MaintenanceWindow{ + StartTime: mustParseTime("10:00"), + StartWeekday: time.Monday, + EndTime: mustParseTime("20:00"), + EndWeekday: time.Sunday, + }}, + {`"Tue:10:00-Sun:23:00"`, + MaintenanceWindow{ + StartTime: mustParseTime("10:00"), + StartWeekday: time.Tuesday, + EndTime: mustParseTime("23:00"), + EndWeekday: time.Sunday, + }}, + {`"Mon:10:00-Mon:10:00"`, + MaintenanceWindow{ + StartTime: mustParseTime("10:00"), + StartWeekday: time.Monday, + EndTime: mustParseTime("10:00"), + EndWeekday: time.Monday, + }}, + {`"Sun:00:00-Sun:00:00"`, + MaintenanceWindow{ + StartTime: mustParseTime("00:00"), + StartWeekday: time.Sunday, + EndTime: mustParseTime("00:00"), + EndWeekday: time.Sunday, + }}, + {`"00:00-10:00"`, + MaintenanceWindow{ + StartTime: mustParseTime("00:00"), + StartWeekday: time.Monday, + EndTime: mustParseTime("10:00"), + EndWeekday: time.Sunday, + }}, + {`"00:00-00:00"`, + MaintenanceWindow{ + StartTime: mustParseTime("00:00"), + StartWeekday: time.Monday, + EndTime: mustParseTime("00:00"), + EndWeekday: time.Sunday, + }}, +} + +var wrongMaintenanceWindows = [][]byte{ + []byte(`"Mon:12:00-Sun:11:00"`), + []byte(`"Mon:12:00-Mon:11:00"`), + []byte(`"Wed:00:00-Tue:26:00"`), + []byte(`"Sun:00:00-Mon:00:00"`), + []byte(`"Wed:00:00-Mon:10:00"`), + []byte(`"10:00-00:00"`), + []byte(`"Mon:00:00:00-Tue:10:00:00"`), + []byte(`"Mon:00:00"`), +} + +var cl = []byte(`{ + "kind": "Postgresql", + "apiVersion": "acid.zalan.do/v1", + "metadata": { + "name": "acid-testcluster1" + }, + "spec": { + "teamId": "ACID", + "volume": { + "size": "5Gi", + "storageClass": "SSD" + }, + "numberOfInstances": 2, + "users": { + "zalando": [ + "superuser", + "createdb" + ] + }, + "allowedSourceRanges": [ + "127.0.0.1/32" + ], + "postgresql": { + "version": "9.6", + "parameters": { + "shared_buffers": "32MB", + "max_connections": "10", + "log_statement": "all" + } + }, + "resources": { + "requests": { + "cpu": "10m", + "memory": "50Mi" + }, + "limits": { + "cpu": "300m", + "memory": "3000Mi" + } + }, + "patroni": { + "initdb": { + "encoding": "UTF8", + "locale": "en_US.UTF-8", + "data-checksums": "true" + }, + "pg_hba": [ + "hostssl all all 0.0.0.0/0 md5", + "host all all 0.0.0.0/0 md5" + ], + "ttl": 30, + "loop_wait": 10, + "retry_timeout": 10, + "maximum_lag_on_failover": 33554432 + }, + "maintenanceWindows": [ + "01:00-06:00", + "Sat:00:00-Sat:04:00" + ] + } +}`) + +var clExp = Postgresql{ + TypeMeta: unversioned.TypeMeta{ + Kind: "Postgresql", + APIVersion: "acid.zalan.do/v1", + }, + Metadata: v1.ObjectMeta{ + Name: "acid-testcluster1", + }, + Spec: PostgresSpec{ + PostgresqlParam: PostgresqlParam{ + PgVersion: "9.6", + Parameters: map[string]string{ + "shared_buffers": "32MB", + "max_connections": "10", + "log_statement": "all", + }, + }, + Volume: Volume{ + Size: "5Gi", + StorageClass: "SSD", + }, + Patroni: Patroni{ + InitDB: map[string]string{ + "encoding": "UTF8", + "locale": "en_US.UTF-8", + "data-checksums": "true", + }, + PgHba: []string{"hostssl all all 0.0.0.0/0 md5", "host all all 0.0.0.0/0 md5"}, + TTL: 30, + LoopWait: 10, + RetryTimeout: 10, + MaximumLagOnFailover: 33554432, + }, + Resources: Resources{ + ResourceRequest: ResourceDescription{CPU: "10m", Memory: "50Mi"}, + ResourceLimits: ResourceDescription{CPU: "300m", Memory: "3000Mi"}, + }, + TeamID: "ACID", + AllowedSourceRanges: []string{"127.0.0.1/32"}, + NumberOfInstances: 2, + Users: map[string]UserFlags{"zalando": UserFlags{"superuser", "createdb"}}, + MaintenanceWindows: []MaintenanceWindow{MaintenanceWindow{ + StartWeekday: time.Monday, + StartTime: mustParseTime("01:00"), + EndTime: mustParseTime("06:00"), + EndWeekday: time.Sunday, + }, + MaintenanceWindow{ + StartWeekday: time.Saturday, + StartTime: mustParseTime("00:00"), + EndTime: mustParseTime("04:00"), + EndWeekday: time.Saturday, + }, + }, + ClusterName: "testcluster1", + }, + Status: "", + Error: nil, +} + +func mustParseTime(s string) time.Time { + v, err := time.Parse("15:04", s) + if err != nil { + panic(err) + } + + return v +} + +func TestParseTime(t *testing.T) { + for _, tt := range pTests { + aTime, weekday, weekdayProvided, err := ParseTime(tt.s) + if err != nil { + t.Errorf("ParseTime error: %v", err) + } + + if aTime != tt.time { + t.Errorf("Expected time: %v, got: %v", tt.time, aTime) + } + + if weekday != tt.weekday { + t.Errorf("Expected weekday: %v, got: %v", tt.weekday, weekday) + } + + if weekdayProvided != tt.weekdayProvided { + t.Errorf("Expected weekdayProvided: %t, got: %t", tt.weekdayProvided, weekdayProvided) + } + } +} + +func TestParseTimeError(t *testing.T) { + for _, tt := range pErr { + _, _, _, err := ParseTime(tt) + if err == nil { + t.Errorf("Error expected for '%s'", tt) + } + } +} + +func TestClusterName(t *testing.T) { + for _, tt := range clusterNames { + name, err := extractClusterName(tt.s, tt.team) + if err != nil { + t.Errorf("extractClusterName error: %v", err) + } + if name != tt.clusterName { + t.Errorf("Expected cluserName: %s, got: %s", tt.clusterName, name) + } + } +} + +func TestClusterNameError(t *testing.T) { + for _, tt := range wrongClusterNames { + _, err := extractClusterName(tt.s, tt.team) + if err == nil { + t.Errorf("Error expected for '%s'", tt) + } + } +} + +func TestUnmarshalMaintenanceWindow(t *testing.T) { + for _, tt := range maintenanceWindows { + var m MaintenanceWindow + err := m.UnmarshalJSON([]byte(tt.s)) + if err != nil { + t.Errorf("Unmarshal Error: %v", err) + } + + if !reflect.DeepEqual(m, tt.m) { + t.Errorf("Expected maintenace window: %#v, got: %#v", tt.m, m) + } + } +} + +func TestMarshalMaintenanceWindow(t *testing.T) { + for _, tt := range maintenanceWindows { + s, err := tt.m.MarshalJSON() + if err != nil { + t.Errorf("Marshal Error: %v", err) + } + + if string(s) != tt.s { + t.Errorf("Expected Marshal: %s, got: %s", tt.s, string(s)) + } + } +} + +func TestUnmarshalMaintWindowsErrs(t *testing.T) { + for _, tt := range wrongMaintenanceWindows { + var m MaintenanceWindow + err := m.UnmarshalJSON(tt) + if err == nil { + t.Errorf("Error expected for '%s'", tt) + } + } +} + +func TestPostgresqlUnmarshal(t *testing.T) { + var cluster Postgresql + err := cluster.UnmarshalJSON(cl) + if err != nil { + t.Errorf("Unmarshal Error: %v", err) + } + + if !reflect.DeepEqual(cluster, clExp) { + t.Errorf("Expected Postgresql: %#v, got %#v", clExp, cluster) + } +} + +func TestPostgresqlMarshal(t *testing.T) { + m, err := json.Marshal(clExp) + if err != nil { + t.Errorf("Marshal Error: %v", err) + } + + if bytes.Compare(m, cl) != 0 { + t.Errorf("Expected postgresql marshal: %s, got %s", string(cl), string(m)) + } +} \ No newline at end of file From 20e343ebc920013709ae5bb4feb8008fffd95246 Mon Sep 17 00:00:00 2001 From: Murat Kabilov Date: Wed, 24 May 2017 18:38:56 +0200 Subject: [PATCH 04/14] namespacedname decoder tests --- pkg/spec/types_test.go | 55 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 pkg/spec/types_test.go diff --git a/pkg/spec/types_test.go b/pkg/spec/types_test.go new file mode 100644 index 000000000..65e234783 --- /dev/null +++ b/pkg/spec/types_test.go @@ -0,0 +1,55 @@ +package spec + +import ( + "bytes" + "testing" +) + +var nnTests = []struct { + s string + expected NamespacedName + expectedMarshal []byte +}{ + {`acid/cluster`, NamespacedName{Namespace: "acid", Name: "cluster"}, []byte(`"acid/cluster"`)}, + {`/name`, NamespacedName{Namespace: "default", Name: "name"}, []byte(`"default/name"`)}, + {`test`, NamespacedName{Namespace: "default", Name: "test"}, []byte(`"default/test"`)}, +} + +var nnErr = []string{"test/", "/", "", "//"} + +func TestNamespacedNameDecode(t *testing.T) { + for _, tt := range nnTests { + var actual NamespacedName + err := actual.Decode(tt.s) + if err != nil { + t.Errorf("Decode error: %v", err) + } + if actual != tt.expected { + t.Errorf("Expected: %v, got %#v", tt.expected, actual) + } + } +} + +func TestNamespacedNameMarshal(t *testing.T) { + for _, tt := range nnTests { + var actual NamespacedName + err := actual.Decode(tt.s) + m, err := actual.MarshalJSON() + if err != nil { + t.Errorf("Marshal error: %v", err) + } + if bytes.Compare(m, tt.expectedMarshal) != 0 { + t.Errorf("Expected marshal: %v, got %#v", tt.expected, actual) + } + } +} + +func TestNamespacedNameError(t *testing.T) { + for _, tt := range nnErr { + var actual NamespacedName + err := actual.Decode(tt) + if err == nil { + t.Errorf("Error expected for '%s', got: %#v", tt, actual) + } + } +} From ac92696d3bdb41a522cedfef556401de31bcf701 Mon Sep 17 00:00:00 2001 From: Murat Kabilov Date: Wed, 24 May 2017 18:39:04 +0200 Subject: [PATCH 05/14] go fmt --- pkg/spec/postgresql.go | 4 ++-- pkg/spec/postgresql_test.go | 16 ++++++++-------- pkg/spec/types.go | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pkg/spec/postgresql.go b/pkg/spec/postgresql.go index d673529f0..8ef7c2402 100644 --- a/pkg/spec/postgresql.go +++ b/pkg/spec/postgresql.go @@ -95,7 +95,7 @@ type PostgresqlList struct { } var alphaRegexp = regexp.MustCompile("^[a-zA-Z]*$") -var weekdays = map[string]int{"Sun":0, "Mon":1, "Tue":2, "Wed":3, "Thu":4, "Fri":5, "Sat":6} +var weekdays = map[string]int{"Sun": 0, "Mon": 1, "Tue": 2, "Wed": 3, "Thu": 4, "Fri": 5, "Sat": 6} func ParseTime(s string) (t time.Time, wd time.Weekday, wdProvided bool, err error) { var timeLayout string @@ -170,7 +170,7 @@ func (m *MaintenanceWindow) UnmarshalJSON(data []byte) error { return fmt.Errorf("'From' time must be prior to the 'To' time") } - if (int(got.StartWeekday) + 6) % 7 > (int(got.EndWeekday) + 6) % 7 { + if (int(got.StartWeekday)+6)%7 > (int(got.EndWeekday)+6)%7 { return fmt.Errorf("'From' weekday must be prior to the 'To' weekday") } diff --git a/pkg/spec/postgresql_test.go b/pkg/spec/postgresql_test.go index 0ec47821d..61e50de15 100644 --- a/pkg/spec/postgresql_test.go +++ b/pkg/spec/postgresql_test.go @@ -1,11 +1,11 @@ package spec import ( + "bytes" + "encoding/json" "reflect" "testing" "time" - "encoding/json" - "bytes" "k8s.io/client-go/pkg/api/unversioned" "k8s.io/client-go/pkg/api/v1" @@ -178,9 +178,9 @@ var clExp = Postgresql{ PostgresqlParam: PostgresqlParam{ PgVersion: "9.6", Parameters: map[string]string{ - "shared_buffers": "32MB", + "shared_buffers": "32MB", "max_connections": "10", - "log_statement": "all", + "log_statement": "all", }, }, Volume: Volume{ @@ -206,14 +206,14 @@ var clExp = Postgresql{ TeamID: "ACID", AllowedSourceRanges: []string{"127.0.0.1/32"}, NumberOfInstances: 2, - Users: map[string]UserFlags{"zalando": UserFlags{"superuser", "createdb"}}, - MaintenanceWindows: []MaintenanceWindow{MaintenanceWindow{ + Users: map[string]UserFlags{"zalando": {"superuser", "createdb"}}, + MaintenanceWindows: []MaintenanceWindow{{ StartWeekday: time.Monday, StartTime: mustParseTime("01:00"), EndTime: mustParseTime("06:00"), EndWeekday: time.Sunday, }, - MaintenanceWindow{ + { StartWeekday: time.Saturday, StartTime: mustParseTime("00:00"), EndTime: mustParseTime("04:00"), @@ -344,4 +344,4 @@ func TestPostgresqlMarshal(t *testing.T) { if bytes.Compare(m, cl) != 0 { t.Errorf("Expected postgresql marshal: %s, got %s", string(cl), string(m)) } -} \ No newline at end of file +} diff --git a/pkg/spec/types.go b/pkg/spec/types.go index 7da8eb497..6eacb67eb 100644 --- a/pkg/spec/types.go +++ b/pkg/spec/types.go @@ -1,9 +1,9 @@ package spec import ( + "database/sql" "fmt" "strings" - "database/sql" "k8s.io/client-go/pkg/api/v1" "k8s.io/client-go/pkg/types" From 0bfb81eb4f76c8b69ad69899dd642077c490f2e1 Mon Sep 17 00:00:00 2001 From: Murat Kabilov Date: Fri, 26 May 2017 12:43:30 +0200 Subject: [PATCH 06/14] postgresql spec status fields omit empty --- pkg/spec/postgresql.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/spec/postgresql.go b/pkg/spec/postgresql.go index 8ef7c2402..d5e9177fa 100644 --- a/pkg/spec/postgresql.go +++ b/pkg/spec/postgresql.go @@ -69,7 +69,7 @@ type Postgresql struct { Metadata v1.ObjectMeta `json:"metadata"` Spec PostgresSpec `json:"spec"` - Status PostgresStatus `json:"status"` + Status PostgresStatus `json:"status,omitempty"` Error error `json:"-"` } From 1708f7b8e0985c54fcc9122aff835c1dd47475f2 Mon Sep 17 00:00:00 2001 From: Murat Kabilov Date: Fri, 26 May 2017 12:44:44 +0200 Subject: [PATCH 07/14] postgresql tests --- pkg/spec/postgresql_test.go | 197 ++++++++++++++++++++++-------------- 1 file changed, 123 insertions(+), 74 deletions(-) diff --git a/pkg/spec/postgresql_test.go b/pkg/spec/postgresql_test.go index 61e50de15..ce9fd7678 100644 --- a/pkg/spec/postgresql_test.go +++ b/pkg/spec/postgresql_test.go @@ -1,8 +1,8 @@ package spec import ( - "bytes" "encoding/json" + "errors" "reflect" "testing" "time" @@ -104,7 +104,31 @@ var wrongMaintenanceWindows = [][]byte{ []byte(`"Mon:00:00"`), } -var cl = []byte(`{ +var unmarshalCluster = []struct { + in []byte + out Postgresql +}{{ + []byte(`{ + "kind": "Postgresql","apiVersion": "acid.zalan.do/v1", + "metadata": {"name": "acid-testcluster1"}, "spec": {"teamId": 100}}`), + Postgresql{ + TypeMeta: unversioned.TypeMeta{ + Kind: "Postgresql", + APIVersion: "acid.zalan.do/v1", + }, + Metadata: v1.ObjectMeta{ + Name: "acid-testcluster1", + }, + Status: ClusterStatusInvalid, + Error: &json.UnmarshalTypeError{ + Value: "number", + Type: reflect.TypeOf(""), + Offset: 126, + Struct: "PostgresSpec", + Field: "teamId", + }, + }}, + {[]byte(`{ "kind": "Postgresql", "apiVersion": "acid.zalan.do/v1", "metadata": { @@ -164,68 +188,88 @@ var cl = []byte(`{ "Sat:00:00-Sat:04:00" ] } -}`) - -var clExp = Postgresql{ - TypeMeta: unversioned.TypeMeta{ - Kind: "Postgresql", - APIVersion: "acid.zalan.do/v1", - }, - Metadata: v1.ObjectMeta{ - Name: "acid-testcluster1", - }, - Spec: PostgresSpec{ - PostgresqlParam: PostgresqlParam{ - PgVersion: "9.6", - Parameters: map[string]string{ - "shared_buffers": "32MB", - "max_connections": "10", - "log_statement": "all", +}`), + Postgresql{ + TypeMeta: unversioned.TypeMeta{ + Kind: "Postgresql", + APIVersion: "acid.zalan.do/v1", }, - }, - Volume: Volume{ - Size: "5Gi", - StorageClass: "SSD", - }, - Patroni: Patroni{ - InitDB: map[string]string{ - "encoding": "UTF8", - "locale": "en_US.UTF-8", - "data-checksums": "true", + Metadata: v1.ObjectMeta{ + Name: "acid-testcluster1", }, - PgHba: []string{"hostssl all all 0.0.0.0/0 md5", "host all all 0.0.0.0/0 md5"}, - TTL: 30, - LoopWait: 10, - RetryTimeout: 10, - MaximumLagOnFailover: 33554432, - }, - Resources: Resources{ - ResourceRequest: ResourceDescription{CPU: "10m", Memory: "50Mi"}, - ResourceLimits: ResourceDescription{CPU: "300m", Memory: "3000Mi"}, - }, - TeamID: "ACID", - AllowedSourceRanges: []string{"127.0.0.1/32"}, - NumberOfInstances: 2, - Users: map[string]UserFlags{"zalando": {"superuser", "createdb"}}, - MaintenanceWindows: []MaintenanceWindow{{ - StartWeekday: time.Monday, - StartTime: mustParseTime("01:00"), - EndTime: mustParseTime("06:00"), - EndWeekday: time.Sunday, - }, - { - StartWeekday: time.Saturday, - StartTime: mustParseTime("00:00"), - EndTime: mustParseTime("04:00"), - EndWeekday: time.Saturday, + Spec: PostgresSpec{ + PostgresqlParam: PostgresqlParam{ + PgVersion: "9.6", + Parameters: map[string]string{ + "shared_buffers": "32MB", + "max_connections": "10", + "log_statement": "all", + }, + }, + Volume: Volume{ + Size: "5Gi", + StorageClass: "SSD", + }, + Patroni: Patroni{ + InitDB: map[string]string{ + "encoding": "UTF8", + "locale": "en_US.UTF-8", + "data-checksums": "true", + }, + PgHba: []string{"hostssl all all 0.0.0.0/0 md5", "host all all 0.0.0.0/0 md5"}, + TTL: 30, + LoopWait: 10, + RetryTimeout: 10, + MaximumLagOnFailover: 33554432, + }, + Resources: Resources{ + ResourceRequest: ResourceDescription{CPU: "10m", Memory: "50Mi"}, + ResourceLimits: ResourceDescription{CPU: "300m", Memory: "3000Mi"}, + }, + TeamID: "ACID", + AllowedSourceRanges: []string{"127.0.0.1/32"}, + NumberOfInstances: 2, + Users: map[string]UserFlags{"zalando": {"superuser", "createdb"}}, + MaintenanceWindows: []MaintenanceWindow{{ + StartWeekday: time.Monday, + StartTime: mustParseTime("01:00"), + EndTime: mustParseTime("06:00"), + EndWeekday: time.Sunday, + }, + { + StartWeekday: time.Saturday, + StartTime: mustParseTime("00:00"), + EndTime: mustParseTime("04:00"), + EndWeekday: time.Saturday, + }, + }, + ClusterName: "testcluster1", }, - }, - ClusterName: "testcluster1", - }, - Status: "", - Error: nil, + Error: nil, + }}, + { + []byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1","metadata": {"name": "teapot-testcluster1"}, "spec": {"teamId": "acid"}}`), + Postgresql{ + TypeMeta: unversioned.TypeMeta{ + Kind: "Postgresql", + APIVersion: "acid.zalan.do/v1", + }, + Metadata: v1.ObjectMeta{ + Name: "teapot-testcluster1", + }, + Spec: PostgresSpec{TeamID: "acid"}, + Status: ClusterStatusInvalid, + Error: errors.New("name must match {TEAM}-{NAME} format"), + }}, } +var invalidClusterSpec = []struct { + in []byte + err error +}{{[]byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1"`), + errors.New("unexpected end of JSON input"), +}} + func mustParseTime(s string) time.Time { v, err := time.Parse("15:04", s) if err != nil { @@ -323,25 +367,30 @@ func TestUnmarshalMaintWindowsErrs(t *testing.T) { } } -func TestPostgresqlUnmarshal(t *testing.T) { - var cluster Postgresql - err := cluster.UnmarshalJSON(cl) - if err != nil { - t.Errorf("Unmarshal Error: %v", err) - } +func TestPostgresUnmarshal(t *testing.T) { + for _, tt := range unmarshalCluster { + var cluster Postgresql + err := cluster.UnmarshalJSON(tt.in) + if err != nil { + t.Errorf("Unmarshal Error: %v", err) + } - if !reflect.DeepEqual(cluster, clExp) { - t.Errorf("Expected Postgresql: %#v, got %#v", clExp, cluster) + if !reflect.DeepEqual(cluster, tt.out) { + t.Errorf("Expected Postgresql: %#v, got %#v", tt.out, cluster) + } } } -func TestPostgresqlMarshal(t *testing.T) { - m, err := json.Marshal(clExp) - if err != nil { - t.Errorf("Marshal Error: %v", err) - } +func TestInvalidPostgresUnmarshal(t *testing.T) { + for _, tt := range invalidClusterSpec { + var cluster Postgresql + err := cluster.UnmarshalJSON(tt.in) + if err == nil { + t.Errorf("Error expected for %s", string(tt.in)) + } - if bytes.Compare(m, cl) != 0 { - t.Errorf("Expected postgresql marshal: %s, got %s", string(cl), string(m)) + if err.Error() != tt.err.Error() { + t.Errorf("Unmarshal error expected: %v, got: %v", tt.err, err) + } } } From 54fe4b530e9586da929cc7466f45fd5f7e5c1104 Mon Sep 17 00:00:00 2001 From: Murat Kabilov Date: Fri, 26 May 2017 16:53:28 +0200 Subject: [PATCH 08/14] postgresql tests --- pkg/spec/postgresql.go | 6 +- pkg/spec/postgresql_test.go | 319 ++++++++++++++++++++++-------------- 2 files changed, 203 insertions(+), 122 deletions(-) diff --git a/pkg/spec/postgresql.go b/pkg/spec/postgresql.go index d5e9177fa..d86e15c4d 100644 --- a/pkg/spec/postgresql.go +++ b/pkg/spec/postgresql.go @@ -226,7 +226,8 @@ type PostgresqlListCopy PostgresqlList type PostgresqlCopy Postgresql func (p *Postgresql) UnmarshalJSON(data []byte) error { - tmp := PostgresqlCopy{} + var tmp PostgresqlCopy + err := json.Unmarshal(data, &tmp) if err != nil { metaErr := json.Unmarshal(data, &tmp.Metadata) @@ -256,7 +257,8 @@ func (p *Postgresql) UnmarshalJSON(data []byte) error { } func (pl *PostgresqlList) UnmarshalJSON(data []byte) error { - tmp := PostgresqlListCopy{} + var tmp PostgresqlListCopy + err := json.Unmarshal(data, &tmp) if err != nil { return err diff --git a/pkg/spec/postgresql_test.go b/pkg/spec/postgresql_test.go index ce9fd7678..073c1e7ab 100644 --- a/pkg/spec/postgresql_test.go +++ b/pkg/spec/postgresql_test.go @@ -1,6 +1,7 @@ package spec import ( + "bytes" "encoding/json" "errors" "reflect" @@ -11,102 +12,100 @@ import ( "k8s.io/client-go/pkg/api/v1" ) -var pTests = []struct { - s string - time time.Time - weekday time.Weekday - weekdayProvided bool +var parseTime = []struct { + in string + out time.Time + outWeekday time.Weekday + outWeekdayProvided bool + err error }{ - {"Mon:16:08", mustParseTime("16:08"), time.Monday, true}, - {"Sun:11:00", mustParseTime("11:00"), time.Sunday, true}, - {"23:59", mustParseTime("23:59"), time.Weekday(0), false}, -} + {"Mon:16:08", mustParseTime("16:08"), time.Monday, true, nil}, + {"Sun:11:00", mustParseTime("11:00"), time.Sunday, true, nil}, + {"23:59", mustParseTime("23:59"), time.Weekday(0), false, nil}, -var pErr = []string{"Thr:00:12", "26:09", "Std:26:09", "Saturday:00:00"} + {"Thr:00:12", time.Now(), time.Sunday, false, errors.New(`incorrect weekday`)}, + {"26:09", time.Now(), time.Sunday, false, errors.New(`parsing time "26:09": hour out of range`)}, + {"Std:26:09", time.Now(), time.Sunday, false, errors.New(`incorrect weekday`)}, + {"Saturday:00:00", time.Now(), time.Sunday, false, errors.New(`weekday must be 3 characters length`)}, +} var clusterNames = []struct { - s string - team string + in string + inTeam string clusterName string + err error }{ - {"acid-test", "acid", "test"}, - {"test-my-name", "test", "my-name"}, - {"my-team-another-test", "my-team", "another-test"}, - {"------strange-team-cluster", "-----", "strange-team-cluster"}, -} - -var wrongClusterNames = []struct { - s string - team string -}{ - {"acid-test", "test"}, - {"-test", ""}, - {"-test", "-"}, - {"", "-"}, - {"-", "-"}, + {"acid-test", "acid", "test", nil}, + {"test-my-name", "test", "my-name", nil}, + {"my-team-another-test", "my-team", "another-test", nil}, + {"------strange-team-cluster", "-----", "strange-team-cluster", nil}, + {"acid-test", "test", "", errors.New("name must match {TEAM}-{NAME} format")}, + {"-test", "", "", errors.New("Team name is empty")}, + {"-test", "-", "", errors.New("name must match {TEAM}-{NAME} format")}, + {"", "-", "", errors.New("name is too short")}, + {"-", "-", "", errors.New("name is too short")}, } var maintenanceWindows = []struct { - s string - m MaintenanceWindow -}{{`"10:00-20:00"`, + in []byte + out MaintenanceWindow + err error +}{{[]byte(`"10:00-20:00"`), MaintenanceWindow{ StartTime: mustParseTime("10:00"), StartWeekday: time.Monday, EndTime: mustParseTime("20:00"), EndWeekday: time.Sunday, - }}, - {`"Tue:10:00-Sun:23:00"`, + }, nil}, + {[]byte(`"Tue:10:00-Sun:23:00"`), MaintenanceWindow{ StartTime: mustParseTime("10:00"), StartWeekday: time.Tuesday, EndTime: mustParseTime("23:00"), EndWeekday: time.Sunday, - }}, - {`"Mon:10:00-Mon:10:00"`, + }, nil}, + {[]byte(`"Mon:10:00-Mon:10:00"`), MaintenanceWindow{ StartTime: mustParseTime("10:00"), StartWeekday: time.Monday, EndTime: mustParseTime("10:00"), EndWeekday: time.Monday, - }}, - {`"Sun:00:00-Sun:00:00"`, + }, nil}, + {[]byte(`"Sun:00:00-Sun:00:00"`), MaintenanceWindow{ StartTime: mustParseTime("00:00"), StartWeekday: time.Sunday, EndTime: mustParseTime("00:00"), EndWeekday: time.Sunday, - }}, - {`"00:00-10:00"`, + }, nil}, + {[]byte(`"00:00-10:00"`), MaintenanceWindow{ StartTime: mustParseTime("00:00"), StartWeekday: time.Monday, EndTime: mustParseTime("10:00"), EndWeekday: time.Sunday, - }}, - {`"00:00-00:00"`, + }, nil}, + {[]byte(`"00:00-00:00"`), MaintenanceWindow{ StartTime: mustParseTime("00:00"), StartWeekday: time.Monday, EndTime: mustParseTime("00:00"), EndWeekday: time.Sunday, - }}, -} - -var wrongMaintenanceWindows = [][]byte{ - []byte(`"Mon:12:00-Sun:11:00"`), - []byte(`"Mon:12:00-Mon:11:00"`), - []byte(`"Wed:00:00-Tue:26:00"`), - []byte(`"Sun:00:00-Mon:00:00"`), - []byte(`"Wed:00:00-Mon:10:00"`), - []byte(`"10:00-00:00"`), - []byte(`"Mon:00:00:00-Tue:10:00:00"`), - []byte(`"Mon:00:00"`), -} + }, nil}, + {[]byte(`"Mon:12:00-Sun:11:00"`), MaintenanceWindow{}, errors.New(`'From' time must be prior to the 'To' time`)}, + {[]byte(`"Mon:12:00-Mon:11:00"`), MaintenanceWindow{}, errors.New(`'From' time must be prior to the 'To' time`)}, + {[]byte(`"Wed:00:00-Tue:26:00"`), MaintenanceWindow{}, errors.New(`parsing time "Tue:26:00": hour out of range`)}, + {[]byte(`"Sun:00:00-Mon:00:00"`), MaintenanceWindow{}, errors.New(`'From' weekday must be prior to the 'To' weekday`)}, + {[]byte(`"Wed:00:00-Mon:10:00"`), MaintenanceWindow{}, errors.New(`'From' weekday must be prior to the 'To' weekday`)}, + {[]byte(`"10:00-00:00"`), MaintenanceWindow{}, errors.New(`'From' time must be prior to the 'To' time`)}, + {[]byte(`"Mon:00:00:00-Tue:10:00:00"`), MaintenanceWindow{}, errors.New(`parsing time "Mon:00:00:00" as "15:04": cannot parse "Mon:00:00:00" as "15"`)}, + {[]byte(`"Mon:00:00"`), MaintenanceWindow{}, errors.New("incorrect maintenance window format")}} var unmarshalCluster = []struct { - in []byte - out Postgresql + in []byte + out Postgresql + marshal []byte + err error }{{ []byte(`{ "kind": "Postgresql","apiVersion": "acid.zalan.do/v1", @@ -127,7 +126,8 @@ var unmarshalCluster = []struct { Struct: "PostgresSpec", Field: "teamId", }, - }}, + }, + []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"","allowedSourceRanges":null,"numberOfInstances":0,"users":null},"status":"Invalid"}`), nil}, {[]byte(`{ "kind": "Postgresql", "apiVersion": "acid.zalan.do/v1", @@ -246,7 +246,8 @@ var unmarshalCluster = []struct { ClusterName: "testcluster1", }, Error: nil, - }}, + }, + []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"9.6","parameters":{"log_statement":"all","max_connections":"10","shared_buffers":"32MB"}},"volume":{"size":"5Gi","storageClass":"SSD"},"patroni":{"initdb":{"data-checksums":"true","encoding":"UTF8","locale":"en_US.UTF-8"},"pg_hba":["hostssl all all 0.0.0.0/0 md5","host all all 0.0.0.0/0 md5"],"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432},"resources":{"requests":{"cpu":"10m","memory":"50Mi"},"limits":{"cpu":"300m","memory":"3000Mi"}},"teamId":"ACID","allowedSourceRanges":["127.0.0.1/32"],"numberOfInstances":2,"users":{"zalando":["superuser","createdb"]},"maintenanceWindows":["01:00-06:00","Sat:00:00-Sat:04:00"]}}`), nil}, { []byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1","metadata": {"name": "teapot-testcluster1"}, "spec": {"teamId": "acid"}}`), Postgresql{ @@ -260,15 +261,57 @@ var unmarshalCluster = []struct { Spec: PostgresSpec{TeamID: "acid"}, Status: ClusterStatusInvalid, Error: errors.New("name must match {TEAM}-{NAME} format"), - }}, -} + }, + []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"teapot-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null},"status":"Invalid"}`), nil}, + {[]byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1"`), + Postgresql{}, + []byte{}, + errors.New("unexpected end of JSON input")}, + {[]byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster","creationTimestamp":qaz},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null},"status":"Invalid"}`), + Postgresql{}, + []byte{}, + errors.New("invalid character 'q' looking for beginning of value")}} -var invalidClusterSpec = []struct { +var postgresqlList = []struct { in []byte + out PostgresqlList err error -}{{[]byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1"`), - errors.New("unexpected end of JSON input"), -}} +}{ + {[]byte(`{"apiVersion":"v1","items":[{"apiVersion":"acid.zalan.do/v1","kind":"Postgresql","metadata":{"labels":{"team":"acid"},"name":"acid-testcluster42","namespace":"default","resourceVersion":"30446957","selfLink":"/apis/acid.zalan.do/v1/namespaces/default/postgresqls/acid-testcluster42","uid":"857cd208-33dc-11e7-b20a-0699041e4b03"},"spec":{"allowedSourceRanges":["185.85.220.0/22"],"numberOfInstances":1,"postgresql":{"version":"9.6"},"teamId":"acid","volume":{"size":"10Gi"}},"status":"Running"}],"kind":"List","metadata":{},"resourceVersion":"","selfLink":""}`), + PostgresqlList{ + TypeMeta: unversioned.TypeMeta{ + Kind: "List", + APIVersion: "v1", + }, + Items: []Postgresql{Postgresql{ + TypeMeta: unversioned.TypeMeta{ + Kind: "Postgresql", + APIVersion: "acid.zalan.do/v1", + }, + Metadata: v1.ObjectMeta{ + Name: "acid-testcluster42", + Namespace: "default", + Labels: map[string]string{"team": "acid"}, + ResourceVersion: "30446957", + SelfLink: "/apis/acid.zalan.do/v1/namespaces/default/postgresqls/acid-testcluster42", + UID: "857cd208-33dc-11e7-b20a-0699041e4b03", + }, + Spec: PostgresSpec{ + ClusterName: "testcluster42", + PostgresqlParam: PostgresqlParam{PgVersion: "9.6"}, + Volume: Volume{Size: "10Gi"}, + TeamID: "acid", + AllowedSourceRanges: []string{"185.85.220.0/22"}, + NumberOfInstances: 1, + }, + Status: ClusterStatusRunning, + Error: nil, + }}, + }, + nil}, + {[]byte(`{"apiVersion":"v1","items":[{"apiVersion":"acid.zalan.do/v1","kind":"Postgresql","metadata":{"labels":{"team":"acid"},"name":"acid-testcluster42","namespace"`), + PostgresqlList{}, + errors.New("unexpected end of JSON input")}} func mustParseTime(s string) time.Time { v, err := time.Parse("15:04", s) @@ -280,40 +323,36 @@ func mustParseTime(s string) time.Time { } func TestParseTime(t *testing.T) { - for _, tt := range pTests { - aTime, weekday, weekdayProvided, err := ParseTime(tt.s) + for _, tt := range parseTime { + aTime, weekday, weekdayProvided, err := ParseTime(tt.in) if err != nil { - t.Errorf("ParseTime error: %v", err) + if err.Error() != tt.err.Error() { + t.Errorf("ParseTime expected error: %v, got: %v", err, tt.err) + } + + continue } - if aTime != tt.time { - t.Errorf("Expected time: %v, got: %v", tt.time, aTime) + if aTime != tt.out { + t.Errorf("Expected time: %v, got: %v", tt.out, aTime) } - if weekday != tt.weekday { - t.Errorf("Expected weekday: %v, got: %v", tt.weekday, weekday) + if weekday != tt.outWeekday { + t.Errorf("Expected weekday: %v, got: %v", tt.outWeekday, weekday) } - if weekdayProvided != tt.weekdayProvided { - t.Errorf("Expected weekdayProvided: %t, got: %t", tt.weekdayProvided, weekdayProvided) - } - } -} - -func TestParseTimeError(t *testing.T) { - for _, tt := range pErr { - _, _, _, err := ParseTime(tt) - if err == nil { - t.Errorf("Error expected for '%s'", tt) + if weekdayProvided != tt.outWeekdayProvided { + t.Errorf("Expected weekdayProvided: %t, got: %t", tt.outWeekdayProvided, weekdayProvided) } } } func TestClusterName(t *testing.T) { for _, tt := range clusterNames { - name, err := extractClusterName(tt.s, tt.team) - if err != nil { - t.Errorf("extractClusterName error: %v", err) + name, err := extractClusterName(tt.in, tt.inTeam) + if err != nil && err.Error() != tt.err.Error() { + t.Errorf("extractClusterName expected error: %v, got: %v", tt.err, err) + continue } if name != tt.clusterName { t.Errorf("Expected cluserName: %s, got: %s", tt.clusterName, name) @@ -321,48 +360,35 @@ func TestClusterName(t *testing.T) { } } -func TestClusterNameError(t *testing.T) { - for _, tt := range wrongClusterNames { - _, err := extractClusterName(tt.s, tt.team) - if err == nil { - t.Errorf("Error expected for '%s'", tt) - } - } -} - func TestUnmarshalMaintenanceWindow(t *testing.T) { for _, tt := range maintenanceWindows { var m MaintenanceWindow - err := m.UnmarshalJSON([]byte(tt.s)) - if err != nil { - t.Errorf("Unmarshal Error: %v", err) + err := m.UnmarshalJSON([]byte(tt.in)) + if err != nil && err.Error() != tt.err.Error() { + t.Errorf("MaintenanceWindow unmarshal expected error: %v, got %v", tt.err, err) + continue } - if !reflect.DeepEqual(m, tt.m) { - t.Errorf("Expected maintenace window: %#v, got: %#v", tt.m, m) + if !reflect.DeepEqual(m, tt.out) { + t.Errorf("Expected maintenace window: %#v, got: %#v", tt.out, m) } } } func TestMarshalMaintenanceWindow(t *testing.T) { for _, tt := range maintenanceWindows { - s, err := tt.m.MarshalJSON() + if tt.err != nil { + continue + } + + s, err := tt.out.MarshalJSON() if err != nil { t.Errorf("Marshal Error: %v", err) + continue } - if string(s) != tt.s { - t.Errorf("Expected Marshal: %s, got: %s", tt.s, string(s)) - } - } -} - -func TestUnmarshalMaintWindowsErrs(t *testing.T) { - for _, tt := range wrongMaintenanceWindows { - var m MaintenanceWindow - err := m.UnmarshalJSON(tt) - if err == nil { - t.Errorf("Error expected for '%s'", tt) + if bytes.Compare(s, tt.in) != 0 { + t.Errorf("Expected Marshal: %s, got: %s", string(tt.in), string(s)) } } } @@ -372,7 +398,11 @@ func TestPostgresUnmarshal(t *testing.T) { var cluster Postgresql err := cluster.UnmarshalJSON(tt.in) if err != nil { - t.Errorf("Unmarshal Error: %v", err) + if err.Error() != tt.err.Error() { + t.Errorf("Unmarshal expected error: %v, got: %v", tt.err, err) + } + + continue } if !reflect.DeepEqual(cluster, tt.out) { @@ -381,16 +411,65 @@ func TestPostgresUnmarshal(t *testing.T) { } } -func TestInvalidPostgresUnmarshal(t *testing.T) { - for _, tt := range invalidClusterSpec { - var cluster Postgresql - err := cluster.UnmarshalJSON(tt.in) - if err == nil { - t.Errorf("Error expected for %s", string(tt.in)) +func TestMarshal(t *testing.T) { + for _, tt := range unmarshalCluster { + if tt.err != nil { + continue } - if err.Error() != tt.err.Error() { - t.Errorf("Unmarshal error expected: %v, got: %v", tt.err, err) + m, err := json.Marshal(tt.out) + if err != nil { + t.Errorf("Marshal error: %v", err) + continue + } + if bytes.Compare(m, tt.marshal) != 0 { + t.Errorf("Marshal Postgresql expected: %s, got: %s", string(tt.marshal), string(m)) } } } + +func TestPostgresMeta(t *testing.T) { + for _, tt := range unmarshalCluster { + if a := tt.out.GetObjectKind(); a != &tt.out.TypeMeta { + t.Errorf("GetObjectKindMeta expected: %v, got: %v", tt.out.TypeMeta, a) + } + + if a := tt.out.GetObjectMeta(); reflect.DeepEqual(a, tt.out.Metadata) { + t.Errorf("GetObjectMeta expected: %v, got: %v", tt.out.Metadata, a) + } + + return + } +} + +func TestUnmarshalPostgresList(t *testing.T) { + for _, tt := range postgresqlList { + var list PostgresqlList + err := list.UnmarshalJSON(tt.in) + if err != nil && err.Error() != tt.err.Error() { + t.Errorf("PostgresqlList unmarshal expected error: %v, got: %v", tt.err, err) + return + } + if !reflect.DeepEqual(list, tt.out) { + t.Errorf("Postgresql list unmarshall expected: %#v, got: %#v", tt.out, list) + } + } +} + +func TestPostgresListMeta(t *testing.T) { + for _, tt := range postgresqlList { + if tt.err != nil { + continue + } + + if a := tt.out.GetObjectKind(); a != &tt.out.TypeMeta { + t.Errorf("GetObjectKindMeta expected: %v, got: %v", tt.out.TypeMeta, a) + } + + if a := tt.out.GetListMeta(); reflect.DeepEqual(a, tt.out.Metadata) { + t.Errorf("GetObjectMeta expected: %v, got: %v", tt.out.Metadata, a) + } + + return + } +} From 8e1c21c3960c9052d08c38b64b0c7e1f73b08982 Mon Sep 17 00:00:00 2001 From: Murat Kabilov Date: Fri, 26 May 2017 17:09:12 +0200 Subject: [PATCH 09/14] fix maintenance window parsing --- pkg/spec/postgresql.go | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/pkg/spec/postgresql.go b/pkg/spec/postgresql.go index e00625c7d..e4dd67eb4 100644 --- a/pkg/spec/postgresql.go +++ b/pkg/spec/postgresql.go @@ -12,8 +12,6 @@ import ( "k8s.io/client-go/pkg/api/v1" ) -var alphaRegexp = regexp.MustCompile("^[a-zA-Z]*$") - type MaintenanceWindow struct { StartTime time.Time // Start time StartWeekday time.Weekday // Start weekday @@ -96,7 +94,10 @@ type PostgresqlList struct { Items []Postgresql `json:"items"` } -func parseTime(s string) (t time.Time, wd time.Weekday, wdProvided bool, err error) { +var alphaRegexp = regexp.MustCompile("^[a-zA-Z]*$") +var weekdays = map[string]int{"Sun": 0, "Mon": 1, "Tue": 2, "Wed": 3, "Thu": 4, "Fri": 5, "Sat": 6} + +func ParseTime(s string) (t time.Time, wd time.Weekday, wdProvided bool, err error) { var timeLayout string parts := strings.Split(s, ":") @@ -107,6 +108,12 @@ func parseTime(s string) (t time.Time, wd time.Weekday, wdProvided bool, err err } timeLayout = "Mon:15:04" wdProvided = true + weekday, ok := weekdays[parts[0]] + if !ok { + err = fmt.Errorf("incorrect weekday") + return + } + wd = time.Weekday(weekday) } else { wdProvided = false timeLayout = "15:04" @@ -116,8 +123,6 @@ func parseTime(s string) (t time.Time, wd time.Weekday, wdProvided bool, err err if err != nil { return } - - wd = tp.Weekday() t = tp.UTC() return @@ -125,7 +130,7 @@ func parseTime(s string) (t time.Time, wd time.Weekday, wdProvided bool, err err func (m *MaintenanceWindow) MarshalJSON() ([]byte, error) { var startWd, endWd string - if m.StartWeekday == time.Sunday && m.EndWeekday == time.Saturday { + if m.StartWeekday == time.Monday && m.EndWeekday == time.Sunday { startWd = "" endWd = "" } else { @@ -151,12 +156,12 @@ func (m *MaintenanceWindow) UnmarshalJSON(data []byte) error { return fmt.Errorf("incorrect maintenance window format") } - got.StartTime, got.StartWeekday, weekdayProvidedFrom, err = parseTime(parts[0]) + got.StartTime, got.StartWeekday, weekdayProvidedFrom, err = ParseTime(parts[0]) if err != nil { return err } - got.EndTime, got.EndWeekday, weekdayProvidedTo, err = parseTime(parts[1]) + got.EndTime, got.EndWeekday, weekdayProvidedTo, err = ParseTime(parts[1]) if err != nil { return err } @@ -165,9 +170,13 @@ func (m *MaintenanceWindow) UnmarshalJSON(data []byte) error { return fmt.Errorf("'From' time must be prior to the 'To' time") } + if (int(got.StartWeekday)+6)%7 > (int(got.EndWeekday)+6)%7 { + return fmt.Errorf("'From' weekday must be prior to the 'To' weekday") + } + if !weekdayProvidedFrom || !weekdayProvidedTo { - got.StartWeekday = time.Sunday - got.EndWeekday = time.Saturday + got.StartWeekday = time.Monday + got.EndWeekday = time.Sunday } *m = got From a21a16d24c524c825bfc29cc5a573b7897935d96 Mon Sep 17 00:00:00 2001 From: Murat Kabilov Date: Fri, 26 May 2017 17:10:14 +0200 Subject: [PATCH 10/14] better naming fix function visibility --- pkg/spec/postgresql.go | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/pkg/spec/postgresql.go b/pkg/spec/postgresql.go index e4dd67eb4..74ba92930 100644 --- a/pkg/spec/postgresql.go +++ b/pkg/spec/postgresql.go @@ -69,7 +69,7 @@ type Postgresql struct { Metadata v1.ObjectMeta `json:"metadata"` Spec PostgresSpec `json:"spec"` - Status PostgresStatus `json:"status"` + Status PostgresStatus `json:"status,omitempty"` Error error `json:"-"` } @@ -97,7 +97,7 @@ type PostgresqlList struct { var alphaRegexp = regexp.MustCompile("^[a-zA-Z]*$") var weekdays = map[string]int{"Sun": 0, "Mon": 1, "Tue": 2, "Wed": 3, "Thu": 4, "Fri": 5, "Sat": 6} -func ParseTime(s string) (t time.Time, wd time.Weekday, wdProvided bool, err error) { +func parseTime(s string) (t time.Time, wd time.Weekday, wdProvided bool, err error) { var timeLayout string parts := strings.Split(s, ":") @@ -156,12 +156,12 @@ func (m *MaintenanceWindow) UnmarshalJSON(data []byte) error { return fmt.Errorf("incorrect maintenance window format") } - got.StartTime, got.StartWeekday, weekdayProvidedFrom, err = ParseTime(parts[0]) + got.StartTime, got.StartWeekday, weekdayProvidedFrom, err = parseTime(parts[0]) if err != nil { return err } - got.EndTime, got.EndWeekday, weekdayProvidedTo, err = ParseTime(parts[1]) + got.EndTime, got.EndWeekday, weekdayProvidedTo, err = parseTime(parts[1]) if err != nil { return err } @@ -200,11 +200,16 @@ func (pl *PostgresqlList) GetListMeta() unversioned.List { return &pl.Metadata } -func clusterName(clusterName string, teamName string) (string, error) { +func extractClusterName(clusterName string, teamName string) (string, error) { teamNameLen := len(teamName) if len(clusterName) < teamNameLen+2 { return "", fmt.Errorf("name is too short") } + + if teamNameLen == 0 { + return "", fmt.Errorf("Team name is empty") + } + if strings.ToLower(clusterName[:teamNameLen+1]) != strings.ToLower(teamName)+"-" { return "", fmt.Errorf("name must match {TEAM}-{NAME} format") } @@ -220,7 +225,8 @@ type PostgresqlListCopy PostgresqlList type PostgresqlCopy Postgresql func (p *Postgresql) UnmarshalJSON(data []byte) error { - tmp := PostgresqlCopy{} + var tmp PostgresqlCopy + err := json.Unmarshal(data, &tmp) if err != nil { metaErr := json.Unmarshal(data, &tmp.Metadata) @@ -237,7 +243,7 @@ func (p *Postgresql) UnmarshalJSON(data []byte) error { } tmp2 := Postgresql(tmp) - clusterName, err := clusterName(tmp2.Metadata.Name, tmp2.Spec.TeamID) + clusterName, err := extractClusterName(tmp2.Metadata.Name, tmp2.Spec.TeamID) if err == nil { tmp2.Spec.ClusterName = clusterName } else { @@ -250,7 +256,8 @@ func (p *Postgresql) UnmarshalJSON(data []byte) error { } func (pl *PostgresqlList) UnmarshalJSON(data []byte) error { - tmp := PostgresqlListCopy{} + var tmp PostgresqlListCopy + err := json.Unmarshal(data, &tmp) if err != nil { return err From 3ab8537b8b328bb769c1656cfc57858f411d5bb8 Mon Sep 17 00:00:00 2001 From: Murat Kabilov Date: Fri, 26 May 2017 18:21:17 +0200 Subject: [PATCH 11/14] util test --- pkg/util/util_test.go | 118 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 pkg/util/util_test.go diff --git a/pkg/util/util_test.go b/pkg/util/util_test.go new file mode 100644 index 000000000..ddf42760f --- /dev/null +++ b/pkg/util/util_test.go @@ -0,0 +1,118 @@ +package util + +import ( + "fmt" + "reflect" + "testing" + + "k8s.io/client-go/pkg/api/v1" + + "github.com/zalando-incubator/postgres-operator/pkg/spec" +) + +var pgUsers = []struct { + in spec.PgUser + out string +}{{spec.PgUser{ + Name: "test", + Password: "password", + Flags: []string{}, + MemberOf: []string{}}, + "md587f77988ccb5aa917c93201ba314fcd4"}, + {spec.PgUser{ + Name: "test", + Password: "md592f413f3974bdf3799bb6fecb5f9f2c6", + Flags: []string{}, + MemberOf: []string{}}, + "md592f413f3974bdf3799bb6fecb5f9f2c6"}} + +var prettyTest = []struct { + in interface{} + out string +}{ + {pgUsers, `[{{test password [] []} md587f77988ccb5aa917c93201ba314fcd4} {{test md592f413f3974bdf3799bb6fecb5f9f2c6 [] []} md592f413f3974bdf3799bb6fecb5f9f2c6}]`}, +} + +var prettyDiffTest = []struct { + inA interface{} + inB interface{} + out string +}{ + {[]int{1, 2, 3, 4}, []int{1, 2, 3}, "[]int[4] != []int[3]"}, + {[]int{1, 2, 3, 4}, []int{1, 2, 3, 4}, ""}, +} + +var substractTest = []struct { + inA []string + inB []string + out []string + outEqual bool +}{ + {[]string{"a", "b", "c", "d"}, []string{"a", "b", "c", "d"}, []string{}, true}, + {[]string{"a", "b", "c", "d"}, []string{"a", "bb", "c", "d"}, []string{"b"}, false}, +} + +func TestRandomPassword(t *testing.T) { + const pwdLength = 10 + pwd := RandomPassword(pwdLength) + if a := len(pwd); a != pwdLength { + t.Errorf("Password length expected: %d, got: %d", pwdLength, a) + } +} + +func TestNameFromMeta(t *testing.T) { + meta := v1.ObjectMeta{ + Name: "testcluster", + Namespace: "default", + } + + expected := spec.NamespacedName{ + Name: "testcluster", + Namespace: "default", + } + + actual := NameFromMeta(meta) + if actual != expected { + t.Errorf("NameFromMeta expected: %#v, got: %#v", expected, actual) + } +} + +func TestPGUserPassword(t *testing.T) { + for _, tt := range pgUsers { + pwd := PGUserPassword(tt.in) + if pwd != tt.out { + t.Errorf("PgUserPassword expected: %s, got: %s", tt.out, pwd) + } + } +} + +func TestPretty(t *testing.T) { + for _, tt := range prettyTest { + if actual := Pretty(tt.in); fmt.Sprintf("%v", actual) != tt.out { + t.Errorf("Pretty expected: %s, got: %s", tt.out, actual) + } + } +} + +func TestPrettyDiff(t *testing.T) { + for _, tt := range prettyDiffTest { + if actual := PrettyDiff(tt.inA, tt.inB); actual != tt.out { + t.Errorf("PrettyDiff expected: %s, got: %s", tt.out, actual) + } + } +} + +func TestSubstractSlices(t *testing.T) { + for _, tt := range substractTest { + actualRes, actualEqual := SubstractStringSlices(tt.inA, tt.inB) + if actualEqual != tt.outEqual { + t.Errorf("SubstractStringSlices expected equal: %t, got: %t", tt.outEqual, actualEqual) + } + + if len(actualRes) == 0 && len(tt.out) == 0 { + continue + } else if !reflect.DeepEqual(actualRes, tt.out) { + t.Errorf("SubstractStringSlices expected res: %v, got: %v", tt.out, actualRes) + } + } +} From fdde527e827c718e35c54f652a3a7af0defdb507 Mon Sep 17 00:00:00 2001 From: Murat Kabilov Date: Fri, 26 May 2017 18:34:05 +0200 Subject: [PATCH 12/14] simplify bytes check --- pkg/spec/postgresql_test.go | 12 +++++------- pkg/spec/types_test.go | 4 ++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/pkg/spec/postgresql_test.go b/pkg/spec/postgresql_test.go index 073c1e7ab..a3e336c87 100644 --- a/pkg/spec/postgresql_test.go +++ b/pkg/spec/postgresql_test.go @@ -12,7 +12,7 @@ import ( "k8s.io/client-go/pkg/api/v1" ) -var parseTime = []struct { +var parseTimeTests = []struct { in string out time.Time outWeekday time.Weekday @@ -323,8 +323,8 @@ func mustParseTime(s string) time.Time { } func TestParseTime(t *testing.T) { - for _, tt := range parseTime { - aTime, weekday, weekdayProvided, err := ParseTime(tt.in) + for _, tt := range parseTimeTests { + aTime, weekday, weekdayProvided, err := parseTime(tt.in) if err != nil { if err.Error() != tt.err.Error() { t.Errorf("ParseTime expected error: %v, got: %v", err, tt.err) @@ -387,7 +387,7 @@ func TestMarshalMaintenanceWindow(t *testing.T) { continue } - if bytes.Compare(s, tt.in) != 0 { + if !bytes.Equal(s, tt.in) { t.Errorf("Expected Marshal: %s, got: %s", string(tt.in), string(s)) } } @@ -422,7 +422,7 @@ func TestMarshal(t *testing.T) { t.Errorf("Marshal error: %v", err) continue } - if bytes.Compare(m, tt.marshal) != 0 { + if !bytes.Equal(m, tt.marshal) { t.Errorf("Marshal Postgresql expected: %s, got: %s", string(tt.marshal), string(m)) } } @@ -437,8 +437,6 @@ func TestPostgresMeta(t *testing.T) { if a := tt.out.GetObjectMeta(); reflect.DeepEqual(a, tt.out.Metadata) { t.Errorf("GetObjectMeta expected: %v, got: %v", tt.out.Metadata, a) } - - return } } diff --git a/pkg/spec/types_test.go b/pkg/spec/types_test.go index 65e234783..b690586e9 100644 --- a/pkg/spec/types_test.go +++ b/pkg/spec/types_test.go @@ -33,12 +33,12 @@ func TestNamespacedNameDecode(t *testing.T) { func TestNamespacedNameMarshal(t *testing.T) { for _, tt := range nnTests { var actual NamespacedName - err := actual.Decode(tt.s) + m, err := actual.MarshalJSON() if err != nil { t.Errorf("Marshal error: %v", err) } - if bytes.Compare(m, tt.expectedMarshal) != 0 { + if bytes.Equal(m, tt.expectedMarshal) { t.Errorf("Expected marshal: %v, got %#v", tt.expected, actual) } } From 312f9e47b363dbb1ce6e08f1a9173746dfe91ece Mon Sep 17 00:00:00 2001 From: Murat Kabilov Date: Mon, 29 May 2017 16:51:40 +0200 Subject: [PATCH 13/14] postgresql spec tests --- pkg/spec/postgresql_test.go | 139 +++++++++++++++++------------------- 1 file changed, 67 insertions(+), 72 deletions(-) diff --git a/pkg/spec/postgresql_test.go b/pkg/spec/postgresql_test.go index a3e336c87..46b27dcb7 100644 --- a/pkg/spec/postgresql_test.go +++ b/pkg/spec/postgresql_test.go @@ -1,7 +1,6 @@ package spec import ( - "bytes" "encoding/json" "errors" "reflect" @@ -10,23 +9,30 @@ import ( "k8s.io/client-go/pkg/api/unversioned" "k8s.io/client-go/pkg/api/v1" + "bytes" ) var parseTimeTests = []struct { in string out time.Time - outWeekday time.Weekday - outWeekdayProvided bool err error }{ - {"Mon:16:08", mustParseTime("16:08"), time.Monday, true, nil}, - {"Sun:11:00", mustParseTime("11:00"), time.Sunday, true, nil}, - {"23:59", mustParseTime("23:59"), time.Weekday(0), false, nil}, + {"16:08", mustParseTime("16:08"), nil}, + {"11:00", mustParseTime("11:00"), nil}, + {"23:59", mustParseTime("23:59"), nil}, - {"Thr:00:12", time.Now(), time.Sunday, false, errors.New(`incorrect weekday`)}, - {"26:09", time.Now(), time.Sunday, false, errors.New(`parsing time "26:09": hour out of range`)}, - {"Std:26:09", time.Now(), time.Sunday, false, errors.New(`incorrect weekday`)}, - {"Saturday:00:00", time.Now(), time.Sunday, false, errors.New(`weekday must be 3 characters length`)}, + {"26:09", time.Now(), errors.New(`parsing time "26:09": hour out of range`)}, + {"23:69", time.Now(), errors.New(`parsing time "23:69": minute out of range`)}, +} + +var parseWeekdayTests = []struct { + in string + out time.Weekday + err error +}{ + {"Wed", time.Wednesday, nil}, + {"Sunday", time.Weekday(0), errors.New("incorrect weekday")}, + {"", time.Weekday(0), errors.New("incorrect weekday")}, } var clusterNames = []struct { @@ -40,7 +46,7 @@ var clusterNames = []struct { {"my-team-another-test", "my-team", "another-test", nil}, {"------strange-team-cluster", "-----", "strange-team-cluster", nil}, {"acid-test", "test", "", errors.New("name must match {TEAM}-{NAME} format")}, - {"-test", "", "", errors.New("Team name is empty")}, + {"-test", "", "", errors.New("team name is empty")}, {"-test", "-", "", errors.New("name must match {TEAM}-{NAME} format")}, {"", "-", "", errors.New("name is too short")}, {"-", "-", "", errors.New("name is too short")}, @@ -50,56 +56,33 @@ var maintenanceWindows = []struct { in []byte out MaintenanceWindow err error -}{{[]byte(`"10:00-20:00"`), +}{{[]byte(`"Tue:10:00-20:00"`), MaintenanceWindow{ + Weekday: time.Tuesday, StartTime: mustParseTime("10:00"), - StartWeekday: time.Monday, EndTime: mustParseTime("20:00"), - EndWeekday: time.Sunday, }, nil}, - {[]byte(`"Tue:10:00-Sun:23:00"`), + {[]byte(`"Mon:10:00-10:00"`), MaintenanceWindow{ + Weekday: time.Monday, StartTime: mustParseTime("10:00"), - StartWeekday: time.Tuesday, - EndTime: mustParseTime("23:00"), - EndWeekday: time.Sunday, - }, nil}, - {[]byte(`"Mon:10:00-Mon:10:00"`), - MaintenanceWindow{ - StartTime: mustParseTime("10:00"), - StartWeekday: time.Monday, EndTime: mustParseTime("10:00"), - EndWeekday: time.Monday, }, nil}, - {[]byte(`"Sun:00:00-Sun:00:00"`), + {[]byte(`"Sun:00:00-00:00"`), MaintenanceWindow{ + Weekday: time.Sunday, StartTime: mustParseTime("00:00"), - StartWeekday: time.Sunday, EndTime: mustParseTime("00:00"), - EndWeekday: time.Sunday, }, nil}, - {[]byte(`"00:00-10:00"`), - MaintenanceWindow{ - StartTime: mustParseTime("00:00"), - StartWeekday: time.Monday, - EndTime: mustParseTime("10:00"), - EndWeekday: time.Sunday, - }, nil}, - {[]byte(`"00:00-00:00"`), - MaintenanceWindow{ - StartTime: mustParseTime("00:00"), - StartWeekday: time.Monday, - EndTime: mustParseTime("00:00"), - EndWeekday: time.Sunday, - }, nil}, - {[]byte(`"Mon:12:00-Sun:11:00"`), MaintenanceWindow{}, errors.New(`'From' time must be prior to the 'To' time`)}, - {[]byte(`"Mon:12:00-Mon:11:00"`), MaintenanceWindow{}, errors.New(`'From' time must be prior to the 'To' time`)}, - {[]byte(`"Wed:00:00-Tue:26:00"`), MaintenanceWindow{}, errors.New(`parsing time "Tue:26:00": hour out of range`)}, - {[]byte(`"Sun:00:00-Mon:00:00"`), MaintenanceWindow{}, errors.New(`'From' weekday must be prior to the 'To' weekday`)}, - {[]byte(`"Wed:00:00-Mon:10:00"`), MaintenanceWindow{}, errors.New(`'From' weekday must be prior to the 'To' weekday`)}, - {[]byte(`"10:00-00:00"`), MaintenanceWindow{}, errors.New(`'From' time must be prior to the 'To' time`)}, - {[]byte(`"Mon:00:00:00-Tue:10:00:00"`), MaintenanceWindow{}, errors.New(`parsing time "Mon:00:00:00" as "15:04": cannot parse "Mon:00:00:00" as "15"`)}, - {[]byte(`"Mon:00:00"`), MaintenanceWindow{}, errors.New("incorrect maintenance window format")}} + {[]byte(`"Mon:12:00-11:00"`), MaintenanceWindow{}, errors.New(`'From' time must be prior to the 'To' time`)}, + {[]byte(`"Wed:33:00-00:00"`), MaintenanceWindow{}, errors.New(`could not parse start time: parsing time "33:00": hour out of range`)}, + {[]byte(`"Wed:00:00-26:00"`), MaintenanceWindow{}, errors.New(`could not parse end time: parsing time "26:00": hour out of range`)}, + {[]byte(`"Sunday:00:00-00:00"`), MaintenanceWindow{}, errors.New(`could not parse weekday: incorrect weekday`)}, + {[]byte(`":00:00-10:00"`), MaintenanceWindow{}, errors.New(`could not parse weekday: incorrect weekday`)}, + {[]byte(`"Mon:10:00-00:00"`), MaintenanceWindow{}, errors.New(`'From' time must be prior to the 'To' time`)}, + {[]byte(`"Mon:00:00:00-10:00:00"`), MaintenanceWindow{}, errors.New(`incorrect maintenance window format`)}, + {[]byte(`"Mon:00:00"`), MaintenanceWindow{}, errors.New("incorrect maintenance window format")}, + {[]byte(`"Mon:00:00-00:00:00"`), MaintenanceWindow{}, errors.New("could not parse end time: incorrect time format")}} var unmarshalCluster = []struct { in []byte @@ -184,8 +167,8 @@ var unmarshalCluster = []struct { "maximum_lag_on_failover": 33554432 }, "maintenanceWindows": [ - "01:00-06:00", - "Sat:00:00-Sat:04:00" + "Mon:01:00-06:00", + "Sat:00:00-04:00" ] } }`), @@ -231,23 +214,21 @@ var unmarshalCluster = []struct { NumberOfInstances: 2, Users: map[string]UserFlags{"zalando": {"superuser", "createdb"}}, MaintenanceWindows: []MaintenanceWindow{{ - StartWeekday: time.Monday, + Weekday: time.Monday, StartTime: mustParseTime("01:00"), EndTime: mustParseTime("06:00"), - EndWeekday: time.Sunday, }, { - StartWeekday: time.Saturday, + Weekday: time.Saturday, StartTime: mustParseTime("00:00"), EndTime: mustParseTime("04:00"), - EndWeekday: time.Saturday, }, }, ClusterName: "testcluster1", }, Error: nil, }, - []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"9.6","parameters":{"log_statement":"all","max_connections":"10","shared_buffers":"32MB"}},"volume":{"size":"5Gi","storageClass":"SSD"},"patroni":{"initdb":{"data-checksums":"true","encoding":"UTF8","locale":"en_US.UTF-8"},"pg_hba":["hostssl all all 0.0.0.0/0 md5","host all all 0.0.0.0/0 md5"],"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432},"resources":{"requests":{"cpu":"10m","memory":"50Mi"},"limits":{"cpu":"300m","memory":"3000Mi"}},"teamId":"ACID","allowedSourceRanges":["127.0.0.1/32"],"numberOfInstances":2,"users":{"zalando":["superuser","createdb"]},"maintenanceWindows":["01:00-06:00","Sat:00:00-Sat:04:00"]}}`), nil}, + []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"9.6","parameters":{"log_statement":"all","max_connections":"10","shared_buffers":"32MB"}},"volume":{"size":"5Gi","storageClass":"SSD"},"patroni":{"initdb":{"data-checksums":"true","encoding":"UTF8","locale":"en_US.UTF-8"},"pg_hba":["hostssl all all 0.0.0.0/0 md5","host all all 0.0.0.0/0 md5"],"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432},"resources":{"requests":{"cpu":"10m","memory":"50Mi"},"limits":{"cpu":"300m","memory":"3000Mi"}},"teamId":"ACID","allowedSourceRanges":["127.0.0.1/32"],"numberOfInstances":2,"users":{"zalando":["superuser","createdb"]},"maintenanceWindows":["Mon:01:00-06:00","Sat:00:00-04:00"]}}`), nil}, { []byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1","metadata": {"name": "teapot-testcluster1"}, "spec": {"teamId": "acid"}}`), Postgresql{ @@ -264,13 +245,13 @@ var unmarshalCluster = []struct { }, []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"teapot-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null},"status":"Invalid"}`), nil}, {[]byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1"`), - Postgresql{}, - []byte{}, - errors.New("unexpected end of JSON input")}, + Postgresql{}, + []byte{}, + errors.New("unexpected end of JSON input")}, {[]byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster","creationTimestamp":qaz},"spec":{"postgresql":{"version":"","parameters":null},"volume":{"size":"","storageClass":""},"patroni":{"initdb":null,"pg_hba":null,"ttl":0,"loop_wait":0,"retry_timeout":0,"maximum_lag_on_failover":0},"resources":{"requests":{"cpu":"","memory":""},"limits":{"cpu":"","memory":""}},"teamId":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null},"status":"Invalid"}`), - Postgresql{}, - []byte{}, - errors.New("invalid character 'q' looking for beginning of value")}} + Postgresql{}, + []byte{}, + errors.New("invalid character 'q' looking for beginning of value")}} var postgresqlList = []struct { in []byte @@ -310,8 +291,8 @@ var postgresqlList = []struct { }, nil}, {[]byte(`{"apiVersion":"v1","items":[{"apiVersion":"acid.zalan.do/v1","kind":"Postgresql","metadata":{"labels":{"team":"acid"},"name":"acid-testcluster42","namespace"`), - PostgresqlList{}, - errors.New("unexpected end of JSON input")}} + PostgresqlList{}, + errors.New("unexpected end of JSON input")}} func mustParseTime(s string) time.Time { v, err := time.Parse("15:04", s) @@ -319,15 +300,15 @@ func mustParseTime(s string) time.Time { panic(err) } - return v + return v.UTC() } func TestParseTime(t *testing.T) { for _, tt := range parseTimeTests { - aTime, weekday, weekdayProvided, err := parseTime(tt.in) + aTime, err := parseTime(tt.in) if err != nil { if err.Error() != tt.err.Error() { - t.Errorf("ParseTime expected error: %v, got: %v", err, tt.err) + t.Errorf("ParseTime expected error: %v, got: %v", tt.err, err) } continue @@ -336,17 +317,27 @@ func TestParseTime(t *testing.T) { if aTime != tt.out { t.Errorf("Expected time: %v, got: %v", tt.out, aTime) } + } +} - if weekday != tt.outWeekday { - t.Errorf("Expected weekday: %v, got: %v", tt.outWeekday, weekday) +func TestWeekdayTime(t *testing.T) { + for _, tt := range parseWeekdayTests { + aTime, err := parseWeekday(tt.in) + if err != nil { + if err.Error() != tt.err.Error() { + t.Errorf("ParseWeekday expected error: %v, got: %v", tt.err, err) + } + + continue } - if weekdayProvided != tt.outWeekdayProvided { - t.Errorf("Expected weekdayProvided: %t, got: %t", tt.outWeekdayProvided, weekdayProvided) + if aTime != tt.out { + t.Errorf("Expected weekday: %v, got: %v", tt.out, aTime) } } } + func TestClusterName(t *testing.T) { for _, tt := range clusterNames { name, err := extractClusterName(tt.in, tt.inTeam) @@ -363,11 +354,15 @@ func TestClusterName(t *testing.T) { func TestUnmarshalMaintenanceWindow(t *testing.T) { for _, tt := range maintenanceWindows { var m MaintenanceWindow - err := m.UnmarshalJSON([]byte(tt.in)) + err := m.UnmarshalJSON(tt.in) if err != nil && err.Error() != tt.err.Error() { t.Errorf("MaintenanceWindow unmarshal expected error: %v, got %v", tt.err, err) continue } + if tt.err != nil && err == nil { + t.Errorf("Expected error") + continue + } if !reflect.DeepEqual(m, tt.out) { t.Errorf("Expected maintenace window: %#v, got: %#v", tt.out, m) From 41b774c0047248fcfd6095906931bdbc816f4fb4 Mon Sep 17 00:00:00 2001 From: Murat Kabilov Date: Mon, 29 May 2017 18:08:55 +0200 Subject: [PATCH 14/14] fix postgresql spec tests --- pkg/spec/postgresql_test.go | 67 +++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/pkg/spec/postgresql_test.go b/pkg/spec/postgresql_test.go index 46b27dcb7..48314d376 100644 --- a/pkg/spec/postgresql_test.go +++ b/pkg/spec/postgresql_test.go @@ -1,6 +1,7 @@ package spec import ( + "bytes" "encoding/json" "errors" "reflect" @@ -9,13 +10,12 @@ import ( "k8s.io/client-go/pkg/api/unversioned" "k8s.io/client-go/pkg/api/v1" - "bytes" ) var parseTimeTests = []struct { - in string - out time.Time - err error + in string + out time.Time + err error }{ {"16:08", mustParseTime("16:08"), nil}, {"11:00", mustParseTime("11:00"), nil}, @@ -26,9 +26,9 @@ var parseTimeTests = []struct { } var parseWeekdayTests = []struct { - in string - out time.Weekday - err error + in string + out time.Weekday + err error }{ {"Wed", time.Wednesday, nil}, {"Sunday", time.Weekday(0), errors.New("incorrect weekday")}, @@ -58,21 +58,31 @@ var maintenanceWindows = []struct { err error }{{[]byte(`"Tue:10:00-20:00"`), MaintenanceWindow{ - Weekday: time.Tuesday, - StartTime: mustParseTime("10:00"), - EndTime: mustParseTime("20:00"), + Everyday: false, + Weekday: time.Tuesday, + StartTime: mustParseTime("10:00"), + EndTime: mustParseTime("20:00"), }, nil}, {[]byte(`"Mon:10:00-10:00"`), MaintenanceWindow{ - Weekday: time.Monday, - StartTime: mustParseTime("10:00"), - EndTime: mustParseTime("10:00"), + Everyday: false, + Weekday: time.Monday, + StartTime: mustParseTime("10:00"), + EndTime: mustParseTime("10:00"), }, nil}, {[]byte(`"Sun:00:00-00:00"`), MaintenanceWindow{ - Weekday: time.Sunday, - StartTime: mustParseTime("00:00"), - EndTime: mustParseTime("00:00"), + Everyday: false, + Weekday: time.Sunday, + StartTime: mustParseTime("00:00"), + EndTime: mustParseTime("00:00"), + }, nil}, + {[]byte(`"01:00-10:00"`), + MaintenanceWindow{ + Everyday: true, + Weekday: time.Sunday, + StartTime: mustParseTime("01:00"), + EndTime: mustParseTime("10:00"), }, nil}, {[]byte(`"Mon:12:00-11:00"`), MaintenanceWindow{}, errors.New(`'From' time must be prior to the 'To' time`)}, {[]byte(`"Wed:33:00-00:00"`), MaintenanceWindow{}, errors.New(`could not parse start time: parsing time "33:00": hour out of range`)}, @@ -168,7 +178,8 @@ var unmarshalCluster = []struct { }, "maintenanceWindows": [ "Mon:01:00-06:00", - "Sat:00:00-04:00" + "Sat:00:00-04:00", + "05:00-05:15" ] } }`), @@ -214,21 +225,28 @@ var unmarshalCluster = []struct { NumberOfInstances: 2, Users: map[string]UserFlags{"zalando": {"superuser", "createdb"}}, MaintenanceWindows: []MaintenanceWindow{{ - Weekday: time.Monday, - StartTime: mustParseTime("01:00"), - EndTime: mustParseTime("06:00"), + Everyday: false, + Weekday: time.Monday, + StartTime: mustParseTime("01:00"), + EndTime: mustParseTime("06:00"), + }, { + Everyday: false, + Weekday: time.Saturday, + StartTime: mustParseTime("00:00"), + EndTime: mustParseTime("04:00"), }, { - Weekday: time.Saturday, - StartTime: mustParseTime("00:00"), - EndTime: mustParseTime("04:00"), + Everyday: true, + Weekday: time.Sunday, + StartTime: mustParseTime("05:00"), + EndTime: mustParseTime("05:15"), }, }, ClusterName: "testcluster1", }, Error: nil, }, - []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"9.6","parameters":{"log_statement":"all","max_connections":"10","shared_buffers":"32MB"}},"volume":{"size":"5Gi","storageClass":"SSD"},"patroni":{"initdb":{"data-checksums":"true","encoding":"UTF8","locale":"en_US.UTF-8"},"pg_hba":["hostssl all all 0.0.0.0/0 md5","host all all 0.0.0.0/0 md5"],"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432},"resources":{"requests":{"cpu":"10m","memory":"50Mi"},"limits":{"cpu":"300m","memory":"3000Mi"}},"teamId":"ACID","allowedSourceRanges":["127.0.0.1/32"],"numberOfInstances":2,"users":{"zalando":["superuser","createdb"]},"maintenanceWindows":["Mon:01:00-06:00","Sat:00:00-04:00"]}}`), nil}, + []byte(`{"kind":"Postgresql","apiVersion":"acid.zalan.do/v1","metadata":{"name":"acid-testcluster1","creationTimestamp":null},"spec":{"postgresql":{"version":"9.6","parameters":{"log_statement":"all","max_connections":"10","shared_buffers":"32MB"}},"volume":{"size":"5Gi","storageClass":"SSD"},"patroni":{"initdb":{"data-checksums":"true","encoding":"UTF8","locale":"en_US.UTF-8"},"pg_hba":["hostssl all all 0.0.0.0/0 md5","host all all 0.0.0.0/0 md5"],"ttl":30,"loop_wait":10,"retry_timeout":10,"maximum_lag_on_failover":33554432},"resources":{"requests":{"cpu":"10m","memory":"50Mi"},"limits":{"cpu":"300m","memory":"3000Mi"}},"teamId":"ACID","allowedSourceRanges":["127.0.0.1/32"],"numberOfInstances":2,"users":{"zalando":["superuser","createdb"]},"maintenanceWindows":["Mon:01:00-06:00","Sat:00:00-04:00","05:00-05:15"]}}`), nil}, { []byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1","metadata": {"name": "teapot-testcluster1"}, "spec": {"teamId": "acid"}}`), Postgresql{ @@ -337,7 +355,6 @@ func TestWeekdayTime(t *testing.T) { } } - func TestClusterName(t *testing.T) { for _, tt := range clusterNames { name, err := extractClusterName(tt.in, tt.inTeam)