http: add alloc service registration agent HTTP endpoint.

This commit is contained in:
James Rasell
2022-03-03 12:13:32 +01:00
parent d3f634329c
commit ab52b02def
2 changed files with 148 additions and 0 deletions

View File

@@ -86,6 +86,8 @@ func (s *HTTPServer) AllocSpecificRequest(resp http.ResponseWriter, req *http.Re
switch tokens[1] {
case "stop":
return s.allocStop(allocID, resp, req)
case "services":
return s.allocServiceRegistrations(resp, req, allocID)
}
return nil, CodedError(404, resourceNotFoundErr)
@@ -167,6 +169,39 @@ func (s *HTTPServer) allocStop(allocID string, resp http.ResponseWriter, req *ht
return &out, nil
}
// allocServiceRegistrations returns a list of all service registrations
// assigned to the job identifier. It is callable via the
// /v1/allocation/:alloc_id/services HTTP API and uses the
// structs.AllocServiceRegistrationsRPCMethod RPC method.
func (s *HTTPServer) allocServiceRegistrations(
resp http.ResponseWriter, req *http.Request, allocID string) (interface{}, error) {
// The endpoint only supports GET requests.
if req.Method != http.MethodGet {
return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
}
// Set up the request args and parse this to ensure the query options are
// set.
args := structs.AllocServiceRegistrationsRequest{AllocID: allocID}
if s.parse(resp, req, &args.Region, &args.QueryOptions) {
return nil, nil
}
// Perform the RPC request.
var reply structs.AllocServiceRegistrationsResponse
if err := s.agent.RPC(structs.AllocServiceRegistrationsRPCMethod, &args, &reply); err != nil {
return nil, err
}
setMeta(resp, &reply.QueryMeta)
if reply.Services == nil {
return nil, CodedError(http.StatusNotFound, allocNotFoundErr)
}
return reply.Services, nil
}
func (s *HTTPServer) ClientAllocRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
reqSuffix := strings.TrimPrefix(req.URL.Path, "/v1/client/allocation/")

View File

@@ -433,6 +433,119 @@ func TestHTTP_AllocStop(t *testing.T) {
})
}
func TestHTTP_allocServiceRegistrations(t *testing.T) {
t.Parallel()
testCases := []struct {
testFn func(srv *TestAgent)
name string
}{
{
testFn: func(s *TestAgent) {
// Grab the state, so we can manipulate it and test against it.
testState := s.Agent.server.State()
// Generate an alloc and upsert this.
alloc := mock.Alloc()
require.NoError(t, testState.UpsertAllocs(
structs.MsgTypeTestSetup, 10, []*structs.Allocation{alloc}))
// Generate a service registration, assigned the allocID to the
// mocked allocation ID, and upsert this.
serviceReg := mock.ServiceRegistrations()[0]
serviceReg.AllocID = alloc.ID
require.NoError(t, testState.UpsertServiceRegistrations(
structs.MsgTypeTestSetup, 20, []*structs.ServiceRegistration{serviceReg}))
// Build the HTTP request.
path := fmt.Sprintf("/v1/allocation/%s/services", alloc.ID)
req, err := http.NewRequest(http.MethodGet, path, nil)
require.NoError(t, err)
respW := httptest.NewRecorder()
// Send the HTTP request.
obj, err := s.Server.AllocSpecificRequest(respW, req)
require.NoError(t, err)
// Check the response.
require.Equal(t, "20", respW.Header().Get("X-Nomad-Index"))
require.ElementsMatch(t, []*structs.ServiceRegistration{serviceReg},
obj.([]*structs.ServiceRegistration))
},
name: "alloc has registrations",
},
{
testFn: func(s *TestAgent) {
// Grab the state, so we can manipulate it and test against it.
testState := s.Agent.server.State()
// Generate an alloc and upsert this.
alloc := mock.Alloc()
require.NoError(t, testState.UpsertAllocs(
structs.MsgTypeTestSetup, 10, []*structs.Allocation{alloc}))
// Build the HTTP request.
path := fmt.Sprintf("/v1/allocation/%s/services", alloc.ID)
req, err := http.NewRequest(http.MethodGet, path, nil)
require.NoError(t, err)
respW := httptest.NewRecorder()
// Send the HTTP request.
obj, err := s.Server.AllocSpecificRequest(respW, req)
require.NoError(t, err)
// Check the response.
require.Equal(t, "1", respW.Header().Get("X-Nomad-Index"))
require.ElementsMatch(t, []*structs.ServiceRegistration{},
obj.([]*structs.ServiceRegistration))
},
name: "alloc without registrations",
},
{
testFn: func(s *TestAgent) {
// Build the HTTP request.
path := fmt.Sprintf("/v1/allocation/%s/services", uuid.Generate())
req, err := http.NewRequest(http.MethodGet, path, nil)
require.NoError(t, err)
respW := httptest.NewRecorder()
// Send the HTTP request.
obj, err := s.Server.AllocSpecificRequest(respW, req)
require.Error(t, err)
require.Contains(t, err.Error(), "allocation not found")
require.Nil(t, obj)
},
name: "alloc not found",
},
{
testFn: func(s *TestAgent) {
// Build the HTTP request.
path := fmt.Sprintf("/v1/allocation/%s/services", uuid.Generate())
req, err := http.NewRequest(http.MethodHead, path, nil)
require.NoError(t, err)
respW := httptest.NewRecorder()
// Send the HTTP request.
obj, err := s.Server.AllocSpecificRequest(respW, req)
require.Error(t, err)
require.Contains(t, err.Error(), "Invalid method")
require.Nil(t, obj)
},
name: "alloc not found",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
httpTest(t, nil, tc.testFn)
})
}
}
func TestHTTP_AllocStats(t *testing.T) {
t.Parallel()
require := require.New(t)