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