@@ -2,8 +2,12 @@ package controller
22
33import (
44 "context"
5+ "strconv"
6+ "strings"
57 "sync"
68
9+ "github.com/aenix-io/etcd-operator/api/v1alpha1"
10+ "github.com/aenix-io/etcd-operator/pkg/set"
711 clientv3 "go.etcd.io/etcd/client/v3"
812 appsv1 "k8s.io/api/apps/v1"
913 corev1 "k8s.io/api/core/v1"
@@ -22,13 +26,14 @@ type etcdStatus struct {
2226// observables stores observations that the operator can make about
2327// states of objects in kubernetes
2428type observables struct {
29+ instance * v1alpha1.EtcdCluster
2530 statefulSet appsv1.StatefulSet
2631 stsExists bool
32+ endpoints []string
2733 endpointsFound bool
2834 etcdStatuses []etcdStatus
2935 clusterID uint64
30- _ int
31- _ []corev1.PersistentVolumeClaim
36+ pvcs []corev1.PersistentVolumeClaim
3237}
3338
3439// setClusterID populates the clusterID field based on etcdStatuses
@@ -43,15 +48,43 @@ func (o *observables) setClusterID() {
4348
4449// inSplitbrain compares clusterID field with clusterIDs in etcdStatuses.
4550// If more than one unique ID is reported, cluster is in splitbrain.
51+ // Also if members have different opinions on the list of members, this is
52+ // also a splitbrain.
4653func (o * observables ) inSplitbrain () bool {
54+ return o .clusterIDsAllEqual () && o .memberListsAllEqual ()
55+ }
56+
57+ func (o * observables ) clusterIDsAllEqual () bool {
58+ ids := set .New [uint64 ]()
4759 for i := range o .etcdStatuses {
4860 if o .etcdStatuses [i ].endpointStatus != nil {
49- if o .clusterID != o .etcdStatuses [i ].endpointStatus .Header .ClusterId {
50- return true
61+ ids .Add (o .etcdStatuses [i ].endpointStatus .Header .ClusterId )
62+ }
63+ }
64+ return len (ids ) <= 1
65+ }
66+
67+ func (o * observables ) memberListsAllEqual () bool {
68+ type m struct {
69+ Name string
70+ ID uint64
71+ }
72+ memberLists := make ([]set.Set [m ], 0 , len (o .etcdStatuses ))
73+ for i := range o .etcdStatuses {
74+ if o .etcdStatuses [i ].memberList != nil {
75+ memberSet := set .New [m ]()
76+ for _ , member := range o .etcdStatuses [i ].memberList .Members {
77+ memberSet .Add (m {member .Name , member .ID })
5178 }
79+ memberLists = append (memberLists , memberSet )
80+ }
81+ }
82+ for i := range memberLists {
83+ if ! memberLists [0 ].Equals (memberLists [i ]) {
84+ return false
5285 }
5386 }
54- return false
87+ return true
5588}
5689
5790// fill takes a single-endpoint client and populates the fields of etcdStatus
@@ -67,14 +100,74 @@ func (s *etcdStatus) fill(ctx context.Context, c *clientv3.Client) {
67100 wg .Wait ()
68101}
69102
70- // TODO: make a real function
71- func (o * observables ) _ () int {
103+ func (o * observables ) pvcMaxIndex () (max int ) {
104+ max = - 1
105+ for i := range o .pvcs {
106+ tokens := strings .Split (o .pvcs [i ].Name , "-" )
107+ index , err := strconv .Atoi (tokens [len (tokens )- 1 ])
108+ if err != nil {
109+ continue
110+ }
111+ if index > max {
112+ max = index
113+ }
114+ }
115+ return max
116+ }
117+
118+ func (o * observables ) endpointMaxIndex () (max int ) {
119+ for i := range o .endpoints {
120+ tokens := strings .Split (o .endpoints [i ], ":" )
121+ if len (tokens ) < 2 {
122+ continue
123+ }
124+ tokens = strings .Split (tokens [len (tokens )- 2 ], "-" )
125+ index , err := strconv .Atoi (tokens [len (tokens )- 1 ])
126+ if err != nil {
127+ continue
128+ }
129+ if index > max {
130+ max = index
131+ }
132+ }
133+ return max
134+ }
135+
136+ // TODO: make a real function to determine the right number of replicas.
137+ // Hint: if ClientURL in the member list is absent, the member has not yet
138+ // started, but if the name field is populated, this is a member of the
139+ // initial cluster. If the name field is empty, this member has just been
140+ // added with etcdctl member add (or equivalent API call).
141+ func (o * observables ) desiredReplicas () (max int ) {
142+ max = - 1
72143 if o .etcdStatuses != nil {
73144 for i := range o .etcdStatuses {
74145 if o .etcdStatuses [i ].memberList != nil {
75- return len (o .etcdStatuses [i ].memberList .Members )
146+ for j := range o .etcdStatuses [i ].memberList .Members {
147+ tokens := strings .Split (o .etcdStatuses [i ].memberList .Members [j ].Name , "-" )
148+ index , err := strconv .Atoi (tokens [len (tokens )- 1 ])
149+ if err != nil {
150+ continue
151+ }
152+ if index > max {
153+ max = index
154+ }
155+ }
76156 }
77157 }
78158 }
79- return 0
159+ if max > - 1 {
160+ return max + 1
161+ }
162+
163+ if epMax := o .endpointMaxIndex (); epMax > max {
164+ max = epMax
165+ }
166+ if pvcMax := o .pvcMaxIndex (); pvcMax > max {
167+ max = pvcMax
168+ }
169+ if max == - 1 {
170+ return int (* o .instance .Spec .Replicas )
171+ }
172+ return max + 1
80173}
0 commit comments