// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package command import ( "sort" "testing" "github.com/hashicorp/nomad/api" "github.com/hashicorp/nomad/ci" "github.com/hashicorp/nomad/testutil" "github.com/mitchellh/cli" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestRecommendationListCommand_Run(t *testing.T) { ci.Parallel(t) require := require.New(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 { require.Equal(0, code) out := ui.OutputWriter.String() require.Contains(out, "No recommendations found") } else { require.Equal(1, code) require.Contains(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) require.NoError(err) registerCode := waitForSuccess(ui, client, fullId, t, regResp.EvalID) require.Equal(0, 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 { require.NoError(err) } else { require.Error(err, "Nomad Enterprise only endpoint") } // Perform a new list which should yield results. code = cmd.Run([]string{"-address=" + url}) if srv.Enterprise { require.Equal(0, code) out := ui.OutputWriter.String() require.Contains(out, "ID") require.Contains(out, "Job") require.Contains(out, "Group") require.Contains(out, "Task") require.Contains(out, "Resource") require.Contains(out, "Value") require.Contains(out, "CPU") } else { require.Equal(1, code) require.Contains(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) assert.Equal(t, tc.expectedOutputList, sortedRecs.r, tc.name) }) } }