diff --git a/CHANGELOG.md b/CHANGELOG.md index afa46baa..9af933de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # developing +- Error codes definition of cache module. [4493](https://github.com/beego/beego/pull/4493) - Remove generateCommentRoute http hook. Using `bee generate routers` commands instead.[4486](https://github.com/beego/beego/pull/4486) [bee PR 762](https://github.com/beego/bee/pull/762) - Fix: /abc.html/aaa match /abc/aaa. [4459](https://github.com/beego/beego/pull/4459) - ORM mock. [4407](https://github.com/beego/beego/pull/4407) diff --git a/ERROR_SPECIFICATION.md b/ERROR_SPECIFICATION.md index cf2f94b2..68a04bd1 100644 --- a/ERROR_SPECIFICATION.md +++ b/ERROR_SPECIFICATION.md @@ -1 +1,5 @@ # Error Module + +## Module code +- httplib 1 +- cache 2 \ No newline at end of file diff --git a/client/cache/cache.go b/client/cache/cache.go index 70c81697..87f7ba62 100644 --- a/client/cache/cache.go +++ b/client/cache/cache.go @@ -33,8 +33,9 @@ package cache import ( "context" - "fmt" "time" + + "github.com/beego/beego/v2/core/berror" ) // Cache interface contains all behaviors for cache adapter. @@ -55,12 +56,14 @@ type Cache interface { // Set a cached value with key and expire time. Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error // Delete cached value by key. + // Should not return error if key not found Delete(ctx context.Context, key string) error // Increment a cached int value by key, as a counter. Incr(ctx context.Context, key string) error // Decrement a cached int value by key, as a counter. Decr(ctx context.Context, key string) error // Check if a cached value exists or not. + // if key is expired, return (false, nil) IsExist(ctx context.Context, key string) (bool, error) // Clear all cache. ClearAll(ctx context.Context) error @@ -78,7 +81,7 @@ var adapters = make(map[string]Instance) // it panics. func Register(name string, adapter Instance) { if adapter == nil { - panic("cache: Register adapter is nil") + panic(berror.Error(NilCacheAdapter, "cache: Register adapter is nil").Error()) } if _, ok := adapters[name]; ok { panic("cache: Register called twice for adapter " + name) @@ -92,7 +95,7 @@ func Register(name string, adapter Instance) { func NewCache(adapterName, config string) (adapter Cache, err error) { instanceFunc, ok := adapters[adapterName] if !ok { - err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName) + err = berror.Errorf(UnknownAdapter, "cache: unknown adapter name %s (forgot to import?)", adapterName) return } adapter = instanceFunc() diff --git a/client/cache/cache_test.go b/client/cache/cache_test.go index 6cecdc16..db651e94 100644 --- a/client/cache/cache_test.go +++ b/client/cache/cache_test.go @@ -18,6 +18,7 @@ import ( "context" "math" "os" + "strings" "sync" "testing" "time" @@ -108,7 +109,7 @@ func TestCache(t *testing.T) { assert.Equal(t, "author1", vv[1]) assert.NotNil(t, err) - assert.Equal(t, "key [astaxie0] error: key not exist", err.Error()) + assert.True(t, strings.Contains(err.Error(), "key isn't exist")) } func TestFileCache(t *testing.T) { diff --git a/client/cache/calc_utils.go b/client/cache/calc_utils.go index 733e2fc2..e18946c1 100644 --- a/client/cache/calc_utils.go +++ b/client/cache/calc_utils.go @@ -1,46 +1,55 @@ package cache import ( - "fmt" "math" + + "github.com/beego/beego/v2/core/berror" ) +var ( + ErrIncrementOverflow = berror.Error(IncrementOverflow, "this incr invocation will overflow.") + ErrDecrementOverflow = berror.Error(DecrementOverflow, "this decr invocation will overflow.") + ErrNotIntegerType = berror.Error(NotIntegerType, "item val is not (u)int (u)int32 (u)int64") +) + + + func incr(originVal interface{}) (interface{}, error) { switch val := originVal.(type) { case int: tmp := val + 1 if val > 0 && tmp < 0 { - return nil, fmt.Errorf("increment would overflow") + return nil, ErrIncrementOverflow } return tmp, nil case int32: if val == math.MaxInt32 { - return nil, fmt.Errorf("increment would overflow") + return nil, ErrIncrementOverflow } return val + 1, nil case int64: if val == math.MaxInt64 { - return nil, fmt.Errorf("increment would overflow") + return nil, ErrIncrementOverflow } return val + 1, nil case uint: tmp := val + 1 if tmp < val { - return nil, fmt.Errorf("increment would overflow") + return nil, ErrIncrementOverflow } return tmp, nil case uint32: if val == math.MaxUint32 { - return nil, fmt.Errorf("increment would overflow") + return nil, ErrIncrementOverflow } return val + 1, nil case uint64: if val == math.MaxUint64 { - return nil, fmt.Errorf("increment would overflow") + return nil, ErrIncrementOverflow } return val + 1, nil default: - return nil, fmt.Errorf("item val is not (u)int (u)int32 (u)int64") + return nil, ErrNotIntegerType } } @@ -49,35 +58,35 @@ func decr(originVal interface{}) (interface{}, error) { case int: tmp := val - 1 if val < 0 && tmp > 0 { - return nil, fmt.Errorf("decrement would overflow") + return nil, ErrDecrementOverflow } return tmp, nil case int32: if val == math.MinInt32 { - return nil, fmt.Errorf("decrement would overflow") + return nil, ErrDecrementOverflow } return val - 1, nil case int64: if val == math.MinInt64 { - return nil, fmt.Errorf("decrement would overflow") + return nil, ErrDecrementOverflow } return val - 1, nil case uint: if val == 0 { - return nil, fmt.Errorf("decrement would overflow") + return nil, ErrDecrementOverflow } return val - 1, nil case uint32: if val == 0 { - return nil, fmt.Errorf("increment would overflow") + return nil, ErrDecrementOverflow } return val - 1, nil case uint64: if val == 0 { - return nil, fmt.Errorf("increment would overflow") + return nil, ErrDecrementOverflow } return val - 1, nil default: - return nil, fmt.Errorf("item val is not (u)int (u)int32 (u)int64") + return nil, ErrNotIntegerType } } diff --git a/client/cache/calc_utils_test.go b/client/cache/calc_utils_test.go index 2a08dfc3..1f8d3377 100644 --- a/client/cache/calc_utils_test.go +++ b/client/cache/calc_utils_test.go @@ -14,61 +14,64 @@ func TestIncr(t *testing.T) { var updateVal interface{} = int(2) val, err := incr(originVal) assert.Nil(t, err) - assert.Equal(t, updateVal, val) + assert.Equal(t, val, updateVal) + _, err = incr(int(1<<(strconv.IntSize-1) - 1)) - assert.NotNil(t, err) + assert.Equal(t, ErrIncrementOverflow, err) // int32 originVal = int32(1) updateVal = int32(2) val, err = incr(originVal) assert.Nil(t, err) - assert.Equal(t, updateVal, val) + assert.Equal(t, val, updateVal) + _, err = incr(int32(math.MaxInt32)) - assert.NotNil(t, err) + assert.Equal(t, ErrIncrementOverflow, err) + // int64 originVal = int64(1) updateVal = int64(2) val, err = incr(originVal) assert.Nil(t, err) - - assert.Equal(t, updateVal, val) + assert.Equal(t, val, updateVal) _, err = incr(int64(math.MaxInt64)) - assert.NotNil(t, err) + assert.Equal(t, ErrIncrementOverflow, err) // uint originVal = uint(1) updateVal = uint(2) val, err = incr(originVal) - assert.Nil(t, err) - assert.Equal(t, updateVal, val) + assert.Nil(t, err) + assert.Equal(t, val, updateVal) _, err = incr(uint(1<<(strconv.IntSize) - 1)) - assert.NotNil(t, err) + assert.Equal(t, ErrIncrementOverflow, err) + // uint32 originVal = uint32(1) updateVal = uint32(2) val, err = incr(originVal) assert.Nil(t, err) - assert.Equal(t, updateVal, val) + assert.Equal(t, val, updateVal) _, err = incr(uint32(math.MaxUint32)) - assert.NotNil(t, err) + assert.Equal(t, ErrIncrementOverflow, err) // uint64 originVal = uint64(1) updateVal = uint64(2) val, err = incr(originVal) assert.Nil(t, err) - assert.Equal(t, updateVal, val) - _, err = incr(uint64(math.MaxUint64)) - assert.NotNil(t, err) + assert.Equal(t, val, updateVal) + _, err = incr(uint64(math.MaxUint64)) + assert.Equal(t, ErrIncrementOverflow, err) // other type _, err = incr("string") - assert.NotNil(t, err) + assert.Equal(t, ErrNotIntegerType, err) } func TestDecr(t *testing.T) { @@ -77,57 +80,61 @@ func TestDecr(t *testing.T) { var updateVal interface{} = int(1) val, err := decr(originVal) assert.Nil(t, err) - assert.Equal(t, updateVal, val) + assert.Equal(t, val, updateVal) + _, err = decr(int(-1 << (strconv.IntSize - 1))) - - assert.NotNil(t, err) - + assert.Equal(t, ErrDecrementOverflow, err) // int32 originVal = int32(2) updateVal = int32(1) val, err = decr(originVal) - assert.Nil(t, err) - assert.Equal(t, updateVal, val) + assert.Equal(t, val, updateVal) + _, err = decr(int32(math.MinInt32)) - assert.NotNil(t, err) + assert.Equal(t, ErrDecrementOverflow, err) // int64 originVal = int64(2) updateVal = int64(1) val, err = decr(originVal) assert.Nil(t, err) - assert.Equal(t, updateVal, val) + assert.Equal(t, val, updateVal) + _, err = decr(int64(math.MinInt64)) - assert.NotNil(t, err) + assert.Equal(t, ErrDecrementOverflow, err) + // uint originVal = uint(2) updateVal = uint(1) val, err = decr(originVal) assert.Nil(t, err) - assert.Equal(t, updateVal, val) + assert.Equal(t, val, updateVal) + _, err = decr(uint(0)) - assert.NotNil(t, err) + assert.Equal(t, ErrDecrementOverflow, err) // uint32 originVal = uint32(2) updateVal = uint32(1) val, err = decr(originVal) assert.Nil(t, err) - assert.Equal(t, updateVal, val) + assert.Equal(t, val, updateVal) + _, err = decr(uint32(0)) - assert.NotNil(t, err) + assert.Equal(t, ErrDecrementOverflow, err) // uint64 originVal = uint64(2) updateVal = uint64(1) val, err = decr(originVal) assert.Nil(t, err) - assert.Equal(t, updateVal, val) + assert.Equal(t, val, updateVal) + _, err = decr(uint64(0)) - assert.NotNil(t, err) + assert.Equal(t, ErrDecrementOverflow, err) // other type _, err = decr("string") - assert.NotNil(t, err) + assert.Equal(t, ErrNotIntegerType, err) } diff --git a/client/cache/error_code.go b/client/cache/error_code.go new file mode 100644 index 00000000..4305d7e4 --- /dev/null +++ b/client/cache/error_code.go @@ -0,0 +1,182 @@ +// Copyright 2021 beego +// +// 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 cache + +import ( + "github.com/beego/beego/v2/core/berror" +) + +var NilCacheAdapter = berror.DefineCode(4002001, moduleName, "NilCacheAdapter", ` +It means that you register cache adapter by pass nil. +A cache adapter is an instance of Cache interface. +`) + +var DuplicateAdapter = berror.DefineCode(4002002, moduleName, "DuplicateAdapter", ` +You register two adapter with same name. In beego cache module, one name one adapter. +Once you got this error, please check the error stack, search adapter +`) + +var UnknownAdapter = berror.DefineCode(4002003, moduleName, "UnknownAdapter", ` +Unknown adapter, do you forget to register the adapter? +You must register adapter before use it. For example, if you want to use redis implementation, +you must import the cache/redis package. +`) + +var IncrementOverflow = berror.DefineCode(4002004, moduleName, "IncrementOverflow", ` +The increment operation will overflow. +`) + +var DecrementOverflow = berror.DefineCode(4002005, moduleName, "DecrementOverflow", ` +The decrement operation will overflow. +`) + +var NotIntegerType = berror.DefineCode(4002006, moduleName, "NotIntegerType", ` +The type of value is not (u)int (u)int32 (u)int64. +When you want to call Incr or Decr function of Cache API, you must confirm that the value's type is one of (u)int (u)int32 (u)int64. +`) + +var InvalidFileCacheDirectoryLevelCfg = berror.DefineCode(4002007, moduleName, "InvalidFileCacheDirectoryLevelCfg", ` +You pass invalid DirectoryLevel parameter when you try to StartAndGC file cache instance. +This parameter must be a integer, and please check your input. +`) + +var InvalidFileCacheEmbedExpiryCfg = berror.DefineCode(4002008, moduleName, "InvalidFileCacheEmbedExpiryCfg", ` +You pass invalid EmbedExpiry parameter when you try to StartAndGC file cache instance. +This parameter must be a integer, and please check your input. +`) + +var CreateFileCacheDirFailed = berror.DefineCode(4002009, moduleName, "CreateFileCacheDirFailed", ` +Beego failed to create file cache directory. There are two cases: +1. You pass invalid CachePath parameter. Please check your input. +2. Beego doesn't have the permission to create this directory. Please check your file mode. +`) + +var InvalidFileCachePath = berror.DefineCode(4002010, moduleName, "InvalidFilePath", ` +The file path of FileCache is invalid. Please correct the config. +`) + +var ReadFileCacheContentFailed = berror.DefineCode(4002011, moduleName, "ReadFileCacheContentFailed", ` +Usually you won't got this error. It means that Beego cannot read the data from the file. +You need to check whether the file exist. Sometimes it may be deleted by other processes. +If the file exists, please check the permission that Beego is able to read data from the file. +`) + +var InvalidGobEncodedData = berror.DefineCode(4002012, moduleName, "InvalidEncodedData", ` +The data is invalid. When you try to decode the invalid data, you got this error. +Please confirm that the data is encoded by GOB correctly. +`) + +var GobEncodeDataFailed = berror.DefineCode(4002013, moduleName, "GobEncodeDataFailed", ` +Beego could not encode the data to GOB byte array. In general, the data type is invalid. +For example, GOB doesn't support function type. +Basic types, string, structure, structure pointer are supported. +`) + +var KeyExpired = berror.DefineCode(4002014, moduleName, "KeyExpired", ` +Cache key is expired. +You should notice that, a key is expired and then it may be deleted by GC goroutine. +So when you query a key which may be expired, you may got this code, or KeyNotExist. +`) + +var KeyNotExist = berror.DefineCode(4002015, moduleName, "KeyNotExist", ` +Key not found. +`) + +var MultiGetFailed = berror.DefineCode(4002016, moduleName, "MultiGetFailed", ` +Get multiple keys failed. Please check the detail msg to find out the root cause. +`) + +var InvalidMemoryCacheCfg = berror.DefineCode(4002017, moduleName, "InvalidMemoryCacheCfg", ` +The config is invalid. Please check your input. It must be a json string. +`) + +var InvalidMemCacheCfg = berror.DefineCode(4002018, moduleName, "InvalidMemCacheCfg", ` +The config is invalid. Please check your input, it must be json string and contains "conn" field. +`) + +var InvalidMemCacheValue = berror.DefineCode(4002019, moduleName, "InvalidMemCacheValue", ` +The value must be string or byte[], please check your input. +`) + +var InvalidRedisCacheCfg = berror.DefineCode(4002020, moduleName, "InvalidRedisCacheCfg", ` +The config must be json string, and has "conn" field. +`) + +var InvalidSsdbCacheCfg = berror.DefineCode(4002021, moduleName, "InvalidSsdbCacheCfg", ` +The config must be json string, and has "conn" field. The value of "conn" field should be "host:port". +"port" must be a valid integer. +`) + +var InvalidSsdbCacheValue = berror.DefineCode(4002022, moduleName, "InvalidSsdbCacheValue", ` +SSDB cache only accept string value. Please check your input. +`) + + + + + + + + + +var DeleteFileCacheItemFailed = berror.DefineCode(5002001, moduleName, "DeleteFileCacheItemFailed", ` +Beego try to delete file cache item failed. +Please check whether Beego generated file correctly. +And then confirm whether this file is already deleted by other processes or other people. +`) + +var MemCacheCurdFailed = berror.DefineCode(5002002, moduleName, "MemCacheError", ` +When you want to get, put, delete key-value from remote memcache servers, you may get error: +1. You pass invalid servers address, so Beego could not connect to remote server; +2. The servers address is correct, but there is some net issue. Typically there is some firewalls between application and memcache server; +3. Key is invalid. The key's length should be less than 250 and must not contains special characters; +4. The response from memcache server is invalid; +`) + +var RedisCacheCurdFailed = berror.DefineCode(5002003, moduleName, "RedisCacheCurdFailed", ` +When Beego uses client to send request to redis server, it failed. +1. The server addresses is invalid; +2. Network issue, firewall issue or network is unstable; +3. Client failed to manage connection. In extreme cases, Beego's redis client didn't maintain connections correctly, for example, Beego try to send request via closed connection; +4. The request are huge and redis server spent too much time to process it, and client is timeout; + +In general, if you always got this error whatever you do, in most cases, it was caused by network issue. +You could check your network state, and confirm that firewall rules are correct. +`) + +var InvalidConnection = berror.DefineCode(5002004, moduleName, "InvalidConnection", ` +The connection is invalid. Please check your connection info, network, firewall. +You could simply uses ping, telnet or write some simple tests to test network. +`) + +var DialFailed = berror.DefineCode(5002005, moduleName, "DialFailed", ` +When Beego try to dial to remote servers, it failed. Please check your connection info and network state, server state. +`) + +var SsdbCacheCurdFailed = berror.DefineCode(5002006, moduleName, "SsdbCacheCurdFailed", ` +When you try to use SSDB cache, it failed. There are many cases: +1. servers unavailable; +2. network issue, including network unstable, firewall; +3. connection issue; +4. request are huge and servers spent too much time to process it, got timeout; +`) + +var SsdbBadResponse = berror.DefineCode(5002007, moduleName, "SsdbBadResponse", ` +The reponse from SSDB server is invalid. +Usually it indicates something wrong on server side. +`) + +var ErrKeyExpired = berror.Error(KeyExpired, "the key is expired") +var ErrKeyNotExist = berror.Error(KeyNotExist, "the key isn't exist") \ No newline at end of file diff --git a/client/cache/file.go b/client/cache/file.go index 87e14b6c..ea00c72e 100644 --- a/client/cache/file.go +++ b/client/cache/file.go @@ -30,7 +30,7 @@ import ( "strings" "time" - "github.com/pkg/errors" + "github.com/beego/beego/v2/core/berror" ) // FileCacheItem is basic unit of file cache adapter which @@ -73,38 +73,60 @@ func (fc *FileCache) StartAndGC(config string) error { if err != nil { return err } - if _, ok := cfg["CachePath"]; !ok { - cfg["CachePath"] = FileCachePath - } - if _, ok := cfg["FileSuffix"]; !ok { - cfg["FileSuffix"] = FileCacheFileSuffix - } - if _, ok := cfg["DirectoryLevel"]; !ok { - cfg["DirectoryLevel"] = strconv.Itoa(FileCacheDirectoryLevel) - } - if _, ok := cfg["EmbedExpiry"]; !ok { - cfg["EmbedExpiry"] = strconv.FormatInt(int64(FileCacheEmbedExpiry.Seconds()), 10) - } - fc.CachePath = cfg["CachePath"] - fc.FileSuffix = cfg["FileSuffix"] - fc.DirectoryLevel, _ = strconv.Atoi(cfg["DirectoryLevel"]) - fc.EmbedExpiry, _ = strconv.Atoi(cfg["EmbedExpiry"]) - fc.Init() - return nil + const cpKey = "CachePath" + const fsKey = "FileSuffix" + const dlKey = "DirectoryLevel" + const eeKey = "EmbedExpiry" + + if _, ok := cfg[cpKey]; !ok { + cfg[cpKey] = FileCachePath + } + + if _, ok := cfg[fsKey]; !ok { + cfg[fsKey] = FileCacheFileSuffix + } + + if _, ok := cfg[dlKey]; !ok { + cfg[dlKey] = strconv.Itoa(FileCacheDirectoryLevel) + } + + if _, ok := cfg[eeKey]; !ok { + cfg[eeKey] = strconv.FormatInt(int64(FileCacheEmbedExpiry.Seconds()), 10) + } + fc.CachePath = cfg[cpKey] + fc.FileSuffix = cfg[fsKey] + fc.DirectoryLevel, err = strconv.Atoi(cfg[dlKey]) + if err != nil { + return berror.Wrapf(err, InvalidFileCacheDirectoryLevelCfg, + "invalid directory level config, please check your input, it must be integer: %s", cfg[dlKey]) + } + fc.EmbedExpiry, err = strconv.Atoi(cfg[eeKey]) + if err != nil { + return berror.Wrapf(err, InvalidFileCacheEmbedExpiryCfg, + "invalid embed expiry config, please check your input, it must be integer: %s", cfg[eeKey]) + } + return fc.Init() } // Init makes new a dir for file cache if it does not already exist -func (fc *FileCache) Init() { - if ok, _ := exists(fc.CachePath); !ok { // todo : error handle - _ = os.MkdirAll(fc.CachePath, os.ModePerm) // todo : error handle +func (fc *FileCache) Init() error { + ok, err := exists(fc.CachePath) + if err != nil || ok { + return err } + err = os.MkdirAll(fc.CachePath, os.ModePerm) + if err != nil { + return berror.Wrapf(err, CreateFileCacheDirFailed, + "could not create directory, please check the config [%s] and file mode.", fc.CachePath) + } + return nil } // getCachedFilename returns an md5 encoded file name. -func (fc *FileCache) getCacheFileName(key string) string { +func (fc *FileCache) getCacheFileName(key string) (string, error) { m := md5.New() - io.WriteString(m, key) + _, _ = io.WriteString(m, key) keyMd5 := hex.EncodeToString(m.Sum(nil)) cachePath := fc.CachePath switch fc.DirectoryLevel { @@ -113,18 +135,29 @@ func (fc *FileCache) getCacheFileName(key string) string { case 1: cachePath = filepath.Join(cachePath, keyMd5[0:2]) } - - if ok, _ := exists(cachePath); !ok { // todo : error handle - _ = os.MkdirAll(cachePath, os.ModePerm) // todo : error handle + ok, err := exists(cachePath) + if err != nil { + return "", err + } + if !ok { + err = os.MkdirAll(cachePath, os.ModePerm) + if err != nil { + return "", berror.Wrapf(err, CreateFileCacheDirFailed, + "could not create the directory: %s", cachePath) + } } - return filepath.Join(cachePath, fmt.Sprintf("%s%s", keyMd5, fc.FileSuffix)) + return filepath.Join(cachePath, fmt.Sprintf("%s%s", keyMd5, fc.FileSuffix)), nil } // Get value from file cache. // if nonexistent or expired return an empty string. func (fc *FileCache) Get(ctx context.Context, key string) (interface{}, error) { - fileData, err := FileGetContents(fc.getCacheFileName(key)) + fn, err := fc.getCacheFileName(key) + if err != nil { + return nil, err + } + fileData, err := FileGetContents(fn) if err != nil { return nil, err } @@ -136,7 +169,7 @@ func (fc *FileCache) Get(ctx context.Context, key string) (interface{}, error) { } if to.Expired.Before(time.Now()) { - return nil, errors.New("The key is expired") + return nil, ErrKeyExpired } return to.Data, nil } @@ -159,7 +192,7 @@ func (fc *FileCache) GetMulti(ctx context.Context, keys []string) ([]interface{} if len(keysErr) == 0 { return rc, nil } - return rc, errors.New(strings.Join(keysErr, "; ")) + return rc, berror.Error(MultiGetFailed, strings.Join(keysErr, "; ")) } // Put value into file cache. @@ -179,14 +212,26 @@ func (fc *FileCache) Put(ctx context.Context, key string, val interface{}, timeo if err != nil { return err } - return FilePutContents(fc.getCacheFileName(key), data) + + fn, err := fc.getCacheFileName(key) + if err != nil { + return err + } + return FilePutContents(fn, data) } // Delete file cache value. func (fc *FileCache) Delete(ctx context.Context, key string) error { - filename := fc.getCacheFileName(key) + filename, err := fc.getCacheFileName(key) + if err != nil { + return err + } if ok, _ := exists(filename); ok { - return os.Remove(filename) + err = os.Remove(filename) + if err != nil { + return berror.Wrapf(err, DeleteFileCacheItemFailed, + "can not delete this file cache key-value, key is %s and file name is %s", key, filename) + } } return nil } @@ -224,8 +269,11 @@ func (fc *FileCache) Decr(ctx context.Context, key string) error { // IsExist checks if value exists. func (fc *FileCache) IsExist(ctx context.Context, key string) (bool, error) { - ret, _ := exists(fc.getCacheFileName(key)) - return ret, nil + fn, err := fc.getCacheFileName(key) + if err != nil { + return false, err + } + return exists(fn) } // ClearAll cleans cached files (not implemented) @@ -242,13 +290,19 @@ func exists(path string) (bool, error) { if os.IsNotExist(err) { return false, nil } - return false, err + return false, berror.Wrapf(err, InvalidFileCachePath, "file cache path is invalid: %s", path) } // FileGetContents Reads bytes from a file. // if non-existent, create this file. -func FileGetContents(filename string) (data []byte, e error) { - return ioutil.ReadFile(filename) +func FileGetContents(filename string) ([]byte, error) { + data, err := ioutil.ReadFile(filename) + if err != nil { + return nil, berror.Wrapf(err, ReadFileCacheContentFailed, + "could not read the data from the file: %s, " + + "please confirm that file exist and Beego has the permission to read the content.", filename) + } + return data, nil } // FilePutContents puts bytes into a file. @@ -263,16 +317,21 @@ func GobEncode(data interface{}) ([]byte, error) { enc := gob.NewEncoder(buf) err := enc.Encode(data) if err != nil { - return nil, err + return nil, berror.Wrap(err, GobEncodeDataFailed, "could not encode this data") } - return buf.Bytes(), err + return buf.Bytes(), nil } // GobDecode Gob decodes a file cache item. func GobDecode(data []byte, to *FileCacheItem) error { buf := bytes.NewBuffer(data) dec := gob.NewDecoder(buf) - return dec.Decode(&to) + err := dec.Decode(&to) + if err != nil { + return berror.Wrap(err, InvalidGobEncodedData, + "could not decode this data to FileCacheItem. Make sure that the data is encoded by GOB.") + } + return nil } func init() { diff --git a/client/cache/file_test.go b/client/cache/file_test.go new file mode 100644 index 00000000..3ffc27f3 --- /dev/null +++ b/client/cache/file_test.go @@ -0,0 +1,108 @@ +// Copyright 2021 beego +// +// 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 cache + +import ( + "context" + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFileCacheStartAndGC(t *testing.T) { + fc := NewFileCache().(*FileCache) + err := fc.StartAndGC(`{`) + assert.NotNil(t, err) + err = fc.StartAndGC(`{}`) + assert.Nil(t, err) + + assert.Equal(t, fc.CachePath, FileCachePath) + assert.Equal(t, fc.DirectoryLevel, FileCacheDirectoryLevel) + assert.Equal(t, fc.EmbedExpiry, int(FileCacheEmbedExpiry)) + assert.Equal(t, fc.FileSuffix, FileCacheFileSuffix) + + err = fc.StartAndGC(`{"CachePath":"/cache","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"0"}`) + // could not create dir + assert.NotNil(t, err) + + str := getTestCacheFilePath() + err = fc.StartAndGC(fmt.Sprintf(`{"CachePath":"%s","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"0"}`, str)) + assert.Nil(t, err) + assert.Equal(t, fc.CachePath, str) + assert.Equal(t, fc.DirectoryLevel, 2) + assert.Equal(t, fc.EmbedExpiry, 0) + assert.Equal(t, fc.FileSuffix, ".bin") + + err = fc.StartAndGC(fmt.Sprintf(`{"CachePath":"%s","FileSuffix":".bin","DirectoryLevel":"aaa","EmbedExpiry":"0"}`, str)) + assert.NotNil(t, err) + + err = fc.StartAndGC(fmt.Sprintf(`{"CachePath":"%s","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"aaa"}`, str)) + assert.NotNil(t, err) +} + +func TestFileCacheInit(t *testing.T) { + fc := NewFileCache().(*FileCache) + fc.CachePath = "////aaa" + err := fc.Init() + assert.NotNil(t, err) + fc.CachePath = getTestCacheFilePath() + err = fc.Init() + assert.Nil(t, err) +} + +func TestFileGetContents(t *testing.T) { + _, err := FileGetContents("/bin/aaa") + assert.NotNil(t, err) + fn := filepath.Join(os.TempDir(), "fileCache.txt") + f, err := os.Create(fn) + assert.Nil(t, err) + _, err = f.WriteString("text") + assert.Nil(t, err) + data, err := FileGetContents(fn) + assert.Nil(t, err) + assert.Equal(t, "text", string(data)) +} + +func TestGobEncodeDecode(t *testing.T) { + _, err := GobEncode(func() { + fmt.Print("test func") + }) + assert.NotNil(t, err) + data, err := GobEncode(&FileCacheItem{ + Data: "hello", + }) + assert.Nil(t, err) + err = GobDecode([]byte("wrong data"), &FileCacheItem{}) + assert.NotNil(t, err) + dci := &FileCacheItem{} + err = GobDecode(data, dci) + assert.Nil(t, err) + assert.Equal(t, "hello", dci.Data) +} + +func TestFileCacheDelete(t *testing.T) { + fc := NewFileCache() + err := fc.StartAndGC(`{}`) + assert.Nil(t, err) + err = fc.Delete(context.Background(), "my-key") + assert.Nil(t, err) +} + +func getTestCacheFilePath() string { + return filepath.Join(os.TempDir(), "test", "file.txt") +} \ No newline at end of file diff --git a/client/cache/memcache/memcache.go b/client/cache/memcache/memcache.go index bbafbd94..3816444f 100644 --- a/client/cache/memcache/memcache.go +++ b/client/cache/memcache/memcache.go @@ -32,7 +32,6 @@ package memcache import ( "context" "encoding/json" - "errors" "fmt" "strings" "time" @@ -40,6 +39,7 @@ import ( "github.com/bradfitz/gomemcache/memcache" "github.com/beego/beego/v2/client/cache" + "github.com/beego/beego/v2/core/berror" ) // Cache Memcache adapter. @@ -55,30 +55,25 @@ func NewMemCache() cache.Cache { // Get get value from memcache. func (rc *Cache) Get(ctx context.Context, key string) (interface{}, error) { - if rc.conn == nil { - if err := rc.connectInit(); err != nil { - return nil, err - } - } if item, err := rc.conn.Get(key); err == nil { return item.Value, nil } else { - return nil, err + return nil, berror.Wrapf(err, cache.MemCacheCurdFailed, + "could not read data from memcache, please check your key, network and connection. Root cause: %s", + err.Error()) } } // GetMulti gets a value from a key in memcache. func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) { rv := make([]interface{}, len(keys)) - if rc.conn == nil { - if err := rc.connectInit(); err != nil { - return rv, err - } - } mv, err := rc.conn.GetMulti(keys) if err != nil { - return rv, err + return rv, berror.Wrapf(err, cache.MemCacheCurdFailed, + "could not read multiple key-values from memcache, " + + "please check your keys, network and connection. Root cause: %s", + err.Error()) } keysErr := make([]string, 0) @@ -93,78 +88,54 @@ func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, er if len(keysErr) == 0 { return rv, nil } - return rv, fmt.Errorf(strings.Join(keysErr, "; ")) + return rv, berror.Error(cache.MultiGetFailed, strings.Join(keysErr, "; ")) } // Put puts a value into memcache. func (rc *Cache) Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error { - if rc.conn == nil { - if err := rc.connectInit(); err != nil { - return err - } - } item := memcache.Item{Key: key, Expiration: int32(timeout / time.Second)} if v, ok := val.([]byte); ok { item.Value = v } else if str, ok := val.(string); ok { item.Value = []byte(str) } else { - return errors.New("val only support string and []byte") + return berror.Errorf(cache.InvalidMemCacheValue, + "the value must be string or byte[]. key: %s, value:%v", key, val) } - return rc.conn.Set(&item) + return berror.Wrapf(rc.conn.Set(&item), cache.MemCacheCurdFailed, + "could not put key-value to memcache, key: %s", key) } // Delete deletes a value in memcache. func (rc *Cache) Delete(ctx context.Context, key string) error { - if rc.conn == nil { - if err := rc.connectInit(); err != nil { - return err - } - } - return rc.conn.Delete(key) + return berror.Wrapf(rc.conn.Delete(key), cache.MemCacheCurdFailed, + "could not delete key-value from memcache, key: %s", key) } // Incr increases counter. func (rc *Cache) Incr(ctx context.Context, key string) error { - if rc.conn == nil { - if err := rc.connectInit(); err != nil { - return err - } - } _, err := rc.conn.Increment(key, 1) - return err + return berror.Wrapf(err, cache.MemCacheCurdFailed, + "could not increase value for key: %s", key) } // Decr decreases counter. func (rc *Cache) Decr(ctx context.Context, key string) error { - if rc.conn == nil { - if err := rc.connectInit(); err != nil { - return err - } - } _, err := rc.conn.Decrement(key, 1) - return err + return berror.Wrapf(err, cache.MemCacheCurdFailed, + "could not decrease value for key: %s", key) } // IsExist checks if a value exists in memcache. func (rc *Cache) IsExist(ctx context.Context, key string) (bool, error) { - if rc.conn == nil { - if err := rc.connectInit(); err != nil { - return false, err - } - } - _, err := rc.conn.Get(key) + _, err := rc.Get(ctx, key) return err == nil, err } // ClearAll clears all cache in memcache. func (rc *Cache) ClearAll(context.Context) error { - if rc.conn == nil { - if err := rc.connectInit(); err != nil { - return err - } - } - return rc.conn.FlushAll() + return berror.Wrap(rc.conn.FlushAll(), cache.MemCacheCurdFailed, + "try to clear all key-value pairs failed") } // StartAndGC starts the memcache adapter. @@ -172,21 +143,15 @@ func (rc *Cache) ClearAll(context.Context) error { // If an error occurs during connecting, an error is returned func (rc *Cache) StartAndGC(config string) error { var cf map[string]string - json.Unmarshal([]byte(config), &cf) + if err := json.Unmarshal([]byte(config), &cf); err != nil { + return berror.Wrapf(err, cache.InvalidMemCacheCfg, + "could not unmarshal this config, it must be valid json stringP: %s", config) + } + if _, ok := cf["conn"]; !ok { - return errors.New("config has no conn key") + return berror.Errorf(cache.InvalidMemCacheCfg, `config must contains "conn" field: %s`, config) } rc.conninfo = strings.Split(cf["conn"], ";") - if rc.conn == nil { - if err := rc.connectInit(); err != nil { - return err - } - } - return nil -} - -// connect to memcache and keep the connection. -func (rc *Cache) connectInit() error { rc.conn = memcache.New(rc.conninfo...) return nil } diff --git a/client/cache/memcache/memcache_test.go b/client/cache/memcache/memcache_test.go index ea528d63..a6c1f19c 100644 --- a/client/cache/memcache/memcache_test.go +++ b/client/cache/memcache/memcache_test.go @@ -19,6 +19,7 @@ import ( "fmt" "os" "strconv" + "strings" "testing" "time" @@ -106,7 +107,7 @@ func TestMemcacheCache(t *testing.T) { assert.Equal(t, "author1", string(vv[1].([]byte))) assert.NotNil(t, err) - assert.Equal(t, "key [astaxie0] error: key not exist", err.Error()) + assert.True(t, strings.Contains(err.Error(), "key not exist")) assert.Nil(t, bm.ClearAll(context.Background())) // test clear all diff --git a/client/cache/memory.go b/client/cache/memory.go index 8dab5f2d..e4b704c9 100644 --- a/client/cache/memory.go +++ b/client/cache/memory.go @@ -17,11 +17,12 @@ package cache import ( "context" "encoding/json" - "errors" "fmt" "strings" "sync" "time" + + "github.com/beego/beego/v2/core/berror" ) var ( @@ -29,8 +30,6 @@ var ( DefaultEvery = 60 // 1 minute ) -const keyNotExistMsg = "key not exist" - // MemoryItem stores memory cache item. type MemoryItem struct { val interface{} @@ -66,13 +65,14 @@ func NewMemoryCache() Cache { func (bc *MemoryCache) Get(ctx context.Context, key string) (interface{}, error) { bc.RLock() defer bc.RUnlock() - if itm, ok := bc.items[key]; ok { + if itm, ok := + bc.items[key]; ok { if itm.isExpire() { - return nil, errors.New("the key is expired") + return nil, ErrKeyExpired } return itm.val, nil } - return nil, errors.New(keyNotExistMsg) + return nil, ErrKeyNotExist } // GetMulti gets caches from memory. @@ -93,7 +93,7 @@ func (bc *MemoryCache) GetMulti(ctx context.Context, keys []string) ([]interface if len(keysErr) == 0 { return rc, nil } - return rc, errors.New(strings.Join(keysErr, "; ")) + return rc, berror.Error(MultiGetFailed, strings.Join(keysErr, "; ")) } // Put puts cache into memory. @@ -110,17 +110,11 @@ func (bc *MemoryCache) Put(ctx context.Context, key string, val interface{}, tim } // Delete cache in memory. +// If the key is not found, it will not return error func (bc *MemoryCache) Delete(ctx context.Context, key string) error { bc.Lock() defer bc.Unlock() - if _, ok := bc.items[key]; !ok { - - return errors.New(keyNotExistMsg) - } delete(bc.items, key) - if _, ok := bc.items[key]; ok { - return errors.New("delete key error") - } return nil } @@ -131,7 +125,7 @@ func (bc *MemoryCache) Incr(ctx context.Context, key string) error { defer bc.Unlock() itm, ok := bc.items[key] if !ok { - return errors.New(keyNotExistMsg) + return ErrKeyNotExist } val, err := incr(itm.val) @@ -148,7 +142,7 @@ func (bc *MemoryCache) Decr(ctx context.Context, key string) error { defer bc.Unlock() itm, ok := bc.items[key] if !ok { - return errors.New(keyNotExistMsg) + return ErrKeyNotExist } val, err := decr(itm.val) @@ -180,7 +174,9 @@ func (bc *MemoryCache) ClearAll(context.Context) error { // StartAndGC starts memory cache. Checks expiration in every clock time. func (bc *MemoryCache) StartAndGC(config string) error { var cf map[string]int - json.Unmarshal([]byte(config), &cf) + if err := json.Unmarshal([]byte(config), &cf); err != nil { + return berror.Wrapf(err, InvalidMemoryCacheCfg, "invalid config, please check your input: %s", config) + } if _, ok := cf["interval"]; !ok { cf = make(map[string]int) cf["interval"] = DefaultEvery diff --git a/client/cache/module.go b/client/cache/module.go new file mode 100644 index 00000000..5a4e499e --- /dev/null +++ b/client/cache/module.go @@ -0,0 +1,17 @@ +// Copyright 2021 beego +// +// 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 cache + +const moduleName = "cache" diff --git a/client/cache/redis/redis.go b/client/cache/redis/redis.go index 180222ae..7e70af2e 100644 --- a/client/cache/redis/redis.go +++ b/client/cache/redis/redis.go @@ -32,7 +32,6 @@ package redis import ( "context" "encoding/json" - "errors" "fmt" "strconv" "strings" @@ -41,6 +40,7 @@ import ( "github.com/gomodule/redigo/redis" "github.com/beego/beego/v2/client/cache" + "github.com/beego/beego/v2/core/berror" ) var ( @@ -67,15 +67,20 @@ func NewRedisCache() cache.Cache { } // Execute the redis commands. args[0] must be the key name -func (rc *Cache) do(commandName string, args ...interface{}) (reply interface{}, err error) { - if len(args) < 1 { - return nil, errors.New("missing required arguments") - } +func (rc *Cache) do(commandName string, args ...interface{}) (interface{}, error) { args[0] = rc.associate(args[0]) c := rc.p.Get() - defer c.Close() + defer func() { + _ = c.Close() + }() - return c.Do(commandName, args...) + reply, err := c.Do(commandName, args...) + if err != nil { + return nil, berror.Wrapf(err, cache.RedisCacheCurdFailed, + "could not execute this command: %s", commandName) + } + + return reply, nil } // associate with config key. @@ -95,7 +100,9 @@ func (rc *Cache) Get(ctx context.Context, key string) (interface{}, error) { // GetMulti gets cache from redis. func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) { c := rc.p.Get() - defer c.Close() + defer func() { + _ = c.Close() + }() var args []interface{} for _, key := range keys { args = append(args, rc.associate(key)) @@ -137,13 +144,16 @@ func (rc *Cache) Decr(ctx context.Context, key string) error { } // ClearAll deletes all cache in the redis collection +// Be careful about this method, because it scans all keys and the delete them one by one func (rc *Cache) ClearAll(context.Context) error { cachedKeys, err := rc.Scan(rc.key + ":*") if err != nil { return err } c := rc.p.Get() - defer c.Close() + defer func() { + _ = c.Close() + }() for _, str := range cachedKeys { if _, err = c.Do("DEL", str); err != nil { return err @@ -155,7 +165,9 @@ func (rc *Cache) ClearAll(context.Context) error { // Scan scans all keys matching a given pattern. func (rc *Cache) Scan(pattern string) (keys []string, err error) { c := rc.p.Get() - defer c.Close() + defer func() { + _ = c.Close() + }() var ( cursor uint64 = 0 // start result []interface{} @@ -186,13 +198,16 @@ func (rc *Cache) Scan(pattern string) (keys []string, err error) { // Cached items in redis are stored forever, no garbage collection happens func (rc *Cache) StartAndGC(config string) error { var cf map[string]string - json.Unmarshal([]byte(config), &cf) + err := json.Unmarshal([]byte(config), &cf) + if err != nil { + return berror.Wrapf(err, cache.InvalidRedisCacheCfg, "could not unmarshal the config: %s", config) + } if _, ok := cf["key"]; !ok { cf["key"] = DefaultKey } if _, ok := cf["conn"]; !ok { - return errors.New("config has no conn key") + return berror.Wrapf(err, cache.InvalidRedisCacheCfg, "config missing conn field: %s", config) } // Format redis://@: @@ -229,9 +244,16 @@ func (rc *Cache) StartAndGC(config string) error { rc.connectInit() c := rc.p.Get() - defer c.Close() + defer func() { + _ = c.Close() + }() - return c.Err() + // test connection + if err = c.Err(); err != nil { + return berror.Wrapf(err, cache.InvalidConnection, + "can not connect to remote redis server, please check the connection info and network state: %s", config) + } + return nil } // connect to redis. @@ -239,19 +261,20 @@ func (rc *Cache) connectInit() { dialFunc := func() (c redis.Conn, err error) { c, err = redis.Dial("tcp", rc.conninfo) if err != nil { - return nil, err + return nil, berror.Wrapf(err, cache.DialFailed, + "could not dial to remote server: %s ", rc.conninfo) } if rc.password != "" { - if _, err := c.Do("AUTH", rc.password); err != nil { - c.Close() + if _, err = c.Do("AUTH", rc.password); err != nil { + _ = c.Close() return nil, err } } _, selecterr := c.Do("SELECT", rc.dbNum) if selecterr != nil { - c.Close() + _ = c.Close() return nil, selecterr } return diff --git a/client/cache/ssdb/ssdb.go b/client/cache/ssdb/ssdb.go index f637d2ab..e715d07f 100644 --- a/client/cache/ssdb/ssdb.go +++ b/client/cache/ssdb/ssdb.go @@ -3,7 +3,6 @@ package ssdb import ( "context" "encoding/json" - "errors" "fmt" "strconv" "strings" @@ -12,6 +11,7 @@ import ( "github.com/ssdb/gossdb/ssdb" "github.com/beego/beego/v2/client/cache" + "github.com/beego/beego/v2/core/berror" ) // Cache SSDB adapter @@ -27,31 +27,21 @@ func NewSsdbCache() cache.Cache { // Get gets a key's value from memcache. func (rc *Cache) Get(ctx context.Context, key string) (interface{}, error) { - if rc.conn == nil { - if err := rc.connectInit(); err != nil { - return nil, err - } - } value, err := rc.conn.Get(key) if err == nil { return value, nil } - return nil, err + return nil, berror.Wrapf(err, cache.SsdbCacheCurdFailed, "could not get value, key: %s", key) } // GetMulti gets one or keys values from ssdb. func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, error) { size := len(keys) values := make([]interface{}, size) - if rc.conn == nil { - if err := rc.connectInit(); err != nil { - return values, err - } - } res, err := rc.conn.Do("multi_get", keys) if err != nil { - return values, err + return values, berror.Wrapf(err, cache.SsdbCacheCurdFailed, "multi_get failed, key: %v", keys) } resSize := len(res) @@ -70,7 +60,7 @@ func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, er } if len(keysErr) != 0 { - return values, fmt.Errorf(strings.Join(keysErr, "; ")) + return values, berror.Error(cache.MultiGetFailed, strings.Join(keysErr, "; ")) } return values, nil @@ -78,26 +68,16 @@ func (rc *Cache) GetMulti(ctx context.Context, keys []string) ([]interface{}, er // DelMulti deletes one or more keys from memcache func (rc *Cache) DelMulti(keys []string) error { - if rc.conn == nil { - if err := rc.connectInit(); err != nil { - return err - } - } _, err := rc.conn.Do("multi_del", keys) - return err + return berror.Wrapf(err, cache.SsdbCacheCurdFailed, "multi_del failed: %v", keys) } // Put puts value into memcache. // value: must be of type string func (rc *Cache) Put(ctx context.Context, key string, val interface{}, timeout time.Duration) error { - if rc.conn == nil { - if err := rc.connectInit(); err != nil { - return err - } - } v, ok := val.(string) if !ok { - return errors.New("value must string") + return berror.Errorf(cache.InvalidSsdbCacheValue, "value must be string: %v", val) } var resp []string var err error @@ -108,57 +88,37 @@ func (rc *Cache) Put(ctx context.Context, key string, val interface{}, timeout t resp, err = rc.conn.Do("setx", key, v, ttl) } if err != nil { - return err + return berror.Wrapf(err, cache.SsdbCacheCurdFailed, "set or setx failed, key: %s", key) } if len(resp) == 2 && resp[0] == "ok" { return nil } - return errors.New("bad response") + return berror.Errorf(cache.SsdbBadResponse, "the response from SSDB server is invalid: %v", resp) } // Delete deletes a value in memcache. func (rc *Cache) Delete(ctx context.Context, key string) error { - if rc.conn == nil { - if err := rc.connectInit(); err != nil { - return err - } - } _, err := rc.conn.Del(key) - return err + return berror.Wrapf(err, cache.SsdbCacheCurdFailed, "del failed: %s", key) } // Incr increases a key's counter. func (rc *Cache) Incr(ctx context.Context, key string) error { - if rc.conn == nil { - if err := rc.connectInit(); err != nil { - return err - } - } _, err := rc.conn.Do("incr", key, 1) - return err + return berror.Wrapf(err, cache.SsdbCacheCurdFailed, "increase failed: %s", key) } // Decr decrements a key's counter. func (rc *Cache) Decr(ctx context.Context, key string) error { - if rc.conn == nil { - if err := rc.connectInit(); err != nil { - return err - } - } _, err := rc.conn.Do("incr", key, -1) - return err + return berror.Wrapf(err, cache.SsdbCacheCurdFailed, "decrease failed: %s", key) } // IsExist checks if a key exists in memcache. func (rc *Cache) IsExist(ctx context.Context, key string) (bool, error) { - if rc.conn == nil { - if err := rc.connectInit(); err != nil { - return false, err - } - } resp, err := rc.conn.Do("exists", key) if err != nil { - return false, err + return false, berror.Wrapf(err, cache.SsdbCacheCurdFailed, "exists failed: %s", key) } if len(resp) == 2 && resp[1] == "1" { return true, nil @@ -167,13 +127,9 @@ func (rc *Cache) IsExist(ctx context.Context, key string) (bool, error) { } -// ClearAll clears all cached items in memcache. +// ClearAll clears all cached items in ssdb. +// If there are many keys, this method may spent much time. func (rc *Cache) ClearAll(context.Context) error { - if rc.conn == nil { - if err := rc.connectInit(); err != nil { - return err - } - } keyStart, keyEnd, limit := "", "", 50 resp, err := rc.Scan(keyStart, keyEnd, limit) for err == nil { @@ -187,21 +143,16 @@ func (rc *Cache) ClearAll(context.Context) error { } _, e := rc.conn.Do("multi_del", keys) if e != nil { - return e + return berror.Wrapf(e, cache.SsdbCacheCurdFailed, "multi_del failed: %v", keys) } keyStart = resp[size-2] resp, err = rc.Scan(keyStart, keyEnd, limit) } - return err + return berror.Wrap(err, cache.SsdbCacheCurdFailed, "scan failed") } // Scan key all cached in ssdb. func (rc *Cache) Scan(keyStart string, keyEnd string, limit int) ([]string, error) { - if rc.conn == nil { - if err := rc.connectInit(); err != nil { - return nil, err - } - } resp, err := rc.conn.Do("scan", keyStart, keyEnd, limit) if err != nil { return nil, err @@ -214,30 +165,36 @@ func (rc *Cache) Scan(keyStart string, keyEnd string, limit int) ([]string, erro // If an error occurs during connection, an error is returned func (rc *Cache) StartAndGC(config string) error { var cf map[string]string - json.Unmarshal([]byte(config), &cf) + err := json.Unmarshal([]byte(config), &cf) + if err != nil { + return berror.Wrapf(err, cache.InvalidSsdbCacheCfg, + "unmarshal this config failed, it must be a valid json string: %s", config) + } if _, ok := cf["conn"]; !ok { - return errors.New("config has no conn key") + return berror.Wrapf(err, cache.InvalidSsdbCacheCfg, + "Missing conn field: %s", config) } rc.conninfo = strings.Split(cf["conn"], ";") - if rc.conn == nil { - if err := rc.connectInit(); err != nil { - return err - } - } - return nil + return rc.connectInit() } // connect to memcache and keep the connection. func (rc *Cache) connectInit() error { conninfoArray := strings.Split(rc.conninfo[0], ":") + if len(conninfoArray) < 2 { + return berror.Errorf(cache.InvalidSsdbCacheCfg, "The value of conn should be host:port: %s", rc.conninfo[0]) + } host := conninfoArray[0] port, e := strconv.Atoi(conninfoArray[1]) if e != nil { - return e + return berror.Errorf(cache.InvalidSsdbCacheCfg, "Port is invalid. It must be integer, %s", rc.conninfo[0]) } var err error - rc.conn, err = ssdb.Connect(host, port) - return err + if rc.conn, err = ssdb.Connect(host, port); err != nil { + return berror.Wrapf(err, cache.InvalidConnection, + "could not connect to SSDB, please check your connection info, network and firewall: %s", rc.conninfo[0]) + } + return nil } func init() { diff --git a/client/cache/ssdb/ssdb_test.go b/client/cache/ssdb/ssdb_test.go index e08ed573..fea755f4 100644 --- a/client/cache/ssdb/ssdb_test.go +++ b/client/cache/ssdb/ssdb_test.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "strconv" + "strings" "testing" "time" @@ -89,7 +90,7 @@ func TestSsdbcacheCache(t *testing.T) { assert.Nil(t, vv[1]) assert.NotNil(t, err) - assert.Equal(t, "key [ssdb11] error: key not exist", err.Error()) + assert.True(t, strings.Contains(err.Error(), "key not exist")) // test clear all done assert.Nil(t, ssdb.ClearAll(context.Background()))