github.com/projecteru2/core@v0.0.0-20240321043226-06bcc1c23f58/store/etcdv3/node.go (about) 1 package etcdv3 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "path/filepath" 8 "strings" 9 "sync" 10 11 "github.com/cockroachdb/errors" 12 "go.etcd.io/etcd/api/v3/mvccpb" 13 clientv3 "go.etcd.io/etcd/client/v3" 14 15 "github.com/projecteru2/core/engine" 16 enginefactory "github.com/projecteru2/core/engine/factory" 17 "github.com/projecteru2/core/engine/fake" 18 "github.com/projecteru2/core/engine/mocks/fakeengine" 19 "github.com/projecteru2/core/log" 20 "github.com/projecteru2/core/store" 21 "github.com/projecteru2/core/types" 22 "github.com/projecteru2/core/utils" 23 ) 24 25 // AddNode save it to etcd 26 // storage path in etcd is `/pod/nodes/:podname/:nodename` 27 // node->pod path in etcd is `/node/pod/:nodename` 28 // func (m *Mercury) AddNode(ctx context.Context, name, endpoint, podname, ca, cert, key string, 29 // cpu, share int, memory, storage int64, labels map[string]string, 30 // numa types.NUMA, numaMemory types.NUMAMemory, volume types.VolumeMap) (*types.Node, error) { 31 func (m *Mercury) AddNode(ctx context.Context, opts *types.AddNodeOptions) (*types.Node, error) { 32 _, err := m.GetPod(ctx, opts.Podname) 33 if err != nil { 34 return nil, err 35 } 36 37 return m.doAddNode(ctx, opts.Nodename, opts.Endpoint, opts.Podname, opts.Ca, opts.Cert, opts.Key, opts.Labels, opts.Test) 38 } 39 40 // RemoveNode delete a node 41 func (m *Mercury) RemoveNode(ctx context.Context, node *types.Node) error { 42 if node == nil { 43 return nil 44 } 45 return m.doRemoveNode(ctx, node.Podname, node.Name, node.Endpoint) 46 } 47 48 // GetNode get node by name 49 func (m *Mercury) GetNode(ctx context.Context, nodename string) (*types.Node, error) { 50 nodes, err := m.GetNodes(ctx, []string{nodename}) 51 if err != nil { 52 return nil, err 53 } 54 return nodes[0], nil 55 } 56 57 // GetNodes get nodes 58 func (m *Mercury) GetNodes(ctx context.Context, nodenames []string) ([]*types.Node, error) { 59 nodesKeys := []string{} 60 for _, nodename := range nodenames { 61 key := fmt.Sprintf(nodeInfoKey, nodename) 62 nodesKeys = append(nodesKeys, key) 63 } 64 65 kvs, err := m.GetMulti(ctx, nodesKeys) 66 if err != nil { 67 return nil, err 68 } 69 return m.doGetNodes(ctx, kvs, nil, true, nil) 70 } 71 72 // GetNodesByPod get all nodes bound to pod 73 // here we use podname instead of pod instance 74 func (m *Mercury) GetNodesByPod(ctx context.Context, nodeFilter *types.NodeFilter, opts ...store.Option) ([]*types.Node, error) { 75 var op store.Op 76 for _, opt := range opts { 77 opt(&op) 78 } 79 do := func(podname string) ([]*types.Node, error) { 80 key := fmt.Sprintf(nodePodKey, podname, "") 81 resp, err := m.Get(ctx, key, clientv3.WithPrefix()) 82 if err != nil { 83 return nil, err 84 } 85 return m.doGetNodes(ctx, resp.Kvs, nodeFilter.Labels, nodeFilter.All, &op) 86 } 87 if nodeFilter.Podname != "" { 88 return do(nodeFilter.Podname) 89 } 90 pods, err := m.GetAllPods(ctx) 91 if err != nil { 92 return nil, err 93 } 94 result := []*types.Node{} 95 for _, pod := range pods { 96 ns, err := do(pod.Name) 97 if err != nil { 98 return nil, err 99 } 100 result = append(result, ns...) 101 } 102 return result, nil 103 } 104 105 // UpdateNodes . 106 func (m *Mercury) UpdateNodes(ctx context.Context, nodes ...*types.Node) error { 107 data := map[string]string{} 108 addIfNotEmpty := func(key, value string) { 109 if value != "" { 110 data[key] = value 111 } 112 } 113 for _, node := range nodes { 114 bytes, err := json.Marshal(node) 115 if err != nil { 116 return err 117 } 118 d := string(bytes) 119 data[fmt.Sprintf(nodeInfoKey, node.Name)] = d 120 data[fmt.Sprintf(nodePodKey, node.Podname, node.Name)] = d 121 addIfNotEmpty(fmt.Sprintf(nodeCaKey, node.Name), node.Ca) 122 addIfNotEmpty(fmt.Sprintf(nodeCertKey, node.Name), node.Cert) 123 addIfNotEmpty(fmt.Sprintf(nodeKeyKey, node.Name), node.Key) 124 } 125 126 resp, err := m.BatchPut(ctx, data) 127 if err != nil { 128 return err 129 } 130 if !resp.Succeeded { 131 return types.ErrTxnConditionFailed 132 } 133 return nil 134 } 135 136 // SetNodeStatus sets status for a node, value will expire after ttl seconds 137 // ttl < 0 means to delete node status 138 // this is heartbeat of node 139 func (m *Mercury) SetNodeStatus(ctx context.Context, node *types.Node, ttl int64) error { 140 if ttl == 0 { 141 return types.ErrInvaildNodeStatusTTL 142 } 143 144 // nodenames are unique 145 statusKey := filepath.Join(nodeStatusPrefix, node.Name) 146 entityKey := fmt.Sprintf(nodeInfoKey, node.Name) 147 148 if ttl < 0 { 149 _, err := m.Delete(ctx, statusKey) 150 return err 151 } 152 153 data, err := json.Marshal(types.NodeStatus{ 154 Nodename: node.Name, 155 Podname: node.Podname, 156 Alive: true, 157 }) 158 if err != nil { 159 return err 160 } 161 162 return m.BindStatus(ctx, entityKey, statusKey, string(data), ttl) 163 } 164 165 // GetNodeStatus returns status for a node 166 func (m *Mercury) GetNodeStatus(ctx context.Context, nodename string) (*types.NodeStatus, error) { 167 key := filepath.Join(nodeStatusPrefix, nodename) 168 ev, err := m.GetOne(ctx, key) 169 if err != nil { 170 return nil, err 171 } 172 173 ns := &types.NodeStatus{} 174 if err := json.Unmarshal(ev.Value, ns); err != nil { 175 return nil, err 176 } 177 return ns, nil 178 } 179 180 // NodeStatusStream returns a stream of node status 181 // it tells you if status of a node is changed, either PUT or DELETE 182 // PUT -> Alive: true 183 // DELETE -> Alive: false 184 func (m *Mercury) NodeStatusStream(ctx context.Context) chan *types.NodeStatus { 185 ch := make(chan *types.NodeStatus) 186 logger := log.WithFunc("store.etcdv3.NodeStatusStream") 187 _ = m.pool.Invoke(func() { 188 defer func() { 189 logger.Info(ctx, "close NodeStatusStream channel") 190 close(ch) 191 }() 192 193 logger.Infof(ctx, "watch on %s", nodeStatusPrefix) 194 for resp := range m.Watch(ctx, nodeStatusPrefix, clientv3.WithPrefix()) { 195 if resp.Err() != nil { 196 if !resp.Canceled { 197 logger.Error(ctx, resp.Err(), "watch failed") 198 } 199 return 200 } 201 for _, event := range resp.Events { 202 nodename := extractNodename(string(event.Kv.Key)) 203 status := &types.NodeStatus{ 204 Nodename: nodename, 205 Alive: event.Type != clientv3.EventTypeDelete, 206 } 207 node, err := m.GetNode(ctx, nodename) 208 if err != nil { 209 status.Error = err 210 } else { 211 status.Podname = node.Podname 212 } 213 ch <- status 214 } 215 } 216 }) 217 return ch 218 } 219 220 func (m *Mercury) LoadNodeCert(ctx context.Context, node *types.Node) (err error) { 221 keyFormats := []string{nodeCaKey, nodeCertKey, nodeKeyKey} 222 data := []string{"", "", ""} 223 for i := 0; i < 3; i++ { 224 ev, err := m.GetOne(ctx, fmt.Sprintf(keyFormats[i], node.Name)) 225 if err != nil { 226 if !errors.Is(err, types.ErrInvaildCount) { 227 log.WithFunc("store.etcdv3.LoadNodeCert").Warn(ctx, err, "Get key failed") 228 return err 229 } 230 continue 231 } 232 data[i] = string(ev.Value) 233 } 234 node.Ca, node.Cert, node.Key = data[0], data[1], data[2] 235 return nil 236 } 237 238 func (m *Mercury) makeClient(ctx context.Context, node *types.Node) (client engine.API, err error) { 239 // try to get from cache without ca/cert/key 240 if client = enginefactory.GetEngineFromCache(ctx, node.Endpoint, "", "", ""); client != nil { 241 return client, nil 242 } 243 244 keyFormats := []string{nodeCaKey, nodeCertKey, nodeKeyKey} 245 data := []string{"", "", ""} 246 for i := 0; i < 3; i++ { 247 ev, err := m.GetOne(ctx, fmt.Sprintf(keyFormats[i], node.Name)) 248 if err != nil { 249 if !errors.Is(err, types.ErrInvaildCount) { 250 log.WithFunc("store.etcdv3.makeClient").Warn(ctx, err, "Get key failed") 251 return nil, err 252 } 253 continue 254 } 255 data[i] = string(ev.Value) 256 } 257 258 return enginefactory.GetEngine(ctx, m.config, node.Name, node.Endpoint, data[0], data[1], data[2]) 259 } 260 261 func (m *Mercury) doAddNode(ctx context.Context, name, endpoint, podname, ca, cert, key string, labels map[string]string, test bool) (*types.Node, error) { 262 data := map[string]string{} 263 // 如果有tls的证书需要保存就保存一下 264 if ca != "" { 265 data[fmt.Sprintf(nodeCaKey, name)] = ca 266 } 267 if cert != "" { 268 data[fmt.Sprintf(nodeCertKey, name)] = cert 269 } 270 if key != "" { 271 data[fmt.Sprintf(nodeKeyKey, name)] = key 272 } 273 274 node := &types.Node{ 275 NodeMeta: types.NodeMeta{ 276 Name: name, 277 Endpoint: endpoint, 278 Podname: podname, 279 Labels: labels, 280 }, 281 Available: true, 282 Bypass: false, 283 Test: test || strings.HasPrefix(endpoint, fakeengine.PrefixKey), 284 } 285 286 bytes, err := json.Marshal(node) 287 if err != nil { 288 return nil, err 289 } 290 291 d := string(bytes) 292 data[fmt.Sprintf(nodeInfoKey, name)] = d 293 data[fmt.Sprintf(nodePodKey, podname, name)] = d 294 295 resp, err := m.BatchCreate(ctx, data) 296 if err != nil { 297 return nil, err 298 } 299 if !resp.Succeeded { 300 return nil, types.ErrTxnConditionFailed 301 } 302 303 return node, nil 304 } 305 306 // 因为是先写etcd的证书再拿client 307 // 所以可能出现实际上node创建失败但是却写好了证书的情况 308 // 所以需要删除这些留存的证书 309 // 至于结果是不是成功就无所谓了 310 func (m *Mercury) doRemoveNode(ctx context.Context, podname, nodename, endpoint string) error { 311 keys := []string{ 312 fmt.Sprintf(nodeInfoKey, nodename), 313 fmt.Sprintf(nodePodKey, podname, nodename), 314 fmt.Sprintf(nodeCaKey, nodename), 315 fmt.Sprintf(nodeCertKey, nodename), 316 fmt.Sprintf(nodeKeyKey, nodename), 317 } 318 319 _, err := m.BatchDelete(ctx, keys) 320 log.WithFunc("store.etcdv3.doRemoveNode").Infof(ctx, "Node (%s, %s, %s) deleted", podname, nodename, endpoint) 321 return err 322 } 323 324 func (m *Mercury) doGetNodes( 325 ctx context.Context, kvs []*mvccpb.KeyValue, 326 labels map[string]string, all bool, op *store.Op, 327 ) (nodes []*types.Node, err error) { 328 allNodes := []*types.Node{} 329 for _, ev := range kvs { 330 node := &types.Node{} 331 if err := json.Unmarshal(ev.Value, node); err != nil { 332 return nil, err 333 } 334 node.Engine = &fake.EngineWithErr{DefaultErr: types.ErrNilEngine} 335 if utils.LabelsFilter(node.Labels, labels) { 336 allNodes = append(allNodes, node) 337 } 338 } 339 logger := log.WithFunc("store.etcdv3.doGetNodes") 340 341 wg := &sync.WaitGroup{} 342 wg.Add(len(allNodes)) 343 nodesCh := make(chan *types.Node, len(allNodes)) 344 345 for _, node := range allNodes { 346 node := node 347 _ = m.pool.Invoke(func() { 348 defer wg.Done() 349 if node.Test { 350 node.Available = true && !node.Bypass 351 } else if _, err := m.GetNodeStatus(ctx, node.Name); err != nil && !errors.Is(err, types.ErrInvaildCount) { 352 logger.Errorf(ctx, err, "failed to get node status of %+v", node.Name) 353 } else { 354 node.Available = err == nil 355 } 356 357 if !all && node.IsDown() { 358 return 359 } 360 361 if op == nil || (!op.WithoutEngine) { 362 // update engine 363 if client, err := m.makeClient(ctx, node); err != nil { 364 logger.Errorf(ctx, err, "failed to make client for %+v", node.Name) 365 } else { 366 node.Engine = client 367 } 368 } 369 nodesCh <- node 370 }) 371 } 372 wg.Wait() 373 close(nodesCh) 374 375 for node := range nodesCh { 376 nodes = append(nodes, node) 377 } 378 379 return nodes, nil 380 } 381 382 // extracts node name from key 383 // /nodestatus/nodename -> nodename 384 func extractNodename(s string) string { 385 ps := strings.Split(s, "/") 386 return ps[len(ps)-1] 387 }