github.com/weaviate/weaviate@v1.24.6/usecases/cluster/state.go (about) 1 // _ _ 2 // __ _____ __ ___ ___ __ _| |_ ___ 3 // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ 4 // \ V V / __/ (_| |\ V /| | (_| | || __/ 5 // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| 6 // 7 // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. 8 // 9 // CONTACT: hello@weaviate.io 10 // 11 12 package cluster 13 14 import ( 15 "fmt" 16 "net" 17 "strings" 18 19 "github.com/hashicorp/memberlist" 20 "github.com/pkg/errors" 21 "github.com/sirupsen/logrus" 22 ) 23 24 type State struct { 25 config Config 26 list *memberlist.Memberlist 27 delegate delegate 28 } 29 30 type Config struct { 31 Hostname string `json:"hostname" yaml:"hostname"` 32 GossipBindPort int `json:"gossipBindPort" yaml:"gossipBindPort"` 33 DataBindPort int `json:"dataBindPort" yaml:"dataBindPort"` 34 Join string `json:"join" yaml:"join"` 35 IgnoreStartupSchemaSync bool `json:"ignoreStartupSchemaSync" yaml:"ignoreStartupSchemaSync"` 36 SkipSchemaSyncRepair bool `json:"skipSchemaSyncRepair" yaml:"skipSchemaSyncRepair"` 37 AuthConfig AuthConfig `json:"auth" yaml:"auth"` 38 AdvertiseAddr string `json:"advertiseAddr" yaml:"advertiseAddr"` 39 AdvertisePort int `json:"advertisePort" yaml:"advertisePort"` 40 } 41 42 type AuthConfig struct { 43 BasicAuth BasicAuth `json:"basic" yaml:"basic"` 44 } 45 46 type BasicAuth struct { 47 Username string `json:"username" yaml:"username"` 48 Password string `json:"password" yaml:"password"` 49 } 50 51 func (ba BasicAuth) Enabled() bool { 52 return ba.Username != "" || ba.Password != "" 53 } 54 55 func Init(userConfig Config, dataPath string, logger logrus.FieldLogger) (_ *State, err error) { 56 cfg := memberlist.DefaultLANConfig() 57 cfg.LogOutput = newLogParser(logger) 58 if userConfig.Hostname != "" { 59 cfg.Name = userConfig.Hostname 60 } 61 state := State{ 62 config: userConfig, 63 delegate: delegate{ 64 Name: cfg.Name, 65 dataPath: dataPath, 66 log: logger, 67 }, 68 } 69 if err := state.delegate.init(diskSpace); err != nil { 70 logger.WithField("action", "init_state.delete_init").Error(err) 71 } 72 cfg.Delegate = &state.delegate 73 cfg.Events = events{&state.delegate} 74 if userConfig.GossipBindPort != 0 { 75 cfg.BindPort = userConfig.GossipBindPort 76 } 77 78 if userConfig.AdvertiseAddr != "" { 79 cfg.AdvertiseAddr = userConfig.AdvertiseAddr 80 } 81 82 if userConfig.AdvertisePort != 0 { 83 cfg.AdvertisePort = userConfig.AdvertisePort 84 } 85 86 if state.list, err = memberlist.Create(cfg); err != nil { 87 logger.WithField("action", "memberlist_init"). 88 WithField("hostname", userConfig.Hostname). 89 WithField("bind_port", userConfig.GossipBindPort). 90 WithError(err). 91 Error("memberlist not created") 92 return nil, errors.Wrap(err, "create member list") 93 } 94 95 var joinAddr []string 96 if userConfig.Join != "" { 97 joinAddr = strings.Split(userConfig.Join, ",") 98 } 99 100 if len(joinAddr) > 0 { 101 102 _, err := net.LookupIP(strings.Split(joinAddr[0], ":")[0]) 103 if err != nil { 104 logger.WithField("action", "cluster_attempt_join"). 105 WithField("remote_hostname", joinAddr[0]). 106 WithError(err). 107 Warn("specified hostname to join cluster cannot be resolved. This is fine" + 108 "if this is the first node of a new cluster, but problematic otherwise.") 109 } else { 110 _, err := state.list.Join(joinAddr) 111 if err != nil { 112 logger.WithField("action", "memberlist_init"). 113 WithField("remote_hostname", joinAddr). 114 WithError(err). 115 Error("memberlist join not successful") 116 return nil, errors.Wrap(err, "join cluster") 117 } 118 } 119 } 120 return &state, nil 121 } 122 123 // Hostnames for all live members, except self. Use AllHostnames to include 124 // self, prefixes the data port. 125 func (s *State) Hostnames() []string { 126 mem := s.list.Members() 127 out := make([]string, len(mem)) 128 129 i := 0 130 for _, m := range mem { 131 if m.Name == s.list.LocalNode().Name { 132 continue 133 } 134 // TODO: how can we find out the actual data port as opposed to relying on 135 // the convention that it's 1 higher than the gossip port 136 out[i] = fmt.Sprintf("%s:%d", m.Addr.String(), m.Port+1) 137 i++ 138 } 139 140 return out[:i] 141 } 142 143 // AllHostnames for live members, including self. 144 func (s *State) AllHostnames() []string { 145 mem := s.list.Members() 146 out := make([]string, len(mem)) 147 148 for i, m := range mem { 149 // TODO: how can we find out the actual data port as opposed to relying on 150 // the convention that it's 1 higher than the gossip port 151 out[i] = fmt.Sprintf("%s:%d", m.Addr.String(), m.Port+1) 152 } 153 154 return out 155 } 156 157 // All node names (not their hostnames!) for live members, including self. 158 func (s *State) AllNames() []string { 159 mem := s.list.Members() 160 out := make([]string, len(mem)) 161 162 for i, m := range mem { 163 out[i] = m.Name 164 } 165 166 return out 167 } 168 169 // Candidates returns list of nodes (names) sorted by the 170 // free amount of disk space in descending order 171 func (s *State) Candidates() []string { 172 return s.delegate.sortCandidates(s.AllNames()) 173 } 174 175 // All node names (not their hostnames!) for live members, including self. 176 func (s *State) NodeCount() int { 177 return s.list.NumMembers() 178 } 179 180 func (s *State) LocalName() string { 181 return s.list.LocalNode().Name 182 } 183 184 func (s *State) ClusterHealthScore() int { 185 return s.list.GetHealthScore() 186 } 187 188 func (s *State) NodeHostname(nodeName string) (string, bool) { 189 for _, mem := range s.list.Members() { 190 if mem.Name == nodeName { 191 // TODO: how can we find out the actual data port as opposed to relying on 192 // the convention that it's 1 higher than the gossip port 193 return fmt.Sprintf("%s:%d", mem.Addr.String(), mem.Port+1), true 194 } 195 } 196 197 return "", false 198 } 199 200 func (s *State) SchemaSyncIgnored() bool { 201 return s.config.IgnoreStartupSchemaSync 202 } 203 204 func (s *State) SkipSchemaRepair() bool { 205 return s.config.SkipSchemaSyncRepair 206 } 207 208 func (s *State) NodeInfo(node string) (NodeInfo, bool) { 209 return s.delegate.get(node) 210 }