package etcd import ( "context" "fmt" "io/ioutil" "net/url" "os" "testing" "time" "github.com/influxdata/chronograf" "github.com/influxdata/chronograf/kv" "github.com/influxdata/chronograf/mocks" "github.com/stretchr/testify/require" clientv3 "go.etcd.io/etcd/client/v3" "go.etcd.io/etcd/server/v3/embed" ) func TestNewClient(t *testing.T) { _, err := NewClient(context.TODO(), WithEndpoints([]string{"localhost:2"}), WithDialTimeout(time.Second*5), WithRequestTimeout(time.Second*5), WithLogger(mocks.NewLogger()), WithLogin("user", "pass"), ) require.Error(t, err) } func NewService(t *testing.T) (chronograf.KVClient, func()) { dir, err := ioutil.TempDir("", "etcd.test") require.NoError(t, err) cfg := embed.NewConfig() cfg.Dir = dir e, err := embed.StartEtcd(cfg) require.NoError(t, err) select { case <-e.Server.ReadyNotify(): break case <-time.After(20 * time.Second): t.Fatal("etcd took too long to start") } endpoints := []string{} for i := range cfg.LPUrls { endpoints = append(endpoints, cfg.LPUrls[i].String()) } c, err := NewClient(context.TODO(), WithEndpoints(endpoints)) require.NoError(t, err) s, err := kv.NewService(context.TODO(), c) require.NoError(t, err) return s, func() { e.Close() os.RemoveAll(cfg.Dir) c.Close() } } func TestEtcd(t *testing.T) { s, closeFn := NewService(t) defer closeFn() ctx := context.TODO() src, err := s.SourcesStore().Add(ctx, chronograf.Source{ Name: "test", URL: "localhost:8086", }) require.NoError(t, err) require.Equal(t, "test", src.Name) srcs, err := s.SourcesStore().All(ctx) require.NoError(t, err) require.Equal(t, 1, len(srcs)) require.Equal(t, "test", srcs[0].Name) require.NoError(t, s.SourcesStore().Delete(ctx, src)) } func Test_WithURL(t *testing.T) { parse := func(val string) *url.URL { url, err := url.Parse(val) if err != nil { require.NoError(t, err) } return url } var tests = []struct { url *url.URL config clientv3.Config err string }{ { url: parse("etcd://u:p@127.0.0.1:2379"), config: clientv3.Config{ Endpoints: []string{"127.0.0.1:2379"}, Username: "u", Password: "p", }, }, { url: parse("etcds://u:p@127.0.0.1:2379"), config: clientv3.Config{ Endpoints: []string{"https://127.0.0.1:2379"}, Username: "u", Password: "p", }, }, { url: parse("etcd://u:p@127.0.0.1:2379?ca=a&ca=b"), err: "query parameter 'ca' can appear at most once", }, { url: parse("etcd://u:p@127.0.0.1:2379?cert=a&key=b&ca=c"), err: "no such file or directory", }, { url: parse("etcd://a:b@1.2.3.4:5555?ca=test.crt&key=test.key&cert=test.crt"), config: clientv3.Config{ Endpoints: []string{"1.2.3.4:5555"}, Username: "a", Password: "b", }, }, { url: parse("etcd://a:b@1.2.3.4:5555?ca=test.crt&cert=test.crt&key=test.key"), config: clientv3.Config{ Endpoints: []string{"1.2.3.4:5555"}, Username: "a", Password: "b", }, }, } for _, test := range tests { t.Run(test.url.String(), func(t *testing.T) { c := &client{} err := WithURL(test.url)(c) if test.err == "" { require.NoError(t, err) tlsConfig := c.config.TLS c.config.TLS = nil require.Equal(t, c.config, test.config) if test.url.Query().Get("ca") != "" { require.NotNil(t, tlsConfig.RootCAs) } if test.url.Query().Get("cert") != "" { require.Equal(t, len(tlsConfig.Certificates), 1) } } else { require.NotNil(t, err) // Contains is used, because nested exceptions can evolve with go versions require.Contains(t, fmt.Sprintf("%v", err), test.err) } }) } }