package api import ( "context" "fmt" "os" "reflect" "sort" "testing" "time" "github.com/hashicorp/nomad/api/internal/testutil" "github.com/stretchr/testify/require" ) func TestAllocations_List(t *testing.T) { testutil.Parallel(t) c, s := makeClient(t, nil, func(c *testutil.TestServerConfig) { c.DevMode = true }) defer s.Stop() a := c.Allocations() // Querying when no allocs exist returns nothing allocs, qm, err := a.List(nil) if err != nil { t.Fatalf("err: %s", err) } if qm.LastIndex != 0 { t.Fatalf("bad index: %d", qm.LastIndex) } if n := len(allocs); n != 0 { t.Fatalf("expected 0 allocs, got: %d", n) } // Create a job and attempt to register it job := testJob() resp, wm, err := c.Jobs().Register(job, nil) require.NoError(t, err) require.NotNil(t, resp) require.NotEmpty(t, resp.EvalID) assertWriteMeta(t, wm) // List the allocations again qo := &QueryOptions{ WaitIndex: wm.LastIndex, } allocs, qm, err = a.List(qo) require.NoError(t, err) require.NotZero(t, qm.LastIndex) // Check that we got the allocation back require.Len(t, allocs, 1) require.Equal(t, resp.EvalID, allocs[0].EvalID) // Resources should be unset by default require.Nil(t, allocs[0].AllocatedResources) } func TestAllocations_PrefixList(t *testing.T) { testutil.Parallel(t) c, s := makeClient(t, nil, nil) defer s.Stop() a := c.Allocations() // Querying when no allocs exist returns nothing allocs, qm, err := a.PrefixList("") if err != nil { t.Fatalf("err: %s", err) } if qm.LastIndex != 0 { t.Fatalf("bad index: %d", qm.LastIndex) } if n := len(allocs); n != 0 { t.Fatalf("expected 0 allocs, got: %d", n) } // TODO: do something that causes an allocation to actually happen // so we can query for them. return //job := &Job{ //ID: stringToPtr("job1"), //Name: stringToPtr("Job #1"), //Type: stringToPtr(JobTypeService), //} //eval, _, err := c.Jobs().Register(job, nil) //if err != nil { //t.Fatalf("err: %s", err) //} //// List the allocations by prefix //allocs, qm, err = a.PrefixList("foobar") //if err != nil { //t.Fatalf("err: %s", err) //} //if qm.LastIndex == 0 { //t.Fatalf("bad index: %d", qm.LastIndex) //} //// Check that we got the allocation back //if len(allocs) == 0 || allocs[0].EvalID != eval { //t.Fatalf("bad: %#v", allocs) //} } func TestAllocations_List_Resources(t *testing.T) { testutil.Parallel(t) c, s := makeClient(t, nil, func(c *testutil.TestServerConfig) { c.DevMode = true }) defer s.Stop() a := c.Allocations() // Create a job and register it job := testJob() resp, wm, err := c.Jobs().Register(job, nil) require.NoError(t, err) require.NotNil(t, resp) require.NotEmpty(t, resp.EvalID) assertWriteMeta(t, wm) // List the allocations qo := &QueryOptions{ Params: map[string]string{"resources": "true"}, WaitIndex: wm.LastIndex, } allocs, qm, err := a.List(qo) require.NoError(t, err) require.NotZero(t, qm.LastIndex) // Check that we got the allocation back with resources require.Len(t, allocs, 1) require.Equal(t, resp.EvalID, allocs[0].EvalID) require.NotNil(t, allocs[0].AllocatedResources) } func TestAllocations_CreateIndexSort(t *testing.T) { testutil.Parallel(t) allocs := []*AllocationListStub{ {CreateIndex: 2}, {CreateIndex: 1}, {CreateIndex: 5}, } sort.Sort(AllocIndexSort(allocs)) expect := []*AllocationListStub{ {CreateIndex: 5}, {CreateIndex: 2}, {CreateIndex: 1}, } if !reflect.DeepEqual(allocs, expect) { t.Fatalf("\n\n%#v\n\n%#v", allocs, expect) } } func TestAllocations_RescheduleInfo(t *testing.T) { testutil.Parallel(t) // Create a job, task group and alloc job := &Job{ Name: pointerOf("foo"), Namespace: pointerOf(DefaultNamespace), ID: pointerOf("bar"), ParentID: pointerOf("lol"), TaskGroups: []*TaskGroup{ { Name: pointerOf("bar"), Tasks: []*Task{ { Name: "task1", }, }, }, }, } job.Canonicalize() alloc := &Allocation{ ID: generateUUID(), Namespace: DefaultNamespace, EvalID: generateUUID(), Name: "foo-bar[1]", NodeID: generateUUID(), TaskGroup: *job.TaskGroups[0].Name, JobID: *job.ID, Job: job, } type testCase struct { desc string reschedulePolicy *ReschedulePolicy rescheduleTracker *RescheduleTracker time time.Time expAttempted int expTotal int } testCases := []testCase{ { desc: "no reschedule policy", expAttempted: 0, expTotal: 0, }, { desc: "no reschedule events", reschedulePolicy: &ReschedulePolicy{ Attempts: pointerOf(3), Interval: pointerOf(15 * time.Minute), }, expAttempted: 0, expTotal: 3, }, { desc: "all reschedule events within interval", reschedulePolicy: &ReschedulePolicy{ Attempts: pointerOf(3), Interval: pointerOf(15 * time.Minute), }, time: time.Now(), rescheduleTracker: &RescheduleTracker{ Events: []*RescheduleEvent{ { RescheduleTime: time.Now().Add(-5 * time.Minute).UTC().UnixNano(), }, }, }, expAttempted: 1, expTotal: 3, }, { desc: "some reschedule events outside interval", reschedulePolicy: &ReschedulePolicy{ Attempts: pointerOf(3), Interval: pointerOf(15 * time.Minute), }, time: time.Now(), rescheduleTracker: &RescheduleTracker{ Events: []*RescheduleEvent{ { RescheduleTime: time.Now().Add(-45 * time.Minute).UTC().UnixNano(), }, { RescheduleTime: time.Now().Add(-30 * time.Minute).UTC().UnixNano(), }, { RescheduleTime: time.Now().Add(-10 * time.Minute).UTC().UnixNano(), }, { RescheduleTime: time.Now().Add(-5 * time.Minute).UTC().UnixNano(), }, }, }, expAttempted: 2, expTotal: 3, }, } for _, tc := range testCases { t.Run(tc.desc, func(t *testing.T) { require := require.New(t) alloc.RescheduleTracker = tc.rescheduleTracker job.TaskGroups[0].ReschedulePolicy = tc.reschedulePolicy attempted, total := alloc.RescheduleInfo(tc.time) require.Equal(tc.expAttempted, attempted) require.Equal(tc.expTotal, total) }) } } // TestAllocations_ExecErrors ensures errors are properly formatted func TestAllocations_ExecErrors(t *testing.T) { c, s := makeClient(t, nil, nil) defer s.Stop() a := c.Allocations() job := &Job{ Name: pointerOf("foo"), Namespace: pointerOf(DefaultNamespace), ID: pointerOf("bar"), ParentID: pointerOf("lol"), TaskGroups: []*TaskGroup{ { Name: pointerOf("bar"), Tasks: []*Task{ { Name: "task1", }, }, }, }, } job.Canonicalize() allocID := generateUUID() alloc := &Allocation{ ID: allocID, Namespace: DefaultNamespace, EvalID: generateUUID(), Name: "foo-bar[1]", NodeID: generateUUID(), TaskGroup: *job.TaskGroups[0].Name, JobID: *job.ID, Job: job, } // Querying when no allocs exist returns nothing sizeCh := make(chan TerminalSize, 1) // make a request that will result in an error // ensure the error is what we expect exitCode, err := a.Exec(context.Background(), alloc, "bar", false, []string{"command"}, os.Stdin, os.Stdout, os.Stderr, sizeCh, nil) require.Equal(t, exitCode, -2) require.Equal(t, err.Error(), fmt.Sprintf("Unknown allocation \"%s\"", allocID)) } func TestAllocation_ServerTerminalStatus(t *testing.T) { testutil.Parallel(t) testCases := []struct { inputAllocation *Allocation expectedOutput bool name string }{ { inputAllocation: &Allocation{DesiredStatus: AllocDesiredStatusEvict}, expectedOutput: true, name: "alloc desired status evict", }, { inputAllocation: &Allocation{DesiredStatus: AllocDesiredStatusStop}, expectedOutput: true, name: "alloc desired status stop", }, { inputAllocation: &Allocation{DesiredStatus: AllocDesiredStatusRun}, expectedOutput: false, name: "alloc desired status run", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { require.Equal(t, tc.expectedOutput, tc.inputAllocation.ServerTerminalStatus(), tc.name) }) } } func TestAllocation_ClientTerminalStatus(t *testing.T) { testutil.Parallel(t) testCases := []struct { inputAllocation *Allocation expectedOutput bool name string }{ { inputAllocation: &Allocation{ClientStatus: AllocClientStatusLost}, expectedOutput: true, name: "alloc client status lost", }, { inputAllocation: &Allocation{ClientStatus: AllocClientStatusFailed}, expectedOutput: true, name: "alloc client status failed", }, { inputAllocation: &Allocation{ClientStatus: AllocClientStatusComplete}, expectedOutput: true, name: "alloc client status complete", }, { inputAllocation: &Allocation{ClientStatus: AllocClientStatusRunning}, expectedOutput: false, name: "alloc client status complete", }, { inputAllocation: &Allocation{ClientStatus: AllocClientStatusPending}, expectedOutput: false, name: "alloc client status running", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { require.Equal(t, tc.expectedOutput, tc.inputAllocation.ClientTerminalStatus(), tc.name) }) } } func TestAllocations_ShouldMigrate(t *testing.T) { testutil.Parallel(t) require.True(t, DesiredTransition{Migrate: pointerOf(true)}.ShouldMigrate()) require.False(t, DesiredTransition{}.ShouldMigrate()) require.False(t, DesiredTransition{Migrate: pointerOf(false)}.ShouldMigrate()) } func TestAllocations_Services(t *testing.T) { // TODO(jrasell) add tests once registration process is in place. }