@@ -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,101 @@ 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+ epc := createNewClusterByPromotingMembers (t , e2e .LastVersion )
187+ defer func () {
188+ require .NoError (t , epc .Close ())
189+ }()
190+
191+ err = e2e .DowngradeUpgradeMembers (t , nil , epc , 3 , false , lastClusterVersion , currentVersion )
192+ require .NoError (t , err )
193+ }
194+
195+ func createNewClusterByPromotingMembers (t * testing.T , clusterVersion e2e.ClusterVersion ) * e2e.EtcdProcessCluster {
196+ var version * semver.Version
197+ var err error
198+
199+ switch clusterVersion {
200+ case e2e .CurrentVersion :
201+ version , err = e2e .GetVersionFromBinary (e2e .BinPath .Etcd )
202+ require .NoErrorf (t , err , "failed to get version from binary" )
203+ case e2e .LastVersion :
204+ if ! fileutil .Exist (e2e .BinPath .EtcdLastRelease ) {
205+ t .Skipf ("%q does not exist" , e2e .BinPath .EtcdLastRelease )
206+ }
207+
208+ version , err = e2e .GetVersionFromBinary (e2e .BinPath .EtcdLastRelease )
209+ require .NoErrorf (t , err , "failed to get version from last release binary" )
210+ default :
211+ t .Fatalf ("unexpected cluster version: %v" , clusterVersion )
212+ }
213+
214+ t .Logf ("Creating new etcd cluster - %v" , version )
215+
216+ t .Log ("Creating first node" )
217+ epc , err := e2e .NewEtcdProcessCluster (t .Context (), t ,
218+ e2e .WithVersion (clusterVersion ),
219+ e2e .WithClusterSize (1 ),
220+ e2e .WithSnapshotCount (10 ),
221+ )
222+ require .NoErrorf (t , err , "failed to start first etcd process" )
223+ defer func () {
224+ if t .Failed () {
225+ epc .Close ()
226+ }
227+ }()
228+
229+ for _ , idx := range []string {"second" , "third" } {
230+ var nodeID uint64
231+ var aerr error
232+
233+ // NOTE: New promoted member needs time to get connected.
234+ t .Logf ("Adding %s node as learner" , idx )
235+ testutils .ExecuteWithTimeout (t , 1 * time .Minute , func () {
236+ for {
237+ nodeID , aerr = epc .StartNewProc (t .Context (), nil , t , true )
238+ if aerr != nil {
239+ if strings .Contains (aerr .Error (), "etcdserver: unhealthy cluster" ) {
240+ time .Sleep (1 * time .Second )
241+ continue
242+ }
243+ }
244+ break
245+ }
246+ })
247+ require .NoError (t , aerr )
248+
249+ t .Logf ("Promoting %s node" , idx )
250+ etcdctl := epc .Procs [0 ].Etcdctl ()
251+ _ , err = etcdctl .MemberPromote (t .Context (), nodeID )
252+ require .NoError (t , err )
253+ }
254+
255+ t .Log ("Checking all member status" )
256+ mresp , merr := epc .Etcdctl ().MemberList (t .Context (), true )
257+ require .NoError (t , merr )
258+ require .Len (t , mresp .Members , 3 )
259+ for _ , m := range mresp .Members {
260+ require .Falsef (t , m .IsLearner , "%s should not be learner" , m .Name )
261+ }
262+
263+ t .Logf ("Adding 10 key/value to trigger snapshot" )
264+ for i := 0 ; i < 10 ; i ++ {
265+ err = epc .Etcdctl ().Put (t .Context (), "foo" , "bar" , config.PutOptions {})
266+ require .NoError (t , err )
267+ }
268+ return epc
269+ }
0 commit comments