lib/dsstore/itest/datastore.go (214 lines of code) (raw):

// Copyright 2019 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Binary itest runs some code against Datastore. package main import ( "context" "flag" "fmt" glog "github.com/golang/glog" /* copybara-comment */ "cloud.google.com/go/datastore" /* copybara-comment */ "google.golang.org/grpc/codes" /* copybara-comment */ "google.golang.org/grpc/status" /* copybara-comment */ "github.com/pborman/uuid" /* copybara-comment */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/dsstore" /* copybara-comment: dsstore */ "github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/storage" /* copybara-comment: storage */ dpb "github.com/golang/protobuf/ptypes/duration" /* copybara-comment */ ) var ( fakeProjectID = flag.String("project_id", "fake-project-id", "") fakeServiceName = "fake-service-name" fakeConfigPath = "fake-config-path" fakeDataType = "fake-datatype" fakeRealm = "fake-realm" fakeUser = "fake-user" ) func main() { ctx := context.Background() flag.Parse() c, err := datastore.NewClient(ctx, *fakeProjectID) if err != nil { glog.Exitf("datastore.NewClient(...) failed: %v", err) } scenarioSimple(ctx, c) scenarioTransactionsConflictingLinearizable(ctx, c) scenarioTransactionsConflictingNonLinearizable(ctx, c) scenarioTransactionsReadAfterWrite(ctx, c) scenarioLock(ctx, c) scenarioLockConcurrency(ctx, c) fmt.Println("All tests passed.") } func scenarioSimple(ctx context.Context, c *datastore.Client) { // Scenario: write, read-check, read-modify-write, read-check, delete, read-check id := "fake-id-simple-" + uuid.New() s := dsstore.New(c, *fakeProjectID, fakeServiceName, fakeConfigPath) { // Write tx, err := s.Tx(true) if err != nil { glog.Exitf("store.Tx(true) failed: %v", err) } entity := &dpb.Duration{Seconds: 60} if err := s.WriteTx(fakeDataType, fakeRealm, fakeUser, id, storage.LatestRev, entity, nil, tx); err != nil { glog.Exitf("store.WriteTx(...) failed: %v", err) } if err := tx.Finish(); err != nil { glog.Exitf("tx.Finish() failed: %v", err) } } { // Read to check it is created. got := &dpb.Duration{} if err := s.Read(fakeDataType, fakeRealm, fakeUser, id, storage.LatestRev, got); err != nil { glog.Exitf("store.Read(...) failed: %v", err) } want := &dpb.Duration{Seconds: 60} if got.GetSeconds() != want.GetSeconds() { glog.Exitf("store.Read(...) = %v, want %v", got, want) } } { // RMW tx, err := s.Tx(true) if err != nil { glog.Exitf("store.Tx(true) failed: %v", err) } resp := &dpb.Duration{} if err := s.ReadTx(fakeDataType, fakeRealm, fakeUser, id, storage.LatestRev, resp, tx); err != nil { glog.Exitf("store.ReadTx(...) failed: %v", err) } resp.Seconds = resp.Seconds + 60 if err := s.WriteTx(fakeDataType, fakeRealm, fakeUser, id, storage.LatestRev, resp, nil, tx); err != nil { glog.Exitf("store.WriteTx(...) failed: %v", err) } if err := tx.Finish(); err != nil { glog.Exitf("tx.Finish() failed: %v", err) } } { // Read to check it is updated. got := &dpb.Duration{} if err := s.Read(fakeDataType, fakeRealm, fakeUser, id, storage.LatestRev, got); err != nil { glog.Exitf("store.Read(...) failed: %v", err) } want := &dpb.Duration{Seconds: 120} if got.GetSeconds() != want.GetSeconds() { glog.Exitf("store.Read(...) = %v, want %v", got, want) } } { // Delete tx, err := s.Tx(true) if err != nil { glog.Exitf("store.Tx(true) failed: %v", err) } if err := s.DeleteTx(fakeDataType, fakeRealm, fakeUser, id, storage.LatestRev, tx); err != nil { glog.Exitf("store.DeleteTx(...) failed: %v", err) } if err := tx.Finish(); err != nil { glog.Exitf("tx.Finish() failed: %v", err) } } { // Read to check it is deleted. if err := s.Read(fakeDataType, fakeRealm, fakeUser, id, storage.LatestRev, &dpb.Duration{}); status.Code(err) != codes.NotFound { glog.Exitf("store.Read(...) = %v, want error with code %v", err, codes.NotFound) } } } func scenarioTransactionsConflictingLinearizable(ctx context.Context, c *datastore.Client) { // Scenario: two concurrent write transactions, the second to commit prevails. id := "fake-id-TransactionsConflictingLinearizable-" + uuid.New() s := dsstore.New(c, *fakeProjectID, fakeServiceName, fakeConfigPath) tx1, err := s.Tx(true) if err != nil { glog.Exitf("store.Tx(true) failed: %v", err) } tx2, err := s.Tx(true) if err != nil { glog.Exitf("store.Tx(true) failed: %v", err) } if err := s.WriteTx(fakeDataType, fakeRealm, fakeUser, id, storage.LatestRev, &dpb.Duration{Seconds: 1}, nil, tx1); err != nil { glog.Exitf("store.WriteTx(...) failed: %v", err) } if err := s.WriteTx(fakeDataType, fakeRealm, fakeUser, id, storage.LatestRev, &dpb.Duration{Seconds: 2}, nil, tx2); err != nil { glog.Exitf("store.WriteTx(...) failed: %v", err) } if err := tx1.Finish(); err != nil { glog.Exitf("tx.Finish() failed: %v", err) } if err := tx2.Finish(); err != nil { glog.Exitf("tx.Finish() failed: %v", err) } got := &dpb.Duration{} if err := s.Read(fakeDataType, fakeRealm, fakeUser, id, storage.LatestRev, got); err != nil { glog.Exitf("store.Read(...) failed: %v", err) } want := &dpb.Duration{Seconds: 2} if got.GetSeconds() != want.GetSeconds() { glog.Exitf("store.Read(...) = %v, want %v", got, want) } } func scenarioTransactionsConflictingNonLinearizable(ctx context.Context, c *datastore.Client) { // Scenario: two concurrent RMW transactions, the second to commit fails. id := "fake-id-TransactionsConflictingNonLinearizable-" + uuid.New() s := dsstore.New(c, *fakeProjectID, fakeServiceName, fakeConfigPath) tx1, err := s.Tx(true) if err != nil { glog.Exitf("store.Tx(true) failed: %v", err) } tx2, err := s.Tx(true) if err != nil { glog.Exitf("store.Tx(true) failed: %v", err) } // Read so the transactions cannot be linearized. s.ReadTx(fakeDataType, fakeRealm, fakeUser, id, storage.LatestRev, &dpb.Duration{}, tx1) if err := s.WriteTx(fakeDataType, fakeRealm, fakeUser, id, storage.LatestRev, &dpb.Duration{Seconds: 1}, nil, tx1); err != nil { glog.Exitf("store.WriteTx(...) failed: %v", err) } // Read so the transactions cannot be linearized. s.ReadTx(fakeDataType, fakeRealm, fakeUser, id, storage.LatestRev, &dpb.Duration{}, tx2) if err := s.WriteTx(fakeDataType, fakeRealm, fakeUser, id, storage.LatestRev, &dpb.Duration{Seconds: 2}, nil, tx2); err != nil { glog.Exitf("store.WriteTx(...) failed: %v", err) } if err := tx1.Finish(); err != nil { glog.Exitf("tx.Finish() failed: %v", err) } if err := tx2.Finish(); err != datastore.ErrConcurrentTransaction { glog.Exitf("tx.Finish() failed: %v", err) } got := &dpb.Duration{} if err := s.Read(fakeDataType, fakeRealm, fakeUser, id, storage.LatestRev, got); err != nil { glog.Exitf("store.Read(...) failed: %v", err) } want := &dpb.Duration{Seconds: 1} if got.GetSeconds() != want.GetSeconds() { glog.Exitf("store.Read(...) = %v, want %v", got, want) } } func scenarioTransactionsReadAfterWrite(ctx context.Context, c *datastore.Client) { // Scenario: one transaction, write followed by read, read doesn't see the write. id := "fake-id-TransactionsReadAfterWrite-" + uuid.New() s := dsstore.New(c, *fakeProjectID, fakeServiceName, fakeConfigPath) { if err := s.Write(fakeDataType, fakeRealm, fakeUser, id, storage.LatestRev, &dpb.Duration{Seconds: 60}, nil); err != nil { glog.Exitf("store.WriteTx(...) failed: %v", err) } } { tx, err := s.Tx(true) if err != nil { glog.Exitf("store.Tx(true) failed: %v", err) } e := &dpb.Duration{Seconds: 120} if err := s.WriteTx(fakeDataType, fakeRealm, fakeUser, id, storage.LatestRev, e, nil, tx); err != nil { glog.Exitf("store.WriteTx(...) failed: %v", err) } got := &dpb.Duration{} if err := s.ReadTx(fakeDataType, fakeRealm, fakeUser, id, storage.LatestRev, got, tx); err != nil { glog.Exitf("store.ReadTx(...) failed: %v", err) } want := &dpb.Duration{Seconds: 60} if got.GetSeconds() != want.GetSeconds() { glog.Exitf("store.ReadTx(...) = %v, want %v", got, want) } if err := tx.Finish(); err != nil { glog.Exitf("tx.Finish() failed: %v", err) } } { got := &dpb.Duration{} if err := s.Read(fakeDataType, fakeRealm, fakeUser, id, storage.LatestRev, got); err != nil { glog.Exitf("store.Read(...) failed: %v", err) } want := &dpb.Duration{Seconds: 120} if got.GetSeconds() != want.GetSeconds() { glog.Exitf("store.Read(...) = %v, want %v", got, want) } } }