diff --git a/e2e/ctl_v3_migrate_test.go b/e2e/ctl_v3_migrate_test.go index 3c6ac15f5def..9d7a0330643f 100644 --- a/e2e/ctl_v3_migrate_test.go +++ b/e2e/ctl_v3_migrate_test.go @@ -52,7 +52,7 @@ func TestCtlV3Migrate(t *testing.T) { for i := range epc.procs { dataDirs[i] = epc.procs[i].cfg.dataDirPath } - if err := epc.Stop(); err != nil { + if err := epc.StopAll(); err != nil { t.Fatalf("error closing etcd processes (%v)", err) } @@ -74,7 +74,7 @@ func TestCtlV3Migrate(t *testing.T) { for i := range epc.procs { epc.procs[i].cfg.keepDataDir = true } - if err := epc.Restart(); err != nil { + if err := epc.RestartAll(); err != nil { t.Fatal(err) } diff --git a/e2e/etcd_release_migrate_test.go b/e2e/etcd_release_migrate_test.go new file mode 100644 index 000000000000..6fe4c64377b4 --- /dev/null +++ b/e2e/etcd_release_migrate_test.go @@ -0,0 +1,93 @@ +package e2e + +import ( + "fmt" + "os" + "path/filepath" + "testing" + "time" + + "github.com/coreos/etcd/pkg/testutil" +) + +// TestReleaseMigrate ensures that changes to master branch does not affect the +// migration from latest etcd releases. +func TestReleaseMigrate(t *testing.T) { + ver := os.Getenv("MIGRATION_FROM") + if ver == "" { + t.Skipf("skipping release migration tests (version %q)", ver) + } + + const ( + fileNameTmpl = "etcd-{{.Version}}-linux-amd64.tar.gz" + extractedDirTmpl = "etcd-{{.Version}}-linux-amd64" + googleTmpl = "https://storage.googleapis.com/etcd/{{.Version}}/etcd-{{.Version}}-linux-amd64.tar.gz" + githubTmpl = "https://github.com/coreos/etcd/releases/download/{{.Version}}/etcd-{{.Version}}-linux-amd64.tar.gz" + ) + + fileName := insertVersion(fileNameTmpl, ver) + extractedDir := insertVersion(extractedDirTmpl, ver) + + os.RemoveAll(fileName) + os.RemoveAll(extractedDir) + if err := downloadExtract(fileName, insertVersion(googleTmpl, ver), insertVersion(githubTmpl, ver)); err != nil { + t.Fatal(err) + } + defer func() { + os.RemoveAll(fileName) + os.RemoveAll(extractedDir) + }() + + releaseBinaryPath := filepath.Join(extractedDir, "etcd") + + defer testutil.AfterTest(t) + + copiedCfg := configNoTLS + copiedCfg.execPath = releaseBinaryPath + epc, err := newEtcdProcessCluster(&copiedCfg) + if err != nil { + t.Fatalf("could not start etcd process cluster (%v)", err) + } + defer func() { + if errC := epc.Close(); errC != nil { + t.Fatalf("error closing etcd processes (%v)", errC) + } + }() + + keys := make([]string, 3) + vals := make([]string, 3) + for i := range keys { + keys[i] = fmt.Sprintf("foo_%d", i) + vals[i] = fmt.Sprintf("bar_%d", i) + } + + os.Setenv("ETCDCTL_API", "3") + defer os.Unsetenv("ETCDCTL_API") + cx := ctlCtx{ + t: t, + cfg: configNoTLS, + dialTimeout: 7 * time.Second, + quorum: true, + epc: epc, + } + for i := range keys { + if err := ctlV3Put(cx, keys[i], vals[i], ""); err != nil { + cx.t.Fatalf("#%d: ctlV3Put error (%v)", i, err) + } + } + + if err := epc.Stop(0); err != nil { + t.Fatalf("error closing etcd process (%v)", err) + } + epc.UpdateExecPath(0, "../bin/etcd") + + if err := epc.Restart(0); err != nil { + t.Fatalf("error restarting etcd process (%v)", err) + } + + for i := range keys { + if err := ctlV3Get(cx, []string{keys[i]}, []kv{{keys[i], vals[i]}}...); err != nil { + cx.t.Fatalf("#%d: ctlV3Get error (%v)", i, err) + } + } +} diff --git a/e2e/etcd_release_migrate_util.go b/e2e/etcd_release_migrate_util.go new file mode 100644 index 000000000000..d11e3ea8d3fe --- /dev/null +++ b/e2e/etcd_release_migrate_util.go @@ -0,0 +1,131 @@ +// Copyright 2016 The etcd Authors +// +// 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 ( + "archive/tar" + "bytes" + "compress/gzip" + "fmt" + "html/template" + "io" + "net/http" + "os" + "path/filepath" + "runtime" +) + +func downloadExtract(fileName string, urls ...string) error { + f, err := os.Create(fileName) + if err != nil { + return err + } + defer f.Close() + + for _, url := range urls { + var resp *http.Response + resp, err = http.Get(url) + if err != nil { + continue + } + defer resp.Body.Close() + + _, err = io.Copy(f, resp.Body) + if err != nil { + continue + } + break + } + if err != nil { + return err + } + return extractTarGz(fileName) +} + +func insertVersion(tmpl, ver string) string { + buf := new(bytes.Buffer) + if err := template.Must(template.New("tmpl").Parse(tmpl)).Execute(buf, struct{ Version string }{ver}); err != nil { + panic(err) + } + return buf.String() +} + +func extractTarGz(target string) error { + f, err := os.Open(target) + if err != nil { + return err + } + defer f.Close() + + gr, err := gzip.NewReader(f) + if err != nil { + return err + } + defer gr.Close() + + tr := tar.NewReader(gr) + for { + header, err := tr.Next() + if err == io.EOF { + break + } else if err != nil { + return err + } + + if err := untar(tr, header); err != nil { + return err + } + } + return nil +} + +func untar(tr *tar.Reader, header *tar.Header) error { + switch header.Typeflag { + case tar.TypeDir: + return os.MkdirAll(header.Name, 0700) + case tar.TypeReg, tar.TypeRegA: + return writeFile(header.Name, tr, header.FileInfo().Mode()) + case tar.TypeSymlink: + return writeSymlink(header.Name, header.Linkname) + default: + return fmt.Errorf("%s has unknown type %v", header.Name, header.Typeflag) + } +} + +func writeFile(fpath string, rd io.Reader, mode os.FileMode) error { + if err := os.MkdirAll(filepath.Dir(fpath), 0700); err != nil { + return err + } + + f, err := os.Create(fpath) + if err != nil { + return err + } + defer f.Close() + + if err = f.Chmod(mode); err != nil && runtime.GOOS != "windows" { + return err + } + + _, err = io.Copy(f, rd) + return err +} + +func writeSymlink(fpath string, target string) error { + if err := os.MkdirAll(filepath.Dir(fpath), 0700); err != nil { + return err + } + return os.Symlink(target, fpath) +} diff --git a/e2e/etcd_test.go b/e2e/etcd_test.go index 54d0d0dd7270..ab41d5eaa312 100644 --- a/e2e/etcd_test.go +++ b/e2e/etcd_test.go @@ -42,17 +42,20 @@ const ( var ( configNoTLS = etcdProcessClusterConfig{ + execPath: "../bin/etcd", clusterSize: 3, proxySize: 0, initialToken: "new", } configAutoTLS = etcdProcessClusterConfig{ + execPath: "../bin/etcd", clusterSize: 3, isPeerTLS: true, isPeerAutoTLS: true, initialToken: "new", } configTLS = etcdProcessClusterConfig{ + execPath: "../bin/etcd", clusterSize: 3, proxySize: 0, clientTLS: clientTLS, @@ -60,18 +63,21 @@ var ( initialToken: "new", } configClientTLS = etcdProcessClusterConfig{ + execPath: "../bin/etcd", clusterSize: 3, proxySize: 0, clientTLS: clientTLS, initialToken: "new", } configClientBoth = etcdProcessClusterConfig{ + execPath: "../bin/etcd", clusterSize: 1, proxySize: 0, clientTLS: clientTLSAndNonTLS, initialToken: "new", } configClientAutoTLS = etcdProcessClusterConfig{ + execPath: "../bin/etcd", clusterSize: 1, proxySize: 0, isClientAutoTLS: true, @@ -79,17 +85,20 @@ var ( initialToken: "new", } configPeerTLS = etcdProcessClusterConfig{ + execPath: "../bin/etcd", clusterSize: 3, proxySize: 0, isPeerTLS: true, initialToken: "new", } configWithProxy = etcdProcessClusterConfig{ + execPath: "../bin/etcd", clusterSize: 3, proxySize: 1, initialToken: "new", } configWithProxyTLS = etcdProcessClusterConfig{ + execPath: "../bin/etcd", clusterSize: 3, proxySize: 1, clientTLS: clientTLS, @@ -97,6 +106,7 @@ var ( initialToken: "new", } configWithProxyPeerTLS = etcdProcessClusterConfig{ + execPath: "../bin/etcd", clusterSize: 3, proxySize: 1, isPeerTLS: true, @@ -122,7 +132,8 @@ type etcdProcess struct { } type etcdProcessConfig struct { - args []string + execPath string + args []string dataDirPath string keepDataDir bool @@ -137,6 +148,7 @@ type etcdProcessConfig struct { } type etcdProcessClusterConfig struct { + execPath string dataDirPath string keepDataDir bool @@ -175,7 +187,7 @@ func newEtcdProcessCluster(cfg *etcdProcessClusterConfig) (*etcdProcessCluster, } func newEtcdProcess(cfg *etcdProcessConfig) (*etcdProcess, error) { - if !fileutil.Exist("../bin/etcd") { + if !fileutil.Exist(cfg.execPath) { return nil, fmt.Errorf("could not find etcd binary") } @@ -185,7 +197,8 @@ func newEtcdProcess(cfg *etcdProcessConfig) (*etcdProcess, error) { } } - child, err := spawnCmd(append([]string{"../bin/etcd"}, cfg.args...)) + fmt.Println(cfg.execPath, cfg.args) + child, err := spawnCmd(append([]string{cfg.execPath}, cfg.args...)) if err != nil { return nil, err } @@ -256,6 +269,7 @@ func (cfg *etcdProcessClusterConfig) etcdProcessConfigs() []*etcdProcessConfig { args = append(args, cfg.tlsArgs()...) etcdCfgs[i] = &etcdProcessConfig{ + execPath: cfg.execPath, args: args, dataDirPath: dataDirPath, keepDataDir: cfg.keepDataDir, @@ -281,6 +295,7 @@ func (cfg *etcdProcessClusterConfig) etcdProcessConfigs() []*etcdProcessConfig { } args = append(args, cfg.tlsArgs()...) etcdCfgs[cfg.clusterSize+i] = &etcdProcessConfig{ + execPath: cfg.execPath, args: args, dataDirPath: dataDirPath, keepDataDir: cfg.keepDataDir, @@ -351,7 +366,7 @@ func (epc *etcdProcessCluster) Start() (err error) { return nil } -func (epc *etcdProcessCluster) Restart() error { +func (epc *etcdProcessCluster) RestartAll() error { for i := range epc.procs { proc, err := newEtcdProcess(epc.procs[i].cfg) if err != nil { @@ -363,7 +378,31 @@ func (epc *etcdProcessCluster) Restart() error { return epc.Start() } -func (epc *etcdProcessCluster) Stop() (err error) { +func (epc *etcdProcessCluster) UpdateExecPath(i int, execPath string) { + epc.procs[i].cfg.execPath = execPath +} + +func (epc *etcdProcessCluster) Restart(i int) error { + proc, err := newEtcdProcess(epc.procs[i].cfg) + if err != nil { + epc.Close() + return err + } + epc.procs[i] = proc + + readyStr := "enabled capabilities for version" + if epc.procs[i].cfg.isProxy { + readyStr = "httpproxy: endpoints found" + } + + if _, err = epc.procs[i].proc.Expect(readyStr); err != nil { + epc.Close() + return err + } + return nil +} + +func (epc *etcdProcessCluster) StopAll() (err error) { for _, p := range epc.procs { if p == nil { continue @@ -380,8 +419,22 @@ func (epc *etcdProcessCluster) Stop() (err error) { return err } +func (epc *etcdProcessCluster) Stop(i int) error { + p := epc.procs[i] + if p == nil { + return nil + } + + if err := p.proc.Stop(); err != nil { + return err + } + + <-p.donec + return nil +} + func (epc *etcdProcessCluster) Close() error { - err := epc.Stop() + err := epc.StopAll() for _, p := range epc.procs { os.RemoveAll(p.cfg.dataDirPath) }