Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: simplify health/readiness checks #79

Merged
merged 2 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,8 @@ linters: # All available linters list: <https://golangci-lint.run/usage/linters/
- copyloopvar # checks for pointers to enclosing loop variables
- gochecknoglobals # Checks that no globals are present in Go code
- gochecknoinits # Checks that no init functions are present in Go code
- gocognit # Computes and checks the cognitive complexity of functions
- goconst # Finds repeated strings that could be replaced by a constant
- gocritic # The most opinionated Go source code linter
- gocyclo # Computes and checks the cyclomatic complexity of functions
- gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification
- goimports # Goimports does everything that gofmt does. Additionally it checks unused imports
- revive
Expand Down
19 changes: 19 additions & 0 deletions api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package status

type Report struct {
PluginName string `json:"plugin_name"`
ErrorMessage string `json:"error_message"`
StatusCode int `json:"status_code"`
}

type JobsReport struct {
Pipeline string `json:"pipeline"`
Priority uint64 `json:"priority"`
Ready bool `json:"ready"`
Queue string `json:"queue"`
Active int64 `json:"active"`
Delayed int64 `json:"delayed"`
Reserved int64 `json:"reserved"`
Driver string `json:"driver"`
ErrorMessage string `json:"error_message"`
}
3 changes: 2 additions & 1 deletion go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/sftp v1.13.1 h1:I2qBYMChEhIjOgazfJmV3/mZM256btk6wkCDRmW7JYs=
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
github.com/roadrunner-server/jobs/v5 v5.0.4 h1:bRJpbVfTlkJraFattpmAu3Xg1hHwkcrDpgdCt91jAZc=
github.com/roadrunner-server/jobs/v5 v5.0.4/go.mod h1:me+Aaf45VQFg0GhuUQjZYUIY9MqhGB2MLy2A87WQ1lo=
github.com/roadrunner-server/status/v4 v4.4.15 h1:AAjCR/ofxxxIE7JdXRXV0jliaQM9ntSMdpgrqJH8MRk=
github.com/roadrunner-server/status/v4 v4.4.15/go.mod h1:hWKyeEfbohz8XqLlrD4+GXgna4lBPUzto+SVYP5XKOY=
github.com/sagikazarmark/crypt v0.10.0 h1:96E1qrToLBU6fGzo+PRRz7KGOc9FkYFiPnR3/zf8Smg=
Expand Down Expand Up @@ -293,7 +295,6 @@ golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA=
golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
Expand Down
163 changes: 125 additions & 38 deletions health.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package status

import (
"fmt"
"html"
"encoding/json"
"net/http"
"sync/atomic"

Expand Down Expand Up @@ -31,58 +30,146 @@ func (rd *Health) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}

if r == nil || r.URL == nil || r.URL.Query() == nil {
http.Error(
w,
"No plugins provided in query. Query should be in form of: health?plugin=plugin1&plugin=plugin2",
http.StatusBadRequest,
)
return
}
// report will be used either for all plugins or for the Plugins in the query
report := make([]*Report, 0, 2)

