github.com/jrxfive/nomad@v0.6.1-0.20170802162750-1fef470e89bf/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_PriorUnblocks(t *testing.T) {
    59  	t.Parallel()
    60  	blocked, _ := testBlockedEvals(t)
    61  
    62  	// Do unblocks prior to blocking
    63  	blocked.Unblock("v1:123", 1000)
    64  	blocked.Unblock("v1:123", 1001)
    65  
    66  	// Create two blocked evals and add them to the blocked tracker.
    67  	e := mock.Eval()
    68  	e.Status = structs.EvalStatusBlocked
    69  	e.ClassEligibility = map[string]bool{"v1:123": false, "v1:456": false}
    70  	e.SnapshotIndex = 999
    71  	blocked.Block(e)
    72  
    73  	// Verify block did track both
    74  	bStats := blocked.Stats()
    75  	if bStats.TotalBlocked != 1 || bStats.TotalEscaped != 0 {
    76  		t.Fatalf("bad: %#v", bStats)
    77  	}
    78  }
    79  
    80  func TestBlockedEvals_GetDuplicates(t *testing.T) {
    81  	t.Parallel()
    82  	blocked, _ := testBlockedEvals(t)
    83  
    84  	// Create duplicate blocked evals and add them to the blocked tracker.
    85  	e := mock.Eval()
    86  	e2 := mock.Eval()
    87  	e2.JobID = e.JobID
    88  	e3 := mock.Eval()
    89  	e3.JobID = e.JobID
    90  	blocked.Block(e)
    91  	blocked.Block(e2)
    92  
    93  	// Verify block did track both
    94  	bStats := blocked.Stats()
    95  	if bStats.TotalBlocked != 1 || bStats.TotalEscaped != 0 {
    96  		t.Fatalf("bad: %#v", bStats)
    97  	}
    98  
    99  	// Get the duplicates.
   100  	out := blocked.GetDuplicates(0)
   101  	if len(out) != 1 || !reflect.DeepEqual(out[0], e2) {
   102  		t.Fatalf("bad: %#v %#v", out, e2)
   103  	}
   104  
   105  	// Call block again after a small sleep.
   106  	go func() {
   107  		time.Sleep(500 * time.Millisecond)
   108  		blocked.Block(e3)
   109  	}()
   110  
   111  	// Get the duplicates.
   112  	out = blocked.GetDuplicates(1 * time.Second)
   113  	if len(out) != 1 || !reflect.DeepEqual(out[0], e3) {
   114  		t.Fatalf("bad: %#v %#v", out, e2)
   115  	}
   116  }
   117  
   118  func TestBlockedEvals_UnblockEscaped(t *testing.T) {
   119  	t.Parallel()
   120  	blocked, broker := testBlockedEvals(t)
   121  
   122  	// Create an escaped eval and add it to the blocked tracker.
   123  	e := mock.Eval()
   124  	e.Status = structs.EvalStatusBlocked
   125  	e.EscapedComputedClass = true
   126  	blocked.Block(e)
   127  
   128  	// Verify block caused the eval to be tracked
   129  	bStats := blocked.Stats()
   130  	if bStats.TotalBlocked != 1 || bStats.TotalEscaped != 1 {
   131  		t.Fatalf("bad: %#v", bStats)
   132  	}
   133  
   134  	blocked.Unblock("v1:123", 1000)
   135  
   136  	testutil.WaitForResult(func() (bool, error) {
   137  		// Verify Unblock caused an enqueue
   138  		brokerStats := broker.Stats()
   139  		if brokerStats.TotalReady != 1 {
   140  			return false, fmt.Errorf("bad: %#v", brokerStats)
   141  		}
   142  
   143  		// Verify Unblock updates the stats
   144  		bStats := blocked.Stats()
   145  		if bStats.TotalBlocked != 0 || bStats.TotalEscaped != 0 {
   146  			return false, fmt.Errorf("bad: %#v", bStats)
   147  		}
   148  		return true, nil
   149  	}, func(err error) {
   150  		t.Fatalf("err: %s", err)
   151  	})
   152  }
   153  
   154  func TestBlockedEvals_UnblockEligible(t *testing.T) {
   155  	t.Parallel()
   156  	blocked, broker := testBlockedEvals(t)
   157  
   158  	// Create a blocked eval that is eligible on a specific node class and add
   159  	// it to the blocked tracker.
   160  	e := mock.Eval()
   161  	e.Status = structs.EvalStatusBlocked
   162  	e.ClassEligibility = map[string]bool{"v1:123": true}
   163  	blocked.Block(e)
   164  
   165  	// Verify block caused the eval to be tracked
   166  	blockedStats := blocked.Stats()
   167  	if blockedStats.TotalBlocked != 1 {
   168  		t.Fatalf("bad: %#v", blockedStats)
   169  	}
   170  
   171  	blocked.Unblock("v1:123", 1000)
   172  
   173  	testutil.WaitForResult(func() (bool, error) {
   174  		// Verify Unblock caused an enqueue
   175  		brokerStats := broker.Stats()
   176  		if brokerStats.TotalReady != 1 {
   177  			return false, fmt.Errorf("bad: %#v", brokerStats)
   178  		}
   179  
   180  		// Verify Unblock updates the stats
   181  		bStats := blocked.Stats()
   182  		if bStats.TotalBlocked != 0 || bStats.TotalEscaped != 0 {
   183  			return false, fmt.Errorf("bad: %#v", bStats)
   184  		}
   185  		return true, nil
   186  	}, func(err error) {
   187  		t.Fatalf("err: %s", err)
   188  	})
   189  }
   190  
   191  func TestBlockedEvals_UnblockIneligible(t *testing.T) {
   192  	t.Parallel()
   193  	blocked, broker := testBlockedEvals(t)
   194  
   195  	// Create a blocked eval that is ineligible on a specific node class and add
   196  	// it to the blocked tracker.
   197  	e := mock.Eval()
   198  	e.Status = structs.EvalStatusBlocked
   199  	e.ClassEligibility = map[string]bool{"v1:123": false}
   200  	blocked.Block(e)
   201  
   202  	// Verify block caused the eval to be tracked
   203  	blockedStats := blocked.Stats()
   204  	if blockedStats.TotalBlocked != 1 && blockedStats.TotalEscaped != 0 {
   205  		t.Fatalf("bad: %#v", blockedStats)
   206  	}
   207  
   208  	// Should do nothing
   209  	blocked.Unblock("v1:123", 1000)
   210  
   211  	testutil.WaitForResult(func() (bool, error) {
   212  		// Verify Unblock didn't cause an enqueue
   213  		brokerStats := broker.Stats()
   214  		if brokerStats.TotalReady != 0 {
   215  			return false, fmt.Errorf("bad: %#v", brokerStats)
   216  		}
   217  
   218  		bStats := blocked.Stats()
   219  		if bStats.TotalBlocked != 1 || bStats.TotalEscaped != 0 {
   220  			return false, fmt.Errorf("bad: %#v", bStats)
   221  		}
   222  		return true, nil
   223  	}, func(err error) {
   224  		t.Fatalf("err: %s", err)
   225  	})
   226  }
   227  
   228  func TestBlockedEvals_UnblockUnknown(t *testing.T) {
   229  	t.Parallel()
   230  	blocked, broker := testBlockedEvals(t)
   231  
   232  	// Create a blocked eval that is ineligible on a specific node class and add
   233  	// it to the blocked tracker.
   234  	e := mock.Eval()
   235  	e.Status = structs.EvalStatusBlocked
   236  	e.ClassEligibility = map[string]bool{"v1:123": true, "v1:456": false}
   237  	blocked.Block(e)
   238  
   239  	// Verify block caused the eval to be tracked
   240  	blockedStats := blocked.Stats()
   241  	if blockedStats.TotalBlocked != 1 && blockedStats.TotalEscaped != 0 {
   242  		t.Fatalf("bad: %#v", blockedStats)
   243  	}
   244  
   245  	// Should unblock because the eval hasn't seen this node class.
   246  	blocked.Unblock("v1:789", 1000)
   247  
   248  	testutil.WaitForResult(func() (bool, error) {
   249  		// Verify Unblock causes an enqueue
   250  		brokerStats := broker.Stats()
   251  		if brokerStats.TotalReady != 1 {
   252  			return false, fmt.Errorf("bad: %#v", brokerStats)
   253  		}
   254  
   255  		// Verify Unblock updates the stats
   256  		bStats := blocked.Stats()
   257  		if bStats.TotalBlocked != 0 || bStats.TotalEscaped != 0 {
   258  			return false, fmt.Errorf("bad: %#v", bStats)
   259  		}
   260  		return true, nil
   261  	}, func(err error) {
   262  		t.Fatalf("err: %s", err)
   263  	})
   264  }
   265  
   266  func TestBlockedEvals_Reblock(t *testing.T) {
   267  	t.Parallel()
   268  	blocked, broker := testBlockedEvals(t)
   269  
   270  	// Create an evaluation, Enqueue/Dequeue it to get a token
   271  	e := mock.Eval()
   272  	e.SnapshotIndex = 500
   273  	e.Status = structs.EvalStatusBlocked
   274  	e.ClassEligibility = map[string]bool{"v1:123": true, "v1:456": false}
   275  	broker.Enqueue(e)
   276  
   277  	_, token, err := broker.Dequeue([]string{e.Type}, time.Second)
   278  	if err != nil {
   279  		t.Fatalf("err: %v", err)
   280  	}
   281  
   282  	// Reblock the evaluation
   283  	blocked.Reblock(e, token)
   284  
   285  	// Verify block caused the eval to be tracked
   286  	blockedStats := blocked.Stats()
   287  	if blockedStats.TotalBlocked != 1 && blockedStats.TotalEscaped != 0 {
   288  		t.Fatalf("bad: %#v", blockedStats)
   289  	}
   290  
   291  	// Should unblock because the eval
   292  	blocked.Unblock("v1:123", 1000)
   293  
   294  	brokerStats := broker.Stats()
   295  	if brokerStats.TotalReady != 0 && brokerStats.TotalUnacked != 1 {
   296  		t.Fatalf("bad: %#v", brokerStats)
   297  	}
   298  
   299  	// Ack the evaluation which should cause the reblocked eval to transistion
   300  	// to ready
   301  	if err := broker.Ack(e.ID, token); err != nil {
   302  		t.Fatalf("err: %v", err)
   303  	}
   304  
   305  	testutil.WaitForResult(func() (bool, error) {
   306  		// Verify Unblock causes an enqueue
   307  		brokerStats := broker.Stats()
   308  		if brokerStats.TotalReady != 1 {
   309  			return false, fmt.Errorf("bad: %#v", brokerStats)
   310  		}
   311  
   312  		// Verify Unblock updates the stats
   313  		bStats := blocked.Stats()
   314  		if bStats.TotalBlocked != 0 || bStats.TotalEscaped != 0 {
   315  			return false, fmt.Errorf("bad: %#v", bStats)
   316  		}
   317  		return true, nil
   318  	}, func(err error) {
   319  		t.Fatalf("err: %s", err)
   320  	})
   321  }
   322  
   323  // Test the block case in which the eval should be immediately unblocked since
   324  // it is escaped and old
   325  func TestBlockedEvals_Block_ImmediateUnblock_Escaped(t *testing.T) {
   326  	t.Parallel()
   327  	blocked, broker := testBlockedEvals(t)
   328  
   329  	// Do an unblock prior to blocking
   330  	blocked.Unblock("v1:123", 1000)
   331  
   332  	// Create a blocked eval that is eligible on a specific node class and add
   333  	// it to the blocked tracker.
   334  	e := mock.Eval()
   335  	e.Status = structs.EvalStatusBlocked
   336  	e.EscapedComputedClass = true
   337  	e.SnapshotIndex = 900
   338  	blocked.Block(e)
   339  
   340  	// Verify block caused the eval to be immediately unblocked
   341  	blockedStats := blocked.Stats()
   342  	if blockedStats.TotalBlocked != 0 && blockedStats.TotalEscaped != 0 {
   343  		t.Fatalf("bad: %#v", blockedStats)
   344  	}
   345  
   346  	testutil.WaitForResult(func() (bool, error) {
   347  		// Verify Unblock caused an enqueue
   348  		brokerStats := broker.Stats()
   349  		if brokerStats.TotalReady != 1 {
   350  			return false, fmt.Errorf("bad: %#v", brokerStats)
   351  		}
   352  
   353  		return true, nil
   354  	}, func(err error) {
   355  		t.Fatalf("err: %s", err)
   356  	})
   357  }
   358  
   359  // Test the block case in which the eval should be immediately unblocked since
   360  // there is an unblock on an unseen class that occurred while it was in the
   361  // scheduler
   362  func TestBlockedEvals_Block_ImmediateUnblock_UnseenClass_After(t *testing.T) {
   363  	t.Parallel()
   364  	blocked, broker := testBlockedEvals(t)
   365  
   366  	// Do an unblock prior to blocking
   367  	blocked.Unblock("v1:123", 1000)
   368  
   369  	// Create a blocked eval that is eligible on a specific node class and add
   370  	// it to the blocked tracker.
   371  	e := mock.Eval()
   372  	e.Status = structs.EvalStatusBlocked
   373  	e.EscapedComputedClass = false
   374  	e.SnapshotIndex = 900
   375  	blocked.Block(e)
   376  
   377  	// Verify block caused the eval to be immediately unblocked
   378  	blockedStats := blocked.Stats()
   379  	if blockedStats.TotalBlocked != 0 && blockedStats.TotalEscaped != 0 {
   380  		t.Fatalf("bad: %#v", blockedStats)
   381  	}
   382  
   383  	testutil.WaitForResult(func() (bool, error) {
   384  		// Verify Unblock caused an enqueue
   385  		brokerStats := broker.Stats()
   386  		if brokerStats.TotalReady != 1 {
   387  			return false, fmt.Errorf("bad: %#v", brokerStats)
   388  		}
   389  
   390  		return true, nil
   391  	}, func(err error) {
   392  		t.Fatalf("err: %s", err)
   393  	})
   394  }
   395  
   396  // Test the block case in which the eval should not immediately unblock since
   397  // there is an unblock on an unseen class that occurred before it was in the
   398  // scheduler
   399  func TestBlockedEvals_Block_ImmediateUnblock_UnseenClass_Before(t *testing.T) {
   400  	t.Parallel()
   401  	blocked, _ := testBlockedEvals(t)
   402  
   403  	// Do an unblock prior to blocking
   404  	blocked.Unblock("v1:123", 500)
   405  
   406  	// Create a blocked eval that is eligible on a specific node class and add
   407  	// it to the blocked tracker.
   408  	e := mock.Eval()
   409  	e.Status = structs.EvalStatusBlocked
   410  	e.EscapedComputedClass = false
   411  	e.SnapshotIndex = 900
   412  	blocked.Block(e)
   413  
   414  	// Verify block caused the eval to be immediately unblocked
   415  	blockedStats := blocked.Stats()
   416  	if blockedStats.TotalBlocked != 1 && blockedStats.TotalEscaped != 0 {
   417  		t.Fatalf("bad: %#v", blockedStats)
   418  	}
   419  }
   420  
   421  // Test the block case in which the eval should be immediately unblocked since
   422  // it a class it is eligible for has been unblocked
   423  func TestBlockedEvals_Block_ImmediateUnblock_SeenClass(t *testing.T) {
   424  	t.Parallel()
   425  	blocked, broker := testBlockedEvals(t)
   426  
   427  	// Do an unblock prior to blocking
   428  	blocked.Unblock("v1:123", 1000)
   429  
   430  	// Create a blocked eval that is eligible on a specific node class and add
   431  	// it to the blocked tracker.
   432  	e := mock.Eval()
   433  	e.Status = structs.EvalStatusBlocked
   434  	e.ClassEligibility = map[string]bool{"v1:123": true, "v1:456": false}
   435  	e.SnapshotIndex = 900
   436  	blocked.Block(e)
   437  
   438  	// Verify block caused the eval to be immediately unblocked
   439  	blockedStats := blocked.Stats()
   440  	if blockedStats.TotalBlocked != 0 && blockedStats.TotalEscaped != 0 {
   441  		t.Fatalf("bad: %#v", blockedStats)
   442  	}
   443  
   444  	testutil.WaitForResult(func() (bool, error) {
   445  		// Verify Unblock caused an enqueue
   446  		brokerStats := broker.Stats()
   447  		if brokerStats.TotalReady != 1 {
   448  			return false, fmt.Errorf("bad: %#v", brokerStats)
   449  		}
   450  
   451  		return true, nil
   452  	}, func(err error) {
   453  		t.Fatalf("err: %s", err)
   454  	})
   455  }
   456  
   457  func TestBlockedEvals_UnblockFailed(t *testing.T) {
   458  	t.Parallel()
   459  	blocked, broker := testBlockedEvals(t)
   460  
   461  	// Create blocked evals that are due to failures
   462  	e := mock.Eval()
   463  	e.Status = structs.EvalStatusBlocked
   464  	e.TriggeredBy = structs.EvalTriggerMaxPlans
   465  	e.EscapedComputedClass = true
   466  	blocked.Block(e)
   467  
   468  	e2 := mock.Eval()
   469  	e2.Status = structs.EvalStatusBlocked
   470  	e2.TriggeredBy = structs.EvalTriggerMaxPlans
   471  	e2.ClassEligibility = map[string]bool{"v1:123": true, "v1:456": false}
   472  	blocked.Block(e2)
   473  
   474  	// Trigger an unblock fail
   475  	blocked.UnblockFailed()
   476  
   477  	// Verify UnblockFailed caused the eval to be immediately unblocked
   478  	blockedStats := blocked.Stats()
   479  	if blockedStats.TotalBlocked != 0 && blockedStats.TotalEscaped != 0 {
   480  		t.Fatalf("bad: %#v", blockedStats)
   481  	}
   482  
   483  	testutil.WaitForResult(func() (bool, error) {
   484  		// Verify Unblock caused an enqueue
   485  		brokerStats := broker.Stats()
   486  		if brokerStats.TotalReady != 2 {
   487  			return false, fmt.Errorf("bad: %#v", brokerStats)
   488  		}
   489  		return true, nil
   490  	}, func(err error) {
   491  		t.Fatalf("err: %s", err)
   492  	})
   493  
   494  	// Reblock an eval for the same job and check that it gets tracked.
   495  	blocked.Block(e)
   496  	blockedStats = blocked.Stats()
   497  	if blockedStats.TotalBlocked != 1 && blockedStats.TotalEscaped != 1 {
   498  		t.Fatalf("bad: %#v", blockedStats)
   499  	}
   500  }
   501  
   502  func TestBlockedEvals_Untrack(t *testing.T) {
   503  	t.Parallel()
   504  	blocked, _ := testBlockedEvals(t)
   505  
   506  	// Create two blocked evals and add them to the blocked tracker.
   507  	e := mock.Eval()
   508  	e.Status = structs.EvalStatusBlocked
   509  	e.ClassEligibility = map[string]bool{"v1:123": false, "v1:456": false}
   510  	e.SnapshotIndex = 1000
   511  	blocked.Block(e)
   512  
   513  	// Verify block did track
   514  	bStats := blocked.Stats()
   515  	if bStats.TotalBlocked != 1 || bStats.TotalEscaped != 0 {
   516  		t.Fatalf("bad: %#v", bStats)
   517  	}
   518  
   519  	// Untrack and verify
   520  	blocked.Untrack(e.JobID)
   521  	bStats = blocked.Stats()
   522  	if bStats.TotalBlocked != 0 || bStats.TotalEscaped != 0 {
   523  		t.Fatalf("bad: %#v", bStats)
   524  	}
   525  }