@@ -1038,6 +1038,23 @@ func TestIsSameReleaseVersion(t *testing.T) {
1038
1038
}
1039
1039
1040
1040
func TestManualRollback (t * testing.T ) {
1041
+ const updatemarkerwatching456NoRollbackAvailable = `
1042
+ version: 4.5.6
1043
+ hash: newver
1044
+ versioned_home: data/elastic-agent-4.5.6-newver
1045
+ updated_on: 2025-07-11T10:11:12.131415Z
1046
+ prev_version: 1.2.3
1047
+ prev_hash: oldver
1048
+ prev_versioned_home: data/elastic-agent-1.2.3-oldver
1049
+ acked: false
1050
+ action: null
1051
+ details:
1052
+ target_version: 4.5.6
1053
+ state: UPG_WATCHING
1054
+ metadata:
1055
+ retry_until: null
1056
+ desired_outcome: UPGRADE
1057
+ `
1041
1058
const updatemarkerwatching456 = `
1042
1059
version: 4.5.6
1043
1060
hash: newver
@@ -1059,6 +1076,7 @@ func TestManualRollback(t *testing.T) {
1059
1076
valid_until: 2025-07-18T10:11:12.131415Z
1060
1077
desired_outcome: UPGRADE
1061
1078
`
1079
+
1062
1080
parsed123Version , err := agtversion .ParseVersion ("1.2.3" )
1063
1081
require .NoError (t , err )
1064
1082
parsed456Version , err := agtversion .ParseVersion ("4.5.6" )
@@ -1078,19 +1096,40 @@ func TestManualRollback(t *testing.T) {
1078
1096
versionedHome : "data/elastic-agent-4.5.6-newver" ,
1079
1097
}
1080
1098
1099
+ // this is the updated_on timestamp in the example
1100
+ nowBeforeTTL , err := time .Parse (time .RFC3339 , `2025-07-11T10:11:12Z` )
1101
+ require .NoError (t , err , "error parsing nowBeforeTTL" )
1102
+
1103
+ // the update marker yaml assume 7d TLL for rollbacks, let's make an extra day pass
1104
+ nowAfterTTL := nowBeforeTTL .Add (8 * 24 * time .Hour )
1105
+
1081
1106
type setupF func (t * testing.T , topDir string , agent * infomocks.Agent , watcherHelper * MockWatcherHelper )
1082
1107
type postRollbackAssertionsF func (t * testing.T , topDir string )
1083
1108
type testcase struct {
1084
1109
name string
1085
1110
setup setupF
1086
1111
artifactSettings * artifact.Config
1087
1112
upgradeSettings * configuration.UpgradeConfig
1113
+ now time.Time
1088
1114
version string
1089
1115
wantErr assert.ErrorAssertionFunc
1090
1116
additionalAsserts postRollbackAssertionsF
1091
1117
}
1092
1118
1093
1119
testcases := []testcase {
1120
+ {
1121
+ name : "no rollback version - rollback fails" ,
1122
+ setup : func (t * testing.T , topDir string , agent * infomocks.Agent , watcherHelper * MockWatcherHelper ) {
1123
+ //do not setup anything here, let the rollback fail
1124
+ },
1125
+ artifactSettings : artifact .DefaultConfig (),
1126
+ upgradeSettings : configuration .DefaultUpgradeConfig (),
1127
+ version : "" ,
1128
+ wantErr : func (t assert.TestingT , err error , i ... interface {}) bool {
1129
+ return assert .ErrorIs (t , err , ErrEmptyRollbackVersion )
1130
+ },
1131
+ additionalAsserts : nil ,
1132
+ },
1094
1133
{
1095
1134
name : "no update marker - rollback fails" ,
1096
1135
setup : func (t * testing.T , topDir string , agent * infomocks.Agent , watcherHelper * MockWatcherHelper ) {
@@ -1104,6 +1143,85 @@ func TestManualRollback(t *testing.T) {
1104
1143
},
1105
1144
additionalAsserts : nil ,
1106
1145
},
1146
+ {
1147
+ name : "update marker ok but rollback available is empty - error" ,
1148
+ setup : func (t * testing.T , topDir string , agent * infomocks.Agent , watcherHelper * MockWatcherHelper ) {
1149
+ err := os .WriteFile (markerFilePath (paths .DataFrom (topDir )), []byte (updatemarkerwatching456NoRollbackAvailable ), 0600 )
1150
+ require .NoError (t , err , "error setting up update marker" )
1151
+ locker := filelock .NewAppLocker (topDir , "watcher.lock" )
1152
+ err = locker .TryLock ()
1153
+ require .NoError (t , err , "error locking initial watcher AppLocker" )
1154
+ watcherHelper .EXPECT ().TakeOverWatcher (t .Context (), mock .Anything , topDir ).Return (locker , nil )
1155
+ },
1156
+ artifactSettings : artifact .DefaultConfig (),
1157
+ upgradeSettings : configuration .DefaultUpgradeConfig (),
1158
+ version : "2.3.4-unknown" ,
1159
+ wantErr : func (t assert.TestingT , err error , i ... interface {}) bool {
1160
+ return assert .ErrorIs (t , err , ErrNoRollbacksAvailable )
1161
+ },
1162
+ additionalAsserts : func (t * testing.T , topDir string ) {
1163
+ // marker should be untouched
1164
+ filePath := markerFilePath (paths .DataFrom (topDir ))
1165
+ require .FileExists (t , filePath )
1166
+ markerFileBytes , readMarkerErr := os .ReadFile (filePath )
1167
+ require .NoError (t , readMarkerErr )
1168
+
1169
+ assert .YAMLEq (t , updatemarkerwatching456NoRollbackAvailable , string (markerFileBytes ), "update marker should be untouched" )
1170
+ },
1171
+ },
1172
+ {
1173
+ name : "update marker ok but version is not available for rollback - error" ,
1174
+ setup : func (t * testing.T , topDir string , agent * infomocks.Agent , watcherHelper * MockWatcherHelper ) {
1175
+ err := os .WriteFile (markerFilePath (paths .DataFrom (topDir )), []byte (updatemarkerwatching456 ), 0600 )
1176
+ require .NoError (t , err , "error setting up update marker" )
1177
+ locker := filelock .NewAppLocker (topDir , "watcher.lock" )
1178
+ err = locker .TryLock ()
1179
+ require .NoError (t , err , "error locking initial watcher AppLocker" )
1180
+ watcherHelper .EXPECT ().TakeOverWatcher (t .Context (), mock .Anything , topDir ).Return (locker , nil )
1181
+ },
1182
+ artifactSettings : artifact .DefaultConfig (),
1183
+ upgradeSettings : configuration .DefaultUpgradeConfig (),
1184
+ version : "2.3.4-unknown" ,
1185
+ wantErr : func (t assert.TestingT , err error , i ... interface {}) bool {
1186
+ return assert .ErrorIs (t , err , ErrNoRollbacksAvailable )
1187
+ },
1188
+ additionalAsserts : func (t * testing.T , topDir string ) {
1189
+ // marker should be untouched
1190
+ filePath := markerFilePath (paths .DataFrom (topDir ))
1191
+ require .FileExists (t , filePath )
1192
+ markerFileBytes , readMarkerErr := os .ReadFile (filePath )
1193
+ require .NoError (t , readMarkerErr )
1194
+
1195
+ assert .YAMLEq (t , updatemarkerwatching456 , string (markerFileBytes ), "update marker should be untouched" )
1196
+ },
1197
+ },
1198
+ {
1199
+ name : "update marker ok but rollback is expired - error" ,
1200
+ setup : func (t * testing.T , topDir string , agent * infomocks.Agent , watcherHelper * MockWatcherHelper ) {
1201
+ err := os .WriteFile (markerFilePath (paths .DataFrom (topDir )), []byte (updatemarkerwatching456 ), 0600 )
1202
+ require .NoError (t , err , "error setting up update marker" )
1203
+ locker := filelock .NewAppLocker (topDir , "watcher.lock" )
1204
+ err = locker .TryLock ()
1205
+ require .NoError (t , err , "error locking initial watcher AppLocker" )
1206
+ watcherHelper .EXPECT ().TakeOverWatcher (t .Context (), mock .Anything , topDir ).Return (locker , nil )
1207
+ },
1208
+ artifactSettings : artifact .DefaultConfig (),
1209
+ upgradeSettings : configuration .DefaultUpgradeConfig (),
1210
+ now : nowAfterTTL ,
1211
+ version : "1.2.3" ,
1212
+ wantErr : func (t assert.TestingT , err error , i ... interface {}) bool {
1213
+ return assert .ErrorIs (t , err , ErrNoRollbacksAvailable )
1214
+ },
1215
+ additionalAsserts : func (t * testing.T , topDir string ) {
1216
+ // marker should be untouched
1217
+ filePath := markerFilePath (paths .DataFrom (topDir ))
1218
+ require .FileExists (t , filePath )
1219
+ markerFileBytes , readMarkerErr := os .ReadFile (filePath )
1220
+ require .NoError (t , readMarkerErr )
1221
+
1222
+ assert .YAMLEq (t , updatemarkerwatching456 , string (markerFileBytes ), "update marker should be untouched" )
1223
+ },
1224
+ },
1107
1225
{
1108
1226
name : "update marker ok - takeover watcher, persist rollback and restart most recent watcher" ,
1109
1227
setup : func (t * testing.T , topDir string , agent * infomocks.Agent , watcherHelper * MockWatcherHelper ) {
@@ -1119,6 +1237,7 @@ func TestManualRollback(t *testing.T) {
1119
1237
},
1120
1238
artifactSettings : artifact .DefaultConfig (),
1121
1239
upgradeSettings : configuration .DefaultUpgradeConfig (),
1240
+ now : nowBeforeTTL ,
1122
1241
version : "1.2.3" ,
1123
1242
wantErr : assert .NoError ,
1124
1243
additionalAsserts : func (t * testing.T , topDir string ) {
@@ -1148,8 +1267,7 @@ func TestManualRollback(t *testing.T) {
1148
1267
1149
1268
upgrader , err := NewUpgrader (log , tc .artifactSettings , tc .upgradeSettings , mockAgentInfo , mockWatcherHelper )
1150
1269
require .NoError (t , err , "error instantiating upgrader" )
1151
-
1152
- _ , err = upgrader .forceRollbackToPreviousVersion (t .Context (), topDir , tc .version , nil , nil )
1270
+ _ , err = upgrader .forceRollbackToPreviousVersion (t .Context (), topDir , tc .now , tc .version , nil )
1153
1271
tc .wantErr (t , err , "unexpected error returned by forceRollbackToPreviousVersion()" )
1154
1272
if tc .additionalAsserts != nil {
1155
1273
tc .additionalAsserts (t , topDir )
0 commit comments