Skip to content

Commit

Permalink
Add /v1/admin/asset-scrape-errors endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
talal committed Mar 24, 2020
1 parent db19c14 commit a027292
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 0 deletions.
35 changes: 35 additions & 0 deletions docs/api-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ This document uses the terminology defined in the [README.md](../README.md#termi
* [GET /v1/operations/recently-failed](#get-v1operationsrecently-failed)
* [GET /v1/operations/recently-succeeded](#get-v1operationsrecently-succeeded)
* [GET /v1/admin/resource-scrape-errors](#get-v1adminresource-scrape-errors)
* [GET /v1/admin/asset-scrape-errors](#get-v1adminasset-scrape-errors)

## GET /v1/projects/:id

Expand Down Expand Up @@ -459,3 +460,37 @@ additional fields:
only shown for non-domain resources.

For each resource, at most one error will be listed (the most recent one).

## GET /v1/admin/asset-scrape-errors

Shows information about asset scrape errors. This is intended to give operators
a view of scrape errors for all assets across all resources.

Returns `200` on success and a JSON response body like this:

```json
{
"asset_scrape_errors": [
{
"asset_id": "c991eb08-e14e-4559-94d6-c9c390c18776",
"asset_type": "nfs-shares",
"checked": {
"at": 1557144799,
"error": "cannot connect to OpenStack"
},
"domain_id": "481b2af2-d816-4453-8743-a05382e7d1ce",
"project_id": "89b76fc7-78fa-454c-b23b-674bd7589390"
}
]
}
```

Most fields on the top level have the same meaning as for `GET
/v1/projects/:id/assets/:type` (see above), except for the following additional
fields:

- `asset_id` identifies the concerning asset.
- `asset_type`, `project_id` and `domain_id` identify the resource to which
this asset belongs. `project_id` is only shown for non-domain resources.

For each asset, at most one error will be listed (the most recent one).
3 changes: 3 additions & 0 deletions internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ func (h *handler) BuildRouter() http.Handler {
router.Methods("GET").
Path(`/v1/admin/resource-scrape-errors`).
HandlerFunc(h.GetResourceScrapeErrors)
router.Methods("GET").
Path(`/v1/admin/asset-scrape-errors`).
HandlerFunc(h.GetAssetScrapeErrors)

return sre.Instrument(router)
}
Expand Down
65 changes: 65 additions & 0 deletions internal/api/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ type ResourceScrapeError struct {
Checked Checked `json:"checked"`
}

// AssetScrapeError is how a resource's scrape error appears in API.
type AssetScrapeError struct {
AssetUUID string `json:"asset_id"`
ProjectUUID string `json:"project_id,omitempty"`
DomainUUID string `json:"domain_id"`
AssetType string `json:"asset_type"`
Checked Checked `json:"checked"`
}

///////////////////////////////////////////////////////////////////////////////
// HTTP handlers

Expand Down Expand Up @@ -81,3 +90,59 @@ func (h handler) GetResourceScrapeErrors(w http.ResponseWriter, r *http.Request)

respondwith.JSON(w, http.StatusOK, result)
}

func (h handler) GetAssetScrapeErrors(w http.ResponseWriter, r *http.Request) {
sre.IdentifyEndpoint(r, "/v1/admin/asset-scrape-errors")
_, token := h.CheckToken(w, r)
if token == nil {
return
}
if !token.Require(w, "cluster:access") {
return
}

var result struct {
AssetScrapeErrors []AssetScrapeError `json:"asset_scrape_errors"`
}

var dbResources []db.Resource
_, err := h.DB.Select(&dbResources,
`SELECT * FROM resources ORDER BY id`)
if respondwith.ErrorText(w, err) {
return
}

for _, res := range dbResources {
var dbAssets []db.Asset
_, err := h.DB.Select(&dbAssets,
`SELECT * FROM assets
WHERE scrape_error_message != '' AND resource_id = $1
ORDER BY id
`, res.ID)
if respondwith.ErrorText(w, err) {
return
}

projectID := ""
// res.ScopeUUID is either a domain- or project UUID.
if res.ScopeUUID != res.DomainUUID {
projectID = res.ScopeUUID
}

for _, a := range dbAssets {
result.AssetScrapeErrors = append(result.AssetScrapeErrors,
AssetScrapeError{
AssetUUID: a.UUID,
ProjectUUID: projectID,
DomainUUID: res.DomainUUID,
AssetType: string(res.AssetType),
Checked: Checked{
AtUnix: a.CheckedAt.Unix(),
ErrorMessage: a.ScrapeErrorMessage,
},
})
}
}

respondwith.JSON(w, http.StatusOK, result)
}
36 changes: 36 additions & 0 deletions internal/api/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,39 @@ func TestGetResourceScrapeErrors(baseT *testing.T) {
}.Check(t.T, hh)
})
}

func TestGetAssetScrapeErrors(baseT *testing.T) {
t := test.T{T: baseT}
withHandler(t, nil, func(h *handler, hh http.Handler, mv *MockValidator, _ []db.Resource, _ []db.Asset) {

//endpoint requires a token with cluster access
mv.Forbid("cluster:access")
assert.HTTPRequest{
Method: "GET",
Path: "/v1/admin/asset-scrape-errors",
ExpectStatus: http.StatusForbidden,
}.Check(t.T, hh)
mv.Allow("cluster:access")

//happy path
assert.HTTPRequest{
Method: "GET",
Path: "/v1/admin/asset-scrape-errors",
ExpectStatus: http.StatusOK,
ExpectBody: assert.JSONObject{
"asset_scrape_errors": []assert.JSONObject{
assert.JSONObject{
"asset_id": "fooasset2",
"asset_type": "foo",
"checked": assert.JSONObject{
"at": 15,
"error": "unexpected uptime",
},
"domain_id": "domain1",
"project_id": "project1",
},
},
},
}.Check(t.T, hh)
})
}

0 comments on commit a027292

Please sign in to comment.