Skip to content

Commit d61fd51

Browse files
author
wei
committed
fix(etcd): canonicalize member join peer URLs
1 parent 93564d1 commit d61fd51

2 files changed

Lines changed: 105 additions & 1 deletion

File tree

addons/etcd/scripts-ut-spec/member_join_spec.sh

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,16 @@ Describe "Etcd Member Join Script Tests"
107107
'"Name" : ""' \
108108
'"PeerURL" : "http://etcd-1.etcd-headless.default.svc.cluster.local:2380"' ''
109109
;;
110+
unstarted-canonical-equivalent)
111+
printf '%s\n' \
112+
'"ID" : 0' \
113+
'"Name" : "etcd-0"' \
114+
'"PeerURL" : "http://etcd-0.etcd-headless.default.svc.cluster.local:2380"' \
115+
'"ClientURL" : "http://etcd-0.etcd-headless.default.svc.cluster.local:2379"' '' \
116+
'"ID" : 1' \
117+
'"Name" : ""' \
118+
'"PeerURL" : "http://ETCD-1.ETCD-HEADLESS.DEFAULT.SVC.CLUSTER.LOCAL.:2380"' ''
119+
;;
110120
unstarted-target-only)
111121
printf '%s\n' \
112122
'"ID" : 1' \
@@ -272,6 +282,39 @@ Describe "Etcd Member Join Script Tests"
272282
The output should eq "exact"
273283
End
274284

285+
It "classifies a canonical-equivalent unstarted peer URL as registered"
286+
write_fields \
287+
'"ID" : 1' \
288+
'"Name" : ""' \
289+
'"PeerURL" : "http://ETCD-1.ETCD-HEADLESS.DEFAULT.SVC.CLUSTER.LOCAL.:2380"' ''
290+
When call classify_fixture "$TEST_DIR/member-list.fields" etcd-1 \
291+
http://etcd-1.etcd-headless.default.svc.cluster.local:2380
292+
The status should be success
293+
The output should eq "unstarted-registered"
294+
End
295+
296+
It "classifies a canonical-equivalent started peer URL as exact"
297+
write_fields \
298+
'"ID" : 1' \
299+
'"Name" : "etcd-1"' \
300+
'"PeerURL" : "http://ETCD-1.ETCD-HEADLESS.DEFAULT.SVC.CLUSTER.LOCAL.:2380"' ''
301+
When call classify_fixture "$TEST_DIR/member-list.fields" etcd-1 \
302+
http://etcd-1.etcd-headless.default.svc.cluster.local:2380
303+
The status should be success
304+
The output should eq "exact"
305+
End
306+
307+
It "keeps a genuinely different canonical peer URL as a name conflict"
308+
write_fields \
309+
'"ID" : 1' \
310+
'"Name" : "etcd-1"' \
311+
'"PeerURL" : "http://ETCD-OTHER.EXAMPLE.:2380"' ''
312+
When call classify_fixture "$TEST_DIR/member-list.fields" etcd-1 \
313+
http://etcd-1.etcd-headless.default.svc.cluster.local:2380
314+
The status should be success
315+
The output should eq "name-conflict"
316+
End
317+
275318
It "classifies the same name with another URL as a name conflict"
276319
write_fields \
277320
'"ID" : 1' \
@@ -617,6 +660,15 @@ Describe "Etcd Member Join Script Tests"
617660
The contents of file "$MEMBER_JOIN_READ_COUNT" should eq "1"
618661
End
619662

663+
It "closes canonical-equivalent unstarted replay without a second add"
664+
set_member_states unstarted-canonical-equivalent
665+
When call add_member
666+
The status should be success
667+
The error should include "registered but not started"
668+
The contents of file "$MEMBER_JOIN_CALL_LOG" should eq ""
669+
The contents of file "$MEMBER_JOIN_READ_COUNT" should eq "1"
670+
End
671+
620672
It "closes an exact replay even when the target is the only current contact"
621673
set_member_states exact-only
622674
When call add_member

addons/etcd/scripts/member-join.sh

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,52 @@ classify_member_state() {
3232
return value
3333
}
3434
35+
function canonical_host(host, parts, count, i, result) {
36+
sub(/[.]$/, "", host)
37+
host = tolower(host)
38+
if (host == "" || length(host) > 253) return ""
39+
if (host ~ /^[0-9.]+$/) {
40+
count = split(host, parts, ".")
41+
if (count != 4) return ""
42+
result = ""
43+
for (i = 1; i <= 4; i++) {
44+
if (parts[i] !~ /^[0-9]+$/ || parts[i] + 0 > 255) return ""
45+
result = result (i == 1 ? "" : ".") (parts[i] + 0)
46+
}
47+
return result
48+
}
49+
count = split(host, parts, ".")
50+
for (i = 1; i <= count; i++) {
51+
if (length(parts[i]) < 1 || length(parts[i]) > 63 ||
52+
parts[i] !~ /^[a-z0-9][a-z0-9-]*$/ || parts[i] ~ /-$/) return ""
53+
}
54+
return host
55+
}
56+
57+
function canonical_peer_url(value, required_protocol, marker, rest, host, port, protocol) {
58+
marker = index(value, "://")
59+
if (marker == 0) return ""
60+
protocol = substr(value, 1, marker - 1)
61+
rest = substr(value, marker + 3)
62+
if (protocol != required_protocol || rest !~ /:[0-9]+$/) return ""
63+
port = rest
64+
sub(/^.*:/, "", port)
65+
if (port !~ /^[1-9][0-9]*$/ || port + 0 > 65535) return ""
66+
host = rest
67+
sub(/:[0-9]+$/, "", host)
68+
host = canonical_host(host)
69+
if (host == "") return ""
70+
return protocol "://" host ":" port
71+
}
72+
73+
BEGIN {
74+
target_protocol = target_peer_url
75+
sub(/:.*/, "", target_protocol)
76+
if (target_protocol != "http" && target_protocol != "https") malformed = 1
77+
canonical_target_peer_url = canonical_peer_url(target_peer_url, target_protocol)
78+
if (canonical_target_peer_url == "") malformed = 1
79+
}
80+
3581
function finish_member() {
3682
if (!in_member) {
3783
return
@@ -89,13 +135,19 @@ classify_member_state() {
89135
}
90136
91137
/^"PeerURL"[[:space:]]*:/ {
138+
peer_url = ""
92139
if (!in_member ||
93140
$0 !~ /^"PeerURL"[[:space:]]*:[[:space:]]*"[^"]+"[[:space:]]*$/) {
94141
malformed = 1
95142
next
96143
}
144+
peer_url = canonical_peer_url(field_value($0), target_protocol)
145+
if (peer_url == "") {
146+
malformed = 1
147+
next
148+
}
97149
peer_seen = 1
98-
if (field_value($0) == target_peer_url) {
150+
if (peer_url == canonical_target_peer_url) {
99151
peer_matches = 1
100152
}
101153
next

0 commit comments

Comments
 (0)