Skip to content

Commit 8318762

Browse files
authored
Merge pull request juju#11384 from babbageclunk/2.8-downgrade-agent
juju#11384 ## Description of change Previously the upgrader rejected a version change if the target minor or major version was less than the current version. This check has been removed to support rolling back the Juju version by restoring a backup taken before an upgrade. The same change has been made in the caasupgrader worker - the upgrade process is different in k8s land but this check was essentially the same. With these changes restoring a previous version of a controller automatically triggers the agents to download the required version of the agent, symlink it and update the upgradedToVersion in the agent.conf. There was a mention of a bug in upgrading from 1.16 to 1.18: https://bugs.launchpad.net/juju-core/+bug/1299802 My understanding is that this behaviour isn't a concern any more because the unit agent no longer considers the machine agent's version; everything is driven by the model agent version instead. ## QA steps * Bootstrap with Juju 2.7. * Deploy some units. * Take a backup of the controller. ```sh juju create-backup -m controller ``` * Upgrade to 2.8 with this change, and upgrade the model as well. ```sh juju upgrade-controller --build-agent juju upgrade-model ``` * Run juju-restore on the controller machine. At the moment this requires a version of juju-restore hacked to remove the check requiring the same version between controller and backup. ```sh juju scp -m controller $(which juju-restore) 0: juju scp -m controller juju-backup-whenever-it-is.tar.gz juju ssh -m controller 0 # On machine sudo grep statepassword /var/lib/juju/agents/machine-0/agent.conf ./juju-restore --username machine-0 --password thepassword --verbose juju-backup-whenever.tar.gz ``` * After restore the controller agent will start complaining about downgrading state from 2.8 to 2.7.4 - this is unnecessary because the db is already at 2.7.4, it's just because the agent.conf is wrong. Stop the agent and edit agent.conf to set upgradedToVersion back to 2.7.4: ```sh $ sudo systemctl stop jujud-machine-0.service $ sudo vi /var/lib/juju/agents/machine-0/agent.conf $ sudo systemctl start jujud-machine-0.service ``` (Once the version downgrade changes are made to juju-restore this will happen automatically.) * The unit and machine agents in the model will update their symlinks to point to the 2.7.4 binary and their agent.confs with the correct upgradedToVersion. * Their log files will show that the right version of the binary is running. ## Documentation changes No change to documentation. ## Bug reference None
2 parents 432f10f + 958c0fd commit 8318762

File tree

8 files changed

+108
-53
lines changed

8 files changed

+108
-53
lines changed

cmd/jujud/agent/bootstrap_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -410,9 +410,9 @@ func (s *BootstrapSuite) TestInitializeEnvironmentInvalidOplogSize(c *gc.C) {
410410
}
411411

