Converge etcd functionality as part of quick.Config

This commit is contained in:
Harshavardhana 2018-04-23 11:05:56 -07:00 committed by kannappanr
parent 6df1e4a529
commit 481390d51a
10 changed files with 164 additions and 317 deletions

View File

@ -27,7 +27,7 @@ import (
"strings" "strings"
"time" "time"
etcdc "github.com/coreos/etcd/client" etcd "github.com/coreos/etcd/client"
"github.com/minio/cli" "github.com/minio/cli"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/auth"
@ -49,7 +49,7 @@ func checkUpdate(mode string) {
// Initialize and load config from remote etcd or local config directory // Initialize and load config from remote etcd or local config directory
func initConfig() { func initConfig() {
if globalEtcdClient != nil { if globalEtcdClient != nil {
kapi := etcdc.NewKeysAPI(globalEtcdClient) kapi := etcd.NewKeysAPI(globalEtcdClient)
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
_, err := kapi.Get(ctx, getConfigFile(), nil) _, err := kapi.Get(ctx, getConfigFile(), nil)
cancel() cancel()
@ -57,7 +57,7 @@ func initConfig() {
logger.FatalIf(migrateConfig(), "Config migration failed.") logger.FatalIf(migrateConfig(), "Config migration failed.")
logger.FatalIf(loadConfig(), "Unable to load config version: '%s'.", serverConfigVersion) logger.FatalIf(loadConfig(), "Unable to load config version: '%s'.", serverConfigVersion)
} else { } else {
if etcdc.IsKeyNotFound(err) { if etcd.IsKeyNotFound(err) {
logger.FatalIf(newConfig(), "Unable to initialize minio config for the first time.") logger.FatalIf(newConfig(), "Unable to initialize minio config for the first time.")
logger.Info("Created minio configuration file successfully at", globalEtcdClient.Endpoints()) logger.Info("Created minio configuration file successfully at", globalEtcdClient.Endpoints())
} else { } else {
@ -154,7 +154,7 @@ func handleCommonEnvVars() {
if ok { if ok {
etcdEndpoints := strings.Split(etcdEndpointsEnv, ",") etcdEndpoints := strings.Split(etcdEndpointsEnv, ",")
var err error var err error
globalEtcdClient, err = etcdc.New(etcdc.Config{ globalEtcdClient, err = etcd.New(etcd.Config{
Endpoints: etcdEndpoints, Endpoints: etcdEndpoints,
Transport: &http.Transport{ Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,

View File

@ -129,26 +129,17 @@ func (s *serverConfig) GetCacheConfig() CacheConfig {
// Save config file to corresponding backend // Save config file to corresponding backend
func Save(configFile string, data interface{}) error { func Save(configFile string, data interface{}) error {
if globalEtcdClient == nil { return quick.SaveConfig(data, configFile, globalEtcdClient)
return quick.SaveLocalConfig(configFile, data)
}
return quick.SaveEtcdConfig(configFile, data, globalEtcdClient)
} }
// Load config from backend // Load config from backend
func Load(configFile string, data interface{}) (quick.Config, error) { func Load(configFile string, data interface{}) (quick.Config, error) {
if globalEtcdClient == nil { return quick.LoadConfig(configFile, globalEtcdClient, data)
return quick.LoadLocalConfig(configFile, data)
}
return quick.LoadEtcdConfig(configFile, data, globalEtcdClient)
} }
// GetVersion gets config version from backend // GetVersion gets config version from backend
func GetVersion(configFile string) (string, error) { func GetVersion(configFile string) (string, error) {
if globalEtcdClient == nil { return quick.GetVersion(configFile, globalEtcdClient)
return quick.GetLocalVersion(configFile)
}
return quick.GetEtcdVersion(configFile, globalEtcdClient)
} }
// Returns the string describing a difference with the given // Returns the string describing a difference with the given
@ -296,17 +287,7 @@ func newConfig() error {
// newQuickConfig - initialize a new server config, with an allocated // newQuickConfig - initialize a new server config, with an allocated
// quick.Config interface. // quick.Config interface.
func newQuickConfig(srvCfg *serverConfig) (*serverConfig, error) { func newQuickConfig(srvCfg *serverConfig) (*serverConfig, error) {
if globalEtcdClient == nil { qcfg, err := quick.NewConfig(srvCfg, globalEtcdClient)
qcfg, err := quick.NewLocalConfig(srvCfg)
if err != nil {
return nil, err
}
srvCfg.Config = qcfg
return srvCfg, nil
}
qcfg, err := quick.NewEtcdConfig(srvCfg, globalEtcdClient)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -21,7 +21,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
etcdc "github.com/coreos/etcd/client" etcd "github.com/coreos/etcd/client"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/event" "github.com/minio/minio/pkg/event"
@ -195,7 +195,7 @@ func purgeV1() error {
cv1 := &configV1{} cv1 := &configV1{}
_, err := Load(configFile, cv1) _, err := Load(configFile, cv1)
if os.IsNotExist(err) || etcdc.IsKeyNotFound(err) { if os.IsNotExist(err) || etcd.IsKeyNotFound(err) {
return nil return nil
} else if err != nil { } else if err != nil {
return fmt.Errorf("Unable to load config version 1. %v", err) return fmt.Errorf("Unable to load config version 1. %v", err)

View File

@ -22,7 +22,7 @@ import (
"runtime" "runtime"
"time" "time"
etcdc "github.com/coreos/etcd/client" etcd "github.com/coreos/etcd/client"
humanize "github.com/dustin/go-humanize" humanize "github.com/dustin/go-humanize"
"github.com/fatih/color" "github.com/fatih/color"
xhttp "github.com/minio/minio/cmd/http" xhttp "github.com/minio/minio/cmd/http"
@ -200,7 +200,7 @@ var (
globalRPCAPIVersion = RPCVersion{3, 0, 0} globalRPCAPIVersion = RPCVersion{3, 0, 0}
// Allocated etcd endpoint for config and bucket DNS. // Allocated etcd endpoint for config and bucket DNS.
globalEtcdClient etcdc.Client globalEtcdClient etcd.Client
// Allocated DNS config wrapper over etcd client. // Allocated DNS config wrapper over etcd client.
globalDNSConfig dns.Config globalDNSConfig dns.Config

View File

@ -4,7 +4,7 @@ There are primarily two types of federation
- Bucket lookup from DNS - Bucket lookup from DNS
- Bucket is shared across many clusters - Bucket is shared across many clusters
This document will explain in detail about how to configure Minio supporting `Bucket lookup` This document will explain about how to configure Minio to support `Bucket lookup from DNS` style federation.
## Federation (Bucket Lookup) ## Federation (Bucket Lookup)
Bucket lookup federation requires two dependencies Bucket lookup federation requires two dependencies
@ -33,15 +33,17 @@ minio server http://rack{5...8}.host{5...8}.domain.com/mnt/export{1...32}
``` ```
In this configuration you can see `MINIO_ETCD_ENDPOINTS` points to the etcd backend which manages Minio's In this configuration you can see `MINIO_ETCD_ENDPOINTS` points to the etcd backend which manages Minio's
`config.json` and bucket SRV records. `MINIO_DOMAIN` indicates the domain suffix for the bucket which `config.json` and bucket DNS SRV records. `MINIO_DOMAIN` indicates the domain suffix for the bucket which
will be used to resolve bucket from DNS. For example if you have a bucket such as `mybucket`, the will be used to resolve bucket through DNS. For example if you have a bucket such as `mybucket`, the
client can use now `mybucket.domain.com` to directly resolve to the right cluster. `MINIO_PUBLIC_IP` client can use now `mybucket.domain.com` to directly resolve itself to the right cluster. `MINIO_PUBLIC_IP`
points to the public IP address where each cluster might be accessible, this is unique per each cluster. points to the public IP address where each cluster might be accessible, this is unique for each cluster.
NOTE: `mybucket` only exists on one cluster either `cluster1` or `cluster2` this is truly random and NOTE: `mybucket` only exists on one cluster either `cluster1` or `cluster2` this is random and
is decided by how `domain.com` gets resolved, if there is a round-robin DNS on `domain.com` then is decided by how `domain.com` gets resolved, if there is a round-robin DNS on `domain.com` then
it is truly random which cluster might provision the bucket. This control is not provided to the it is randomized which cluster might provision the bucket.
client yet, but can be done based on the `region` parameter as supported by `AWS S3` specification.
TODO: For now the control to create the bucket from a client to the right cluster using `region` paramter
is not implemented yet.

View File

@ -28,7 +28,7 @@ import (
"time" "time"
"github.com/coredns/coredns/plugin/etcd/msg" "github.com/coredns/coredns/plugin/etcd/msg"
etcdc "github.com/coreos/etcd/client" etcd "github.com/coreos/etcd/client"
) )
// create a new coredns service record for the bucket. // create a new coredns service record for the bucket.
@ -43,7 +43,7 @@ func newCoreDNSMsg(bucket string, ip string, port int, ttl uint32) ([]byte, erro
// Retrieves list of DNS entries for a bucket. // Retrieves list of DNS entries for a bucket.
func (c *coreDNS) List() ([]SrvRecord, error) { func (c *coreDNS) List() ([]SrvRecord, error) {
kapi := etcdc.NewKeysAPI(c.etcdClient) kapi := etcd.NewKeysAPI(c.etcdClient)
key := msg.Path(fmt.Sprintf("%s.", c.domainName), defaultPrefixPath) key := msg.Path(fmt.Sprintf("%s.", c.domainName), defaultPrefixPath)
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout) ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
r, err := kapi.Get(ctx, key, nil) r, err := kapi.Get(ctx, key, nil)
@ -69,7 +69,7 @@ func (c *coreDNS) List() ([]SrvRecord, error) {
// Retrieves DNS record for a bucket. // Retrieves DNS record for a bucket.
func (c *coreDNS) Get(bucket string) (SrvRecord, error) { func (c *coreDNS) Get(bucket string) (SrvRecord, error) {
kapi := etcdc.NewKeysAPI(c.etcdClient) kapi := etcd.NewKeysAPI(c.etcdClient)
key := msg.Path(fmt.Sprintf("%s.%s.", bucket, c.domainName), defaultPrefixPath) key := msg.Path(fmt.Sprintf("%s.%s.", bucket, c.domainName), defaultPrefixPath)
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout) ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
r, err := kapi.Get(ctx, key, nil) r, err := kapi.Get(ctx, key, nil)
@ -91,7 +91,7 @@ func (c *coreDNS) Put(bucket string) error {
if err != nil { if err != nil {
return err return err
} }
kapi := etcdc.NewKeysAPI(c.etcdClient) kapi := etcd.NewKeysAPI(c.etcdClient)
key := msg.Path(fmt.Sprintf("%s.%s.", bucket, c.domainName), defaultPrefixPath) key := msg.Path(fmt.Sprintf("%s.%s.", bucket, c.domainName), defaultPrefixPath)
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout) ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
_, err = kapi.Set(ctx, key, string(bucketMsg), nil) _, err = kapi.Set(ctx, key, string(bucketMsg), nil)
@ -101,7 +101,7 @@ func (c *coreDNS) Put(bucket string) error {
// Removes DNS entries added in Put(). // Removes DNS entries added in Put().
func (c *coreDNS) Delete(bucket string) error { func (c *coreDNS) Delete(bucket string) error {
kapi := etcdc.NewKeysAPI(c.etcdClient) kapi := etcd.NewKeysAPI(c.etcdClient)
key := msg.Path(fmt.Sprintf("%s.%s.", bucket, c.domainName), defaultPrefixPath) key := msg.Path(fmt.Sprintf("%s.%s.", bucket, c.domainName), defaultPrefixPath)
ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout) ctx, cancel := context.WithTimeout(context.Background(), defaultContextTimeout)
_, err := kapi.Delete(ctx, key, nil) _, err := kapi.Delete(ctx, key, nil)
@ -113,11 +113,11 @@ func (c *coreDNS) Delete(bucket string) error {
type coreDNS struct { type coreDNS struct {
domainName, domainIP string domainName, domainIP string
domainPort int domainPort int
etcdClient etcdc.Client etcdClient etcd.Client
} }
// NewCoreDNS - initialize a new coreDNS set/unset values. // NewCoreDNS - initialize a new coreDNS set/unset values.
func NewCoreDNS(domainName, domainIP, domainPort string, etcdClient etcdc.Client) (Config, error) { func NewCoreDNS(domainName, domainIP, domainPort string, etcdClient etcd.Client) (Config, error) {
if domainName == "" || domainIP == "" || etcdClient == nil { if domainName == "" || domainIP == "" || etcdClient == nil {
return nil, errors.New("invalid argument") return nil, errors.New("invalid argument")
} }

View File

@ -20,6 +20,7 @@ package quick
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -27,7 +28,9 @@ import (
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"time"
etcd "github.com/coreos/etcd/client"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
) )
@ -122,6 +125,55 @@ func saveFileConfig(filename string, v interface{}) error {
} }
func saveFileConfigEtcd(filename string, clnt etcd.Client, v interface{}) error {
// Fetch filename's extension
ext := filepath.Ext(filename)
// Marshal data
dataBytes, err := toMarshaller(ext)(v)
if err != nil {
return err
}
if runtime.GOOS == "windows" {
dataBytes = []byte(strings.Replace(string(dataBytes), "\n", "\r\n", -1))
}
kapi := etcd.NewKeysAPI(clnt)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
_, err = kapi.Update(ctx, filename, string(dataBytes))
cancel()
return err
}
func loadFileConfigEtcd(filename string, clnt etcd.Client, v interface{}) error {
kapi := etcd.NewKeysAPI(clnt)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
resp, err := kapi.Get(ctx, filename, nil)
cancel()
if err != nil {
return err
}
var ev *etcd.Node
switch {
case resp.Node.Dir:
for _, ev = range resp.Node.Nodes {
if string(ev.Key) == filename {
break
}
}
default:
ev = resp.Node
}
fileData := ev.Value
if runtime.GOOS == "windows" {
fileData = strings.Replace(ev.Value, "\r\n", "\n", -1)
}
// Unmarshal file's content
return toUnmarshaller(filepath.Ext(filename))([]byte(fileData), v)
}
// loadFileConfig unmarshals the file's content with the right // loadFileConfig unmarshals the file's content with the right
// decoder format according to the filename extension. If no // decoder format according to the filename extension. If no
// extension is provided, json will be selected by default. // extension is provided, json will be selected by default.

View File

@ -26,6 +26,7 @@ import (
"reflect" "reflect"
"sync" "sync"
etcd "github.com/coreos/etcd/client"
"github.com/fatih/structs" "github.com/fatih/structs"
"github.com/minio/minio/pkg/safe" "github.com/minio/minio/pkg/safe"
) )
@ -41,21 +42,22 @@ type Config interface {
DeepDiff(Config) ([]structs.Field, error) DeepDiff(Config) ([]structs.Field, error)
} }
// localConfig - implements quick.Config interface // config - implements quick.Config interface
type localConfig struct { type config struct {
data interface{} data interface{}
clnt etcd.Client
lock *sync.RWMutex lock *sync.RWMutex
} }
// Version returns the current config file format version // Version returns the current config file format version
func (d localConfig) Version() string { func (d config) Version() string {
st := structs.New(d.data) st := structs.New(d.data)
f := st.Field("Version") f := st.Field("Version")
return f.Value().(string) return f.Value().(string)
} }
// String converts JSON config to printable string // String converts JSON config to printable string
func (d localConfig) String() string { func (d config) String() string {
configBytes, _ := json.MarshalIndent(d.data, "", "\t") configBytes, _ := json.MarshalIndent(d.data, "", "\t")
return string(configBytes) return string(configBytes)
} }
@ -63,10 +65,14 @@ func (d localConfig) String() string {
// Save writes config data to a file. Data format // Save writes config data to a file. Data format
// is selected based on file extension or JSON if // is selected based on file extension or JSON if
// not provided. // not provided.
func (d localConfig) Save(filename string) error { func (d config) Save(filename string) error {
d.lock.Lock() d.lock.Lock()
defer d.lock.Unlock() defer d.lock.Unlock()
if d.clnt != nil {
return saveFileConfigEtcd(filename, d.clnt, d.data)
}
// Backup if given file exists // Backup if given file exists
oldData, err := ioutil.ReadFile(filename) oldData, err := ioutil.ReadFile(filename)
if err != nil { if err != nil {
@ -89,19 +95,22 @@ func (d localConfig) Save(filename string) error {
// Load - loads config from file and merge with currently set values // Load - loads config from file and merge with currently set values
// File content format is guessed from the file name extension, if not // File content format is guessed from the file name extension, if not
// available, consider that we have JSON. // available, consider that we have JSON.
func (d localConfig) Load(filename string) error { func (d config) Load(filename string) error {
d.lock.Lock() d.lock.Lock()
defer d.lock.Unlock() defer d.lock.Unlock()
if d.clnt != nil {
return loadFileConfigEtcd(filename, d.clnt, d.data)
}
return loadFileConfig(filename, d.data) return loadFileConfig(filename, d.data)
} }
// Data - grab internal data map for reading // Data - grab internal data map for reading
func (d localConfig) Data() interface{} { func (d config) Data() interface{} {
return d.data return d.data
} }
// Diff - list fields that are in A but not in B // Diff - list fields that are in A but not in B
func (d localConfig) Diff(c Config) ([]structs.Field, error) { func (d config) Diff(c Config) ([]structs.Field, error) {
var fields []structs.Field var fields []structs.Field
currFields := structs.Fields(d.Data()) currFields := structs.Fields(d.Data())
@ -123,7 +132,7 @@ func (d localConfig) Diff(c Config) ([]structs.Field, error) {
} }
// DeepDiff - list fields in A that are missing or not equal to fields in B // DeepDiff - list fields in A that are missing or not equal to fields in B
func (d localConfig) DeepDiff(c Config) ([]structs.Field, error) { func (d config) DeepDiff(c Config) ([]structs.Field, error) {
var fields []structs.Field var fields []structs.Field
currFields := structs.Fields(d.Data()) currFields := structs.Fields(d.Data())
@ -179,43 +188,50 @@ func writeFile(filename string, data []byte) error {
return safeFile.Close() return safeFile.Close()
} }
// NewLocalConfig - instantiate a new config r/w configs from local files. // GetVersion - extracts the version information.
func NewLocalConfig(data interface{}) (Config, error) { func GetVersion(filename string, clnt etcd.Client) (version string, err error) {
var qc Config
qc, err = LoadConfig(filename, clnt, &struct {
Version string
}{})
if err != nil {
return "", err
}
return qc.Version(), nil
}
// LoadConfig - loads json config from filename for the a given struct data
func LoadConfig(filename string, clnt etcd.Client, data interface{}) (qc Config, err error) {
qc, err = NewConfig(data, clnt)
if err != nil {
return nil, err
}
return qc, qc.Load(filename)
}
// SaveConfig - saves given configuration data into given file as JSON.
func SaveConfig(data interface{}, filename string, clnt etcd.Client) (err error) {
if err = checkData(data); err != nil {
return err
}
var qc Config
qc, err = NewConfig(data, clnt)
if err != nil {
return err
}
return qc.Save(filename)
}
// NewConfig loads config from etcd client if provided, otherwise loads from a local filename.
// fails when all else fails.
func NewConfig(data interface{}, clnt etcd.Client) (cfg Config, err error) {
if err := checkData(data); err != nil { if err := checkData(data); err != nil {
return nil, err return nil, err
} }
d := new(localConfig) d := new(config)
d.data = data d.data = data
d.clnt = clnt
d.lock = new(sync.RWMutex) d.lock = new(sync.RWMutex)
return d, nil return d, nil
} }
// GetLocalVersion - extracts the version information.
func GetLocalVersion(filename string) (version string, err error) {
var qc Config
if qc, err = LoadLocalConfig(filename, &struct {
Version string
}{}); err != nil {
return "", err
}
return qc.Version(), err
}
// LoadLocalConfig - loads json config from filename for the a given struct data
func LoadLocalConfig(filename string, data interface{}) (qc Config, err error) {
if qc, err = NewLocalConfig(data); err == nil {
err = qc.Load(filename)
}
return qc, err
}
// SaveLocalConfig - saves given configuration data into given file as JSON.
func SaveLocalConfig(filename string, data interface{}) (err error) {
var qc Config
if qc, err = NewLocalConfig(data); err == nil {
err = qc.Save(filename)
}
return err
}

View File

@ -1,204 +0,0 @@
/*
* Quick - Quick key value store for config files and persistent state files
*
* Quick (C) 2018 Minio, Inc.
*
* 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 quick
import (
"encoding/json"
"path/filepath"
"reflect"
"runtime"
"strings"
"sync"
"time"
etcdc "github.com/coreos/etcd/client"
"github.com/fatih/structs"
"golang.org/x/net/context"
)
// etcdConfig - implements quick.Config interface
type etcdConfig struct {
lock *sync.Mutex
data interface{}
clnt etcdc.Client
}
// Version returns the current config file format version
func (d etcdConfig) Version() string {
st := structs.New(d.data)
f := st.Field("Version")
return f.Value().(string)
}
// String converts JSON config to printable string
func (d etcdConfig) String() string {
configBytes, _ := json.MarshalIndent(d.data, "", "\t")
return string(configBytes)
}
// Save writes config data to an configured etcd endpoints. Data format
// is selected based on file extension or JSON if not provided.
func (d etcdConfig) Save(filename string) error {
d.lock.Lock()
defer d.lock.Unlock()
// Fetch filename's extension
ext := filepath.Ext(filename)
// Marshal data
dataBytes, err := toMarshaller(ext)(d.data)
if err != nil {
return err
}
if runtime.GOOS == "windows" {
dataBytes = []byte(strings.Replace(string(dataBytes), "\n", "\r\n", -1))
}
kapi := etcdc.NewKeysAPI(d.clnt)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
_, err = kapi.Update(ctx, filename, string(dataBytes))
cancel()
return err
}
// Load - loads config from file and merge with currently set values
// File content format is guessed from the file name extension, if not
// available, consider that we have JSON.
func (d etcdConfig) Load(filename string) error {
d.lock.Lock()
defer d.lock.Unlock()
kapi := etcdc.NewKeysAPI(d.clnt)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
resp, err := kapi.Get(ctx, filename, nil)
cancel()
if err != nil {
return err
}
var ev *etcdc.Node
switch {
case resp.Node.Dir:
for _, ev = range resp.Node.Nodes {
if string(ev.Key) == filename {
break
}
}
default:
ev = resp.Node
}
fileData := ev.Value
if runtime.GOOS == "windows" {
fileData = strings.Replace(ev.Value, "\r\n", "\n", -1)
}
// Unmarshal file's content
return toUnmarshaller(filepath.Ext(filename))([]byte(fileData), d.data)
}
// Data - grab internal data map for reading
func (d etcdConfig) Data() interface{} {
return d.data
}
//Diff - list fields that are in A but not in B
func (d etcdConfig) Diff(c Config) ([]structs.Field, error) {
var fields []structs.Field
currFields := structs.Fields(d.Data())
newFields := structs.Fields(c.Data())
var found bool
for _, currField := range currFields {
found = false
for _, newField := range newFields {
if reflect.DeepEqual(currField.Name(), newField.Name()) {
found = true
}
}
if !found {
fields = append(fields, *currField)
}
}
return fields, nil
}
// DeepDiff - list fields in A that are missing or not equal to fields in B
func (d etcdConfig) DeepDiff(c Config) ([]structs.Field, error) {
var fields []structs.Field
currFields := structs.Fields(d.Data())
newFields := structs.Fields(c.Data())
var found bool
for _, currField := range currFields {
found = false
for _, newField := range newFields {
if reflect.DeepEqual(currField.Value(), newField.Value()) {
found = true
}
}
if !found {
fields = append(fields, *currField)
}
}
return fields, nil
}
// NewEtcdConfig - instantiate a new etcd config
func NewEtcdConfig(data interface{}, clnt etcdc.Client) (Config, error) {
if err := checkData(data); err != nil {
return nil, err
}
d := new(etcdConfig)
d.data = data
d.clnt = clnt
d.lock = &sync.Mutex{}
return d, nil
}
// GetEtcdVersion - extracts the version information.
func GetEtcdVersion(filename string, clnt etcdc.Client) (version string, err error) {
var qc Config
if qc, err = LoadEtcdConfig(filename, &struct {
Version string
}{}, clnt); err != nil {
return "", err
}
return qc.Version(), err
}
// LoadEtcdConfig - loads json config from etcd backend for the given struct data
func LoadEtcdConfig(filename string, data interface{}, clnt etcdc.Client) (qc Config, err error) {
if qc, err = NewEtcdConfig(data, clnt); err == nil {
err = qc.Load(filename)
}
return qc, err
}
// SaveEtcdConfig - saves given configuration data into etcd backend.
func SaveEtcdConfig(filename string, data interface{}, clnt etcdc.Client) (err error) {
var qc Config
if qc, err = NewEtcdConfig(data, clnt); err == nil {
err = qc.Save(filename)
}
return err
}

View File

@ -36,7 +36,7 @@ func TestReadVersion(t *testing.T) {
Version string Version string
} }
saveMe := myStruct{"1"} saveMe := myStruct{"1"}
config, err := NewLocalConfig(&saveMe) config, err := NewConfig(&saveMe, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -45,7 +45,7 @@ func TestReadVersion(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
version, err := GetLocalVersion("test.json") version, err := GetVersion("test.json", nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -59,7 +59,7 @@ func TestReadVersionErr(t *testing.T) {
Version int Version int
} }
saveMe := myStruct{1} saveMe := myStruct{1}
_, err := NewLocalConfig(&saveMe) _, err := NewConfig(&saveMe, nil)
if err == nil { if err == nil {
t.Fatal("Unexpected should fail in initialization for bad input") t.Fatal("Unexpected should fail in initialization for bad input")
} }
@ -69,7 +69,7 @@ func TestReadVersionErr(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
_, err = GetLocalVersion("test.json") _, err = GetVersion("test.json", nil)
if err == nil { if err == nil {
t.Fatal("Unexpected should fail to fetch version") t.Fatal("Unexpected should fail to fetch version")
} }
@ -79,7 +79,7 @@ func TestReadVersionErr(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
_, err = GetLocalVersion("test.json") _, err = GetVersion("test.json", nil)
if err == nil { if err == nil {
t.Fatal("Unexpected should fail to fetch version") t.Fatal("Unexpected should fail to fetch version")
} }
@ -95,7 +95,7 @@ func TestSaveFailOnDir(t *testing.T) {
Version string Version string
} }
saveMe := myStruct{"1"} saveMe := myStruct{"1"}
config, err := NewLocalConfig(&saveMe) config, err := NewConfig(&saveMe, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -155,7 +155,7 @@ func TestLoadFile(t *testing.T) {
Directories []string Directories []string
} }
saveMe := myStruct{} saveMe := myStruct{}
_, err := LoadLocalConfig("test.json", &saveMe) _, err := LoadConfig("test.json", nil, &saveMe)
if err == nil { if err == nil {
t.Fatal(err) t.Fatal(err)
} }
@ -167,11 +167,11 @@ func TestLoadFile(t *testing.T) {
if err = file.Close(); err != nil { if err = file.Close(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, err = LoadLocalConfig("test.json", &saveMe) _, err = LoadConfig("test.json", nil, &saveMe)
if err == nil { if err == nil {
t.Fatal("Unexpected should fail to load empty JSON") t.Fatal("Unexpected should fail to load empty JSON")
} }
config, err := NewLocalConfig(&saveMe) config, err := NewConfig(&saveMe, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -186,7 +186,7 @@ func TestLoadFile(t *testing.T) {
} }
saveMe = myStruct{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}} saveMe = myStruct{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}}
config, err = NewLocalConfig(&saveMe) config, err = NewConfig(&saveMe, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -195,7 +195,7 @@ func TestLoadFile(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
saveMe1 := myStruct{} saveMe1 := myStruct{}
_, err = LoadLocalConfig("test.json", &saveMe1) _, err = LoadConfig("test.json", nil, &saveMe1)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -240,7 +240,7 @@ directories:
saveMe := myStruct{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}} saveMe := myStruct{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}}
// Save format using // Save format using
config, err := NewLocalConfig(&saveMe) config, err := NewConfig(&saveMe, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -262,7 +262,7 @@ directories:
// Check if the loaded data is the same as the saved one // Check if the loaded data is the same as the saved one
loadMe := myStruct{} loadMe := myStruct{}
config, err = NewLocalConfig(&loadMe) config, err = NewConfig(&loadMe, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -306,7 +306,7 @@ func TestJSONFormat(t *testing.T) {
saveMe := myStruct{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}} saveMe := myStruct{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}}
// Save format using // Save format using
config, err := NewLocalConfig(&saveMe) config, err := NewConfig(&saveMe, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -328,7 +328,7 @@ func TestJSONFormat(t *testing.T) {
// Check if the loaded data is the same as the saved one // Check if the loaded data is the same as the saved one
loadMe := myStruct{} loadMe := myStruct{}
config, err = NewLocalConfig(&loadMe) config, err = NewConfig(&loadMe, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -351,7 +351,7 @@ func TestSaveLoad(t *testing.T) {
Directories []string Directories []string
} }
saveMe := myStruct{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}} saveMe := myStruct{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}}
config, err := NewLocalConfig(&saveMe) config, err := NewConfig(&saveMe, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -361,7 +361,7 @@ func TestSaveLoad(t *testing.T) {
} }
loadMe := myStruct{Version: "1"} loadMe := myStruct{Version: "1"}
newConfig, err := NewLocalConfig(&loadMe) newConfig, err := NewConfig(&loadMe, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -393,7 +393,7 @@ func TestSaveBackup(t *testing.T) {
Directories []string Directories []string
} }
saveMe := myStruct{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}} saveMe := myStruct{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}}
config, err := NewLocalConfig(&saveMe) config, err := NewConfig(&saveMe, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -403,7 +403,7 @@ func TestSaveBackup(t *testing.T) {
} }
loadMe := myStruct{Version: "1"} loadMe := myStruct{Version: "1"}
newConfig, err := NewLocalConfig(&loadMe) newConfig, err := NewConfig(&loadMe, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -424,7 +424,7 @@ func TestSaveBackup(t *testing.T) {
t.Fatal("Expected to mismatch but succeeded instead") t.Fatal("Expected to mismatch but succeeded instead")
} }
config, err = NewLocalConfig(&mismatch) config, err = NewConfig(&mismatch, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -442,20 +442,20 @@ func TestDiff(t *testing.T) {
Directories []string Directories []string
} }
saveMe := myStruct{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}} saveMe := myStruct{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}}
config, err := NewLocalConfig(&saveMe) config, err := NewConfig(&saveMe, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
type myNewLocalConfigStruct struct { type myNewConfigStruct struct {
Version string Version string
// User string // User string
Password string Password string
Directories []string Directories []string
} }
mismatch := myNewLocalConfigStruct{"1", "nopassword", []string{"Work", "documents", "Music"}} mismatch := myNewConfigStruct{"1", "nopassword", []string{"Work", "documents", "Music"}}
newConfig, err := NewLocalConfig(&mismatch) newConfig, err := NewConfig(&mismatch, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -482,13 +482,13 @@ func TestDeepDiff(t *testing.T) {
Directories []string Directories []string
} }
saveMe := myStruct{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}} saveMe := myStruct{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}}
config, err := NewLocalConfig(&saveMe) config, err := NewConfig(&saveMe, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
mismatch := myStruct{"1", "Guest", "nopassword", []string{"Work", "documents", "Music"}} mismatch := myStruct{"1", "Guest", "nopassword", []string{"Work", "documents", "Music"}}
newConfig, err := NewLocalConfig(&mismatch) newConfig, err := NewConfig(&mismatch, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }