Skip to content

Commit 19c1f08

Browse files
committed
refactor manual rollback function and tests on a separate file
1 parent 55926d9 commit 19c1f08

File tree

4 files changed

+467
-423
lines changed

4 files changed

+467
-423
lines changed
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
2+
// or more contributor license agreements. Licensed under the Elastic License 2.0;
3+
// you may not use this file except in compliance with the Elastic License 2.0.
4+
5+
package upgrade
6+
7+
import (
8+
"context"
9+
"errors"
10+
"fmt"
11+
"os"
12+
"time"
13+
14+
"github.com/elastic/elastic-agent/internal/pkg/agent/application/filelock"
15+
"github.com/elastic/elastic-agent/internal/pkg/agent/application/paths"
16+
"github.com/elastic/elastic-agent/internal/pkg/agent/application/reexec"
17+
"github.com/elastic/elastic-agent/internal/pkg/fleetapi"
18+
v1 "github.com/elastic/elastic-agent/pkg/api/v1"
19+
"github.com/elastic/elastic-agent/pkg/core/logger"
20+
"github.com/elastic/elastic-agent/pkg/version"
21+
)
22+
23+
func (u *Upgrader) rollbackToPreviousVersion(ctx context.Context, topDir string, now time.Time, version string, action *fleetapi.ActionUpgrade) (reexec.ShutdownCallbackFn, error) {
24+
if version == "" {
25+
return nil, ErrEmptyRollbackVersion
26+
}
27+
28+
// check that the upgrade marker exists and is accessible
29+
updateMarkerPath := markerFilePath(paths.DataFrom(topDir))
30+
_, err := os.Stat(updateMarkerPath)
31+
if err != nil {
32+
return nil, fmt.Errorf("stat() on upgrade marker %q failed: %w", updateMarkerPath, err)
33+
}
34+
35+
// read the upgrade marker
36+
updateMarker, err := LoadMarker(paths.DataFrom(topDir))
37+
if err != nil {
38+
return nil, fmt.Errorf("loading marker: %w", err)
39+
}
40+
41+
if updateMarker == nil {
42+
return nil, ErrNilUpdateMarker
43+
}
44+
45+
// extract the agent installs involved in the upgrade and select the most appropriate watcher executable
46+
previous, current, err := extractAgentInstallsFromMarker(updateMarker)
47+
if err != nil {
48+
return nil, fmt.Errorf("extracting current and previous install details: %w", err)
49+
}
50+
watcherExecutable := u.watcherHelper.SelectWatcherExecutable(topDir, previous, current)
51+
52+
err = withTakeOverWatcher(ctx, u.log, topDir, u.watcherHelper, func() error {
53+
// read the upgrade marker
54+
updateMarker, err = LoadMarker(paths.DataFrom(topDir))
55+
if err != nil {
56+
return fmt.Errorf("loading marker: %w", err)
57+
}
58+
59+
if updateMarker == nil {
60+
return ErrNilUpdateMarker
61+
}
62+
63+
if len(updateMarker.RollbacksAvailable) == 0 {
64+
return ErrNoRollbacksAvailable
65+
}
66+
var selectedRollback *RollbackAvailable
67+
for _, rollback := range updateMarker.RollbacksAvailable {
68+
if rollback.Version == version && now.Before(rollback.ValidUntil) {
69+
selectedRollback = &rollback
70+
break
71+
}
72+
}
73+
if selectedRollback == nil {
74+
return fmt.Errorf("version %q not listed among the available rollbacks: %w", version, ErrNoRollbacksAvailable)
75+
}
76+
77+
// rollback
78+
_, err = u.watcherHelper.InvokeWatcher(u.log, watcherExecutable, "watch", "--rollback", updateMarker.PrevVersionedHome)
79+
if err != nil {
80+
return fmt.Errorf("starting rollback command: %w", err)
81+
}
82+
u.log.Debug("rollback command started successfully, PID")
83+
return nil
84+
})
85+
86+
if err != nil {
87+
// Invoke watcher again (now that we released the watcher applocks)
88+
_, invokeWatcherErr := u.watcherHelper.InvokeWatcher(u.log, watcherExecutable)
89+
if invokeWatcherErr != nil {
90+
return nil, errors.Join(err, fmt.Errorf("invoking watcher: %w", invokeWatcherErr))
91+
}
92+
return nil, err
93+
}
94+
95+
return nil, nil
96+
97+
}
98+
99+
func withTakeOverWatcher(ctx context.Context, log *logger.Logger, topDir string, watcherHelper WatcherHelper, f func() error) error {
100+
watcherApplock, err := watcherHelper.TakeOverWatcher(ctx, log, topDir)
101+
if err != nil {
102+
return fmt.Errorf("taking over watcher processes: %w", err)
103+
}
104+
defer func(watcherApplock *filelock.AppLocker) {
105+
releaseWatcherAppLockerErr := watcherApplock.Unlock()
106+
if releaseWatcherAppLockerErr != nil {
107+
log.Warnw("error releasing watcher applock", "error", releaseWatcherAppLockerErr)
108+
}
109+
}(watcherApplock)
110+
111+
return f()
112+
}
113+
114+
func extractAgentInstallsFromMarker(updateMarker *UpdateMarker) (previous agentInstall, current agentInstall, err error) {
115+
previousParsedVersion, err := version.ParseVersion(updateMarker.PrevVersion)
116+
if err != nil {
117+
return previous, current, fmt.Errorf("parsing previous version %q: %w", updateMarker.PrevVersion, err)
118+
}
119+
previous = agentInstall{
120+
parsedVersion: previousParsedVersion,
121+
version: updateMarker.PrevVersion,
122+
hash: updateMarker.PrevHash,
123+
versionedHome: updateMarker.PrevVersionedHome,
124+
}
125+
126+
currentParsedVersion, err := version.ParseVersion(updateMarker.Version)
127+
if err != nil {
128+
return previous, current, fmt.Errorf("parsing current version %q: %w", updateMarker.Version, err)
129+
}
130+
current = agentInstall{
131+
parsedVersion: currentParsedVersion,
132+
version: updateMarker.Version,
133+
hash: updateMarker.Hash,
134+
versionedHome: updateMarker.VersionedHome,
135+
}
136+
137+
return previous, current, nil
138+
}
139+
140+
func getAvailableRollbacks(rollbackWindow time.Duration, version *version.ParsedSemVer, now time.Time, newVersionedHome string, descriptor *v1.InstallDescriptor) []v1.AgentInstallDesc {
141+
if rollbackWindow == 0 {
142+
// if there's no rollback window it means that no rollback should survive the watcher cleanup at the end of the grace period.
143+
return nil
144+
}
145+
146+
if version == nil || version.Less(*Version_9_2_0_SNAPSHOT) {
147+
// if we have a not empty rollback window, write the prev version in the rollbacks_available field
148+
// we also need to check the destination version because the manual rollback and delayed cleanup will be
149+
// handled by that version of agent, so it needs to be recent enough
150+
return nil
151+
}
152+
153+
res := make([]v1.AgentInstallDesc, 0, len(descriptor.AgentInstalls))
154+
for _, installDesc := range descriptor.AgentInstalls {
155+
if installDesc.VersionedHome != newVersionedHome && (installDesc.TTL == nil || now.Before(*installDesc.TTL)) {
156+
// this is a valid possible rollback target, so we have to keep it available beyond the end of the grace period
157+
res = append(res, installDesc)
158+
}
159+
}
160+
return res
161+
}
162+
163+
func getCurrentInstallTTL(rollbackWindow time.Duration, now time.Time) *time.Time {
164+
if rollbackWindow == 0 {
165+
// no rollback window, no TTL
166+
return nil
167+
}
168+
169+
currentInstallTTLVar := now.Add(rollbackWindow)
170+
return &currentInstallTTLVar
171+
}

0 commit comments

Comments
 (0)