github.com/projecteru2/core@v0.0.0-20240321043226-06bcc1c23f58/store/redis/workload.go (about)

     1  package redis
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"path/filepath"
     8  
     9  	"github.com/projecteru2/core/log"
    10  	"github.com/projecteru2/core/types"
    11  	"github.com/projecteru2/core/utils"
    12  )
    13  
    14  // AddWorkload add a workload
    15  // mainly record its relationship on pod and node
    16  // actually if we already know its node, we will know its pod
    17  // but we still store it
    18  // storage path in etcd is `/workload/:workloadid`
    19  func (r *Rediaron) AddWorkload(ctx context.Context, workload *types.Workload, processing *types.Processing) error {
    20  	return r.doOpsWorkload(ctx, workload, processing, true)
    21  }
    22  
    23  // UpdateWorkload update a workload
    24  func (r *Rediaron) UpdateWorkload(ctx context.Context, workload *types.Workload) error {
    25  	return r.doOpsWorkload(ctx, workload, nil, false)
    26  }
    27  
    28  // RemoveWorkload remove a workload
    29  // workload ID must be in full length
    30  func (r *Rediaron) RemoveWorkload(ctx context.Context, workload *types.Workload) error {
    31  	return r.cleanWorkloadData(ctx, workload)
    32  }
    33  
    34  // GetWorkload get a workload
    35  // workload if must be in full length, or we can't find it in etcd
    36  // storage path in etcd is `/workload/:workloadid`
    37  func (r *Rediaron) GetWorkload(ctx context.Context, ID string) (*types.Workload, error) {
    38  	workloads, err := r.GetWorkloads(ctx, []string{ID})
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  	return workloads[0], nil
    43  }
    44  
    45  // GetWorkloads get many workloads
    46  func (r *Rediaron) GetWorkloads(ctx context.Context, IDs []string) (workloads []*types.Workload, err error) {
    47  	keys := []string{}
    48  	for _, ID := range IDs {
    49  		keys = append(keys, fmt.Sprintf(workloadInfoKey, ID))
    50  	}
    51  
    52  	return r.doGetWorkloads(ctx, keys)
    53  }
    54  
    55  // GetWorkloadStatus get workload status
    56  func (r *Rediaron) GetWorkloadStatus(ctx context.Context, ID string) (*types.StatusMeta, error) {
    57  	workload, err := r.GetWorkload(ctx, ID)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  	return workload.StatusMeta, nil
    62  }
    63  
    64  // SetWorkloadStatus set workload status
    65  func (r *Rediaron) SetWorkloadStatus(ctx context.Context, status *types.StatusMeta, ttl int64) error {
    66  	if status.Appname == "" || status.Entrypoint == "" || status.Nodename == "" {
    67  		return types.ErrInvaildWorkloadStatus
    68  	}
    69  
    70  	data, err := json.Marshal(status)
    71  	if err != nil {
    72  		return err
    73  	}
    74  	statusVal := string(data)
    75  	statusKey := filepath.Join(workloadStatusPrefix, status.Appname, status.Entrypoint, status.Nodename, status.ID)
    76  	workloadKey := fmt.Sprintf(workloadInfoKey, status.ID)
    77  	return r.BindStatus(ctx, workloadKey, statusKey, statusVal, ttl)
    78  }
    79  
    80  // ListWorkloads list workloads
    81  func (r *Rediaron) ListWorkloads(ctx context.Context, appname, entrypoint, nodename string, limit int64, labels map[string]string) ([]*types.Workload, error) {
    82  	if appname == "" {
    83  		entrypoint = ""
    84  	}
    85  	if entrypoint == "" {
    86  		nodename = ""
    87  	}
    88  	// 这里显式加个 / 来保证 prefix 是唯一的
    89  	key := filepath.Join(workloadDeployPrefix, appname, entrypoint, nodename) + "/*"
    90  	data, err := r.getByKeyPattern(ctx, key, limit)
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	workloads := []*types.Workload{}
    96  	for _, v := range data {
    97  		workload := &types.Workload{}
    98  		if err := json.Unmarshal([]byte(v), workload); err != nil {
    99  			return nil, err
   100  		}
   101  		if utils.LabelsFilter(workload.Labels, labels) {
   102  			workloads = append(workloads, workload)
   103  		}
   104  	}
   105  
   106  	return r.bindWorkloadsAdditions(ctx, workloads)
   107  }
   108  
   109  // ListNodeWorkloads list workloads belong to one node
   110  func (r *Rediaron) ListNodeWorkloads(ctx context.Context, nodename string, labels map[string]string) ([]*types.Workload, error) {
   111  	key := fmt.Sprintf(nodeWorkloadsKey, nodename, "*")
   112  	data, err := r.getByKeyPattern(ctx, key, 0)
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  
   117  	workloads := []*types.Workload{}
   118  	for _, v := range data {
   119  		workload := &types.Workload{}
   120  		if err := json.Unmarshal([]byte(v), workload); err != nil {
   121  			return nil, err
   122  		}
   123  		if utils.LabelsFilter(workload.Labels, labels) {
   124  			workloads = append(workloads, workload)
   125  		}
   126  	}
   127  
   128  	return r.bindWorkloadsAdditions(ctx, workloads)
   129  }
   130  
   131  // WorkloadStatusStream watch deployed status
   132  func (r *Rediaron) WorkloadStatusStream(ctx context.Context, appname, entrypoint, nodename string, labels map[string]string) chan *types.WorkloadStatus {
   133  	if appname == "" {
   134  		entrypoint = ""
   135  	}
   136  	if entrypoint == "" {
   137  		nodename = ""
   138  	}
   139  	// 显式加个 / 保证 prefix 唯一
   140  	statusKey := filepath.Join(workloadStatusPrefix, appname, entrypoint, nodename) + "/*"
   141  	ch := make(chan *types.WorkloadStatus)
   142  	logger := log.WithFunc("store.redis.WorkloadStatusStream")
   143  	_ = r.pool.Invoke(func() {
   144  		defer func() {
   145  			logger.Info(ctx, "close WorkloadStatus channel")
   146  			close(ch)
   147  		}()
   148  
   149  		logger.Infof(ctx, "watch on %s", statusKey)
   150  		for message := range r.KNotify(ctx, statusKey) {
   151  			_, _, _, ID := parseStatusKey(message.Key)
   152  			msg := &types.WorkloadStatus{
   153  				ID:     ID,
   154  				Delete: message.Action == actionDel || message.Action == actionExpired,
   155  			}
   156  			workload, err := r.GetWorkload(ctx, ID)
   157  			switch {
   158  			case err != nil:
   159  				msg.Error = err
   160  			case utils.LabelsFilter(workload.Labels, labels):
   161  				logger.Debugf(ctx, "workload %s status changed", workload.ID)
   162  				msg.Workload = workload
   163  			default:
   164  				continue
   165  			}
   166  			ch <- msg
   167  		}
   168  	})
   169  	return ch
   170  }
   171  
   172  func (r *Rediaron) cleanWorkloadData(ctx context.Context, workload *types.Workload) error {
   173  	appname, entrypoint, _, err := utils.ParseWorkloadName(workload.Name)
   174  	if err != nil {
   175  		return err
   176  	}
   177  
   178  	keys := []string{
   179  		filepath.Join(workloadStatusPrefix, appname, entrypoint, workload.Nodename, workload.ID), // workload deploy status
   180  		filepath.Join(workloadDeployPrefix, appname, entrypoint, workload.Nodename, workload.ID), // workload deploy status
   181  		fmt.Sprintf(workloadInfoKey, workload.ID),                                                // workload info
   182  		fmt.Sprintf(nodeWorkloadsKey, workload.Nodename, workload.ID),                            // node workloads
   183  	}
   184  	return r.BatchDelete(ctx, keys)
   185  }
   186  
   187  func (r *Rediaron) doGetWorkloads(ctx context.Context, keys []string) ([]*types.Workload, error) {
   188  	data, err := r.GetMulti(ctx, keys)
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  
   193  	workloads := []*types.Workload{}
   194  	for k, v := range data {
   195  		workload := &types.Workload{}
   196  		if err = json.Unmarshal([]byte(v), workload); err != nil {
   197  			log.WithFunc("store.redis.doGetWorkloads").Errorf(ctx, err, "failed to unmarshal %+v", k)
   198  			return nil, err
   199  		}
   200  		workloads = append(workloads, workload)
   201  	}
   202  
   203  	return r.bindWorkloadsAdditions(ctx, workloads)
   204  }
   205  
   206  func (r *Rediaron) bindWorkloadsAdditions(ctx context.Context, workloads []*types.Workload) ([]*types.Workload, error) {
   207  	nodes := map[string]*types.Node{}
   208  	nodenames := []string{}
   209  	nodenameCache := map[string]struct{}{}
   210  	statusKeys := map[string]string{}
   211  	logger := log.WithFunc("store.redis.bindWorkloadsAdditions")
   212  	for _, workload := range workloads {
   213  		appname, entrypoint, _, err := utils.ParseWorkloadName(workload.Name)
   214  		if err != nil {
   215  			return nil, err
   216  		}
   217  		statusKeys[workload.ID] = filepath.Join(workloadStatusPrefix, appname, entrypoint, workload.Nodename, workload.ID)
   218  		if _, ok := nodenameCache[workload.Nodename]; !ok {
   219  			nodenameCache[workload.Nodename] = struct{}{}
   220  			nodenames = append(nodenames, workload.Nodename)
   221  		}
   222  	}
   223  	ns, err := r.GetNodes(ctx, nodenames)
   224  	if err != nil {
   225  		return nil, err
   226  	}
   227  	for _, node := range ns {
   228  		nodes[node.Name] = node
   229  	}
   230  
   231  	for index, workload := range workloads {
   232  		if _, ok := nodes[workload.Nodename]; !ok {
   233  			return nil, types.ErrInvaildWorkloadMeta
   234  		}
   235  		workloads[index].Engine = nodes[workload.Nodename].Engine
   236  		if _, ok := statusKeys[workload.ID]; !ok {
   237  			continue
   238  		}
   239  		v, err := r.GetOne(ctx, statusKeys[workload.ID])
   240  		if err != nil {
   241  			continue
   242  		}
   243  		status := &types.StatusMeta{}
   244  		if err := json.Unmarshal([]byte(v), &status); err != nil {
   245  			logger.Warnf(ctx, "unmarshal %s status data, raw %s", workload.ID, v)
   246  			logger.Error(ctx, err, "unmarshal status failed")
   247  			continue
   248  		}
   249  		workloads[index].StatusMeta = status
   250  	}
   251  	return workloads, nil
   252  }
   253  
   254  func (r *Rediaron) doOpsWorkload(ctx context.Context, workload *types.Workload, processing *types.Processing, create bool) error {
   255  	var err error
   256  	appname, entrypoint, _, err := utils.ParseWorkloadName(workload.Name)
   257  	if err != nil {
   258  		return err
   259  	}
   260  
   261  	// now everything is ok
   262  	// we use full length ID instead
   263  	bytes, err := json.Marshal(workload)
   264  	if err != nil {
   265  		return err
   266  	}
   267  	workloadData := string(bytes)
   268  
   269  	data := map[string]string{
   270  		fmt.Sprintf(workloadInfoKey, workload.ID):                                                workloadData,
   271  		fmt.Sprintf(nodeWorkloadsKey, workload.Nodename, workload.ID):                            workloadData,
   272  		filepath.Join(workloadDeployPrefix, appname, entrypoint, workload.Nodename, workload.ID): workloadData,
   273  	}
   274  
   275  	if create {
   276  		if processing != nil {
   277  			err = r.BatchCreateAndDecr(ctx, data, r.getProcessingKey(processing))
   278  		} else {
   279  			err = r.BatchCreate(ctx, data)
   280  		}
   281  	} else {
   282  		err = r.BatchUpdate(ctx, data)
   283  	}
   284  	return err
   285  }