Skip to content
This repository has been archived by the owner on May 10, 2024. It is now read-only.

Commit

Permalink
Merge pull request #187 from keskad/main
Browse files Browse the repository at this point in the history
Add health check endpoint
  • Loading branch information
blackandred authored Feb 28, 2022
2 parents 7c8cca9 + 872de63 commit 9a3e184
Show file tree
Hide file tree
Showing 19 changed files with 230 additions and 31 deletions.
3 changes: 3 additions & 0 deletions server-go/Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
all: build run

test_health:
curl -s -X GET 'http://localhost:8080/health'

test_login:
curl -s -X POST -d '{"username":"admin","password":"admin"}' -H 'Content-Type: application/json' 'http://localhost:8080/api/stable/auth/login'
@echo "Now do export TOKEN=..."
Expand Down
1 change: 1 addition & 0 deletions server-go/config/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ type ConfigurationProvider interface {
GetSingleDocumentAnyType(kind string, id string, apiGroup string, apiVersion string) (string, error)

StoreDocument(kind string, document interface{}) error
GetHealth() error
}
16 changes: 16 additions & 0 deletions server-go/config/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package config
import (
"context"
"github.com/fatih/structs"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
Expand All @@ -18,6 +19,21 @@ type ConfigurationInKubernetes struct {
apiVersion string
}

func (o *ConfigurationInKubernetes) GetHealth() error {
resources := []schema.GroupVersionResource{
{Group: o.apiGroup, Version: o.apiVersion, Resource: "backupusers"},
{Group: o.apiGroup, Version: o.apiVersion, Resource: "backupcollections"},
}

for _, resource := range resources {
if _, err := o.api.Resource(resource).Namespace(o.namespace).List(context.Background(), metav1.ListOptions{}); err != nil {
return errors.Wrapf(err, "cannot access Kubrenetes resources: '%v'", resource.String())
}
}

return nil
}

func (o *ConfigurationInKubernetes) GetSingleDocumentAnyType(kind string, id string, apiGroup string, apiVersion string) (string, error) {
resource := schema.GroupVersionResource{Group: apiGroup, Version: apiVersion, Resource: kind}
object, err := o.api.Resource(resource).Namespace(o.namespace).Get(context.Background(), id, metav1.GetOptions{})
Expand Down
3 changes: 3 additions & 0 deletions server-go/core/ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@ import (
"github.com/riotkit-org/backup-repository/security"
"github.com/riotkit-org/backup-repository/storage"
"github.com/riotkit-org/backup-repository/users"
"gorm.io/gorm"
)

type ApplicationContainer struct {
Db *gorm.DB
Config *config.ConfigurationProvider
Users *users.Service
GrantedAccesses *security.Service
Collections *collections.Service
Storage *storage.Service
JwtSecretKey string
HealthCheckKey string
Locks *concurrency.LocksService
}
3 changes: 1 addition & 2 deletions server-go/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,4 @@ Interactions with server are done using HTTP API that talks JSON in both ways, a

### Collections

### Administrative

### [Administrative](api/administrative/README.md)
37 changes: 37 additions & 0 deletions server-go/docs/api/administrative/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
Administrative API endpoints
============================

## GET `/health``

**Example:**

```bash
curl -s -X GET 'http://localhost:8080/health'
```

**Example response (200):**

```json
{
"data": {
"health": [
{
"message": "OK",
"name": "DbValidator",
"status": true,
"statusText": "DbValidator=true"
},
{
"message": "OK",
"name": "StorageAvailabilityValidator",
"status": true,
"statusText": "StorageAvailabilityValidator=true"
}
]
},
"status": true
}
```

**Other responses:**
- [500](../common-responses.md)
6 changes: 3 additions & 3 deletions server-go/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ go 1.17
require (
github.com/appleboy/gin-jwt/v2 v2.8.0
github.com/fatih/structs v1.1.0
github.com/gin-contrib/timeout v0.0.3
github.com/gin-gonic/gin v1.7.7
github.com/google/uuid v1.3.0
github.com/jessevdk/go-flags v1.5.0
github.com/julianshen/gin-limiter v0.0.0-20161123033831-fc39b5e90fe7
github.com/labstack/gommon v0.3.1
github.com/pkg/errors v0.9.1
github.com/robfig/cron/v3 v3.0.1
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.0
Expand Down Expand Up @@ -38,7 +41,6 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-contrib/timeout v0.0.3 // indirect
github.com/go-logr/logr v1.2.0 // indirect
github.com/go-playground/locales v0.13.0 // indirect
github.com/go-playground/universal-translator v0.17.0 // indirect
Expand Down Expand Up @@ -66,13 +68,11 @@ require (
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/juju/ratelimit v1.0.1 // indirect
github.com/labstack/gommon v0.3.1 // indirect
github.com/leodido/go-urn v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-sqlite3 v1.14.9 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/tidwall/match v1.1.1 // indirect
Expand Down
13 changes: 7 additions & 6 deletions server-go/health/backupwindow.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import (

type BackupWindowValidator struct {
svc *storage.Service
c *collections.Collection
}

func (v BackupWindowValidator) Validate(c *collections.Collection) error {
latest, err := v.svc.FindLatestVersion(c.GetId())
func (v BackupWindowValidator) Validate() error {
latest, err := v.svc.FindLatestVersion(v.c.GetId())
if err != nil {
return err
}
Expand All @@ -23,11 +24,11 @@ func (v BackupWindowValidator) Validate(c *collections.Collection) error {
now := time.Now()

// Backup Windows are optional
if len(c.Spec.Windows) == 0 {
if len(v.c.Spec.Windows) == 0 {
return nil
}

for _, window := range c.Spec.Windows {
for _, window := range v.c.Spec.Windows {
matches, err := window.IsInPreviousWindowTimeSlot(now, latest.CreatedAt)
if err != nil {
return errors.New(fmt.Sprintf("failed to calculate previous run for window '%v' - %v", window, err))
Expand All @@ -47,6 +48,6 @@ func (v BackupWindowValidator) Validate(c *collections.Collection) error {
return errors.Errorf("previous backup was not executed in expected time slots: %v", strings.Trim(allowedSlots, ", "))
}

func NewBackupWindowValidator(svc *storage.Service) BackupWindowValidator {
return BackupWindowValidator{svc}
func NewBackupWindowValidator(svc *storage.Service, c *collections.Collection) BackupWindowValidator {
return BackupWindowValidator{svc, c}
}
22 changes: 22 additions & 0 deletions server-go/health/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package health

import (
"github.com/pkg/errors"
"github.com/riotkit-org/backup-repository/config"
)

type ConfigurationProviderValidator struct {
cfg config.ConfigurationProvider
}

func (v ConfigurationProviderValidator) Validate() error {
if err := v.cfg.GetHealth(); err != nil {
return errors.Wrapf(err, "configuration provider is not usable")
}

return nil
}

func NewConfigurationProviderValidator(cfg config.ConfigurationProvider) ConfigurationProviderValidator {
return ConfigurationProviderValidator{cfg}
}
22 changes: 22 additions & 0 deletions server-go/health/db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package health

import (
"github.com/pkg/errors"
"gorm.io/gorm"
)

type DbValidator struct {
db *gorm.DB
}

func (v DbValidator) Validate() error {
err := v.db.Raw("SELECT 1").Error
if err != nil {
return errors.Wrapf(err, "cannot connect to database")
}
return nil
}

func NewDbValidator(db *gorm.DB) DbValidator {
return DbValidator{db}
}
7 changes: 3 additions & 4 deletions server-go/health/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,19 @@ package health
import (
"encoding/json"
"fmt"
"github.com/riotkit-org/backup-repository/collections"
"reflect"
)

type Validator interface {
Validate(c *collections.Collection) error
Validate() error
}
type Validators []Validator

func (v Validators) Validate(c *collections.Collection) StatusCollection {
func (v Validators) Validate() StatusCollection {
var status StatusCollection

for _, validator := range v {
if err := validator.Validate(c); err != nil {
if err := validator.Validate(); err != nil {
status = append(status, Status{
Name: reflect.TypeOf(validator).Name(),
StatusMsg: err.Error(),
Expand Down
15 changes: 8 additions & 7 deletions server-go/health/size.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,18 @@ import (

type VersionsSizeValidator struct {
svc *storage.Service
c *collections.Collection
}

func (v VersionsSizeValidator) Validate(c *collections.Collection) error {
versions, err := v.svc.FindAllActiveVersionsFor(c.GetId())
func (v VersionsSizeValidator) Validate() error {
versions, err := v.svc.FindAllActiveVersionsFor(v.c.GetId())
if err != nil {
return errors.Wrapf(err, "Cannot list versions for collection id=%v", c.GetId())
return errors.Wrapf(err, "Cannot list versions for collection id=%v", v.c.GetId())
}

maxVersionSize, err := c.GetMaxOneVersionSizeInBytes()
maxVersionSize, err := v.c.GetMaxOneVersionSizeInBytes()
if err != nil {
return errors.Wrapf(err, "Cannot list versions for collection id=%v", c.GetId())
return errors.Wrapf(err, "Cannot list versions for collection id=%v", v.c.GetId())
}

for _, v := range versions {
Expand All @@ -30,6 +31,6 @@ func (v VersionsSizeValidator) Validate(c *collections.Collection) error {
return nil
}

func NewVersionsSizeValidator(svc *storage.Service) VersionsSizeValidator {
return VersionsSizeValidator{svc}
func NewVersionsSizeValidator(svc *storage.Service, c *collections.Collection) VersionsSizeValidator {
return VersionsSizeValidator{svc, c}
}
23 changes: 23 additions & 0 deletions server-go/health/storage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package health

import (
"github.com/pkg/errors"
"github.com/riotkit-org/backup-repository/storage"
)

type StorageAvailabilityValidator struct {
storage *storage.Service
}

func (v StorageAvailabilityValidator) Validate() error {
err := v.storage.TestReadWrite()
if err != nil {
return errors.Wrapf(err, "storage not operable")
}

return nil
}

func NewStorageValidator(storage *storage.Service) StorageAvailabilityValidator {
return StorageAvailabilityValidator{storage}
}
11 changes: 6 additions & 5 deletions server-go/health/sumofversions.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,18 @@ import (

type SumOfVersionsValidator struct {
svc *storage.Service
c *collections.Collection
}

func (v SumOfVersionsValidator) Validate(c *collections.Collection) error {
func (v SumOfVersionsValidator) Validate() error {
var totalSize int64
allActive, _ := v.svc.FindAllActiveVersionsFor(c.GetId())
allActive, _ := v.svc.FindAllActiveVersionsFor(v.c.GetId())

for _, version := range allActive {
totalSize += version.Filesize
}

maxCollectionSize, _ := c.GetCollectionMaxSize()
maxCollectionSize, _ := v.c.GetCollectionMaxSize()

if totalSize > maxCollectionSize {
return errors.Errorf("Summary of all files is %vb, while collection hard limit is %vb", totalSize, maxCollectionSize)
Expand All @@ -27,6 +28,6 @@ func (v SumOfVersionsValidator) Validate(c *collections.Collection) error {
return nil
}

func NewSumOfVersionsValidator(svc *storage.Service) SumOfVersionsValidator {
return SumOfVersionsValidator{svc}
func NewSumOfVersionsValidator(svc *storage.Service, c *collections.Collection) SumOfVersionsValidator {
return SumOfVersionsValidator{svc, c}
}
8 changes: 4 additions & 4 deletions server-go/http/collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,10 @@ func addCollectionHealthRoute(r *gin.Engine, ctx *core.ApplicationContainer, rat

// Run all the checks
healthStatuses := health.Validators{
health.NewBackupWindowValidator(ctx.Storage),
health.NewVersionsSizeValidator(ctx.Storage),
health.NewSumOfVersionsValidator(ctx.Storage),
}.Validate(collection)
health.NewBackupWindowValidator(ctx.Storage, collection),
health.NewVersionsSizeValidator(ctx.Storage, collection),
health.NewSumOfVersionsValidator(ctx.Storage, collection),
}.Validate()

if !healthStatuses.GetOverallStatus() {
ServerErrorResponseWithData(c, errors.New("one of checks failed"), gin.H{
Expand Down
39 changes: 39 additions & 0 deletions server-go/http/health.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package http

import (
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
"github.com/riotkit-org/backup-repository/core"
"github.com/riotkit-org/backup-repository/health"
)

func addServerHealthEndpoint(r *gin.Engine, ctx *core.ApplicationContainer, rateLimiter gin.HandlerFunc) {
r.GET("/health", rateLimiter, func(c *gin.Context) {
// Authorization
healthCode := c.GetHeader("Authorization")
if healthCode == "" {
healthCode = c.Query("code")
}
if healthCode != ctx.HealthCheckKey {
UnauthorizedResponse(c, errors.New("health code invalid. Should be provided withing 'Authorization' header or 'code' query string. Must match --health-check-code commandline switch value"))
return
}

healthStatuses := health.Validators{
health.NewDbValidator(ctx.Db),
health.NewStorageValidator(ctx.Storage),
health.NewConfigurationProviderValidator(*ctx.Config),
}.Validate()

if !healthStatuses.GetOverallStatus() {
ServerErrorResponseWithData(c, errors.New("one of checks failed"), gin.H{
"health": healthStatuses,
})
return
}

OKResponse(c, gin.H{
"health": healthStatuses,
})
})
}
Loading

0 comments on commit 9a3e184

Please sign in to comment.