@@ -2,12 +2,12 @@ package controller
2
2
3
3
import (
4
4
"context"
5
- // "strconv"
6
- // "strings"
5
+ "strconv"
6
+ "strings"
7
7
"sync"
8
8
9
9
"github.com/aenix-io/etcd-operator/api/v1alpha1"
10
- // "github.com/aenix-io/etcd-operator/pkg/set"
10
+ "github.com/aenix-io/etcd-operator/pkg/set"
11
11
clientv3 "go.etcd.io/etcd/client/v3"
12
12
appsv1 "k8s.io/api/apps/v1"
13
13
corev1 "k8s.io/api/core/v1"
@@ -49,15 +49,43 @@ func (o *observables) setClusterID() {
49
49
50
50
// inSplitbrain compares clusterID field with clusterIDs in etcdStatuses.
51
51
// If more than one unique ID is reported, cluster is in splitbrain.
52
+ // Also if members have different opinions on the list of members, this is
53
+ // also a splitbrain.
52
54
func (o * observables ) inSplitbrain () bool {
55
+ return o .clusterIDsAllEqual () && o .memberListsAllEqual ()
56
+ }
57
+
58
+ func (o * observables ) clusterIDsAllEqual () bool {
59
+ ids := set .New [uint64 ]()
53
60
for i := range o .etcdStatuses {
54
61
if o .etcdStatuses [i ].endpointStatus != nil {
55
- if o .clusterID != o .etcdStatuses [i ].endpointStatus .Header .ClusterId {
56
- return true
62
+ ids .Add (o .etcdStatuses [i ].endpointStatus .Header .ClusterId )
63
+ }
64
+ }
65
+ return len (ids ) <= 1
66
+ }
67
+
68
+ func (o * observables ) memberListsAllEqual () bool {
69
+ type m struct {
70
+ Name string
71
+ ID uint64
72
+ }
73
+ memberLists := make ([]set.Set [m ], 0 , len (o .etcdStatuses ))
74
+ for i := range o .etcdStatuses {
75
+ if o .etcdStatuses [i ].memberList != nil {
76
+ memberSet := set .New [m ]()
77
+ for _ , member := range o .etcdStatuses [i ].memberList .Members {
78
+ memberSet .Add (m {member .Name , member .ID })
57
79
}
80
+ memberLists = append (memberLists , memberSet )
81
+ }
82
+ }
83
+ for i := range memberLists {
84
+ if ! memberLists [0 ].Equals (memberLists [i ]) {
85
+ return false
58
86
}
59
87
}
60
- return false
88
+ return true
61
89
}
62
90
63
91
// fill takes a single-endpoint client and populates the fields of etcdStatus
@@ -73,15 +101,75 @@ func (s *etcdStatus) fill(ctx context.Context, c *clientv3.Client) {
73
101
wg .Wait ()
74
102
}
75
103
76
- // TODO: make a real function
104
+ func (o * observables ) pvcMaxIndex () (max int ) {
105
+ max = - 1
106
+ for i := range o .pvcs {
107
+ tokens := strings .Split (o .pvcs [i ].Name , "-" )
108
+ index , err := strconv .Atoi (tokens [len (tokens )- 1 ])
109
+ if err != nil {
110
+ continue
111
+ }
112
+ if index > max {
113
+ max = index
114
+ }
115
+ }
116
+ return max
117
+ }
118
+
119
+ func (o * observables ) endpointMaxIndex () (max int ) {
120
+ for i := range o .endpoints {
121
+ tokens := strings .Split (o .endpoints [i ], ":" )
122
+ if len (tokens ) < 2 {
123
+ continue
124
+ }
125
+ tokens = strings .Split (tokens [len (tokens )- 2 ], "-" )
126
+ index , err := strconv .Atoi (tokens [len (tokens )- 1 ])
127
+ if err != nil {
128
+ continue
129
+ }
130
+ if index > max {
131
+ max = index
132
+ }
133
+ }
134
+ return max
135
+ }
136
+
137
+ // TODO: make a real function to determine the right number of replicas.
138
+ // Hint: if ClientURL in the member list is absent, the member has not yet
139
+ // started, but if the name field is populated, this is a member of the
140
+ // initial cluster. If the name field is empty, this member has just been
141
+ // added with etcdctl member add (or equivalent API call).
77
142
// nolint:unused
78
- func (o * observables ) desiredReplicas () int {
143
+ func (o * observables ) desiredReplicas () (max int ) {
144
+ max = - 1
79
145
if o .etcdStatuses != nil {
80
146
for i := range o .etcdStatuses {
81
147
if o .etcdStatuses [i ].memberList != nil {
82
- return len (o .etcdStatuses [i ].memberList .Members )
148
+ for j := range o .etcdStatuses [i ].memberList .Members {
149
+ tokens := strings .Split (o .etcdStatuses [i ].memberList .Members [j ].Name , "-" )
150
+ index , err := strconv .Atoi (tokens [len (tokens )- 1 ])
151
+ if err != nil {
152
+ continue
153
+ }
154
+ if index > max {
155
+ max = index
156
+ }
157
+ }
83
158
}
84
159
}
85
160
}
86
- return 0
161
+ if max > - 1 {
162
+ return max + 1
163
+ }
164
+
165
+ if epMax := o .endpointMaxIndex (); epMax > max {
166
+ max = epMax
167
+ }
168
+ if pvcMax := o .pvcMaxIndex (); pvcMax > max {
169
+ max = pvcMax
170
+ }
171
+ if max == - 1 {
172
+ return int (* o .instance .Spec .Replicas )
173
+ }
174
+ return max + 1
87
175
}
0 commit comments