From 9c05300a9d01fed835bbd0a70cb6edf2131ba89d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Francisco=20L=C3=B3pez?= Date: Thu, 21 Dec 2023 11:38:37 -0300 Subject: [PATCH] Add ZCache pkg with full redis interface implementation (#40) * Add zcache pkg * tests * mutex tests --- go.mod | 8 +++ go.sum | 26 ++++++++ pkg/zcache/config.go | 40 +++++++++++ pkg/zcache/readme.md | 126 +++++++++++++++++++++++++++++++++++ pkg/zcache/redis_cache.go | 73 ++++++++++++++++++++ pkg/zcache/zcache.go | 30 +++++++++ pkg/zcache/zcache_mock.go | 76 +++++++++++++++++++++ pkg/zcache/zcache_test.go | 137 ++++++++++++++++++++++++++++++++++++++ pkg/zcache/zmutex.go | 37 ++++++++++ pkg/zcache/zmutex_mock.go | 24 +++++++ pkg/zcache/zmutex_test.go | 53 +++++++++++++++ 11 files changed, 630 insertions(+) create mode 100644 pkg/zcache/config.go create mode 100644 pkg/zcache/readme.md create mode 100644 pkg/zcache/redis_cache.go create mode 100644 pkg/zcache/zcache.go create mode 100644 pkg/zcache/zcache_mock.go create mode 100644 pkg/zcache/zcache_test.go create mode 100644 pkg/zcache/zmutex.go create mode 100644 pkg/zcache/zmutex_mock.go create mode 100644 pkg/zcache/zmutex_test.go diff --git a/go.mod b/go.mod index 415861e..60faa6c 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,11 @@ go 1.19 require ( github.com/ClickHouse/clickhouse-go/v2 v2.15.0 + github.com/alicebob/miniredis/v2 v2.31.0 github.com/go-chi/chi/v5 v5.0.10 github.com/go-chi/cors v1.2.1 + github.com/go-redis/redis/v8 v8.11.5 + github.com/go-redsync/redsync/v4 v4.11.0 github.com/prometheus/client_golang v1.17.0 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.17.0 @@ -20,15 +23,19 @@ require ( require ( github.com/ClickHouse/ch-go v0.58.2 // indirect + github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect github.com/andybalholm/brotli v1.0.6 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-faster/city v1.0.1 // indirect github.com/go-faster/errors v0.6.1 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/uuid v1.3.1 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -59,6 +66,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.1 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/yuin/gopher-lua v1.1.0 // indirect go.opentelemetry.io/otel v1.19.0 // indirect go.opentelemetry.io/otel/trace v1.19.0 // indirect go.uber.org/multierr v1.11.0 // indirect diff --git a/go.sum b/go.sum index d3cf386..b5ea282 100644 --- a/go.sum +++ b/go.sum @@ -70,6 +70,7 @@ github.com/ClickHouse/clickhouse-go v1.5.4/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHg github.com/ClickHouse/clickhouse-go/v2 v2.8.3/go.mod h1:teXfZNM90iQ99Jnuht+dxQXCuhDZ8nvvMoTJOFrcmcg= github.com/ClickHouse/clickhouse-go/v2 v2.15.0 h1:G0hTKyO8fXXR1bGnZ0DY3vTG01xYfOGW76zgjg5tmC4= github.com/ClickHouse/clickhouse-go/v2 v2.15.0/go.mod h1:kXt1SRq0PIRa6aKZD7TnFnY9PQKmc2b13sHtOYcK6cQ= +github.com/DmitriyVTitov/size v1.5.0/go.mod h1:le6rNI4CoLQV1b9gzp1+3d7hMAD/uu2QcJ+aYbNgiU0= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= @@ -110,6 +111,10 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk= +github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= +github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= +github.com/alicebob/miniredis/v2 v2.31.0 h1:ObEFUNlJwoIiyjxdrYF0QIDE7qXcLc7D3WpSH4c22PU= +github.com/alicebob/miniredis/v2 v2.31.0/go.mod h1:UB/T2Uztp7MlFSDakaX1sTXUv5CASoprx0wulRT6HBg= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= @@ -320,6 +325,7 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dmarkham/enumer v1.5.7/go.mod h1:eAawajOQnFBxf0NndBKgbqJImkHytg3eFEngUovqgo8= @@ -422,7 +428,12 @@ github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dp github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= +github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-redsync/redsync/v4 v4.11.0 h1:OPEcAxHBb95EzfwCKWM93ksOwHd5bTce2BD4+R14N6k= +github.com/go-redsync/redsync/v4 v4.11.0/go.mod h1:ZfayzutkgeBmEmBlUR3j+rF6kN44UUGtEdfzhBFZTPc= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -477,6 +488,7 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= @@ -551,12 +563,14 @@ github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBt github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= @@ -718,6 +732,7 @@ github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ github.com/networkplumbing/go-nft v0.2.0/go.mod h1:HnnM+tYvlGAsMU7yoYwXEVLLiDW9gdMmb5HoGcwpuQs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= @@ -733,6 +748,7 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= @@ -746,6 +762,7 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -849,6 +866,8 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/redis/go-redis/v9 v9.0.2 h1:BA426Zqe/7r56kCcvxYLWe1mkaz71LKF77GwgFzSxfE= +github.com/redis/rueidis v1.0.19 h1:s65oWtotzlIFN8eMPhyYwxlwLR1lUdhza2KtWprKYSo= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -940,6 +959,7 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= @@ -986,6 +1006,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE= +github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= @@ -1182,6 +1204,7 @@ golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1218,6 +1241,7 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1575,6 +1599,7 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1584,6 +1609,7 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/zcache/config.go b/pkg/zcache/config.go new file mode 100644 index 0000000..d00035d --- /dev/null +++ b/pkg/zcache/config.go @@ -0,0 +1,40 @@ +package zcache + +import ( + "github.com/go-redis/redis/v8" + "time" +) + +type Config struct { + Network string + Addr string + Password string + DB int + DialTimeout time.Duration + ReadTimeout time.Duration + WriteTimeout time.Duration + PoolSize int + MinIdleConns int + MaxConnAge time.Duration + PoolTimeout time.Duration + IdleTimeout time.Duration + IdleCheckFrequency time.Duration +} + +func (c *Config) ToRedisConfig() *redis.Options { + return &redis.Options{ + Network: c.Network, + Addr: c.Addr, + Password: c.Password, + DB: c.DB, + DialTimeout: c.DialTimeout, + ReadTimeout: c.ReadTimeout, + WriteTimeout: c.WriteTimeout, + PoolSize: c.PoolSize, + MinIdleConns: c.MinIdleConns, + MaxConnAge: c.MaxConnAge, + PoolTimeout: c.PoolTimeout, + IdleTimeout: c.IdleTimeout, + IdleCheckFrequency: c.IdleCheckFrequency, + } +} diff --git a/pkg/zcache/readme.md b/pkg/zcache/readme.md new file mode 100644 index 0000000..6a2a255 --- /dev/null +++ b/pkg/zcache/readme.md @@ -0,0 +1,126 @@ +# zcache Package + +## Overview +The `zcache` package provides an abstraction layer over Redis, allowing easy integration of caching mechanisms into Go applications. It simplifies interacting with Redis by offering a common interface for various caching operations. + +## Table of Contents +1. [Features](#features) +2. [Installation](#installation) +3. [Usage](#usage) +4. [Configuration](#configuration) +5. [Mocking Support](#mocking-support) + +## Features +- **Unified Caching Interface**: Offers a consistent API for common caching operations, abstracting the complexity of direct Redis interactions. +- **Distributed Mutex Locks**: Supports distributed synchronization using Redis-based mutex locks, crucial for concurrent operations. +- **Extensibility**: Easy to extend with additional methods for more Redis operations. +- **Serialization and Deserialization**: Automatically handles the conversion of Go data structures to and from Redis storage formats. +- **Mocking for Testing**: Includes mock implementations for easy unit testing without a live Redis instance. +- **Connection Pool Management**: Efficiently handles Redis connection pooling. +- **Supported Operations**: Includes a variety of caching operations like Set, Get, Delete, as well as more advanced operations like Incr, Decr, and others. + +--- + +## Installation +```bash +go get github.com/zondax/golem/pkg/zcache +``` + +--- + +## Usage + +```go +import ( + "github.com/zondax/golem/pkg/zcache" + "context" + "time" +) + +func main() { + config := zcache.Config{Addr: "localhost:6379"} + cache := zcache.NewCache(config) + ctx := context.Background() + + // Set a value + cache.Set(ctx, "key1", "value1", 10*time.Minute) + + // Get a value + if value, err := cache.Get(ctx, "key1"); err == nil { + fmt.Println("Retrieved value:", value) + } + + // Delete a value + cache.Delete(ctx, "key1") +} +``` + +--- + +## Configuration + +Configure zcache using the Config struct, which includes network settings, server address, timeouts, and other connection parameters. This struct allows you to customize the behavior of your cache and mutex instances to fit your application's needs. + +```go +type Config struct { + Addr string // Redis server address + Password string // Redis server password + DB int // Redis database + DialTimeout time.Duration // Timeout for connecting to Redis + ReadTimeout time.Duration // Timeout for reading from Redis + WriteTimeout time.Duration // Timeout for writing to Redis + PoolSize int // Number of connections in the pool + MinIdleConns int // Minimum number of idle connections + IdleTimeout time.Duration // Timeout for idle connections +} +``` +--- + +## Working with mutex + +```go +func main() { + cache := zcache.NewCache(zcache.Config{Addr: "localhost:6379"}) + mutex := cache.NewMutex("mutex_name", 2*time.Minute) + + // Acquire lock + if err := mutex.Lock(); err != nil { + log.Fatalf("Failed to acquire mutex: %v", err) + } + + // Perform operations under lock + // ... + + // Release lock + if ok, err := mutex.Unlock(); !ok || err != nil { + log.Fatalf("Failed to release mutex: %v", err) + } +} +``` +--- + +## Mocking support + +Use MockZCache and MockZMutex for unit testing. + +```go +func TestCacheOperation(t *testing.T) { + mockCache := new(zcache.MockZCache) + mockCache.On("Get", mock.Anything, "key1").Return("value1", nil) + // Use mockCache in your tests +} + +func TestSomeFunctionWithMutex(t *testing.T) { + mockMutex := new(zcache.MockZMutex) + mockMutex.On("Lock").Return(nil) + mockMutex.On("Unlock").Return(true, nil) + mockMutex.On("Name").Return("myMutex") + + result, err := SomeFunctionThatUsesMutex(mockMutex) + assert.NoError(t, err) + assert.Equal(t, expectedResult, result) + + mockMutex.AssertExpectations(t) +} +``` + diff --git a/pkg/zcache/redis_cache.go b/pkg/zcache/redis_cache.go new file mode 100644 index 0000000..0551115 --- /dev/null +++ b/pkg/zcache/redis_cache.go @@ -0,0 +1,73 @@ +package zcache + +import ( + "context" + "encoding/json" + "time" + + "github.com/go-redis/redis/v8" +) + +type redisCache struct { + client *redis.Client +} + +func (c *redisCache) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error { + val, err := json.Marshal(value) + if err != nil { + return err + } + return c.client.Set(ctx, key, val, expiration).Err() +} + +func (c *redisCache) Get(ctx context.Context, key string, data interface{}) error { + val, err := c.client.Get(ctx, key).Result() + if err != nil { + return err + } + return json.Unmarshal([]byte(val), &data) +} + +func (c *redisCache) Delete(ctx context.Context, key string) error { + return c.client.Del(ctx, key).Err() +} + +func (c *redisCache) Exists(ctx context.Context, keys ...string) (int64, error) { + return c.client.Exists(ctx, keys...).Result() +} + +func (c *redisCache) Incr(ctx context.Context, key string) (int64, error) { + return c.client.Incr(ctx, key).Result() +} + +func (c *redisCache) Decr(ctx context.Context, key string) (int64, error) { + return c.client.Decr(ctx, key).Result() +} + +func (c *redisCache) FlushAll(ctx context.Context) error { + return c.client.FlushAll(ctx).Err() +} + +func (c *redisCache) LPush(ctx context.Context, key string, values ...interface{}) (int64, error) { + return c.client.LPush(ctx, key, values...).Result() +} + +func (c *redisCache) RPush(ctx context.Context, key string, values ...interface{}) (int64, error) { + return c.client.RPush(ctx, key, values...).Result() +} + +func (c *redisCache) SMembers(ctx context.Context, key string) ([]string, error) { + return c.client.SMembers(ctx, key).Result() +} + +func (c *redisCache) SAdd(ctx context.Context, key string, members ...interface{}) (int64, error) { + return c.client.SAdd(ctx, key, members...).Result() +} + +func (c *redisCache) HSet(ctx context.Context, key string, values ...interface{}) (int64, error) { + return c.client.HSet(ctx, key, values...).Result() +} + +func (c *redisCache) HGet(ctx context.Context, key, field string) (string, error) { + return c.client.HGet(ctx, key, field).Result() +} diff --git a/pkg/zcache/zcache.go b/pkg/zcache/zcache.go new file mode 100644 index 0000000..28b3997 --- /dev/null +++ b/pkg/zcache/zcache.go @@ -0,0 +1,30 @@ +package zcache + +import ( + "context" + "time" + + "github.com/go-redis/redis/v8" +) + +type ZCache interface { + Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error + Get(ctx context.Context, key string, data interface{}) error + Delete(ctx context.Context, key string) error + Exists(ctx context.Context, keys ...string) (int64, error) + Incr(ctx context.Context, key string) (int64, error) + Decr(ctx context.Context, key string) (int64, error) + FlushAll(ctx context.Context) error + LPush(ctx context.Context, key string, values ...interface{}) (int64, error) + RPush(ctx context.Context, key string, values ...interface{}) (int64, error) + SMembers(ctx context.Context, key string) ([]string, error) + SAdd(ctx context.Context, key string, members ...interface{}) (int64, error) + HSet(ctx context.Context, key string, values ...interface{}) (int64, error) + HGet(ctx context.Context, key, field string) (string, error) +} + +func NewCache(config *Config) ZCache { + redisOptions := config.ToRedisConfig() + client := redis.NewClient(redisOptions) + return &redisCache{client: client} +} diff --git a/pkg/zcache/zcache_mock.go b/pkg/zcache/zcache_mock.go new file mode 100644 index 0000000..bf5ca93 --- /dev/null +++ b/pkg/zcache/zcache_mock.go @@ -0,0 +1,76 @@ +package zcache + +import ( + "context" + "github.com/stretchr/testify/mock" + "time" +) + +type MockZCache struct { + mock.Mock +} + +func (m *MockZCache) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error { + args := m.Called(ctx, key, value, expiration) + return args.Error(0) +} + +func (m *MockZCache) Get(ctx context.Context, key string, data interface{}) error { + args := m.Called(ctx, key, data) + return args.Error(0) +} + +func (m *MockZCache) Delete(ctx context.Context, key string) error { + args := m.Called(ctx, key) + return args.Error(0) +} + +func (m *MockZCache) Exists(ctx context.Context, keys ...string) (int64, error) { + args := m.Called(ctx, keys) + return args.Get(0).(int64), args.Error(1) +} + +func (m *MockZCache) Incr(ctx context.Context, key string) (int64, error) { + args := m.Called(ctx, key) + return args.Get(0).(int64), args.Error(1) +} + +func (m *MockZCache) Decr(ctx context.Context, key string) (int64, error) { + args := m.Called(ctx, key) + return args.Get(0).(int64), args.Error(1) +} + +func (m *MockZCache) FlushAll(ctx context.Context) error { + args := m.Called(ctx) + return args.Error(0) +} + +func (m *MockZCache) LPush(ctx context.Context, key string, values ...interface{}) (int64, error) { + args := m.Called(ctx, key, values) + return args.Get(0).(int64), args.Error(1) +} + +func (m *MockZCache) RPush(ctx context.Context, key string, values ...interface{}) (int64, error) { + args := m.Called(ctx, key, values) + return args.Get(0).(int64), args.Error(1) +} + +func (m *MockZCache) SMembers(ctx context.Context, key string) ([]string, error) { + args := m.Called(ctx, key) + return args.Get(0).([]string), args.Error(1) +} + +func (m *MockZCache) SAdd(ctx context.Context, key string, members ...interface{}) (int64, error) { + args := m.Called(ctx, key, members) + return args.Get(0).(int64), args.Error(1) +} + +func (m *MockZCache) HSet(ctx context.Context, key string, values ...interface{}) (int64, error) { + args := m.Called(ctx, key, values) + return args.Get(0).(int64), args.Error(1) +} + +func (m *MockZCache) HGet(ctx context.Context, key, field string) (string, error) { + args := m.Called(ctx, key, field) + return args.Get(0).(string), args.Error(1) +} diff --git a/pkg/zcache/zcache_test.go b/pkg/zcache/zcache_test.go new file mode 100644 index 0000000..fa9b0e4 --- /dev/null +++ b/pkg/zcache/zcache_test.go @@ -0,0 +1,137 @@ +package zcache + +import ( + "context" + "github.com/stretchr/testify/suite" + "testing" + "time" + + "github.com/alicebob/miniredis/v2" +) + +func TestZCacheTestSuite(t *testing.T) { + suite.Run(t, new(ZCacheTestSuite)) +} + +type ZCacheTestSuite struct { + suite.Suite + mr *miniredis.Miniredis + cache ZCache +} + +func (suite *ZCacheTestSuite) SetupSuite() { + mr, err := miniredis.Run() + suite.Require().NoError(err) + suite.mr = mr + + config := &Config{ + Addr: mr.Addr(), + } + + suite.cache = NewCache(config) +} + +func (suite *ZCacheTestSuite) TearDownSuite() { + suite.mr.Close() +} + +func (suite *ZCacheTestSuite) TestSetAndGet() { + ctx := context.Background() + err := suite.cache.Set(ctx, "key1", "value1", 10*time.Second) + suite.NoError(err) + + var result string + err = suite.cache.Get(ctx, "key1", &result) + suite.NoError(err) + suite.Equal("value1", result) +} + +func (suite *ZCacheTestSuite) TestDelete() { + ctx := context.Background() + + suite.NoError(suite.cache.Set(ctx, "key2", "value2", 10*time.Second)) + + err := suite.cache.Delete(ctx, "key2") + suite.NoError(err) + + err = suite.cache.Get(ctx, "key2", new(string)) + suite.Error(err) +} + +func (suite *ZCacheTestSuite) TestExists() { + ctx := context.Background() + + suite.NoError(suite.cache.Set(ctx, "key3", "value3", 10*time.Second)) + suite.NoError(suite.cache.Set(ctx, "key4", "value4", 10*time.Second)) + + count, err := suite.cache.Exists(ctx, "key3", "key4", "nonExistingKey") + suite.NoError(err) + suite.Equal(int64(2), count) +} + +func (suite *ZCacheTestSuite) TestIncrDecr() { + ctx := context.Background() + key := "counterKey" + + suite.NoError(suite.cache.Set(ctx, key, 0, 10*time.Second)) + + newValue, err := suite.cache.Incr(ctx, key) + suite.NoError(err) + suite.Equal(int64(1), newValue) + + newValue, err = suite.cache.Decr(ctx, key) + suite.NoError(err) + suite.Equal(int64(0), newValue) +} + +func (suite *ZCacheTestSuite) TestFlushAll() { + ctx := context.Background() + suite.NoError(suite.cache.Set(ctx, "key5", "value5", 10*time.Second)) + + err := suite.cache.FlushAll(ctx) + suite.NoError(err) + + count, err := suite.cache.Exists(ctx, "key5") + suite.NoError(err) + suite.Equal(int64(0), count) +} + +func (suite *ZCacheTestSuite) TestLPushAndRPush() { + ctx := context.Background() + listKey := "listKey" + + lLen, err := suite.cache.LPush(ctx, listKey, "value6") + suite.NoError(err) + suite.Equal(int64(1), lLen) + + rLen, err := suite.cache.RPush(ctx, listKey, "value7") + suite.NoError(err) + suite.Equal(int64(2), rLen) +} + +func (suite *ZCacheTestSuite) TestSMembersAndSAdd() { + ctx := context.Background() + setKey := "setKey" + + addCount, err := suite.cache.SAdd(ctx, setKey, "member1", "member2") + suite.NoError(err) + suite.Equal(int64(2), addCount) + + members, err := suite.cache.SMembers(ctx, setKey) + suite.NoError(err) + suite.Contains(members, "member1") + suite.Contains(members, "member2") +} + +func (suite *ZCacheTestSuite) TestHSetAndHGet() { + ctx := context.Background() + hashKey := "hashKey" + + hSetCount, err := suite.cache.HSet(ctx, hashKey, "field1", "value8") + suite.NoError(err) + suite.Equal(int64(1), hSetCount) + + value, err := suite.cache.HGet(ctx, hashKey, "field1") + suite.NoError(err) + suite.Equal("value8", value) +} diff --git a/pkg/zcache/zmutex.go b/pkg/zcache/zmutex.go new file mode 100644 index 0000000..56ad968 --- /dev/null +++ b/pkg/zcache/zmutex.go @@ -0,0 +1,37 @@ +package zcache + +import ( + "github.com/go-redsync/redsync/v4" + "github.com/go-redsync/redsync/v4/redis/goredis/v8" + "time" +) + +type ZMutex interface { + Lock() error + Unlock() (bool, error) + Name() string +} + +type zMutex struct { + mutex *redsync.Mutex +} + +func (c *redisCache) NewMutex(name string, expiry time.Duration) ZMutex { + pool := goredis.NewPool(c.client) + rs := redsync.New(pool) + + mutex := rs.NewMutex(name, redsync.WithExpiry(expiry)) + return &zMutex{mutex: mutex} +} + +func (m *zMutex) Lock() error { + return m.mutex.Lock() +} + +func (m *zMutex) Unlock() (bool, error) { + return m.mutex.Unlock() +} + +func (m *zMutex) Name() string { + return m.mutex.Name() +} diff --git a/pkg/zcache/zmutex_mock.go b/pkg/zcache/zmutex_mock.go new file mode 100644 index 0000000..8faf206 --- /dev/null +++ b/pkg/zcache/zmutex_mock.go @@ -0,0 +1,24 @@ +package zcache + +import ( + "github.com/stretchr/testify/mock" +) + +type MockZMutex struct { + mock.Mock +} + +func (m *MockZMutex) Lock() error { + args := m.Called() + return args.Error(0) +} + +func (m *MockZMutex) Unlock() (bool, error) { + args := m.Called() + return args.Bool(0), args.Error(1) +} + +func (m *MockZMutex) Name() string { + args := m.Called() + return args.String(0) +} diff --git a/pkg/zcache/zmutex_test.go b/pkg/zcache/zmutex_test.go new file mode 100644 index 0000000..52b4997 --- /dev/null +++ b/pkg/zcache/zmutex_test.go @@ -0,0 +1,53 @@ +package zcache + +import ( + "github.com/alicebob/miniredis/v2" + "github.com/stretchr/testify/suite" + "testing" + "time" +) + +func TestZMutexTestSuite(t *testing.T) { + suite.Run(t, new(ZMutexTestSuite)) +} + +type ZMutexTestSuite struct { + suite.Suite + mr *miniredis.Miniredis + cache *redisCache +} + +func (suite *ZMutexTestSuite) SetupSuite() { + mr, err := miniredis.Run() + suite.Require().NoError(err) + suite.mr = mr + + config := &Config{ + Addr: mr.Addr(), + } + + suite.cache = NewCache(config).(*redisCache) +} + +func (suite *ZMutexTestSuite) TearDownSuite() { + suite.mr.Close() +} + +func (suite *ZMutexTestSuite) TestNewMutex() { + mutexName := "testMutex" + expiry := 10 * time.Second + + mutex := suite.cache.NewMutex(mutexName, expiry) + suite.NotNil(mutex) + suite.Equal(mutexName, mutex.Name()) +} + +func (suite *ZMutexTestSuite) TestMutexLockUnlock() { + mutex := suite.cache.NewMutex("testMutex", 10*time.Second) + err := mutex.Lock() + suite.NoError(err) + + unlocked, err := mutex.Unlock() + suite.NoError(err) + suite.True(unlocked) +}