412412
func (s *BootstrapSuite) TestInitializeEnvironmentToolsNotFound(c *gc.C) {
413-
// bootstrap with 1.99.1 but there will be no tools so version will be reset.
413+
// bootstrap with 2.99.1 but there will be no tools so version will be reset.
414414
cfg, err := s.bootstrapParams.ControllerModelConfig.Apply(map[string]interface{}{
415-
"agent-version": "1.99.1",
415+
"agent-version": "2.99.1",
416416
})
417417
c.Assert(err, jc.ErrorIsNil)
418418
s.bootstrapParams.ControllerModelConfig = cfg
@@ -433,7 +433,7 @@ func (s *BootstrapSuite) TestInitializeEnvironmentToolsNotFound(c *gc.C) {
433433
c.Assert(err, jc.ErrorIsNil)
434434
vers, ok := cfg.AgentVersion()
435435
c.Assert(ok, jc.IsTrue)
436-
c.Assert(vers.String(), gc.Equals, "1.99.0")
436+
c.Assert(vers.String(), gc.Equals, "2.99.0")
437437
}
438438

439439
func (s *BootstrapSuite) TestSetConstraints(c *gc.C) {

environs/bootstrap/bootstrap_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -667,7 +667,7 @@ func (s *bootstrapSuite) TestBootstrapBuildAgent(c *gc.C) {
667667
BuildAgentTarball: func(build bool, ver *version.Number, _ string) (*sync.BuiltAgent, error) {
668668
c.Logf("BuildAgentTarball version %s", ver)
669669
c.Assert(build, jc.IsTrue)
670-
c.Assert(ver.String(), gc.Equals, "1.99.0.1")
670+
c.Assert(ver.String(), gc.Equals, "2.99.0.1")
671671
localVer := *ver
672672
return &sync.BuiltAgent{
673673
Dir: c.MkDir(),
@@ -687,7 +687,7 @@ func (s *bootstrapSuite) TestBootstrapBuildAgent(c *gc.C) {
687687
cfg := env.instanceConfig.Bootstrap.ControllerModelConfig
688688
agentVersion, valid := cfg.AgentVersion()
689689
c.Check(valid, jc.IsTrue)
690-
c.Check(agentVersion.String(), gc.Equals, "1.99.0")
690+
c.Check(agentVersion.String(), gc.Equals, "2.99.0")
691691
}
692692

693693
func (s *bootstrapSuite) assertBootstrapPackagedToolsAvailable(c *gc.C, clientArch string) {

featuretests/upgrade_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ func (s *upgradeSuite) SetUpTest(c *gc.C) {
7676
Arch: arch.HostArch(),
7777
Series: series.MustHostSeries(),
7878
}
79-
s.oldVersion.Major = 1
80-
s.oldVersion.Minor = 16
79+
s.oldVersion.Major = 2
80+
s.oldVersion.Minor = 1
8181

8282
// Don't wait so long in tests.
8383
s.PatchValue(&upgradesteps.UpgradeStartTimeoutMaster, time.Millisecond*50)

testing/environ.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ var (
3737
)
3838

3939
// FakeVersionNumber is a valid version number that can be used in testing.
40-
var FakeVersionNumber = version.MustParse("1.99.0")
40+
var FakeVersionNumber = version.MustParse("2.99.0")
4141

4242
// ModelTag is a defined known valid UUID that can be used in testing.
4343
var ModelTag = names.NewModelTag("deadbeef-0bad-400d-8000-4b1d0d06f00d")

worker/caasupgrader/upgrader.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,15 +155,18 @@ func (u *Upgrader) loop() error {
155155
} else if !upgrader.AllowedTargetVersion(
156156
u.config.OrigAgentVersion,
157157
jujuversion.Current,
158-
!u.config.UpgradeStepsWaiter.IsUnlocked(),
159158
wantVersion,
160159
) {
161160
logger.Warningf("desired agent binary version: %s is older than current %s, refusing to downgrade",
162161
wantVersion, jujuversion.Current)
163162
u.config.InitialUpgradeCheckComplete.Unlock()
164163
continue
165164
}
166-
logger.Debugf("upgrade requested for %v from %v to %v", u.tag, jujuversion.Current, wantVersion)
165+
direction := "upgrade"
166+
if wantVersion.Compare(jujuversion.Current) == -1 {
167+
direction = "downgrade"
168+
}
169+
logger.Debugf("%s requested for %v from %v to %v", direction, u.tag, jujuversion.Current, wantVersion)
167170
err = u.operatorUpgrader.Upgrade(u.tag.String(), wantVersion)
168171
if err != nil {
169172
return errors.Annotatef(err, "requesting upgrade for %f from %v to %v", u.tag, jujuversion.Current, wantVersion)

worker/caasupgrader/upgrader_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,23 @@ func (s *UpgraderSuite) TestUpgraderDowngradePatch(c *gc.C) {
121121
s.operatorUpgrader.CheckCall(c, 0, "Upgrade", "machine-0", s.upgraderClient.desired)
122122
}
123123

124+
func (s *UpgraderSuite) TestUpgraderDowngradeMinor(c *gc.C) {
125+
// We'll allow this for the case of restoring a backup from a
126+
// previous juju version.
127+
vers := version.MustParse("6.6.7")
128+
s.PatchValue(&jujuversion.Current, vers)
129+
s.upgraderClient.desired = version.MustParse("6.5.10")
130+
131+
u := s.makeUpgrader(c, names.NewMachineTag("0"))
132+
workertest.CleanKill(c, u)
133+
134+
s.expectInitialUpgradeCheckNotDone(c)
135+
c.Assert(s.upgraderClient.actual.Number, gc.DeepEquals, vers)
136+
s.upgraderClient.CheckCallNames(c, "SetVersion", "DesiredVersion")
137+
s.operatorUpgrader.CheckCallNames(c, "Upgrade")
138+
s.operatorUpgrader.CheckCall(c, 0, "Upgrade", "machine-0", s.upgraderClient.desired)
139+
}
140+
124141
func (s *UpgraderSuite) expectInitialUpgradeCheckDone(c *gc.C) {
125142
c.Assert(s.initialCheckComplete.IsUnlocked(), jc.IsTrue)
126143
}

worker/upgrader/upgrader.go

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -115,16 +115,10 @@ func (u *Upgrader) Stop() error {
115115
func AllowedTargetVersion(
116116
origAgentVersion version.Number,
117117
curVersion version.Number,
118-
upgradeStepsRunning bool,
119118
targetVersion version.Number,
120119
) bool {
121-
if upgradeStepsRunning && targetVersion == origAgentVersion {
122-
return true
123-
}
124-
if targetVersion.Major < curVersion.Major {
125-
return false
126-
}
127-
if targetVersion.Major == curVersion.Major && targetVersion.Minor < curVersion.Minor {
120+
// Don't allow downgrading from higher versions to version 1.x
121+
if curVersion.Major >= 2 && targetVersion.Major == 1 {
128122
return false
129123
}
130124
return true
@@ -208,20 +202,20 @@ func (u *Upgrader) loop() error {
208202
} else if !AllowedTargetVersion(
209203
u.config.OrigAgentVersion,
210204
haveVersion,
211-
!u.config.UpgradeStepsWaiter.IsUnlocked(),
212205
wantVersion,
213206
) {
214-
// See also bug #1299802 where when upgrading from
215-
// 1.16 to 1.18 there is a race condition that can
216-
// cause the unit agent to upgrade, and then want to
217-
// downgrade when its associate machine agent has not
218-
// finished upgrading.
219-
logger.Infof("desired agent binary version: %s is older than current %s, refusing to downgrade",
220-
wantVersion, haveVersion)
207+
// Don't allow downgrading to v1.x - we don't support
208+
// restoring from a 1.x backup.
209+
logger.Infof("desired agent binary version: %s is older than 2.0.0, refusing to downgrade",
210+
wantVersion)
221211
u.config.InitialUpgradeCheckComplete.Unlock()
222212
continue
223213
}
224-
logger.Infof("upgrade requested from %v to %v", haveVersion, wantVersion)
214+
direction := "upgrade"
215+
if wantVersion.Compare(haveVersion) == -1 {
216+
direction = "downgrade"
217+
}
218+
logger.Infof("%s requested from %v to %v", direction, haveVersion, wantVersion)
225219

226220
// Check if tools have already been downloaded.
227221
wantVersionBinary := toBinaryVersion(wantVersion)

worker/upgrader/upgrader_test.go

Lines changed: 67 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,9 @@ func (s *UpgraderSuite) TestUsesAlreadyDownloadedToolsIfAvailable(c *gc.C) {
296296
})
297297
}
298298

299-
func (s *UpgraderSuite) TestUpgraderRefusesToDowngradeMinorVersions(c *gc.C) {
299+
func (s *UpgraderSuite) TestUpgraderAllowsDowngradingMinorVersions(c *gc.C) {
300+
// We allow this scenario to allow reverting upgrades by restoring
301+
// a backup from the previous version.
300302
stor := s.DefaultToolsStorage
301303
origTools := envtesting.PrimeTools(c, stor, s.DataDir(), s.Environ.Config().AgentStream(), version.MustParseBinary("5.4.3-precise-amd64"))
302304
s.patchVersion(origTools.Version)
@@ -305,16 +307,45 @@ func (s *UpgraderSuite) TestUpgraderRefusesToDowngradeMinorVersions(c *gc.C) {
305307
err := statetesting.SetAgentVersion(s.State, downgradeTools.Version.Number)
306308
c.Assert(err, jc.ErrorIsNil)
307309

310+
u := s.makeUpgrader(c)
311+
err = u.Stop()
312+
s.expectInitialUpgradeCheckNotDone(c)
313+
314+
envtesting.CheckUpgraderReadyError(c, err, &upgrader.UpgradeReadyError{
315+
AgentName: s.machine.Tag().String(),
316+
OldTools: origTools.Version,
317+
NewTools: downgradeTools.Version,
318+
DataDir: s.DataDir(),
319+
})
320+
foundTools, err := agenttools.ReadTools(s.DataDir(), downgradeTools.Version)
321+
c.Assert(err, jc.ErrorIsNil)
322+
downgradeTools.URL = fmt.Sprintf("https://%s/model/%s/tools/5.3.3-precise-amd64",
323+
s.APIState.Addr(), coretesting.ModelTag.Id())
324+
envtesting.CheckTools(c, foundTools, downgradeTools)
325+
}
326+
327+
func (s *UpgraderSuite) TestUpgraderForbidsDowngradingToMajorVersion1(c *gc.C) {
328+
// We allow this scenario to allow reverting upgrades by restoring
329+
// a backup from the previous version.
330+
stor := s.DefaultToolsStorage
331+
origTools := envtesting.PrimeTools(c, stor, s.DataDir(), s.Environ.Config().AgentStream(), version.MustParseBinary("2.4.3-precise-amd64"))
332+
s.patchVersion(origTools.Version)
333+
downgradeTools := envtesting.AssertUploadFakeToolsVersions(
334+
c, stor, s.Environ.Config().AgentStream(), s.Environ.Config().AgentStream(), version.MustParseBinary("1.25.3-precise-amd64"))[0]
335+
err := statetesting.SetAgentVersion(s.State, downgradeTools.Version.Number)
336+
c.Assert(err, jc.ErrorIsNil)
337+
308338
u := s.makeUpgrader(c)
309339
err = u.Stop()
310340
s.expectInitialUpgradeCheckDone(c)
311-
// If the upgrade would have triggered, we would have gotten an
312-
// UpgradeReadyError, since it was skipped, we get no error
313-
c.Check(err, jc.ErrorIsNil)
341+
342+
// If the upgrade had been allowed we would get an
343+
// UpgradeReadyError.
344+
c.Assert(err, jc.ErrorIsNil)
314345
_, err = agenttools.ReadTools(s.DataDir(), downgradeTools.Version)
315-
// TODO: ReadTools *should* be returning some form of errors.NotFound,
316-
// however, it just passes back a fmt.Errorf so we live with it
317-
// c.Assert(err, jc.Satisfies, errors.IsNotFound)
346+
// TODO: ReadTools *should* be returning some form of
347+
// errors.NotFound, however, it just passes back a fmt.Errorf so
348+
// we live with it c.Assert(err, jc.Satisfies, errors.IsNotFound)
318349
c.Check(err, gc.ErrorMatches, "cannot read agent metadata in directory.*"+utils.NoSuchFileErrRegexp)
319350
}
320351

@@ -372,7 +403,9 @@ func (s *UpgraderSuite) TestUpgraderAllowsDowngradeToOrigVersionIfUpgradeInProgr
372403
envtesting.CheckTools(c, foundTools, downgradeTools)
373404
}
374405

375-
func (s *UpgraderSuite) TestUpgraderRefusesDowngradeToOrigVersionIfUpgradeNotInProgress(c *gc.C) {
406+
func (s *UpgraderSuite) TestUpgraderAllowsDowngradeToOrigVersionIfUpgradeNotInProgress(c *gc.C) {
407+
// We now allow this to support restoring a backup from a previous
408+
// version.
376409
downgradeVersion := version.MustParseBinary("5.3.0-precise-amd64")
377410
s.confVersion = downgradeVersion.Number
378411
s.upgradeStepsComplete.Unlock()
@@ -382,16 +415,28 @@ func (s *UpgraderSuite) TestUpgraderRefusesDowngradeToOrigVersionIfUpgradeNotInP
382415
s.patchVersion(origTools.Version)
383416
envtesting.AssertUploadFakeToolsVersions(
384417
c, stor, s.Environ.Config().AgentStream(), s.Environ.Config().AgentStream(), downgradeVersion)
418+
419+
prevTools := envtesting.AssertUploadFakeToolsVersions(
420+
c, stor, s.Environ.Config().AgentStream(), s.Environ.Config().AgentStream(), downgradeVersion)[0]
421+
385422
err := statetesting.SetAgentVersion(s.State, downgradeVersion.Number)
386423
c.Assert(err, jc.ErrorIsNil)
387424

388425
u := s.makeUpgrader(c)
389426
err = u.Stop()
390-
s.expectInitialUpgradeCheckDone(c)
427+
s.expectInitialUpgradeCheckNotDone(c)
391428

392-
// If the upgrade would have triggered, we would have gotten an
393-
// UpgradeReadyError, since it was skipped, we get no error
394-
c.Check(err, jc.ErrorIsNil)
429+
envtesting.CheckUpgraderReadyError(c, err, &upgrader.UpgradeReadyError{
430+
AgentName: s.machine.Tag().String(),
431+
OldTools: origTools.Version,
432+
NewTools: prevTools.Version,
433+
DataDir: s.DataDir(),
434+
})
435+
foundTools, err := agenttools.ReadTools(s.DataDir(), prevTools.Version)
436+
c.Assert(err, jc.ErrorIsNil)
437+
prevTools.URL = fmt.Sprintf("https://%s/model/%s/tools/5.3.0-precise-amd64",
438+
s.APIState.Addr(), coretesting.ModelTag.Id())
439+
envtesting.CheckTools(c, foundTools, prevTools)
395440
}
396441

397442
func (s *UpgraderSuite) TestChecksSpaceBeforeDownloading(c *gc.C) {
@@ -444,30 +489,26 @@ func (s *UpgraderSuite) TestChecksSpaceBeforeDownloading(c *gc.C) {
444489
}
445490

446491
type allowedTest struct {
447-
original string
448-
current string
449-
target string
450-
upgradeRunning bool
451-
allowed bool
492+
original string
493+
current string
494+
target string
495+
allowed bool
452496
}
453497

454498
func (s *AllowedTargetVersionSuite) TestAllowedTargetVersionSuite(c *gc.C) {
455499
cases := []allowedTest{
456-
{original: "1.2.3", current: "1.2.3", upgradeRunning: false, target: "1.3.3", allowed: true},
457-
{original: "1.2.3", current: "1.2.3", upgradeRunning: false, target: "1.2.3", allowed: true},
458-
{original: "1.2.3", current: "1.2.3", upgradeRunning: false, target: "2.2.3", allowed: true},
459-
{original: "1.2.3", current: "1.2.3", upgradeRunning: false, target: "1.1.3", allowed: false},
460-
{original: "1.2.3", current: "1.2.3", upgradeRunning: false, target: "1.2.2", allowed: true}, // downgrade between builds
461-
{original: "1.2.3", current: "1.2.3", upgradeRunning: false, target: "0.2.3", allowed: false},
462-
{original: "0.2.3", current: "1.2.3", upgradeRunning: false, target: "0.2.3", allowed: false},
463-
{original: "0.2.3", current: "1.2.3", upgradeRunning: true, target: "0.2.3", allowed: true}, // downgrade during upgrade
500+
{original: "2.7.4", current: "2.7.4", target: "2.8.0", allowed: true}, // normal upgrade
501+
{original: "2.8.0", current: "2.8.0", target: "2.7.4", allowed: true}, // downgrade caused by restore after upgrade
502+
{original: "2.8.0", current: "2.8.0", target: "1.2.3", allowed: false}, // can't downgrade to v1.x
503+
{original: "2.7.4", current: "2.7.4", target: "2.7.5", allowed: true}, // point release
504+
{original: "2.7.4", current: "2.8.0", target: "2.7.4", allowed: true}, // downgrade after upgrade but before config file updated
464505
}
465506
for i, test := range cases {
466507
c.Logf("test case %d, %#v", i, test)
467508
original := version.MustParse(test.original)
468509
current := version.MustParse(test.current)
469510
target := version.MustParse(test.target)
470-
result := upgrader.AllowedTargetVersion(original, current, test.upgradeRunning, target)
511+
result := upgrader.AllowedTargetVersion(original, current, target)
471512
c.Check(result, gc.Equals, test.allowed)
472513
}
473514
}

0 commit comments

Comments
 (0)