package s3local import ( "context" "flag" "fmt" "time" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/service/tree" "git.frostfs.info/TrueCloudLab/xk6-frostfs/internal/local" "git.frostfs.info/TrueCloudLab/xk6-frostfs/internal/local/rawclient" "git.frostfs.info/TrueCloudLab/xk6-frostfs/internal/stats" "go.k6.io/k6/js/modules" "go.k6.io/k6/metrics" "go.uber.org/zap" ) // RootModule is the global module object type. It is instantiated once per test // run and will be used to create k6/x/frostfs/s3local module instances for each VU. type RootModule struct { m *local.RootModule } // Local represents an instance of the module for every VU. type Local struct { l *local.Local } // Ensure the interfaces are implemented correctly. var ( _ modules.Module = &RootModule{} _ modules.Instance = &Local{} internalObjPutSuccess, internalObjPutFails, internalObjPutDuration, internalObjPutData *metrics.Metric internalObjGetSuccess, internalObjGetFails, internalObjGetDuration, internalObjGetData *metrics.Metric objPutSuccess, objPutFails, objPutDuration, objPutData *metrics.Metric objGetSuccess, objGetFails, objGetDuration, objGetData *metrics.Metric ) func init() { modules.Register("k6/x/frostfs/s3local", &RootModule{ m: &local.RootModule{}, }) } // NewModuleInstance implements the modules.Module interface and returns // a new instance for each VU. func (r *RootModule) NewModuleInstance(vu modules.VU) modules.Instance { return &Local{local.NewLocalModuleInstance(vu, r.m.GetOrCreateEngine)} } // Exports implements the modules.Instance interface and returns the exports // of the JS module. func (s *Local) Exports() modules.Exports { return modules.Exports{Default: s} } func (s *Local) Connect(configFile string, configDir string, params map[string]string, bucketMapping map[string]string, maxSizeGB int64) (*Client, error) { // Parse configuration flags. fs := flag.NewFlagSet("s3local", flag.ContinueOnError) hexKey := fs.String("hex_key", "", "Private key to use as a hexadecimal string. A random one is created if none is provided") nodePosition := fs.Int("node_position", 0, "Position of this node in the node array if loading multiple nodes independently") nodeCount := fs.Int("node_count", 1, "Number of nodes in the node array if loading multiple nodes independently") debugLogger := fs.Bool("debug_logger", false, "Whether to use the development logger instead of the default one for debugging purposes") { args := make([]string, 0, len(params)) for k, v := range params { args = append(args, fmt.Sprintf("-%s=%s", k, v)) } if err := fs.Parse(args); err != nil { return nil, fmt.Errorf("parsing parameters: %v", err) } } // Validate and read configuration flags. key, err := local.ParseOrCreateKey(*hexKey) if err != nil { return nil, fmt.Errorf("parsing hex_key: %v", err) } if *nodeCount <= 0 { return nil, fmt.Errorf("node_count must be positive") } if *nodePosition < 0 || *nodePosition >= *nodeCount { return nil, fmt.Errorf("node_position must be in the range [0, node_count-1]") } // Register metrics. internalObjPutSuccess, _ = stats.Registry.NewMetric("s3local_internal_obj_put_success", metrics.Counter) internalObjPutFails, _ = stats.Registry.NewMetric("s3local_internal_obj_put_fails", metrics.Counter) internalObjPutDuration, _ = stats.Registry.NewMetric("s3local_internal_obj_put_duration", metrics.Trend, metrics.Time) internalObjPutData, _ = stats.Registry.NewMetric("s3local_internal_obj_put_bytes", metrics.Counter, metrics.Data) internalObjGetSuccess, _ = stats.Registry.NewMetric("s3local_internal_obj_get_success", metrics.Counter) internalObjGetFails, _ = stats.Registry.NewMetric("s3local_internal_obj_get_fails", metrics.Counter) internalObjGetDuration, _ = stats.Registry.NewMetric("s3local_internal_obj_get_duration", metrics.Trend, metrics.Time) internalObjGetData, _ = stats.Registry.NewMetric("s3local_internal_obj_get_bytes", metrics.Counter, metrics.Data) objPutSuccess, _ = stats.Registry.NewMetric("s3local_obj_put_success", metrics.Counter) objPutFails, _ = stats.Registry.NewMetric("s3local_obj_put_fails", metrics.Counter) objPutDuration, _ = stats.Registry.NewMetric("s3local_obj_put_duration", metrics.Trend, metrics.Time) objPutData, _ = stats.Registry.NewMetric("s3local_obj_put_bytes", metrics.Counter, metrics.Data) objGetSuccess, _ = stats.Registry.NewMetric("s3local_obj_get_success", metrics.Counter) objGetFails, _ = stats.Registry.NewMetric("s3local_obj_get_fails", metrics.Counter) objGetDuration, _ = stats.Registry.NewMetric("s3local_obj_get_duration", metrics.Trend, metrics.Time) objGetData, _ = stats.Registry.NewMetric("s3local_obj_get_bytes", metrics.Counter, metrics.Data) // Create S3 layer backed by local storage engine and tree service. ng, limiter, err := s.l.ResolveEngine(s.l.VU().Context(), configFile, configDir, *debugLogger, maxSizeGB) if err != nil { return nil, fmt.Errorf("connecting to engine for config - file %q dir %q: %v", configFile, configDir, err) } treeSvc := tree.NewTree(treeServiceEngineWrapper{ ng: ng, pos: *nodePosition, size: *nodeCount, }, zap.L()) rc := rawclient.New(ng, rawclient.WithKey(key.PrivateKey), rawclient.WithPutHandler(func(sz uint64, err error, dt time.Duration) { if err != nil { stats.Report(s.l.VU(), internalObjPutFails, 1) } else { stats.Report(s.l.VU(), internalObjPutSuccess, 1) stats.Report(s.l.VU(), internalObjPutDuration, metrics.D(dt)) stats.Report(s.l.VU(), internalObjPutData, float64(sz)) } }), rawclient.WithGetHandler(func(sz uint64, err error, dt time.Duration) { if err != nil { stats.Report(s.l.VU(), internalObjGetFails, 1) } else { stats.Report(s.l.VU(), internalObjGetSuccess, 1) stats.Report(s.l.VU(), internalObjGetDuration, metrics.D(dt)) stats.Report(s.l.VU(), internalObjGetData, float64(sz)) } }), ) resolver, err := newFixedBucketResolver(bucketMapping) if err != nil { return nil, fmt.Errorf("creating bucket resolver: %v", err) } cfg := &layer.Config{ Caches: layer.DefaultCachesConfigs(zap.L()), AnonKey: layer.AnonymousKey{Key: key}, Resolver: resolver, TreeService: treeSvc, } l := layer.NewLayer(zap.L(), &frostfs{rc}, cfg) err = l.Initialize(s.l.VU().Context(), nopEventListener{}) if err != nil { return nil, fmt.Errorf("initialize: %w", err) } return &Client{ vu: s.l.VU(), l: l, ownerID: rc.OwnerID(), resolver: resolver, limiter: limiter, }, nil } type nopEventListener struct{} func (nopEventListener) Subscribe(context.Context, string, layer.MsgHandler) error { return nil } func (nopEventListener) Listen(context.Context) {}