1// Copyright 2015 The etcd Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package rafthttp
16
17import (
18	"bytes"
19	"encoding/binary"
20	"io"
21	"net/http"
22	"reflect"
23	"testing"
24
25	"go.etcd.io/etcd/raft/raftpb"
26	"go.etcd.io/etcd/version"
27
28	"github.com/coreos/go-semver/semver"
29)
30
31func TestEntry(t *testing.T) {
32	tests := []raftpb.Entry{
33		{},
34		{Term: 1, Index: 1},
35		{Term: 1, Index: 1, Data: []byte("some data")},
36	}
37	for i, tt := range tests {
38		b := &bytes.Buffer{}
39		if err := writeEntryTo(b, &tt); err != nil {
40			t.Errorf("#%d: unexpected write ents error: %v", i, err)
41			continue
42		}
43		var ent raftpb.Entry
44		if err := readEntryFrom(b, &ent); err != nil {
45			t.Errorf("#%d: unexpected read ents error: %v", i, err)
46			continue
47		}
48		if !reflect.DeepEqual(ent, tt) {
49			t.Errorf("#%d: ent = %+v, want %+v", i, ent, tt)
50		}
51	}
52}
53
54func TestCompareMajorMinorVersion(t *testing.T) {
55	tests := []struct {
56		va, vb *semver.Version
57		w      int
58	}{
59		// equal to
60		{
61			semver.Must(semver.NewVersion("2.1.0")),
62			semver.Must(semver.NewVersion("2.1.0")),
63			0,
64		},
65		// smaller than
66		{
67			semver.Must(semver.NewVersion("2.0.0")),
68			semver.Must(semver.NewVersion("2.1.0")),
69			-1,
70		},
71		// bigger than
72		{
73			semver.Must(semver.NewVersion("2.2.0")),
74			semver.Must(semver.NewVersion("2.1.0")),
75			1,
76		},
77		// ignore patch
78		{
79			semver.Must(semver.NewVersion("2.1.1")),
80			semver.Must(semver.NewVersion("2.1.0")),
81			0,
82		},
83		// ignore prerelease
84		{
85			semver.Must(semver.NewVersion("2.1.0-alpha.0")),
86			semver.Must(semver.NewVersion("2.1.0")),
87			0,
88		},
89	}
90	for i, tt := range tests {
91		if g := compareMajorMinorVersion(tt.va, tt.vb); g != tt.w {
92			t.Errorf("#%d: compare = %d, want %d", i, g, tt.w)
93		}
94	}
95}
96
97func TestServerVersion(t *testing.T) {
98	tests := []struct {
99		h  http.Header
100		wv *semver.Version
101	}{
102		// backward compatibility with etcd 2.0
103		{
104			http.Header{},
105			semver.Must(semver.NewVersion("2.0.0")),
106		},
107		{
108			http.Header{"X-Server-Version": []string{"2.1.0"}},
109			semver.Must(semver.NewVersion("2.1.0")),
110		},
111		{
112			http.Header{"X-Server-Version": []string{"2.1.0-alpha.0+git"}},
113			semver.Must(semver.NewVersion("2.1.0-alpha.0+git")),
114		},
115	}
116	for i, tt := range tests {
117		v := serverVersion(tt.h)
118		if v.String() != tt.wv.String() {
119			t.Errorf("#%d: version = %s, want %s", i, v, tt.wv)
120		}
121	}
122}
123
124func TestMinClusterVersion(t *testing.T) {
125	tests := []struct {
126		h  http.Header
127		wv *semver.Version
128	}{
129		// backward compatibility with etcd 2.0
130		{
131			http.Header{},
132			semver.Must(semver.NewVersion("2.0.0")),
133		},
134		{
135			http.Header{"X-Min-Cluster-Version": []string{"2.1.0"}},
136			semver.Must(semver.NewVersion("2.1.0")),
137		},
138		{
139			http.Header{"X-Min-Cluster-Version": []string{"2.1.0-alpha.0+git"}},
140			semver.Must(semver.NewVersion("2.1.0-alpha.0+git")),
141		},
142	}
143	for i, tt := range tests {
144		v := minClusterVersion(tt.h)
145		if v.String() != tt.wv.String() {
146			t.Errorf("#%d: version = %s, want %s", i, v, tt.wv)
147		}
148	}
149}
150
151func TestCheckVersionCompatibility(t *testing.T) {
152	ls := semver.Must(semver.NewVersion(version.Version))
153	lmc := semver.Must(semver.NewVersion(version.MinClusterVersion))
154	tests := []struct {
155		server     *semver.Version
156		minCluster *semver.Version
157		wok        bool
158	}{
159		// the same version as local
160		{
161			ls,
162			lmc,
163			true,
164		},
165		// one version lower
166		{
167			lmc,
168			&semver.Version{},
169			true,
170		},
171		// one version higher
172		{
173			&semver.Version{Major: ls.Major + 1},
174			ls,
175			true,
176		},
177		// too low version
178		{
179			&semver.Version{Major: lmc.Major - 1},
180			&semver.Version{},
181			false,
182		},
183		// too high version
184		{
185			&semver.Version{Major: ls.Major + 1, Minor: 1},
186			&semver.Version{Major: ls.Major + 1},
187			false,
188		},
189	}
190	for i, tt := range tests {
191		_, _, err := checkVersionCompatibility("", tt.server, tt.minCluster)
192		if ok := err == nil; ok != tt.wok {
193			t.Errorf("#%d: ok = %v, want %v", i, ok, tt.wok)
194		}
195	}
196}
197
198func writeEntryTo(w io.Writer, ent *raftpb.Entry) error {
199	size := ent.Size()
200	if err := binary.Write(w, binary.BigEndian, uint64(size)); err != nil {
201		return err
202	}
203	b, err := ent.Marshal()
204	if err != nil {
205		return err
206	}
207	_, err = w.Write(b)
208	return err
209}
210
211func readEntryFrom(r io.Reader, ent *raftpb.Entry) error {
212	var l uint64
213	if err := binary.Read(r, binary.BigEndian, &l); err != nil {
214		return err
215	}
216	buf := make([]byte, int(l))
217	if _, err := io.ReadFull(r, buf); err != nil {
218		return err
219	}
220	return ent.Unmarshal(buf)
221}
222