github.com/machinefi/w3bstream@v1.6.5-rc9.0.20240426031326-b8c7c4876e72/pkg/modules/deploy/deploy.go (about) 1 package deploy 2 3 import ( 4 "context" 5 "fmt" 6 "strconv" 7 "strings" 8 9 "github.com/pkg/errors" 10 11 confid "github.com/machinefi/w3bstream/pkg/depends/conf/id" 12 "github.com/machinefi/w3bstream/pkg/depends/conf/logger" 13 "github.com/machinefi/w3bstream/pkg/depends/kit/logr" 14 "github.com/machinefi/w3bstream/pkg/depends/kit/sqlx" 15 "github.com/machinefi/w3bstream/pkg/depends/kit/sqlx/builder" 16 "github.com/machinefi/w3bstream/pkg/depends/x/contextx" 17 "github.com/machinefi/w3bstream/pkg/enums" 18 "github.com/machinefi/w3bstream/pkg/errors/status" 19 "github.com/machinefi/w3bstream/pkg/models" 20 "github.com/machinefi/w3bstream/pkg/modules/config" 21 "github.com/machinefi/w3bstream/pkg/modules/resource" 22 "github.com/machinefi/w3bstream/pkg/modules/robot_notifier" 23 "github.com/machinefi/w3bstream/pkg/modules/robot_notifier/lark" 24 "github.com/machinefi/w3bstream/pkg/modules/vm" 25 "github.com/machinefi/w3bstream/pkg/modules/wasmlog" 26 "github.com/machinefi/w3bstream/pkg/types" 27 ) 28 29 func Init(ctx context.Context) error { 30 ctx, l := logger.NewSpanContext(ctx, "deploy.Init") 31 defer l.End() 32 33 var ( 34 d = types.MustMgrDBExecutorFromContext(ctx) 35 36 ins = &models.Instance{} 37 app *models.Applet 38 res *models.Resource 39 40 code []byte 41 42 fails = []string{"\ninstances failed to deploy:"} 43 succs = []string{"\ndeployed instances:"} 44 ) 45 46 defer func() { 47 message := "" 48 if len(fails) > 1 { 49 message += strings.Join(fails, "\n") 50 } 51 if len(succs) > 1 { 52 message += strings.Join(succs, "\n") 53 } 54 body, err := lark.Build(ctx, "Instances Deploying", "INFO", message) 55 if err != nil { 56 return 57 } 58 _ = robot_notifier.Push(ctx, body) 59 }() 60 61 // make wasm database lazy init to reduce database connections 62 wasmdbconf := *(types.MustWasmDBConfigFromContext(ctx)) 63 wasmdbconf.LazyInit = true 64 ctx = types.WithWasmDBConfig(ctx, &wasmdbconf) 65 66 list, err := ins.List(d, nil) 67 if err != nil { 68 l.Error(err) 69 return err 70 } 71 l = l.WithValues("total", len(list)) 72 73 for i := range list { 74 ins = &list[i] 75 l := l.WithValues("ins", ins.InstanceID, "index", i) 76 77 app = &models.Applet{RelApplet: models.RelApplet{AppletID: ins.AppletID}} 78 err = app.FetchByAppletID(d) 79 if err != nil { 80 err = errors.Errorf("%v: failed to get applet %v %v", ins.InstanceID, ins.AppletID, err) 81 fails = append(fails, err.Error()) 82 l.Warn(err) 83 continue 84 } 85 86 res, code, err = resource.GetContentBySFID(ctx, app.ResourceID) 87 if err != nil { 88 err = errors.Errorf("%v: failed to get resource %v %v", ins.InstanceID, app.ResourceID, err) 89 fails = append(fails, err.Error()) 90 l.Warn(err) 91 continue 92 } 93 94 ctx = contextx.WithContextCompose( 95 types.WithResourceContext(res), 96 types.WithAppletContext(app), 97 )(ctx) 98 99 state := ins.State 100 l = l.WithValues("state_db", ins.State) 101 102 _ins, err := UpsertByCode(ctx, nil, code, state, ins.InstanceID) 103 if err != nil { 104 err = errors.Errorf("%v: failed to deploy %v", ins.InstanceID, err) 105 fails = append(fails, err.Error()) 106 l.Warn(err) 107 continue 108 } 109 110 if _ins.State != state { 111 l.WithValues("state_mem", ins.State).Warn(errors.New("create vm failed")) 112 err = errors.Errorf("%v: instance not started", ins.InstanceID) 113 fails = append(fails, err.Error()) 114 continue 115 } 116 succs = append(succs, ins.InstanceID.String()) 117 l.Info("started") 118 } 119 return nil 120 } 121 122 func GetBySFID(ctx context.Context, id types.SFID) (*models.Instance, error) { 123 d := types.MustMgrDBExecutorFromContext(ctx) 124 m := &models.Instance{RelInstance: models.RelInstance{InstanceID: id}} 125 126 if err := m.FetchByInstanceID(d); err != nil { 127 if sqlx.DBErr(err).IsNotFound() { 128 return nil, status.InstanceNotFound 129 } 130 return nil, status.DatabaseError.StatusErr().WithDesc(err.Error()) 131 } 132 m.State, _ = vm.GetInstanceState(m.InstanceID) 133 return m, nil 134 } 135 136 func GetByAppletSFID(ctx context.Context, id types.SFID) (*models.Instance, error) { 137 d := types.MustMgrDBExecutorFromContext(ctx) 138 m := &models.Instance{RelApplet: models.RelApplet{AppletID: id}} 139 140 if err := m.FetchByAppletID(d); err != nil { 141 if sqlx.DBErr(err).IsNotFound() { 142 return nil, status.InstanceNotFound 143 } 144 return nil, status.DatabaseError.StatusErr().WithDesc(err.Error()) 145 } 146 m.State, _ = vm.GetInstanceState(m.InstanceID) 147 return m, nil 148 } 149 150 func List(ctx context.Context, r *ListReq) (ret *ListRsp, err error) { 151 d := types.MustMgrDBExecutorFromContext(ctx) 152 m := &models.Instance{} 153 154 ret = &ListRsp{} 155 156 adds := builder.Additions{ 157 builder.Where(r.Condition()), 158 r.Addition(), 159 builder.Comment("Instance.ListWithProjectPermission"), 160 } 161 if r.ProjectID != 0 { 162 app := &models.Applet{} 163 adds = append(adds, 164 builder.LeftJoin(d.T(app)).On(m.ColAppletID().Eq(app.ColAppletID())), 165 ) 166 } 167 168 err = d.QueryAndScan(builder.Select(nil).From(d.T(m), adds...), &ret.Data) 169 if err != nil { 170 return nil, status.DatabaseError.StatusErr().WithDesc(err.Error()) 171 } 172 err = d.QueryAndScan(builder.Select(builder.Count()).From(d.T(m), adds...), &ret.Total) 173 if err != nil { 174 return nil, status.DatabaseError.StatusErr().WithDesc(err.Error()) 175 } 176 177 return ret, nil 178 } 179 180 func ListByCond(ctx context.Context, r *CondArgs) (data []models.Instance, err error) { 181 d := types.MustMgrDBExecutorFromContext(ctx) 182 m := &models.Instance{} 183 184 adds := builder.Additions{ 185 builder.Where(r.Condition()), 186 builder.Comment("Instance.ListWithProjectPermission"), 187 } 188 189 if r.ProjectID != 0 { 190 app := &models.Applet{} 191 adds = append(adds, 192 builder.LeftJoin(d.T(app)).On(m.ColAppletID().Eq(app.ColAppletID())), 193 ) 194 } 195 196 err = d.QueryAndScan(builder.Select(nil).From(d.T(m), adds...), &data) 197 if err != nil { 198 return nil, status.DatabaseError.StatusErr().WithDesc(err.Error()) 199 } 200 return data, nil 201 } 202 203 func RemoveBySFID(ctx context.Context, id types.SFID) error { 204 d := types.MustMgrDBExecutorFromContext(ctx) 205 m := &models.Instance{RelInstance: models.RelInstance{InstanceID: id}} 206 207 return sqlx.NewTasks(d).With( 208 func(d sqlx.DBExecutor) error { 209 if err := m.DeleteByInstanceID(d); err != nil { 210 return status.DatabaseError.StatusErr(). 211 WithDesc(errors.Wrap(err, id.String()).Error()) 212 } 213 return nil 214 }, 215 func(d sqlx.DBExecutor) error { 216 ctx := types.WithMgrDBExecutor(ctx, d) 217 return config.Remove(ctx, &config.CondArgs{RelIDs: []types.SFID{id}}) 218 }, 219 func(d sqlx.DBExecutor) error { 220 if err := vm.DelInstance(ctx, m.InstanceID); err != nil { 221 // Warn 222 } 223 return nil 224 }, 225 func(d sqlx.DBExecutor) error { 226 ctx := types.WithMgrDBExecutor(ctx, d) 227 return wasmlog.Remove(ctx, &wasmlog.CondArgs{InstanceID: m.InstanceID}) 228 }, 229 ).Do() 230 } 231 232 func RemoveByAppletSFID(ctx context.Context, id types.SFID) (err error) { 233 var ( 234 d = types.MustMgrDBExecutorFromContext(ctx) 235 m *models.Instance 236 ) 237 238 return sqlx.NewTasks(d).With( 239 func(d sqlx.DBExecutor) error { 240 ctx := types.WithMgrDBExecutor(ctx, d) 241 m, err = GetByAppletSFID(ctx, id) 242 return err 243 }, 244 func(d sqlx.DBExecutor) error { 245 ctx := types.WithMgrDBExecutor(ctx, d) 246 return RemoveBySFID(ctx, m.InstanceID) 247 }, 248 ).Do() 249 } 250 251 func Remove(ctx context.Context, r *CondArgs) error { 252 var ( 253 lst []models.Instance 254 err error 255 ) 256 257 return sqlx.NewTasks(types.MustMgrDBExecutorFromContext(ctx)).With( 258 func(db sqlx.DBExecutor) error { 259 ctx := types.WithMgrDBExecutor(ctx, db) 260 lst, err = ListByCond(ctx, r) 261 return err 262 }, 263 func(db sqlx.DBExecutor) error { 264 ctx := types.WithMgrDBExecutor(ctx, db) 265 for i := range lst { 266 err = RemoveBySFID(ctx, lst[i].InstanceID) 267 if err != nil { 268 return err 269 } 270 } 271 return nil 272 }, 273 ).Do() 274 } 275 276 // UpsertByCode upsert instance and its config, and deploy wasm if needed 277 func UpsertByCode(ctx context.Context, r *CreateReq, code []byte, state enums.InstanceState, old ...types.SFID) (*models.Instance, error) { 278 ctx, l := logr.Start(ctx, "deploy.UpsertByCode") 279 defer l.End() 280 281 var ( 282 d = types.MustMgrDBExecutorFromContext(ctx) 283 idg = confid.MustSFIDGeneratorFromContext(ctx) 284 forUpdate = false 285 ) 286 287 app := types.MustAppletFromContext(ctx) 288 ins := &models.Instance{} 289 290 if state != enums.INSTANCE_STATE__STARTED && state != enums.INSTANCE_STATE__STOPPED { 291 return nil, status.InvalidVMState.StatusErr().WithDesc(state.String()) 292 } 293 294 err := sqlx.NewTasks(d).With( 295 func(d sqlx.DBExecutor) error { 296 ins.AppletID = app.AppletID 297 if err := ins.FetchByAppletID(d); err != nil { 298 if sqlx.DBErr(err).IsNotFound() { 299 forUpdate = false 300 ins.InstanceID = idg.MustGenSFID() 301 ins.State = state 302 return nil 303 } else { 304 return status.DatabaseError.StatusErr().WithDesc(err.Error()) 305 } 306 } 307 if len(old) > 0 && old[0] != ins.InstanceID { 308 return status.InvalidAppletContext.StatusErr().WithDesc( 309 fmt.Sprintf("database: %v arg: %v", ins.InstanceID, old[0]), 310 ) 311 } 312 ins.State = state 313 forUpdate = true 314 return nil 315 }, 316 func(d sqlx.DBExecutor) error { 317 var err error 318 if forUpdate { 319 err = ins.UpdateByInstanceID(d) 320 } else { 321 err = ins.Create(d) 322 } 323 if err != nil { 324 if sqlx.DBErr(err).IsConflict() { 325 return status.MultiInstanceDeployed.StatusErr(). 326 WithDesc(app.AppletID.String()) 327 } 328 return status.DatabaseError.StatusErr().WithDesc(err.Error()) 329 } 330 return nil 331 }, 332 func(db sqlx.DBExecutor) error { 333 ctx := types.WithMgrDBExecutor(ctx, db) 334 if r != nil && r.Cache != nil { 335 if err := config.Remove(ctx, &config.CondArgs{ 336 RelIDs: []types.SFID{ins.InstanceID}, 337 }); err != nil { 338 return err 339 } 340 if _, err := config.Create(ctx, ins.InstanceID, r.Cache); err != nil { 341 return err 342 } 343 } 344 return nil 345 }, 346 func(d sqlx.DBExecutor) error { 347 ctx := types.WithMgrDBExecutor(ctx, d) 348 if forUpdate { 349 _ = vm.DelInstance(ctx, ins.InstanceID) 350 } 351 _ctx, err := WithInstanceRuntimeContext(types.WithInstance(ctx, ins)) 352 if err != nil { 353 return err 354 } 355 // TODO should below actions be in a critical section? 356 if err = vm.NewInstance(_ctx, code, ins.InstanceID, state); err != nil { 357 return status.CreateInstanceFailed.StatusErr().WithDesc(err.Error()) 358 } 359 ins.State, _ = vm.GetInstanceState(ins.InstanceID) 360 if ins.State != state { 361 l.Warn(errors.New("unmatched instance state")) 362 } 363 return nil 364 }, 365 ).Do() 366 if err != nil { 367 return nil, err 368 } 369 return ins, nil 370 } 371 372 func Upsert(ctx context.Context, r *CreateReq, state enums.InstanceState, old ...types.SFID) (*models.Instance, error) { 373 res := types.MustResourceFromContext(ctx) 374 375 _, code, err := resource.GetContentBySFID(ctx, res.ResourceID) 376 if err != nil { 377 return nil, err 378 } 379 380 return UpsertByCode(ctx, r, code, state, old...) 381 } 382 383 func Deploy(ctx context.Context, cmd enums.DeployCmd) (err error) { 384 var m = types.MustInstanceFromContext(ctx) 385 386 switch cmd { 387 case enums.DEPLOY_CMD__HUNGUP: 388 m.State = enums.INSTANCE_STATE__STOPPED 389 case enums.DEPLOY_CMD__START: 390 m.State = enums.INSTANCE_STATE__STARTED 391 default: 392 return status.UnknownDeployCommand.StatusErr(). 393 WithDesc(strconv.Itoa(int(cmd))) 394 } 395 396 return sqlx.NewTasks(types.MustMgrDBExecutorFromContext(ctx)).With( 397 func(d sqlx.DBExecutor) error { 398 if err = m.UpdateByInstanceID(d); err != nil { 399 if sqlx.DBErr(err).IsConflict() { 400 return status.MultiInstanceDeployed.StatusErr(). 401 WithDesc(m.AppletID.String()) 402 } 403 if sqlx.DBErr(err).IsNotFound() { 404 return status.InstanceNotFound.StatusErr(). 405 WithDesc(m.AppletID.String()) 406 } 407 return status.DatabaseError.StatusErr().WithDesc(err.Error()) 408 } 409 return nil 410 }, 411 func(d sqlx.DBExecutor) error { 412 switch m.State { 413 case enums.INSTANCE_STATE__STOPPED: 414 err = vm.StopInstance(ctx, m.InstanceID) 415 case enums.INSTANCE_STATE__STARTED: 416 err = vm.StartInstance(ctx, m.InstanceID) 417 } 418 if err != nil { 419 // Warn 420 } 421 return nil 422 }, 423 ).Do() 424 }