// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package command import ( "sort" "testing" "github.com/hashicorp/cli" "github.com/hashicorp/nomad/api" "github.com/hashicorp/nomad/ci" "github.com/hashicorp/nomad/testutil" "github.com/shoenig/test/must" ) func TestRecommendationListCommand_Run(t *testing.T) { ci.Parallel(t) srv, client, url := testServer(t, true, nil) defer srv.Shutdown() testutil.WaitForLeader(t, srv.Agent.RPC) clientID := srv.Agent.Client().NodeID() testutil.WaitForClient(t, srv.Agent.Client().RPC, clientID, srv.Agent.Client().Region()) ui := cli.NewMockUi() cmd := &RecommendationListCommand{Meta: Meta{Ui: ui}} // Perform an initial list, which should return zero results. code := cmd.Run([]string{"-address=" + url}) if srv.Enterprise { must.Zero(t, code) out := ui.OutputWriter.String() must.StrContains(t, out, "No recommendations found") } else { must.One(t, code) must.StrContains(t, ui.ErrorWriter.String(), "Nomad Enterprise only endpoint") } // Register a test job to write a recommendation against. testJob := testJob("recommendation_list") regResp, _, err := client.Jobs().Register(testJob, nil) must.NoError(t, err) registerCode := waitForSuccess(ui, client, fullId, t, regResp.EvalID) must.Zero(t, registerCode) // Write a recommendation. rec := api.Recommendation{ JobID: *testJob.ID, Group: *testJob.TaskGroups[0].Name, Task: testJob.TaskGroups[0].Tasks[0].Name, Resource: "CPU", Value: 1050, Meta: map[string]interface{}{"test-meta-entry": "test-meta-value"}, Stats: map[string]float64{"p13": 1.13}, } _, _, err = client.Recommendations().Upsert(&rec, nil) if srv.Enterprise { must.NoError(t, err) } else { must.ErrorContains(t, err, "Nomad Enterprise only endpoint") } // Perform a new list which should yield results. code = cmd.Run([]string{"-address=" + url}) if srv.Enterprise { must.Zero(t, code) out := ui.OutputWriter.String() must.StrContains(t, out, "ID") must.StrContains(t, out, "Job") must.StrContains(t, out, "Group") must.StrContains(t, out, "Task") must.StrContains(t, out, "Resource") must.StrContains(t, out, "Value") must.StrContains(t, out, "CPU") } else { must.One(t, code) must.StrContains(t, ui.ErrorWriter.String(), "Nomad Enterprise only endpoint") } } func TestRecommendationListCommand_Sort(t *testing.T) { ci.Parallel(t) testCases := []struct { inputRecommendationList []*api.Recommendation expectedOutputList []*api.Recommendation name string }{ { inputRecommendationList: []*api.Recommendation{ {Namespace: "default", JobID: "example", Group: "cache", Task: "redis", Resource: "MemoryMB"}, {Namespace: "default", JobID: "example", Group: "cache", Task: "redis", Resource: "CPU"}, }, expectedOutputList: []*api.Recommendation{ {Namespace: "default", JobID: "example", Group: "cache", Task: "redis", Resource: "CPU"}, {Namespace: "default", JobID: "example", Group: "cache", Task: "redis", Resource: "MemoryMB"}, }, name: "single job with both resources", }, { inputRecommendationList: []*api.Recommendation{ {Namespace: "default", JobID: "example", Group: "cache", Task: "redis", Resource: "MemoryMB"}, {Namespace: "default", JobID: "example", Group: "cache", Task: "redis", Resource: "CPU"}, {Namespace: "default", JobID: "example", Group: "cache", Task: "mongodb", Resource: "MemoryMB"}, {Namespace: "default", JobID: "example", Group: "cache", Task: "mongodb", Resource: "CPU"}, }, expectedOutputList: []*api.Recommendation{ {Namespace: "default", JobID: "example", Group: "cache", Task: "mongodb", Resource: "CPU"}, {Namespace: "default", JobID: "example", Group: "cache", Task: "mongodb", Resource: "MemoryMB"}, {Namespace: "default", JobID: "example", Group: "cache", Task: "redis", Resource: "CPU"}, {Namespace: "default", JobID: "example", Group: "cache", Task: "redis", Resource: "MemoryMB"}, }, name: "single job with multiple groups", }, { inputRecommendationList: []*api.Recommendation{ {Namespace: "default", JobID: "example", Group: "cache", Task: "redis", Resource: "MemoryMB"}, {Namespace: "default", JobID: "example", Group: "cache", Task: "redis", Resource: "CPU"}, {Namespace: "default", JobID: "distro", Group: "cache", Task: "redis", Resource: "MemoryMB"}, {Namespace: "default", JobID: "example", Group: "cache", Task: "mongodb", Resource: "MemoryMB"}, {Namespace: "default", JobID: "example", Group: "cache", Task: "mongodb", Resource: "CPU"}, {Namespace: "default", JobID: "distro", Group: "cache", Task: "redis", Resource: "CPU"}, }, expectedOutputList: []*api.Recommendation{ {Namespace: "default", JobID: "distro", Group: "cache", Task: "redis", Resource: "CPU"}, {Namespace: "default", JobID: "distro", Group: "cache", Task: "redis", Resource: "MemoryMB"}, {Namespace: "default", JobID: "example", Group: "cache", Task: "mongodb", Resource: "CPU"}, {Namespace: "default", JobID: "example", Group: "cache", Task: "mongodb", Resource: "MemoryMB"}, {Namespace: "default", JobID: "example", Group: "cache", Task: "redis", Resource: "CPU"}, {Namespace: "default", JobID: "example", Group: "cache", Task: "redis", Resource: "MemoryMB"}, }, name: "multiple jobs", }, { inputRecommendationList: []*api.Recommendation{ {Namespace: "default", JobID: "example", Group: "cache", Task: "redis", Resource: "MemoryMB"}, {Namespace: "default", JobID: "example", Group: "cache", Task: "redis", Resource: "CPU"}, {Namespace: "cefault", JobID: "distro", Group: "cache", Task: "redis", Resource: "MemoryMB"}, {Namespace: "default", JobID: "distro", Group: "cache", Task: "redis", Resource: "MemoryMB"}, {Namespace: "default", JobID: "example", Group: "cache", Task: "mongodb", Resource: "MemoryMB"}, {Namespace: "default", JobID: "example", Group: "cache", Task: "mongodb", Resource: "CPU"}, {Namespace: "default", JobID: "distro", Group: "cache", Task: "redis", Resource: "CPU"}, {Namespace: "cefault", JobID: "distro", Group: "cache", Task: "redis", Resource: "CPU"}, }, expectedOutputList: []*api.Recommendation{ {Namespace: "cefault", JobID: "distro", Group: "cache", Task: "redis", Resource: "CPU"}, {Namespace: "cefault", JobID: "distro", Group: "cache", Task: "redis", Resource: "MemoryMB"}, {Namespace: "default", JobID: "distro", Group: "cache", Task: "redis", Resource: "CPU"}, {Namespace: "default", JobID: "distro", Group: "cache", Task: "redis", Resource: "MemoryMB"}, {Namespace: "default", JobID: "example", Group: "cache", Task: "mongodb", Resource: "CPU"}, {Namespace: "default", JobID: "example", Group: "cache", Task: "mongodb", Resource: "MemoryMB"}, {Namespace: "default", JobID: "example", Group: "cache", Task: "redis", Resource: "CPU"}, {Namespace: "default", JobID: "example", Group: "cache", Task: "redis", Resource: "MemoryMB"}, }, name: "multiple namespaces", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { sortedRecs := recommendationList{r: tc.inputRecommendationList} sort.Sort(sortedRecs) must.Eq(t, tc.expectedOutputList, sortedRecs.r) }) } }