package boltdb import ( "errors" "strconv" "testing" portainer "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/dataservices" "github.com/stretchr/testify/require" ) const testBucketName = "test-bucket" const testId = 1234 type testStruct struct { Key string Value string } func TestTxs(t *testing.T) { t.Parallel() conn := DbConnection{Path: t.TempDir()} err := conn.Open() require.NoError(t, err) t.Cleanup(func() { err := conn.Close() require.NoError(t, err) }) // Error propagation err = conn.UpdateTx(func(tx portainer.Transaction) error { return errors.New("this is an error") }) require.Error(t, err) // Create an object newObj := testStruct{Key: "key", Value: "value"} err = conn.UpdateTx(func(tx portainer.Transaction) error { if err := tx.SetServiceName(testBucketName); err != nil { return err } return tx.CreateObjectWithId(testBucketName, testId, newObj) }) require.NoError(t, err) obj := testStruct{} err = conn.ViewTx(func(tx portainer.Transaction) error { return tx.GetObject(testBucketName, conn.ConvertToKey(testId), &obj) }) require.NoError(t, err) if obj.Key != newObj.Key || obj.Value != newObj.Value { t.Fatalf("expected %s:%s, got %s:%s instead", newObj.Key, newObj.Value, obj.Key, obj.Value) } // Update an object updatedObj := testStruct{Key: "updated-key", Value: "updated-value"} err = conn.UpdateTx(func(tx portainer.Transaction) error { return tx.UpdateObject(testBucketName, conn.ConvertToKey(testId), &updatedObj) }) require.NoError(t, err) err = conn.ViewTx(func(tx portainer.Transaction) error { return tx.GetObject(testBucketName, conn.ConvertToKey(testId), &obj) }) require.NoError(t, err) if obj.Key != updatedObj.Key || obj.Value != updatedObj.Value { t.Fatalf("expected %s:%s, got %s:%s instead", updatedObj.Key, updatedObj.Value, obj.Key, obj.Value) } // Delete an object err = conn.UpdateTx(func(tx portainer.Transaction) error { return tx.DeleteObject(testBucketName, conn.ConvertToKey(testId)) }) require.NoError(t, err) err = conn.ViewTx(func(tx portainer.Transaction) error { return tx.GetObject(testBucketName, conn.ConvertToKey(testId), &obj) }) require.True(t, dataservices.IsErrObjectNotFound(err)) // Get next identifier err = conn.UpdateTx(func(tx portainer.Transaction) error { id1 := tx.GetNextIdentifier(testBucketName) id2 := tx.GetNextIdentifier(testBucketName) if id1+1 != id2 { return errors.New("unexpected identifier sequence") } return nil }) require.NoError(t, err) // Try to write in a read transaction err = conn.ViewTx(func(tx portainer.Transaction) error { return tx.CreateObjectWithId(testBucketName, testId, newObj) }) require.Error(t, err) } func BenchmarkGetAll(b *testing.B) { const endpointBucket = "endpoints" const n = 10000 conn := DbConnection{Path: b.TempDir()} err := conn.Open() require.NoError(b, err) b.Cleanup(func() { err := conn.Close() require.NoError(b, err) }) err = conn.UpdateTx(func(tx portainer.Transaction) error { if err := tx.SetServiceName(endpointBucket); err != nil { return err } for i := 1; i <= n; i++ { ep := portainer.Endpoint{ ID: portainer.EndpointID(i), Name: "env-" + strconv.Itoa(i), Type: portainer.DockerEnvironment, URL: "tcp://192.168.1." + strconv.Itoa(i%254+1) + ":2375", PublicURL: "https://env-" + strconv.Itoa(i) + ".example.com", GroupID: portainer.EndpointGroupID(i%10 + 1), TagIDs: []portainer.TagID{portainer.TagID(i%5 + 1), portainer.TagID(i%3 + 1)}, LastCheckInDate: int64(i) * 1000, EdgeID: "edge-" + strconv.Itoa(i), } if err := tx.CreateObjectWithId(endpointBucket, i, &ep); err != nil { return err } } return nil }) require.NoError(b, err) b.ResetTimer() b.ReportAllocs() for b.Loop() { var collection []portainer.Endpoint if err := conn.ViewTx(func(tx portainer.Transaction) error { return tx.GetAll(endpointBucket, new(portainer.Endpoint), dataservices.AppendFn(&collection)) }); err != nil { b.Fatal(err) } } }