Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log warning on same version upgrade to prevent failed status #6273

Merged
merged 18 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Kind can be one of:
# - breaking-change: a change to previously-documented behavior
# - deprecation: functionality that is being removed in a later release
# - bug-fix: fixes a problem in a previous version
# - enhancement: extends functionality but does not break or fix existing behavior
# - feature: new functionality
# - known-issue: problems that we are aware of in a given version
# - security: impacts on the security of a product or a user’s deployment.
# - upgrade: important information for someone upgrading from a prior version
# - other: does not fit into any of the other categories
kind: bug-fix

# Change summary; a 80ish characters long description of the change.
summary: Log warning on same version upgrade attempts

# Long description; in case the summary is not enough to describe the change
# this field accommodate a description without length limits.
# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment.
description: |
Log a warning instead of reporting an error whan a same-version upgrade is
attempted. This prevents the agent from reporting a "failed" status.

# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc.
component: elastic-agent

# PR URL; optional; the PR number that added the changeset.
# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added.
# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number.
# Please provide it if you are adding a fragment for a different PR.
#pr: https://github.com/owner/repo/1234

# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of).
# If not present is automatically filled by the tooling with the issue linked to the PR number.
issue: https://github.com/elastic/elastic-agent/issues/6186
6 changes: 6 additions & 0 deletions internal/pkg/agent/application/coordinator/coordinator.go
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,12 @@ func (c *Coordinator) Upgrade(ctx context.Context, version string, sourceURI str
cb, err := c.upgradeMgr.Upgrade(ctx, version, sourceURI, action, det, skipVerifyOverride, skipDefaultPgp, pgpBytes...)
if err != nil {
c.ClearOverrideState()
if errors.Is(err, upgrade.ErrUpgradeSameVersion) {
c.logger.Info("Same ver upgrade detected.")
Copy link
Contributor

@michalpristas michalpristas Jan 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont't we end up with 2 log lines with different log levels for same thing one Warning coming from internal/pkg/agent/application/upgrade/upgrade.go:L175 or line 227 the other one here

// Set upgrade state to completed so update no longer shows in-progress.
det.SetState(details.StateCompleted)
return nil
}
det.Fail(err)
return err
}
Expand Down
35 changes: 28 additions & 7 deletions internal/pkg/agent/application/upgrade/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ var agentArtifact = artifact.Artifact{
}

var ErrWatcherNotStarted = errors.New("watcher did not start in time")
var ErrUpgradeSameVersion = errors.New("upgrade did not occur because it is the same version")

// Upgrader performs an upgrade
type Upgrader struct {
Expand Down Expand Up @@ -162,6 +163,17 @@ func (av agentVersion) String() string {
func (u *Upgrader) Upgrade(ctx context.Context, version string, sourceURI string, action *fleetapi.ActionUpgrade, det *details.Details, skipVerifyOverride bool, skipDefaultPgp bool, pgpBytes ...string) (_ reexec.ShutdownCallbackFn, err error) {
u.log.Infow("Upgrading agent", "version", version, "source_uri", sourceURI)

currentVersion := agentVersion{
version: release.Version(),
snapshot: release.Snapshot(),
hash: release.Commit(),
}

if isSameReleaseVersion(currentVersion, version) {
u.log.Warnf("Upgrade action skipped because agent is already at version %s", currentVersion)
return nil, ErrUpgradeSameVersion
}

// Inform the Upgrade Marker Watcher that we've started upgrading. Note that this
// is only possible to do in-memory since, today, the process that's initiating
// the upgrade is the same as the Agent process in which the Upgrade Marker Watcher is
Expand Down Expand Up @@ -206,15 +218,10 @@ func (u *Upgrader) Upgrade(ctx context.Context, version string, sourceURI string
return nil, fmt.Errorf("reading metadata for elastic agent version %s package %q: %w", version, archivePath, err)
}

currentVersion := agentVersion{
version: release.Version(),
snapshot: release.Snapshot(),
hash: release.Commit(),
}

same, newVersion := isSameVersion(u.log, currentVersion, metadata, version)
if same {
return nil, fmt.Errorf("agent version is already %s", currentVersion)
u.log.Warnf("Upgrade action skipped because agent is already at version %s", currentVersion)
return nil, ErrUpgradeSameVersion
michel-laterman marked this conversation as resolved.
Show resolved Hide resolved
}

u.log.Infow("Unpacking agent package", "version", newVersion)
Expand Down Expand Up @@ -626,3 +633,17 @@ func IsInProgress(c client.Client, watcherPIDsFetcher func() ([]int, error)) (bo

return state.State == cproto.State_UPGRADING, nil
}

// isSameReleaseVersion will if upgradeVersion and currentVersion are equal using only release numbers.
// They are not equal if either are a SNAPSHOT, or if the semver numbers (including prerelease and build identifiers) differ.
michel-laterman marked this conversation as resolved.
Show resolved Hide resolved
func isSameReleaseVersion(current agentVersion, upgradeVersion string) bool {
if current.snapshot {
return false
}
target, _ := agtversion.ParseVersion(upgradeVersion)
michel-laterman marked this conversation as resolved.
Show resolved Hide resolved
targetVersion, targetSnapshot := target.ExtractSnapshotFromVersionString()
if targetSnapshot {
return false
}
return current.version == targetVersion
}
74 changes: 74 additions & 0 deletions internal/pkg/agent/application/upgrade/upgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,23 @@ func TestIsSameVersion(t *testing.T) {
newVersion: agentVersion123SNAPSHOTghijkl,
},
},
{
name: "same version and snapshot, no hash (SNAPSHOT upgrade before download)",
args: args{
current: agentVersion123SNAPSHOTabcdef,
metadata: packageMetadata{
manifest: nil,
},
version: "1.2.3-SNAPSHOT",
},
want: want{
same: false,
newVersion: agentVersion{
version: "1.2.3",
snapshot: true,
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
Expand Down Expand Up @@ -983,3 +1000,60 @@ func Test_selectWatcherExecutable(t *testing.T) {
})
}
}

func TestIsSameReleaseVersion(t *testing.T) {
tests := []struct {
name string
current agentVersion
target string
expect bool
}{{
name: "current version is snapshot",
current: agentVersion{
version: "1.2.3",
snapshot: true,
},
target: "1.2.3",
expect: false,
}, {
name: "target version is snapshot",
current: agentVersion{
version: "1.2.3",
},
target: "1.2.3-SNAPSHOT",
expect: false,
}, {
name: "target version is different version",
current: agentVersion{
version: "1.2.3",
},
target: "1.2.4",
expect: false,
}, {
name: "target version is same with pre-release",
michel-laterman marked this conversation as resolved.
Show resolved Hide resolved
current: agentVersion{
version: "1.2.3",
},
target: "1.2.3-custom.info",
expect: false,
}, {
name: "target version is same with build",
current: agentVersion{
version: "1.2.3",
},
target: "1.2.3+buildID",
expect: false,
}, {
name: "target version is same",
current: agentVersion{
version: "1.2.3",
},
target: "1.2.3",
expect: true,
}}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expect, isSameReleaseVersion(tc.current, tc.target))
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func TestStandaloneUpgradeSameCommit(t *testing.T) {
upgradetest.WithUnprivileged(unprivilegedAvailable),
upgradetest.WithDisableHashCheck(true),
)
assert.ErrorContainsf(t, err, fmt.Sprintf("agent version is already %s", currentVersion), "upgrade should fail indicating we are already at the same version")
assert.NoError(t, err)
michel-laterman marked this conversation as resolved.
Show resolved Hide resolved
})

t.Run(fmt.Sprintf("Upgrade on a repackaged version of agent %s (%s)", currentVersion, unPrivilegedString), func(t *testing.T) {
Expand Down
28 changes: 28 additions & 0 deletions testing/upgradetest/upgrader.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,14 @@ func PerformUpgrade(
}
}

// check status
if status := getStatus(ctx, startFixture); status != nil {
if status.State == 2 && status.UpgradeDetails == nil {
logger.Logf("Agent status indicates no upgrade is in progress.")
return nil
}
}

// wait for the watcher to show up
logger.Logf("waiting for upgrade watcher to start")
err = WaitForWatcher(ctx, 5*time.Minute, 10*time.Second)
Expand Down Expand Up @@ -653,3 +661,23 @@ func windowsBaseTemp() (string, error) {
}
return baseTmp, nil
}

// getStatus will attempt to get the agent status with retries if enounters an error
func getStatus(ctx context.Context, fixture *atesting.Fixture) *atesting.AgentStatusOutput {
ctx, cancel := context.WithTimeout(ctx, time.Second*30)
defer cancel()
ticker := time.NewTicker(time.Second * 5)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return nil
case <-ticker.C:
status, err := fixture.ExecStatus(ctx)
if err != nil {
continue
}
return &status
}
}
}
Loading