github.com/projecteru2/core@v0.0.0-20240321043226-06bcc1c23f58/cluster/calcium/lambda.go (about)

     1  package calcium
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"strconv"
     9  	"sync"
    10  
    11  	enginetypes "github.com/projecteru2/core/engine/types"
    12  	"github.com/projecteru2/core/log"
    13  	"github.com/projecteru2/core/strategy"
    14  	"github.com/projecteru2/core/types"
    15  	"github.com/projecteru2/core/utils"
    16  )
    17  
    18  const (
    19  	exitDataPrefix = "[exitcode] "
    20  )
    21  
    22  // RunAndWait implement lambda
    23  func (c *Calcium) RunAndWait(ctx context.Context, opts *types.DeployOptions, inCh <-chan []byte) ([]string, <-chan *types.AttachWorkloadMessage, error) {
    24  	workloadIDs := []string{}
    25  
    26  	logger := log.WithFunc("calcium.RunAndWait").WithField("opts", opts)
    27  	if err := opts.Validate(); err != nil {
    28  		logger.Error(ctx, err)
    29  		return workloadIDs, nil, err
    30  	}
    31  	opts.Lambda = true
    32  	// count = 1 && OpenStdin
    33  	if opts.OpenStdin && (opts.Count != 1 || opts.DeployStrategy != strategy.Auto) {
    34  		logger.Errorf(ctx, types.ErrRunAndWaitCountOneWithStdin, "Count %d method %s", opts.Count, opts.DeployStrategy)
    35  		return workloadIDs, nil, types.ErrRunAndWaitCountOneWithStdin
    36  	}
    37  
    38  	createChan, err := c.CreateWorkload(ctx, opts)
    39  	if err != nil {
    40  		logger.Error(ctx, err, "Create workload error")
    41  		return workloadIDs, nil, err
    42  	}
    43  
    44  	var (
    45  		runMsgCh = make(chan *types.AttachWorkloadMessage)
    46  		wg       = &sync.WaitGroup{}
    47  	)
    48  
    49  	lambda := func(message *types.CreateWorkloadMessage) (attachMessage *types.AttachWorkloadMessage) {
    50  		// should Done this waitgroup anyway
    51  		defer wg.Done()
    52  
    53  		defer func() {
    54  			runMsgCh <- attachMessage
    55  		}()
    56  
    57  		// if workload is empty, which means error occurred when created workload
    58  		// we don't need to remove this non-existing workload
    59  		// so just send the error message and return
    60  		if message.Error != nil || message.WorkloadID == "" {
    61  			logger.Error(ctx, message.Error, "Create workload failed")
    62  			return &types.AttachWorkloadMessage{
    63  				WorkloadID:    "",
    64  				Data:          []byte(fmt.Sprintf("Create workload failed %+v", message.Error)),
    65  				StdStreamType: types.EruError,
    66  			}
    67  		}
    68  
    69  		commit, err := c.wal.Log(eventCreateLambda, message.WorkloadID)
    70  		if err != nil {
    71  			logger.Error(ctx, err)
    72  			return &types.AttachWorkloadMessage{
    73  				WorkloadID:    message.WorkloadID,
    74  				Data:          []byte(fmt.Sprintf("Create wal failed: %s, %+v", message.WorkloadID, err)),
    75  				StdStreamType: types.EruError,
    76  			}
    77  		}
    78  		defer func() {
    79  			if err := commit(); err != nil {
    80  				logger.Errorf(ctx, err, "Commit WAL %s failed: %s", eventCreateLambda, message.WorkloadID)
    81  			}
    82  		}()
    83  
    84  		// the workload should be removed if it exists
    85  		// no matter the workload exits successfully or not
    86  		defer func() {
    87  			ctx, cancel := context.WithCancel(utils.NewInheritCtx(ctx))
    88  			defer cancel()
    89  			if err := c.doRemoveWorkloadSync(ctx, []string{message.WorkloadID}); err != nil {
    90  				logger.Error(ctx, err, "Remove lambda workload failed")
    91  			} else {
    92  				logger.Infof(ctx, "Workload %s finished and removed", utils.ShortID(message.WorkloadID))
    93  			}
    94  		}()
    95  
    96  		// if we can't get the workload but message has workload field
    97  		// this is weird, we return the error directly and try to delete data
    98  		workload, err := c.GetWorkload(ctx, message.WorkloadID)
    99  		if err != nil {
   100  			logger.Error(ctx, err, "Get workload failed")
   101  			return &types.AttachWorkloadMessage{
   102  				WorkloadID:    message.WorkloadID,
   103  				Data:          []byte(fmt.Sprintf("Get workload %s failed %+v", message.WorkloadID, err)),
   104  				StdStreamType: types.EruError,
   105  			}
   106  		}
   107  
   108  		// for other cases, we have the workload and it works fine
   109  		// then we need to forward log, and finally delete the workload
   110  		// of course all the error messages will be sent back
   111  		var stdout, stderr io.ReadCloser
   112  		if stdout, stderr, err = workload.Engine.VirtualizationLogs(ctx, &enginetypes.VirtualizationLogStreamOptions{
   113  			ID:     message.WorkloadID,
   114  			Follow: true,
   115  			Stdout: true,
   116  			Stderr: true,
   117  		}); err != nil {
   118  			logger.Errorf(ctx, err, "Can't fetch log of workload %s", message.WorkloadID)
   119  			return &types.AttachWorkloadMessage{
   120  				WorkloadID:    message.WorkloadID,
   121  				Data:          []byte(fmt.Sprintf("Fetch log for workload %s failed %+v", message.WorkloadID, err)),
   122  				StdStreamType: types.EruError,
   123  			}
   124  		}
   125  
   126  		splitFunc, split := bufio.ScanLines, byte('\n')
   127  
   128  		// use attach if use stdin
   129  		if opts.OpenStdin {
   130  			var inStream io.WriteCloser
   131  			stdout, stderr, inStream, err = workload.Engine.VirtualizationAttach(ctx, message.WorkloadID, true, true)
   132  			if err != nil {
   133  				logger.Errorf(ctx, err, "Can't attach workload %s", message.WorkloadID)
   134  				return &types.AttachWorkloadMessage{
   135  					WorkloadID:    message.WorkloadID,
   136  					Data:          []byte(fmt.Sprintf("Attach to workload %s failed %+v", message.WorkloadID, err)),
   137  					StdStreamType: types.EruError,
   138  				}
   139  			}
   140  
   141  			c.processVirtualizationInStream(ctx, inStream, inCh, func(height, width uint) error {
   142  				return workload.Engine.VirtualizationResize(ctx, message.WorkloadID, height, width)
   143  			})
   144  
   145  			splitFunc, split = bufio.ScanBytes, byte(0)
   146  		}
   147  
   148  		for m := range c.processStdStream(ctx, stdout, stderr, splitFunc, split) {
   149  			runMsgCh <- &types.AttachWorkloadMessage{
   150  				WorkloadID:    message.WorkloadID,
   151  				Data:          m.Data,
   152  				StdStreamType: m.StdStreamType,
   153  			}
   154  		}
   155  
   156  		// wait and forward exitcode
   157  		r, err := workload.Engine.VirtualizationWait(ctx, message.WorkloadID, "")
   158  		if err != nil {
   159  			logger.Errorf(ctx, err, "%s wait failed", utils.ShortID(message.WorkloadID))
   160  			return &types.AttachWorkloadMessage{
   161  				WorkloadID:    message.WorkloadID,
   162  				Data:          []byte(fmt.Sprintf("Wait workload %s failed %+v", message.WorkloadID, err)),
   163  				StdStreamType: types.EruError,
   164  			}
   165  		}
   166  
   167  		if r.Code != 0 {
   168  			logger.Errorf(ctx, err, "%s run failed %s", utils.ShortID(message.WorkloadID), r.Message)
   169  		}
   170  
   171  		exitData := []byte(exitDataPrefix + strconv.Itoa(int(r.Code)))
   172  		return &types.AttachWorkloadMessage{
   173  			WorkloadID:    message.WorkloadID,
   174  			Data:          exitData,
   175  			StdStreamType: types.Stdout,
   176  		}
   177  	}
   178  
   179  	for message := range createChan {
   180  		// iterate over messages to store workload IDs
   181  		workloadIDs = append(workloadIDs, message.WorkloadID)
   182  		wg.Add(1)
   183  		_ = c.pool.Invoke(func(msg *types.CreateWorkloadMessage) func() {
   184  			return func() {
   185  				lambda(msg)
   186  			}
   187  		}(message))
   188  	}
   189  
   190  	_ = c.pool.Invoke(func() {
   191  		defer close(runMsgCh)
   192  		wg.Wait()
   193  
   194  		logger.Infof(ctx, "%+v", "Finish run and wait for workloads")
   195  	})
   196  
   197  	return workloadIDs, runMsgCh, nil
   198  }