diff --git a/design/design.go b/design/design.go index 16a698890a20ddf0466f2f8c1175e5ddcdcd6d0e..ab4a588b968db49bc06f167adf45f447df6868f9 100644 --- a/design/design.go +++ b/design/design.go @@ -29,6 +29,9 @@ var _ = Service("policy", func() { Header("evaluationID:x-evaluation-id", String, "EvaluationID allows overwriting the randomly generated evaluationID", func() { Example("did:web:example.com") }) + Header("ttl:x-cache-ttl", Int, "Policy result cache TTL in seconds", func() { + Example(60) + }) Body("input") Response(StatusOK, func() { Body("result") diff --git a/design/types.go b/design/types.go index e301f2853833ccdc5e3cb2492ca05cc42e87c8d0..33542dfcf8e0900821cc4909eda4f17610be8df9 100644 --- a/design/types.go +++ b/design/types.go @@ -15,6 +15,7 @@ var EvaluateRequest = Type("EvaluateRequest", func() { }) Field(4, "input", Any, "Input data passed to the policy execution runtime.") Field(5, "evaluationID", String, "Identifier created by external system and passed as parameter to overwrite the randomly generated evaluationID.") + Field(6, "ttl", Int, "TTL for storing policy result in cache") Required("group", "policyName", "version") }) diff --git a/internal/clients/cache/client.go b/internal/clients/cache/client.go index a128681312e815b82e88341fca684865faf897f8..4e317d47408226df5053806073ddb2734efd035f 100644 --- a/internal/clients/cache/client.go +++ b/internal/clients/cache/client.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "net/http" + "strconv" "gitlab.com/gaia-x/data-infrastructure-federation-services/tsa/golib/errors" ) @@ -29,7 +30,7 @@ func New(addr string, opts ...Option) *Client { return c } -func (c *Client) Set(ctx context.Context, key, namespace, scope string, value []byte) error { +func (c *Client) Set(ctx context.Context, key, namespace, scope string, value []byte, ttl int) error { req, err := http.NewRequestWithContext(ctx, "POST", c.addr+"/v1/cache", bytes.NewReader(value)) if err != nil { return err @@ -40,6 +41,9 @@ func (c *Client) Set(ctx context.Context, key, namespace, scope string, value [] "x-cache-namespace": []string{namespace}, "x-cache-scope": []string{scope}, } + if ttl != 0 { + req.Header.Add("x-cache-ttl", strconv.Itoa(ttl)) + } resp, err := c.httpClient.Do(req) if err != nil { diff --git a/internal/service/policy/policyfakes/fake_cache.go b/internal/service/policy/policyfakes/fake_cache.go index 711539af213407e90c2782fbf313642a3457a89f..957b761a068952f4c6fd20fe9c4f891ab6bbdd10 100644 --- a/internal/service/policy/policyfakes/fake_cache.go +++ b/internal/service/policy/policyfakes/fake_cache.go @@ -25,7 +25,7 @@ type FakeCache struct { result1 []byte result2 error } - SetStub func(context.Context, string, string, string, []byte) error + SetStub func(context.Context, string, string, string, []byte, int) error setMutex sync.RWMutex setArgsForCall []struct { arg1 context.Context @@ -33,6 +33,7 @@ type FakeCache struct { arg3 string arg4 string arg5 []byte + arg6 int } setReturns struct { result1 error @@ -111,7 +112,7 @@ func (fake *FakeCache) GetReturnsOnCall(i int, result1 []byte, result2 error) { }{result1, result2} } -func (fake *FakeCache) Set(arg1 context.Context, arg2 string, arg3 string, arg4 string, arg5 []byte) error { +func (fake *FakeCache) Set(arg1 context.Context, arg2 string, arg3 string, arg4 string, arg5 []byte, arg6 int) error { var arg5Copy []byte if arg5 != nil { arg5Copy = make([]byte, len(arg5)) @@ -125,13 +126,14 @@ func (fake *FakeCache) Set(arg1 context.Context, arg2 string, arg3 string, arg4 arg3 string arg4 string arg5 []byte - }{arg1, arg2, arg3, arg4, arg5Copy}) + arg6 int + }{arg1, arg2, arg3, arg4, arg5Copy, arg6}) stub := fake.SetStub fakeReturns := fake.setReturns - fake.recordInvocation("Set", []interface{}{arg1, arg2, arg3, arg4, arg5Copy}) + fake.recordInvocation("Set", []interface{}{arg1, arg2, arg3, arg4, arg5Copy, arg6}) fake.setMutex.Unlock() if stub != nil { - return stub(arg1, arg2, arg3, arg4, arg5) + return stub(arg1, arg2, arg3, arg4, arg5, arg6) } if specificReturn { return ret.result1 @@ -145,17 +147,17 @@ func (fake *FakeCache) SetCallCount() int { return len(fake.setArgsForCall) } -func (fake *FakeCache) SetCalls(stub func(context.Context, string, string, string, []byte) error) { +func (fake *FakeCache) SetCalls(stub func(context.Context, string, string, string, []byte, int) error) { fake.setMutex.Lock() defer fake.setMutex.Unlock() fake.SetStub = stub } -func (fake *FakeCache) SetArgsForCall(i int) (context.Context, string, string, string, []byte) { +func (fake *FakeCache) SetArgsForCall(i int) (context.Context, string, string, string, []byte, int) { fake.setMutex.RLock() defer fake.setMutex.RUnlock() argsForCall := fake.setArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5, argsForCall.arg6 } func (fake *FakeCache) SetReturns(result1 error) { diff --git a/internal/service/policy/service.go b/internal/service/policy/service.go index 8ff2196dae5f3bf179432cb51a5c87238f1eed29..e6cd92ae23ba29cb5b0af3d43a8514a2ff9796b0 100644 --- a/internal/service/policy/service.go +++ b/internal/service/policy/service.go @@ -21,7 +21,7 @@ import ( //go:generate counterfeiter . RegoCache type Cache interface { - Set(ctx context.Context, key, namespace, scope string, value []byte) error + Set(ctx context.Context, key, namespace, scope string, value []byte, ttl int) error Get(ctx context.Context, key, namespace, scope string) ([]byte, error) } @@ -116,7 +116,11 @@ func (s *Service) Evaluate(ctx context.Context, req *policy.EvaluateRequest) (*p return nil, errors.New("error encoding result to json") } - if err := s.cache.Set(ctx, evaluationID, "", "", jsonValue); err != nil { + var ttl int + if req.TTL != nil { + ttl = *req.TTL + } + if err := s.cache.Set(ctx, evaluationID, "", "", jsonValue, ttl); err != nil { logger.Error("error storing policy result in cache", zap.Error(err)) return nil, errors.New("error storing policy result in cache") } diff --git a/internal/service/policy/service_test.go b/internal/service/policy/service_test.go index 9748c6118228ec14c8865f6fda732923c2f818ee..a239e5e7187f82f5e7a472a8efa2154918ca2c01 100644 --- a/internal/service/policy/service_test.go +++ b/internal/service/policy/service_test.go @@ -10,6 +10,7 @@ import ( "go.uber.org/zap" "gitlab.com/gaia-x/data-infrastructure-federation-services/tsa/golib/errors" + "gitlab.com/gaia-x/data-infrastructure-federation-services/tsa/golib/ptr" goapolicy "gitlab.com/gaia-x/data-infrastructure-federation-services/tsa/policy/gen/policy" "gitlab.com/gaia-x/data-infrastructure-federation-services/tsa/policy/internal/service/policy" "gitlab.com/gaia-x/data-infrastructure-federation-services/tsa/policy/internal/service/policy/policyfakes" @@ -72,7 +73,7 @@ func TestService_Evaluate(t *testing.T) { }, }, cache: &policyfakes.FakeCache{ - SetStub: func(ctx context.Context, s string, s2 string, s3 string, bytes []byte) error { + SetStub: func(ctx context.Context, s string, s2 string, s3 string, bytes []byte, i int) error { return nil }, }, @@ -152,7 +153,7 @@ func TestService_Evaluate(t *testing.T) { }, }, cache: &policyfakes.FakeCache{ - SetStub: func(ctx context.Context, s string, s2 string, s3 string, bytes []byte) error { + SetStub: func(ctx context.Context, s string, s2 string, s3 string, bytes []byte, i int) error { return nil }, }, @@ -181,7 +182,7 @@ func TestService_Evaluate(t *testing.T) { }, }, cache: &policyfakes.FakeCache{ - SetStub: func(ctx context.Context, s string, s2 string, s3 string, bytes []byte) error { + SetStub: func(ctx context.Context, s string, s2 string, s3 string, bytes []byte, i int) error { return errors.New("some error") }, }, @@ -209,7 +210,42 @@ func TestService_Evaluate(t *testing.T) { }, }, cache: &policyfakes.FakeCache{ - SetStub: func(ctx context.Context, s string, s2 string, s3 string, bytes []byte) error { + SetStub: func(ctx context.Context, s string, s2 string, s3 string, bytes []byte, i int) error { + return nil + }, + }, + res: &goapolicy.EvaluateResult{ + Result: map[string]interface{}{"hello": "world"}, + }, + }, + { + name: "policy is evaluated successfully with TTL sent in the request headers", + req: &goapolicy.EvaluateRequest{ + Group: "testgroup", + PolicyName: "example", + Version: "1.0", + Input: map[string]interface{}{"msg": "yes"}, + TTL: ptr.Int(30), + }, + regocache: &policyfakes.FakeRegoCache{ + GetStub: func(key string) (*rego.PreparedEvalQuery, bool) { + return nil, false + }, + }, + storage: &policyfakes.FakeStorage{ + PolicyStub: func(ctx context.Context, s string, s2 string, s3 string) (*storage.Policy, error) { + return &storage.Policy{ + Name: "example", + Group: "testgroup", + Version: "1.0", + Rego: testPolicyBlankAssignment, + Locked: false, + LastUpdate: time.Now(), + }, nil + }, + }, + cache: &policyfakes.FakeCache{ + SetStub: func(ctx context.Context, s string, s2 string, s3 string, bytes []byte, i int) error { return nil }, }, @@ -239,7 +275,7 @@ func TestService_Evaluate(t *testing.T) { }, }, cache: &policyfakes.FakeCache{ - SetStub: func(ctx context.Context, s string, s2 string, s3 string, bytes []byte) error { + SetStub: func(ctx context.Context, s string, s2 string, s3 string, bytes []byte, i int) error { return nil }, },