diff --git a/configs/config.yaml b/configs/config.yaml index a92f235..4f5816f 100644 --- a/configs/config.yaml +++ b/configs/config.yaml @@ -17,3 +17,4 @@ data: token: "${PRESHARED}" # token takes precedence over tokenFile tokenFile: "${PRESHARED_FILE:.secrets/local-spicedb-secret}" schemaFile: "${SCHEMA_FILE:deploy/schema.zed}" + fullyConsistent: false diff --git a/deploy/kessel-relations.yaml b/deploy/kessel-relations.yaml index f14ac77..463a11b 100644 --- a/deploy/kessel-relations.yaml +++ b/deploy/kessel-relations.yaml @@ -34,6 +34,7 @@ objects: token: "${PRESHARED}" # token takes precedence over tokenFile tokenFile: "${PRESHARED_FILE:.secrets/local-spicedb-secret}" schemaFile: "${SCHEMA_FILE:deploy/schema.zed}" + fullyConsistent: false - apiVersion: v1 kind: Secret metadata: diff --git a/internal/conf/conf.pb.go b/internal/conf/conf.pb.go index f29d44b..183cdf7 100644 --- a/internal/conf/conf.pb.go +++ b/internal/conf/conf.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.0 +// protoc-gen-go v1.36.3 // protoc (unknown) // source: conf.proto @@ -366,14 +366,15 @@ func (x *Server_Auth) GetJwksUrl() string { } type Data_SpiceDb struct { - state protoimpl.MessageState `protogen:"open.v1"` - UseTLS bool `protobuf:"varint,1,opt,name=useTLS,proto3" json:"useTLS,omitempty"` - Endpoint string `protobuf:"bytes,2,opt,name=endpoint,proto3" json:"endpoint,omitempty"` - Token string `protobuf:"bytes,3,opt,name=token,proto3" json:"token,omitempty"` - TokenFile string `protobuf:"bytes,4,opt,name=tokenFile,proto3" json:"tokenFile,omitempty"` - SchemaFile string `protobuf:"bytes,5,opt,name=schemaFile,proto3" json:"schemaFile,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + UseTLS bool `protobuf:"varint,1,opt,name=useTLS,proto3" json:"useTLS,omitempty"` + Endpoint string `protobuf:"bytes,2,opt,name=endpoint,proto3" json:"endpoint,omitempty"` + Token string `protobuf:"bytes,3,opt,name=token,proto3" json:"token,omitempty"` + TokenFile string `protobuf:"bytes,4,opt,name=tokenFile,proto3" json:"tokenFile,omitempty"` + SchemaFile string `protobuf:"bytes,5,opt,name=schemaFile,proto3" json:"schemaFile,omitempty"` + FullyConsistent bool `protobuf:"varint,6,opt,name=fullyConsistent,proto3" json:"fullyConsistent,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Data_SpiceDb) Reset() { @@ -441,6 +442,13 @@ func (x *Data_SpiceDb) GetSchemaFile() string { return "" } +func (x *Data_SpiceDb) GetFullyConsistent() bool { + if x != nil { + return x.FullyConsistent + } + return false +} + var File_conf_proto protoreflect.FileDescriptor var file_conf_proto_rawDesc = []byte{ @@ -485,11 +493,11 @@ var file_conf_proto_rawDesc = []byte{ 0x01, 0x28, 0x08, 0x52, 0x0a, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x75, 0x74, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x6a, 0x77, 0x6b, 0x73, 0x55, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6a, 0x77, 0x6b, 0x73, 0x55, 0x72, 0x6c, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x6d, 0x69, - 0x6e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0xce, 0x01, 0x0a, 0x04, 0x44, 0x61, + 0x6e, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x22, 0xf8, 0x01, 0x0a, 0x04, 0x44, 0x61, 0x74, 0x61, 0x12, 0x32, 0x0a, 0x07, 0x73, 0x70, 0x69, 0x63, 0x65, 0x44, 0x62, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6b, 0x72, 0x61, 0x74, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x2e, 0x53, 0x70, 0x69, 0x63, 0x65, 0x44, 0x62, 0x52, 0x07, 0x73, - 0x70, 0x69, 0x63, 0x65, 0x44, 0x62, 0x1a, 0x91, 0x01, 0x0a, 0x07, 0x53, 0x70, 0x69, 0x63, 0x65, + 0x70, 0x69, 0x63, 0x65, 0x44, 0x62, 0x1a, 0xbb, 0x01, 0x0a, 0x07, 0x53, 0x70, 0x69, 0x63, 0x65, 0x44, 0x62, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x54, 0x4c, 0x53, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x75, 0x73, 0x65, 0x54, 0x4c, 0x53, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e, @@ -498,11 +506,14 @@ var file_conf_proto_rawDesc = []byte{ 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x46, 0x69, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x46, 0x69, 0x6c, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x46, 0x69, 0x6c, 0x65, 0x42, 0x3c, 0x5a, 0x3a, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, - 0x2d, 0x6b, 0x65, 0x73, 0x73, 0x65, 0x6c, 0x2f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x2d, 0x61, 0x70, 0x69, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x63, - 0x6f, 0x6e, 0x66, 0x3b, 0x63, 0x6f, 0x6e, 0x66, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x66, 0x75, + 0x6c, 0x6c, 0x79, 0x43, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x0f, 0x66, 0x75, 0x6c, 0x6c, 0x79, 0x43, 0x6f, 0x6e, 0x73, 0x69, 0x73, + 0x74, 0x65, 0x6e, 0x74, 0x42, 0x3c, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2d, 0x6b, 0x65, 0x73, 0x73, 0x65, + 0x6c, 0x2f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2d, 0x61, 0x70, 0x69, 0x2f, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x3b, 0x63, 0x6f, + 0x6e, 0x66, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/internal/conf/conf.proto b/internal/conf/conf.proto index f2d35bf..b7cd909 100644 --- a/internal/conf/conf.proto +++ b/internal/conf/conf.proto @@ -40,6 +40,7 @@ message Data { string token = 3; string tokenFile = 4; string schemaFile = 5; + bool fullyConsistent = 6; } SpiceDb spiceDb = 1; } diff --git a/internal/data/LocalSpiceDbContainer.go b/internal/data/LocalSpiceDbContainer.go index b0dc9b1..3d3801a 100644 --- a/internal/data/LocalSpiceDbContainer.go +++ b/internal/data/LocalSpiceDbContainer.go @@ -33,6 +33,9 @@ const ( SpicedbSchemaBootstrapFile = "spicedb-test-data/basic_schema.zed" // SpicedbRelationsBootstrapFile specifies an optional bootstrap file containing relations to be used for testing SpicedbRelationsBootstrapFile = "" + // FullyConsistent specifices the consistency mode used for our read API calls + // may experience different results between tests and manual probing if the values differ + FullyConsistent = true // Should probably be inline with our config file. (TODO: Can we make our tests grab the same value?) ) // LocalSpiceDbContainer struct that holds pointers to the container, dockertest pool and exposes the port @@ -136,7 +139,9 @@ func (l *LocalSpiceDbContainer) NewToken() (string, error) { // WaitForQuantizationInterval needed to avoid read-before-write when loading the schema func (l *LocalSpiceDbContainer) WaitForQuantizationInterval() { - time.Sleep(10 * time.Millisecond) + if !FullyConsistent { + time.Sleep(10 * time.Millisecond) + } } // CreateClient creates a new client that connects to the dockerized spicedb instance and the right store @@ -162,10 +167,11 @@ func (l *LocalSpiceDbContainer) CreateSpiceDbRepository() (*SpiceDbRepository, e defer os.RemoveAll(tmpDir) spiceDbConf := &conf.Data_SpiceDb{ - UseTLS: false, - Endpoint: "localhost:" + l.port, - Token: tmpFile.Name(), - SchemaFile: l.schemaLocation, + UseTLS: false, + Endpoint: "localhost:" + l.port, + Token: tmpFile.Name(), + SchemaFile: l.schemaLocation, + FullyConsistent: FullyConsistent, // Should be inline with our config file } repo, _, err := NewSpiceDbRepository(&conf.Data{SpiceDb: spiceDbConf}, l.logger) if err != nil { diff --git a/internal/data/spicedb.go b/internal/data/spicedb.go index 23eb676..55c43bb 100644 --- a/internal/data/spicedb.go +++ b/internal/data/spicedb.go @@ -35,6 +35,12 @@ const ( relationPrefix = "t_" ) +var ( + // Default consistency for read APIs is minimize_latency + // will attempt to minimize the latency of the API call by selecting data that is most likely exist in the cache. + consistency = &v1.Consistency{Requirement: &v1.Consistency_MinimizeLatency{MinimizeLatency: true}} +) + // NewSpiceDbRepository . func NewSpiceDbRepository(c *conf.Data, logger log.Logger) (*SpiceDbRepository, func(), error) { log.NewHelper(logger).Info("creating spicedb connection") @@ -90,6 +96,11 @@ func NewSpiceDbRepository(c *conf.Data, logger log.Logger) (*SpiceDbRepository, log.NewHelper(logger).Info("spicedb connection cleanup requested (nothing to clean up)") } + if c.SpiceDb.FullyConsistent { + // will ensure that all data used is fully consistent with the latest data available within the SpiceDB datastore. + consistency = &v1.Consistency{Requirement: &v1.Consistency_FullyConsistent{FullyConsistent: true}} + } + return &SpiceDbRepository{client, healthClient, c.SpiceDb.SchemaFile, false}, cleanup, nil } @@ -128,6 +139,7 @@ func (s *SpiceDbRepository) LookupSubjects(ctx context.Context, subject_type *ap } req := &v1.LookupSubjectsRequest{ + Consistency: consistency, Resource: &v1.ObjectReference{ ObjectType: kesselTypeToSpiceDBType(object.Type), ObjectId: object.Id, @@ -194,6 +206,7 @@ func (s *SpiceDbRepository) LookupResources(ctx context.Context, resouce_type *a } } client, err := s.client.LookupResources(ctx, &v1.LookupResourcesRequest{ + Consistency: consistency, ResourceObjectType: kesselTypeToSpiceDBType(resouce_type), Permission: relation, Subject: &v1.SubjectReference{ @@ -347,6 +360,7 @@ func (s *SpiceDbRepository) ReadRelationships(ctx context.Context, filter *apiV1 } req := &v1.ReadRelationshipsRequest{ + Consistency: consistency, RelationshipFilter: relationshipFilter, OptionalLimit: limit, OptionalCursor: cursor, @@ -448,9 +462,10 @@ func (s *SpiceDbRepository) Check(ctx context.Context, check *apiV1beta1.CheckRe ObjectId: check.GetResource().GetId(), } req := &v1.CheckPermissionRequest{ - Resource: resource, - Permission: check.GetRelation(), - Subject: subject, + Consistency: consistency, + Resource: resource, + Permission: check.GetRelation(), + Subject: subject, } checkResponse, err := s.client.CheckPermission(ctx, req) if err != nil {