github.com/mre-fog/trillianxx@v1.1.2-0.20180615153820-ae375a99d36a/quota/mysqlqm/mysql_quota_test.go (about)

     1  // Copyright 2017 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package mysqlqm_test
    16  
    17  import (
    18  	"context"
    19  	"crypto"
    20  	"database/sql"
    21  	"fmt"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/google/trillian"
    26  	"github.com/google/trillian/quota"
    27  	"github.com/google/trillian/quota/mysqlqm"
    28  	"github.com/google/trillian/storage"
    29  	"github.com/google/trillian/storage/mysql"
    30  	"github.com/google/trillian/storage/testdb"
    31  	"github.com/google/trillian/testonly"
    32  	"github.com/google/trillian/trees"
    33  	"github.com/google/trillian/types"
    34  	"github.com/kylelemons/godebug/pretty"
    35  
    36  	tcrypto "github.com/google/trillian/crypto"
    37  	stestonly "github.com/google/trillian/storage/testonly"
    38  )
    39  
    40  func TestQuotaManager_GetTokens(t *testing.T) {
    41  	testdb.SkipIfNoMySQL(t)
    42  	ctx := context.Background()
    43  
    44  	db, err := testdb.NewTrillianDB(ctx)
    45  	if err != nil {
    46  		t.Fatalf("GetTestDB() returned err = %v", err)
    47  	}
    48  	defer db.Close()
    49  
    50  	tree, err := createTree(ctx, db)
    51  	if err != nil {
    52  		t.Fatalf("createTree() returned err = %v", err)
    53  	}
    54  	user := (&mysqlqm.QuotaManager{}).GetUser(ctx, nil /* req */)
    55  
    56  	tests := []struct {
    57  		desc                                           string
    58  		unsequencedRows, maxUnsequencedRows, numTokens int
    59  		specs                                          []quota.Spec
    60  		wantErr                                        bool
    61  	}{
    62  		{
    63  			desc:               "globalWriteSingleToken",
    64  			unsequencedRows:    10,
    65  			maxUnsequencedRows: 20,
    66  			numTokens:          1,
    67  			specs:              []quota.Spec{{Group: quota.Global, Kind: quota.Write}},
    68  		},
    69  		{
    70  			desc:               "globalWriteMultiToken",
    71  			unsequencedRows:    10,
    72  			maxUnsequencedRows: 20,
    73  			numTokens:          5,
    74  			specs:              []quota.Spec{{Group: quota.Global, Kind: quota.Write}},
    75  		},
    76  		{
    77  			desc:               "globalWriteOverQuota1",
    78  			unsequencedRows:    20,
    79  			maxUnsequencedRows: 20,
    80  			numTokens:          1,
    81  			specs:              []quota.Spec{{Group: quota.Global, Kind: quota.Write}},
    82  			wantErr:            true,
    83  		},
    84  		{
    85  			desc:               "globalWriteOverQuota2",
    86  			unsequencedRows:    15,
    87  			maxUnsequencedRows: 20,
    88  			numTokens:          10,
    89  			specs:              []quota.Spec{{Group: quota.Global, Kind: quota.Write}},
    90  			wantErr:            true,
    91  		},
    92  		{
    93  			desc:      "unlimitedQuotas",
    94  			numTokens: 10,
    95  			specs: []quota.Spec{
    96  				{Group: quota.User, Kind: quota.Read, User: user},
    97  				{Group: quota.Tree, Kind: quota.Read, TreeID: tree.TreeId},
    98  				{Group: quota.Global, Kind: quota.Read},
    99  				{Group: quota.User, Kind: quota.Write, User: user},
   100  				{Group: quota.Tree, Kind: quota.Write, TreeID: tree.TreeId},
   101  			},
   102  		},
   103  	}
   104  
   105  	for _, test := range tests {
   106  		if err := setUnsequencedRows(ctx, db, tree, test.unsequencedRows); err != nil {
   107  			t.Errorf("setUnsequencedRows() returned err = %v", err)
   108  			continue
   109  		}
   110  
   111  		// Test general cases using select count(*) to avoid flakiness / allow for more
   112  		// precise assertions.
   113  		// See TestQuotaManager_GetTokens_InformationSchema for information schema tests.
   114  		qm := &mysqlqm.QuotaManager{DB: db, MaxUnsequencedRows: test.maxUnsequencedRows, UseSelectCount: true}
   115  		err := qm.GetTokens(ctx, test.numTokens, test.specs)
   116  		if hasErr := err == mysqlqm.ErrTooManyUnsequencedRows; hasErr != test.wantErr {
   117  			t.Errorf("%v: GetTokens() returned err = %q, wantErr = %v", test.desc, err, test.wantErr)
   118  		}
   119  	}
   120  }
   121  
   122  func TestQuotaManager_GetTokens_InformationSchema(t *testing.T) {
   123  	testdb.SkipIfNoMySQL(t)
   124  	ctx := context.Background()
   125  
   126  	maxUnsequenced := 20
   127  	globalWriteSpec := []quota.Spec{{Group: quota.Global, Kind: quota.Write}}
   128  
   129  	// Make both variants go through the test.
   130  	tests := []struct {
   131  		useSelectCount bool
   132  	}{
   133  		{useSelectCount: true},
   134  		{useSelectCount: false},
   135  	}
   136  	for _, test := range tests {
   137  		desc := fmt.Sprintf("useSelectCount = %v", test.useSelectCount)
   138  		t.Run(desc, func(t *testing.T) {
   139  			db, err := testdb.NewTrillianDB(ctx)
   140  			if err != nil {
   141  				t.Fatalf("NewTrillianDB() returned err = %v", err)
   142  			}
   143  			defer db.Close()
   144  
   145  			tree, err := createTree(ctx, db)
   146  			if err != nil {
   147  				t.Fatalf("createTree() returned err = %v", err)
   148  			}
   149  
   150  			qm := &mysqlqm.QuotaManager{DB: db, MaxUnsequencedRows: maxUnsequenced, UseSelectCount: test.useSelectCount}
   151  
   152  			// All GetTokens() calls where leaves < maxUnsequenced should succeed:
   153  			// information_schema may be outdated, but it should refer to a valid point in the
   154  			// past.
   155  			for i := 0; i < maxUnsequenced-1; i++ {
   156  				if err := queueLeaves(ctx, db, tree, i /* firstID */, 1 /* num */); err != nil {
   157  					t.Fatalf("queueLeaves() returned err = %v", err)
   158  				}
   159  				if err := qm.GetTokens(ctx, 1 /* numTokens */, globalWriteSpec); err != nil {
   160  					t.Errorf("GetTokens() returned err = %v (%v leaves)", err, i+1)
   161  				}
   162  			}
   163  
   164  			// Make leaves = maxUnsequenced
   165  			if err := queueLeaves(ctx, db, tree, maxUnsequenced-1 /* firstID */, 1 /* num */); err != nil {
   166  				t.Fatalf("queueLeaves() returned err = %v", err)
   167  			}
   168  
   169  			// Allow some time for information_schema to "catch up".
   170  			stop := false
   171  			timeout := time.After(1 * time.Second)
   172  			for !stop {
   173  				select {
   174  				case <-timeout:
   175  					t.Errorf("timed out")
   176  					stop = true
   177  				default:
   178  					// An error means that GetTokens is working correctly
   179  					stop = qm.GetTokens(ctx, 1 /* numTokens */, globalWriteSpec) == mysqlqm.ErrTooManyUnsequencedRows
   180  				}
   181  			}
   182  		})
   183  	}
   184  }
   185  
   186  func TestQuotaManager_PeekTokens(t *testing.T) {
   187  	testdb.SkipIfNoMySQL(t)
   188  	ctx := context.Background()
   189  
   190  	db, err := testdb.NewTrillianDB(ctx)
   191  	if err != nil {
   192  		t.Fatalf("GetTestDB() returned err = %v", err)
   193  	}
   194  	defer db.Close()
   195  
   196  	tree, err := createTree(ctx, db)
   197  	if err != nil {
   198  		t.Fatalf("createTree() returned err = %v", err)
   199  	}
   200  
   201  	unsequencedRows := 10
   202  	maxUnsequencedRows := 1000
   203  	wantRows := maxUnsequencedRows - unsequencedRows
   204  	if err := setUnsequencedRows(ctx, db, tree, unsequencedRows); err != nil {
   205  		t.Fatalf("setUnsequencedRows() returned err = %v", err)
   206  	}
   207  
   208  	// Test using select count(*) to allow for precise assertions without flakiness.
   209  	qm := &mysqlqm.QuotaManager{DB: db, MaxUnsequencedRows: maxUnsequencedRows, UseSelectCount: true}
   210  	specs := allSpecs(ctx, qm, tree.TreeId)
   211  	tokens, err := qm.PeekTokens(ctx, specs)
   212  	if err != nil {
   213  		t.Fatalf("PeekTokens() returned err = %v", err)
   214  	}
   215  
   216  	// All specs but Global/Write are infinite
   217  	wantTokens := make(map[quota.Spec]int)
   218  	for _, spec := range specs {
   219  		wantTokens[spec] = quota.MaxTokens
   220  	}
   221  	wantTokens[quota.Spec{Group: quota.Global, Kind: quota.Write}] = wantRows
   222  
   223  	if diff := pretty.Compare(tokens, wantTokens); diff != "" {
   224  		t.Errorf("post-PeekTokens() diff:\n%v", diff)
   225  	}
   226  }
   227  
   228  func TestQuotaManager_Noops(t *testing.T) {
   229  	testdb.SkipIfNoMySQL(t)
   230  	ctx := context.Background()
   231  
   232  	db, err := testdb.NewTrillianDB(ctx)
   233  	if err != nil {
   234  		t.Fatalf("GetTestDB() returned err = %v", err)
   235  	}
   236  	defer db.Close()
   237  
   238  	qm := &mysqlqm.QuotaManager{DB: db, MaxUnsequencedRows: 1000}
   239  	specs := allSpecs(ctx, qm, 10 /* treeID */)
   240  
   241  	tests := []struct {
   242  		desc string
   243  		fn   func() error
   244  	}{
   245  		{
   246  			desc: "GetUser",
   247  			fn: func() error {
   248  				_ = qm.GetUser(ctx, nil /* req */)
   249  				return nil
   250  			},
   251  		},
   252  		{
   253  			desc: "PutTokens",
   254  			fn: func() error {
   255  				return qm.PutTokens(ctx, 10 /* numTokens */, specs)
   256  			},
   257  		},
   258  		{
   259  			desc: "ResetQuota",
   260  			fn: func() error {
   261  				return qm.ResetQuota(ctx, specs)
   262  			},
   263  		},
   264  	}
   265  	for _, test := range tests {
   266  		if err := test.fn(); err != nil {
   267  			t.Errorf("%v: got err = %v", test.desc, err)
   268  		}
   269  	}
   270  }
   271  
   272  func allSpecs(ctx context.Context, qm quota.Manager, treeID int64) []quota.Spec {
   273  	user := qm.GetUser(ctx, nil /* req */)
   274  	return []quota.Spec{
   275  		{Group: quota.User, Kind: quota.Read, User: user},
   276  		{Group: quota.Tree, Kind: quota.Read, TreeID: treeID},
   277  		{Group: quota.Global, Kind: quota.Read},
   278  		{Group: quota.User, Kind: quota.Write, User: user},
   279  		{Group: quota.Tree, Kind: quota.Write, TreeID: treeID},
   280  		{Group: quota.Global, Kind: quota.Write},
   281  	}
   282  }
   283  
   284  func countUnsequenced(ctx context.Context, db *sql.DB) (int, error) {
   285  	var count int
   286  	if err := db.QueryRowContext(ctx, "SELECT COUNT(*) FROM Unsequenced").Scan(&count); err != nil {
   287  		return 0, err
   288  	}
   289  	return count, nil
   290  }
   291  
   292  func createTree(ctx context.Context, db *sql.DB) (*trillian.Tree, error) {
   293  	var tree *trillian.Tree
   294  
   295  	{
   296  		as := mysql.NewAdminStorage(db)
   297  		err := as.ReadWriteTransaction(ctx, func(ctx context.Context, tx storage.AdminTX) error {
   298  			var err error
   299  			tree, err = tx.CreateTree(ctx, stestonly.LogTree)
   300  			return err
   301  		})
   302  		if err != nil {
   303  			return nil, err
   304  		}
   305  	}
   306  
   307  	{
   308  		ls := mysql.NewLogStorage(db, nil)
   309  		err := ls.ReadWriteTransaction(ctx, tree, func(ctx context.Context, tx storage.LogTreeTX) error {
   310  			signer := tcrypto.NewSigner(0, testonly.NewSignerWithFixedSig(nil, []byte("notempty")), crypto.SHA256)
   311  			slr, err := signer.SignLogRoot(&types.LogRootV1{RootHash: []byte{0}})
   312  			if err != nil {
   313  				return err
   314  			}
   315  			return tx.StoreSignedLogRoot(ctx, *slr)
   316  		})
   317  		if err != nil {
   318  			return nil, err
   319  		}
   320  	}
   321  
   322  	return tree, nil
   323  }
   324  
   325  func queueLeaves(ctx context.Context, db *sql.DB, tree *trillian.Tree, firstID, num int) error {
   326  	hasherFn, err := trees.Hash(tree)
   327  	if err != nil {
   328  		return err
   329  	}
   330  	hasher := hasherFn.New()
   331  
   332  	leaves := []*trillian.LogLeaf{}
   333  	for i := 0; i < num; i++ {
   334  		value := []byte(fmt.Sprintf("leaf-%v", firstID+i))
   335  		hasher.Reset()
   336  		if _, err := hasher.Write(value); err != nil {
   337  			return err
   338  		}
   339  		hash := hasher.Sum(nil)
   340  		leaves = append(leaves, &trillian.LogLeaf{
   341  			MerkleLeafHash:   hash,
   342  			LeafValue:        value,
   343  			ExtraData:        []byte("extra data"),
   344  			LeafIdentityHash: hash,
   345  		})
   346  	}
   347  
   348  	ls := mysql.NewLogStorage(db, nil)
   349  	return ls.ReadWriteTransaction(ctx, tree, func(ctx context.Context, tx storage.LogTreeTX) error {
   350  		_, err := tx.QueueLeaves(ctx, leaves, time.Now())
   351  		return err
   352  	})
   353  }
   354  
   355  func setUnsequencedRows(ctx context.Context, db *sql.DB, tree *trillian.Tree, wantRows int) error {
   356  	count, err := countUnsequenced(ctx, db)
   357  	if err != nil {
   358  		return err
   359  	}
   360  	if count == wantRows {
   361  		return nil
   362  	}
   363  
   364  	// Clear the tables and re-create leaves from scratch. It's easier than having to reason
   365  	// about duplicate entries.
   366  	if _, err := db.ExecContext(ctx, "DELETE FROM LeafData"); err != nil {
   367  		return err
   368  	}
   369  	if _, err := db.ExecContext(ctx, "DELETE FROM Unsequenced"); err != nil {
   370  		return err
   371  	}
   372  	if err := queueLeaves(ctx, db, tree, 0 /* firstID */, wantRows); err != nil {
   373  		return err
   374  	}
   375  
   376  	// Sanity check the final count
   377  	count, err = countUnsequenced(ctx, db)
   378  	if err != nil {
   379  		return err
   380  	}
   381  	if count != wantRows {
   382  		return fmt.Errorf("got %v unsequenced rows, want = %v", count, wantRows)
   383  	}
   384  
   385  	return nil
   386  }