github.com/ferranbt/nomad@v0.9.3-0.20190607002617-85c449b7667c/nomad/blocked_evals_test.go (about)

     1  package nomad
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/hashicorp/nomad/helper/testlog"
    10  	"github.com/hashicorp/nomad/nomad/mock"
    11  	"github.com/hashicorp/nomad/nomad/structs"
    12  	"github.com/hashicorp/nomad/testutil"
    13  )
    14  
    15  func testBlockedEvals(t *testing.T) (*BlockedEvals, *EvalBroker) {
    16  	broker := testBroker(t, 0)
    17  	broker.SetEnabled(true)
    18  	blocked := NewBlockedEvals(broker, testlog.HCLogger(t))
    19  	blocked.SetEnabled(true)
    20  	return blocked, broker
    21  }
    22  
    23  func TestBlockedEvals_Block_Disabled(t *testing.T) {
    24  	t.Parallel()
    25  	blocked, _ := testBlockedEvals(t)
    26  	blocked.SetEnabled(false)
    27  
    28  	// Create an escaped eval and add it to the blocked tracker.
    29  	e := mock.Eval()
    30  	e.Status = structs.EvalStatusBlocked
    31  	e.EscapedComputedClass = true
    32  	blocked.Block(e)
    33  
    34  	// Verify block did nothing
    35  	bStats := blocked.Stats()
    36  	if bStats.TotalBlocked != 0 || bStats.TotalEscaped != 0 {
    37  		t.Fatalf("bad: %#v", bStats)
    38  	}
    39  }
    40  
    41  func TestBlockedEvals_Block_SameJob(t *testing.T) {
    42  	t.Parallel()
    43  	blocked, _ := testBlockedEvals(t)
    44  
    45  	// Create two blocked evals and add them to the blocked tracker.
    46  	e := mock.Eval()
    47  	e2 := mock.Eval()
    48  	e2.JobID = e.JobID
    49  	blocked.Block(e)
    50  	blocked.Block(e2)
    51  
    52  	// Verify block did track both
    53  	bStats := blocked.Stats()
    54  	if bStats.TotalBlocked != 1 || bStats.TotalEscaped != 0 {
    55  		t.Fatalf("bad: %#v", bStats)
    56  	}
    57  }
    58  
    59  func TestBlockedEvals_Block_Quota(t *testing.T) {
    60  	t.Parallel()
    61  	blocked, _ := testBlockedEvals(t)
    62  
    63  	// Create a blocked evals on quota
    64  	e := mock.Eval()
    65  	e.QuotaLimitReached = "foo"
    66  	blocked.Block(e)
    67  
    68  	// Verify block did track both
    69  	bs := blocked.Stats()
    70  	if bs.TotalBlocked != 1 || bs.TotalEscaped != 0 || bs.TotalQuotaLimit != 1 {
    71  		t.Fatalf("bad: %#v", bs)
    72  	}
    73  }
    74  
    75  func TestBlockedEvals_Block_PriorUnblocks(t *testing.T) {
    76  	t.Parallel()
    77  	blocked, _ := testBlockedEvals(t)
    78  
    79  	// Do unblocks prior to blocking
    80  	blocked.Unblock("v1:123", 1000)
    81  	blocked.Unblock("v1:123", 1001)
    82  
    83  	// Create two blocked evals and add them to the blocked tracker.
    84  	e := mock.Eval()
    85  	e.Status = structs.EvalStatusBlocked
    86  	e.ClassEligibility = map[string]bool{"v1:123": false, "v1:456": false}
    87  	e.SnapshotIndex = 999
    88  	blocked.Block(e)
    89  
    90  	// Verify block did track both
    91  	bStats := blocked.Stats()
    92  	if bStats.TotalBlocked != 1 || bStats.TotalEscaped != 0 {
    93  		t.Fatalf("bad: %#v", bStats)
    94  	}
    95  }
    96  
    97  func TestBlockedEvals_GetDuplicates(t *testing.T) {
    98  	t.Parallel()
    99  	blocked, _ := testBlockedEvals(t)
   100  
   101  	// Create duplicate blocked evals and add them to the blocked tracker.
   102  	e := mock.Eval()
   103  	e.CreateIndex = 100
   104  	e2 := mock.Eval()
   105  	e2.JobID = e.JobID
   106  	e2.CreateIndex = 101
   107  	e3 := mock.Eval()
   108  	e3.JobID = e.JobID
   109  	e3.CreateIndex = 102
   110  	e4 := mock.Eval()
   111  	e4.JobID = e.JobID
   112  	e4.CreateIndex = 100
   113  	blocked.Block(e)
   114  	blocked.Block(e2)
   115  
   116  	// Verify stats such that we are only tracking one
   117  	bStats := blocked.Stats()
   118  	if bStats.TotalBlocked != 1 || bStats.TotalEscaped != 0 {
   119  		t.Fatalf("bad: %#v", bStats)
   120  	}
   121  
   122  	// Get the duplicates.
   123  	out := blocked.GetDuplicates(0)
   124  	if len(out) != 1 || !reflect.DeepEqual(out[0], e) {
   125  		t.Fatalf("bad: %#v %#v", out, e)
   126  	}
   127  
   128  	// Call block again after a small sleep.
   129  	go func() {
   130  		time.Sleep(500 * time.Millisecond)
   131  		blocked.Block(e3)
   132  	}()
   133  
   134  	// Get the duplicates.
   135  	out = blocked.GetDuplicates(1 * time.Second)
   136  	if len(out) != 1 || !reflect.DeepEqual(out[0], e2) {
   137  		t.Fatalf("bad: %#v %#v", out, e2)
   138  	}
   139  
   140  	// Verify stats such that we are only tracking one
   141  	bStats = blocked.Stats()
   142  	if bStats.TotalBlocked != 1 || bStats.TotalEscaped != 0 {
   143  		t.Fatalf("bad: %#v", bStats)
   144  	}
   145  
   146  	// Add an older evaluation and assert it gets cancelled
   147  	blocked.Block(e4)
   148  	out = blocked.GetDuplicates(0)
   149  	if len(out) != 1 || !reflect.DeepEqual(out[0], e4) {
   150  		t.Fatalf("bad: %#v %#v", out, e4)
   151  	}
   152  
   153  	// Verify stats such that we are only tracking one
   154  	bStats = blocked.Stats()
   155  	if bStats.TotalBlocked != 1 || bStats.TotalEscaped != 0 {
   156  		t.Fatalf("bad: %#v", bStats)
   157  	}
   158  }
   159  
   160  func TestBlockedEvals_UnblockEscaped(t *testing.T) {
   161  	t.Parallel()
   162  	blocked, broker := testBlockedEvals(t)
   163  
   164  	// Create an escaped eval and add it to the blocked tracker.
   165  	e := mock.Eval()
   166  	e.Status = structs.EvalStatusBlocked
   167  	e.EscapedComputedClass = true
   168  	blocked.Block(e)
   169  
   170  	// Verify block caused the eval to be tracked
   171  	bStats := blocked.Stats()
   172  	if bStats.TotalBlocked != 1 || bStats.TotalEscaped != 1 {
   173  		t.Fatalf("bad: %#v", bStats)
   174  	}
   175  
   176  	blocked.Unblock("v1:123", 1000)
   177  
   178  	testutil.WaitForResult(func() (bool, error) {
   179  		// Verify Unblock caused an enqueue
   180  		brokerStats := broker.Stats()
   181  		if brokerStats.TotalReady != 1 {
   182  			return false, fmt.Errorf("bad: %#v", brokerStats)
   183  		}
   184  
   185  		// Verify Unblock updates the stats
   186  		bStats := blocked.Stats()
   187  		if bStats.TotalBlocked != 0 || bStats.TotalEscaped != 0 {
   188  			return false, fmt.Errorf("bad: %#v", bStats)
   189  		}
   190  		return true, nil
   191  	}, func(err error) {
   192  		t.Fatalf("err: %s", err)
   193  	})
   194  }
   195  
   196  func TestBlockedEvals_UnblockEligible(t *testing.T) {
   197  	t.Parallel()
   198  	blocked, broker := testBlockedEvals(t)
   199  
   200  	// Create a blocked eval that is eligible on a specific node class and add
   201  	// it to the blocked tracker.
   202  	e := mock.Eval()
   203  	e.Status = structs.EvalStatusBlocked
   204  	e.ClassEligibility = map[string]bool{"v1:123": true}
   205  	blocked.Block(e)
   206  
   207  	// Verify block caused the eval to be tracked
   208  	blockedStats := blocked.Stats()
   209  	if blockedStats.TotalBlocked != 1 {
   210  		t.Fatalf("bad: %#v", blockedStats)
   211  	}
   212  
   213  	blocked.Unblock("v1:123", 1000)
   214  
   215  	testutil.WaitForResult(func() (bool, error) {
   216  		// Verify Unblock caused an enqueue
   217  		brokerStats := broker.Stats()
   218  		if brokerStats.TotalReady != 1 {
   219  			return false, fmt.Errorf("bad: %#v", brokerStats)
   220  		}
   221  
   222  		// Verify Unblock updates the stats
   223  		bStats := blocked.Stats()
   224  		if bStats.TotalBlocked != 0 || bStats.TotalEscaped != 0 {
   225  			return false, fmt.Errorf("bad: %#v", bStats)
   226  		}
   227  		return true, nil
   228  	}, func(err error) {
   229  		t.Fatalf("err: %s", err)
   230  	})
   231  }
   232  
   233  func TestBlockedEvals_UnblockIneligible(t *testing.T) {
   234  	t.Parallel()
   235  	blocked, broker := testBlockedEvals(t)
   236  
   237  	// Create a blocked eval that is ineligible on a specific node class and add
   238  	// it to the blocked tracker.
   239  	e := mock.Eval()
   240  	e.Status = structs.EvalStatusBlocked
   241  	e.ClassEligibility = map[string]bool{"v1:123": false}
   242  	blocked.Block(e)
   243  
   244  	// Verify block caused the eval to be tracked
   245  	blockedStats := blocked.Stats()
   246  	if blockedStats.TotalBlocked != 1 && blockedStats.TotalEscaped != 0 {
   247  		t.Fatalf("bad: %#v", blockedStats)
   248  	}
   249  
   250  	// Should do nothing
   251  	blocked.Unblock("v1:123", 1000)
   252  
   253  	testutil.WaitForResult(func() (bool, error) {
   254  		// Verify Unblock didn't cause an enqueue
   255  		brokerStats := broker.Stats()
   256  		if brokerStats.TotalReady != 0 {
   257  			return false, fmt.Errorf("bad: %#v", brokerStats)
   258  		}
   259  
   260  		bStats := blocked.Stats()
   261  		if bStats.TotalBlocked != 1 || bStats.TotalEscaped != 0 {
   262  			return false, fmt.Errorf("bad: %#v", bStats)
   263  		}
   264  		return true, nil
   265  	}, func(err error) {
   266  		t.Fatalf("err: %s", err)
   267  	})
   268  }
   269  
   270  func TestBlockedEvals_UnblockUnknown(t *testing.T) {
   271  	t.Parallel()
   272  	blocked, broker := testBlockedEvals(t)
   273  
   274  	// Create a blocked eval that is ineligible on a specific node class and add
   275  	// it to the blocked tracker.
   276  	e := mock.Eval()
   277  	e.Status = structs.EvalStatusBlocked
   278  	e.ClassEligibility = map[string]bool{"v1:123": true, "v1:456": false}
   279  	blocked.Block(e)
   280  
   281  	// Verify block caused the eval to be tracked
   282  	blockedStats := blocked.Stats()
   283  	if blockedStats.TotalBlocked != 1 && blockedStats.TotalEscaped != 0 {
   284  		t.Fatalf("bad: %#v", blockedStats)
   285  	}
   286  
   287  	// Should unblock because the eval hasn't seen this node class.
   288  	blocked.Unblock("v1:789", 1000)
   289  
   290  	testutil.WaitForResult(func() (bool, error) {
   291  		// Verify Unblock causes an enqueue
   292  		brokerStats := broker.Stats()
   293  		if brokerStats.TotalReady != 1 {
   294  			return false, fmt.Errorf("bad: %#v", brokerStats)
   295  		}
   296  
   297  		// Verify Unblock updates the stats
   298  		bStats := blocked.Stats()
   299  		if bStats.TotalBlocked != 0 || bStats.TotalEscaped != 0 {
   300  			return false, fmt.Errorf("bad: %#v", bStats)
   301  		}
   302  		return true, nil
   303  	}, func(err error) {
   304  		t.Fatalf("err: %s", err)
   305  	})
   306  }
   307  
   308  func TestBlockedEvals_UnblockEligible_Quota(t *testing.T) {
   309  	t.Parallel()
   310  	blocked, broker := testBlockedEvals(t)
   311  
   312  	// Create a blocked eval that is eligible for a particular quota
   313  	e := mock.Eval()
   314  	e.Status = structs.EvalStatusBlocked
   315  	e.QuotaLimitReached = "foo"
   316  	blocked.Block(e)
   317  
   318  	// Verify block caused the eval to be tracked
   319  	bs := blocked.Stats()
   320  	if bs.TotalBlocked != 1 || bs.TotalQuotaLimit != 1 {
   321  		t.Fatalf("bad: %#v", bs)
   322  	}
   323  
   324  	blocked.UnblockQuota("foo", 1000)
   325  
   326  	testutil.WaitForResult(func() (bool, error) {
   327  		// Verify Unblock caused an enqueue
   328  		brokerStats := broker.Stats()
   329  		if brokerStats.TotalReady != 1 {
   330  			return false, fmt.Errorf("bad: %#v", brokerStats)
   331  		}
   332  
   333  		// Verify Unblock updates the stats
   334  		bs := blocked.Stats()
   335  		if bs.TotalBlocked != 0 || bs.TotalEscaped != 0 || bs.TotalQuotaLimit != 0 {
   336  			return false, fmt.Errorf("bad: %#v", bs)
   337  		}
   338  		return true, nil
   339  	}, func(err error) {
   340  		t.Fatalf("err: %s", err)
   341  	})
   342  }
   343  
   344  func TestBlockedEvals_UnblockIneligible_Quota(t *testing.T) {
   345  	t.Parallel()
   346  	blocked, broker := testBlockedEvals(t)
   347  
   348  	// Create a blocked eval that is eligible on a specific quota
   349  	e := mock.Eval()
   350  	e.Status = structs.EvalStatusBlocked
   351  	e.QuotaLimitReached = "foo"
   352  	blocked.Block(e)
   353  
   354  	// Verify block caused the eval to be tracked
   355  	bs := blocked.Stats()
   356  	if bs.TotalBlocked != 1 || bs.TotalQuotaLimit != 1 {
   357  		t.Fatalf("bad: %#v", bs)
   358  	}
   359  
   360  	// Should do nothing
   361  	blocked.UnblockQuota("bar", 1000)
   362  
   363  	testutil.WaitForResult(func() (bool, error) {
   364  		// Verify Unblock didn't cause an enqueue
   365  		brokerStats := broker.Stats()
   366  		if brokerStats.TotalReady != 0 {
   367  			return false, fmt.Errorf("bad: %#v", brokerStats)
   368  		}
   369  
   370  		bs := blocked.Stats()
   371  		if bs.TotalBlocked != 1 || bs.TotalEscaped != 0 || bs.TotalQuotaLimit != 1 {
   372  			return false, fmt.Errorf("bad: %#v", bs)
   373  		}
   374  		return true, nil
   375  	}, func(err error) {
   376  		t.Fatalf("err: %s", err)
   377  	})
   378  }
   379  
   380  func TestBlockedEvals_Reblock(t *testing.T) {
   381  	t.Parallel()
   382  	blocked, broker := testBlockedEvals(t)
   383  
   384  	// Create an evaluation, Enqueue/Dequeue it to get a token
   385  	e := mock.Eval()
   386  	e.SnapshotIndex = 500
   387  	e.Status = structs.EvalStatusBlocked
   388  	e.ClassEligibility = map[string]bool{"v1:123": true, "v1:456": false}
   389  	broker.Enqueue(e)
   390  
   391  	_, token, err := broker.Dequeue([]string{e.Type}, time.Second)
   392  	if err != nil {
   393  		t.Fatalf("err: %v", err)
   394  	}
   395  
   396  	// Reblock the evaluation
   397  	blocked.Reblock(e, token)
   398  
   399  	// Verify block caused the eval to be tracked
   400  	blockedStats := blocked.Stats()
   401  	if blockedStats.TotalBlocked != 1 && blockedStats.TotalEscaped != 0 {
   402  		t.Fatalf("bad: %#v", blockedStats)
   403  	}
   404  
   405  	// Should unblock because the eval
   406  	blocked.Unblock("v1:123", 1000)
   407  
   408  	brokerStats := broker.Stats()
   409  	if brokerStats.TotalReady != 0 && brokerStats.TotalUnacked != 1 {
   410  		t.Fatalf("bad: %#v", brokerStats)
   411  	}
   412  
   413  	// Ack the evaluation which should cause the reblocked eval to transition
   414  	// to ready
   415  	if err := broker.Ack(e.ID, token); err != nil {
   416  		t.Fatalf("err: %v", err)
   417  	}
   418  
   419  	testutil.WaitForResult(func() (bool, error) {
   420  		// Verify Unblock causes an enqueue
   421  		brokerStats := broker.Stats()
   422  		if brokerStats.TotalReady != 1 {
   423  			return false, fmt.Errorf("bad: %#v", brokerStats)
   424  		}
   425  
   426  		// Verify Unblock updates the stats
   427  		bStats := blocked.Stats()
   428  		if bStats.TotalBlocked != 0 || bStats.TotalEscaped != 0 {
   429  			return false, fmt.Errorf("bad: %#v", bStats)
   430  		}
   431  		return true, nil
   432  	}, func(err error) {
   433  		t.Fatalf("err: %s", err)
   434  	})
   435  }
   436  
   437  // Test the block case in which the eval should be immediately unblocked since
   438  // it is escaped and old
   439  func TestBlockedEvals_Block_ImmediateUnblock_Escaped(t *testing.T) {
   440  	t.Parallel()
   441  	blocked, broker := testBlockedEvals(t)
   442  
   443  	// Do an unblock prior to blocking
   444  	blocked.Unblock("v1:123", 1000)
   445  
   446  	// Create a blocked eval that is eligible on a specific node class and add
   447  	// it to the blocked tracker.
   448  	e := mock.Eval()
   449  	e.Status = structs.EvalStatusBlocked
   450  	e.EscapedComputedClass = true
   451  	e.SnapshotIndex = 900
   452  	blocked.Block(e)
   453  
   454  	// Verify block caused the eval to be immediately unblocked
   455  	blockedStats := blocked.Stats()
   456  	if blockedStats.TotalBlocked != 0 && blockedStats.TotalEscaped != 0 {
   457  		t.Fatalf("bad: %#v", blockedStats)
   458  	}
   459  
   460  	testutil.WaitForResult(func() (bool, error) {
   461  		// Verify Unblock caused an enqueue
   462  		brokerStats := broker.Stats()
   463  		if brokerStats.TotalReady != 1 {
   464  			return false, fmt.Errorf("bad: %#v", brokerStats)
   465  		}
   466  
   467  		return true, nil
   468  	}, func(err error) {
   469  		t.Fatalf("err: %s", err)
   470  	})
   471  }
   472  
   473  // Test the block case in which the eval should be immediately unblocked since
   474  // there is an unblock on an unseen class that occurred while it was in the
   475  // scheduler
   476  func TestBlockedEvals_Block_ImmediateUnblock_UnseenClass_After(t *testing.T) {
   477  	t.Parallel()
   478  	blocked, broker := testBlockedEvals(t)
   479  
   480  	// Do an unblock prior to blocking
   481  	blocked.Unblock("v1:123", 1000)
   482  
   483  	// Create a blocked eval that is eligible on a specific node class and add
   484  	// it to the blocked tracker.
   485  	e := mock.Eval()
   486  	e.Status = structs.EvalStatusBlocked
   487  	e.EscapedComputedClass = false
   488  	e.SnapshotIndex = 900
   489  	blocked.Block(e)
   490  
   491  	// Verify block caused the eval to be immediately unblocked
   492  	blockedStats := blocked.Stats()
   493  	if blockedStats.TotalBlocked != 0 && blockedStats.TotalEscaped != 0 {
   494  		t.Fatalf("bad: %#v", blockedStats)
   495  	}
   496  
   497  	testutil.WaitForResult(func() (bool, error) {
   498  		// Verify Unblock caused an enqueue
   499  		brokerStats := broker.Stats()
   500  		if brokerStats.TotalReady != 1 {
   501  			return false, fmt.Errorf("bad: %#v", brokerStats)
   502  		}
   503  
   504  		return true, nil
   505  	}, func(err error) {
   506  		t.Fatalf("err: %s", err)
   507  	})
   508  }
   509  
   510  // Test the block case in which the eval should not immediately unblock since
   511  // there is an unblock on an unseen class that occurred before it was in the
   512  // scheduler
   513  func TestBlockedEvals_Block_ImmediateUnblock_UnseenClass_Before(t *testing.T) {
   514  	t.Parallel()
   515  	blocked, _ := testBlockedEvals(t)
   516  
   517  	// Do an unblock prior to blocking
   518  	blocked.Unblock("v1:123", 500)
   519  
   520  	// Create a blocked eval that is eligible on a specific node class and add
   521  	// it to the blocked tracker.
   522  	e := mock.Eval()
   523  	e.Status = structs.EvalStatusBlocked
   524  	e.EscapedComputedClass = false
   525  	e.SnapshotIndex = 900
   526  	blocked.Block(e)
   527  
   528  	// Verify block caused the eval to be immediately unblocked
   529  	blockedStats := blocked.Stats()
   530  	if blockedStats.TotalBlocked != 1 && blockedStats.TotalEscaped != 0 {
   531  		t.Fatalf("bad: %#v", blockedStats)
   532  	}
   533  }
   534  
   535  // Test the block case in which the eval should be immediately unblocked since
   536  // it a class it is eligible for has been unblocked
   537  func TestBlockedEvals_Block_ImmediateUnblock_SeenClass(t *testing.T) {
   538  	t.Parallel()
   539  	blocked, broker := testBlockedEvals(t)
   540  
   541  	// Do an unblock prior to blocking
   542  	blocked.Unblock("v1:123", 1000)
   543  
   544  	// Create a blocked eval that is eligible on a specific node class and add
   545  	// it to the blocked tracker.
   546  	e := mock.Eval()
   547  	e.Status = structs.EvalStatusBlocked
   548  	e.ClassEligibility = map[string]bool{"v1:123": true, "v1:456": false}
   549  	e.SnapshotIndex = 900
   550  	blocked.Block(e)
   551  
   552  	// Verify block caused the eval to be immediately unblocked
   553  	blockedStats := blocked.Stats()
   554  	if blockedStats.TotalBlocked != 0 && blockedStats.TotalEscaped != 0 {
   555  		t.Fatalf("bad: %#v", blockedStats)
   556  	}
   557  
   558  	testutil.WaitForResult(func() (bool, error) {
   559  		// Verify Unblock caused an enqueue
   560  		brokerStats := broker.Stats()
   561  		if brokerStats.TotalReady != 1 {
   562  			return false, fmt.Errorf("bad: %#v", brokerStats)
   563  		}
   564  
   565  		return true, nil
   566  	}, func(err error) {
   567  		t.Fatalf("err: %s", err)
   568  	})
   569  }
   570  
   571  // Test the block case in which the eval should be immediately unblocked since
   572  // it a quota has changed that it is using
   573  func TestBlockedEvals_Block_ImmediateUnblock_Quota(t *testing.T) {
   574  	t.Parallel()
   575  	blocked, broker := testBlockedEvals(t)
   576  
   577  	// Do an unblock prior to blocking
   578  	blocked.UnblockQuota("my-quota", 1000)
   579  
   580  	// Create a blocked eval that is eligible on a specific node class and add
   581  	// it to the blocked tracker.
   582  	e := mock.Eval()
   583  	e.Status = structs.EvalStatusBlocked
   584  	e.QuotaLimitReached = "my-quota"
   585  	e.SnapshotIndex = 900
   586  	blocked.Block(e)
   587  
   588  	// Verify block caused the eval to be immediately unblocked
   589  	bs := blocked.Stats()
   590  	if bs.TotalBlocked != 0 && bs.TotalEscaped != 0 && bs.TotalQuotaLimit != 0 {
   591  		t.Fatalf("bad: %#v", bs)
   592  	}
   593  
   594  	testutil.WaitForResult(func() (bool, error) {
   595  		// Verify Unblock caused an enqueue
   596  		brokerStats := broker.Stats()
   597  		if brokerStats.TotalReady != 1 {
   598  			return false, fmt.Errorf("bad: %#v", brokerStats)
   599  		}
   600  
   601  		return true, nil
   602  	}, func(err error) {
   603  		t.Fatalf("err: %s", err)
   604  	})
   605  }
   606  
   607  func TestBlockedEvals_UnblockFailed(t *testing.T) {
   608  	t.Parallel()
   609  	blocked, broker := testBlockedEvals(t)
   610  
   611  	// Create blocked evals that are due to failures
   612  	e := mock.Eval()
   613  	e.Status = structs.EvalStatusBlocked
   614  	e.TriggeredBy = structs.EvalTriggerMaxPlans
   615  	e.EscapedComputedClass = true
   616  	blocked.Block(e)
   617  
   618  	e2 := mock.Eval()
   619  	e2.Status = structs.EvalStatusBlocked
   620  	e2.TriggeredBy = structs.EvalTriggerMaxPlans
   621  	e2.ClassEligibility = map[string]bool{"v1:123": true, "v1:456": false}
   622  	blocked.Block(e2)
   623  
   624  	e3 := mock.Eval()
   625  	e3.Status = structs.EvalStatusBlocked
   626  	e3.TriggeredBy = structs.EvalTriggerMaxPlans
   627  	e3.QuotaLimitReached = "foo"
   628  	blocked.Block(e3)
   629  
   630  	// Trigger an unblock fail
   631  	blocked.UnblockFailed()
   632  
   633  	// Verify UnblockFailed caused the eval to be immediately unblocked
   634  	bs := blocked.Stats()
   635  	if bs.TotalBlocked != 0 || bs.TotalEscaped != 0 || bs.TotalQuotaLimit != 0 {
   636  		t.Fatalf("bad: %#v", bs)
   637  	}
   638  
   639  	testutil.WaitForResult(func() (bool, error) {
   640  		// Verify Unblock caused an enqueue
   641  		brokerStats := broker.Stats()
   642  		if brokerStats.TotalReady != 3 {
   643  			return false, fmt.Errorf("bad: %#v", brokerStats)
   644  		}
   645  		return true, nil
   646  	}, func(err error) {
   647  		t.Fatalf("err: %s", err)
   648  	})
   649  
   650  	// Reblock an eval for the same job and check that it gets tracked.
   651  	blocked.Block(e)
   652  	bs = blocked.Stats()
   653  	if bs.TotalBlocked != 1 || bs.TotalEscaped != 1 {
   654  		t.Fatalf("bad: %#v", bs)
   655  	}
   656  }
   657  
   658  func TestBlockedEvals_Untrack(t *testing.T) {
   659  	t.Parallel()
   660  	blocked, _ := testBlockedEvals(t)
   661  
   662  	// Create two blocked evals and add them to the blocked tracker.
   663  	e := mock.Eval()
   664  	e.Status = structs.EvalStatusBlocked
   665  	e.ClassEligibility = map[string]bool{"v1:123": false, "v1:456": false}
   666  	e.SnapshotIndex = 1000
   667  	blocked.Block(e)
   668  
   669  	// Verify block did track
   670  	bStats := blocked.Stats()
   671  	if bStats.TotalBlocked != 1 || bStats.TotalEscaped != 0 {
   672  		t.Fatalf("bad: %#v", bStats)
   673  	}
   674  
   675  	// Untrack and verify
   676  	blocked.Untrack(e.JobID, e.Namespace)
   677  	bStats = blocked.Stats()
   678  	if bStats.TotalBlocked != 0 || bStats.TotalEscaped != 0 {
   679  		t.Fatalf("bad: %#v", bStats)
   680  	}
   681  }
   682  
   683  func TestBlockedEvals_Untrack_Quota(t *testing.T) {
   684  	t.Parallel()
   685  	blocked, _ := testBlockedEvals(t)
   686  
   687  	// Create a blocked evals and add it to the blocked tracker.
   688  	e := mock.Eval()
   689  	e.Status = structs.EvalStatusBlocked
   690  	e.QuotaLimitReached = "foo"
   691  	e.SnapshotIndex = 1000
   692  	blocked.Block(e)
   693  
   694  	// Verify block did track
   695  	bs := blocked.Stats()
   696  	if bs.TotalBlocked != 1 || bs.TotalEscaped != 0 || bs.TotalQuotaLimit != 1 {
   697  		t.Fatalf("bad: %#v", bs)
   698  	}
   699  
   700  	// Untrack and verify
   701  	blocked.Untrack(e.JobID, e.Namespace)
   702  	bs = blocked.Stats()
   703  	if bs.TotalBlocked != 0 || bs.TotalEscaped != 0 || bs.TotalQuotaLimit != 0 {
   704  		t.Fatalf("bad: %#v", bs)
   705  	}
   706  }