diff --git a/nomad/client_endpoint.go b/nomad/client_endpoint.go index 8639e3fa4..becaa2cd0 100644 --- a/nomad/client_endpoint.go +++ b/nomad/client_endpoint.go @@ -316,6 +316,30 @@ func (c *ClientEndpoint) GetAllocs(args *structs.NodeSpecificRequest, return c.srv.blockingRPC(&opts) } +// UpdateAlloc is used to update the client status of an allocation +func (c *ClientEndpoint) UpdateAlloc(args *structs.AllocUpdateRequest, reply *structs.GenericResponse) error { + if done, err := c.srv.forward("Client.UpdateAlloc", args, args, reply); done { + return err + } + defer metrics.MeasureSince([]string{"nomad", "client", "update_alloc"}, time.Now()) + + // Ensure only a single alloc + if len(args.Alloc) != 1 { + return fmt.Errorf("must update a single allocation") + } + + // Commit this update via Raft + _, index, err := c.srv.raftApply(structs.AllocClientUpdateRequestType, args) + if err != nil { + c.srv.logger.Printf("[ERR] nomad.client: alloc update failed: %v", err) + return err + } + + // Setup the response + reply.Index = index + return nil +} + // createNodeEvals is used to create evaluations for each alloc on a node. // Each Eval is scoped to a job, so we need to potentially trigger many evals. func (c *ClientEndpoint) createNodeEvals(nodeID string, nodeIndex uint64) ([]string, uint64, error) { diff --git a/nomad/client_endpoint_test.go b/nomad/client_endpoint_test.go index 90fb9b41d..b8a3dc25d 100644 --- a/nomad/client_endpoint_test.go +++ b/nomad/client_endpoint_test.go @@ -368,6 +368,62 @@ func TestClientEndpoint_GetAllocs_Blocking(t *testing.T) { } } +func TestClientEndpoint_UpdateAlloc(t *testing.T) { + s1 := testServer(t, nil) + defer s1.Shutdown() + codec := rpcClient(t, s1) + testutil.WaitForLeader(t, s1.RPC) + + // Create the register request + node := mock.Node() + reg := &structs.NodeRegisterRequest{ + Node: node, + WriteRequest: structs.WriteRequest{Region: "region1"}, + } + + // Fetch the response + var resp structs.GenericResponse + if err := msgpackrpc.CallWithCodec(codec, "Client.Register", reg, &resp); err != nil { + t.Fatalf("err: %v", err) + } + + // Inject fake evaluations + alloc := mock.Alloc() + alloc.NodeID = node.ID + state := s1.fsm.State() + err := state.UpdateAllocations(100, []*structs.Allocation{alloc}) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Attempt update + clientAlloc := new(structs.Allocation) + *clientAlloc = *alloc + clientAlloc.ClientStatus = structs.AllocClientStatusFailed + + // Update the alloc + update := &structs.AllocUpdateRequest{ + Alloc: []*structs.Allocation{clientAlloc}, + WriteRequest: structs.WriteRequest{Region: "region1"}, + } + var resp2 structs.NodeAllocsResponse + if err := msgpackrpc.CallWithCodec(codec, "Client.UpdateAlloc", update, &resp2); err != nil { + t.Fatalf("err: %v", err) + } + if resp2.Index == 0 { + t.Fatalf("Bad index: %d", resp2.Index) + } + + // Lookup the alloc + out, err := state.GetAllocByID(alloc.ID) + if err != nil { + t.Fatalf("err: %v", err) + } + if out.ClientStatus != structs.AllocClientStatusFailed { + t.Fatalf("Bad: %#v", out) + } +} + func TestClientEndpoint_CreateNodeEvals(t *testing.T) { s1 := testServer(t, nil) defer s1.Shutdown() diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index 871b49c00..004aa421e 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -220,6 +220,7 @@ type PlanRequest struct { type AllocUpdateRequest struct { // Alloc is the list of new allocations to assign Alloc []*Allocation + WriteRequest } // GenericRequest is used to request where no