github.com/machinefi/w3bstream@v1.6.5-rc9.0.20240426031326-b8c7c4876e72/pkg/modules/applet/applet.go (about)

     1  package applet
     2  
     3  import (
     4  	"context"
     5  
     6  	confid "github.com/machinefi/w3bstream/pkg/depends/conf/id"
     7  	"github.com/machinefi/w3bstream/pkg/depends/kit/sqlx"
     8  	"github.com/machinefi/w3bstream/pkg/depends/kit/statusx"
     9  	"github.com/machinefi/w3bstream/pkg/enums"
    10  	"github.com/machinefi/w3bstream/pkg/errors/status"
    11  	"github.com/machinefi/w3bstream/pkg/models"
    12  	"github.com/machinefi/w3bstream/pkg/modules/deploy"
    13  	"github.com/machinefi/w3bstream/pkg/modules/resource"
    14  	"github.com/machinefi/w3bstream/pkg/modules/strategy"
    15  	"github.com/machinefi/w3bstream/pkg/types"
    16  	"github.com/machinefi/w3bstream/pkg/types/wasm"
    17  )
    18  
    19  func GetBySFID(ctx context.Context, id types.SFID) (*models.Applet, error) {
    20  	d := types.MustMgrDBExecutorFromContext(ctx)
    21  	m := &models.Applet{RelApplet: models.RelApplet{AppletID: id}}
    22  
    23  	if err := m.FetchByAppletID(d); err != nil {
    24  		if sqlx.DBErr(err).IsNotFound() {
    25  			return nil, status.AppletNotFound
    26  		}
    27  		return nil, status.DatabaseError.StatusErr().WithDesc(err.Error())
    28  	}
    29  	return m, nil
    30  }
    31  
    32  func RemoveBySFID(ctx context.Context, id types.SFID) error {
    33  	d := types.MustMgrDBExecutorFromContext(ctx)
    34  	m := &models.Applet{RelApplet: models.RelApplet{AppletID: id}}
    35  
    36  	return sqlx.NewTasks(d).With(
    37  		func(d sqlx.DBExecutor) error {
    38  			if err := m.DeleteByAppletID(d); err != nil {
    39  				return status.DatabaseError.StatusErr().WithDesc(err.Error())
    40  			}
    41  			return nil
    42  		},
    43  		func(d sqlx.DBExecutor) error {
    44  			ctx := types.WithMgrDBExecutor(ctx, d)
    45  			return strategy.Remove(ctx, &strategy.CondArgs{
    46  				AppletIDs: types.SFIDs{id},
    47  			})
    48  		},
    49  		func(d sqlx.DBExecutor) error {
    50  			ctx := types.WithMgrDBExecutor(ctx, d)
    51  			return deploy.RemoveByAppletSFID(ctx, m.AppletID)
    52  		},
    53  	).Do()
    54  }
    55  
    56  func Remove(ctx context.Context, r *CondArgs) error {
    57  	var (
    58  		d = types.MustMgrDBExecutorFromContext(ctx)
    59  		m = &models.Applet{}
    60  
    61  		err error
    62  		lst []models.Applet
    63  	)
    64  
    65  	return sqlx.NewTasks(d).With(
    66  		func(d sqlx.DBExecutor) error {
    67  			lst, err = m.List(d, r.Condition())
    68  			if err != nil {
    69  				return status.DatabaseError.StatusErr().WithDesc(err.Error())
    70  			}
    71  			return nil
    72  		},
    73  		func(d sqlx.DBExecutor) error {
    74  			ctx := types.WithMgrDBExecutor(ctx, d)
    75  			summary := statusx.ErrorFields{}
    76  			for i := range lst {
    77  				v := &lst[i]
    78  				if err = RemoveBySFID(ctx, v.AppletID); err != nil {
    79  					se := statusx.FromErr(err)
    80  					summary = append(summary, &statusx.ErrorField{
    81  						In:    v.AppletID.String(),
    82  						Field: se.Key,
    83  						Msg:   se.Desc,
    84  					})
    85  				}
    86  			}
    87  			if len(summary) > 0 {
    88  				return status.BatchRemoveAppletFailed.StatusErr().
    89  					AppendErrorFields(summary...)
    90  			}
    91  			return nil
    92  		},
    93  	).Do()
    94  }
    95  
    96  func List(ctx context.Context, r *ListReq) (*ListRsp, error) {
    97  	var (
    98  		d    = types.MustMgrDBExecutorFromContext(ctx)
    99  		err  error
   100  		app  = &models.Applet{}
   101  		ret  = &ListRsp{}
   102  		cond = r.Condition()
   103  	)
   104  
   105  	if ret.Data, err = app.List(d, cond, r.Addition()); err != nil {
   106  		return nil, status.DatabaseError.StatusErr().WithDesc(err.Error())
   107  	}
   108  	if ret.Total, err = app.Count(d, cond); err != nil {
   109  		return nil, status.DatabaseError.StatusErr().WithDesc(err.Error())
   110  	}
   111  	return ret, nil
   112  }
   113  
   114  func ListDetail(ctx context.Context, r *ListReq) (*ListDetailRsp, error) {
   115  	var (
   116  		lst *ListRsp
   117  		err error
   118  		ins *models.Instance
   119  		res *models.Resource
   120  		ret = &ListDetailRsp{}
   121  	)
   122  
   123  	lst, err = List(ctx, r)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  	ret = &ListDetailRsp{Total: lst.Total}
   128  
   129  	for i := range lst.Data {
   130  		app := &lst.Data[i]
   131  		if ins, _ = deploy.GetByAppletSFID(ctx, app.AppletID); ins == nil {
   132  			continue
   133  		}
   134  		if res, err = resource.GetBySFID(ctx, app.ResourceID); err != nil {
   135  			return nil, err
   136  		}
   137  		ret.Data = append(ret.Data, detail(app, ins, res))
   138  	}
   139  	return ret, nil
   140  }
   141  
   142  func Create(ctx context.Context, r *CreateReq) (*CreateRsp, error) {
   143  	var (
   144  		res *models.Resource
   145  		acc = types.MustAccountFromContext(ctx)
   146  		raw []byte
   147  		err error
   148  	)
   149  
   150  	filename := r.WasmName
   151  	if filename == "" {
   152  		filename = r.AppletName + ".wasm"
   153  	}
   154  	res, raw, err = resource.Create(ctx, acc.AccountID, r.File, filename, r.WasmMd5)
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  	ctx = types.WithResource(ctx, res)
   159  
   160  	var (
   161  		idg = confid.MustNewSFIDGenerator()
   162  		prj = types.MustProjectFromContext(ctx)
   163  		app = &models.Applet{
   164  			RelApplet:   models.RelApplet{AppletID: idg.MustGenSFID()},
   165  			RelProject:  models.RelProject{ProjectID: prj.ProjectID},
   166  			RelResource: models.RelResource{ResourceID: res.ResourceID},
   167  			AppletInfo:  models.AppletInfo{Name: r.AppletName},
   168  		}
   169  		sty []models.Strategy
   170  		ins *models.Instance
   171  	)
   172  
   173  	err = sqlx.NewTasks(types.MustMgrDBExecutorFromContext(ctx)).With(
   174  		func(d sqlx.DBExecutor) error {
   175  			if err = app.Create(d); err != nil {
   176  				if sqlx.DBErr(err).IsConflict() {
   177  					return status.AppletNameConflict
   178  				}
   179  				return status.DatabaseError.StatusErr().WithDesc(err.Error())
   180  			}
   181  			ctx = types.WithApplet(ctx, app)
   182  			return nil
   183  		},
   184  		func(d sqlx.DBExecutor) error {
   185  			ctx := types.WithMgrDBExecutor(ctx, d)
   186  			return strategy.BatchCreate(ctx, r.BuildStrategies(ctx))
   187  		},
   188  		func(d sqlx.DBExecutor) error {
   189  			if r.WasmCache == nil {
   190  				r.WasmCache = wasm.DefaultCache()
   191  			}
   192  			ctx := types.WithMgrDBExecutor(ctx, d)
   193  			rb := &deploy.CreateReq{Cache: r.WasmCache}
   194  			ins, err = deploy.UpsertByCode(ctx, rb, raw, enums.INSTANCE_STATE__STARTED)
   195  			return err
   196  		},
   197  	).Do()
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  
   202  	return &CreateRsp{
   203  		Applet:     app,
   204  		Instance:   ins,
   205  		Resource:   res,
   206  		Strategies: sty,
   207  	}, nil
   208  }
   209  
   210  func Update(ctx context.Context, r *UpdateReq) (*UpdateRsp, error) {
   211  	var (
   212  		d   = types.MustMgrDBExecutorFromContext(ctx)
   213  		app = types.MustAppletFromContext(ctx)
   214  		ins *models.Instance // maybe not deployed
   215  		res *models.Resource
   216  		sty []models.Strategy
   217  		raw []byte
   218  		err error
   219  	)
   220  
   221  	// create resource if needed
   222  	if r.File != nil {
   223  		acc := types.MustAccountFromContext(ctx)
   224  		filename, md5 := r.Info.WasmName, r.Info.WasmMd5
   225  		if filename == "" {
   226  			filename = r.AppletName + ".wasm"
   227  		}
   228  		res, raw, err = resource.Create(ctx, acc.AccountID, r.File, filename, md5)
   229  		if err != nil {
   230  			return nil, err
   231  		}
   232  	}
   233  
   234  	err = sqlx.NewTasks(d).With(
   235  		// update strategy
   236  		func(d sqlx.DBExecutor) error {
   237  			ctx := types.WithMgrDBExecutor(ctx, d)
   238  			sty = r.BuildStrategies(ctx)
   239  			if len(sty) == 0 {
   240  				return nil
   241  			}
   242  			if err = strategy.Remove(ctx, &strategy.CondArgs{
   243  				AppletIDs: types.SFIDs{app.AppletID},
   244  			}); err != nil {
   245  				return err
   246  			}
   247  			if err = strategy.BatchCreate(ctx, r.BuildStrategies(ctx)); err != nil {
   248  				return err
   249  			}
   250  			return nil
   251  		},
   252  		// update applet info
   253  		func(d sqlx.DBExecutor) error {
   254  			if r.Info.AppletName == "" && res == nil {
   255  				return nil
   256  			}
   257  			if r.Info.AppletName != "" {
   258  				app.Name = r.Info.AppletName
   259  			}
   260  			if res != nil {
   261  				app.ResourceID = res.ResourceID
   262  			}
   263  			if err = app.UpdateByAppletID(d); err != nil {
   264  				if sqlx.DBErr(err).IsConflict() {
   265  					return status.AppletNameConflict
   266  				}
   267  				return status.DatabaseError.StatusErr().WithDesc(err.Error())
   268  			}
   269  			return nil
   270  		},
   271  		// update and deploy instance
   272  		func(d sqlx.DBExecutor) error {
   273  			ctx := types.WithMgrDBExecutor(ctx, d)
   274  			if r.File == nil {
   275  				return nil // instance state will not be changed
   276  			}
   277  			ins, err = deploy.GetByAppletSFID(ctx, app.AppletID)
   278  			if err != nil {
   279  				return err
   280  			}
   281  			var rb *deploy.CreateReq
   282  			if r.Info.WasmCache != nil {
   283  				rb = &deploy.CreateReq{Cache: r.Info.WasmCache}
   284  			}
   285  			ins, err = deploy.UpsertByCode(ctx, rb, raw, enums.INSTANCE_STATE__STARTED, ins.InstanceID)
   286  			return err
   287  		},
   288  	).Do()
   289  	if err != nil {
   290  		return nil, err
   291  	}
   292  
   293  	return &UpdateRsp{app, ins, res, sty}, nil
   294  }