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 }