@@ -10,6 +10,7 @@ import (
10
10
"context"
11
11
"errors"
12
12
"fmt"
13
+ "path/filepath"
13
14
"runtime"
14
15
"strings"
15
16
"testing"
@@ -24,6 +25,7 @@ import (
24
25
"github.com/elastic/elastic-agent/internal/pkg/agent/application/paths"
25
26
"github.com/elastic/elastic-agent/internal/pkg/agent/application/upgrade/details"
26
27
"github.com/elastic/elastic-agent/internal/pkg/agent/install"
28
+ "github.com/elastic/elastic-agent/pkg/control/v2/client"
27
29
"github.com/elastic/elastic-agent/pkg/control/v2/cproto"
28
30
atesting "github.com/elastic/elastic-agent/pkg/testing"
29
31
"github.com/elastic/elastic-agent/pkg/testing/define"
@@ -39,6 +41,15 @@ agent.upgrade.watcher:
39
41
error_check.interval: 5s
40
42
`
41
43
44
+ const fastWatcherCfgWithRollbackWindow = `
45
+ agent.upgrade:
46
+ watcher:
47
+ grace_period: 2m
48
+ error_check.interval: 5s
49
+ rollback:
50
+ window: 10m
51
+ `
52
+
42
53
// TestStandaloneUpgradeRollback tests the scenario where upgrading to a new version
43
54
// of Agent fails due to the new Agent binary reporting an unhealthy status. It checks
44
55
// that the Agent is rolled back to the previous version.
@@ -232,21 +243,26 @@ func TestStandaloneUpgradeRollbackOnRestarts(t *testing.T) {
232
243
require .NoError (t , err )
233
244
234
245
// Create a new package with a different version (IAR-style)
235
- newPackageContainingDir := t .TempDir ()
236
-
237
246
// modify the version with the "+buildYYYYMMDDHHMMSS"
238
247
currentVersion , err := version .ParseVersion (define .Version ())
239
248
require .NoErrorf (t , err , "define.Version() %q is not parsable." , define .Version ())
240
249
241
250
newVersionBuildMetadata := "build" + time .Now ().Format ("20060102150405" )
242
251
parsedNewVersion := version .NewParsedSemVer (currentVersion .Major (), currentVersion .Minor (), currentVersion .Patch (), "" , newVersionBuildMetadata )
243
252
244
- versionForFixture , err := repackageArchive (t .Context (), t , fromFixture , newVersionBuildMetadata , currentVersion , newPackageContainingDir , parsedNewVersion )
253
+ err = fromFixture .EnsurePrepared (t .Context ())
254
+ require .NoErrorf (t , err , "fixture should be prepared" )
255
+
256
+ // retrieve the compressed package file location
257
+ srcPackage , err := fromFixture .SrcPackage (t .Context ())
258
+ require .NoErrorf (t , err , "error retrieving start fixture source package" )
259
+
260
+ versionForFixture , repackagedArchiveFile , err := repackageArchive (t , srcPackage , newVersionBuildMetadata , currentVersion , parsedNewVersion )
245
261
require .NoError (t , err , "error repackaging the archive built from the same commit" )
246
262
247
263
// I wish I could just pass the location of the package on disk to the whole upgrade tests/fixture/fetcher code
248
264
// but I would have to break too much code for that, when in Rome... add more code on top of inflexible code
249
- repackagedLocalFetcher := atesting .LocalFetcher (newPackageContainingDir )
265
+ repackagedLocalFetcher := atesting .LocalFetcher (filepath . Dir ( repackagedArchiveFile ) )
250
266
toFixture , err := atesting .NewFixture (
251
267
t ,
252
268
versionForFixture .String (),
@@ -323,6 +339,79 @@ func TestFleetManagedUpgradeRollbackOnRestarts(t *testing.T) {
323
339
}
324
340
}
325
341
342
+ // TestStandaloneUpgradeManualRollback tests the scenario where, after upgrading to a new version
343
+ // of Agent, a manual rollback is triggered. It checks that the Agent is rolled back to the previous version.
344
+ func TestStandaloneUpgradeManualRollback (t * testing.T ) {
345
+ define .Require (t , define.Requirements {
346
+ Group : integration .Upgrade ,
347
+ Local : false , // requires Agent installation
348
+ Sudo : true , // requires Agent installation
349
+ })
350
+
351
+ type fixturesSetupFunc func (t * testing.T ) (from * atesting.Fixture , to * atesting.Fixture )
352
+ testcases := []struct {
353
+ name string
354
+ fixturesSetup fixturesSetupFunc
355
+ }{
356
+ {
357
+ name : "upgrade to a repackaged agent built from the same commit" ,
358
+ fixturesSetup : func (t * testing.T ) (from * atesting.Fixture , to * atesting.Fixture ) {
359
+ // Upgrade from the current build to the same build as Independent Agent Release.
360
+
361
+ // Start from the build under test.
362
+ fromFixture , err := define .NewFixtureFromLocalBuild (t , define .Version ())
363
+ require .NoError (t , err )
364
+
365
+ // Create a new package with a different version (IAR-style)
366
+ // modify the version with the "+buildYYYYMMDDHHMMSS"
367
+ currentVersion , err := version .ParseVersion (define .Version ())
368
+ require .NoErrorf (t , err , "define.Version() %q is not parsable." , define .Version ())
369
+
370
+ newVersionBuildMetadata := "build" + time .Now ().Format ("20060102150405" )
371
+ parsedNewVersion := version .NewParsedSemVer (currentVersion .Major (), currentVersion .Minor (), currentVersion .Patch (), "" , newVersionBuildMetadata )
372
+
373
+ err = fromFixture .EnsurePrepared (t .Context ())
374
+ require .NoErrorf (t , err , "fixture should be prepared" )
375
+
376
+ // retrieve the compressed package file location
377
+ srcPackage , err := fromFixture .SrcPackage (t .Context ())
378
+ require .NoErrorf (t , err , "error retrieving start fixture source package" )
379
+
380
+ versionForFixture , repackagedArchiveFile , err := repackageArchive (t , srcPackage , newVersionBuildMetadata , currentVersion , parsedNewVersion )
381
+ require .NoError (t , err , "error repackaging the archive built from the same commit" )
382
+
383
+ repackagedLocalFetcher := atesting .LocalFetcher (filepath .Dir (repackagedArchiveFile ))
384
+ toFixture , err := atesting .NewFixture (
385
+ t ,
386
+ versionForFixture .String (),
387
+ atesting .WithFetcher (repackagedLocalFetcher ),
388
+ )
389
+ require .NoError (t , err )
390
+
391
+ return fromFixture , toFixture
392
+ },
393
+ },
394
+ }
395
+
396
+ // set up start ficture with a rollback window of 1h
397
+ rollbackWindowConfig := `
398
+ agent.upgrade.rollback.window: 1h
399
+ `
400
+
401
+ for _ , tc := range testcases {
402
+ t .Run (tc .name , func (t * testing.T ) {
403
+ ctx , cancel := testcontext .WithDeadline (t , t .Context (), time .Now ().Add (10 * time .Minute ))
404
+ defer cancel ()
405
+ from , to := tc .fixturesSetup (t )
406
+
407
+ err := from .Configure (ctx , []byte (rollbackWindowConfig ))
408
+ require .NoError (t , err , "error setting up rollback window" )
409
+ standaloneManualRollbackTest (ctx , t , from , to )
410
+ })
411
+ }
412
+
413
+ }
414
+
326
415
func managedRollbackRestartTest (ctx context.Context , t * testing.T , info * define.Info , from * atesting.Fixture , to * atesting.Fixture ) {
327
416
328
417
startVersionInfo , err := from .ExecVersion (ctx )
@@ -401,11 +490,29 @@ func managedRollbackRestartTest(ctx context.Context, t *testing.T, info *define.
401
490
}
402
491
403
492
func standaloneRollbackRestartTest (ctx context.Context , t * testing.T , startFixture * atesting.Fixture , endFixture * atesting.Fixture ) {
493
+ standaloneRollbackTest (ctx , t , startFixture , endFixture , reallyFastWatcherCfg , details .ReasonWatchFailed ,
494
+ func (t * testing.T , _ client.Client ) {
495
+ restartAgentNTimes (t , 3 , 10 * time .Second )
496
+ })
497
+ }
498
+
499
+ func standaloneManualRollbackTest (ctx context.Context , t * testing.T , startFixture * atesting.Fixture , endFixture * atesting.Fixture ) {
500
+ standaloneRollbackTest (ctx , t , startFixture , endFixture , fastWatcherCfgWithRollbackWindow , details .ReasonManualRollback ,
501
+ func (t * testing.T , client client.Client ) {
502
+ t .Logf ("sending version=%s rollback=%v upgrade to agent" , startFixture .Version (), true )
503
+ retVal , err := client .Upgrade (ctx , startFixture .Version (), true , "" , false , false )
504
+ require .NoError (t , err , "error triggering manual rollback to version %s" , startFixture .Version ())
505
+ t .Logf ("received output %s from upgrade command" , retVal )
506
+ },
507
+ )
508
+ }
509
+
510
+ func standaloneRollbackTest (ctx context.Context , t * testing.T , startFixture * atesting.Fixture , endFixture * atesting.Fixture , customConfig string , rollbackReason string , rollbackTrigger func (t * testing.T , client client.Client )) {
404
511
405
512
startVersionInfo , err := startFixture .ExecVersion (ctx )
406
513
require .NoError (t , err , "failed to get start agent build version info" )
407
514
408
- endVersionInfo , err := startFixture .ExecVersion (ctx )
515
+ endVersionInfo , err := endFixture .ExecVersion (ctx )
409
516
require .NoError (t , err , "failed to get end agent build version info" )
410
517
411
518
t .Logf ("Testing Elastic Agent upgrade from %s to %s..." , startFixture .Version (), endVersionInfo .Binary .String ())
@@ -420,15 +527,19 @@ func standaloneRollbackRestartTest(ctx context.Context, t *testing.T, startFixtu
420
527
err = upgradetest .PerformUpgrade (
421
528
ctx , startFixture , endFixture , t ,
422
529
upgradetest .WithPostUpgradeHook (postUpgradeHook ),
423
- upgradetest .WithCustomWatcherConfig (reallyFastWatcherCfg ),
530
+ upgradetest .WithCustomWatcherConfig (customConfig ),
424
531
upgradetest .WithDisableHashCheck (true ))
425
532
if ! errors .Is (err , ErrPostExit ) {
426
533
require .NoError (t , err )
427
534
}
428
535
429
- // A few seconds after the upgrade, deliberately restart upgraded Agent a
430
- // couple of times to simulate Agent crashing.
431
- restartAgentNTimes (t , 3 , 10 * time .Second )
536
+ elasticAgentClient := startFixture .Client ()
537
+ err = elasticAgentClient .Connect (ctx )
538
+ require .NoError (t , err , "error connecting to installed elastic agent" )
539
+ defer elasticAgentClient .Disconnect ()
540
+
541
+ // A few seconds after the upgrade, trigger a rollback using the passed trigger
542
+ rollbackTrigger (t , elasticAgentClient )
432
543
433
544
// wait for the agent to be healthy and back at the start version
434
545
err = upgradetest .WaitHealthyAndVersion (ctx , startFixture , startVersionInfo .Binary , 2 * time .Minute , 10 * time .Second , t )
@@ -448,17 +559,15 @@ func standaloneRollbackRestartTest(ctx context.Context, t *testing.T, startFixtu
448
559
require .NoError (t , err )
449
560
450
561
if ! startVersion .Less (* version .NewParsedSemVer (8 , 12 , 0 , "" , "" )) {
451
- client := startFixture .Client ()
452
- err = client .Connect (ctx )
453
562
require .NoError (t , err )
454
563
455
- state , err := client .State (ctx )
564
+ state , err := elasticAgentClient .State (ctx )
456
565
require .NoError (t , err )
457
566
458
567
require .NotNil (t , state .UpgradeDetails )
459
568
assert .Equal (t , details .StateRollback , details .State (state .UpgradeDetails .State ))
460
569
if ! startVersion .Less (* upgradetest .Version_9_2_0_SNAPSHOT ) {
461
- assert .Equal (t , details . ReasonWatchFailed , state .UpgradeDetails .Metadata .Reason )
570
+ assert .Equal (t , rollbackReason , state .UpgradeDetails .Metadata .Reason )
462
571
}
463
572
}
464
573
0 commit comments