-
Notifications
You must be signed in to change notification settings - Fork 1k
Remove the check for the clone cluster name. #270
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
33da7f8
9c01081
04d6a76
f5e1bde
e14eb54
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ package spec | |
import ( | ||
"encoding/json" | ||
"fmt" | ||
"regexp" | ||
"strings" | ||
"time" | ||
|
||
|
@@ -76,6 +77,12 @@ const ( | |
ClusterStatusInvalid PostgresStatus = "Invalid" | ||
) | ||
|
||
const ( | ||
serviceNameMaxLength = 63 | ||
clusterNameMaxLength = serviceNameMaxLength - len("-repl") | ||
serviceNameRegexString = `^[a-z]([-a-z0-9]*[a-z0-9])?$` | ||
) | ||
|
||
// Postgresql defines PostgreSQL Custom Resource Definition Object. | ||
type Postgresql struct { | ||
metav1.TypeMeta `json:",inline"` | ||
|
@@ -126,7 +133,10 @@ type PostgresqlList struct { | |
Items []Postgresql `json:"items"` | ||
} | ||
|
||
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} | ||
serviceNameRegex = regexp.MustCompile(serviceNameRegexString) | ||
) | ||
|
||
func parseTime(s string) (time.Time, error) { | ||
parts := strings.Split(s, ":") | ||
|
@@ -225,10 +235,31 @@ func extractClusterName(clusterName string, teamName string) (string, error) { | |
if strings.ToLower(clusterName[:teamNameLen+1]) != strings.ToLower(teamName)+"-" { | ||
return "", fmt.Errorf("name must match {TEAM}-{NAME} format") | ||
} | ||
if len(clusterName) > clusterNameMaxLength { | ||
return "", fmt.Errorf("name cannot be longer than %d characters", clusterNameMaxLength) | ||
} | ||
if !serviceNameRegex.MatchString(clusterName) { | ||
return "", fmt.Errorf("name must confirm to DNS-1035, regex used for validation is %q", | ||
serviceNameRegexString) | ||
} | ||
|
||
return clusterName[teamNameLen+1:], nil | ||
} | ||
|
||
func validateCloneClusterDescription(clone *CloneDescription) error { | ||
// when cloning from the basebackup (no end timestamp) check that the cluster name is a valid service name | ||
if clone.ClusterName != "" && clone.EndTimestamp == "" { | ||
if !serviceNameRegex.MatchString(clone.ClusterName) { | ||
return fmt.Errorf("clone cluster name must confirm to DNS-1035, regex used for validation is %q", | ||
serviceNameRegexString) | ||
} | ||
if len(clone.ClusterName) > serviceNameMaxLength { | ||
return fmt.Errorf("clone cluster name must be no longer than %d characters", serviceNameMaxLength) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
type postgresqlListCopy PostgresqlList | ||
type postgresqlCopy Postgresql | ||
|
||
|
@@ -252,22 +283,16 @@ func (p *Postgresql) UnmarshalJSON(data []byte) error { | |
} | ||
tmp2 := Postgresql(tmp) | ||
|
||
clusterName, err := extractClusterName(tmp2.ObjectMeta.Name, tmp2.Spec.TeamID) | ||
if err == nil { | ||
tmp2.Spec.ClusterName = clusterName | ||
} else { | ||
if clusterName, err := extractClusterName(tmp2.ObjectMeta.Name, tmp2.Spec.TeamID); err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will this code now support restoring from S3 where the cluster name is basically: "foobar" (spilo ec2 deployments) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That line doesn't validate the clone cluster name, it happens at https://github.com/zalando-incubator/postgres-operator/pull/270/files/04d6a7607508fef669faacfb57ef9ef443b587da#diff-44fe837275fc3b64cc6c85eb90ce74c9R247 There, the name for the cluster clone that comes from S3 is not validated. |
||
tmp2.Error = err | ||
tmp2.Status = ClusterStatusInvalid | ||
} else if err := validateCloneClusterDescription(&tmp2.Spec.Clone); err != nil { | ||
tmp2.Error = err | ||
tmp2.Status = ClusterStatusInvalid | ||
} else { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i feel the summary of this discussion is worth a comment here:
i.e. it is outside of the operator scope to check this |
||
tmp2.Spec.ClusterName = clusterName | ||
} | ||
// The assumption below is that a cluster to clone, if any, belongs to the same team | ||
if tmp2.Spec.Clone.ClusterName != "" { | ||
_, err := extractClusterName(tmp2.Spec.Clone.ClusterName, tmp2.Spec.TeamID) | ||
if err != nil { | ||
tmp2.Error = fmt.Errorf("%s for the cluster to clone", err) | ||
tmp2.Spec.Clone = CloneDescription{} | ||
tmp2.Status = ClusterStatusInvalid | ||
} | ||
} | ||
|
||
*p = tmp2 | ||
|
||
return nil | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,14 +43,29 @@ var clusterNames = []struct { | |
{"acid-test", "acid", "test", nil}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. seems like "foobar" is not covered but should work as this is team "foo" and cluster "bar" in ec2 spilo way There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Jan-M which test do you have in mind? We do test with exactly 'foobar' as a clone cluster name for cloning from basebackup in line 66 of this patch. |
||
{"test-my-name", "test", "my-name", nil}, | ||
{"my-team-another-test", "my-team", "another-test", nil}, | ||
{"------strange-team-cluster", "-----", "strange-team-cluster", nil}, | ||
{"------strange-team-cluster", "-----", "strange-team-cluster", | ||
errors.New(`name must confirm to DNS-1035, regex used for validation is "^[a-z]([-a-z0-9]*[a-z0-9])?$"`)}, | ||
{"fooobar-fooobarfooobarfooobarfooobarfooobarfooobarfooobarfooobar", "fooobar", "", | ||
errors.New("name cannot be longer than 58 characters")}, | ||
{"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 cloneClusterDescriptions = []struct { | ||
in *CloneDescription | ||
err error | ||
}{ | ||
{&CloneDescription{"foo+bar", "", "NotEmpty"}, nil}, | ||
{&CloneDescription{"foo+bar", "", ""}, | ||
errors.New(`clone cluster name must confirm to DNS-1035, regex used for validation is "^[a-z]([-a-z0-9]*[a-z0-9])?$"`)}, | ||
{&CloneDescription{"foobar123456789012345678901234567890123456789012345678901234567890", "", ""}, | ||
errors.New("clone cluster name must be no longer than 63 characters")}, | ||
{&CloneDescription{"foobar", "", ""}, nil}, | ||
} | ||
|
||
var maintenanceWindows = []struct { | ||
in []byte | ||
out MaintenanceWindow | ||
|
@@ -279,14 +294,15 @@ var unmarshalCluster = []struct { | |
Name: "acid-testcluster1", | ||
}, | ||
Spec: PostgresSpec{ | ||
TeamID: "acid", | ||
Clone: CloneDescription{}, | ||
TeamID: "acid", | ||
Clone: CloneDescription{ | ||
ClusterName: "team-batman", | ||
}, | ||
ClusterName: "testcluster1", | ||
}, | ||
Status: ClusterStatusInvalid, | ||
Error: errors.New("name must match {TEAM}-{NAME} format for the cluster to clone"), | ||
Error: nil, | ||
}, | ||
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}, | ||
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}, | ||
{[]byte(`{"kind": "Postgresql","apiVersion": "acid.zalan.do/v1"`), | ||
Postgresql{}, | ||
[]byte{}, | ||
|
@@ -350,11 +366,12 @@ func TestParseTime(t *testing.T) { | |
for _, tt := range parseTimeTests { | ||
aTime, err := parseTime(tt.in) | ||
if err != nil { | ||
if err.Error() != tt.err.Error() { | ||
if tt.err == nil || err.Error() != tt.err.Error() { | ||
t.Errorf("ParseTime expected error: %v, got: %v", tt.err, err) | ||
} | ||
|
||
continue | ||
} else if tt.err != nil { | ||
t.Errorf("Expected error: %v", tt.err) | ||
} | ||
|
||
if aTime != tt.out { | ||
|
@@ -367,11 +384,12 @@ func TestWeekdayTime(t *testing.T) { | |
for _, tt := range parseWeekdayTests { | ||
aTime, err := parseWeekday(tt.in) | ||
if err != nil { | ||
if err.Error() != tt.err.Error() { | ||
if tt.err == nil || err.Error() != tt.err.Error() { | ||
t.Errorf("ParseWeekday expected error: %v, got: %v", tt.err, err) | ||
} | ||
|
||
continue | ||
} else if tt.err != nil { | ||
t.Errorf("Expected error: %v", tt.err) | ||
} | ||
|
||
if aTime != tt.out { | ||
|
@@ -383,27 +401,43 @@ func TestWeekdayTime(t *testing.T) { | |
func TestClusterName(t *testing.T) { | ||
for _, tt := range clusterNames { | ||
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) | ||
if err != nil { | ||
if tt.err == nil || err.Error() != tt.err.Error() { | ||
t.Errorf("extractClusterName expected error: %v, got: %v", tt.err, err) | ||
} | ||
continue | ||
} else if tt.err != nil { | ||
t.Errorf("Expected error: %v", tt.err) | ||
} | ||
if name != tt.clusterName { | ||
t.Errorf("Expected cluserName: %q, got: %q", tt.clusterName, name) | ||
} | ||
} | ||
} | ||
|
||
func TestCloneClusterDescription(t *testing.T) { | ||
for _, tt := range cloneClusterDescriptions { | ||
if err := validateCloneClusterDescription(tt.in); err != nil { | ||
if tt.err == nil || err.Error() != tt.err.Error() { | ||
t.Errorf("testCloneClusterDescription expected error: %v, got: %v", tt.err, err) | ||
} | ||
} else if tt.err != nil { | ||
t.Errorf("Expected error: %v", tt.err) | ||
} | ||
} | ||
} | ||
|
||
func TestUnmarshalMaintenanceWindow(t *testing.T) { | ||
for _, tt := range maintenanceWindows { | ||
var m MaintenanceWindow | ||
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") | ||
if err != nil { | ||
if tt.err == nil || err.Error() != tt.err.Error() { | ||
t.Errorf("MaintenanceWindow unmarshal expected error: %v, got %v", tt.err, err) | ||
} | ||
continue | ||
} else if tt.err != nil { | ||
t.Errorf("Expected error: %v", tt.err) | ||
} | ||
|
||
if !reflect.DeepEqual(m, tt.out) { | ||
|
@@ -421,7 +455,6 @@ func TestMarshalMaintenanceWindow(t *testing.T) { | |
s, err := tt.out.MarshalJSON() | ||
if err != nil { | ||
t.Errorf("Marshal Error: %v", err) | ||
continue | ||
} | ||
|
||
if !bytes.Equal(s, tt.in) { | ||
|
@@ -435,11 +468,12 @@ func TestPostgresUnmarshal(t *testing.T) { | |
var cluster Postgresql | ||
err := cluster.UnmarshalJSON(tt.in) | ||
if err != nil { | ||
if err.Error() != tt.err.Error() { | ||
if tt.err == nil || err.Error() != tt.err.Error() { | ||
t.Errorf("Unmarshal expected error: %v, got: %v", tt.err, err) | ||
} | ||
|
||
continue | ||
} else if tt.err != nil { | ||
t.Errorf("Expected error: %v", tt.err) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and go no error, right ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes |
||
} | ||
|
||
if !reflect.DeepEqual(cluster, tt.out) { | ||
|
@@ -457,7 +491,6 @@ func TestMarshal(t *testing.T) { | |
m, err := json.Marshal(tt.out) | ||
if err != nil { | ||
t.Errorf("Marshal error: %v", err) | ||
continue | ||
} | ||
if !bytes.Equal(m, tt.marshal) { | ||
t.Errorf("Marshal Postgresql expected: %q, got: %q", string(tt.marshal), string(m)) | ||
|
@@ -481,10 +514,15 @@ 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 err != nil { | ||
if tt.err == nil || err.Error() != tt.err.Error() { | ||
t.Errorf("PostgresqlList unmarshal expected error: %v, got: %v", tt.err, err) | ||
} | ||
continue | ||
} else if tt.err != nil { | ||
t.Errorf("Expected error: %v", tt.err) | ||
} | ||
|
||
if !reflect.DeepEqual(list, tt.out) { | ||
t.Errorf("Postgresql list unmarshall expected: %#v, got: %#v", tt.out, list) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do existing cluster clones always have names adhering to this format ? that should be enforced by k8s, right ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes