@@ -16,17 +16,21 @@ package e2e
1616
1717import (
1818 "fmt"
19+ "strings"
1920 "sync"
2021 "testing"
2122 "time"
2223
24+ "github.com/coreos/go-semver/semver"
2325 "github.com/stretchr/testify/assert"
2426 "github.com/stretchr/testify/require"
2527
2628 "go.etcd.io/etcd/api/v3/version"
2729 "go.etcd.io/etcd/client/pkg/v3/fileutil"
2830 "go.etcd.io/etcd/pkg/v3/expect"
31+ "go.etcd.io/etcd/tests/v3/framework/config"
2932 "go.etcd.io/etcd/tests/v3/framework/e2e"
33+ "go.etcd.io/etcd/tests/v3/framework/testutils"
3034)
3135
3236// TestReleaseUpgrade ensures that changes to master branch does not affect
@@ -165,3 +169,113 @@ func TestReleaseUpgradeWithRestart(t *testing.T) {
165169
166170 require .NoError (t , ctlV3Get (cx , []string {kvs [0 ].key }, []kv {kvs [0 ]}... ))
167171}
172+
173+ func TestClusterUpgradeAfterPromotingMembers (t * testing.T ) {
174+ if ! fileutil .Exist (e2e .BinPath .EtcdLastRelease ) {
175+ t .Skipf ("%q does not exist" , e2e .BinPath .EtcdLastRelease )
176+ }
177+
178+ e2e .BeforeTest (t )
179+
180+ currentVersion , err := e2e .GetVersionFromBinary (e2e .BinPath .Etcd )
181+ require .NoErrorf (t , err , "failed to get version from binary" )
182+
183+ lastClusterVersion , err := e2e .GetVersionFromBinary (e2e .BinPath .EtcdLastRelease )
184+ require .NoErrorf (t , err , "failed to get version from last release binary" )
185+
186+ clusterSize := 3
187+
188+ epc := createNewClusterByPromotingMembers (t , e2e .LastVersion , clusterSize )
189+ defer func () {
190+ require .NoError (t , epc .Close ())
191+ }()
192+
193+ err = e2e .DowngradeUpgradeMembers (t , nil , epc , 3 , false , lastClusterVersion , currentVersion )
194+ require .NoError (t , err )
195+
196+ t .Log ("Checking all member status after upgrading" )
197+ mresp , merr := epc .Etcdctl ().MemberList (t .Context (), true )
198+ require .NoError (t , merr )
199+ require .Len (t , mresp .Members , clusterSize )
200+ for _ , m := range mresp .Members {
201+ require .Falsef (t , m .IsLearner , "%s should not be learner" , m .Name )
202+ }
203+ }
204+
205+ func createNewClusterByPromotingMembers (t * testing.T , clusterVersion e2e.ClusterVersion , clusterSize int ) * e2e.EtcdProcessCluster {
206+ require .Truef (t , clusterSize >= 1 , "clusterSize >= 1" )
207+
208+ var version * semver.Version
209+ var err error
210+
211+ switch clusterVersion {
212+ case e2e .CurrentVersion :
213+ version , err = e2e .GetVersionFromBinary (e2e .BinPath .Etcd )
214+ require .NoErrorf (t , err , "failed to get version from binary" )
215+ case e2e .LastVersion :
216+ if ! fileutil .Exist (e2e .BinPath .EtcdLastRelease ) {
217+ t .Skipf ("%q does not exist" , e2e .BinPath .EtcdLastRelease )
218+ }
219+
220+ version , err = e2e .GetVersionFromBinary (e2e .BinPath .EtcdLastRelease )
221+ require .NoErrorf (t , err , "failed to get version from last release binary" )
222+ default :
223+ t .Fatalf ("unexpected cluster version: %v" , clusterVersion )
224+ }
225+
226+ t .Logf ("Creating new etcd cluster - version: %v, clusterSize: %v" , version , clusterSize )
227+
228+ t .Log ("Creating first node" )
229+ epc , err := e2e .NewEtcdProcessCluster (t .Context (), t ,
230+ e2e .WithVersion (clusterVersion ),
231+ e2e .WithClusterSize (1 ),
232+ e2e .WithSnapshotCount (10 ),
233+ )
234+ require .NoErrorf (t , err , "failed to start first etcd process" )
235+ defer func () {
236+ if t .Failed () {
237+ epc .Close ()
238+ }
239+ }()
240+
241+ for i := 1 ; i < clusterSize ; i ++ {
242+ var nodeID uint64
243+ var aerr error
244+
245+ // NOTE: New promoted member needs time to get connected.
246+ t .Logf ("[%d] Adding new node as learner" , i )
247+ testutils .ExecuteWithTimeout (t , 1 * time .Minute , func () {
248+ for {
249+ nodeID , aerr = epc .StartNewProc (t .Context (), nil , t , true )
250+ if aerr != nil {
251+ if strings .Contains (aerr .Error (), "etcdserver: unhealthy cluster" ) {
252+ time .Sleep (1 * time .Second )
253+ continue
254+ }
255+ }
256+ break
257+ }
258+ })
259+ require .NoError (t , aerr )
260+
261+ t .Logf ("[%d] Promoting node(%d)" , i , nodeID )
262+ etcdctl := epc .Procs [0 ].Etcdctl ()
263+ _ , err = etcdctl .MemberPromote (t .Context (), nodeID )
264+ require .NoError (t , err )
265+ }
266+
267+ t .Log ("Checking all member status" )
268+ mresp , merr := epc .Etcdctl ().MemberList (t .Context (), true )
269+ require .NoError (t , merr )
270+ require .Len (t , mresp .Members , clusterSize )
271+ for _ , m := range mresp .Members {
272+ require .Falsef (t , m .IsLearner , "%s should not be learner" , m .Name )
273+ }
274+
275+ t .Logf ("Adding 10 key/value to trigger snapshot" )
276+ for i := 0 ; i < 10 ; i ++ {
277+ err = epc .Etcdctl ().Put (t .Context (), "foo" , "bar" , config.PutOptions {})
278+ require .NoError (t , err )
279+ }
280+ return epc
281+ }
0 commit comments