You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							318 lines
						
					
					
						
							8.4 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							318 lines
						
					
					
						
							8.4 KiB
						
					
					
				
								//go:build tarantool
							 | 
						|
								// +build tarantool
							 | 
						|
								
							 | 
						|
								package tarantool
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"context"
							 | 
						|
									"fmt"
							 | 
						|
									"reflect"
							 | 
						|
									"strings"
							 | 
						|
									"time"
							 | 
						|
								
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/filer"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/glog"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/util"
							 | 
						|
									weed_util "github.com/seaweedfs/seaweedfs/weed/util"
							 | 
						|
									"github.com/tarantool/go-tarantool/v2"
							 | 
						|
									"github.com/tarantool/go-tarantool/v2/crud"
							 | 
						|
									"github.com/tarantool/go-tarantool/v2/pool"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								const (
							 | 
						|
									tarantoolSpaceName = "filer_metadata"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								func init() {
							 | 
						|
									filer.Stores = append(filer.Stores, &TarantoolStore{})
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type TarantoolStore struct {
							 | 
						|
									pool *pool.ConnectionPool
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (store *TarantoolStore) GetName() string {
							 | 
						|
									return "tarantool"
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (store *TarantoolStore) Initialize(configuration weed_util.Configuration, prefix string) error {
							 | 
						|
								
							 | 
						|
									configuration.SetDefault(prefix+"address", "localhost:3301")
							 | 
						|
									configuration.SetDefault(prefix+"user", "guest")
							 | 
						|
									configuration.SetDefault(prefix+"password", "")
							 | 
						|
									configuration.SetDefault(prefix+"timeout", "5s")
							 | 
						|
									configuration.SetDefault(prefix+"maxReconnects", "1000")
							 | 
						|
								
							 | 
						|
									address := configuration.GetString(prefix + "address")
							 | 
						|
									user := configuration.GetString(prefix + "user")
							 | 
						|
									password := configuration.GetString(prefix + "password")
							 | 
						|
								
							 | 
						|
									timeoutStr := configuration.GetString(prefix + "timeout")
							 | 
						|
									timeout, err := time.ParseDuration(timeoutStr)
							 | 
						|
									if err != nil {
							 | 
						|
										return fmt.Errorf("parse tarantool store timeout: %w", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									maxReconnects := configuration.GetInt(prefix + "maxReconnects")
							 | 
						|
									if maxReconnects < 0 {
							 | 
						|
										return fmt.Errorf("maxReconnects is negative")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									addresses := strings.Split(address, ",")
							 | 
						|
								
							 | 
						|
									return store.initialize(addresses, user, password, timeout, uint(maxReconnects))
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (store *TarantoolStore) initialize(addresses []string, user string, password string, timeout time.Duration, maxReconnects uint) error {
							 | 
						|
								
							 | 
						|
									opts := tarantool.Opts{
							 | 
						|
										Timeout:       timeout,
							 | 
						|
										Reconnect:     time.Second,
							 | 
						|
										MaxReconnects: maxReconnects,
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									poolInstances := makePoolInstances(addresses, user, password, opts)
							 | 
						|
									poolOpts := pool.Opts{
							 | 
						|
										CheckTimeout: time.Second,
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									ctx := context.Background()
							 | 
						|
									p, err := pool.ConnectWithOpts(ctx, poolInstances, poolOpts)
							 | 
						|
									if err != nil {
							 | 
						|
										return fmt.Errorf("Can't create connection pool: %w", err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									_, err = p.Do(tarantool.NewPingRequest(), pool.ANY).Get()
							 | 
						|
									if err != nil {
							 | 
						|
										return err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									store.pool = p
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func makePoolInstances(addresses []string, user string, password string, opts tarantool.Opts) []pool.Instance {
							 | 
						|
									poolInstances := make([]pool.Instance, 0, len(addresses))
							 | 
						|
									for i, address := range addresses {
							 | 
						|
										poolInstances = append(poolInstances, makePoolInstance(address, user, password, opts, i))
							 | 
						|
									}
							 | 
						|
									return poolInstances
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func makePoolInstance(address string, user string, password string, opts tarantool.Opts, serial int) pool.Instance {
							 | 
						|
									return pool.Instance{
							 | 
						|
										Name: fmt.Sprintf("instance%d", serial),
							 | 
						|
										Dialer: tarantool.NetDialer{
							 | 
						|
											Address:  address,
							 | 
						|
											User:     user,
							 | 
						|
											Password: password,
							 | 
						|
										},
							 | 
						|
										Opts: opts,
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (store *TarantoolStore) BeginTransaction(ctx context.Context) (context.Context, error) {
							 | 
						|
									return ctx, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (store *TarantoolStore) CommitTransaction(ctx context.Context) error {
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (store *TarantoolStore) RollbackTransaction(ctx context.Context) error {
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (store *TarantoolStore) InsertEntry(ctx context.Context, entry *filer.Entry) (err error) {
							 | 
						|
									dir, name := entry.FullPath.DirAndName()
							 | 
						|
									meta, err := entry.EncodeAttributesAndChunks()
							 | 
						|
									if err != nil {
							 | 
						|
										return fmt.Errorf("encode %s: %s", entry.FullPath, err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if len(entry.GetChunks()) > filer.CountEntryChunksForGzip {
							 | 
						|
										meta = util.MaybeGzipData(meta)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									var ttl int64
							 | 
						|
									if entry.TtlSec > 0 {
							 | 
						|
										ttl = time.Now().Unix() + int64(entry.TtlSec)
							 | 
						|
									} else {
							 | 
						|
										ttl = 0
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									var operations = []crud.Operation{
							 | 
						|
										{
							 | 
						|
											Operator: crud.Insert,
							 | 
						|
											Field:    "data",
							 | 
						|
											Value:    string(meta),
							 | 
						|
										},
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									req := crud.MakeUpsertRequest(tarantoolSpaceName).
							 | 
						|
										Tuple([]interface{}{dir, nil, name, ttl, string(meta)}).
							 | 
						|
										Operations(operations)
							 | 
						|
								
							 | 
						|
									ret := crud.Result{}
							 | 
						|
								
							 | 
						|
									if err := store.pool.Do(req, pool.RW).GetTyped(&ret); err != nil {
							 | 
						|
										return fmt.Errorf("insert %s: %s", entry.FullPath, err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (store *TarantoolStore) UpdateEntry(ctx context.Context, entry *filer.Entry) (err error) {
							 | 
						|
									return store.InsertEntry(ctx, entry)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (store *TarantoolStore) FindEntry(ctx context.Context, fullpath weed_util.FullPath) (entry *filer.Entry, err error) {
							 | 
						|
									dir, name := fullpath.DirAndName()
							 | 
						|
								
							 | 
						|
									findEntryGetOpts := crud.GetOpts{
							 | 
						|
										Fields:        crud.MakeOptTuple([]interface{}{"data"}),
							 | 
						|
										Mode:          crud.MakeOptString("read"),
							 | 
						|
										PreferReplica: crud.MakeOptBool(true),
							 | 
						|
										Balance:       crud.MakeOptBool(true),
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									req := crud.MakeGetRequest(tarantoolSpaceName).
							 | 
						|
										Key(crud.Tuple([]interface{}{dir, name})).
							 | 
						|
										Opts(findEntryGetOpts)
							 | 
						|
								
							 | 
						|
									resp := crud.Result{}
							 | 
						|
								
							 | 
						|
									err = store.pool.Do(req, pool.PreferRO).GetTyped(&resp)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									results, ok := resp.Rows.([]interface{})
							 | 
						|
									if !ok || len(results) != 1 {
							 | 
						|
										return nil, filer_pb.ErrNotFound
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									rows, ok := results[0].([]interface{})
							 | 
						|
									if !ok || len(rows) != 1 {
							 | 
						|
										return nil, filer_pb.ErrNotFound
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									row, ok := rows[0].(string)
							 | 
						|
									if !ok {
							 | 
						|
										return nil, fmt.Errorf("Can't convert rows[0] field to string. Actual type: %v, value: %v", reflect.TypeOf(rows[0]), rows[0])
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									entry = &filer.Entry{
							 | 
						|
										FullPath: fullpath,
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									err = entry.DecodeAttributesAndChunks(weed_util.MaybeDecompressData([]byte(row)))
							 | 
						|
									if err != nil {
							 | 
						|
										return entry, fmt.Errorf("decode %s : %v", entry.FullPath, err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return entry, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (store *TarantoolStore) DeleteEntry(ctx context.Context, fullpath weed_util.FullPath) (err error) {
							 | 
						|
									dir, name := fullpath.DirAndName()
							 | 
						|
								
							 | 
						|
									delOpts := crud.DeleteOpts{
							 | 
						|
										Noreturn: crud.MakeOptBool(true),
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									req := crud.MakeDeleteRequest(tarantoolSpaceName).
							 | 
						|
										Key(crud.Tuple([]interface{}{dir, name})).
							 | 
						|
										Opts(delOpts)
							 | 
						|
								
							 | 
						|
									if _, err := store.pool.Do(req, pool.RW).Get(); err != nil {
							 | 
						|
										return fmt.Errorf("delete %s : %v", fullpath, err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (store *TarantoolStore) DeleteFolderChildren(ctx context.Context, fullpath weed_util.FullPath) (err error) {
							 | 
						|
									req := tarantool.NewCallRequest("filer_metadata.delete_by_directory_idx").
							 | 
						|
										Args([]interface{}{fullpath})
							 | 
						|
								
							 | 
						|
									if _, err := store.pool.Do(req, pool.RW).Get(); err != nil {
							 | 
						|
										return fmt.Errorf("delete %s : %v", fullpath, err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (store *TarantoolStore) ListDirectoryPrefixedEntries(ctx context.Context, dirPath weed_util.FullPath, startFileName string, includeStartFile bool, limit int64, prefix string, eachEntryFunc filer.ListEachEntryFunc) (lastFileName string, err error) {
							 | 
						|
									return lastFileName, filer.ErrUnsupportedListDirectoryPrefixed
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (store *TarantoolStore) ListDirectoryEntries(ctx context.Context, dirPath weed_util.FullPath, startFileName string, includeStartFile bool, limit int64, eachEntryFunc filer.ListEachEntryFunc) (lastFileName string, err error) {
							 | 
						|
								
							 | 
						|
									req := tarantool.NewCallRequest("filer_metadata.find_by_directory_idx_and_name").
							 | 
						|
										Args([]interface{}{string(dirPath), startFileName, includeStartFile, limit})
							 | 
						|
								
							 | 
						|
									results, err := store.pool.Do(req, pool.PreferRO).Get()
							 | 
						|
									if err != nil {
							 | 
						|
										return
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if len(results) < 1 {
							 | 
						|
										glog.ErrorfCtx(ctx, "Can't find results, data is empty")
							 | 
						|
										return
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									rows, ok := results[0].([]interface{})
							 | 
						|
									if !ok {
							 | 
						|
										glog.ErrorfCtx(ctx, "Can't convert results[0] to list")
							 | 
						|
										return
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for _, result := range rows {
							 | 
						|
										row, ok := result.([]interface{})
							 | 
						|
										if !ok {
							 | 
						|
											glog.ErrorfCtx(ctx, "Can't convert result to list")
							 | 
						|
											return
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if len(row) < 5 {
							 | 
						|
											glog.ErrorfCtx(ctx, "Length of result is less than needed: %v", len(row))
							 | 
						|
											return
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										nameRaw := row[2]
							 | 
						|
										name, ok := nameRaw.(string)
							 | 
						|
										if !ok {
							 | 
						|
											glog.ErrorfCtx(ctx, "Can't convert name field to string. Actual type: %v, value: %v", reflect.TypeOf(nameRaw), nameRaw)
							 | 
						|
											return
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										dataRaw := row[4]
							 | 
						|
										data, ok := dataRaw.(string)
							 | 
						|
										if !ok {
							 | 
						|
											glog.ErrorfCtx(ctx, "Can't convert data field to string. Actual type: %v, value: %v", reflect.TypeOf(dataRaw), dataRaw)
							 | 
						|
											return
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										entry := &filer.Entry{
							 | 
						|
											FullPath: util.NewFullPath(string(dirPath), name),
							 | 
						|
										}
							 | 
						|
										lastFileName = name
							 | 
						|
										if decodeErr := entry.DecodeAttributesAndChunks(util.MaybeDecompressData([]byte(data))); decodeErr != nil {
							 | 
						|
											err = decodeErr
							 | 
						|
											glog.V(0).InfofCtx(ctx, "list %s : %v", entry.FullPath, err)
							 | 
						|
											break
							 | 
						|
										}
							 | 
						|
										if !eachEntryFunc(entry) {
							 | 
						|
											break
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return lastFileName, err
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (store *TarantoolStore) Shutdown() {
							 | 
						|
									store.pool.Close()
							 | 
						|
								}
							 |