mirror of
https://github.com/goharbor/harbor.git
synced 2025-01-01 13:37:47 +01:00
add redis lock, it will be used to lock digest in the quota scenario
Signed-off-by: wang yan <wangyan@vmware.com>
This commit is contained in:
parent
fa51ac6406
commit
ef14f0cf35
114
src/common/utils/redis/helper.go
Normal file
114
src/common/utils/redis/helper.go
Normal file
@ -0,0 +1,114 @@
|
||||
// Copyright Project Harbor 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 redis
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/garyburd/redigo/redis"
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrUnLock ...
|
||||
ErrUnLock = errors.New("error to release the redis lock")
|
||||
)
|
||||
|
||||
const (
|
||||
unlockScript = `
|
||||
if redis.call("get",KEYS[1]) == ARGV[1] then
|
||||
return redis.call("del",KEYS[1])
|
||||
else
|
||||
return 0
|
||||
end
|
||||
`
|
||||
defaultDelay = 5 * time.Second
|
||||
defaultMaxRetry = 5
|
||||
defaultExpiry = 600 * time.Second
|
||||
)
|
||||
|
||||
// Mutex ...
|
||||
type Mutex struct {
|
||||
Conn redis.Conn
|
||||
key string
|
||||
value string
|
||||
opts Options
|
||||
}
|
||||
|
||||
// New ...
|
||||
func New(conn redis.Conn, key, value string) *Mutex {
|
||||
o := *DefaultOptions()
|
||||
if value == "" {
|
||||
value = utils.GenerateRandomString()
|
||||
}
|
||||
return &Mutex{conn, key, value, o}
|
||||
}
|
||||
|
||||
// Require retry to require the lock
|
||||
func (rm *Mutex) Require() (bool, error) {
|
||||
var isRequired bool
|
||||
var err error
|
||||
|
||||
for i := 0; i < rm.opts.maxRetry; i++ {
|
||||
isRequired, err = rm.require()
|
||||
if isRequired {
|
||||
break
|
||||
}
|
||||
if err != nil || !isRequired {
|
||||
time.Sleep(rm.opts.retryDelay)
|
||||
}
|
||||
}
|
||||
|
||||
return isRequired, err
|
||||
}
|
||||
|
||||
// require get the redis lock, for details, just refer to https://redis.io/topics/distlock
|
||||
func (rm *Mutex) require() (bool, error) {
|
||||
reply, err := redis.String(rm.Conn.Do("SET", rm.key, rm.value, "NX", "PX", int(rm.opts.expiry/time.Millisecond)))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return reply == "OK", nil
|
||||
}
|
||||
|
||||
// Free releases the lock, for details, just refer to https://redis.io/topics/distlock
|
||||
func (rm *Mutex) Free() (bool, error) {
|
||||
script := redis.NewScript(1, unlockScript)
|
||||
resp, err := redis.Int(script.Do(rm.Conn, rm.key, rm.value))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if resp == 0 {
|
||||
return false, ErrUnLock
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Options ...
|
||||
type Options struct {
|
||||
retryDelay time.Duration
|
||||
expiry time.Duration
|
||||
maxRetry int
|
||||
}
|
||||
|
||||
// DefaultOptions ...
|
||||
func DefaultOptions() *Options {
|
||||
opt := &Options{
|
||||
retryDelay: defaultDelay,
|
||||
expiry: defaultExpiry,
|
||||
maxRetry: defaultMaxRetry,
|
||||
}
|
||||
return opt
|
||||
}
|
62
src/common/utils/redis/helper_test.go
Normal file
62
src/common/utils/redis/helper_test.go
Normal file
@ -0,0 +1,62 @@
|
||||
// Copyright Project Harbor 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 redis
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/garyburd/redigo/redis"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
const testingRedisHost = "REDIS_HOST"
|
||||
|
||||
func TestRedisLock(t *testing.T) {
|
||||
con, err := redis.Dial(
|
||||
"tcp",
|
||||
fmt.Sprintf("%s:%d", getRedisHost(), 6379),
|
||||
redis.DialConnectTimeout(30*time.Second),
|
||||
redis.DialReadTimeout(time.Minute+10*time.Second),
|
||||
redis.DialWriteTimeout(10*time.Second),
|
||||
)
|
||||
assert.Nil(t, err)
|
||||
defer con.Close()
|
||||
|
||||
rm := New(con, "test-redis-lock", "test-value")
|
||||
|
||||
successLock, err := rm.Require()
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, successLock)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
_, err = rm.Require()
|
||||
assert.NotNil(t, err)
|
||||
|
||||
successUnLock, err := rm.Free()
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, successUnLock)
|
||||
|
||||
}
|
||||
|
||||
func getRedisHost() string {
|
||||
redisHost := os.Getenv(testingRedisHost)
|
||||
if redisHost == "" {
|
||||
redisHost = "127.0.0.1" // for local test
|
||||
}
|
||||
|
||||
return redisHost
|
||||
}
|
Loading…
Reference in New Issue
Block a user