@@ -10,6 +10,7 @@ import (
1010 "context"
1111 "errors"
1212 "fmt"
13+ "path/filepath"
1314 "runtime"
1415 "strings"
1516 "testing"
@@ -24,6 +25,7 @@ import (
2425 "github.com/elastic/elastic-agent/internal/pkg/agent/application/paths"
2526 "github.com/elastic/elastic-agent/internal/pkg/agent/application/upgrade/details"
2627 "github.com/elastic/elastic-agent/internal/pkg/agent/install"
28+ "github.com/elastic/elastic-agent/pkg/control/v2/client"
2729 "github.com/elastic/elastic-agent/pkg/control/v2/cproto"
2830 atesting "github.com/elastic/elastic-agent/pkg/testing"
2931 "github.com/elastic/elastic-agent/pkg/testing/define"
@@ -39,6 +41,15 @@ agent.upgrade.watcher:
3941 error_check.interval: 5s
4042`
4143
44+ const fastWatcherCfgWithRollbackWindow = `
45+ agent.upgrade:
46+ watcher:
47+ grace_period: 2m
48+ error_check.interval: 5s
49+ rollback:
50+ window: 10m
51+ `
52+
4253// TestStandaloneUpgradeRollback tests the scenario where upgrading to a new version
4354// of Agent fails due to the new Agent binary reporting an unhealthy status. It checks
4455// that the Agent is rolled back to the previous version.
@@ -232,21 +243,26 @@ func TestStandaloneUpgradeRollbackOnRestarts(t *testing.T) {
232243 require .NoError (t , err )
233244
234245 // Create a new package with a different version (IAR-style)
235- newPackageContainingDir := t .TempDir ()
236-
237246 // modify the version with the "+buildYYYYMMDDHHMMSS"
238247 currentVersion , err := version .ParseVersion (define .Version ())
239248 require .NoErrorf (t , err , "define.Version() %q is not parsable." , define .Version ())
240249
241250 newVersionBuildMetadata := "build" + time .Now ().Format ("20060102150405" )
242251 parsedNewVersion := version .NewParsedSemVer (currentVersion .Major (), currentVersion .Minor (), currentVersion .Patch (), "" , newVersionBuildMetadata )
243252
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 )
245261 require .NoError (t , err , "error repackaging the archive built from the same commit" )
246262
247263 // I wish I could just pass the location of the package on disk to the whole upgrade tests/fixture/fetcher code
248264 // 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 ) )
250266 toFixture , err := atesting .NewFixture (
251267 t ,
252268 versionForFixture .String (),
@@ -323,6 +339,79 @@ func TestFleetManagedUpgradeRollbackOnRestarts(t *testing.T) {
323339 }
324340}
325341
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+
326415func managedRollbackRestartTest (ctx context.Context , t * testing.T , info * define.Info , from * atesting.Fixture , to * atesting.Fixture ) {
327416
328417 startVersionInfo , err := from .ExecVersion (ctx )
@@ -401,11 +490,29 @@ func managedRollbackRestartTest(ctx context.Context, t *testing.T, info *define.
401490}
402491
403492func 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 )) {
404511
405512 startVersionInfo , err := startFixture .ExecVersion (ctx )
406513 require .NoError (t , err , "failed to get start agent build version info" )
407514
408- endVersionInfo , err := startFixture .ExecVersion (ctx )
515+ endVersionInfo , err := endFixture .ExecVersion (ctx )
409516 require .NoError (t , err , "failed to get end agent build version info" )
410517
411518 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
420527 err = upgradetest .PerformUpgrade (
421528 ctx , startFixture , endFixture , t ,
422529 upgradetest .WithPostUpgradeHook (postUpgradeHook ),
423- upgradetest .WithCustomWatcherConfig (reallyFastWatcherCfg ),
530+ upgradetest .WithCustomWatcherConfig (customConfig ),
424531 upgradetest .WithDisableHashCheck (true ))
425532 if ! errors .Is (err , ErrPostExit ) {
426533 require .NoError (t , err )
427534 }
428535
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 )
432543
433544 // wait for the agent to be healthy and back at the start version
434545 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
448559 require .NoError (t , err )
449560
450561 if ! startVersion .Less (* version .NewParsedSemVer (8 , 12 , 0 , "" , "" )) {
451- client := startFixture .Client ()
452- err = client .Connect (ctx )
453562 require .NoError (t , err )
454563
455- state , err := client .State (ctx )
564+ state , err := elasticAgentClient .State (ctx )
456565 require .NoError (t , err )
457566
458567 require .NotNil (t , state .UpgradeDetails )
459568 assert .Equal (t , details .StateRollback , details .State (state .UpgradeDetails .State ))
460569 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 )
462571 }
463572 }
464573
0 commit comments