github.com/ranjib/nomad@v0.1.1-0.20160225204057-97751b02f70b/client/task_runner_test.go (about)

     1  package client
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"path/filepath"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/hashicorp/nomad/client/allocdir"
    12  	"github.com/hashicorp/nomad/client/driver"
    13  	"github.com/hashicorp/nomad/nomad/mock"
    14  	"github.com/hashicorp/nomad/nomad/structs"
    15  	"github.com/hashicorp/nomad/testutil"
    16  
    17  	ctestutil "github.com/hashicorp/nomad/client/testutil"
    18  )
    19  
    20  func testLogger() *log.Logger {
    21  	return log.New(os.Stderr, "", log.LstdFlags)
    22  }
    23  
    24  type MockTaskStateUpdater struct {
    25  	state  string
    26  	events []*structs.TaskEvent
    27  }
    28  
    29  func (m *MockTaskStateUpdater) Update(name, state string, event *structs.TaskEvent) {
    30  	m.state = state
    31  	m.events = append(m.events, event)
    32  }
    33  
    34  func testTaskRunner(restarts bool) (*MockTaskStateUpdater, *TaskRunner) {
    35  	logger := testLogger()
    36  	conf := DefaultConfig()
    37  	conf.StateDir = os.TempDir()
    38  	conf.AllocDir = os.TempDir()
    39  	upd := &MockTaskStateUpdater{}
    40  	alloc := mock.Alloc()
    41  	task := alloc.Job.TaskGroups[0].Tasks[0]
    42  	consulClient, _ := NewConsulService(&consulServiceConfig{logger, "127.0.0.1:8500", "", "", false, false, &structs.Node{}})
    43  	// Initialize the port listing. This should be done by the offer process but
    44  	// we have a mock so that doesn't happen.
    45  	task.Resources.Networks[0].ReservedPorts = []structs.Port{{"", 80}}
    46  
    47  	allocDir := allocdir.NewAllocDir(filepath.Join(conf.AllocDir, alloc.ID))
    48  	allocDir.Build([]*structs.Task{task})
    49  
    50  	ctx := driver.NewExecContext(allocDir, alloc.ID)
    51  	tr := NewTaskRunner(logger, conf, upd.Update, ctx, mock.Alloc(), task, consulClient)
    52  	if !restarts {
    53  		tr.restartTracker = noRestartsTracker()
    54  	}
    55  	return upd, tr
    56  }
    57  
    58  func TestTaskRunner_SimpleRun(t *testing.T) {
    59  	ctestutil.ExecCompatible(t)
    60  	upd, tr := testTaskRunner(false)
    61  	go tr.Run()
    62  	defer tr.Destroy()
    63  	defer tr.ctx.AllocDir.Destroy()
    64  
    65  	select {
    66  	case <-tr.WaitCh():
    67  	case <-time.After(time.Duration(testutil.TestMultiplier()*15) * time.Second):
    68  		t.Fatalf("timeout")
    69  	}
    70  
    71  	if len(upd.events) != 3 {
    72  		t.Fatalf("should have 3 updates: %#v", upd.events)
    73  	}
    74  
    75  	if upd.state != structs.TaskStateDead {
    76  		t.Fatalf("TaskState %v; want %v", upd.state, structs.TaskStateDead)
    77  	}
    78  
    79  	if upd.events[0].Type != structs.TaskReceived {
    80  		t.Fatalf("First Event was %v; want %v", upd.events[0].Type, structs.TaskReceived)
    81  	}
    82  
    83  	if upd.events[1].Type != structs.TaskStarted {
    84  		t.Fatalf("Second Event was %v; want %v", upd.events[1].Type, structs.TaskStarted)
    85  	}
    86  
    87  	if upd.events[2].Type != structs.TaskTerminated {
    88  		t.Fatalf("Third Event was %v; want %v", upd.events[2].Type, structs.TaskTerminated)
    89  	}
    90  }
    91  
    92  func TestTaskRunner_Destroy(t *testing.T) {
    93  	ctestutil.ExecCompatible(t)
    94  	upd, tr := testTaskRunner(true)
    95  	defer tr.ctx.AllocDir.Destroy()
    96  
    97  	// Change command to ensure we run for a bit
    98  	tr.task.Config["command"] = "/bin/sleep"
    99  	tr.task.Config["args"] = []string{"10"}
   100  	go tr.Run()
   101  
   102  	// Begin the tear down
   103  	go func() {
   104  		time.Sleep(100 * time.Millisecond)
   105  		tr.Destroy()
   106  	}()
   107  
   108  	select {
   109  	case <-tr.WaitCh():
   110  	case <-time.After(time.Duration(testutil.TestMultiplier()*15) * time.Second):
   111  		t.Fatalf("timeout")
   112  	}
   113  
   114  	if len(upd.events) != 3 {
   115  		t.Fatalf("should have 3 updates: %#v", upd.events)
   116  	}
   117  
   118  	if upd.state != structs.TaskStateDead {
   119  		t.Fatalf("TaskState %v; want %v", upd.state, structs.TaskStateDead)
   120  	}
   121  
   122  	if upd.events[0].Type != structs.TaskReceived {
   123  		t.Fatalf("First Event was %v; want %v", upd.events[0].Type, structs.TaskReceived)
   124  	}
   125  
   126  	if upd.events[1].Type != structs.TaskStarted {
   127  		t.Fatalf("Second Event was %v; want %v", upd.events[1].Type, structs.TaskStarted)
   128  	}
   129  
   130  	if upd.events[2].Type != structs.TaskKilled {
   131  		t.Fatalf("Third Event was %v; want %v", upd.events[2].Type, structs.TaskKilled)
   132  	}
   133  
   134  }
   135  
   136  func TestTaskRunner_Update(t *testing.T) {
   137  	ctestutil.ExecCompatible(t)
   138  	_, tr := testTaskRunner(false)
   139  
   140  	// Change command to ensure we run for a bit
   141  	tr.task.Config["command"] = "/bin/sleep"
   142  	tr.task.Config["args"] = []string{"100"}
   143  	go tr.Run()
   144  	defer tr.Destroy()
   145  	defer tr.ctx.AllocDir.Destroy()
   146  
   147  	// Update the task definition
   148  	updateAlloc := tr.alloc.Copy()
   149  
   150  	// Update the restart policy
   151  	newTG := updateAlloc.Job.TaskGroups[0]
   152  	newMode := "foo"
   153  	newTG.RestartPolicy.Mode = newMode
   154  
   155  	newTask := updateAlloc.Job.TaskGroups[0].Tasks[0]
   156  	newTask.Driver = "foobar"
   157  
   158  	// Update the kill timeout
   159  	testutil.WaitForResult(func() (bool, error) {
   160  		if tr.handle == nil {
   161  			return false, fmt.Errorf("task not started")
   162  		}
   163  		return true, nil
   164  	}, func(err error) {
   165  		t.Fatalf("err: %v", err)
   166  	})
   167  
   168  	oldHandle := tr.handle.ID()
   169  	newTask.KillTimeout = time.Hour
   170  
   171  	tr.Update(updateAlloc)
   172  
   173  	// Wait for update to take place
   174  	testutil.WaitForResult(func() (bool, error) {
   175  		if tr.task != newTask {
   176  			return false, fmt.Errorf("task not updated")
   177  		}
   178  		if tr.restartTracker.policy.Mode != newMode {
   179  			return false, fmt.Errorf("restart policy not updated")
   180  		}
   181  		if tr.handle.ID() == oldHandle {
   182  			return false, fmt.Errorf("handle not updated")
   183  		}
   184  		return true, nil
   185  	}, func(err error) {
   186  		t.Fatalf("err: %v", err)
   187  	})
   188  }
   189  
   190  func TestTaskRunner_SaveRestoreState(t *testing.T) {
   191  	ctestutil.ExecCompatible(t)
   192  	upd, tr := testTaskRunner(false)
   193  
   194  	// Change command to ensure we run for a bit
   195  	tr.task.Config["command"] = "/bin/sleep"
   196  	tr.task.Config["args"] = []string{"10"}
   197  	go tr.Run()
   198  	defer tr.Destroy()
   199  
   200  	// Snapshot state
   201  	time.Sleep(2 * time.Second)
   202  	if err := tr.SaveState(); err != nil {
   203  		t.Fatalf("err: %v", err)
   204  	}
   205  
   206  	// Create a new task runner
   207  	consulClient, _ := NewConsulService(&consulServiceConfig{tr.logger, "127.0.0.1:8500", "", "", false, false, &structs.Node{}})
   208  	tr2 := NewTaskRunner(tr.logger, tr.config, upd.Update,
   209  		tr.ctx, tr.alloc, &structs.Task{Name: tr.task.Name}, consulClient)
   210  	if err := tr2.RestoreState(); err != nil {
   211  		t.Fatalf("err: %v", err)
   212  	}
   213  	go tr2.Run()
   214  	defer tr2.Destroy()
   215  
   216  	// Destroy and wait
   217  	testutil.WaitForResult(func() (bool, error) {
   218  		return tr2.handle != nil, fmt.Errorf("RestoreState() didn't open handle")
   219  	}, func(err error) {
   220  		t.Fatalf("err: %v", err)
   221  	})
   222  }