github.com/mre-fog/trillianxx@v1.1.2-0.20180615153820-ae375a99d36a/quota/etcd/etcdqm/etcdqm_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 etcdqm
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"math"
    21  	"os"
    22  	"testing"
    23  
    24  	"github.com/coreos/etcd/clientv3"
    25  	"github.com/google/trillian/quota"
    26  	"github.com/google/trillian/quota/etcd/storage"
    27  	"github.com/google/trillian/quota/etcd/storagepb"
    28  	"github.com/google/trillian/testonly/integration/etcd"
    29  	"github.com/kylelemons/godebug/pretty"
    30  )
    31  
    32  const (
    33  	treeID = 12345
    34  	userID = "llama"
    35  )
    36  
    37  var (
    38  	cfgs = &storagepb.Configs{
    39  		Configs: []*storagepb.Config{
    40  			{
    41  				Name:      "quotas/global/write/config",
    42  				State:     storagepb.Config_ENABLED,
    43  				MaxTokens: 100,
    44  				ReplenishmentStrategy: &storagepb.Config_SequencingBased{
    45  					SequencingBased: &storagepb.SequencingBasedStrategy{},
    46  				},
    47  			},
    48  			{
    49  				Name:      fmt.Sprintf("quotas/trees/%v/write/config", treeID),
    50  				State:     storagepb.Config_ENABLED,
    51  				MaxTokens: 200,
    52  				ReplenishmentStrategy: &storagepb.Config_SequencingBased{
    53  					SequencingBased: &storagepb.SequencingBasedStrategy{},
    54  				},
    55  			},
    56  			{
    57  				Name:      fmt.Sprintf("quotas/users/%v/read/config", userID),
    58  				State:     storagepb.Config_ENABLED,
    59  				MaxTokens: 1000,
    60  				ReplenishmentStrategy: &storagepb.Config_TimeBased{
    61  					TimeBased: &storagepb.TimeBasedStrategy{
    62  						ReplenishIntervalSeconds: 100,
    63  						TokensToReplenish:        1000,
    64  					},
    65  				},
    66  			},
    67  		},
    68  	}
    69  	globalWriteConfig = cfgs.Configs[0]
    70  	treeWriteConfig   = cfgs.Configs[1]
    71  	userReadConfig    = cfgs.Configs[2]
    72  
    73  	globalWriteSpec = quota.Spec{Group: quota.Global, Kind: quota.Write}
    74  	treeWriteSpec   = quota.Spec{Group: quota.Tree, Kind: quota.Write, TreeID: treeID}
    75  	userReadSpec    = quota.Spec{Group: quota.User, Kind: quota.Read, User: userID}
    76  
    77  	// client is an etcd client.
    78  	// Initialized by TestMain().
    79  	client *clientv3.Client
    80  )
    81  
    82  func TestMain(m *testing.M) {
    83  	_, c, cleanup, err := etcd.StartEtcd()
    84  	if err != nil {
    85  		panic(fmt.Sprintf("StartEtcd() returned err = %v", err))
    86  	}
    87  	client = c
    88  	exitCode := m.Run()
    89  	cleanup()
    90  	os.Exit(exitCode)
    91  }
    92  
    93  func TestManager_GetTokens(t *testing.T) {
    94  	tests := []struct {
    95  		desc      string
    96  		numTokens int
    97  		specs     []quota.Spec
    98  	}{
    99  		{
   100  			desc:      "singleSpec",
   101  			numTokens: 10,
   102  			specs:     []quota.Spec{globalWriteSpec},
   103  		},
   104  		{
   105  			desc:      "multiSpecs",
   106  			numTokens: 10,
   107  			specs:     []quota.Spec{userReadSpec, treeWriteSpec, globalWriteSpec},
   108  		},
   109  	}
   110  
   111  	qs := &storage.QuotaStorage{Client: client}
   112  	qm := New(client)
   113  
   114  	ctx := context.Background()
   115  	for _, test := range tests {
   116  		if err := reset(ctx, qs, cfgs); err != nil {
   117  			t.Fatalf("%v: reset: %v", test.desc, err)
   118  		}
   119  		differ := newQuotaDiffer(qs, test.specs)
   120  		if err := differ.snapshot(ctx); err != nil {
   121  			t.Fatalf("%v: snapshot: %v", test.desc, err)
   122  		}
   123  		if err := qm.GetTokens(ctx, test.numTokens, test.specs); err != nil {
   124  			t.Errorf("%v: GetTokens() returned err = %v", test.desc, err)
   125  			continue
   126  		}
   127  		if err := differ.assertDiff(ctx, -test.numTokens); err != nil {
   128  			t.Errorf("%v: assertDiff: %v", test.desc, err)
   129  		}
   130  	}
   131  }
   132  
   133  func TestManager_GetTokensErrors(t *testing.T) {
   134  	tests := []struct {
   135  		desc      string
   136  		numTokens int
   137  		specs     []quota.Spec
   138  	}{
   139  		{
   140  			desc:      "singleSpec",
   141  			numTokens: int(globalWriteConfig.MaxTokens) + 1,
   142  			specs:     []quota.Spec{globalWriteSpec},
   143  		},
   144  		{
   145  			desc:      "multiSpecs",
   146  			numTokens: int(min(userReadConfig.MaxTokens, treeWriteConfig.MaxTokens, globalWriteConfig.MaxTokens)) + 1,
   147  			specs:     []quota.Spec{userReadSpec, treeWriteSpec, globalWriteSpec},
   148  		},
   149  	}
   150  
   151  	qs := &storage.QuotaStorage{Client: client}
   152  	qm := New(client)
   153  
   154  	ctx := context.Background()
   155  	for _, test := range tests {
   156  		if err := reset(ctx, qs, cfgs); err != nil {
   157  			t.Fatalf("%v: reset: %v", test.desc, err)
   158  		}
   159  		if err := qm.GetTokens(ctx, test.numTokens, test.specs); err == nil {
   160  			t.Errorf("%v: GetTokens() returned err = nil, want non-nil", test.desc)
   161  		}
   162  	}
   163  }
   164  
   165  func TestManager_PeekTokens(t *testing.T) {
   166  	ctx := context.Background()
   167  	qs := &storage.QuotaStorage{Client: client}
   168  	if err := reset(ctx, qs, cfgs); err != nil {
   169  		t.Fatalf("reset() returned err = %v", err)
   170  	}
   171  
   172  	tests := []struct {
   173  		desc  string
   174  		specs []quota.Spec
   175  		want  map[quota.Spec]int
   176  	}{
   177  		{
   178  			desc:  "singleSpec",
   179  			specs: []quota.Spec{globalWriteSpec},
   180  			want: map[quota.Spec]int{
   181  				globalWriteSpec: int(globalWriteConfig.MaxTokens),
   182  			},
   183  		},
   184  		{
   185  			desc:  "multiSpecs",
   186  			specs: []quota.Spec{userReadSpec, treeWriteSpec, globalWriteSpec},
   187  			want: map[quota.Spec]int{
   188  				globalWriteSpec: int(globalWriteConfig.MaxTokens),
   189  				treeWriteSpec:   int(treeWriteConfig.MaxTokens),
   190  				userReadSpec:    int(userReadConfig.MaxTokens),
   191  			},
   192  		},
   193  	}
   194  
   195  	qm := New(client)
   196  	for _, test := range tests {
   197  		tokens, err := qm.PeekTokens(ctx, test.specs)
   198  		if err != nil {
   199  			t.Errorf("%v: PeekTokens() returned err = %v", test.desc, err)
   200  			continue
   201  		}
   202  		if diff := pretty.Compare(tokens, test.want); diff != "" {
   203  			t.Errorf("%v: post-PeekTokens() diff (-got +want):\n%v", test.desc, diff)
   204  		}
   205  	}
   206  }
   207  
   208  func TestManager_PutTokens(t *testing.T) {
   209  	ctx := context.Background()
   210  	qs := &storage.QuotaStorage{Client: client}
   211  	if err := drain(ctx, qs, cfgs); err != nil {
   212  		t.Fatalf("drain() returned err = %v", err)
   213  	}
   214  
   215  	tests := []struct {
   216  		desc      string
   217  		numTokens int
   218  		specs     []quota.Spec
   219  	}{
   220  		{
   221  			desc:      "singleSpec",
   222  			numTokens: 10,
   223  			specs:     []quota.Spec{globalWriteSpec},
   224  		},
   225  		{
   226  			desc:      "multiSpecs",
   227  			numTokens: 11,
   228  			specs:     []quota.Spec{globalWriteSpec, treeWriteSpec},
   229  		},
   230  	}
   231  
   232  	qm := New(client)
   233  	for _, test := range tests {
   234  		differ := newQuotaDiffer(qs, test.specs)
   235  		if err := differ.snapshot(ctx); err != nil {
   236  			t.Fatalf("%v: snapshot: %v", test.desc, err)
   237  		}
   238  		if err := qm.PutTokens(ctx, test.numTokens, test.specs); err != nil {
   239  			t.Errorf("%v: PutTokens() returned err = %v", test.desc, err)
   240  			continue
   241  		}
   242  		if err := differ.assertDiff(ctx, test.numTokens); err != nil {
   243  			t.Errorf("%v: assertDiff: %v", test.desc, err)
   244  		}
   245  	}
   246  }
   247  
   248  func TestManager_ResetQuota(t *testing.T) {
   249  	tests := []struct {
   250  		desc  string
   251  		specs []quota.Spec
   252  		want  map[quota.Spec]int
   253  	}{
   254  		{
   255  			desc:  "singleSpec",
   256  			specs: []quota.Spec{globalWriteSpec},
   257  			want: map[quota.Spec]int{
   258  				globalWriteSpec: int(globalWriteConfig.MaxTokens),
   259  			},
   260  		},
   261  		{
   262  			desc:  "multiSpecs",
   263  			specs: []quota.Spec{globalWriteSpec, treeWriteSpec, userReadSpec},
   264  			want: map[quota.Spec]int{
   265  				globalWriteSpec: int(globalWriteConfig.MaxTokens),
   266  				treeWriteSpec:   int(treeWriteConfig.MaxTokens),
   267  				userReadSpec:    int(userReadConfig.MaxTokens),
   268  			},
   269  		},
   270  	}
   271  
   272  	qs := &storage.QuotaStorage{Client: client}
   273  	qm := New(client)
   274  
   275  	ctx := context.Background()
   276  	for _, test := range tests {
   277  		if err := drain(ctx, qs, cfgs); err != nil {
   278  			t.Fatalf("%v: drain() returned err = %v", test.desc, err)
   279  		}
   280  		if err := qm.ResetQuota(ctx, test.specs); err != nil {
   281  			t.Errorf("%v: ResetQuota() returned err = %v", test.desc, err)
   282  			continue
   283  		}
   284  		tokens, err := qm.PeekTokens(ctx, test.specs)
   285  		if err != nil {
   286  			t.Fatalf("%v: PeekTokens() returned err = %v", test.desc, err)
   287  		}
   288  		if diff := pretty.Compare(tokens, test.want); diff != "" {
   289  			t.Errorf("%v: post-PeekTokens() diff (-got +want):\n%v", test.desc, diff)
   290  		}
   291  	}
   292  }
   293  
   294  func TestConfigName(t *testing.T) {
   295  	tests := []struct {
   296  		spec quota.Spec
   297  		want string
   298  	}{
   299  		{
   300  			spec: quota.Spec{Group: quota.Global, Kind: quota.Read},
   301  			want: "quotas/global/read/config",
   302  		},
   303  		{
   304  			spec: quota.Spec{Group: quota.Global, Kind: quota.Write},
   305  			want: "quotas/global/write/config",
   306  		},
   307  		{
   308  			spec: quota.Spec{Group: quota.Tree, Kind: quota.Read, TreeID: 10},
   309  			want: "quotas/trees/10/read/config",
   310  		},
   311  		{
   312  			spec: quota.Spec{Group: quota.Tree, Kind: quota.Write, TreeID: 11},
   313  			want: "quotas/trees/11/write/config",
   314  		},
   315  		{
   316  			spec: quota.Spec{Group: quota.User, Kind: quota.Read, User: "alpaca"},
   317  			want: "quotas/users/alpaca/read/config",
   318  		},
   319  		{
   320  			spec: quota.Spec{Group: quota.User, Kind: quota.Write, User: "llama"},
   321  			want: "quotas/users/llama/write/config",
   322  		},
   323  	}
   324  	for _, test := range tests {
   325  		if got := configName(test.spec); got != test.want {
   326  			t.Errorf("configName(%+v) = %v, want = %v", test.spec, got, test.want)
   327  		}
   328  	}
   329  }
   330  
   331  func min(nums ...int64) int64 {
   332  	ret := int64(math.MaxInt64)
   333  	for _, n := range nums {
   334  		if n < ret {
   335  			ret = n
   336  		}
   337  	}
   338  	return ret
   339  }
   340  
   341  // drain applies cfgs (as per reset()) and consumes all tokens from it.
   342  func drain(ctx context.Context, qs *storage.QuotaStorage, cfgs *storagepb.Configs) error {
   343  	if err := reset(ctx, qs, cfgs); err != nil {
   344  		return err
   345  	}
   346  	for _, cfg := range cfgs.Configs {
   347  		if err := qs.Get(ctx, []string{cfg.Name}, cfg.MaxTokens); err != nil {
   348  			return fmt.Errorf("%v: %v", cfg.Name, err)
   349  		}
   350  	}
   351  	return nil
   352  }
   353  
   354  func reset(ctx context.Context, qs *storage.QuotaStorage, cfgs *storagepb.Configs) error {
   355  	if _, err := qs.UpdateConfigs(ctx, true /* reset */, func(c *storagepb.Configs) { *c = *cfgs }); err != nil {
   356  		return fmt.Errorf("UpdateConfigs() returned err = %v", err)
   357  	}
   358  	return nil
   359  }
   360  
   361  type quotaDiffer struct {
   362  	qs     *storage.QuotaStorage
   363  	names  []string
   364  	tokens map[string]int64
   365  }
   366  
   367  func newQuotaDiffer(qs *storage.QuotaStorage, specs []quota.Spec) *quotaDiffer {
   368  	return &quotaDiffer{qs: qs, names: configNames(specs)}
   369  }
   370  
   371  func (d *quotaDiffer) snapshot(ctx context.Context) error {
   372  	var err error
   373  	d.tokens, err = d.qs.Peek(ctx, d.names)
   374  	return err
   375  }
   376  
   377  func (d *quotaDiffer) assertDiff(ctx context.Context, want int) error {
   378  	currentTokens, err := d.qs.Peek(ctx, d.names)
   379  	if err != nil {
   380  		return fmt.Errorf("Peek() returned err = %v", err)
   381  	}
   382  	want64 := int64(want)
   383  	for k, v := range currentTokens {
   384  		if got := v - d.tokens[k]; got != want64 {
   385  			return fmt.Errorf("%v has a diff of %v, want = %v", k, got, want64)
   386  		}
   387  	}
   388  	return nil
   389  }