github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/client/allocrunner/task_hook_coordinator_test.go (about)

     1  package allocrunner
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/stretchr/testify/require"
     9  
    10  	"github.com/hashicorp/nomad/client/allocrunner/taskrunner"
    11  	"github.com/hashicorp/nomad/nomad/structs"
    12  
    13  	"github.com/hashicorp/nomad/helper/testlog"
    14  	"github.com/hashicorp/nomad/nomad/mock"
    15  )
    16  
    17  func TestTaskHookCoordinator_OnlyMainApp(t *testing.T) {
    18  	alloc := mock.Alloc()
    19  	tasks := alloc.Job.TaskGroups[0].Tasks
    20  	task := tasks[0]
    21  	logger := testlog.HCLogger(t)
    22  
    23  	coord := newTaskHookCoordinator(logger, tasks)
    24  
    25  	ch := coord.startConditionForTask(task)
    26  
    27  	require.Truef(t, isChannelClosed(ch), "%s channel was open, should be closed", task.Name)
    28  }
    29  
    30  func TestTaskHookCoordinator_PrestartRunsBeforeMain(t *testing.T) {
    31  	logger := testlog.HCLogger(t)
    32  
    33  	alloc := mock.LifecycleAlloc()
    34  	tasks := alloc.Job.TaskGroups[0].Tasks
    35  
    36  	mainTask := tasks[0]
    37  	sideTask := tasks[1]
    38  	initTask := tasks[2]
    39  
    40  	coord := newTaskHookCoordinator(logger, tasks)
    41  	initCh := coord.startConditionForTask(initTask)
    42  	sideCh := coord.startConditionForTask(sideTask)
    43  	mainCh := coord.startConditionForTask(mainTask)
    44  
    45  	require.Truef(t, isChannelClosed(initCh), "%s channel was open, should be closed", initTask.Name)
    46  	require.Truef(t, isChannelClosed(sideCh), "%s channel was open, should be closed", sideTask.Name)
    47  	require.Falsef(t, isChannelClosed(mainCh), "%s channel was closed, should be open", mainTask.Name)
    48  }
    49  
    50  func TestTaskHookCoordinator_MainRunsAfterPrestart(t *testing.T) {
    51  	logger := testlog.HCLogger(t)
    52  
    53  	alloc := mock.LifecycleAlloc()
    54  	tasks := alloc.Job.TaskGroups[0].Tasks
    55  
    56  	mainTask := tasks[0]
    57  	sideTask := tasks[1]
    58  	initTask := tasks[2]
    59  
    60  	coord := newTaskHookCoordinator(logger, tasks)
    61  	initCh := coord.startConditionForTask(initTask)
    62  	sideCh := coord.startConditionForTask(sideTask)
    63  	mainCh := coord.startConditionForTask(mainTask)
    64  
    65  	require.Truef(t, isChannelClosed(initCh), "%s channel was open, should be closed", initTask.Name)
    66  	require.Truef(t, isChannelClosed(sideCh), "%s channel was open, should be closed", sideTask.Name)
    67  	require.Falsef(t, isChannelClosed(mainCh), "%s channel was closed, should be open", mainTask.Name)
    68  
    69  	states := map[string]*structs.TaskState{
    70  		mainTask.Name: {
    71  			State:  structs.TaskStatePending,
    72  			Failed: false,
    73  		},
    74  		initTask.Name: {
    75  			State:      structs.TaskStateDead,
    76  			Failed:     false,
    77  			StartedAt:  time.Now(),
    78  			FinishedAt: time.Now(),
    79  		},
    80  		sideTask.Name: {
    81  			State:     structs.TaskStateRunning,
    82  			Failed:    false,
    83  			StartedAt: time.Now(),
    84  		},
    85  	}
    86  
    87  	coord.taskStateUpdated(states)
    88  
    89  	require.Truef(t, isChannelClosed(initCh), "%s channel was open, should be closed", initTask.Name)
    90  	require.Truef(t, isChannelClosed(sideCh), "%s channel was open, should be closed", sideTask.Name)
    91  	require.Truef(t, isChannelClosed(mainCh), "%s channel was open, should be closed", mainTask.Name)
    92  }
    93  
    94  func TestTaskHookCoordinator_MainRunsAfterManyInitTasks(t *testing.T) {
    95  	logger := testlog.HCLogger(t)
    96  
    97  	alloc := mock.LifecycleAlloc()
    98  	alloc.Job = mock.VariableLifecycleJob(structs.Resources{CPU: 100, MemoryMB: 256}, 1, 2, 0)
    99  	tasks := alloc.Job.TaskGroups[0].Tasks
   100  
   101  	mainTask := tasks[0]
   102  	init1Task := tasks[1]
   103  	init2Task := tasks[2]
   104  
   105  	coord := newTaskHookCoordinator(logger, tasks)
   106  	mainCh := coord.startConditionForTask(mainTask)
   107  	init1Ch := coord.startConditionForTask(init1Task)
   108  	init2Ch := coord.startConditionForTask(init2Task)
   109  
   110  	require.Truef(t, isChannelClosed(init1Ch), "%s channel was open, should be closed", init1Task.Name)
   111  	require.Truef(t, isChannelClosed(init2Ch), "%s channel was open, should be closed", init2Task.Name)
   112  	require.Falsef(t, isChannelClosed(mainCh), "%s channel was closed, should be open", mainTask.Name)
   113  
   114  	states := map[string]*structs.TaskState{
   115  		mainTask.Name: {
   116  			State:  structs.TaskStatePending,
   117  			Failed: false,
   118  		},
   119  		init1Task.Name: {
   120  			State:      structs.TaskStateDead,
   121  			Failed:     false,
   122  			StartedAt:  time.Now(),
   123  			FinishedAt: time.Now(),
   124  		},
   125  		init2Task.Name: {
   126  			State:     structs.TaskStateDead,
   127  			Failed:    false,
   128  			StartedAt: time.Now(),
   129  		},
   130  	}
   131  
   132  	coord.taskStateUpdated(states)
   133  
   134  	require.Truef(t, isChannelClosed(init1Ch), "%s channel was open, should be closed", init1Task.Name)
   135  	require.Truef(t, isChannelClosed(init2Ch), "%s channel was open, should be closed", init2Task.Name)
   136  	require.Truef(t, isChannelClosed(mainCh), "%s channel was open, should be closed", mainTask.Name)
   137  }
   138  
   139  func TestTaskHookCoordinator_FailedInitTask(t *testing.T) {
   140  	logger := testlog.HCLogger(t)
   141  
   142  	alloc := mock.LifecycleAlloc()
   143  	alloc.Job = mock.VariableLifecycleJob(structs.Resources{CPU: 100, MemoryMB: 256}, 1, 2, 0)
   144  	tasks := alloc.Job.TaskGroups[0].Tasks
   145  
   146  	mainTask := tasks[0]
   147  	init1Task := tasks[1]
   148  	init2Task := tasks[2]
   149  
   150  	coord := newTaskHookCoordinator(logger, tasks)
   151  	mainCh := coord.startConditionForTask(mainTask)
   152  	init1Ch := coord.startConditionForTask(init1Task)
   153  	init2Ch := coord.startConditionForTask(init2Task)
   154  
   155  	require.Truef(t, isChannelClosed(init1Ch), "%s channel was open, should be closed", init1Task.Name)
   156  	require.Truef(t, isChannelClosed(init2Ch), "%s channel was open, should be closed", init2Task.Name)
   157  	require.Falsef(t, isChannelClosed(mainCh), "%s channel was closed, should be open", mainTask.Name)
   158  
   159  	states := map[string]*structs.TaskState{
   160  		mainTask.Name: {
   161  			State:  structs.TaskStatePending,
   162  			Failed: false,
   163  		},
   164  		init1Task.Name: {
   165  			State:      structs.TaskStateDead,
   166  			Failed:     false,
   167  			StartedAt:  time.Now(),
   168  			FinishedAt: time.Now(),
   169  		},
   170  		init2Task.Name: {
   171  			State:     structs.TaskStateDead,
   172  			Failed:    true,
   173  			StartedAt: time.Now(),
   174  		},
   175  	}
   176  
   177  	coord.taskStateUpdated(states)
   178  
   179  	require.Truef(t, isChannelClosed(init1Ch), "%s channel was open, should be closed", init1Task.Name)
   180  	require.Truef(t, isChannelClosed(init2Ch), "%s channel was open, should be closed", init2Task.Name)
   181  	require.Falsef(t, isChannelClosed(mainCh), "%s channel was closed, should be open", mainTask.Name)
   182  }
   183  
   184  func TestTaskHookCoordinator_SidecarNeverStarts(t *testing.T) {
   185  	logger := testlog.HCLogger(t)
   186  
   187  	alloc := mock.LifecycleAlloc()
   188  	tasks := alloc.Job.TaskGroups[0].Tasks
   189  
   190  	mainTask := tasks[0]
   191  	sideTask := tasks[1]
   192  	initTask := tasks[2]
   193  
   194  	coord := newTaskHookCoordinator(logger, tasks)
   195  	initCh := coord.startConditionForTask(initTask)
   196  	sideCh := coord.startConditionForTask(sideTask)
   197  	mainCh := coord.startConditionForTask(mainTask)
   198  
   199  	require.Truef(t, isChannelClosed(initCh), "%s channel was open, should be closed", initTask.Name)
   200  	require.Truef(t, isChannelClosed(sideCh), "%s channel was open, should be closed", sideTask.Name)
   201  	require.Falsef(t, isChannelClosed(mainCh), "%s channel was closed, should be open", mainTask.Name)
   202  
   203  	states := map[string]*structs.TaskState{
   204  		mainTask.Name: {
   205  			State:  structs.TaskStatePending,
   206  			Failed: false,
   207  		},
   208  		initTask.Name: {
   209  			State:      structs.TaskStateDead,
   210  			Failed:     false,
   211  			StartedAt:  time.Now(),
   212  			FinishedAt: time.Now(),
   213  		},
   214  		sideTask.Name: {
   215  			State:  structs.TaskStatePending,
   216  			Failed: false,
   217  		},
   218  	}
   219  
   220  	coord.taskStateUpdated(states)
   221  
   222  	require.Truef(t, isChannelClosed(initCh), "%s channel was open, should be closed", initTask.Name)
   223  	require.Truef(t, isChannelClosed(sideCh), "%s channel was open, should be closed", sideTask.Name)
   224  	require.Falsef(t, isChannelClosed(mainCh), "%s channel was closed, should be open", mainTask.Name)
   225  }
   226  
   227  func TestTaskHookCoordinator_PoststartStartsAfterMain(t *testing.T) {
   228  	logger := testlog.HCLogger(t)
   229  
   230  	alloc := mock.LifecycleAlloc()
   231  	tasks := alloc.Job.TaskGroups[0].Tasks
   232  
   233  	mainTask := tasks[0]
   234  	sideTask := tasks[1]
   235  	postTask := tasks[2]
   236  
   237  	// Make the the third task a poststart hook
   238  	postTask.Lifecycle.Hook = structs.TaskLifecycleHookPoststart
   239  
   240  	coord := newTaskHookCoordinator(logger, tasks)
   241  	postCh := coord.startConditionForTask(postTask)
   242  	sideCh := coord.startConditionForTask(sideTask)
   243  	mainCh := coord.startConditionForTask(mainTask)
   244  
   245  	require.Truef(t, isChannelClosed(sideCh), "%s channel was open, should be closed", sideTask.Name)
   246  	require.Falsef(t, isChannelClosed(mainCh), "%s channel was closed, should be open", mainTask.Name)
   247  	require.Falsef(t, isChannelClosed(mainCh), "%s channel was closed, should be open", postTask.Name)
   248  
   249  	states := map[string]*structs.TaskState{
   250  		postTask.Name: {
   251  			State:  structs.TaskStatePending,
   252  			Failed: false,
   253  		},
   254  		mainTask.Name: {
   255  			State:     structs.TaskStateRunning,
   256  			Failed:    false,
   257  			StartedAt: time.Now(),
   258  		},
   259  		sideTask.Name: {
   260  			State:     structs.TaskStateRunning,
   261  			Failed:    false,
   262  			StartedAt: time.Now(),
   263  		},
   264  	}
   265  
   266  	coord.taskStateUpdated(states)
   267  
   268  	require.Truef(t, isChannelClosed(postCh), "%s channel was open, should be closed", postTask.Name)
   269  	require.Truef(t, isChannelClosed(sideCh), "%s channel was open, should be closed", sideTask.Name)
   270  	require.Truef(t, isChannelClosed(mainCh), "%s channel was open, should be closed", mainTask.Name)
   271  }
   272  
   273  func isChannelClosed(ch <-chan struct{}) bool {
   274  	select {
   275  	case <-ch:
   276  		return true
   277  	default:
   278  		return false
   279  	}
   280  }
   281  
   282  func TestHasSidecarTasks(t *testing.T) {
   283  
   284  	falseV, trueV := false, true
   285  
   286  	cases := []struct {
   287  		name string
   288  		// nil if main task, false if non-sidecar hook, true if sidecar hook
   289  		indicators []*bool
   290  
   291  		hasSidecars    bool
   292  		hasNonsidecars bool
   293  	}{
   294  		{
   295  			name:           "all sidecar - one",
   296  			indicators:     []*bool{&trueV},
   297  			hasSidecars:    true,
   298  			hasNonsidecars: false,
   299  		},
   300  		{
   301  			name:           "all sidecar - multiple",
   302  			indicators:     []*bool{&trueV, &trueV, &trueV},
   303  			hasSidecars:    true,
   304  			hasNonsidecars: false,
   305  		},
   306  		{
   307  			name:           "some sidecars, some others",
   308  			indicators:     []*bool{nil, &falseV, &trueV},
   309  			hasSidecars:    true,
   310  			hasNonsidecars: true,
   311  		},
   312  		{
   313  			name:           "no sidecars",
   314  			indicators:     []*bool{nil, &falseV, nil},
   315  			hasSidecars:    false,
   316  			hasNonsidecars: true,
   317  		},
   318  	}
   319  
   320  	for _, c := range cases {
   321  		t.Run(c.name, func(t *testing.T) {
   322  			alloc := allocWithSidecarIndicators(c.indicators)
   323  			arConf, cleanup := testAllocRunnerConfig(t, alloc)
   324  			defer cleanup()
   325  
   326  			ar, err := NewAllocRunner(arConf)
   327  			require.NoError(t, err)
   328  
   329  			require.Equal(t, c.hasSidecars, hasSidecarTasks(ar.tasks), "sidecars")
   330  
   331  			runners := []*taskrunner.TaskRunner{}
   332  			for _, r := range ar.tasks {
   333  				runners = append(runners, r)
   334  			}
   335  			require.Equal(t, c.hasNonsidecars, hasNonSidecarTasks(runners), "non-sidecars")
   336  
   337  		})
   338  	}
   339  }
   340  
   341  func allocWithSidecarIndicators(indicators []*bool) *structs.Allocation {
   342  	alloc := mock.BatchAlloc()
   343  
   344  	tasks := []*structs.Task{}
   345  	resources := map[string]*structs.AllocatedTaskResources{}
   346  
   347  	tr := alloc.AllocatedResources.Tasks[alloc.Job.TaskGroups[0].Tasks[0].Name]
   348  
   349  	for i, indicator := range indicators {
   350  		task := alloc.Job.TaskGroups[0].Tasks[0].Copy()
   351  		task.Name = fmt.Sprintf("task%d", i)
   352  		if indicator != nil {
   353  			task.Lifecycle = &structs.TaskLifecycleConfig{
   354  				Hook:    structs.TaskLifecycleHookPrestart,
   355  				Sidecar: *indicator,
   356  			}
   357  		}
   358  		tasks = append(tasks, task)
   359  		resources[task.Name] = tr
   360  	}
   361  
   362  	alloc.Job.TaskGroups[0].Tasks = tasks
   363  
   364  	alloc.AllocatedResources.Tasks = resources
   365  	return alloc
   366  
   367  }