diff --git a/e2e/etcd_test.go b/e2e/etcd_test.go index 586b73df8fc8..bb505cdd6cf1 100644 --- a/e2e/etcd_test.go +++ b/e2e/etcd_test.go @@ -183,6 +183,7 @@ type etcdProcessClusterConfig struct { isPeerTLS bool isPeerAutoTLS bool initialToken string + isV3 bool } // newEtcdProcessCluster launches a new cluster from etcd processes, returning @@ -283,6 +284,9 @@ func (cfg *etcdProcessClusterConfig) etcdProcessConfigs() []*etcdProcessConfig { "--initial-cluster-token", cfg.initialToken, "--data-dir", dataDirPath, } + if cfg.isV3 { + args = append(args, "--experimental-v3demo") + } args = append(args, cfg.tlsArgs()...) diff --git a/e2e/etcdctlv3_test.go b/e2e/etcdctlv3_test.go new file mode 100644 index 000000000000..766d704ee718 --- /dev/null +++ b/e2e/etcdctlv3_test.go @@ -0,0 +1,143 @@ +// Copyright 2016 CoreOS, 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 e2e + +import ( + "fmt" + "strings" + "testing" + "time" + + "github.com/coreos/etcd/pkg/fileutil" + "github.com/coreos/etcd/pkg/testutil" +) + +func TestCtlV3SetQuorum(t *testing.T) { + checkTesting(t, testCtlV3Set(t, &configNoTLS, 5*time.Second, true)) +} + +func TestCtlV3SetQuorumTimeout(t *testing.T) { + err := testCtlV3Set(t, &configNoTLS, time.Nanosecond, true) + if !strings.Contains(err.Error(), "grpc: timed out trying to connect") { + t.Fatalf("expected timeout error, got %v", err) + } +} + +// TODO: Getting transport: x509: certificate signed by unknown authority +// func TestCtlV3SetClientTLSQuorum(t *testing.T) { +// checkTesting(t, testCtlV3Set(t, &configClientTLS, 5*time.Second, true)) +// } + +func TestCtlV3SetPeerTLSQuorum(t *testing.T) { + checkTesting(t, testCtlV3Set(t, &configPeerTLS, 5*time.Second, true)) +} + +// TODO: Getting transport: x509: certificate signed by unknown authority +// func TestCtlV3SetTLSQuorum(t *testing.T) { +// checkTesting(t, testCtlV3Set(t, &configTLS, 5*time.Second, true)) +// } + +func checkTesting(t *testing.T, err error) { + if err != nil { + t.Fatal(err) + } +} + +func stripSchema(s string) string { + if strings.HasPrefix(s, "http://") { + s = strings.Replace(s, "http://", "", -1) + } + if strings.HasPrefix(s, "https://") { + s = strings.Replace(s, "https://", "", -1) + } + return s +} + +func testCtlV3Set(t *testing.T, cfg *etcdProcessClusterConfig, dialTimeout time.Duration, quorum bool) error { + defer testutil.AfterTest(t) + + epc := setupEtcdctlV3Test(t, cfg, quorum) + defer func() { + if errC := epc.Close(); errC != nil { + t.Fatalf("error closing etcd processes (%v)", errC) + } + }() + + key, value := "foo", "bar" + + if err := etcdctlV3Put(epc, key, value, dialTimeout); err != nil { + return fmt.Errorf("put error (%v)", err) + } + + if err := etcdctlV3Get(epc, key, value, dialTimeout, quorum); err != nil { + return fmt.Errorf("get error (%v)", err) + } + + return nil +} + +func etcdctlV3PrefixArgs(clus *etcdProcessCluster, dialTimeout time.Duration) []string { + endpoints := "" + // TODO: add proxy check as in v2 + if backends := clus.backends(); len(backends) != 0 { + es := []string{} + for _, b := range backends { + es = append(es, stripSchema(b.cfg.acurl.String())) + } + endpoints = strings.Join(es, ",") + } + cmdArgs := []string{"../bin/etcdctlv3", "--endpoints", endpoints, "--dial-timeout", dialTimeout.String()} + if clus.cfg.isClientTLS { + cmdArgs = append(cmdArgs, "--cacert", caPath, "--cert", certPath, "--key", privateKeyPath) + } + return cmdArgs +} + +func etcdctlV3Put(clus *etcdProcessCluster, key, value string, dialTimeout time.Duration) error { + cmdArgs := append(etcdctlV3PrefixArgs(clus, dialTimeout), "put", key, value) + return spawnWithExpectedString(cmdArgs, "OK") +} + +func etcdctlV3Get(clus *etcdProcessCluster, key, value string, dialTimeout time.Duration, quorum bool) error { + // TODO: add serialized option + // if quorum {} + + cmdArgs := append(etcdctlV3PrefixArgs(clus, dialTimeout), "get", key) + + // TODO: match by value. Currently it prints out both key and value in multi-lines + return spawnWithExpectedString(cmdArgs, key) +} + +func mustEtcdctlV3(t *testing.T) { + if !fileutil.Exist("../bin/etcdctlv3") { + t.Fatalf("could not find etcdctlv3 binary") + } +} + +func setupEtcdctlV3Test(t *testing.T, cfg *etcdProcessClusterConfig, quorum bool) *etcdProcessCluster { + // TODO: add serialized option + // if quorum {} + + mustEtcdctlV3(t) + if !quorum { + cfg = configStandalone(*cfg) + } + cfg.isV3 = true + epc, err := newEtcdProcessCluster(cfg) + if err != nil { + t.Fatalf("could not start etcd process cluster (%v)", err) + } + return epc +} diff --git a/etcdctlv3/command/global.go b/etcdctlv3/command/global.go index a0ea35a3470b..cbdb79ebfcba 100644 --- a/etcdctlv3/command/global.go +++ b/etcdctlv3/command/global.go @@ -28,7 +28,8 @@ import ( // GlobalFlags are flags that defined globally // and are inherited to all sub-commands. type GlobalFlags struct { - Endpoints []string + Endpoints []string + DialTimeout time.Duration TLS transport.TLSInfo @@ -44,6 +45,8 @@ func mustClientFromCmd(cmd *cobra.Command) *clientv3.Client { ExitWithError(ExitError, err) } + dialTimeout := dialTimeoutFromCmd(cmd) + var cert, key, cacert string if cert, err = cmd.Flags().GetString("cert"); err != nil { ExitWithError(ExitBadArgs, err) @@ -69,10 +72,10 @@ func mustClientFromCmd(cmd *cobra.Command) *clientv3.Client { ExitWithError(ExitBadFeature, errors.New("unsupported output format")) } - return mustClient(endpoints, cert, key, cacert) + return mustClient(endpoints, dialTimeout, cert, key, cacert) } -func mustClient(endpoints []string, cert, key, cacert string) *clientv3.Client { +func mustClient(endpoints []string, dialTimeout time.Duration, cert, key, cacert string) *clientv3.Client { // set tls if any one tls option set var cfgtls *transport.TLSInfo tls := transport.TLSInfo{} @@ -94,7 +97,7 @@ func mustClient(endpoints []string, cert, key, cacert string) *clientv3.Client { cfg := clientv3.Config{ Endpoints: endpoints, - DialTimeout: 20 * time.Second, + DialTimeout: dialTimeout, } if cfgtls != nil { clientTLS, err := cfgtls.ClientConfig() @@ -122,3 +125,11 @@ func argOrStdin(args []string, stdin io.Reader, i int) (string, error) { } return string(bytes), nil } + +func dialTimeoutFromCmd(cmd *cobra.Command) time.Duration { + dialTimeout, err := cmd.Flags().GetDuration("dial-timeout") + if err != nil { + ExitWithError(ExitError, err) + } + return dialTimeout +} diff --git a/etcdctlv3/command/make_mirror_command.go b/etcdctlv3/command/make_mirror_command.go index fa105b55f8d5..c2e249d9712f 100644 --- a/etcdctlv3/command/make_mirror_command.go +++ b/etcdctlv3/command/make_mirror_command.go @@ -57,7 +57,9 @@ func makeMirrorCommandFunc(cmd *cobra.Command, args []string) { ExitWithError(ExitBadArgs, errors.New("make-mirror takes one destination arguement.")) } - dc := mustClient([]string{args[0]}, mmcert, mmkey, mmcacert) + dialTimeout := dialTimeoutFromCmd(cmd) + + dc := mustClient([]string{args[0]}, dialTimeout, mmcert, mmkey, mmcacert) c := mustClientFromCmd(cmd) err := makeMirror(context.TODO(), c, dc) diff --git a/etcdctlv3/main.go b/etcdctlv3/main.go index c0f4014d2b57..b8897ef76d2c 100644 --- a/etcdctlv3/main.go +++ b/etcdctlv3/main.go @@ -17,6 +17,7 @@ package main import ( "text/tabwriter" + "time" "github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra" "github.com/coreos/etcd/etcdctlv3/command" @@ -46,6 +47,8 @@ func init() { rootCmd.PersistentFlags().StringVarP(&globalFlags.OutputFormat, "write-out", "w", "simple", "set the output format (simple, json, protobuf)") rootCmd.PersistentFlags().BoolVar(&globalFlags.IsHex, "hex", false, "print byte strings as hex encoded strings") + rootCmd.PersistentFlags().DurationVar(&globalFlags.DialTimeout, "dial-timeout", 5*time.Second, "dial timeout for API connections") + rootCmd.PersistentFlags().StringVar(&globalFlags.TLS.CertFile, "cert", "", "identify secure client using this TLS certificate file") rootCmd.PersistentFlags().StringVar(&globalFlags.TLS.KeyFile, "key", "", "identify secure client using this TLS key file") rootCmd.PersistentFlags().StringVar(&globalFlags.TLS.CAFile, "cacert", "", "verify certificates of TLS-enabled secure servers using this CA bundle")