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:
wang yan 2019-07-08 14:22:00 +08:00
parent fa51ac6406
commit ef14f0cf35
2 changed files with 176 additions and 0 deletions

View 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
}

View 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
}