github.com/letsencrypt/trillian@v1.1.2-0.20180615153820-ae375a99d36a/util/election/runner_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 election_test
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"math"
    21  	"sync"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/google/trillian/util"
    26  	"github.com/google/trillian/util/election"
    27  	"github.com/google/trillian/util/election/stub"
    28  )
    29  
    30  func TestShouldResign(t *testing.T) {
    31  	startTime := time.Date(1970, 9, 19, 12, 00, 00, 00, time.UTC)
    32  	var tests = []struct {
    33  		hold     time.Duration
    34  		odds     int
    35  		wantHold time.Duration
    36  		wantProb float64
    37  	}{
    38  		{hold: 12 * time.Second, odds: 10, wantHold: 12 * time.Second, wantProb: 0.1},
    39  		{hold: time.Second, odds: 10, wantHold: 10 * time.Second, wantProb: 0.1},
    40  		{hold: 10 * time.Second, odds: 0, wantHold: 10 * time.Second, wantProb: 1.0},
    41  		{hold: 10 * time.Second, odds: 1, wantHold: 10 * time.Second, wantProb: 1.0},
    42  		{hold: 10 * time.Second, odds: -1, wantHold: 10 * time.Second, wantProb: 1.0},
    43  	}
    44  	for _, test := range tests {
    45  		fakeTimeSource := util.FakeTimeSource{}
    46  		cfg := election.RunnerConfig{
    47  			MasterHoldInterval: test.hold,
    48  			ResignOdds:         test.odds,
    49  			TimeSource:         &fakeTimeSource,
    50  		}
    51  		er := election.NewRunner(6962, &cfg, nil, nil, nil)
    52  
    53  		holdChecks := int64(10)
    54  		holdNanos := test.wantHold.Nanoseconds() / holdChecks
    55  	timeslot:
    56  		for i := int64(-1); i < holdChecks; i++ {
    57  			gapNanos := time.Duration(i * holdNanos)
    58  			fakeTimeSource.Set(startTime.Add(gapNanos))
    59  			for j := 0; j < 100; j++ {
    60  				if er.ShouldResign(startTime) {
    61  					t.Errorf("shouldResign(hold=%v,odds=%v @ start+%v)=true; want false", test.hold, test.odds, gapNanos)
    62  					continue timeslot
    63  				}
    64  			}
    65  		}
    66  
    67  		fakeTimeSource.Set(startTime.Add(test.wantHold).Add(time.Nanosecond))
    68  		iterations := 10000
    69  		count := 0
    70  		for i := 0; i < iterations; i++ {
    71  			if er.ShouldResign(startTime) {
    72  				count++
    73  			}
    74  		}
    75  		got := float64(count) / float64(iterations)
    76  		deltaFraction := math.Abs(got-test.wantProb) / test.wantProb
    77  		if deltaFraction > 0.05 {
    78  			t.Errorf("P(shouldResign(hold=%v,odds=%v))=%f; want ~%f", test.hold, test.odds, got, test.wantProb)
    79  		}
    80  	}
    81  }
    82  
    83  // TODO(pavelkalinnikov): Reduce flakiness risk in this test by making fewer
    84  // time assumptions.
    85  func TestElectionRunnerRun(t *testing.T) {
    86  	fakeTimeSource := util.NewFakeTimeSource(time.Date(2016, 6, 28, 13, 40, 12, 45, time.UTC))
    87  	cfg := election.RunnerConfig{TimeSource: fakeTimeSource}
    88  	var tests = []struct {
    89  		desc       string
    90  		election   *stub.MasterElection
    91  		lostMaster bool
    92  		wantMaster bool
    93  	}{
    94  		// Basic cases
    95  		{
    96  			desc:     "IsNotMaster",
    97  			election: stub.NewMasterElection(false, nil),
    98  		},
    99  		{
   100  			desc:       "IsMaster",
   101  			election:   stub.NewMasterElection(true, nil),
   102  			wantMaster: true,
   103  		},
   104  		{
   105  			desc:       "LostMaster",
   106  			election:   stub.NewMasterElection(true, nil),
   107  			lostMaster: true,
   108  			wantMaster: false,
   109  		},
   110  		// Error cases
   111  		{
   112  			desc: "ErrorOnStart",
   113  			election: stub.NewMasterElection(false,
   114  				&stub.Errors{Start: errors.New("on start")}),
   115  		},
   116  		{
   117  			desc: "ErrorOnWait",
   118  			election: stub.NewMasterElection(false,
   119  				&stub.Errors{Wait: errors.New("on wait")}),
   120  		},
   121  		{
   122  			desc: "ErrorOnIsMaster",
   123  			election: stub.NewMasterElection(true,
   124  				&stub.Errors{IsMaster: errors.New("on IsMaster")}),
   125  			wantMaster: true,
   126  		},
   127  		{
   128  			desc: "ErrorOnResign",
   129  			election: stub.NewMasterElection(true,
   130  				&stub.Errors{Resign: errors.New("resignation failure")}),
   131  			wantMaster: true,
   132  		},
   133  	}
   134  	const logID = int64(6962)
   135  	for _, test := range tests {
   136  		t.Run(test.desc, func(t *testing.T) {
   137  			var wg sync.WaitGroup
   138  			ctx, cancel := context.WithCancel(context.Background())
   139  
   140  			startTime := time.Now()
   141  			fakeTimeSource := util.NewFakeTimeSource(startTime)
   142  
   143  			el := test.election
   144  			tracker := election.NewMasterTracker([]int64{logID}, nil)
   145  			er := election.NewRunner(logID, &cfg, tracker, nil, el)
   146  			resignations := make(chan election.Resignation, 100)
   147  			wg.Add(1)
   148  			go func() {
   149  				defer wg.Done()
   150  				er.Run(ctx, resignations)
   151  			}()
   152  			time.Sleep((3 * election.MinPreElectionPause) / 2)
   153  			if test.lostMaster {
   154  				el.Update(false, nil)
   155  			}
   156  
   157  			// Advance fake time so that shouldResign() triggers too.
   158  			fakeTimeSource.Set(startTime.Add(24 * 60 * time.Hour))
   159  			time.Sleep(election.MinMasterCheckInterval)
   160  			for len(resignations) > 0 {
   161  				r := <-resignations
   162  				r.Execute(ctx)
   163  			}
   164  
   165  			cancel()
   166  			wg.Wait()
   167  			held := tracker.Held()
   168  			if got := (len(held) > 0 && held[0] == logID); got != test.wantMaster {
   169  				t.Errorf("held=%v => master=%v; want %v", held, got, test.wantMaster)
   170  			}
   171  		})
   172  	}
   173  }