Skip to content

Commit

Permalink
added support for caps cached repository (ortuman#205)
Browse files Browse the repository at this point in the history
* added support for caps cached repository

* updated CHANGELOG.md
  • Loading branch information
ortuman authored Feb 3, 2022
1 parent f139b68 commit e8b2c1d
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* [ENHANCEMENT] Added support for Redis cached repository. #202
* [ENHANCEMENT] Cached VCard repository. #203
* [ENHANCEMENT] Cached Last repository. #204
* [ENHANCEMENT] Cached Capabilities repository. #205
* [CHANGE] Introduced measured repository transaction type. #200
* [CHANGE] Use PgSQL locker. #201
* [BUGFIX] Fix S2S db key check when nop KV is used. #199
4 changes: 2 additions & 2 deletions pkg/storage/cached/cached.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,12 @@ func New(cfg Config, rep repository.Repository, logger kitlog.Logger) (repositor
return &CachedRepository{
User: &cachedUserRep{c: c, rep: rep},
Last: &cachedLastRep{c: c, rep: rep},
Capabilities: rep,
Offline: rep,
Capabilities: &cachedCapsRep{c: c, rep: rep},
BlockList: rep,
Private: rep,
Roster: rep,
VCard: &cachedVCardRep{c: c, rep: rep},
Offline: rep,
Locker: rep,
rep: rep,
cache: c,
Expand Down
95 changes: 95 additions & 0 deletions pkg/storage/cached/capabilities.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright 2021 The jackal Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cachedrepository

import (
"context"
"fmt"

"github.com/golang/protobuf/proto"
capsmodel "github.com/ortuman/jackal/pkg/model/caps"
"github.com/ortuman/jackal/pkg/storage/repository"
)

type capsCodec struct {
val *capsmodel.Capabilities
}

func (c *capsCodec) encode(i interface{}) ([]byte, error) {
return proto.Marshal(i.(*capsmodel.Capabilities))
}

func (c *capsCodec) decode(b []byte) error {
var caps capsmodel.Capabilities
if err := proto.Unmarshal(b, &caps); err != nil {
return err
}
c.val = &caps
return nil
}

func (c *capsCodec) value() interface{} {
return c.val
}

type cachedCapsRep struct {
c Cache
rep repository.Capabilities
}

func (c *cachedCapsRep) UpsertCapabilities(ctx context.Context, caps *capsmodel.Capabilities) error {
op := updateOp{
c: c.c,
key: capsKey(caps.Node, caps.Ver),
updateFn: func(ctx context.Context) error {
return c.rep.UpsertCapabilities(ctx, caps)
},
}
return op.do(ctx)
}

func (c *cachedCapsRep) CapabilitiesExist(ctx context.Context, node, ver string) (bool, error) {
op := existsOp{
c: c.c,
key: capsKey(node, ver),
missFn: func(ctx context.Context) (bool, error) {
return c.rep.CapabilitiesExist(ctx, node, ver)
},
}
return op.do(ctx)
}

func (c *cachedCapsRep) FetchCapabilities(ctx context.Context, node, ver string) (*capsmodel.Capabilities, error) {
op := fetchOp{
c: c.c,
key: capsKey(node, ver),
codec: &capsCodec{},
missFn: func(ctx context.Context) (interface{}, error) {
return c.rep.FetchCapabilities(ctx, node, ver)
},
}
v, err := op.do(ctx)
switch {
case err != nil:
return nil, err
case v != nil:
return v.(*capsmodel.Capabilities), nil
}
return nil, nil
}

func capsKey(node, ver string) string {
return fmt.Sprintf("caps:%s:%s", node, ver)
}
129 changes: 129 additions & 0 deletions pkg/storage/cached/capabilities_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright 2021 The jackal Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cachedrepository

import (
"context"
"testing"

capsmodel "github.com/ortuman/jackal/pkg/model/caps"
"github.com/stretchr/testify/require"
)

func TestCachedCapsRep_UpsertCaps(t *testing.T) {
// given
var cacheKey string

cacheMock := &cacheMock{}
cacheMock.DelFunc = func(ctx context.Context, k string) error {
cacheKey = k
return nil
}

repMock := &repositoryMock{}
repMock.UpsertCapabilitiesFunc = func(ctx context.Context, caps *capsmodel.Capabilities) error {
return nil
}

// when
rep := cachedCapsRep{
c: cacheMock,
rep: repMock,
}
err := rep.UpsertCapabilities(context.Background(), &capsmodel.Capabilities{
Node: "n1",
Ver: "v1",
Features: []string{"f0"},
})

// then
require.NoError(t, err)
require.Equal(t, capsKey("n1", "v1"), cacheKey)
require.Len(t, repMock.UpsertCapabilitiesCalls(), 1)
}

func TestCachedCapsRep_CapsExist(t *testing.T) {
// given
cacheMock := &cacheMock{}
cacheMock.HasKeyFunc = func(ctx context.Context, k string) (bool, error) {
if k == capsKey("n1", "v1") {
return true, nil
}
return false, nil
}

repMock := &repositoryMock{}
repMock.CapabilitiesExistFunc = func(ctx context.Context, node, ver string) (bool, error) {
return node == "n2" && ver == "v2", nil
}

// when
rep := cachedCapsRep{
c: cacheMock,
rep: repMock,
}
ok1, err1 := rep.CapabilitiesExist(context.Background(), "n1", "v1")
ok2, err2 := rep.CapabilitiesExist(context.Background(), "n2", "v2")
ok3, err3 := rep.CapabilitiesExist(context.Background(), "n3", "v3")

// then
require.True(t, ok1)
require.NoError(t, err1)

require.True(t, ok2)
require.NoError(t, err2)

require.False(t, ok3)
require.NoError(t, err3)

require.Len(t, repMock.CapabilitiesExistCalls(), 2)
}

func TestCachedCapsRep_FetchCaps(t *testing.T) {
// given
cacheMock := &cacheMock{}
cacheMock.GetFunc = func(ctx context.Context, k string) ([]byte, error) {
return nil, nil
}
cacheMock.PutFunc = func(ctx context.Context, k string, val []byte) error {
return nil
}

repMock := &repositoryMock{}
repMock.FetchCapabilitiesFunc = func(ctx context.Context, node, ver string) (*capsmodel.Capabilities, error) {
return &capsmodel.Capabilities{
Node: "n1",
Ver: "v1",
}, nil
}

// when
rep := cachedCapsRep{
c: cacheMock,
rep: repMock,
}
caps, err := rep.FetchCapabilities(context.Background(), "n1", "v1")

// then
require.NotNil(t, caps)
require.NoError(t, err)

require.Equal(t, "n1", caps.Node)
require.Equal(t, "v1", caps.Ver)

require.Len(t, cacheMock.GetCalls(), 1)
require.Len(t, cacheMock.PutCalls(), 1)
require.Len(t, repMock.FetchCapabilitiesCalls(), 1)
}
4 changes: 2 additions & 2 deletions pkg/storage/cached/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ func newCacheTx(c Cache, tx repository.Transaction) *cachedTx {
return &cachedTx{
User: &cachedUserRep{c: c, rep: tx},
Last: &cachedLastRep{c: c, rep: tx},
Capabilities: tx,
Offline: tx,
Capabilities: &cachedCapsRep{c: c, rep: tx},
BlockList: tx,
Private: tx,
Roster: tx,
VCard: &cachedVCardRep{c: c, rep: tx},
Offline: tx,
Locker: tx,
}
}

0 comments on commit e8b2c1d

Please sign in to comment.