1package command
2
3import (
4	"fmt"
5	"testing"
6
7	"github.com/hashicorp/nomad/api"
8	"github.com/hashicorp/nomad/helper"
9	"github.com/hashicorp/nomad/nomad/mock"
10	"github.com/hashicorp/nomad/nomad/structs"
11	"github.com/hashicorp/nomad/testutil"
12	"github.com/mitchellh/cli"
13	"github.com/posener/complete"
14	"github.com/stretchr/testify/require"
15)
16
17func TestJobPeriodicForceCommand_Implements(t *testing.T) {
18	t.Parallel()
19	var _ cli.Command = &JobPeriodicForceCommand{}
20}
21
22func TestJobPeriodicForceCommand_Fails(t *testing.T) {
23	t.Parallel()
24	ui := new(cli.MockUi)
25	cmd := &JobPeriodicForceCommand{Meta: Meta{Ui: ui}}
26
27	// Fails on misuse
28	code := cmd.Run([]string{"some", "bad", "args"})
29	require.Equal(t, code, 1, "expected error")
30	out := ui.ErrorWriter.String()
31	require.Contains(t, out, commandErrorText(cmd), "expected help output")
32	ui.ErrorWriter.Reset()
33
34	code = cmd.Run([]string{"-address=nope", "12"})
35	require.Equal(t, code, 1, "expected error")
36	out = ui.ErrorWriter.String()
37	require.Contains(t, out, "Error forcing periodic job", "expected force error")
38}
39
40func TestJobPeriodicForceCommand_AutocompleteArgs(t *testing.T) {
41	t.Parallel()
42
43	srv, _, url := testServer(t, true, nil)
44	defer srv.Shutdown()
45
46	ui := new(cli.MockUi)
47	cmd := &JobPeriodicForceCommand{Meta: Meta{Ui: ui, flagAddress: url}}
48
49	// Create a fake job, not periodic
50	state := srv.Agent.Server().State()
51	j := mock.Job()
52	require.NoError(t, state.UpsertJob(1000, j))
53
54	predictor := cmd.AutocompleteArgs()
55
56	res := predictor.Predict(complete.Args{Last: j.ID[:len(j.ID)-5]})
57	require.Empty(t, res)
58
59	// Create another fake job, periodic
60	state = srv.Agent.Server().State()
61	j2 := mock.Job()
62	j2.Periodic = &structs.PeriodicConfig{
63		Enabled:         true,
64		Spec:            "spec",
65		SpecType:        "cron",
66		ProhibitOverlap: true,
67		TimeZone:        "test zone",
68	}
69	require.NoError(t, state.UpsertJob(1000, j2))
70
71	res = predictor.Predict(complete.Args{Last: j2.ID[:len(j.ID)-5]})
72	require.Equal(t, []string{j2.ID}, res)
73
74	res = predictor.Predict(complete.Args{})
75	require.Equal(t, []string{j2.ID}, res)
76}
77
78func TestJobPeriodicForceCommand_NonPeriodicJob(t *testing.T) {
79	t.Parallel()
80	srv, client, url := testServer(t, true, nil)
81	defer srv.Shutdown()
82	testutil.WaitForResult(func() (bool, error) {
83		nodes, _, err := client.Nodes().List(nil)
84		if err != nil {
85			return false, err
86		}
87		if len(nodes) == 0 {
88			return false, fmt.Errorf("missing node")
89		}
90		if _, ok := nodes[0].Drivers["mock_driver"]; !ok {
91			return false, fmt.Errorf("mock_driver not ready")
92		}
93		return true, nil
94	}, func(err error) {
95		require.NoError(t, err)
96	})
97
98	// Register a job
99	j := testJob("job_not_periodic")
100
101	ui := new(cli.MockUi)
102	cmd := &JobPeriodicForceCommand{Meta: Meta{Ui: ui, flagAddress: url}}
103
104	resp, _, err := client.Jobs().Register(j, nil)
105	require.NoError(t, err)
106	code := waitForSuccess(ui, client, fullId, t, resp.EvalID)
107	require.Equal(t, 0, code)
108
109	code = cmd.Run([]string{"-address=" + url, "job_not_periodic"})
110	require.Equal(t, 1, code, "expected exit code")
111	out := ui.ErrorWriter.String()
112	require.Contains(t, out, "No periodic job(s)", "non-periodic error message")
113}
114
115func TestJobPeriodicForceCommand_SuccessfulPeriodicForceDetach(t *testing.T) {
116	t.Parallel()
117	srv, client, url := testServer(t, true, nil)
118	defer srv.Shutdown()
119	testutil.WaitForResult(func() (bool, error) {
120		nodes, _, err := client.Nodes().List(nil)
121		if err != nil {
122			return false, err
123		}
124		if len(nodes) == 0 {
125			return false, fmt.Errorf("missing node")
126		}
127		if _, ok := nodes[0].Drivers["mock_driver"]; !ok {
128			return false, fmt.Errorf("mock_driver not ready")
129		}
130		return true, nil
131	}, func(err error) {
132		require.NoError(t, err)
133	})
134
135	// Register a job
136	j := testJob("job1_is_periodic")
137	j.Periodic = &api.PeriodicConfig{
138		SpecType:        helper.StringToPtr(api.PeriodicSpecCron),
139		Spec:            helper.StringToPtr("*/15 * * * * *"),
140		ProhibitOverlap: helper.BoolToPtr(true),
141		TimeZone:        helper.StringToPtr("Europe/Minsk"),
142	}
143
144	ui := new(cli.MockUi)
145	cmd := &JobPeriodicForceCommand{Meta: Meta{Ui: ui, flagAddress: url}}
146
147	_, _, err := client.Jobs().Register(j, nil)
148	require.NoError(t, err)
149
150	code := cmd.Run([]string{"-address=" + url, "-detach", "job1_is_periodic"})
151	require.Equal(t, 0, code, "expected no error code")
152	out := ui.OutputWriter.String()
153	require.Contains(t, out, "Force periodic successful")
154	require.Contains(t, out, "Evaluation ID:")
155}
156
157func TestJobPeriodicForceCommand_SuccessfulPeriodicForce(t *testing.T) {
158	t.Parallel()
159	srv, client, url := testServer(t, true, nil)
160	defer srv.Shutdown()
161	testutil.WaitForResult(func() (bool, error) {
162		nodes, _, err := client.Nodes().List(nil)
163		if err != nil {
164			return false, err
165		}
166		if len(nodes) == 0 {
167			return false, fmt.Errorf("missing node")
168		}
169		if _, ok := nodes[0].Drivers["mock_driver"]; !ok {
170			return false, fmt.Errorf("mock_driver not ready")
171		}
172		return true, nil
173	}, func(err error) {
174		require.NoError(t, err)
175	})
176
177	// Register a job
178	j := testJob("job2_is_periodic")
179	j.Periodic = &api.PeriodicConfig{
180		SpecType:        helper.StringToPtr(api.PeriodicSpecCron),
181		Spec:            helper.StringToPtr("*/15 * * * * *"),
182		ProhibitOverlap: helper.BoolToPtr(true),
183		TimeZone:        helper.StringToPtr("Europe/Minsk"),
184	}
185
186	ui := new(cli.MockUi)
187	cmd := &JobPeriodicForceCommand{Meta: Meta{Ui: ui, flagAddress: url}}
188
189	_, _, err := client.Jobs().Register(j, nil)
190	require.NoError(t, err)
191
192	code := cmd.Run([]string{"-address=" + url, "job2_is_periodic"})
193	require.Equal(t, 0, code, "expected no error code")
194	out := ui.OutputWriter.String()
195	require.Contains(t, out, "Monitoring evaluation")
196	require.Contains(t, out, "finished with status \"complete\"")
197}
198