github.com/dkerwin/nomad@v0.3.3-0.20160525181927-74554135514b/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  // Test the block case in which the eval should be immediately unblocked since
   259  // it is escaped and old
   260  func TestBlockedEvals_Block_ImmediateUnblock_Escaped(t *testing.T) {
   261  	blocked, broker := testBlockedEvals(t)
   262  
   263  	// Do an unblock prior to blocking
   264  	blocked.Unblock("v1:123", 1000)
   265  
   266  	// Create a blocked eval that is eligible on a specific node class and add
   267  	// it to the blocked tracker.
   268  	e := mock.Eval()
   269  	e.Status = structs.EvalStatusBlocked
   270  	e.EscapedComputedClass = true
   271  	e.SnapshotIndex = 900
   272  	blocked.Block(e)
   273  
   274  	// Verify block caused the eval to be immediately unblocked
   275  	blockedStats := blocked.Stats()
   276  	if blockedStats.TotalBlocked != 0 && blockedStats.TotalEscaped != 0 {
   277  		t.Fatalf("bad: %#v", blockedStats)
   278  	}
   279  
   280  	testutil.WaitForResult(func() (bool, error) {
   281  		// Verify Unblock caused an enqueue
   282  		brokerStats := broker.Stats()
   283  		if brokerStats.TotalReady != 1 {
   284  			return false, fmt.Errorf("bad: %#v", brokerStats)
   285  		}
   286  
   287  		return true, nil
   288  	}, func(err error) {
   289  		t.Fatalf("err: %s", err)
   290  	})
   291  }
   292  
   293  // Test the block case in which the eval should be immediately unblocked since
   294  // it there is an unblock on an unseen class
   295  func TestBlockedEvals_Block_ImmediateUnblock_UnseenClass(t *testing.T) {
   296  	blocked, broker := testBlockedEvals(t)
   297  
   298  	// Do an unblock prior to blocking
   299  	blocked.Unblock("v1:123", 1000)
   300  
   301  	// Create a blocked eval that is eligible on a specific node class and add
   302  	// it to the blocked tracker.
   303  	e := mock.Eval()
   304  	e.Status = structs.EvalStatusBlocked
   305  	e.EscapedComputedClass = false
   306  	e.SnapshotIndex = 900
   307  	blocked.Block(e)
   308  
   309  	// Verify block caused the eval to be immediately unblocked
   310  	blockedStats := blocked.Stats()
   311  	if blockedStats.TotalBlocked != 0 && blockedStats.TotalEscaped != 0 {
   312  		t.Fatalf("bad: %#v", blockedStats)
   313  	}
   314  
   315  	testutil.WaitForResult(func() (bool, error) {
   316  		// Verify Unblock caused an enqueue
   317  		brokerStats := broker.Stats()
   318  		if brokerStats.TotalReady != 1 {
   319  			return false, fmt.Errorf("bad: %#v", brokerStats)
   320  		}
   321  
   322  		return true, nil
   323  	}, func(err error) {
   324  		t.Fatalf("err: %s", err)
   325  	})
   326  }
   327  
   328  // Test the block case in which the eval should be immediately unblocked since
   329  // it a class it is eligible for has been unblocked
   330  func TestBlockedEvals_Block_ImmediateUnblock_SeenClass(t *testing.T) {
   331  	blocked, broker := testBlockedEvals(t)
   332  
   333  	// Do an unblock prior to blocking
   334  	blocked.Unblock("v1:123", 1000)
   335  
   336  	// Create a blocked eval that is eligible on a specific node class and add
   337  	// it to the blocked tracker.
   338  	e := mock.Eval()
   339  	e.Status = structs.EvalStatusBlocked
   340  	e.ClassEligibility = map[string]bool{"v1:123": true, "v1:456": false}
   341  	e.SnapshotIndex = 900
   342  	blocked.Block(e)
   343  
   344  	// Verify block caused the eval to be immediately unblocked
   345  	blockedStats := blocked.Stats()
   346  	if blockedStats.TotalBlocked != 0 && blockedStats.TotalEscaped != 0 {
   347  		t.Fatalf("bad: %#v", blockedStats)
   348  	}
   349  
   350  	testutil.WaitForResult(func() (bool, error) {
   351  		// Verify Unblock caused an enqueue
   352  		brokerStats := broker.Stats()
   353  		if brokerStats.TotalReady != 1 {
   354  			return false, fmt.Errorf("bad: %#v", brokerStats)
   355  		}
   356  
   357  		return true, nil
   358  	}, func(err error) {
   359  		t.Fatalf("err: %s", err)
   360  	})
   361  }
   362  
   363  func TestBlockedEvals_UnblockFailed(t *testing.T) {
   364  	blocked, broker := testBlockedEvals(t)
   365  
   366  	// Create blocked evals that are due to failures
   367  	e := mock.Eval()
   368  	e.Status = structs.EvalStatusBlocked
   369  	e.TriggeredBy = structs.EvalTriggerMaxPlans
   370  	e.EscapedComputedClass = true
   371  	blocked.Block(e)
   372  
   373  	e2 := mock.Eval()
   374  	e2.Status = structs.EvalStatusBlocked
   375  	e2.TriggeredBy = structs.EvalTriggerMaxPlans
   376  	e2.ClassEligibility = map[string]bool{"v1:123": true, "v1:456": false}
   377  	blocked.Block(e2)
   378  
   379  	// Trigger an unblock fail
   380  	blocked.UnblockFailed()
   381  
   382  	testutil.WaitForResult(func() (bool, error) {
   383  		// Verify Unblock caused an enqueue
   384  		brokerStats := broker.Stats()
   385  		if brokerStats.TotalReady != 2 {
   386  			return false, fmt.Errorf("bad: %#v", brokerStats)
   387  		}
   388  		return true, nil
   389  	}, func(err error) {
   390  		t.Fatalf("err: %s", err)
   391  	})
   392  }