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 }