Skip to content

Commit 4c8dfd7

Browse files
Remove the check for the clone cluster name. (zalando#270)
* Sanity checks for the cluster name, improve tests. - check that the normal and clone cluster name complies with the valid service name. For clone cluster, only do it if clone timestamp is not set; with a clone timestamp set, the clone name points to the S3 bucket - add tests and improve existing ones, making sure we don't call Error() method for an empty error, as well as that we don't miss cases where expected error is not empty, but actual call to be tested does not return an error. Code review by @zerg-junior and @Jan-M
1 parent fe47f9e commit 4c8dfd7

File tree

2 files changed

+102
-39
lines changed

2 files changed

+102
-39
lines changed

pkg/spec/postgresql.go

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package spec
33
import (
44
"encoding/json"
55
"fmt"
6+
"regexp"
67
"strings"
78
"time"
89

@@ -76,6 +77,12 @@ const (
7677
ClusterStatusInvalid PostgresStatus = "Invalid"
7778
)
7879

80+
const (
81+
serviceNameMaxLength = 63
82+
clusterNameMaxLength = serviceNameMaxLength - len("-repl")
83+
serviceNameRegexString = `^[a-z]([-a-z0-9]*[a-z0-9])?$`
84+
)
85+
7986
// Postgresql defines PostgreSQL Custom Resource Definition Object.
8087
type Postgresql struct {
8188
metav1.TypeMeta `json:",inline"`
@@ -126,7 +133,10 @@ type PostgresqlList struct {
126133
Items []Postgresql `json:"items"`
127134
}
128135

129-
var weekdays = map[string]int{"Sun": 0, "Mon": 1, "Tue": 2, "Wed": 3, "Thu": 4, "Fri": 5, "Sat": 6}
136+
var (
137+
weekdays = map[string]int{"Sun": 0, "Mon": 1, "Tue": 2, "Wed": 3, "Thu": 4, "Fri": 5, "Sat": 6}
138+
serviceNameRegex = regexp.MustCompile(serviceNameRegexString)
139+
)
130140

131141
func parseTime(s string) (time.Time, error) {
132142
parts := strings.Split(s, ":")
@@ -225,10 +235,31 @@ func extractClusterName(clusterName string, teamName string) (string, error) {
225235
if strings.ToLower(clusterName[:teamNameLen+1]) != strings.ToLower(teamName)+"-" {
226236
return "", fmt.Errorf("name must match {TEAM}-{NAME} format")
227237
}
238+
if len(clusterName) > clusterNameMaxLength {
239+
return "", fmt.Errorf("name cannot be longer than %d characters", clusterNameMaxLength)
240+
}
241+
if !serviceNameRegex.MatchString(clusterName) {
242+
return "", fmt.Errorf("name must confirm to DNS-1035, regex used for validation is %q",
243+
serviceNameRegexString)
244+
}
228245

229246
return clusterName[teamNameLen+1:], nil
230247
}
231248

249+
func validateCloneClusterDescription(clone *CloneDescription) error {
250+
// when cloning from the basebackup (no end timestamp) check that the cluster name is a valid service name
251+
if clone.ClusterName != "" && clone.EndTimestamp == "" {
252+
if !serviceNameRegex.MatchString(clone.ClusterName) {
253+
return fmt.Errorf("clone cluster name must confirm to DNS-1035, regex used for validation is %q",
254+
serviceNameRegexString)
255+
}
256+
if len(clone.ClusterName) > serviceNameMaxLength {
257+
return fmt.Errorf("clone cluster name must be no longer than %d characters", serviceNameMaxLength)
258+
}
259+
}
260+
return nil
261+
}
262+
232263
type postgresqlListCopy PostgresqlList
233264
type postgresqlCopy Postgresql
234265

@@ -252,22 +283,16 @@ func (p *Postgresql) UnmarshalJSON(data []byte) error {
252283
}
253284
tmp2 := Postgresql(tmp)
254285

255-
clusterName, err := extractClusterName(tmp2.ObjectMeta.Name, tmp2.Spec.TeamID)
256-
if err == nil {
257-
tmp2.Spec.ClusterName = clusterName
258-
} else {
286+
if clusterName, err := extractClusterName(tmp2.ObjectMeta.Name, tmp2.Spec.TeamID); err != nil {
259287
tmp2.Error = err
260288
tmp2.Status = ClusterStatusInvalid
289+
} else if err := validateCloneClusterDescription(&tmp2.Spec.Clone); err != nil {
290+
tmp2.Error = err
291+
tmp2.Status = ClusterStatusInvalid
292+
} else {
293+
tmp2.Spec.ClusterName = clusterName
261294
}
262-
// The assumption below is that a cluster to clone, if any, belongs to the same team
263-
if tmp2.Spec.Clone.ClusterName != "" {
264-
_, err := extractClusterName(tmp2.Spec.Clone.ClusterName, tmp2.Spec.TeamID)
265-
if err != nil {
266-
tmp2.Error = fmt.Errorf("%s for the cluster to clone", err)
267-
tmp2.Spec.Clone = CloneDescription{}
268-
tmp2.Status = ClusterStatusInvalid
269-
}
270-
}
295+
271296
*p = tmp2
272297

273298
return nil

pkg/spec/postgresql_test.go

Lines changed: 63 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,29 @@ var clusterNames = []struct {
4343
{"acid-test", "acid", "test", nil},
4444
{"test-my-name", "test", "my-name", nil},
4545
{"my-team-another-test", "my-team", "another-test", nil},
46-
{"------strange-team-cluster", "-----", "strange-team-cluster", nil},
46+
{"------strange-team-cluster", "-----", "strange-team-cluster",
47+
errors.New(`name must confirm to DNS-1035, regex used for validation is "^[a-z]([-a-z0-9]*[a-z0-9])?$"`)},
48+
{"fooobar-fooobarfooobarfooobarfooobarfooobarfooobarfooobarfooobar", "fooobar", "",
49+
errors.New("name cannot be longer than 58 characters")},
4750
{"acid-test", "test", "", errors.New("name must match {TEAM}-{NAME} format")},
4851
{"-test", "", "", errors.New("team name is empty")},
4952
{"-test", "-", "", errors.New("name must match {TEAM}-{NAME} format")},
5053
{"", "-", "", errors.New("name is too short")},
5154
{"-", "-", "", errors.New("name is too short")},
5255
}
5356

57+
var cloneClusterDescriptions = []struct {
58+
in *CloneDescription
59+
err error
60+
}{
61+
{&CloneDescription{"foo+bar", "", "NotEmpty"}, nil},
62+
{&CloneDescription{"foo+bar", "", ""},
63+
errors.New(`clone cluster name must confirm to DNS-1035, regex used for validation is "^[a-z]([-a-z0-9]*[a-z0-9])?$"`)},
64+
{&CloneDescription{"foobar123456789012345678901234567890123456789012345678901234567890", "", ""},
65+
errors.New("clone cluster name must be no longer than 63 characters")},
66+
{&CloneDescription{"foobar", "", ""}, nil},
67+
}
68+
5469
var maintenanceWindows = []struct {
5570
in []byte
5671
out MaintenanceWindow
@@ -279,14 +294,15 @@ var unmarshalCluster = []struct {
279294
Name: "acid-testcluster1",
280295
},
281296
Spec: PostgresSpec{
282-
TeamID: "acid",
283-
Clone: CloneDescription{},
297+
TeamID: "acid",
298+
Clone: CloneDescription{
299+
ClusterName: "team-batman",
300+
},
284301
ClusterName: "testcluster1",
285302
},
286-
Status: ClusterStatusInvalid,
287-
Error: errors.New("name must match {TEAM}-{NAME} format for the cluster to clone"),
303+
Error: nil,
288304
},
289-
marshal: []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":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{}},"status":"Invalid"}`), err: nil},
305+
marshal: []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":"acid","allowedSourceRanges":null,"numberOfInstances":0,"users":null,"clone":{"cluster":"team-batman"}}}`), err: nil},
290306
{[]byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1"`),
291307
Postgresql{},
292308
[]byte{},
@@ -350,11 +366,12 @@ func TestParseTime(t *testing.T) {
350366
for _, tt := range parseTimeTests {
351367
aTime, err := parseTime(tt.in)
352368
if err != nil {
353-
if err.Error() != tt.err.Error() {
369+
if tt.err == nil || err.Error() != tt.err.Error() {
354370
t.Errorf("ParseTime expected error: %v, got: %v", tt.err, err)
355371
}
356-
357372
continue
373+
} else if tt.err != nil {
374+
t.Errorf("Expected error: %v", tt.err)
358375
}
359376

360377
if aTime != tt.out {
@@ -367,11 +384,12 @@ func TestWeekdayTime(t *testing.T) {
367384
for _, tt := range parseWeekdayTests {
368385
aTime, err := parseWeekday(tt.in)
369386
if err != nil {
370-
if err.Error() != tt.err.Error() {
387+
if tt.err == nil || err.Error() != tt.err.Error() {
371388
t.Errorf("ParseWeekday expected error: %v, got: %v", tt.err, err)
372389
}
373-
374390
continue
391+
} else if tt.err != nil {
392+
t.Errorf("Expected error: %v", tt.err)
375393
}
376394

377395
if aTime != tt.out {
@@ -383,27 +401,43 @@ func TestWeekdayTime(t *testing.T) {
383401
func TestClusterName(t *testing.T) {
384402
for _, tt := range clusterNames {
385403
name, err := extractClusterName(tt.in, tt.inTeam)
386-
if err != nil && err.Error() != tt.err.Error() {
387-
t.Errorf("extractClusterName expected error: %v, got: %v", tt.err, err)
404+
if err != nil {
405+
if tt.err == nil || err.Error() != tt.err.Error() {
406+
t.Errorf("extractClusterName expected error: %v, got: %v", tt.err, err)
407+
}
388408
continue
409+
} else if tt.err != nil {
410+
t.Errorf("Expected error: %v", tt.err)
389411
}
390412
if name != tt.clusterName {
391413
t.Errorf("Expected cluserName: %q, got: %q", tt.clusterName, name)
392414
}
393415
}
394416
}
395417

418+
func TestCloneClusterDescription(t *testing.T) {
419+
for _, tt := range cloneClusterDescriptions {
420+
if err := validateCloneClusterDescription(tt.in); err != nil {
421+
if tt.err == nil || err.Error() != tt.err.Error() {
422+
t.Errorf("testCloneClusterDescription expected error: %v, got: %v", tt.err, err)
423+
}
424+
} else if tt.err != nil {
425+
t.Errorf("Expected error: %v", tt.err)
426+
}
427+
}
428+
}
429+
396430
func TestUnmarshalMaintenanceWindow(t *testing.T) {
397431
for _, tt := range maintenanceWindows {
398432
var m MaintenanceWindow
399433
err := m.UnmarshalJSON(tt.in)
400-
if err != nil && err.Error() != tt.err.Error() {
401-
t.Errorf("MaintenanceWindow unmarshal expected error: %v, got %v", tt.err, err)
402-
continue
403-
}
404-
if tt.err != nil && err == nil {
405-
t.Errorf("Expected error")
434+
if err != nil {
435+
if tt.err == nil || err.Error() != tt.err.Error() {
436+
t.Errorf("MaintenanceWindow unmarshal expected error: %v, got %v", tt.err, err)
437+
}
406438
continue
439+
} else if tt.err != nil {
440+
t.Errorf("Expected error: %v", tt.err)
407441
}
408442

409443
if !reflect.DeepEqual(m, tt.out) {
@@ -421,7 +455,6 @@ func TestMarshalMaintenanceWindow(t *testing.T) {
421455
s, err := tt.out.MarshalJSON()
422456
if err != nil {
423457
t.Errorf("Marshal Error: %v", err)
424-
continue
425458
}
426459

427460
if !bytes.Equal(s, tt.in) {
@@ -435,11 +468,12 @@ func TestPostgresUnmarshal(t *testing.T) {
435468
var cluster Postgresql
436469
err := cluster.UnmarshalJSON(tt.in)
437470
if err != nil {
438-
if err.Error() != tt.err.Error() {
471+
if tt.err == nil || err.Error() != tt.err.Error() {
439472
t.Errorf("Unmarshal expected error: %v, got: %v", tt.err, err)
440473
}
441-
442474
continue
475+
} else if tt.err != nil {
476+
t.Errorf("Expected error: %v", tt.err)
443477
}
444478

445479
if !reflect.DeepEqual(cluster, tt.out) {
@@ -457,7 +491,6 @@ func TestMarshal(t *testing.T) {
457491
m, err := json.Marshal(tt.out)
458492
if err != nil {
459493
t.Errorf("Marshal error: %v", err)
460-
continue
461494
}
462495
if !bytes.Equal(m, tt.marshal) {
463496
t.Errorf("Marshal Postgresql expected: %q, got: %q", string(tt.marshal), string(m))
@@ -481,10 +514,15 @@ func TestUnmarshalPostgresList(t *testing.T) {
481514
for _, tt := range postgresqlList {
482515
var list PostgresqlList
483516
err := list.UnmarshalJSON(tt.in)
484-
if err != nil && err.Error() != tt.err.Error() {
485-
t.Errorf("PostgresqlList unmarshal expected error: %v, got: %v", tt.err, err)
486-
return
517+
if err != nil {
518+
if tt.err == nil || err.Error() != tt.err.Error() {
519+
t.Errorf("PostgresqlList unmarshal expected error: %v, got: %v", tt.err, err)
520+
}
521+
continue
522+
} else if tt.err != nil {
523+
t.Errorf("Expected error: %v", tt.err)
487524
}
525+
488526
if !reflect.DeepEqual(list, tt.out) {
489527
t.Errorf("Postgresql list unmarshall expected: %#v, got: %#v", tt.out, list)
490528
}

0 commit comments

Comments
 (0)