api: set last index and request time on alloc stop (#16319)

Some of the methods in `Allocations()` incorrectly use the `putQuery`
in API calls where `put` is more appropriate since they are not reading
information back. These methods are also not returning request metadata
such as `LastIndex` back to callers, which can be useful to have in some
scenarios.

They also provide poor developer experience as they take an
`*api.Allocation` struct when only the allocation ID is necessary. This
can lead consumers to make unnecessary API calls to fetch the full
allocation.

Fixing these problems require updating the methods' signatures so they
take `*WriteOptions` instead of `*QueryOptions` and return `*WriteMeta`,
but this is a breaking change that requires advanced notice to consumers.

This commit adds a future breaking change notice and also fixes the
`Stop` method so it properly returns request metadata in a backwards
compatible way.
This commit is contained in:
Luiz Aoqui 2023-03-03 15:52:41 -05:00 committed by GitHub
parent 3c0eaba9db
commit 3f1ea9da4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 75 additions and 1 deletions

11
.changelog/16319.txt Normal file
View File

@ -0,0 +1,11 @@
```release-note:bug
api: Fix `Allocations().Stop()` method to properly set the request `LastIndex` and `RequestTime` in the response
```
```release-note:deprecation
api: The `Restart()`, `Stop()`, and `Signal()` methods in the `Allocations` struct will have their signatures modified in Nomad 1.6.0
```
```release-note:deprecation
api: The `RestartAllTasks()` method in the `Allocations` struct will be removed in Nomad 1.6.0
```

View File

@ -148,6 +148,9 @@ func (a *Allocations) GC(alloc *Allocation, q *QueryOptions) error {
// Note: for cluster topologies where API consumers don't have network access to // Note: for cluster topologies where API consumers don't have network access to
// Nomad clients, set api.ClientConnTimeout to a small value (ex 1ms) to avoid // Nomad clients, set api.ClientConnTimeout to a small value (ex 1ms) to avoid
// long pauses on this API call. // long pauses on this API call.
//
// BREAKING: This method will have the following signature in 1.6.0
// func (a *Allocations) Restart(allocID string, taskName string, allTasks bool, w *WriteOptions) (*WriteMeta, error) {
func (a *Allocations) Restart(alloc *Allocation, taskName string, q *QueryOptions) error { func (a *Allocations) Restart(alloc *Allocation, taskName string, q *QueryOptions) error {
req := AllocationRestartRequest{ req := AllocationRestartRequest{
TaskName: taskName, TaskName: taskName,
@ -164,6 +167,8 @@ func (a *Allocations) Restart(alloc *Allocation, taskName string, q *QueryOption
// Note: for cluster topologies where API consumers don't have network access to // Note: for cluster topologies where API consumers don't have network access to
// Nomad clients, set api.ClientConnTimeout to a small value (ex 1ms) to avoid // Nomad clients, set api.ClientConnTimeout to a small value (ex 1ms) to avoid
// long pauses on this API call. // long pauses on this API call.
//
// DEPRECATED: This method will be removed in 1.6.0
func (a *Allocations) RestartAllTasks(alloc *Allocation, q *QueryOptions) error { func (a *Allocations) RestartAllTasks(alloc *Allocation, q *QueryOptions) error {
req := AllocationRestartRequest{ req := AllocationRestartRequest{
AllTasks: true, AllTasks: true,
@ -179,9 +184,29 @@ func (a *Allocations) RestartAllTasks(alloc *Allocation, q *QueryOptions) error
// Note: for cluster topologies where API consumers don't have network access to // Note: for cluster topologies where API consumers don't have network access to
// Nomad clients, set api.ClientConnTimeout to a small value (ex 1ms) to avoid // Nomad clients, set api.ClientConnTimeout to a small value (ex 1ms) to avoid
// long pauses on this API call. // long pauses on this API call.
//
// BREAKING: This method will have the following signature in 1.6.0
// func (a *Allocations) Stop(allocID string, w *WriteOptions) (*AllocStopResponse, error) {
func (a *Allocations) Stop(alloc *Allocation, q *QueryOptions) (*AllocStopResponse, error) { func (a *Allocations) Stop(alloc *Allocation, q *QueryOptions) (*AllocStopResponse, error) {
// COMPAT: Remove in 1.6.0
var w *WriteOptions
if q != nil {
w = &WriteOptions{
Region: q.Region,
Namespace: q.Namespace,
AuthToken: q.AuthToken,
Headers: q.Headers,
ctx: q.ctx,
}
}
var resp AllocStopResponse var resp AllocStopResponse
_, err := a.client.putQuery("/v1/allocation/"+alloc.ID+"/stop", nil, &resp, q) wm, err := a.client.put("/v1/allocation/"+alloc.ID+"/stop", nil, &resp, w)
if wm != nil {
resp.LastIndex = wm.LastIndex
resp.RequestTime = wm.RequestTime
}
return &resp, err return &resp, err
} }
@ -198,6 +223,9 @@ type AllocStopResponse struct {
// Note: for cluster topologies where API consumers don't have network access to // Note: for cluster topologies where API consumers don't have network access to
// Nomad clients, set api.ClientConnTimeout to a small value (ex 1ms) to avoid // Nomad clients, set api.ClientConnTimeout to a small value (ex 1ms) to avoid
// long pauses on this API call. // long pauses on this API call.
//
// BREAKING: This method will have the following signature in 1.6.0
// func (a *Allocations) Signal(allocID string, task string, signal string, w *WriteOptions) (*WriteMeta, error) {
func (a *Allocations) Signal(alloc *Allocation, q *QueryOptions, task, signal string) error { func (a *Allocations) Signal(alloc *Allocation, q *QueryOptions, task, signal string) error {
req := AllocSignalRequest{ req := AllocSignalRequest{
Signal: signal, Signal: signal,

View File

@ -9,6 +9,7 @@ import (
"time" "time"
"github.com/hashicorp/nomad/api/internal/testutil" "github.com/hashicorp/nomad/api/internal/testutil"
"github.com/shoenig/test"
"github.com/shoenig/test/must" "github.com/shoenig/test/must"
) )
@ -271,6 +272,40 @@ func TestAllocations_RescheduleInfo(t *testing.T) {
} }
func TestAllocations_Stop(t *testing.T) {
testutil.RequireRoot(t)
testutil.Parallel(t)
c, s := makeClient(t, nil, func(c *testutil.TestServerConfig) {
c.DevMode = true
})
defer s.Stop()
a := c.Allocations()
// wait for node
_ = oneNodeFromNodeList(t, c.Nodes())
// Create a job and register it
job := testJob()
_, wm, err := c.Jobs().Register(job, nil)
must.NoError(t, err)
// List allocations.
stubs, qm, err := a.List(&QueryOptions{WaitIndex: wm.LastIndex})
must.NoError(t, err)
must.SliceLen(t, 1, stubs)
// Stop the first allocation.
resp, err := a.Stop(&Allocation{ID: stubs[0].ID}, &QueryOptions{WaitIndex: qm.LastIndex})
must.NoError(t, err)
test.UUIDv4(t, resp.EvalID)
test.NonZero(t, resp.LastIndex)
// Stop allocation that doesn't exist.
resp, err = a.Stop(&Allocation{ID: "invalid"}, &QueryOptions{WaitIndex: qm.LastIndex})
must.Error(t, err)
}
// TestAllocations_ExecErrors ensures errors are properly formatted // TestAllocations_ExecErrors ensures errors are properly formatted
func TestAllocations_ExecErrors(t *testing.T) { func TestAllocations_ExecErrors(t *testing.T) {
testutil.Parallel(t) testutil.Parallel(t)