plg := r.URL.Query()[pluginsQuery]
// if no Plugins provided, check them all
if len(plg) == 0 {
rd.log.Debug("no plugins provided, checking all plugins")

for k, pl := range rd.statusRegistry {
if pl == nil {
report = append(report, &Report{
PluginName: k,
ErrorMessage: "plugin is nil or not initialized",
StatusCode: http.StatusNotFound,
})

rd.log.Info("plugin is nil or not initialized", zap.String("plugin", k))
continue
}

st, err := pl.Status()
if err != nil {
w.WriteHeader(rd.unavailableStatusCode)
report = append(report, &Report{
PluginName: k,
ErrorMessage: err.Error(),
StatusCode: rd.unavailableStatusCode,
})
continue
}

if st == nil {
report = append(report, &Report{
PluginName: k,
ErrorMessage: "plugin is not available, returned nil",
StatusCode: rd.unavailableStatusCode,
})
continue
}

pl := r.URL.Query()[pluginsQuery]
switch {
case st.Code >= 500:
w.WriteHeader(rd.unavailableStatusCode)

report = append(report, &Report{
PluginName: k,
ErrorMessage: "internal server error, see logs",
StatusCode: rd.unavailableStatusCode,
})
case st.Code >= 100 && st.Code <= 400:
report = append(report, &Report{
PluginName: k,
StatusCode: st.Code,
})
default:
report = append(report, &Report{
PluginName: k,
ErrorMessage: "unexpected status code",
StatusCode: st.Code,
})
}
}

data, err := json.Marshal(report)
if err != nil {
// TODO do we need to write this error to the ResponseWriter?
rd.log.Error("failed to marshal response", zap.Error(err))
return
}
// write the response
_, err = w.Write(data)
if err != nil {
rd.log.Error("failed to write response", zap.Error(err))
}

if len(pl) == 0 {
http.Error(
w,
"No plugins provided in query. Query should be in form of: health?plugin=plugin1&plugin=plugin2",
http.StatusBadRequest,
)
return
}

// iterate over all provided plugins
for i := 0; i < len(pl); i++ {
switch {
// check workers for the plugin
case rd.statusRegistry[pl[i]] != nil:
st, errS := rd.statusRegistry[pl[i]].Status()
if errS != nil {
http.Error(w, errS.Error(), rd.unavailableStatusCode)
return
// iterate over all provided Plugins
for i := 0; i < len(plg); i++ {
if svc, ok := rd.statusRegistry[plg[i]]; ok {
if svc == nil {
continue
}

st, err := rd.statusRegistry[plg[i]].Status()
if err != nil {
report = append(report, &Report{
PluginName: plg[i],
ErrorMessage: err.Error(),
StatusCode: http.StatusInternalServerError,
})

continue
}

if st == nil {
w.WriteHeader(rd.unavailableStatusCode)
// nil can be only if the service unavailable
_, _ = w.Write([]byte(fmt.Sprintf(template, html.EscapeString(pl[i]), rd.unavailableStatusCode)))
return
report = append(report, &Report{
PluginName: plg[i],
ErrorMessage: "plugin is not available",
StatusCode: rd.unavailableStatusCode,
})

continue
}

switch {
case st.Code >= 500:
// on >=500, write header, because it'll be written on Write (200)
w.WriteHeader(rd.unavailableStatusCode)
// if there is 500 or 503 status code return immediately
_, _ = w.Write([]byte(fmt.Sprintf(template, html.EscapeString(pl[i]), rd.unavailableStatusCode)))
return
report = append(report, &Report{
PluginName: plg[i],
ErrorMessage: "internal server error, see logs",
StatusCode: rd.unavailableStatusCode,
})
case st.Code >= 100 && st.Code <= 400:
_, _ = w.Write([]byte(fmt.Sprintf(template, html.EscapeString(pl[i]), st.Code)))
continue
report = append(report, &Report{
PluginName: plg[i],
StatusCode: st.Code,
})
default:
_, _ = w.Write([]byte(fmt.Sprintf("plugin: %s not found", html.EscapeString(pl[i]))))
report = append(report, &Report{
PluginName: plg[i],
ErrorMessage: "unexpected status code",
StatusCode: st.Code,
})
}
default:
_, _ = w.Write([]byte(fmt.Sprintf("plugin: %s not found", html.EscapeString(pl[i]))))
} else {
rd.log.Info("plugin does not support health checks", zap.String("plugin", plg[i]))
}
}

data, err := json.Marshal(report)
if err != nil {
rd.log.Error("failed to marshal response", zap.Error(err))
}

// write the response
_, err = w.Write(data)
if err != nil {
rd.log.Error("failed to write response", zap.Error(err))
}
}
43 changes: 25 additions & 18 deletions jobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,13 @@ package status

import (
"context"
"fmt"
"html"
"encoding/json"
"net/http"
"sync/atomic"

"go.uber.org/zap"
)

const (
jobsTemplate string = "plugin: %s: pipeline: %s | priority: %d | ready: %t | queue: %s | active: %d | delayed: %d | reserved: %d | driver: %s | error: %s \n"
)

type Jobs struct {
statusJobsRegistry JobsChecker
unavailableStatusCode int
Expand Down Expand Up @@ -47,19 +42,31 @@ func (jb *Jobs) ServeHTTP(w http.ResponseWriter, _ *http.Request) {
return
}

report := make([]*JobsReport, 0, len(jobStates))

// write info about underlying drivers
for i := 0; i < len(jobStates); i++ {
_, _ = w.Write([]byte(html.EscapeString(fmt.Sprintf(jobsTemplate,
"jobs", // only JOBS plugin
jobStates[i].Pipeline,
jobStates[i].Priority,
jobStates[i].Ready,
jobStates[i].Queue,
jobStates[i].Active,
jobStates[i].Delayed,
jobStates[i].Reserved,
jobStates[i].Driver,
jobStates[i].ErrorMessage,
))))
report = append(report, &JobsReport{
Pipeline: jobStates[i].Pipeline,
Priority: jobStates[i].Priority,
Ready: jobStates[i].Ready,
Queue: jobStates[i].Queue,
Active: jobStates[i].Active,
Delayed: jobStates[i].Delayed,
Reserved: jobStates[i].Reserved,
Driver: jobStates[i].Driver,
ErrorMessage: jobStates[i].ErrorMessage,
})
}

data, err := json.Marshal(report)
if err != nil {
jb.log.Error("failed to marshal jobs state report", zap.Error(err))
return
}

_, err = w.Write(data)
if err != nil {
jb.log.Error("failed to write jobs state report", zap.Error(err))
}
}
1 change: 0 additions & 1 deletion plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ const (
// PluginName declares public plugin name.
PluginName = "status"
pluginsQuery string = "plugin"
template string = "plugin: %s, status: %d\n"
)

type Configurer interface {
Expand Down
Loading
Loading