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

Implement Readyz endpoint in Relations API #111

Merged
merged 10 commits into from
Jun 27, 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
88 changes: 64 additions & 24 deletions api/health/v1/health.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions api/health/v1/health.pb.validate.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions api/health/v1/health.proto
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@ service KesselHealth {
}

message GetLivezRequest {}
message GetLivezReply {}
message GetLivezReply {
string status = 1;
uint32 code = 2;
}

message GetReadyzRequest {}
message GetReadyzReply {}
message GetReadyzReply {
string status = 1;
uint32 code = 2;
}
3 changes: 2 additions & 1 deletion cmd/kessel-relations/wire_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion internal/biz/biz.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ import (
)

// ProviderSet is biz providers.
var ProviderSet = wire.NewSet(NewCreateRelationshipsUsecase, NewReadRelationshipsUsecase, NewDeleteRelationshipsUsecase, NewCheckUsecase, NewGetSubjectsUseCase, NewGetResourcesUseCase)
var ProviderSet = wire.NewSet(NewCreateRelationshipsUsecase, NewReadRelationshipsUsecase, NewDeleteRelationshipsUsecase, NewCheckUsecase, NewGetSubjectsUseCase, NewGetResourcesUseCase, NewIsBackendAvailableUsecase)
13 changes: 13 additions & 0 deletions internal/biz/health.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package biz

type IsBackendAvaliableUsecase struct {
repo ZanzibarRepository
}

func NewIsBackendAvailableUsecase(repo ZanzibarRepository) *IsBackendAvaliableUsecase {
return &IsBackendAvaliableUsecase{repo: repo}
}

func (rc *IsBackendAvaliableUsecase) IsBackendAvailable() error {
return rc.repo.IsBackendAvailable()
}
2 changes: 2 additions & 0 deletions internal/biz/relationships.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package biz

