Files
my-rook-config/pkg/util/mocketcd.go
T

482 lines
13 KiB
Go

/*
Copyright 2016 The Rook Authors. All rights reserved.
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 util
import (
"errors"
"fmt"
"path"
"sort"
"strings"
etcd "github.com/coreos/etcd/client"
etcdError "github.com/coreos/etcd/error"
"golang.org/x/net/context"
)
// ************************************************************************************************
// KeysAPI interface mock implementation
// ************************************************************************************************
type MockEtcdClient struct {
MockSet func(ctx context.Context, key, value string, opts *etcd.SetOptions) (*etcd.Response, error)
MockGet func(ctx context.Context, key string, opts *etcd.GetOptions) (*etcd.Response, error)
MockDelete func(ctx context.Context, key string, opts *etcd.DeleteOptions) (*etcd.Response, error)
MockWatcher func(key string, opts *etcd.WatcherOptions) etcd.Watcher
WatcherResponses map[string]string
store *MockEtcdDir
index uint64
watchCount int
}
type MockEtcdDir struct {
Path string
Dirs map[string]*MockEtcdDir
Values map[string]string
}
func NewMockEtcdClient() *MockEtcdClient {
client := &MockEtcdClient{}
client.ResetStore()
return client
}
// Get an etcd value. Defaults to an in-memory response to values that have been set for this client.
func (r *MockEtcdClient) Get(ctx context.Context, key string, opts *etcd.GetOptions) (*etcd.Response, error) {
if r.MockGet != nil {
resp, err := r.MockGet(ctx, key, opts)
if resp != nil || err != nil {
return resp, err
}
}
return r.memoryGet(ctx, key, opts)
}
// Set an etcd value. Defaults to an in-memory store.
func (r *MockEtcdClient) Set(ctx context.Context, key, value string, opts *etcd.SetOptions) (*etcd.Response, error) {
r.index++
if r.MockSet != nil {
resp, err := r.MockSet(ctx, key, value, opts)
if resp != nil || err != nil {
return resp, err
}
}
return r.memorySet(ctx, key, value, opts)
}
func (r *MockEtcdClient) Delete(ctx context.Context, key string, opts *etcd.DeleteOptions) (*etcd.Response, error) {
if r.MockDelete != nil {
return r.MockDelete(ctx, key, opts)
}
parent, child, err := r.store.getParentDir(key)
if err != nil {
// Ignore when a key to delete does not exist
return nil, nil
}
delete(parent.Values, child)
if opts != nil {
if opts.Dir {
delete(parent.Dirs, child)
}
if opts.Recursive {
// How is this different than deleting the dir? It's already cleaned up implicitly
}
}
return nil, nil
}
func (r *MockEtcdClient) Create(ctx context.Context, key, value string) (*etcd.Response, error) {
return nil, nil
}
func (r *MockEtcdClient) CreateInOrder(ctx context.Context, dir, value string, opts *etcd.CreateInOrderOptions) (*etcd.Response, error) {
return nil, nil
}
func (r *MockEtcdClient) Update(ctx context.Context, key, value string) (*etcd.Response, error) {
return nil, nil
}
func (r *MockEtcdClient) Watcher(key string, opts *etcd.WatcherOptions) etcd.Watcher {
if r.MockWatcher != nil {
return r.MockWatcher(key, opts)
} else if r.WatcherResponses != nil && len(r.WatcherResponses) > 0 {
r.watchCount++
if r.watchCount > 500 {
r.Dump()
panic(fmt.Sprintf("I feel overworked watching etcd so fast. key=%s", key))
}
if response, ok := r.WatcherResponses[key]; ok {
return &MockWatcher{
MockNext: func(ctx context.Context) (*etcd.Response, error) {
return MockEtcdValueResponse(key, response, 1), nil
},
}
} else {
// If the test is specifying WatcherResponses, the key must be set
panic(fmt.Sprintf("key %s was not found in the mock WatcherResponses", key))
}
}
// default implementation is to just return a watcher that cancels right away
return &MockWatcher{
MockNext: func(ctx context.Context) (*etcd.Response, error) {
return nil, context.Canceled
},
}
}
// Reset the test value and directory store
func (r *MockEtcdClient) ResetStore() {
r.store = &MockEtcdDir{Path: "/"}
if r.WatcherResponses == nil {
r.WatcherResponses = make(map[string]string)
}
}
func (r *MockEtcdClient) Dump() {
if r.store == nil {
r.ResetStore()
}
r.store.dumpSortedSubdirs()
r.store.dumpSortedValues()
}
func (d *MockEtcdDir) dumpSortedSubdirs() {
logger.Infof("%s (%d subkeys, %d values)", d.Path, len(d.Dirs), len(d.Values))
// first make a copy of the keys from the dir store so we can sort it and not affect the original
dirs := make([]string, len(d.Dirs))
i := 0
for dir, _ := range d.Dirs {
dirs[i] = dir
i++
}
// now sort our copy and then print out the dirs
sort.Strings(dirs)
for _, dir := range dirs {
// Recursively print the values
subdir, _ := d.Dirs[dir]
subdir.dumpSortedSubdirs()
subdir.dumpSortedValues()
}
}
func (d *MockEtcdDir) dumpSortedValues() {
// first make a copy of the keys to not affect the original
keys := make([]string, len(d.Values))
i := 0
for k, _ := range d.Values {
keys[i] = k
i++
}
// now sort our copy and then print out the key/value pairs
sort.Strings(keys)
for _, key := range keys {
logger.Infof("%s/%s=%s", d.Path, key, d.Values[key])
}
}
// Get the child directories of the key
func (r *MockEtcdClient) GetChildDirs(key string) *Set {
children, err := GetDirChildKeys(r, key)
if err != nil {
logger.Warningf("failed to get children of key %s. err=%v", key, err)
return nil
}
return children
}
// Get a value. If the key was not found, returns the empty string.
// To distinguish between an empty value and a key not found, call Get()
func (r *MockEtcdClient) GetValue(key string) string {
response, err := r.memoryGet(context.Background(), key, nil)
if err != nil {
return ""
}
return response.Node.Value
}
// Set a value
func (r *MockEtcdClient) SetValue(key, value string) {
r.memorySet(context.Background(), key, value, nil)
}
// Delete a value
func (r *MockEtcdClient) DeleteValue(key string) {
r.Delete(context.Background(), key, nil)
}
// Delete a key and its children
func (r *MockEtcdClient) DeleteDir(key string) {
r.Delete(context.Background(), key, &etcd.DeleteOptions{Dir: true})
}
// Create a key and its parents as needed
func (r *MockEtcdClient) CreateDir(key string) {
r.Set(context.Background(), key, "", &etcd.SetOptions{Dir: true})
}
func (r *MockEtcdClient) CreateDirs(key string, children *Set) {
for child := range children.Iter() {
r.CreateDir(path.Join(key, child))
}
}
func (r *MockEtcdClient) memoryGet(ctx context.Context, key string, opts *etcd.GetOptions) (*etcd.Response, error) {
if r.store == nil {
r.store = &MockEtcdDir{Path: "/"}
}
parent, child, err := r.store.getParentDir(key)
if err == nil {
logger.Debugf("memoryGet: parent=%s, child=%s, values=%d, dirs=%d", parent.Path, child, len(parent.Values), len(parent.Dirs))
if existingValue, ok := parent.Values[child]; ok {
return MockEtcdValueResponse(key, existingValue, r.index), nil
}
recursive := opts != nil && opts.Recursive
if key == "/" {
// Returning the root directory
return r.MockEtcdDirResponse(key, parent, recursive, r.index), nil
} else if childDir, ok := parent.Dirs[child]; ok {
// Returning child directories
return r.MockEtcdDirResponse(key, childDir, recursive, r.index), nil
}
}
return nil, etcdError.NewError(etcdError.EcodeKeyNotFound, "value not found", r.index)
}
// Set a value in the memory store
func (r *MockEtcdClient) memorySet(ctx context.Context, key, value string, opts *etcd.SetOptions) (*etcd.Response, error) {
// Initialize the map on first use
if r.store == nil {
r.ResetStore()
}
parent, child, err := r.store.getParentDir(key)
// Check if the value already exists
if err == nil {
if existingValue, ok := parent.Values[child]; ok {
if opts != nil && opts.PrevExist == etcd.PrevNoExist {
return MockEtcdValueResponse(key, existingValue, r.index), errors.New("value already set")
}
}
}
// Set the value
if opts != nil && opts.Dir {
if _, ok := parent.Dirs[child]; ok {
if opts.PrevExist == etcd.PrevNoExist {
return nil, etcdError.NewError(etcdError.EcodeNodeExist, "dir already exists", r.index)
}
}
// Add the directory and all its parents
r.addDirectory(key)
} else {
// Add the key's parent(s)
parentKey, child := splitParentKey(key)
if parentKey == "" {
parent = r.store
} else {
parent = r.addDirectory(parentKey)
}
// Add the value
if parent.Values == nil {
parent.Values = make(map[string]string)
}
parent.Values[child] = value
}
return MockEtcdValueResponse(key, value, r.index), nil
}
func (d *MockEtcdDir) getParentDir(key string) (parent *MockEtcdDir, child string, err error) {
// Trim the leading slash
key = strings.TrimPrefix(key, "/")
segments := strings.Split(key, "/")
parent = d
err = nil
if len(segments) == 0 {
child = ""
return
}
if len(segments) == 1 {
child = segments[0]
return
}
// The leaf is the last segment of the key
child = segments[len(segments)-1]
// Traverse the tree to find the last dir
for i, segment := range segments {
if i == len(segments)-1 {
child = segment
// The last segment should be ignored since it's the child
break
}
dir, ok := parent.Dirs[segment]
if !ok {
err = errors.New("key not found: " + key)
return
}
parent = dir
}
return
}
func splitParentKey(key string) (parent, child string) {
childIndex := strings.LastIndex(key, "/")
parent = key[0:childIndex]
child = key[childIndex+1 : len(key)]
return
}
func splitFirstParentKey(key string) (parent, child string) {
key = strings.TrimPrefix(key, "/")
childIndex := strings.Index(key, "/")
if childIndex < 0 {
parent = key
child = ""
return
}
parent = key[0:childIndex]
child = key[childIndex+1 : len(key)]
return
}
// Add a directory to etcd and ensure all its parents are created as needed
func (r *MockEtcdClient) addDirectory(key string) *MockEtcdDir {
key = strings.TrimPrefix(key, "/")
if key == "" {
return r.store
}
return r.store.addDirectory(key)
}
func (d *MockEtcdDir) addDirectory(key string) *MockEtcdDir {
if key == "" {
// The recursion bottomed out
return d
}
child, recursiveChildren := splitFirstParentKey(key)
// Check if the directory already exists
if dir, ok := d.Dirs[child]; ok {
return dir.addDirectory(recursiveChildren)
}
// Create the directory
childDir := &MockEtcdDir{Path: path.Join(d.Path, child), Dirs: make(map[string]*MockEtcdDir), Values: make(map[string]string)}
if d.Dirs == nil {
d.Dirs = make(map[string]*MockEtcdDir)
}
d.Dirs[child] = childDir
return childDir.addDirectory(recursiveChildren)
}
// ************************************************************************************************
// Helpers to create etcd mocked responses
// ************************************************************************************************
func (r *MockEtcdClient) MockEtcdDirResponse(key string, dir *MockEtcdDir, recursive bool, index uint64) *etcd.Response {
return &etcd.Response{
Node: &etcd.Node{
Key: key,
Dir: true,
Nodes: dir.getChildNodes(recursive),
},
Index: index,
}
}
func (d *MockEtcdDir) getChildNodes(recursive bool) etcd.Nodes {
// Initialize the etcd nodes collection that will contain them all
childNodes := make(etcd.Nodes, len(d.Dirs)+len(d.Values))
childIndex := 0
// create and add etcd nodes for each child value
for childKey, childValue := range d.Values {
childNode := &etcd.Node{
Key: path.Join(d.Path, childKey),
Value: childValue,
Dir: false,
}
childNodes[childIndex] = childNode
childIndex++
}
// create and add etcd nodes for each child dir
for name, childDir := range d.Dirs {
childNode := &etcd.Node{
Key: path.Join(d.Path, name),
Dir: true,
}
childNode.Nodes = childDir.getChildNodes(recursive)
childNodes[childIndex] = childNode
childIndex++
}
return childNodes
}
func MockEtcdValueResponse(key string, value string, index uint64) *etcd.Response {
return &etcd.Response{
Node: &etcd.Node{
Key: key,
Dir: false,
Value: value,
},
Index: index,
}
}
// ************************************************************************************************
// Watcher interface Mock implementation
// ************************************************************************************************
type MockWatcher struct {
MockNext func(context.Context) (*etcd.Response, error)
}
func (r *MockWatcher) Next(ctx context.Context) (*etcd.Response, error) {
if r.MockNext != nil {
return r.MockNext(ctx)
}
return nil, nil
}