Esempio n. 1
0
func TestDecideClusterVersion(t *testing.T) {
	tests := []struct {
		vers  map[string]*version.Versions
		wdver *semver.Version
	}{
		{
			map[string]*version.Versions{"a": &version.Versions{Server: "2.0.0"}},
			semver.Must(semver.NewVersion("2.0.0")),
		},
		// unknow
		{
			map[string]*version.Versions{"a": nil},
			nil,
		},
		{
			map[string]*version.Versions{"a": &version.Versions{Server: "2.0.0"}, "b": &version.Versions{Server: "2.1.0"}, "c": &version.Versions{Server: "2.1.0"}},
			semver.Must(semver.NewVersion("2.0.0")),
		},
		{
			map[string]*version.Versions{"a": &version.Versions{Server: "2.1.0"}, "b": &version.Versions{Server: "2.1.0"}, "c": &version.Versions{Server: "2.1.0"}},
			semver.Must(semver.NewVersion("2.1.0")),
		},
		{
			map[string]*version.Versions{"a": nil, "b": &version.Versions{Server: "2.1.0"}, "c": &version.Versions{Server: "2.1.0"}},
			nil,
		},
	}

	for i, tt := range tests {
		dver := decideClusterVersion(tt.vers)
		if !reflect.DeepEqual(dver, tt.wdver) {
			t.Errorf("#%d: ver = %+v, want %+v", i, dver, tt.wdver)
		}
	}
}
Esempio n. 2
0
func TestServerVersion(t *testing.T) {
	tests := []struct {
		h  http.Header
		wv *semver.Version
	}{
		// backward compatibility with etcd 2.0
		{
			http.Header{},
			semver.Must(semver.NewVersion("2.0.0")),
		},
		{
			http.Header{"X-Server-Version": []string{"2.1.0"}},
			semver.Must(semver.NewVersion("2.1.0")),
		},
		{
			http.Header{"X-Server-Version": []string{"2.1.0-alpha.0+git"}},
			semver.Must(semver.NewVersion("2.1.0-alpha.0+git")),
		},
	}
	for i, tt := range tests {
		v := serverVersion(tt.h)
		if v.String() != tt.wv.String() {
			t.Errorf("#%d: version = %s, want %s", i, v, tt.wv)
		}
	}
}
Esempio n. 3
0
// isCompatibleWithCluster return true if the local member has a compitable version with
// the current running cluster.
// The version is considered as compitable when at least one of the other members in the cluster has a
// cluster version in the range of [MinClusterVersion, Version] and no known members has a cluster version
// out of the range.
// We set this rule since when the local member joins, another member might be offline.
func isCompatibleWithCluster(cl Cluster, local types.ID, tr *http.Transport) bool {
	vers := getVersions(cl, local, tr)
	minV := semver.Must(semver.NewVersion(version.MinClusterVersion))
	maxV := semver.Must(semver.NewVersion(version.Version))
	maxV = &semver.Version{
		Major: maxV.Major,
		Minor: maxV.Minor,
	}

	return isCompatibleWithVers(vers, local, minV, maxV)
}
Esempio n. 4
0
// checkVersionCompability checks whether the given version is compatible
// with the local version.
func checkVersionCompability(name string, server, minCluster *semver.Version) error {
	localServer := semver.Must(semver.NewVersion(version.Version))
	localMinCluster := semver.Must(semver.NewVersion(version.MinClusterVersion))
	if compareMajorMinorVersion(server, localMinCluster) == -1 {
		return fmt.Errorf("remote version is too low: remote[%s]=%s, local=%s", name, server, localServer)
	}
	if compareMajorMinorVersion(minCluster, localServer) == 1 {
		return fmt.Errorf("local version is too low: remote[%s]=%s, local=%s", name, server, localServer)
	}
	return nil
}
Esempio n. 5
0
// decideClusterVersion decides the cluster version based on the versions map.
// The returned version is the min server version in the map, or nil if the min
// version in unknown.
func decideClusterVersion(vers map[string]*version.Versions) *semver.Version {
	var cv *semver.Version
	lv := semver.Must(semver.NewVersion(version.Version))

	for mid, ver := range vers {
		if ver == nil {
			return nil
		}
		v, err := semver.NewVersion(ver.Server)
		if err != nil {
			plog.Errorf("cannot understand the version of member %s (%v)", mid, err)
			return nil
		}
		if lv.LessThan(*v) {
			plog.Warningf("the local etcd version %s is not up-to-date", lv.String())
			plog.Warningf("member %s has a higher version %s", mid, ver.Server)
		}
		if cv == nil {
			cv = v
		} else if v.LessThan(*cv) {
			cv = v
		}
	}
	return cv
}
Esempio n. 6
0
func (c *cluster) Version() *semver.Version {
	c.Lock()
	defer c.Unlock()
	if c.version == nil {
		return nil
	}
	return semver.Must(semver.NewVersion(c.version.String()))
}
Esempio n. 7
0
func MustDetectDowngrade(cv *semver.Version) {
	lv := semver.Must(semver.NewVersion(version.Version))
	// only keep major.minor version for comparison against cluster version
	lv = &semver.Version{Major: lv.Major, Minor: lv.Minor}
	if cv != nil && lv.LessThan(*cv) {
		plog.Fatalf("cluster cannot be downgraded (current version: %s is lower than determined cluster version: %s).", version.Version, version.Cluster(cv.String()))
	}
}
Esempio n. 8
0
func TestCheckVersionCompatibility(t *testing.T) {
	ls := semver.Must(semver.NewVersion(version.Version))
	lmc := semver.Must(semver.NewVersion(version.MinClusterVersion))
	tests := []struct {
		server     *semver.Version
		minCluster *semver.Version
		wok        bool
	}{
		// the same version as local
		{
			ls,
			lmc,
			true,
		},
		// one version lower
		{
			lmc,
			&semver.Version{},
			true,
		},
		// one version higher
		{
			&semver.Version{Major: ls.Major + 1},
			ls,
			true,
		},
		// too low version
		{
			&semver.Version{Major: lmc.Major - 1},
			&semver.Version{},
			false,
		},
		// too high version
		{
			&semver.Version{Major: ls.Major + 1, Minor: 1},
			&semver.Version{Major: ls.Major + 1},
			false,
		},
	}
	for i, tt := range tests {
		err := checkVersionCompability("", tt.server, tt.minCluster)
		if ok := err == nil; ok != tt.wok {
			t.Errorf("#%d: ok = %v, want %v", i, ok, tt.wok)
		}
	}
}
Esempio n. 9
0
// serverVersion returns the min cluster version from the given header.
func minClusterVersion(h http.Header) *semver.Version {
	verStr := h.Get("X-Min-Cluster-Version")
	// backward compatibility with etcd 2.0
	if verStr == "" {
		verStr = "2.0.0"
	}
	return semver.Must(semver.NewVersion(verStr))
}
Esempio n. 10
0
func clusterVersionFromStore(st store.Store) *semver.Version {
	e, err := st.Get(path.Join(StoreClusterPrefix, "version"), false, false)
	if err != nil {
		if isKeyNotFound(err) {
			return nil
		}
		plog.Panicf("unexpected error (%v) when getting cluster version from store", err)
	}
	return semver.Must(semver.NewVersion(*e.Node.Value))
}
Esempio n. 11
0
// applyRequest interprets r as a call to store.X and returns a Response interpreted
// from store.Event
func (s *EtcdServer) applyRequest(r pb.Request) Response {
	f := func(ev *store.Event, err error) Response {
		return Response{Event: ev, err: err}
	}
	expr := timeutil.UnixNanoToTime(r.Expiration)
	switch r.Method {
	case "POST":
		return f(s.store.Create(r.Path, r.Dir, r.Val, true, expr))
	case "PUT":
		exists, existsSet := pbutil.GetBool(r.PrevExist)
		switch {
		case existsSet:
			if exists {
				if r.PrevIndex == 0 && r.PrevValue == "" {
					return f(s.store.Update(r.Path, r.Val, expr))
				} else {
					return f(s.store.CompareAndSwap(r.Path, r.PrevValue, r.PrevIndex, r.Val, expr))
				}
			}
			return f(s.store.Create(r.Path, r.Dir, r.Val, false, expr))
		case r.PrevIndex > 0 || r.PrevValue != "":
			return f(s.store.CompareAndSwap(r.Path, r.PrevValue, r.PrevIndex, r.Val, expr))
		default:
			// TODO (yicheng): cluster should be the owner of cluster prefix store
			// we should not modify cluster store here.
			if storeMemberAttributeRegexp.MatchString(r.Path) {
				id := mustParseMemberIDFromKey(path.Dir(r.Path))
				var attr Attributes
				if err := json.Unmarshal([]byte(r.Val), &attr); err != nil {
					plog.Panicf("unmarshal %s should never fail: %v", r.Val, err)
				}
				s.cluster.UpdateAttributes(id, attr)
			}
			if r.Path == path.Join(StoreClusterPrefix, "version") {
				s.cluster.SetVersion(semver.Must(semver.NewVersion(r.Val)))
			}
			return f(s.store.Set(r.Path, r.Dir, r.Val, expr))
		}
	case "DELETE":
		switch {
		case r.PrevIndex > 0 || r.PrevValue != "":
			return f(s.store.CompareAndDelete(r.Path, r.PrevValue, r.PrevIndex))
		default:
			return f(s.store.Delete(r.Path, r.Dir, r.Recursive))
		}
	case "QGET":
		return f(s.store.Get(r.Path, r.Recursive, r.Sorted))
	case "SYNC":
		s.store.DeleteExpiredKeys(time.Unix(0, r.Time))
		return Response{}
	default:
		// This should never be reached, but just in case:
		return Response{err: ErrUnknownMethod}
	}
}
Esempio n. 12
0
func TestCompareMajorMinorVersion(t *testing.T) {
	tests := []struct {
		va, vb *semver.Version
		w      int
	}{
		// equal to
		{
			semver.Must(semver.NewVersion("2.1.0")),
			semver.Must(semver.NewVersion("2.1.0")),
			0,
		},
		// smaller than
		{
			semver.Must(semver.NewVersion("2.0.0")),
			semver.Must(semver.NewVersion("2.1.0")),
			-1,
		},
		// bigger than
		{
			semver.Must(semver.NewVersion("2.2.0")),
			semver.Must(semver.NewVersion("2.1.0")),
			1,
		},
		// ignore patch
		{
			semver.Must(semver.NewVersion("2.1.1")),
			semver.Must(semver.NewVersion("2.1.0")),
			0,
		},
		// ignore prerelease
		{
			semver.Must(semver.NewVersion("2.1.0-alpha.0")),
			semver.Must(semver.NewVersion("2.1.0")),
			0,
		},
	}
	for i, tt := range tests {
		if g := compareMajorMinorVersion(tt.va, tt.vb); g != tt.w {
			t.Errorf("#%d: compare = %d, want %d", i, g, tt.w)
		}
	}
}
Esempio n. 13
0
func TestCheckStreamSupport(t *testing.T) {
	tests := []struct {
		v *semver.Version
		t streamType
		w bool
	}{
		// support
		{
			semver.Must(semver.NewVersion("2.0.0")),
			streamTypeMsgApp,
			true,
		},
		// ignore patch
		{
			semver.Must(semver.NewVersion("2.0.9")),
			streamTypeMsgApp,
			true,
		},
		// ignore prerelease
		{
			semver.Must(semver.NewVersion("2.0.0-alpha")),
			streamTypeMsgApp,
			true,
		},
		// not support
		{
			semver.Must(semver.NewVersion("2.0.0")),
			streamTypeMsgAppV2,
			false,
		},
	}
	for i, tt := range tests {
		if g := checkStreamSupport(tt.v, tt.t); g != tt.w {
			t.Errorf("#%d: check = %v, want %v", i, g, tt.w)
		}
	}
}
Esempio n. 14
0
func (cr *streamReader) dial(t streamType) (io.ReadCloser, error) {
	u := cr.picker.pick()
	cr.mu.Lock()
	term := cr.msgAppTerm
	cr.mu.Unlock()

	uu := u
	uu.Path = path.Join(t.endpoint(), cr.local.String())

	req, err := http.NewRequest("GET", uu.String(), nil)
	if err != nil {
		cr.picker.unreachable(u)
		return nil, fmt.Errorf("failed to make http request to %s (%v)", u, err)
	}
	req.Header.Set("X-Server-From", cr.local.String())
	req.Header.Set("X-Server-Version", version.Version)
	req.Header.Set("X-Min-Cluster-Version", version.MinClusterVersion)
	req.Header.Set("X-Etcd-Cluster-ID", cr.cid.String())
	req.Header.Set("X-Raft-To", cr.remote.String())
	if t == streamTypeMsgApp {
		req.Header.Set("X-Raft-Term", strconv.FormatUint(term, 10))
	}

	cr.mu.Lock()
	cr.req = req
	cr.mu.Unlock()

	resp, err := cr.tr.RoundTrip(req)
	if err != nil {
		cr.picker.unreachable(u)
		return nil, err
	}

	rv := serverVersion(resp.Header)
	lv := semver.Must(semver.NewVersion(version.Version))
	if compareMajorMinorVersion(rv, lv) == -1 && !checkStreamSupport(rv, t) {
		resp.Body.Close()
		return nil, errUnsupportedStreamType
	}

	switch resp.StatusCode {
	case http.StatusGone:
		resp.Body.Close()
		err := fmt.Errorf("the member has been permanently removed from the cluster")
		select {
		case cr.errorc <- err:
		default:
		}
		return nil, err
	case http.StatusOK:
		return resp.Body, nil
	case http.StatusNotFound:
		resp.Body.Close()
		return nil, fmt.Errorf("remote member %s could not recognize local member", cr.remote)
	case http.StatusPreconditionFailed:
		b, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			cr.picker.unreachable(u)
			return nil, err
		}
		resp.Body.Close()

		switch strings.TrimSuffix(string(b), "\n") {
		case errIncompatibleVersion.Error():
			plog.Errorf("request sent was ignored by peer %s (server version incompatible)", cr.remote)
			return nil, errIncompatibleVersion
		case errClusterIDMismatch.Error():
			plog.Errorf("request sent was ignored (cluster ID mismatch: remote[%s]=%s, local=%s)",
				cr.remote, resp.Header.Get("X-Etcd-Cluster-ID"), cr.cid)
			return nil, errClusterIDMismatch
		default:
			return nil, fmt.Errorf("unhandled error %q when precondition failed", string(b))
		}
	default:
		resp.Body.Close()
		return nil, fmt.Errorf("unhandled http status %d", resp.StatusCode)
	}
}
Esempio n. 15
0
func TestIsCompatibleWithVers(t *testing.T) {
	tests := []struct {
		vers       map[string]*version.Versions
		local      types.ID
		minV, maxV *semver.Version
		wok        bool
	}{
		// too low
		{
			map[string]*version.Versions{
				"a": &version.Versions{Server: "2.0.0", Cluster: "not_decided"},
				"b": &version.Versions{Server: "2.1.0", Cluster: "2.1.0"},
				"c": &version.Versions{Server: "2.1.0", Cluster: "2.1.0"},
			},
			0xa,
			semver.Must(semver.NewVersion("2.0.0")), semver.Must(semver.NewVersion("2.0.0")),
			false,
		},
		{
			map[string]*version.Versions{
				"a": &version.Versions{Server: "2.1.0", Cluster: "not_decided"},
				"b": &version.Versions{Server: "2.1.0", Cluster: "2.1.0"},
				"c": &version.Versions{Server: "2.1.0", Cluster: "2.1.0"},
			},
			0xa,
			semver.Must(semver.NewVersion("2.0.0")), semver.Must(semver.NewVersion("2.1.0")),
			true,
		},
		// too high
		{
			map[string]*version.Versions{
				"a": &version.Versions{Server: "2.2.0", Cluster: "not_decided"},
				"b": &version.Versions{Server: "2.0.0", Cluster: "2.0.0"},
				"c": &version.Versions{Server: "2.0.0", Cluster: "2.0.0"},
			},
			0xa,
			semver.Must(semver.NewVersion("2.1.0")), semver.Must(semver.NewVersion("2.2.0")),
			false,
		},
		// cannot get b's version, expect ok
		{
			map[string]*version.Versions{
				"a": &version.Versions{Server: "2.1.0", Cluster: "not_decided"},
				"b": nil,
				"c": &version.Versions{Server: "2.1.0", Cluster: "2.1.0"},
			},
			0xa,
			semver.Must(semver.NewVersion("2.0.0")), semver.Must(semver.NewVersion("2.1.0")),
			true,
		},
		// cannot get b and c's version, expect not ok
		{
			map[string]*version.Versions{
				"a": &version.Versions{Server: "2.1.0", Cluster: "not_decided"},
				"b": nil,
				"c": nil,
			},
			0xa,
			semver.Must(semver.NewVersion("2.0.0")), semver.Must(semver.NewVersion("2.1.0")),
			false,
		},
	}

	for i, tt := range tests {
		ok := isCompatibleWithVers(tt.vers, tt.local, tt.minV, tt.maxV)
		if ok != tt.wok {
			t.Errorf("#%d: ok = %+v, want %+v", i, ok, tt.wok)
		}
	}
}