import (
"context"

v0 "github.com/project-kessel/relations-api/api/relations/v0"

"github.com/go-kratos/kratos/v2/log"
Expand Down Expand Up @@ -32,6 +33,7 @@ type ZanzibarRepository interface {
DeleteRelationships(context.Context, *v0.RelationTupleFilter) error
LookupSubjects(ctx context.Context, subjectType *v0.ObjectType, subject_relation, relation string, resource *v0.ObjectReference, limit uint32, continuation ContinuationToken) (chan *SubjectResult, chan error, error)
LookupResources(ctx context.Context, resouce_type *v0.ObjectType, relation string, subject *v0.SubjectReference, limit uint32, continuation ContinuationToken) (chan *ResourceResult, chan error, error)
IsBackendAvailable() error
}

type CheckUsecase struct {
Expand Down
1 change: 0 additions & 1 deletion internal/conf/conf.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 38 additions & 2 deletions internal/data/spicedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io"
"os"
"strings"
"time"

apiV0 "github.com/project-kessel/relations-api/api/relations/v0"
"github.com/project-kessel/relations-api/internal/biz"
Expand All @@ -18,11 +19,13 @@ import (
"github.com/go-kratos/kratos/v2/log"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/health/grpc_health_v1"
)

// SpiceDbRepository .
type SpiceDbRepository struct {
client *authzed.Client
client *authzed.Client
healthClient grpc_health_v1.HealthClient
}

// NewSpiceDbRepository .
Expand Down Expand Up @@ -66,11 +69,21 @@ func NewSpiceDbRepository(c *conf.Data, logger log.Logger) (*SpiceDbRepository,
return nil, nil, fmt.Errorf("error creating spicedb client: %w", err)
}

// Create health client for readyz
conn, err := grpc.NewClient(
c.SpiceDb.Endpoint,
opts...,
)
if err != nil {
return nil, nil, fmt.Errorf("error creating grpc health client: %w", err)
}
healthClient := grpc_health_v1.NewHealthClient(conn)

cleanup := func() {
log.NewHelper(logger).Info("spicedb connection cleanup requested (nothing to clean up)")
}

return &SpiceDbRepository{client}, cleanup, nil
return &SpiceDbRepository{client, healthClient}, cleanup, nil
}

func (s *SpiceDbRepository) LookupSubjects(ctx context.Context, subject_type *apiV0.ObjectType, subject_relation, relation string, object *apiV0.ObjectReference, limit uint32, continuation biz.ContinuationToken) (chan *biz.SubjectResult, chan error, error) {
Expand Down Expand Up @@ -326,6 +339,29 @@ func (s *SpiceDbRepository) Check(ctx context.Context, check *apiV0.CheckRequest
return &apiV0.CheckResponse{Allowed: apiV0.CheckResponse_ALLOWED_FALSE}, nil
}

func (s *SpiceDbRepository) IsBackendAvailable() error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

resp, err := s.healthClient.Check(ctx, &grpc_health_v1.HealthCheckRequest{})
if err != nil {
return err
}

select {
case <-ctx.Done():
return fmt.Errorf("timeout connecting to backend")
default:
switch resp.Status {
case grpc_health_v1.HealthCheckResponse_NOT_SERVING, grpc_health_v1.HealthCheckResponse_SERVICE_UNKNOWN:
return fmt.Errorf("error connecting to backend: %v", resp.Status.String())
case grpc_health_v1.HealthCheckResponse_SERVING:
return nil
}
}
return fmt.Errorf("error connecting to backend")
}

func createSpiceDbRelationshipFilter(filter *apiV0.RelationTupleFilter) *v1.RelationshipFilter {
spiceDbRelationshipFilter := &v1.RelationshipFilter{
ResourceType: filter.GetResourceType(),
Expand Down
31 changes: 29 additions & 2 deletions internal/data/spicedb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package data
import (
"context"
"fmt"
apiV0 "github.com/project-kessel/relations-api/api/relations/v0"
"github.com/project-kessel/relations-api/internal/biz"
"os"
"testing"

apiV0 "github.com/project-kessel/relations-api/api/relations/v0"
"github.com/project-kessel/relations-api/internal/biz"
"github.com/project-kessel/relations-api/internal/conf"

"github.com/go-kratos/kratos/v2/log"
"github.com/go-kratos/kratos/v2/middleware/tracing"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -117,6 +119,31 @@ func TestSecondCreateRelationshipSucceedsWithTouchTrue(t *testing.T) {
assert.True(t, exists)
}

func TestIsBackendAvailable(t *testing.T) {
lennysgarage marked this conversation as resolved.
Show resolved Hide resolved
t.Parallel()

spiceDbrepo, err := container.CreateSpiceDbRepository()
assert.NoError(t, err)

err = spiceDbrepo.IsBackendAvailable()
assert.NoError(t, err)
}

func TestIsBackendUnavailable(t *testing.T) {
t.Parallel()

spiceDBRepo, _, err := NewSpiceDbRepository(&conf.Data{
SpiceDb: &conf.Data_SpiceDb{
Endpoint: "-1",
Token: "foobar",
UseTLS: true,
}}, log.GetLogger())
assert.NoError(t, err)

err = spiceDBRepo.IsBackendAvailable()
assert.Error(t, err)
}

func TestCreateRelationshipFailsWithBadSubjectType(t *testing.T) {
t.Parallel()

Expand Down
17 changes: 13 additions & 4 deletions internal/service/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,28 @@ import (
"context"

pb "github.com/project-kessel/relations-api/api/health/v1"
"github.com/project-kessel/relations-api/internal/biz"
)

type HealthService struct {
pb.UnimplementedKesselHealthServer
backendUseCase *biz.IsBackendAvaliableUsecase
}

func NewHealthService() *HealthService {
return &HealthService{}
func NewHealthService(backendUsecase *biz.IsBackendAvaliableUsecase) *HealthService {
return &HealthService{
backendUseCase: backendUsecase,
}
}

func (s *HealthService) GetLivez(ctx context.Context, req *pb.GetLivezRequest) (*pb.GetLivezReply, error) {
return &pb.GetLivezReply{}, nil
return &pb.GetLivezReply{Status: "OK", Code: 200}, nil
}

func (s *HealthService) GetReadyz(ctx context.Context, req *pb.GetReadyzRequest) (*pb.GetReadyzReply, error) {
return &pb.GetReadyzReply{}, nil
err := s.backendUseCase.IsBackendAvailable()
if err != nil {
return &pb.GetReadyzReply{Status: "Unavailable", Code: 503}, nil
}
return &pb.GetReadyzReply{Status: "OK", Code: 200}, nil
}
Loading
Loading