diff --git a/accounts/pkg/indexer/index/cs3/autoincrement.go b/accounts/pkg/indexer/index/cs3/autoincrement.go index c7fde88752..7e0abd597e 100644 --- a/accounts/pkg/indexer/index/cs3/autoincrement.go +++ b/accounts/pkg/indexer/index/cs3/autoincrement.go @@ -3,10 +3,13 @@ package cs3 import ( "context" "fmt" + idxerrs "github.com/owncloud/ocis/accounts/pkg/indexer/errors" "io/ioutil" "net/http" "os" "path" + "sort" + "strconv" "strings" user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" @@ -113,7 +116,20 @@ func (idx AutoincrementIndex) Lookup(v string) ([]string, error) { } func (idx AutoincrementIndex) Add(id, v string) (string, error) { - panic("implement me") + next, err := idx.next() + if err != nil { + return "", err + } + newName := path.Join(idx.indexRootDir, strconv.Itoa(next)) + if err := idx.createSymlink(id, newName); err != nil { + if os.IsExist(err) { + return "", &idxerrs.AlreadyExistsErr{TypeName: idx.typeName, Key: idx.indexBy, Value: v} + } + + return "", err + } + + return newName, nil } func (idx AutoincrementIndex) Remove(id string, v string) error { @@ -222,3 +238,39 @@ func (idx *AutoincrementIndex) authenticate(ctx context.Context) (token string, } return idx.tokenManager.MintToken(ctx, u) } + +func (idx AutoincrementIndex) next() (int, error) { + ctx := context.Background() + t, err := idx.authenticate(ctx) + if err != nil { + return -1, err + } + + ctx = metadata.AppendToOutgoingContext(ctx, token.TokenHeader, t) + res, err := idx.storageProvider.ListContainer(ctx, &provider.ListContainerRequest{ + Ref: &provider.Reference{ + Spec: &provider.Reference_Path{Path: path.Join("/meta", idx.indexRootDir)}, + }, + }) + + if err != nil { + return -1, err + } + + if len(res.GetInfos()) == 0 { + return 0, nil + } + + infos := res.GetInfos() + sort.Slice(infos, func(i, j int) bool { + a, _ := strconv.Atoi(path.Base(infos[i].Path)) + b, _ := strconv.Atoi(path.Base(infos[j].Path)) + return a < b + }) + + latest, err := strconv.Atoi(path.Base(infos[len(infos)-1].Path)) // would returning a string be a better interface? + if err != nil { + return -1, err + } + return latest + 1, nil +} diff --git a/accounts/pkg/indexer/index/cs3/autoincrement_test.go b/accounts/pkg/indexer/index/cs3/autoincrement_test.go index af80df5f4d..1bbe3f5668 100644 --- a/accounts/pkg/indexer/index/cs3/autoincrement_test.go +++ b/accounts/pkg/indexer/index/cs3/autoincrement_test.go @@ -1,5 +1,87 @@ package cs3 -func TestAutoincrementIndexAdd() { +import ( + "os" + "testing" + "github.com/owncloud/ocis/accounts/pkg/config" + "github.com/owncloud/ocis/accounts/pkg/indexer/option" + . "github.com/owncloud/ocis/accounts/pkg/indexer/test" + "github.com/stretchr/testify/assert" +) + +func TestAutoincrementIndexAdd(t *testing.T) { + dataDir := WriteIndexTestDataCS3(t, Data, "ID") + cfg := config.Config{ + Repo: config.Repo{ + Disk: config.Disk{ + Path: "", + }, + CS3: config.CS3{ + ProviderAddr: "0.0.0.0:9215", + DataURL: "http://localhost:9216", + DataPrefix: "data", + JWTSecret: "Pive-Fumkiu4", + }, + }, + } + + sut := NewAutoincrementIndex( + option.WithTypeName(GetTypeFQN(User{})), + option.WithIndexBy("UID"), + option.WithDataURL(cfg.Repo.CS3.DataURL), + option.WithDataPrefix(cfg.Repo.CS3.DataPrefix), + option.WithJWTSecret(cfg.Repo.CS3.JWTSecret), + option.WithProviderAddr(cfg.Repo.CS3.ProviderAddr), + ) + + err := sut.Init() + assert.NoError(t, err) + + for i := 0; i < 5; i++ { + res, err := sut.Add("abcdefg-123", "ignored") + assert.NoError(t, err) + t.Log(res) + } + + _ = os.RemoveAll(dataDir) +} + +func BenchmarkAutoincrementIndexAdd(b *testing.B) { + dataDir := WriteIndexBenchmarkDataCS3(b, Data, "ID") + cfg := config.Config{ + Repo: config.Repo{ + Disk: config.Disk{ + Path: "", + }, + CS3: config.CS3{ + ProviderAddr: "0.0.0.0:9215", + DataURL: "http://localhost:9216", + DataPrefix: "data", + JWTSecret: "Pive-Fumkiu4", + }, + }, + } + + sut := NewAutoincrementIndex( + option.WithTypeName(GetTypeFQN(User{})), + option.WithIndexBy("UID"), + option.WithDataURL(cfg.Repo.CS3.DataURL), + option.WithDataPrefix(cfg.Repo.CS3.DataPrefix), + option.WithJWTSecret(cfg.Repo.CS3.JWTSecret), + option.WithProviderAddr(cfg.Repo.CS3.ProviderAddr), + ) + + err := sut.Init() + assert.NoError(b, err) + + for n := 0; n < b.N; n++ { + _, err := sut.Add("abcdefg-123", "ignored") + if err != nil { + b.Error(err) + } + assert.NoError(b, err) + } + + _ = os.RemoveAll(dataDir) } diff --git a/accounts/pkg/indexer/test/data.go b/accounts/pkg/indexer/test/data.go index 955c453415..b96ab18f37 100644 --- a/accounts/pkg/indexer/test/data.go +++ b/accounts/pkg/indexer/test/data.go @@ -11,26 +11,28 @@ import ( // User is a user. type User struct { ID, UserName, Email string + UID int } // Pet is a pet. type Pet struct { ID, Kind, Color, Name string + UID int } // Data mock data. var Data = map[string][]interface{}{ "users": { - User{ID: "abcdefg-123", UserName: "mikey", Email: "mikey@example.com"}, - User{ID: "hijklmn-456", UserName: "frank", Email: "frank@example.com"}, - User{ID: "ewf4ofk-555", UserName: "jacky", Email: "jacky@example.com"}, - User{ID: "rulan54-777", UserName: "jones", Email: "jones@example.com"}, + User{ID: "abcdefg-123", UserName: "mikey", Email: "mikey@example.com", UID: -1}, + User{ID: "hijklmn-456", UserName: "frank", Email: "frank@example.com", UID: -1}, + User{ID: "ewf4ofk-555", UserName: "jacky", Email: "jacky@example.com", UID: -1}, + User{ID: "rulan54-777", UserName: "jones", Email: "jones@example.com", UID: -1}, }, "pets": { - Pet{ID: "rebef-123", Kind: "Dog", Color: "Brown", Name: "Waldo"}, - Pet{ID: "wefwe-456", Kind: "Cat", Color: "White", Name: "Snowy"}, - Pet{ID: "goefe-789", Kind: "Hog", Color: "Green", Name: "Dicky"}, - Pet{ID: "xadaf-189", Kind: "Hog", Color: "Green", Name: "Ricky"}, + Pet{ID: "rebef-123", Kind: "Dog", Color: "Brown", Name: "Waldo", UID: -1}, + Pet{ID: "wefwe-456", Kind: "Cat", Color: "White", Name: "Snowy", UID: -1}, + Pet{ID: "goefe-789", Kind: "Hog", Color: "Green", Name: "Dicky", UID: -1}, + Pet{ID: "xadaf-189", Kind: "Hog", Color: "Green", Name: "Ricky", UID: -1}, }, } @@ -83,3 +85,28 @@ func WriteIndexTestDataCS3(t *testing.T, m map[string][]interface{}, pk string) return rootDir } + +// WriteIndexBenchmarkDataCS3 writes more data to disk. +func WriteIndexBenchmarkDataCS3(b *testing.B, m map[string][]interface{}, pk string) string { + rootDir := "/var/tmp/ocis/storage/users/data" + for dirName := range m { + fileTypePath := path.Join(rootDir, dirName) + + if err := os.MkdirAll(fileTypePath, 0777); err != nil { + b.Fatal(err) + } + for _, u := range m[dirName] { + data, err := json.Marshal(u) + if err != nil { + b.Fatal(err) + } + + pkVal := ValueOf(u, pk) + if err := ioutil.WriteFile(path.Join(fileTypePath, pkVal), data, 0777); err != nil { + b.Fatal(err) + } + } + } + + return rootDir +}