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  }