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