diff --git a/.mockery.yaml b/.mockery.yaml index c2445f42811..ea5df1eebbe 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -29,3 +29,11 @@ packages: github.com/elastic/elastic-agent/internal/pkg/agent/application/info: interfaces: Agent: + github.com/elastic/elastic-agent/internal/pkg/agent/cmd: + interfaces: + agentWatcher: + config: + mockname: "AgentWatcher" + installationModifier: + config: + mockname: "InstallationModifier" diff --git a/NOTICE-fips.txt b/NOTICE-fips.txt index 1a7297d3477..4237a9151c6 100644 --- a/NOTICE-fips.txt +++ b/NOTICE-fips.txt @@ -1254,11 +1254,11 @@ SOFTWARE -------------------------------------------------------------------------------- Dependency : github.com/elastic/elastic-agent-libs -Version: v0.20.1 +Version: v0.21.0 Licence type (autodetected): Apache-2.0 -------------------------------------------------------------------------------- -Contents of probable licence file $GOMODCACHE/github.com/elastic/elastic-agent-libs@v0.20.1/LICENSE: +Contents of probable licence file $GOMODCACHE/github.com/elastic/elastic-agent-libs@v0.21.0/LICENSE: Apache License Version 2.0, January 2004 diff --git a/NOTICE.txt b/NOTICE.txt index 836e0a89058..512d23728e2 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1254,11 +1254,11 @@ SOFTWARE -------------------------------------------------------------------------------- Dependency : github.com/elastic/elastic-agent-libs -Version: v0.20.1 +Version: v0.21.0 Licence type (autodetected): Apache-2.0 -------------------------------------------------------------------------------- -Contents of probable licence file $GOMODCACHE/github.com/elastic/elastic-agent-libs@v0.20.1/LICENSE: +Contents of probable licence file $GOMODCACHE/github.com/elastic/elastic-agent-libs@v0.21.0/LICENSE: Apache License Version 2.0, January 2004 diff --git a/changelog/fragments/1751024454-preserve-upgrade-marker-and-add-rollback-reason.yaml b/changelog/fragments/1751024454-preserve-upgrade-marker-and-add-rollback-reason.yaml new file mode 100644 index 00000000000..08b69f468a4 --- /dev/null +++ b/changelog/fragments/1751024454-preserve-upgrade-marker-and-add-rollback-reason.yaml @@ -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: feature + +# Change summary; a 80ish characters long description of the change. +summary: Preserve upgrade marker when rolling back upgrade and add rollback reason + +# 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: Upgrade marker is now preserved by the watcher when performing a rollback and a new `reason` + field is added to the upgrade details structure. The reason for keeping the upgrade marker when rolling back is + to allow the rolled back agent to read the rollback reason and communicate that to user/Fleet. + +# 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/elastic/elastic-agent/8407 + +# 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/owner/repo/1234 diff --git a/control_v2.proto b/control_v2.proto index 0bfd98e8580..55a988a37ff 100644 --- a/control_v2.proto +++ b/control_v2.proto @@ -265,6 +265,10 @@ message UpgradeDetailsMetadata { // The deadline until when a retryable upgrade step, e.g. the download // step, will be retried. string retry_until = 6; + + // Reason is a string that may give out more information about transitioning to the current state. + // It has been introduced initially to distinguish between manual and automatic rollbacks + string reason = 7; } // DiagnosticFileResult is a file result from a diagnostic result. diff --git a/go.mod b/go.mod index 53e299a4dc9..5f29c31e6b8 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/elastic/cloud-on-k8s/v2 v2.0.0-20250327073047-b624240832ae github.com/elastic/elastic-agent-autodiscover v0.9.2 github.com/elastic/elastic-agent-client/v7 v7.17.2 - github.com/elastic/elastic-agent-libs v0.20.1 + github.com/elastic/elastic-agent-libs v0.21.0 github.com/elastic/elastic-agent-system-metrics v0.11.16 github.com/elastic/elastic-transport-go/v8 v8.7.0 github.com/elastic/go-elasticsearch/v8 v8.18.1 diff --git a/go.sum b/go.sum index 6091f7cb1b3..b9eef4089ae 100644 --- a/go.sum +++ b/go.sum @@ -522,8 +522,8 @@ github.com/elastic/elastic-agent-autodiscover v0.9.2 h1:eBmru2v66HRRHOFf89rDl9OZ github.com/elastic/elastic-agent-autodiscover v0.9.2/go.mod h1:RNaHnOTYfNptSTQUyZYnjypxmrR5AaE6BIap/175F5c= github.com/elastic/elastic-agent-client/v7 v7.17.2 h1:Cl2TeABqWZgW40t5fchGWT/sRk4MDDLWA0d8iHHOxLA= github.com/elastic/elastic-agent-client/v7 v7.17.2/go.mod h1:5irRFqp6HLqtu1S+OeY0jg8x7K6PLL+DW+PwVk1vJnk= -github.com/elastic/elastic-agent-libs v0.20.1 h1:M7ZME7yctVhI9349OiG0VQ8a+RsuParA/ZUgCuctwBE= -github.com/elastic/elastic-agent-libs v0.20.1/go.mod h1:xSeIP3NtOIT4N2pPS4EyURmS1Q8mK0lWZ8Wd1Du6q3w= +github.com/elastic/elastic-agent-libs v0.21.0 h1:lt2Xc87Si0mea0BgRKZGZA30j8LEx57k7GAyiKmZP/8= +github.com/elastic/elastic-agent-libs v0.21.0/go.mod h1:xSeIP3NtOIT4N2pPS4EyURmS1Q8mK0lWZ8Wd1Du6q3w= github.com/elastic/elastic-agent-system-metrics v0.11.16 h1:cLjuO8pE5cUwPGWUHmy1VOERmJVDaep8gY+U4YRQ5vs= github.com/elastic/elastic-agent-system-metrics v0.11.16/go.mod h1:qiZC5p1hd8te4XVnhh7FkXdcYhxFnl5i9GJpROtf6zo= github.com/elastic/elastic-transport-go/v8 v8.7.0 h1:OgTneVuXP2uip4BA658Xi6Hfw+PeIOod2rY3GVMGoVE= diff --git a/internal/pkg/agent/application/upgrade/details/details.go b/internal/pkg/agent/application/upgrade/details/details.go index 835cb8900bc..cd83c61855d 100644 --- a/internal/pkg/agent/application/upgrade/details/details.go +++ b/internal/pkg/agent/application/upgrade/details/details.go @@ -55,6 +55,10 @@ type Metadata struct { // the Fail() method of UpgradeDetails to correctly record details when // an upgrade fails. ErrorMsg string `json:"error_msg,omitempty" yaml:"error_msg,omitempty"` + + // Reason is a string that may give out more information about transitioning to the current state. It has been + // introduced initially to distinguish between manual and automatic rollbacks + Reason string `json:"reason,omitempty" yaml:"reason,omitempty"` } func NewDetails(targetVersion string, initialState State, actionID string) *Details { @@ -87,6 +91,27 @@ func (d *Details) SetState(s State) { d.notifyObservers() } +// SetStateWithReason is a convenience method to set the state of the upgrade, the metadata.reason and +// notify all observers. +// Do NOT call SetStateWithReason with StateFailed; call the Fail method instead. +func (d *Details) SetStateWithReason(s State, reason string) { + d.mu.Lock() + defer d.mu.Unlock() + + d.State = s + d.Metadata.Reason = reason + + // If State is something other than StateFailed, make sure to clear + // Metadata.FailedState and Metadata.ErrorMsg as those two fields + // should be set when State is set to StateFailed. See the Fail method. + if s != StateFailed { + d.Metadata.ErrorMsg = "" + d.Metadata.FailedState = "" + } + + d.notifyObservers() +} + // SetDownloadProgress is a convenience method to set the download percent // and download rate when the upgrade is in UPG_DOWNLOADING state. func (d *Details) SetDownloadProgress(percent, rateBytesPerSecond float64) { diff --git a/internal/pkg/agent/application/upgrade/details/details_test.go b/internal/pkg/agent/application/upgrade/details/details_test.go index 7e10cd8916d..a3e7b5803c7 100644 --- a/internal/pkg/agent/application/upgrade/details/details_test.go +++ b/internal/pkg/agent/application/upgrade/details/details_test.go @@ -11,6 +11,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -30,6 +31,15 @@ func TestDetailsSetState(t *testing.T) { require.Equal(t, StateDownloading, det.State) } +func TestDetailsSetStateWithReason(t *testing.T) { + det := NewDetails("99.999.9999", StateWatching, "test_action_id") + require.Equal(t, StateWatching, det.State) + + det.SetStateWithReason(StateRollback, ReasonWatchFailed) + assert.Equal(t, StateRollback, det.State) + assert.Equal(t, ReasonWatchFailed, det.Metadata.Reason) +} + func TestDetailsFail(t *testing.T) { det := NewDetails("99.999.9999", StateRequested, "test_action_id") require.Equal(t, StateRequested, det.State) diff --git a/internal/pkg/agent/application/upgrade/details/state.go b/internal/pkg/agent/application/upgrade/details/state.go index d02c3fa4d3d..41a04698cdb 100644 --- a/internal/pkg/agent/application/upgrade/details/state.go +++ b/internal/pkg/agent/application/upgrade/details/state.go @@ -6,9 +6,9 @@ package details type State string -// The values of these State* constants should match those enumerated for -// upgrade_details.state in https://github.com/elastic/fleet-server/blob/main/model/openapi.yml const ( + // The values of these State* constants should match those enumerated for + // upgrade_details.state in https://github.com/elastic/fleet-server/blob/main/model/openapi.yml StateRequested State = "UPG_REQUESTED" StateScheduled State = "UPG_SCHEDULED" StateDownloading State = "UPG_DOWNLOADING" @@ -19,4 +19,7 @@ const ( StateRollback State = "UPG_ROLLBACK" StateCompleted State = "UPG_COMPLETED" StateFailed State = "UPG_FAILED" + + // List of well-known reasons for state transitions + ReasonWatchFailed = "watch failed" ) diff --git a/internal/pkg/agent/application/upgrade/marker_watcher.go b/internal/pkg/agent/application/upgrade/marker_watcher.go index c34043722bc..f4a1b33d849 100644 --- a/internal/pkg/agent/application/upgrade/marker_watcher.go +++ b/internal/pkg/agent/application/upgrade/marker_watcher.go @@ -146,7 +146,7 @@ func (mfw *MarkerFileWatcher) processMarker(currentVersion string, commit string if marker.Details == nil { marker.Details = details.NewDetails("unknown", details.StateRollback, marker.GetActionID()) } else if marker.Details.State == "" { - marker.Details.SetState(details.StateRollback) + marker.Details.SetStateWithReason(details.StateRollback, details.ReasonWatchFailed) } } diff --git a/internal/pkg/agent/application/upgrade/rollback.go b/internal/pkg/agent/application/upgrade/rollback.go index b562ab323b6..90be1bbe2df 100644 --- a/internal/pkg/agent/application/upgrade/rollback.go +++ b/internal/pkg/agent/application/upgrade/rollback.go @@ -63,7 +63,7 @@ func Rollback(ctx context.Context, log *logger.Logger, c client.Client, topDirPa } // cleanup everything except version we're rolling back into - return Cleanup(log, topDirPath, prevVersionedHome, prevHash, true, true) + return Cleanup(log, topDirPath, prevVersionedHome, prevHash, false, true) } // Cleanup removes all artifacts and files related to a specified version. diff --git a/internal/pkg/agent/application/upgrade/rollback_test.go b/internal/pkg/agent/application/upgrade/rollback_test.go index 664eb72978b..3f9cc0a33ab 100644 --- a/internal/pkg/agent/application/upgrade/rollback_test.go +++ b/internal/pkg/agent/application/upgrade/rollback_test.go @@ -390,7 +390,7 @@ func checkFilesAfterRollback(t *testing.T, topDir, oldAgentHome, newAgentHome st assert.Equal(t, []byte("Placeholder for agent 1.2.3-SNAPSHOT"), elasticAgentBytes, "reading elastic-agent content through symbolic link should point to the old version") } - assert.NoFileExists(t, filepath.Join(topDir, "data", markerFilename), "update marker should have been cleaned up") + assert.FileExists(t, filepath.Join(topDir, "data", markerFilename), "update marker should survive cleanup in case of rollback") } // setupAgents create fake agent installs, update marker file and symlink pointing to one of the installations' elastic-agent placeholder diff --git a/internal/pkg/agent/application/upgrade/step_mark.go b/internal/pkg/agent/application/upgrade/step_mark.go index c9dd6862bd5..6e270ee7d24 100644 --- a/internal/pkg/agent/application/upgrade/step_mark.go +++ b/internal/pkg/agent/application/upgrade/step_mark.go @@ -313,8 +313,8 @@ func loadMarker(markerFile string) (*UpdateMarker, error) { // SaveMarker serializes and persists the given upgrade marker to disk. // For critical upgrade transitions, pass shouldFsync as true so the marker // file is immediately flushed to persistent storage. -func SaveMarker(marker *UpdateMarker, shouldFsync bool) error { - return saveMarkerToPath(marker, markerFilePath(paths.Data()), shouldFsync) +func SaveMarker(dataDirPath string, marker *UpdateMarker, shouldFsync bool) error { + return saveMarkerToPath(marker, markerFilePath(dataDirPath), shouldFsync) } func saveMarkerToPath(marker *UpdateMarker, markerFile string, shouldFsync bool) error { diff --git a/internal/pkg/agent/application/upgrade/upgrade.go b/internal/pkg/agent/application/upgrade/upgrade.go index 1738cfe0170..19d3b67cb2b 100644 --- a/internal/pkg/agent/application/upgrade/upgrade.go +++ b/internal/pkg/agent/application/upgrade/upgrade.go @@ -417,7 +417,7 @@ func waitForWatcherWithTimeoutCreationFunc(ctx context.Context, log *logger.Logg } case <-watcherContext.Done(): - log.Error("upgrade watcher did not start watching within %s or context has expired", waitTime) + log.Errorf("upgrade watcher did not start watching within %s or context has expired", waitTime) return goerrors.Join(ErrWatcherNotStarted, watcherContext.Err()) } } @@ -449,7 +449,7 @@ func (u *Upgrader) Ack(ctx context.Context, acker acker.Acker) error { marker.Acked = true - return SaveMarker(marker, false) + return SaveMarker(paths.Data(), marker, false) } func (u *Upgrader) AckAction(ctx context.Context, acker acker.Acker, action fleetapi.Action) error { diff --git a/internal/pkg/agent/cmd/status.go b/internal/pkg/agent/cmd/status.go index d9c82359462..bdffc8e3420 100644 --- a/internal/pkg/agent/cmd/status.go +++ b/internal/pkg/agent/cmd/status.go @@ -230,6 +230,9 @@ func listUpgradeDetails(l list.Writer, upgradeDetails *cproto.UpgradeDetails) { if upgradeDetails.Metadata.RetryErrorMsg != "" { l.AppendItem("retry_error_msg: " + upgradeDetails.Metadata.RetryErrorMsg) } + if upgradeDetails.Metadata.Reason != "" { + l.AppendItem("reason: " + upgradeDetails.Metadata.Reason) + } l.UnIndent() } diff --git a/internal/pkg/agent/cmd/watch.go b/internal/pkg/agent/cmd/watch.go index 037c01a91d0..f203c1814f7 100644 --- a/internal/pkg/agent/cmd/watch.go +++ b/internal/pkg/agent/cmd/watch.go @@ -53,7 +53,7 @@ func newWatchCommandWithArgs(_ []string, streams *cli.IOStreams) *cobra.Command // Make sure to flush any buffered logs before we're done. defer log.Sync() //nolint:errcheck // flushing buffered logs is best effort. - if err := watchCmd(log, cfg); err != nil { + if err := watchCmd(log, paths.Top(), cfg.Settings.Upgrade.Watcher, new(upgradeAgentWatcher), new(upgradeInstallationModifier)); err != nil { log.Errorw("Watch command failed", "error.message", err) fmt.Fprintf(streams.Err, "Watch command failed: %v\n%s\n", err, troubleshootMessage()) os.Exit(4) @@ -64,21 +64,30 @@ func newWatchCommandWithArgs(_ []string, streams *cli.IOStreams) *cobra.Command return cmd } -func watchCmd(log *logp.Logger, cfg *configuration.Configuration) error { - log.Infow("Upgrade Watcher started", "process.pid", os.Getpid(), "agent.version", version.GetAgentPackageVersion()) - marker, err := upgrade.LoadMarker(paths.Data()) +type agentWatcher interface { + Watch(ctx context.Context, tilGrace, errorCheckInterval time.Duration, log *logp.Logger) error +} + +type installationModifier interface { + Cleanup(log *logger.Logger, topDirPath, currentVersionedHome, currentHash string, removeMarker, keepLogs bool) error + Rollback(ctx context.Context, log *logger.Logger, c client.Client, topDirPath, prevVersionedHome, prevHash string) error +} + +func watchCmd(log *logp.Logger, topDir string, cfg *configuration.UpgradeWatcherConfig, watcher agentWatcher, installModifier installationModifier) error { + log.Infow("Upgrade Watcher started", "process.pid", os.Getpid(), "agent.version", version.GetAgentPackageVersion(), "config", cfg) + dataDir := paths.DataFrom(topDir) + marker, err := upgrade.LoadMarker(dataDir) if err != nil { log.Error("failed to load marker", err) return err } if marker == nil { // no marker found we're not in upgrade process - log.Infof("update marker not present at '%s'", paths.Data()) + log.Infof("update marker not present at '%s'", dataDir) return nil } - log.Infof("Loaded update marker %+v", marker) - + log.With("marker", marker, "details", marker.Details).Info("Loaded update marker") locker := filelock.NewAppLocker(paths.Top(), watcherLockFile) if err := locker.TryLock(); err != nil { if errors.Is(err, filelock.ErrAppAlreadyRunning) { @@ -93,14 +102,18 @@ func watchCmd(log *logp.Logger, cfg *configuration.Configuration) error { _ = locker.Unlock() }() - isWithinGrace, tilGrace := gracePeriod(marker, cfg.Settings.Upgrade.Watcher.GracePeriod) - if !isWithinGrace { - log.Infof("not within grace [updatedOn %v] %v", marker.UpdatedOn.String(), time.Since(marker.UpdatedOn).String()) + isWithinGrace, tilGrace := gracePeriod(marker, cfg.GracePeriod) + if isTerminalState(marker) || !isWithinGrace { + stateString := "" + if marker.Details != nil { + stateString = string(marker.Details.State) + } + log.Infof("not within grace [updatedOn %v] %v or agent have been rolled back [state: %s]", marker.UpdatedOn.String(), time.Since(marker.UpdatedOn).String(), stateString) // if it is started outside of upgrade loop // if we're not within grace and marker is still there it might mean // that cleanup was not performed ok, cleanup everything except current version // hash is the same as hash of agent which initiated watcher. - if err := upgrade.Cleanup(log, paths.Top(), paths.VersionedHome(paths.Top()), release.ShortCommit(), true, false); err != nil { + if err := installModifier.Cleanup(log, paths.Top(), paths.VersionedHome(topDir), release.ShortCommit(), true, false); err != nil { log.Error("clean up of prior watcher run failed", err) } // exit nicely @@ -109,15 +122,18 @@ func watchCmd(log *logp.Logger, cfg *configuration.Configuration) error { // About to start watching the upgrade. Initialize upgrade details and save them in the // upgrade marker. - upgradeDetails := initUpgradeDetails(marker, upgrade.SaveMarker, log) + saveMarkerFunc := func(marker *upgrade.UpdateMarker, b bool) error { + return upgrade.SaveMarker(dataDir, marker, b) + } + upgradeDetails := initUpgradeDetails(marker, saveMarkerFunc, log) - errorCheckInterval := cfg.Settings.Upgrade.Watcher.ErrorCheck.Interval + errorCheckInterval := cfg.ErrorCheck.Interval ctx := context.Background() - if err := watch(ctx, tilGrace, errorCheckInterval, log); err != nil { + if err := watcher.Watch(ctx, tilGrace, errorCheckInterval, log); err != nil { log.Error("Error detected, proceeding to rollback: %v", err) - upgradeDetails.SetState(details.StateRollback) - err = upgrade.Rollback(ctx, log, client.New(), paths.Top(), marker.PrevVersionedHome, marker.PrevHash) + upgradeDetails.SetStateWithReason(details.StateRollback, details.ReasonWatchFailed) + err = installModifier.Rollback(ctx, log, client.New(), paths.Top(), marker.PrevVersionedHome, marker.PrevHash) if err != nil { log.Error("rollback failed", err) upgradeDetails.Fail(err) @@ -135,13 +151,31 @@ func watchCmd(log *logp.Logger, cfg *configuration.Configuration) error { // Why is this being skipped on Windows? The comment above is not clear. // issue: https://github.com/elastic/elastic-agent/issues/3027 removeMarker := !isWindows() - err = upgrade.Cleanup(log, paths.Top(), marker.VersionedHome, marker.Hash, removeMarker, false) + err = installModifier.Cleanup(log, topDir, marker.VersionedHome, marker.Hash, removeMarker, false) if err != nil { log.Error("cleanup after successful watch failed", err) } return err } +// isTerminalState returns true if the state in the upgrade marker contains details and the upgrade details state is a +// terminal one: UPG_COMPLETE, UPG_ROLLBACK and UPG_FAILED +// If the upgrade marker or the upgrade marker details are nil the function will return false: as +// no state is specified, having simply a marker without details would mean that some upgrade operation is ongoing +// (probably initiated by an older agent). +func isTerminalState(marker *upgrade.UpdateMarker) bool { + if marker.Details == nil { + return false + } + + switch marker.Details.State { + case details.StateCompleted, details.StateRollback, details.StateFailed: + return true + default: + return false + } +} + func isWindows() bool { return runtime.GOOS == "windows" } diff --git a/internal/pkg/agent/cmd/watch_impl.go b/internal/pkg/agent/cmd/watch_impl.go new file mode 100644 index 00000000000..92e3118435c --- /dev/null +++ b/internal/pkg/agent/cmd/watch_impl.go @@ -0,0 +1,31 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License 2.0; +// you may not use this file except in compliance with the Elastic License 2.0. + +package cmd + +import ( + "context" + "time" + + "github.com/elastic/elastic-agent-libs/logp" + "github.com/elastic/elastic-agent/internal/pkg/agent/application/upgrade" + "github.com/elastic/elastic-agent/pkg/control/v2/client" + "github.com/elastic/elastic-agent/pkg/core/logger" +) + +type upgradeAgentWatcher struct{} + +func (a upgradeAgentWatcher) Watch(ctx context.Context, tilGrace, errorCheckInterval time.Duration, log *logp.Logger) error { + return watch(ctx, tilGrace, errorCheckInterval, log) +} + +type upgradeInstallationModifier struct{} + +func (a upgradeInstallationModifier) Cleanup(log *logger.Logger, topDirPath, currentVersionedHome, currentHash string, removeMarker, keepLogs bool) error { + return upgrade.Cleanup(log, topDirPath, currentVersionedHome, currentHash, removeMarker, keepLogs) +} + +func (a upgradeInstallationModifier) Rollback(ctx context.Context, log *logger.Logger, c client.Client, topDirPath, prevVersionedHome, prevHash string) error { + return upgrade.Rollback(ctx, log, c, topDirPath, prevVersionedHome, prevHash) +} diff --git a/internal/pkg/agent/cmd/watch_test.go b/internal/pkg/agent/cmd/watch_test.go index e1f6524c012..9451c476543 100644 --- a/internal/pkg/agent/cmd/watch_test.go +++ b/internal/pkg/agent/cmd/watch_test.go @@ -5,18 +5,28 @@ package cmd import ( + "fmt" + "os" + "runtime" "testing" + "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "go.uber.org/zap/zapcore" + "github.com/stretchr/testify/require" + + "github.com/elastic/elastic-agent/internal/pkg/agent/application/paths" "github.com/elastic/elastic-agent/internal/pkg/agent/application/upgrade/details" + "github.com/elastic/elastic-agent/internal/pkg/agent/configuration" "github.com/elastic/elastic-agent/internal/pkg/agent/errors" "github.com/elastic/elastic-agent/internal/pkg/fleetapi" + "github.com/elastic/elastic-agent/internal/pkg/release" "github.com/elastic/elastic-agent/pkg/core/logger/loggertest" - "github.com/stretchr/testify/require" - "github.com/elastic/elastic-agent/internal/pkg/agent/application/upgrade" + cmdmocks "github.com/elastic/elastic-agent/testing/mocks/internal_/pkg/agent/cmd" ) func TestInitUpgradeDetails(t *testing.T) { @@ -74,3 +84,200 @@ func TestInitUpgradeDetails(t *testing.T) { require.Equal(t, zapcore.ErrorLevel, logs[0].Level) require.Equal(t, `unable to save upgrade marker after clearing upgrade details: some error`, logs[0].Message) } + +func Test_watchCmd(t *testing.T) { + type args struct { + cfg *configuration.UpgradeWatcherConfig + } + tests := []struct { + name string + setupUpgradeMarker func(t *testing.T, tmpDir string, watcher *cmdmocks.AgentWatcher, installModifier *cmdmocks.InstallationModifier) + args args + wantErr assert.ErrorAssertionFunc + }{ + { + name: "no upgrade marker, no party", + setupUpgradeMarker: func(t *testing.T, topDir string, watcher *cmdmocks.AgentWatcher, installModifier *cmdmocks.InstallationModifier) { + dataDirPath := paths.DataFrom(topDir) + err := os.MkdirAll(dataDirPath, 0755) + require.NoError(t, err) + }, + args: args{ + cfg: configuration.DefaultUpgradeConfig().Watcher, + }, + wantErr: assert.NoError, + }, + { + name: "happy path: no error watching, cleanup prev install", + setupUpgradeMarker: func(t *testing.T, topDir string, watcher *cmdmocks.AgentWatcher, installModifier *cmdmocks.InstallationModifier) { + dataDirPath := paths.DataFrom(topDir) + err := os.MkdirAll(dataDirPath, 0755) + require.NoError(t, err) + err = upgrade.SaveMarker( + dataDirPath, + &upgrade.UpdateMarker{ + Version: "4.5.6", + Hash: "newver", + VersionedHome: "elastic-agent-4.5.6-newver", + UpdatedOn: time.Now(), + PrevVersion: "1.2.3", + PrevHash: "prvver", + PrevVersionedHome: "elastic-agent-prvver", + Acked: false, + Action: nil, + Details: nil, //details.NewDetails("4.5.6", details.StateReplacing, ""), + DesiredOutcome: upgrade.OUTCOME_UPGRADE, + }, + true, + ) + require.NoError(t, err) + + watcher.EXPECT(). + Watch(mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return(nil) + + // on windows the marker is not removed immediately to allow for cleanup on restart + expectedRemoveMarkerFlag := runtime.GOOS != "windows" + + installModifier.EXPECT(). + Cleanup(mock.Anything, topDir, "elastic-agent-4.5.6-newver", "newver", expectedRemoveMarkerFlag, false). + Return(nil) + }, + args: args{ + cfg: configuration.DefaultUpgradeConfig().Watcher, + }, + wantErr: assert.NoError, + }, + { + name: "unhappy path: error watching, rollback to previous install", + setupUpgradeMarker: func(t *testing.T, topDir string, watcher *cmdmocks.AgentWatcher, installModifier *cmdmocks.InstallationModifier) { + dataDirPath := paths.DataFrom(topDir) + err := os.MkdirAll(dataDirPath, 0755) + require.NoError(t, err) + err = upgrade.SaveMarker( + dataDirPath, + &upgrade.UpdateMarker{ + Version: "4.5.6", + Hash: "newver", + VersionedHome: "elastic-agent-4.5.6-newver", + UpdatedOn: time.Now(), + PrevVersion: "1.2.3", + PrevHash: "prvver", + PrevVersionedHome: "elastic-agent-prvver", + Acked: false, + Action: nil, + Details: nil, //details.NewDetails("4.5.6", details.StateReplacing, ""), + DesiredOutcome: upgrade.OUTCOME_UPGRADE, + }, + true, + ) + require.NoError(t, err) + + watcher.EXPECT(). + Watch(mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return(errors.New("some watch error due to agent misbehaving")) + installModifier.EXPECT(). + Rollback(mock.Anything, mock.Anything, mock.Anything, paths.Top(), "elastic-agent-prvver", "prvver"). + Return(nil) + }, + args: args{ + cfg: configuration.DefaultUpgradeConfig().Watcher, + }, + wantErr: assert.NoError, + }, + { + name: "upgrade rolled back: no watching, cleanup must be called", + setupUpgradeMarker: func(t *testing.T, topDir string, watcher *cmdmocks.AgentWatcher, installModifier *cmdmocks.InstallationModifier) { + dataDirPath := paths.DataFrom(topDir) + err := os.MkdirAll(dataDirPath, 0755) + require.NoError(t, err) + err = upgrade.SaveMarker( + dataDirPath, + &upgrade.UpdateMarker{ + Version: "4.5.6", + Hash: "newver", + VersionedHome: "elastic-agent-4.5.6-newver", + UpdatedOn: time.Now(), + PrevVersion: "1.2.3", + PrevHash: "prvver", + PrevVersionedHome: "elastic-agent-prvver", + Acked: false, + Action: nil, + Details: &details.Details{ + TargetVersion: "4.5.6", + State: details.StateRollback, + Metadata: details.Metadata{ + Reason: details.ReasonWatchFailed, + }, + }, + DesiredOutcome: upgrade.OUTCOME_UPGRADE, + }, + true, + ) + require.NoError(t, err) + // topdir, prevVersionedHome and prevHash are not taken from the upgrade marker, otherwise they would be + // + installModifier.EXPECT(). + Cleanup(mock.Anything, paths.Top(), paths.VersionedHome(topDir), release.ShortCommit(), true, false). + Return(nil) + }, + args: args{ + cfg: configuration.DefaultUpgradeConfig().Watcher, + }, + wantErr: assert.NoError, + }, + { + name: "after grace period: no watching, cleanup must be called", + setupUpgradeMarker: func(t *testing.T, topDir string, watcher *cmdmocks.AgentWatcher, installModifier *cmdmocks.InstallationModifier) { + dataDirPath := paths.DataFrom(topDir) + err := os.MkdirAll(dataDirPath, 0755) + require.NoError(t, err) + updatedOn := time.Now().Add(-5 * time.Minute) + err = upgrade.SaveMarker( + dataDirPath, + &upgrade.UpdateMarker{ + Version: "4.5.6", + Hash: "newver", + VersionedHome: "elastic-agent-4.5.6-newver", + UpdatedOn: updatedOn, + PrevVersion: "1.2.3", + PrevHash: "prvver", + PrevVersionedHome: "elastic-agent-prvver", + Acked: false, + Action: nil, + Details: nil, + DesiredOutcome: upgrade.OUTCOME_UPGRADE, + }, + true, + ) + require.NoError(t, err) + + // topdir, prevVersionedHome and prevHash are not taken from the upgrade marker, otherwise they would be + // + installModifier.EXPECT(). + Cleanup(mock.Anything, paths.Top(), paths.VersionedHome(topDir), release.ShortCommit(), true, false). + Return(nil) + }, + args: args{ + cfg: &configuration.UpgradeWatcherConfig{ + GracePeriod: 2 * time.Minute, + ErrorCheck: configuration.UpgradeWatcherCheckConfig{ + Interval: time.Second, + }, + }, + }, + wantErr: assert.NoError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + log, obs := loggertest.New(t.Name()) + tmpDir := t.TempDir() + mockWatcher := cmdmocks.NewAgentWatcher(t) + mockInstallModifier := cmdmocks.NewInstallationModifier(t) + tt.setupUpgradeMarker(t, tmpDir, mockWatcher, mockInstallModifier) + tt.wantErr(t, watchCmd(log, tmpDir, tt.args.cfg, mockWatcher, mockInstallModifier), fmt.Sprintf("watchCmd(%v, ...)", tt.args.cfg)) + t.Logf("watchCmd logs:\n%v", obs.All()) + }) + } +} diff --git a/pkg/control/v2/cproto/control_v2.pb.go b/pkg/control/v2/cproto/control_v2.pb.go index e3df292cc67..674529fdbad 100644 --- a/pkg/control/v2/cproto/control_v2.pb.go +++ b/pkg/control/v2/cproto/control_v2.pb.go @@ -423,7 +423,7 @@ type VersionResponse struct { BuildTime string `protobuf:"bytes,3,opt,name=buildTime,proto3" json:"buildTime,omitempty"` // Current running version is a snapshot. Snapshot bool `protobuf:"varint,4,opt,name=snapshot,proto3" json:"snapshot,omitempty"` - // Current running version is FIPS-capable. + // Current running version is FIPS-compliant. Fips bool `protobuf:"varint,5,opt,name=fips,proto3" json:"fips,omitempty"` } @@ -1353,6 +1353,9 @@ type UpgradeDetailsMetadata struct { // The deadline until when a retryable upgrade step, e.g. the download // step, will be retried. RetryUntil string `protobuf:"bytes,6,opt,name=retry_until,json=retryUntil,proto3" json:"retry_until,omitempty"` + // Reason is a string that may give out more information about transitioning to the current state. + // It has been introduced initially to distinguish between manual and automatic rollbacks + Reason string `protobuf:"bytes,7,opt,name=reason,proto3" json:"reason,omitempty"` } func (x *UpgradeDetailsMetadata) Reset() { @@ -1429,6 +1432,13 @@ func (x *UpgradeDetailsMetadata) GetRetryUntil() string { return "" } +func (x *UpgradeDetailsMetadata) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + // DiagnosticFileResult is a file result from a diagnostic result. type DiagnosticFileResult struct { state protoimpl.MessageState @@ -2236,7 +2246,7 @@ var file_control_v2_proto_rawDesc = []byte{ 0x64, 0x12, 0x3a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x4d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0xef, 0x01, + 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x87, 0x02, 0x0a, 0x16, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, @@ -2251,170 +2261,172 @@ var file_control_v2_proto_rawDesc = []byte{ 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x74, 0x72, 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x73, 0x67, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x74, 0x72, 0x79, 0x5f, 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x74, 0x72, 0x79, 0x55, 0x6e, 0x74, 0x69, 0x6c, 0x22, - 0xdf, 0x01, 0x0a, 0x14, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x46, 0x69, - 0x6c, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, - 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, - 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, - 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, - 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x67, 0x65, 0x6e, 0x65, 0x72, - 0x61, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, - 0x64, 0x22, 0x6c, 0x0a, 0x16, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x41, - 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x52, 0x0a, 0x12, 0x61, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x74, 0x72, 0x79, 0x55, 0x6e, 0x74, 0x69, 0x6c, 0x12, + 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0xdf, 0x01, 0x0a, 0x14, 0x44, 0x69, 0x61, 0x67, + 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, + 0x38, 0x0a, 0x09, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, + 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x22, 0x6c, 0x0a, 0x16, 0x44, 0x69, 0x61, + 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x52, 0x0a, 0x12, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, + 0x6c, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0e, 0x32, + 0x23, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x61, 0x6c, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x52, 0x11, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, + 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x22, 0xb5, 0x01, 0x0a, 0x1b, 0x44, 0x69, 0x61, 0x67, + 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x6f, + 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x63, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x43, + 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, + 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x52, 0x0a, 0x12, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x11, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x22, - 0xb5, 0x01, 0x0a, 0x1b, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x43, 0x6f, - 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x42, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, - 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, - 0x6e, 0x74, 0x73, 0x12, 0x52, 0x0a, 0x12, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, - 0x6c, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0e, 0x32, - 0x23, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, - 0x6e, 0x61, 0x6c, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x52, 0x11, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, - 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x22, 0x3f, 0x0a, 0x1a, 0x44, 0x69, 0x61, 0x67, 0x6e, - 0x6f, 0x73, 0x74, 0x69, 0x63, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, - 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6d, - 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x51, 0x0a, 0x17, 0x44, 0x69, 0x61, 0x67, - 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, + 0x3f, 0x0a, 0x1a, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x43, 0x6f, 0x6d, + 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, + 0x0c, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x49, 0x64, + 0x22, 0x51, 0x0a, 0x17, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x41, 0x67, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x07, 0x72, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, + 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x73, 0x22, 0x82, 0x01, 0x0a, 0x15, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, + 0x69, 0x63, 0x55, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, + 0x0c, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x49, 0x64, + 0x12, 0x2d, 0x0a, 0x09, 0x75, 0x6e, 0x69, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55, 0x6e, 0x69, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x75, 0x6e, 0x69, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x17, 0x0a, 0x07, 0x75, 0x6e, 0x69, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x75, 0x6e, 0x69, 0x74, 0x49, 0x64, 0x22, 0x4d, 0x0a, 0x16, 0x44, 0x69, 0x61, 0x67, + 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x33, 0x0a, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x1d, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, + 0x6f, 0x73, 0x74, 0x69, 0x63, 0x55, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x52, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x22, 0xd1, 0x01, 0x0a, 0x16, 0x44, 0x69, 0x61, 0x67, + 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x55, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, + 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x09, 0x75, 0x6e, 0x69, 0x74, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x55, 0x6e, 0x69, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x75, 0x6e, 0x69, 0x74, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x6e, 0x69, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x6e, 0x69, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, + 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x12, 0x36, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x82, 0x01, 0x0a, 0x15, + 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x8e, 0x01, 0x0a, 0x1b, + 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, + 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x14, + 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, + 0x72, 0x72, 0x6f, 0x72, 0x12, 0x36, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, + 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, + 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x4f, 0x0a, 0x17, + 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x55, 0x6e, 0x69, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, - 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6d, - 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x09, 0x75, 0x6e, 0x69, 0x74, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x63, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55, 0x6e, 0x69, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x75, - 0x6e, 0x69, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x6e, 0x69, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x6e, 0x69, 0x74, 0x49, 0x64, - 0x22, 0x4d, 0x0a, 0x16, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x55, 0x6e, - 0x69, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x05, 0x75, 0x6e, - 0x69, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x63, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x55, 0x6e, 0x69, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x22, - 0xd1, 0x01, 0x0a, 0x16, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x55, 0x6e, - 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, - 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x2d, 0x0a, - 0x09, 0x75, 0x6e, 0x69, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x10, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55, 0x6e, 0x69, 0x74, 0x54, 0x79, - 0x70, 0x65, 0x52, 0x08, 0x75, 0x6e, 0x69, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x07, - 0x75, 0x6e, 0x69, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, - 0x6e, 0x69, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x36, 0x0a, 0x07, 0x72, - 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, - 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x73, 0x22, 0x8e, 0x01, 0x0a, 0x1b, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, - 0x69, 0x63, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x70, 0x6f, - 0x6e, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x36, 0x0a, 0x07, - 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, - 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, - 0x63, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, - 0x75, 0x6c, 0x74, 0x73, 0x22, 0x4f, 0x0a, 0x17, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, - 0x69, 0x63, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x34, 0x0a, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, - 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, - 0x69, 0x63, 0x55, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, - 0x75, 0x6e, 0x69, 0x74, 0x73, 0x22, 0x2a, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, - 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x2a, 0x85, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x53, - 0x54, 0x41, 0x52, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x43, 0x4f, 0x4e, - 0x46, 0x49, 0x47, 0x55, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x48, 0x45, - 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x45, 0x47, 0x52, 0x41, - 0x44, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, - 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x54, 0x4f, 0x50, 0x50, 0x49, 0x4e, 0x47, 0x10, 0x05, 0x12, - 0x0b, 0x0a, 0x07, 0x53, 0x54, 0x4f, 0x50, 0x50, 0x45, 0x44, 0x10, 0x06, 0x12, 0x0d, 0x0a, 0x09, - 0x55, 0x50, 0x47, 0x52, 0x41, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x07, 0x12, 0x0c, 0x0a, 0x08, 0x52, - 0x4f, 0x4c, 0x4c, 0x42, 0x41, 0x43, 0x4b, 0x10, 0x08, 0x2a, 0xbf, 0x01, 0x0a, 0x18, 0x43, 0x6f, - 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x4e, 0x6f, 0x6e, 0x65, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x53, 0x74, 0x61, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x4f, 0x4b, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x72, 0x72, - 0x6f, 0x72, 0x10, 0x03, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x50, 0x65, - 0x72, 0x6d, 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0x04, 0x12, 0x14, - 0x0a, 0x10, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x46, 0x61, 0x74, 0x61, 0x6c, 0x45, 0x72, 0x72, - 0x6f, 0x72, 0x10, 0x05, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x53, 0x74, - 0x6f, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x10, 0x06, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x53, 0x74, 0x6f, 0x70, 0x70, 0x65, 0x64, 0x10, 0x07, 0x2a, 0x21, 0x0a, 0x08, 0x55, - 0x6e, 0x69, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x49, 0x4e, 0x50, 0x55, 0x54, - 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4f, 0x55, 0x54, 0x50, 0x55, 0x54, 0x10, 0x01, 0x2a, 0x28, - 0x0a, 0x0c, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, - 0x0a, 0x07, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x46, - 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x01, 0x2a, 0x7f, 0x0a, 0x0b, 0x50, 0x70, 0x72, 0x6f, - 0x66, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x4c, 0x4c, 0x4f, 0x43, - 0x53, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x42, 0x4c, 0x4f, 0x43, 0x4b, 0x10, 0x01, 0x12, 0x0b, - 0x0a, 0x07, 0x43, 0x4d, 0x44, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x47, - 0x4f, 0x52, 0x4f, 0x55, 0x54, 0x49, 0x4e, 0x45, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x45, - 0x41, 0x50, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x4d, 0x55, 0x54, 0x45, 0x58, 0x10, 0x05, 0x12, - 0x0b, 0x0a, 0x07, 0x50, 0x52, 0x4f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x06, 0x12, 0x10, 0x0a, 0x0c, - 0x54, 0x48, 0x52, 0x45, 0x41, 0x44, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, 0x07, 0x12, 0x09, - 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x08, 0x2a, 0x30, 0x0a, 0x1b, 0x41, 0x64, 0x64, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, - 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x07, 0x0a, 0x03, 0x43, 0x50, 0x55, 0x10, - 0x00, 0x12, 0x08, 0x0a, 0x04, 0x43, 0x4f, 0x4e, 0x4e, 0x10, 0x01, 0x32, 0xdf, 0x04, 0x0a, 0x13, - 0x45, 0x6c, 0x61, 0x73, 0x74, 0x69, 0x63, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x74, - 0x72, 0x6f, 0x6c, 0x12, 0x31, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0d, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x22, 0x2a, 0x0a, + 0x10, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2a, 0x85, 0x01, 0x0a, 0x05, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x54, 0x41, 0x52, 0x54, 0x49, 0x4e, 0x47, 0x10, + 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x55, 0x52, 0x49, 0x4e, 0x47, + 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x02, 0x12, + 0x0c, 0x0a, 0x08, 0x44, 0x45, 0x47, 0x52, 0x41, 0x44, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0a, 0x0a, + 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x54, 0x4f, + 0x50, 0x50, 0x49, 0x4e, 0x47, 0x10, 0x05, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x54, 0x4f, 0x50, 0x50, + 0x45, 0x44, 0x10, 0x06, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x50, 0x47, 0x52, 0x41, 0x44, 0x49, 0x4e, + 0x47, 0x10, 0x07, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x4f, 0x4c, 0x4c, 0x42, 0x41, 0x43, 0x4b, 0x10, + 0x08, 0x2a, 0xbf, 0x01, 0x0a, 0x18, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x43, + 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0e, + 0x0a, 0x0a, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x4e, 0x6f, 0x6e, 0x65, 0x10, 0x00, 0x12, 0x12, + 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x53, 0x74, 0x61, 0x72, 0x74, 0x69, 0x6e, 0x67, + 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x4f, 0x4b, 0x10, 0x02, + 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x63, 0x6f, 0x76, 0x65, + 0x72, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0x03, 0x12, 0x18, 0x0a, 0x14, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x50, 0x65, 0x72, 0x6d, 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x10, 0x04, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x46, 0x61, 0x74, 0x61, 0x6c, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0x05, 0x12, 0x12, 0x0a, 0x0e, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x53, 0x74, 0x6f, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x10, 0x06, + 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x53, 0x74, 0x6f, 0x70, 0x70, 0x65, + 0x64, 0x10, 0x07, 0x2a, 0x21, 0x0a, 0x08, 0x55, 0x6e, 0x69, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x09, 0x0a, 0x05, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4f, 0x55, + 0x54, 0x50, 0x55, 0x54, 0x10, 0x01, 0x2a, 0x28, 0x0a, 0x0c, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, + 0x53, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x01, + 0x2a, 0x7f, 0x0a, 0x0b, 0x50, 0x70, 0x72, 0x6f, 0x66, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x0a, 0x0a, 0x06, 0x41, 0x4c, 0x4c, 0x4f, 0x43, 0x53, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x42, + 0x4c, 0x4f, 0x43, 0x4b, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4d, 0x44, 0x4c, 0x49, 0x4e, + 0x45, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x47, 0x4f, 0x52, 0x4f, 0x55, 0x54, 0x49, 0x4e, 0x45, + 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x45, 0x41, 0x50, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, + 0x4d, 0x55, 0x54, 0x45, 0x58, 0x10, 0x05, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x52, 0x4f, 0x46, 0x49, + 0x4c, 0x45, 0x10, 0x06, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x48, 0x52, 0x45, 0x41, 0x44, 0x43, 0x52, + 0x45, 0x41, 0x54, 0x45, 0x10, 0x07, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, + 0x08, 0x2a, 0x30, 0x0a, 0x1b, 0x41, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x44, + 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x07, 0x0a, 0x03, 0x43, 0x50, 0x55, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x43, 0x4f, 0x4e, + 0x4e, 0x10, 0x01, 0x32, 0xdf, 0x04, 0x0a, 0x13, 0x45, 0x6c, 0x61, 0x73, 0x74, 0x69, 0x63, 0x41, + 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x31, 0x0a, 0x07, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0d, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x17, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, + 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0d, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, + 0x0a, 0x53, 0x74, 0x61, 0x74, 0x65, 0x57, 0x61, 0x74, 0x63, 0x68, 0x12, 0x0d, 0x2e, 0x63, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x63, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x30, 0x01, 0x12, 0x31, 0x0a, 0x07, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x0d, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x17, 0x2e, - 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, - 0x0d, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x15, - 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x0a, 0x53, 0x74, 0x61, 0x74, 0x65, 0x57, 0x61, - 0x74, 0x63, 0x68, 0x12, 0x0d, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x74, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x31, 0x0a, 0x07, 0x52, - 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x0d, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x17, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, - 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, - 0x0a, 0x07, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x12, 0x16, 0x2e, 0x63, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x17, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55, 0x70, 0x67, 0x72, 0x61, - 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0f, 0x44, 0x69, - 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x2e, - 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, - 0x63, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, - 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, - 0x63, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, - 0x0a, 0x0f, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x55, 0x6e, 0x69, 0x74, - 0x73, 0x12, 0x1e, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, - 0x6f, 0x73, 0x74, 0x69, 0x63, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1e, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, - 0x6f, 0x73, 0x74, 0x69, 0x63, 0x55, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x30, 0x01, 0x12, 0x62, 0x0a, 0x14, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, - 0x63, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x23, 0x2e, 0x63, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x43, - 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x23, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, - 0x73, 0x74, 0x69, 0x63, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x34, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x75, 0x72, 0x65, 0x12, 0x18, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0d, - 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x29, 0x5a, - 0x24, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x67, - 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x2f, 0x76, 0x32, 0x2f, 0x63, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0xf8, 0x01, 0x01, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x07, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, + 0x65, 0x12, 0x16, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55, 0x70, 0x67, 0x72, 0x61, + 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x63, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0f, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, + 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, + 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, + 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x0f, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, + 0x73, 0x74, 0x69, 0x63, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x12, 0x1e, 0x2e, 0x63, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x55, 0x6e, 0x69, + 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x63, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x55, 0x6e, 0x69, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x62, 0x0a, 0x14, 0x44, + 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, + 0x6e, 0x74, 0x73, 0x12, 0x23, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, + 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x43, 0x6f, 0x6d, 0x70, + 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, + 0x34, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x12, 0x18, 0x2e, 0x63, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0d, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x29, 0x5a, 0x24, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x2f, 0x76, 0x32, 0x2f, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0xf8, 0x01, 0x01, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/pkg/control/v2/server/server.go b/pkg/control/v2/server/server.go index 2ea355ce92e..5ef757b064e 100644 --- a/pkg/control/v2/server/server.go +++ b/pkg/control/v2/server/server.go @@ -378,6 +378,7 @@ func stateToProto(state *coordinator.State, agentInfo info.Agent) (*cproto.State FailedState: string(state.UpgradeDetails.Metadata.FailedState), ErrorMsg: state.UpgradeDetails.Metadata.ErrorMsg, RetryErrorMsg: state.UpgradeDetails.Metadata.RetryErrorMsg, + Reason: state.UpgradeDetails.Metadata.Reason, }, } diff --git a/testing/integration/ess/repackage.go b/testing/integration/ess/repackage.go new file mode 100644 index 00000000000..3e21c36f8f7 --- /dev/null +++ b/testing/integration/ess/repackage.go @@ -0,0 +1,297 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License 2.0; +// you may not use this file except in compliance with the Elastic License 2.0. + +//go:build integration + +package ess + +import ( + "archive/tar" + "archive/zip" + "bytes" + "compress/gzip" + "context" + "errors" + "io" + "os" + "path" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/elastic/elastic-agent/dev-tools/mage" + v1 "github.com/elastic/elastic-agent/pkg/api/v1" + atesting "github.com/elastic/elastic-agent/pkg/testing" + "github.com/elastic/elastic-agent/pkg/version" + agtversion "github.com/elastic/elastic-agent/version" +) + +func repackageArchive(ctx context.Context, t *testing.T, startFixture *atesting.Fixture, newVersionBuildMetadata string, currentVersion *version.ParsedSemVer, newPackageContainingDir string, parsedNewVersion *version.ParsedSemVer) (*version.ParsedSemVer, error) { + err := startFixture.EnsurePrepared(ctx) + require.NoErrorf(t, err, "fixture should be prepared") + + // retrieve the compressed package file location + srcPackage, err := startFixture.SrcPackage(ctx) + require.NoErrorf(t, err, "error retrieving start fixture source package") + + originalPackageFileName := filepath.Base(srcPackage) + + // integration test fixtures and package names treat the version as a string including the "-SNAPSHOT" suffix + // while the repackage functions below separate version from the snapshot flag. + // Normally the early release versions are not snapshots but this test runs on PRs and main branch when we test + // starting from SNAPSHOT packages, so we have to work around the fact that we cannot simply re-generate the packages + // by defining versions in 2 separate ways for repackage hack and for fixtures + buildMetadataForAgentFixture := newVersionBuildMetadata + if currentVersion.IsSnapshot() { + buildMetadataForAgentFixture += "-SNAPSHOT" + } + versionForFixture := version.NewParsedSemVer(currentVersion.Major(), currentVersion.Minor(), currentVersion.Patch(), "", buildMetadataForAgentFixture) + + // calculate the new package name + newPackageFileName := strings.Replace(originalPackageFileName, currentVersion.String(), versionForFixture.String(), 1) + t.Logf("originalPackageName: %q newPackageFileName: %q", originalPackageFileName, newPackageFileName) + + newPackageAbsPath := filepath.Join(newPackageContainingDir, newPackageFileName) + + // hack the package based on type + ext := filepath.Ext(originalPackageFileName) + if ext == ".gz" { + // fetch the next extension + ext = filepath.Ext(strings.TrimRight(originalPackageFileName, ext)) + ext + } + switch ext { + case ".zip": + t.Logf("file %q is a .zip package", originalPackageFileName) + repackageZipArchive(t, srcPackage, newPackageAbsPath, parsedNewVersion) + case ".tar.gz": + t.Logf("file %q is a .tar.gz package", originalPackageFileName) + repackageTarArchive(t, srcPackage, newPackageAbsPath, parsedNewVersion) + default: + t.Logf("unknown extension %q for package file %q ", ext, originalPackageFileName) + t.FailNow() + } + + // Create hash file for the new package + err = mage.CreateSHA512File(newPackageAbsPath) + require.NoErrorf(t, err, "error creating .sha512 for file %q", newPackageAbsPath) + return versionForFixture, err +} + +func repackageTarArchive(t *testing.T, srcPackagePath string, newPackagePath string, newVersion *version.ParsedSemVer) { + oldTopDirectoryName := strings.TrimRight(filepath.Base(srcPackagePath), ".tar.gz") + newTopDirectoryName := strings.TrimRight(filepath.Base(newPackagePath), ".tar.gz") + + // Open the source package and create readers + srcPackageFile, err := os.Open(srcPackagePath) + require.NoErrorf(t, err, "error opening source file %q", srcPackagePath) + defer func(srcPackageFile *os.File) { + err := srcPackageFile.Close() + if err != nil { + assert.Failf(t, "error closing source file %q: %v", srcPackagePath, err) + } + }(srcPackageFile) + + gzReader, err := gzip.NewReader(srcPackageFile) + require.NoErrorf(t, err, "error creating gzip reader for file %q", srcPackagePath) + defer func(gzReader *gzip.Reader) { + err := gzReader.Close() + if err != nil { + assert.Failf(t, "error closing gzip reader for source file %q: %v", srcPackagePath, err) + } + }(gzReader) + + tarReader := tar.NewReader(gzReader) + + // Create the output file and its writers + newPackageFile, err := os.OpenFile(newPackagePath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0o750) + require.NoErrorf(t, err, "error opening output file %q", newPackagePath) + defer func(newPackageFile *os.File) { + err := newPackageFile.Close() + if err != nil { + assert.Failf(t, "error closing output file %q: %v", newPackagePath, err) + } + }(newPackageFile) + + gzWriter := gzip.NewWriter(newPackageFile) + defer func(gzWriter *gzip.Writer) { + err := gzWriter.Close() + if err != nil { + assert.Failf(t, "error closing gzip writer for file %q: %v", newPackagePath, err) + } + }(gzWriter) + + tarWriter := tar.NewWriter(gzWriter) + defer func(tarWriter *tar.Writer) { + err := tarWriter.Close() + if err != nil { + assert.Failf(t, "error closing tar writer for file %q: %v", newPackagePath, err) + } + }(tarWriter) + + hackTarGzPackage(t, tarReader, tarWriter, oldTopDirectoryName, newTopDirectoryName, newVersion) +} + +func hackTarGzPackage(t *testing.T, reader *tar.Reader, writer *tar.Writer, oldTopDirName string, newTopDirName string, newVersion *version.ParsedSemVer) { + + for { + f, err := reader.Next() + if errors.Is(err, io.EOF) { + break + } + require.NoError(t, err, "error reading source package") + + // tar format uses forward slash as path separator, make sure we use only "path" package for checking and manipulation + switch path.Base(f.Name) { + case v1.ManifestFileName: + // read old content and generate the new manifest based on that + newManifest := generateNewManifestContent(t, reader, newVersion) + newManifestBytes := []byte(newManifest) + + // fix file length in header + writeModifiedTarHeader(t, writer, f, oldTopDirName, newTopDirName, int64(len(newManifestBytes))) + + // write the new manifest body + _, err = writer.Write(newManifestBytes) + require.NoError(t, err, "error writing out modified manifest") + + case agtversion.PackageVersionFileName: + + t.Logf("writing new package version: %q", newVersion.String()) + + // new package version file contents + newPackageVersionBytes := []byte(newVersion.String()) + // write new header + writeModifiedTarHeader(t, writer, f, oldTopDirName, newTopDirName, int64(len(newPackageVersionBytes))) + // write content + _, err := writer.Write(newPackageVersionBytes) + require.NoError(t, err, "error writing out modified package version") + default: + // write entry header with the size untouched + writeModifiedTarHeader(t, writer, f, oldTopDirName, newTopDirName, f.Size) + + // copy body + _, err := io.Copy(writer, reader) + require.NoErrorf(t, err, "error writing file content for %+v", f) + } + + } + +} + +func writeModifiedTarHeader(t *testing.T, writer *tar.Writer, header *tar.Header, oldTopDirName, newTopDirName string, size int64) { + // replace top dir in the path + header.Name = strings.Replace(header.Name, oldTopDirName, newTopDirName, 1) + header.Size = size + + err := writer.WriteHeader(header) + require.NoErrorf(t, err, "error writing tar header %+v", header) +} + +func repackageZipArchive(t *testing.T, srcPackagePath string, newPackagePath string, newVersion *version.ParsedSemVer) { + oldTopDirectoryName := strings.TrimRight(filepath.Base(srcPackagePath), ".zip") + newTopDirectoryName := strings.TrimRight(filepath.Base(newPackagePath), ".zip") + + // Open the source package and create readers + zipReader, err := zip.OpenReader(srcPackagePath) + require.NoErrorf(t, err, "error opening source file %q", srcPackagePath) + defer func(zipReader *zip.ReadCloser) { + err := zipReader.Close() + if err != nil { + assert.Failf(t, "error closing source file %q: %v", srcPackagePath, err) + } + }(zipReader) + + // Create the output file and its writers + newPackageFile, err := os.OpenFile(newPackagePath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0o750) + require.NoErrorf(t, err, "error opening output file %q", newPackagePath) + defer func(newPackageFile *os.File) { + err := newPackageFile.Close() + if err != nil { + assert.Failf(t, "error closing output file %q: %v", newPackagePath, err) + } + }(newPackageFile) + + zipWriter := zip.NewWriter(newPackageFile) + defer func(zipWriter *zip.Writer) { + err := zipWriter.Close() + if err != nil { + assert.Failf(t, "error closing zip writer for output file %q: %v", newPackagePath, err) + } + }(zipWriter) + + hackZipPackage(t, zipReader, zipWriter, oldTopDirectoryName, newTopDirectoryName, newVersion) +} + +func hackZipPackage(t *testing.T, reader *zip.ReadCloser, writer *zip.Writer, oldTopDirName string, newTopDirName string, newVersion *version.ParsedSemVer) { + for _, zippedFile := range reader.File { + zippedFileHeader := zippedFile.FileHeader + + // zip format uses forward slash as path separator, make sure we use only "path" package for checking and manipulation + switch path.Base(zippedFile.Name) { + case v1.ManifestFileName: + // read old content + manifestReader, err := zippedFile.Open() + require.NoError(t, err, "error opening manifest file in zipped package") + + // generate new manifest based on the old manifest and the new version + newManifest := generateNewManifestContent(t, manifestReader, newVersion) + + // we need to close the file content reader + err = manifestReader.Close() + require.NoError(t, err, "error closing manifest file in zipped package") + + newManifestBytes := []byte(newManifest) + fileContentWriter := writeModifiedZipFileHeader(t, writer, zippedFileHeader, oldTopDirName, newTopDirName, uint64(len(newManifest))) + + _, err = io.Copy(fileContentWriter, bytes.NewReader(newManifestBytes)) + require.NoError(t, err, "error writing out modified manifest") + + case agtversion.PackageVersionFileName: + t.Logf("writing new package version: %q", newVersion.String()) + // new package version file contents + newPackageVersionBytes := []byte(newVersion.String()) + fileContentWriter := writeModifiedZipFileHeader(t, writer, zippedFileHeader, oldTopDirName, newTopDirName, uint64(len(newPackageVersionBytes))) + + _, err := io.Copy(fileContentWriter, bytes.NewReader(newPackageVersionBytes)) + require.NoError(t, err, "error writing out modified package version") + default: + // write entry header with the size untouched + fileContentWriter := writeModifiedZipFileHeader(t, writer, zippedFileHeader, oldTopDirName, newTopDirName, zippedFile.UncompressedSize64) + fileContentReader, err := zippedFile.Open() + require.NoErrorf(t, err, "error opening zip file content reader for %+v", zippedFileHeader) + // copy body + _, err = io.Copy(fileContentWriter, fileContentReader) + require.NoErrorf(t, err, "error writing file content for %+v", zippedFileHeader) + + // we need to close the file content reader + err = fileContentReader.Close() + require.NoError(t, err, "error closing zipped file writer for %+v", zippedFileHeader) + } + } +} + +func writeModifiedZipFileHeader(t *testing.T, writer *zip.Writer, header zip.FileHeader, oldTopDirName, newTopDirName string, size uint64) io.Writer { + header.Name = strings.Replace(header.Name, oldTopDirName, newTopDirName, 1) + header.UncompressedSize64 = size + fileContentWriter, err := writer.CreateHeader(&header) + require.NoErrorf(t, err, "error creating header for %+v", header) + return fileContentWriter +} + +func generateNewManifestContent(t *testing.T, manifestReader io.Reader, newVersion *version.ParsedSemVer) string { + oldManifest, err := v1.ParseManifest(manifestReader) + require.NoError(t, err, "reading manifest content from tar source archive") + + t.Logf("read old manifest: %+v", oldManifest) + + // replace manifest content + newManifest, err := mage.GeneratePackageManifest("elastic-agent", newVersion.String(), oldManifest.Package.Snapshot, oldManifest.Package.Hash, oldManifest.Package.Hash[:6], oldManifest.Package.Fips, nil) + require.NoErrorf(t, err, "GeneratePackageManifest(%v, %v, %v, %v, %v) failed", newVersion.String(), oldManifest.Package.Snapshot, oldManifest.Package.Hash, oldManifest.Package.Hash[:6], nil) + + t.Logf("generated new manifest:\n%s", newManifest) + return newManifest +} diff --git a/testing/integration/ess/upgrade_fleet_test.go b/testing/integration/ess/upgrade_fleet_test.go index 23dc5d447c5..d77aa72a15a 100644 --- a/testing/integration/ess/upgrade_fleet_test.go +++ b/testing/integration/ess/upgrade_fleet_test.go @@ -24,8 +24,8 @@ import ( "testing" "time" + "github.com/cenkalti/backoff/v5" "github.com/gofrs/uuid/v5" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/elastic/elastic-agent-libs/kibana" @@ -393,7 +393,19 @@ func testUpgradeFleetManagedElasticAgent( unprivileged bool, opts ...upgradetest.UpgradeOpt, ) { + require.NoError(t, PerformManagedUpgrade(ctx, t, info, startFixture, endFixture, policy, unprivileged, opts...)) +} +func PerformManagedUpgrade( + ctx context.Context, + t *testing.T, + info *define.Info, + startFixture *atesting.Fixture, + endFixture *atesting.Fixture, + policy kibana.AgentPolicy, + unprivileged bool, + opts ...upgradetest.UpgradeOpt, +) error { // use the passed in options to perform the upgrade var upgradeOpts upgradetest.UpgradeOpts for _, o := range opts { @@ -403,13 +415,21 @@ func testUpgradeFleetManagedElasticAgent( kibClient := info.KibanaClient startVersionInfo, err := startFixture.ExecVersion(ctx) - require.NoError(t, err) + if err != nil { + return fmt.Errorf("exec version on startFixture: %w", err) + } startParsedVersion, err := version.ParseVersion(startVersionInfo.Binary.String()) - require.NoError(t, err) + if err != nil { + return fmt.Errorf("parsing version on startVersionInfo: %w", err) + } endVersionInfo, err := endFixture.ExecVersion(ctx) - require.NoError(t, err) + if err != nil { + return fmt.Errorf("exec version on endFixture: %w", err) + } endParsedVersion, err := version.ParseVersion(endVersionInfo.Binary.String()) - require.NoError(t, err) + if err != nil { + return fmt.Errorf("parsing version on endVersionInfo: %w", err) + } if unprivileged { if !upgradetest.SupportsUnprivileged(startParsedVersion, endParsedVersion) { @@ -419,24 +439,29 @@ func testUpgradeFleetManagedElasticAgent( if startVersionInfo.Binary.Commit == endVersionInfo.Binary.Commit { t.Skipf("target version has the same commit hash %q", endVersionInfo.Binary.Commit) - return } t.Log("Creating Agent policy...") policyResp, err := kibClient.CreatePolicy(ctx, policy) - require.NoError(t, err, "failed creating policy") - policy = policyResp.AgentPolicy + if err != nil { + return fmt.Errorf("failed creating policy: %w", err) + } + policy = policyResp.AgentPolicy t.Log("Creating Agent enrollment API key...") createEnrollmentApiKeyReq := kibana.CreateEnrollmentAPIKeyRequest{ PolicyID: policyResp.ID, } enrollmentToken, err := kibClient.CreateEnrollmentAPIKey(ctx, createEnrollmentApiKeyReq) - require.NoError(t, err, "failed creating enrollment API key") + if err != nil { + return fmt.Errorf("failed creating enrollment token: %w", err) + } t.Log("Getting default Fleet Server URL...") fleetServerURL, err := fleettools.DefaultURL(ctx, kibClient) - require.NoError(t, err, "failed getting Fleet Server URL") + if err != nil { + return fmt.Errorf("failed getting default Fleet Server URL: %w", err) + } t.Logf("Installing Elastic Agent (unprivileged: %t)...", unprivileged) var nonInteractiveFlag bool @@ -453,78 +478,135 @@ func testUpgradeFleetManagedElasticAgent( Privileged: !unprivileged, } output, err := startFixture.Install(ctx, &installOpts) - require.NoError(t, err, "failed to install start agent [output: %s]", string(output)) + t.Logf("install start agent output:\n%s", string(output)) + if err != nil { + return fmt.Errorf("failed to install start agent: %w", err) + } + + // start fixture gets the agent configured to use a faster watcher + // THIS IS A HACK: we are modifying elastic-agent.yaml after enrollment because the watcher reads only that file to + // configure itself. This is obviously not fit for production code or even guaranteed to be stable. + if upgradeOpts.CustomWatcherCfg != "" { + t.Log("Setting custom watcher config") + err = startFixture.Configure(ctx, []byte("fleet.enabled: true\n"+upgradeOpts.CustomWatcherCfg)) + } t.Log("Waiting for Agent to be correct version and healthy...") err = upgradetest.WaitHealthyAndVersion(ctx, startFixture, startVersionInfo.Binary, 2*time.Minute, 10*time.Second, t) - require.NoError(t, err) + if err != nil { + return fmt.Errorf("waiting for agent to become healthy: %w", err) + } agentID, err := startFixture.AgentID(ctx) - require.NoError(t, err) + if err != nil { + return fmt.Errorf("retrieving agent ID: %w", err) + } t.Logf("Agent ID: %q", agentID) t.Log("Waiting for enrolled Agent status to be online...") - require.Eventually(t, - check.FleetAgentStatus( - ctx, t, kibClient, agentID, "online"), - 2*time.Minute, - 10*time.Second, - "Agent status is not online") + _, err = backoff.Retry(ctx, func() (bool, error) { + checkSuccessful := check.FleetAgentStatus( + ctx, t, kibClient, agentID, "online")() + if !checkSuccessful { + return checkSuccessful, fmt.Errorf("agent status is not online") + } + return checkSuccessful, nil + }, backoff.WithMaxElapsedTime(2*time.Minute), backoff.WithBackOff(backoff.NewConstantBackOff(10*time.Second))) + if err != nil { + return fmt.Errorf("waiting for upgraded agent to be online: %w", err) + } t.Logf("Upgrading from version \"%s-%s\" to version \"%s-%s\"...", startParsedVersion, startVersionInfo.Binary.Commit, endVersionInfo.Binary.String(), endVersionInfo.Binary.Commit) err = fleettools.UpgradeAgent(ctx, kibClient, agentID, endVersionInfo.Binary.String(), true) - require.NoError(t, err) + if err != nil { + return fmt.Errorf("requesting agent upgrade: %w", err) + } t.Log("Waiting from upgrade details to show up in Fleet") - var agent kibana.GetAgentResponse - require.Eventuallyf(t, func() bool { - agent, err = kibClient.GetAgent(ctx, kibana.GetAgentRequest{ID: agentID}) - return err == nil && agent.UpgradeDetails != nil - }, - 5*time.Minute, time.Second, - "last error: %v. agent.UpgradeDetails: %s", - err, agentUpgradeDetailsString(agent)) + _, err = backoff.Retry[kibana.GetAgentResponse](ctx, func() (kibana.GetAgentResponse, error) { + agent, getAgentErr := kibClient.GetAgent(ctx, kibana.GetAgentRequest{ID: agentID}) + if getAgentErr != nil { + return agent, getAgentErr + } + if agent.UpgradeDetails == nil { + return agent, fmt.Errorf("agent upgrade details is empty") + } + return agent, nil + }, backoff.WithMaxElapsedTime(5*time.Minute), backoff.WithBackOff(backoff.NewConstantBackOff(time.Second))) + if err != nil { + return fmt.Errorf("waiting for upgrade details to show up in Fleet: %w", err) + } // wait for the watcher to show up t.Logf("Waiting for upgrade watcher to start...") err = upgradetest.WaitForWatcher(ctx, 5*time.Minute, 10*time.Second) - require.NoError(t, err, "upgrade watcher did not start") + if err != nil { + return fmt.Errorf("waiting for upgrade watcher to start: %w", err) + } t.Logf("Upgrade watcher started") + if upgradeOpts.PostUpgradeHook != nil { + if err := upgradeOpts.PostUpgradeHook(); err != nil { + return fmt.Errorf("post upgrade hook failed: %w", err) + } + } + // wait for the agent to be healthy and correct version err = upgradetest.WaitHealthyAndVersion(ctx, startFixture, endVersionInfo.Binary, 2*time.Minute, 10*time.Second, t) - require.NoError(t, err) + if err != nil { + return fmt.Errorf("waiting for agent to be healthy and version %s: %w", endVersionInfo.Binary.String(), err) + } - t.Log("Waiting for enrolled Agent status to be online...") - require.Eventually(t, check.FleetAgentStatus(ctx, t, kibClient, agentID, "online"), 10*time.Minute, 15*time.Second, "Agent status is not online") + t.Log("Waiting for upgraded Agent status to be online...") + _, err = backoff.Retry(ctx, func() (any, error) { + checkSuccessful := check.FleetAgentStatus(ctx, t, kibClient, agentID, "online")() + if !checkSuccessful { + return checkSuccessful, fmt.Errorf("agent status is not online") + } + return checkSuccessful, nil + }, backoff.WithMaxElapsedTime(10*time.Minute), backoff.WithBackOff(backoff.NewConstantBackOff(15*time.Second))) + + if err != nil { + return fmt.Errorf("waiting for upgraded agent to be online: %w", err) + } // wait for version - require.Eventually(t, func() bool { + _, err = backoff.Retry(ctx, func() (string, error) { t.Log("Getting Agent version...") newVersion, err := fleettools.GetAgentVersion(ctx, kibClient, agentID) if err != nil { t.Logf("error getting agent version: %v", err) - return false + return "", fmt.Errorf("getting agent information: %w", err) } - return endVersionInfo.Binary.Version == newVersion - }, 5*time.Minute, time.Second) + if endVersionInfo.Binary.Version != newVersion { + return newVersion, fmt.Errorf("agent version mismatch: got %s, want %s", newVersion, endVersionInfo.Binary.Version) + } + return newVersion, nil + }, backoff.WithMaxElapsedTime(5*time.Minute), backoff.WithBackOff(backoff.NewConstantBackOff(time.Second))) t.Logf("Waiting for upgrade watcher to finish...") err = upgradetest.WaitForNoWatcher(ctx, 2*time.Minute, 10*time.Second, 1*time.Minute+15*time.Second) - require.NoError(t, err) + if err != nil { + return fmt.Errorf("waiting for upgrade watcher to finish: %w", err) + } t.Logf("Upgrade watcher finished") // now that the watcher has stopped lets ensure that it's still the expected // version, otherwise it's possible that it was rolled back to the original version err = upgradetest.CheckHealthyAndVersion(ctx, startFixture, endVersionInfo.Binary) - assert.NoError(t, err) + if err != nil { + return fmt.Errorf("checking agent has not been rolled back: %w", err) + } if upgradeOpts.PostWatcherSuccessHook != nil { err = upgradeOpts.PostWatcherSuccessHook(ctx, startFixture) - require.NoError(t, err) + if err != nil { + return fmt.Errorf("PostWatcherSuccessHook failed: %w", err) + } } + return nil } func defaultPolicy() kibana.AgentPolicy { diff --git a/testing/integration/ess/upgrade_rollback_test.go b/testing/integration/ess/upgrade_rollback_test.go index 359d056a8ae..26ce4aa475b 100644 --- a/testing/integration/ess/upgrade_rollback_test.go +++ b/testing/integration/ess/upgrade_rollback_test.go @@ -15,10 +15,12 @@ import ( "testing" "time" + "github.com/gofrs/uuid/v5" "github.com/kardianos/service" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/elastic/elastic-agent-libs/kibana" "github.com/elastic/elastic-agent/internal/pkg/agent/application/paths" "github.com/elastic/elastic-agent/internal/pkg/agent/application/upgrade/details" "github.com/elastic/elastic-agent/internal/pkg/agent/install" @@ -33,7 +35,7 @@ import ( const reallyFastWatcherCfg = ` agent.upgrade.watcher: - grace_period: 1m + grace_period: 2m error_check.interval: 5s ` @@ -140,7 +142,11 @@ inputs: if state.UpgradeDetails == nil { t.Fatal("upgrade details in the state cannot be nil") } - require.Equal(t, details.StateRollback, details.State(state.UpgradeDetails.State)) + + assert.Equal(t, details.StateRollback, details.State(state.UpgradeDetails.State)) + if !startVersion.Less(*upgradetest.Version_9_1_0_SNAPSHOT) { + assert.Equal(t, details.ReasonWatchFailed, state.UpgradeDetails.Metadata.Reason) + } } else { t.Logf("rollback finished, status is '%s', cannot check UpgradeDetails", state.State.String()) } @@ -172,27 +178,241 @@ func TestStandaloneUpgradeRollbackOnRestarts(t *testing.T) { t.Skip("This test is flaky on windows. See https://github.com/elastic/elastic-agent/issues/6733") } - ctx, cancel := testcontext.WithDeadline(t, context.Background(), time.Now().Add(10*time.Minute)) - defer cancel() + type fixturesSetupFunc func(t *testing.T) (from *atesting.Fixture, to *atesting.Fixture) + testcases := []struct { + name string + fixturesSetup fixturesSetupFunc + }{ + { + name: "upgrade from previous minor to current version", + fixturesSetup: func(t *testing.T) (from *atesting.Fixture, to *atesting.Fixture) { + // Upgrade from an old build because the new watcher from the new build will + // be ran. Otherwise the test will run the old watcher from the old build. + upgradeFromVersion, err := upgradetest.PreviousMinor() + require.NoError(t, err) + startFixture, err := atesting.NewFixture( + t, + upgradeFromVersion.String(), + atesting.WithFetcher(atesting.ArtifactFetcher()), + ) + require.NoError(t, err) + + // Upgrade to the build under test. + endFixture, err := define.NewFixtureFromLocalBuild(t, define.Version()) + require.NoError(t, err) + return startFixture, endFixture + }, + }, + { + name: "downgrade from current version to previous minor", + fixturesSetup: func(t *testing.T) (from *atesting.Fixture, to *atesting.Fixture) { + // Upgrade from the current build to an older one. The new watcher will be run anyway, and we can check + // the postconditions on a rollback + + // Start from the build under test. + fromFixture, err := define.NewFixtureFromLocalBuild(t, define.Version()) + require.NoError(t, err) + + // Downgrade to a previous version (doesn't really matter what) + upgradeToVersion, err := upgradetest.PreviousMinor() + require.NoError(t, err) + toFixture, err := atesting.NewFixture( + t, + upgradeToVersion.String(), + atesting.WithFetcher(atesting.ArtifactFetcher()), + ) + require.NoError(t, err) + + return fromFixture, toFixture + }, + }, + { + name: "upgrade to a repackaged agent built from the same commit", + fixturesSetup: func(t *testing.T) (from *atesting.Fixture, to *atesting.Fixture) { + // Upgrade from the current build to the same build as Independent Agent Release. + + // Start from the build under test. + fromFixture, err := define.NewFixtureFromLocalBuild(t, define.Version()) + require.NoError(t, err) + + // Create a new package with a different version (IAR-style) + newPackageContainingDir := t.TempDir() + + // modify the version with the "+buildYYYYMMDDHHMMSS" + currentVersion, err := version.ParseVersion(define.Version()) + require.NoErrorf(t, err, "define.Version() %q is not parsable.", define.Version()) + + newVersionBuildMetadata := "build" + time.Now().Format("20060102150405") + parsedNewVersion := version.NewParsedSemVer(currentVersion.Major(), currentVersion.Minor(), currentVersion.Patch(), "", newVersionBuildMetadata) + + versionForFixture, err := repackageArchive(t.Context(), t, fromFixture, newVersionBuildMetadata, currentVersion, newPackageContainingDir, parsedNewVersion) + require.NoError(t, err, "error repackaging the archive built from the same commit") + + // I wish I could just pass the location of the package on disk to the whole upgrade tests/fixture/fetcher code + // but I would have to break too much code for that, when in Rome... add more code on top of inflexible code + repackagedLocalFetcher := atesting.LocalFetcher(newPackageContainingDir) + toFixture, err := atesting.NewFixture( + t, + versionForFixture.String(), + atesting.WithFetcher(repackagedLocalFetcher), + ) + require.NoError(t, err) + + return fromFixture, toFixture + }, + }, + } - // Upgrade from an old build because the new watcher from the new build will - // be ran. Otherwise the test will run the old watcher from the old build. - upgradeFromVersion, err := upgradetest.PreviousMinor() - require.NoError(t, err) - startFixture, err := atesting.NewFixture( - t, - upgradeFromVersion.String(), - atesting.WithFetcher(atesting.ArtifactFetcher()), + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + ctx, cancel := testcontext.WithDeadline(t, t.Context(), time.Now().Add(10*time.Minute)) + defer cancel() + from, to := tc.fixturesSetup(t) + + standaloneRollbackRestartTest(ctx, t, from, to) + }) + } + +} + +// TestFleetManagedUpgradeRollbackOnRestarts tests the scenario where upgrading to a new version +// of Agent fails due to the new Agent binary not starting up. It checks that the Agent is +// rolled back to the previous version and that Fleet reports the correct informations +func TestFleetManagedUpgradeRollbackOnRestarts(t *testing.T) { + info := define.Require(t, define.Requirements{ + Group: integration.Fleet, + Local: false, // requires Agent installation + Sudo: true, // requires Agent installation + Stack: &define.Stack{}, + }) + + type fixturesSetupFunc func(t *testing.T) (from *atesting.Fixture, to *atesting.Fixture) + testcases := []struct { + name string + fixturesSetup fixturesSetupFunc + }{ + { + name: "downgrade from current version to previous minor", + fixturesSetup: func(t *testing.T) (from *atesting.Fixture, to *atesting.Fixture) { + // Upgrade from the current build to an older one. The new watcher will be run anyway, and we can check + // the postconditions on a rollback + + // Start from the build under test. + fromFixture, err := define.NewFixtureFromLocalBuild(t, define.Version()) + require.NoError(t, err) + + // Downgrade to a previous version (doesn't really matter what) + upgradeToVersion, err := upgradetest.PreviousMinor() + require.NoError(t, err) + toFixture, err := atesting.NewFixture( + t, + upgradeToVersion.String(), + atesting.WithFetcher(atesting.ArtifactFetcher()), + ) + require.NoError(t, err) + + return fromFixture, toFixture + }, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + ctx, cancel := testcontext.WithDeadline(t, t.Context(), time.Now().Add(10*time.Minute)) + defer cancel() + from, to := tc.fixturesSetup(t) + + managedRollbackRestartTest(ctx, t, info, from, to) + }) + } +} + +func managedRollbackRestartTest(ctx context.Context, t *testing.T, info *define.Info, from *atesting.Fixture, to *atesting.Fixture) { + + startVersionInfo, err := from.ExecVersion(ctx) + require.NoError(t, err, "failed to get start agent build version info") + + endVersionInfo, err := to.ExecVersion(ctx) + require.NoError(t, err, "failed to get end agent build version info") + + t.Logf("Testing Elastic Agent upgrade from %s to %s...", from.Version(), endVersionInfo.Binary.String()) + + policyUUID := uuid.Must(uuid.NewV4()).String() + + policy := kibana.AgentPolicy{ + Name: fmt.Sprintf("%s-policy-%s", t.Name(), policyUUID), + Namespace: "default", + Description: fmt.Sprintf("Rollback test policy %s (%s)", t.Name(), policyUUID), + MonitoringEnabled: []kibana.MonitoringEnabledOption{ + kibana.MonitoringEnabledLogs, + kibana.MonitoringEnabledMetrics, + }, + } + + // Use the post-upgrade hook to skip part of the PerformUpgrade (the checks during the grace period) + // because we want to do our own checks for the rollback. + var ErrSkipGrace = errors.New("skip grace period") + postUpgradeHook := func() error { + return ErrSkipGrace + } + + err = PerformManagedUpgrade(ctx, t, info, from, to, policy, false, + upgradetest.WithPostUpgradeHook(postUpgradeHook), + upgradetest.WithDisableHashCheck(true), + upgradetest.WithCustomWatcherConfig(reallyFastWatcherCfg), ) + + // we expect ErrSkipGrace at this point, meaning that we finished installing but didn't wait for agent to become healthy + require.ErrorIs(t, err, ErrSkipGrace, "managed upgrade failed with unexpected error") + + // A few seconds after the upgrade, deliberately restart upgraded Agent a + // couple of times to simulate Agent crashing. + restartAgentNTimes(t, 3, 10*time.Second) + + // wait for the agent to be healthy and correct version + err = upgradetest.WaitHealthyAndVersion(ctx, from, startVersionInfo.Binary, 2*time.Minute, 10*time.Second, t) + require.NoError(t, err, "agent never came online with version %s", startVersionInfo.Binary.String()) + + agentID, err := from.AgentID(ctx) + require.NoError(t, err, "error retrieving agent ID") + + // ensure that upgrade details now show the state as UPG_ROLLBACK. This is only possible with Elastic + // Agent versions >= 8.12.0. + startVersion, err := version.ParseVersion(startVersionInfo.Binary.Version) require.NoError(t, err) + + if !startVersion.Less(*version.NewParsedSemVer(8, 12, 0, "", "")) { + fleetAgent, fleetAgentErr := info.KibanaClient.GetAgent(ctx, kibana.GetAgentRequest{ID: agentID}) + require.NoError(t, fleetAgentErr, "error getting agent from Fleet") + require.NotNil(t, fleetAgent.UpgradeDetails, "upgrade details not set") + assert.Equal(t, details.StateRollback, details.State(fleetAgent.UpgradeDetails.State)) + if !startVersion.Less(*upgradetest.Version_9_1_0_SNAPSHOT) { + assert.Equal(t, details.ReasonWatchFailed, fleetAgent.UpgradeDetails.Metadata.Reason) + } + } + + // rollback should stop the watcher + // killTimeout is greater than timeout as the watcher should have been + // stopped on its own, and we don't want this test to hide that fact + err = upgradetest.WaitForNoWatcher(ctx, 2*time.Minute, 10*time.Second, 3*time.Minute) + require.NoError(t, err) + + // now that the watcher has stopped lets ensure that it's still the expected + // version, otherwise it's possible that it was rolled back to the original version + err = upgradetest.CheckHealthyAndVersion(ctx, from, startVersionInfo.Binary) + assert.NoError(t, err) + +} + +func standaloneRollbackRestartTest(ctx context.Context, t *testing.T, startFixture *atesting.Fixture, endFixture *atesting.Fixture) { + startVersionInfo, err := startFixture.ExecVersion(ctx) require.NoError(t, err, "failed to get start agent build version info") - // Upgrade to the build under test. - endFixture, err := define.NewFixtureFromLocalBuild(t, define.Version()) - require.NoError(t, err) + endVersionInfo, err := startFixture.ExecVersion(ctx) + require.NoError(t, err, "failed to get end agent build version info") - t.Logf("Testing Elastic Agent upgrade from %s to %s...", upgradeFromVersion, define.Version()) + t.Logf("Testing Elastic Agent upgrade from %s to %s...", startFixture.Version(), endVersionInfo.Binary.String()) // Use the post-upgrade hook to bypass the remainder of the PerformUpgrade // because we want to do our own checks for the rollback. @@ -204,57 +424,18 @@ func TestStandaloneUpgradeRollbackOnRestarts(t *testing.T) { err = upgradetest.PerformUpgrade( ctx, startFixture, endFixture, t, upgradetest.WithPostUpgradeHook(postUpgradeHook), - upgradetest.WithCustomWatcherConfig(reallyFastWatcherCfg)) + upgradetest.WithCustomWatcherConfig(reallyFastWatcherCfg), + upgradetest.WithDisableHashCheck(true)) if !errors.Is(err, ErrPostExit) { require.NoError(t, err) } // A few seconds after the upgrade, deliberately restart upgraded Agent a // couple of times to simulate Agent crashing. - for restartIdx := 0; restartIdx < 3; restartIdx++ { - time.Sleep(10 * time.Second) - topPath := paths.Top() - - t.Logf("Stopping agent via service to simulate crashing") - err = install.StopService(topPath, install.DefaultStopTimeout, install.DefaultStopInterval) - if err != nil && runtime.GOOS == define.Windows && strings.Contains(err.Error(), "The service has not been started.") { - // Due to the quick restarts every 10 seconds its possible that this is faster than Windows - // can handle. Decrementing restartIdx means that the loop will occur again. - t.Logf("Got an allowed error on Windows: %s", err) - err = nil - } - require.NoError(t, err) - - // ensure that it's stopped before starting it again - var status service.Status - var statusErr error - require.Eventuallyf(t, func() bool { - status, statusErr = install.StatusService(topPath) - if statusErr != nil { - return false - } - return status != service.StatusRunning - }, 2*time.Minute, 1*time.Second, "service never fully stopped (status: %v): %s", status, statusErr) - t.Logf("Stopped agent via service to simulate crashing") - - // start it again - t.Logf("Starting agent via service to simulate crashing") - err = install.StartService(topPath) - require.NoError(t, err) - - // ensure that it's started before next loop - require.Eventuallyf(t, func() bool { - status, statusErr = install.StatusService(topPath) - if statusErr != nil { - return false - } - return status == service.StatusRunning - }, 2*time.Minute, 1*time.Second, "service never fully started (status: %v): %s", status, statusErr) - t.Logf("Started agent via service to simulate crashing") - } + restartAgentNTimes(t, 3, 10*time.Second) // wait for the agent to be healthy and back at the start version - err = upgradetest.WaitHealthyAndVersion(ctx, startFixture, startVersionInfo.Binary, 10*time.Minute, 10*time.Second, t) + err = upgradetest.WaitHealthyAndVersion(ctx, startFixture, startVersionInfo.Binary, 2*time.Minute, 10*time.Second, t) if err != nil { // agent never got healthy, but we need to ensure the watcher is stopped before continuing // this kills the watcher instantly and waits for it to be gone before continuing @@ -279,7 +460,10 @@ func TestStandaloneUpgradeRollbackOnRestarts(t *testing.T) { require.NoError(t, err) require.NotNil(t, state.UpgradeDetails) - require.Equal(t, details.StateRollback, details.State(state.UpgradeDetails.State)) + assert.Equal(t, details.StateRollback, details.State(state.UpgradeDetails.State)) + if !startVersion.Less(*upgradetest.Version_9_1_0_SNAPSHOT) { + assert.Equal(t, details.ReasonWatchFailed, state.UpgradeDetails.Metadata.Reason) + } } // rollback should stop the watcher @@ -293,3 +477,48 @@ func TestStandaloneUpgradeRollbackOnRestarts(t *testing.T) { err = upgradetest.CheckHealthyAndVersion(ctx, startFixture, startVersionInfo.Binary) assert.NoError(t, err) } + +func restartAgentNTimes(t *testing.T, noOfRestarts int, sleepBetweenIterations time.Duration) { + topPath := paths.Top() + + for restartIdx := 0; restartIdx < noOfRestarts; restartIdx++ { + time.Sleep(sleepBetweenIterations) + + t.Logf("Stopping agent via service to simulate crashing") + err := install.StopService(topPath, install.DefaultStopTimeout, install.DefaultStopInterval) + if err != nil && runtime.GOOS == define.Windows && strings.Contains(err.Error(), "The service has not been started.") { + // Due to the quick restarts every 10 seconds its possible that this is faster than Windows + // can handle. Decrementing restartIdx means that the loop will occur again. + t.Logf("Got an allowed error on Windows: %s", err) + err = nil + } + require.NoError(t, err) + + // ensure that it's stopped before starting it again + var status service.Status + var statusErr error + require.Eventuallyf(t, func() bool { + status, statusErr = install.StatusService(topPath) + if statusErr != nil { + return false + } + return status != service.StatusRunning + }, 5*time.Minute, 1*time.Second, "service never fully stopped (status: %v): %s", status, statusErr) + t.Logf("Stopped agent via service to simulate crashing") + + // start it again + t.Logf("Starting agent via service to simulate crashing") + err = install.StartService(topPath) + require.NoError(t, err) + + // ensure that it's started before next loop + require.Eventuallyf(t, func() bool { + status, statusErr = install.StatusService(topPath) + if statusErr != nil { + return false + } + return status == service.StatusRunning + }, 5*time.Minute, 1*time.Second, "service never fully started (status: %v): %s", status, statusErr) + t.Logf("Started agent via service to simulate crashing") + } +} diff --git a/testing/integration/ess/upgrade_standalone_same_commit_test.go b/testing/integration/ess/upgrade_standalone_same_commit_test.go index b470a9c120b..48e6e5993f6 100644 --- a/testing/integration/ess/upgrade_standalone_same_commit_test.go +++ b/testing/integration/ess/upgrade_standalone_same_commit_test.go @@ -7,33 +7,20 @@ package ess import ( - "archive/tar" - "archive/zip" - "bytes" - "compress/gzip" "context" - "errors" "fmt" - "io" - "os" - "path" - "path/filepath" - "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/elastic/elastic-agent/dev-tools/mage" - v1 "github.com/elastic/elastic-agent/pkg/api/v1" atesting "github.com/elastic/elastic-agent/pkg/testing" "github.com/elastic/elastic-agent/pkg/testing/define" "github.com/elastic/elastic-agent/pkg/testing/tools/testcontext" "github.com/elastic/elastic-agent/pkg/version" "github.com/elastic/elastic-agent/testing/integration" "github.com/elastic/elastic-agent/testing/upgradetest" - agtversion "github.com/elastic/elastic-agent/version" ) func TestStandaloneUpgradeSameCommit(t *testing.T) { @@ -44,8 +31,8 @@ func TestStandaloneUpgradeSameCommit(t *testing.T) { }) // parse the version we are testing - currentVersion, err := version.ParseVersion(define.Version()) - require.NoError(t, err) + currentVersion, parseVersionErr := version.ParseVersion(define.Version()) + require.NoError(t, parseVersionErr) // 8.13.0-SNAPSHOT is the minimum version we need for testing upgrading with the same hash if currentVersion.Less(*upgradetest.Version_8_13_0_SNAPSHOT) { @@ -82,7 +69,7 @@ func TestStandaloneUpgradeSameCommit(t *testing.T) { }) t.Run(fmt.Sprintf("Upgrade on a repackaged version of agent %s (%s)", currentVersion, unPrivilegedString), func(t *testing.T) { - ctx, cancel := testcontext.WithDeadline(t, context.Background(), time.Now().Add(10*time.Minute)) + ctx, cancel := context.WithDeadline(t.Context(), time.Now().Add(10*time.Minute)) defer cancel() startFixture, err := define.NewFixtureFromLocalBuild( @@ -95,54 +82,9 @@ func TestStandaloneUpgradeSameCommit(t *testing.T) { newVersionBuildMetadata := "build" + time.Now().Format("20060102150405") parsedNewVersion := version.NewParsedSemVer(currentVersion.Major(), currentVersion.Minor(), currentVersion.Patch(), "", newVersionBuildMetadata) - err = startFixture.EnsurePrepared(ctx) - require.NoErrorf(t, err, "fixture should be prepared") - - // retrieve the compressed package file location - srcPackage, err := startFixture.SrcPackage(ctx) - require.NoErrorf(t, err, "error retrieving start fixture source package") - - originalPackageFileName := filepath.Base(srcPackage) - - // integration test fixtures and package names treat the version as a string including the "-SNAPSHOT" suffix - // while the repackage functions below separate version from the snapshot flag. - // Normally the early release versions are not snapshots but this test runs on PRs and main branch when we test - // starting from SNAPSHOT packages, so we have to work around the fact that we cannot simply re-generate the packages - // by defining versions in 2 separate ways for repackage hack and for fixtures - buildMetadataForAgentFixture := newVersionBuildMetadata - if currentVersion.IsSnapshot() { - buildMetadataForAgentFixture += "-SNAPSHOT" - } - versionForFixture := version.NewParsedSemVer(currentVersion.Major(), currentVersion.Minor(), currentVersion.Patch(), "", buildMetadataForAgentFixture) - - // calculate the new package name - newPackageFileName := strings.Replace(originalPackageFileName, currentVersion.String(), versionForFixture.String(), 1) - t.Logf("originalPackageName: %q newPackageFileName: %q", originalPackageFileName, newPackageFileName) - newPackageContainingDir := t.TempDir() - newPackageAbsPath := filepath.Join(newPackageContainingDir, newPackageFileName) - - // hack the package based on type - ext := filepath.Ext(originalPackageFileName) - if ext == ".gz" { - // fetch the next extension - ext = filepath.Ext(strings.TrimRight(originalPackageFileName, ext)) + ext - } - switch ext { - case ".zip": - t.Logf("file %q is a .zip package", originalPackageFileName) - repackageZipArchive(t, srcPackage, newPackageAbsPath, parsedNewVersion) - case ".tar.gz": - t.Logf("file %q is a .tar.gz package", originalPackageFileName) - repackageTarArchive(t, srcPackage, newPackageAbsPath, parsedNewVersion) - default: - t.Logf("unknown extension %q for package file %q ", ext, originalPackageFileName) - t.FailNow() - } - // Create hash file for the new package - err = mage.CreateSHA512File(newPackageAbsPath) - require.NoErrorf(t, err, "error creating .sha512 for file %q", newPackageAbsPath) + versionForFixture, err := repackageArchive(ctx, t, startFixture, newVersionBuildMetadata, currentVersion, newPackageContainingDir, parsedNewVersion) // I wish I could just pass the location of the package on disk to the whole upgrade tests/fixture/fetcher code // but I would have to break too much code for that, when in Rome... add more code on top of inflexible code @@ -160,218 +102,3 @@ func TestStandaloneUpgradeSameCommit(t *testing.T) { }) } - -func repackageTarArchive(t *testing.T, srcPackagePath string, newPackagePath string, newVersion *version.ParsedSemVer) { - oldTopDirectoryName := strings.TrimRight(filepath.Base(srcPackagePath), ".tar.gz") - newTopDirectoryName := strings.TrimRight(filepath.Base(newPackagePath), ".tar.gz") - - // Open the source package and create readers - srcPackageFile, err := os.Open(srcPackagePath) - require.NoErrorf(t, err, "error opening source file %q", srcPackagePath) - defer func(srcPackageFile *os.File) { - err := srcPackageFile.Close() - if err != nil { - assert.Failf(t, "error closing source file %q: %v", srcPackagePath, err) - } - }(srcPackageFile) - - gzReader, err := gzip.NewReader(srcPackageFile) - require.NoErrorf(t, err, "error creating gzip reader for file %q", srcPackagePath) - defer func(gzReader *gzip.Reader) { - err := gzReader.Close() - if err != nil { - assert.Failf(t, "error closing gzip reader for source file %q: %v", srcPackagePath, err) - } - }(gzReader) - - tarReader := tar.NewReader(gzReader) - - // Create the output file and its writers - newPackageFile, err := os.OpenFile(newPackagePath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0o750) - require.NoErrorf(t, err, "error opening output file %q", newPackageFile) - defer func(newPackageFile *os.File) { - err := newPackageFile.Close() - if err != nil { - assert.Failf(t, "error closing output file %q: %v", newPackagePath, err) - } - }(newPackageFile) - - gzWriter := gzip.NewWriter(newPackageFile) - defer func(gzWriter *gzip.Writer) { - err := gzWriter.Close() - if err != nil { - assert.Failf(t, "error closing gzip writer for file %q: %v", newPackagePath, err) - } - }(gzWriter) - - tarWriter := tar.NewWriter(gzWriter) - defer func(tarWriter *tar.Writer) { - err := tarWriter.Close() - if err != nil { - assert.Failf(t, "error closing tar writer for file %q: %v", newPackagePath, err) - } - }(tarWriter) - - hackTarGzPackage(t, tarReader, tarWriter, oldTopDirectoryName, newTopDirectoryName, newVersion) -} - -func hackTarGzPackage(t *testing.T, reader *tar.Reader, writer *tar.Writer, oldTopDirName string, newTopDirName string, newVersion *version.ParsedSemVer) { - - for { - f, err := reader.Next() - if errors.Is(err, io.EOF) { - break - } - require.NoError(t, err, "error reading source package") - - // tar format uses forward slash as path separator, make sure we use only "path" package for checking and manipulation - switch path.Base(f.Name) { - case v1.ManifestFileName: - // read old content and generate the new manifest based on that - newManifest := generateNewManifestContent(t, reader, newVersion) - newManifestBytes := []byte(newManifest) - - // fix file length in header - writeModifiedTarHeader(t, writer, f, oldTopDirName, newTopDirName, int64(len(newManifestBytes))) - - // write the new manifest body - _, err = writer.Write(newManifestBytes) - require.NoError(t, err, "error writing out modified manifest") - - case agtversion.PackageVersionFileName: - - t.Logf("writing new package version: %q", newVersion.String()) - - // new package version file contents - newPackageVersionBytes := []byte(newVersion.String()) - // write new header - writeModifiedTarHeader(t, writer, f, oldTopDirName, newTopDirName, int64(len(newPackageVersionBytes))) - // write content - _, err := writer.Write(newPackageVersionBytes) - require.NoError(t, err, "error writing out modified package version") - default: - // write entry header with the size untouched - writeModifiedTarHeader(t, writer, f, oldTopDirName, newTopDirName, f.Size) - - // copy body - _, err := io.Copy(writer, reader) - require.NoErrorf(t, err, "error writing file content for %+v", f) - } - - } - -} - -func writeModifiedTarHeader(t *testing.T, writer *tar.Writer, header *tar.Header, oldTopDirName, newTopDirName string, size int64) { - // replace top dir in the path - header.Name = strings.Replace(header.Name, oldTopDirName, newTopDirName, 1) - header.Size = size - - err := writer.WriteHeader(header) - require.NoErrorf(t, err, "error writing tar header %+v", header) -} - -func repackageZipArchive(t *testing.T, srcPackagePath string, newPackagePath string, newVersion *version.ParsedSemVer) { - oldTopDirectoryName := strings.TrimRight(filepath.Base(srcPackagePath), ".zip") - newTopDirectoryName := strings.TrimRight(filepath.Base(newPackagePath), ".zip") - - // Open the source package and create readers - zipReader, err := zip.OpenReader(srcPackagePath) - require.NoErrorf(t, err, "error opening source file %q", srcPackagePath) - defer func(zipReader *zip.ReadCloser) { - err := zipReader.Close() - if err != nil { - assert.Failf(t, "error closing source file %q: %v", srcPackagePath, err) - } - }(zipReader) - - // Create the output file and its writers - newPackageFile, err := os.OpenFile(newPackagePath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0o750) - require.NoErrorf(t, err, "error opening output file %q", newPackageFile) - defer func(newPackageFile *os.File) { - err := newPackageFile.Close() - if err != nil { - assert.Failf(t, "error closing output file %q: %v", newPackagePath, err) - } - }(newPackageFile) - - zipWriter := zip.NewWriter(newPackageFile) - defer func(zipWriter *zip.Writer) { - err := zipWriter.Close() - if err != nil { - assert.Failf(t, "error closing zip writer for output file %q: %v", newPackagePath, err) - } - }(zipWriter) - - hackZipPackage(t, zipReader, zipWriter, oldTopDirectoryName, newTopDirectoryName, newVersion) -} - -func hackZipPackage(t *testing.T, reader *zip.ReadCloser, writer *zip.Writer, oldTopDirName string, newTopDirName string, newVersion *version.ParsedSemVer) { - for _, zippedFile := range reader.File { - zippedFileHeader := zippedFile.FileHeader - - // zip format uses forward slash as path separator, make sure we use only "path" package for checking and manipulation - switch path.Base(zippedFile.Name) { - case v1.ManifestFileName: - // read old content - manifestReader, err := zippedFile.Open() - require.NoError(t, err, "error opening manifest file in zipped package") - - // generate new manifest based on the old manifest and the new version - newManifest := generateNewManifestContent(t, manifestReader, newVersion) - - // we need to close the file content reader - err = manifestReader.Close() - require.NoError(t, err, "error closing manifest file in zipped package") - - newManifestBytes := []byte(newManifest) - fileContentWriter := writeModifiedZipFileHeader(t, writer, zippedFileHeader, oldTopDirName, newTopDirName, uint64(len(newManifest))) - - _, err = io.Copy(fileContentWriter, bytes.NewReader(newManifestBytes)) - require.NoError(t, err, "error writing out modified manifest") - - case agtversion.PackageVersionFileName: - t.Logf("writing new package version: %q", newVersion.String()) - // new package version file contents - newPackageVersionBytes := []byte(newVersion.String()) - fileContentWriter := writeModifiedZipFileHeader(t, writer, zippedFileHeader, oldTopDirName, newTopDirName, uint64(len(newPackageVersionBytes))) - - _, err := io.Copy(fileContentWriter, bytes.NewReader(newPackageVersionBytes)) - require.NoError(t, err, "error writing out modified package version") - default: - // write entry header with the size untouched - fileContentWriter := writeModifiedZipFileHeader(t, writer, zippedFileHeader, oldTopDirName, newTopDirName, zippedFile.UncompressedSize64) - fileContentReader, err := zippedFile.Open() - require.NoErrorf(t, err, "error opening zip file content reader for %+v", zippedFileHeader) - // copy body - _, err = io.Copy(fileContentWriter, fileContentReader) - require.NoErrorf(t, err, "error writing file content for %+v", zippedFileHeader) - - // we need to close the file content reader - err = fileContentReader.Close() - require.NoError(t, err, "error closing zipped file writer for %+v", zippedFileHeader) - } - } -} - -func writeModifiedZipFileHeader(t *testing.T, writer *zip.Writer, header zip.FileHeader, oldTopDirName, newTopDirName string, size uint64) io.Writer { - header.Name = strings.Replace(header.Name, oldTopDirName, newTopDirName, 1) - header.UncompressedSize64 = size - fileContentWriter, err := writer.CreateHeader(&header) - require.NoErrorf(t, err, "error creating header for %+v", header) - return fileContentWriter -} - -func generateNewManifestContent(t *testing.T, manifestReader io.Reader, newVersion *version.ParsedSemVer) string { - oldManifest, err := v1.ParseManifest(manifestReader) - require.NoError(t, err, "reading manifest content from tar source archive") - - t.Logf("read old manifest: %+v", oldManifest) - - // replace manifest content - newManifest, err := mage.GeneratePackageManifest("elastic-agent", newVersion.String(), oldManifest.Package.Snapshot, oldManifest.Package.Hash, oldManifest.Package.Hash[:6], oldManifest.Package.Fips, nil) - require.NoErrorf(t, err, "GeneratePackageManifest(%v, %v, %v, %v, %v) failed", newVersion.String(), oldManifest.Package.Snapshot, oldManifest.Package.Hash, oldManifest.Package.Hash[:6], nil) - - t.Logf("generated new manifest:\n%s", newManifest) - return newManifest -} diff --git a/testing/mocks/internal_/pkg/agent/application/actions/handlers/diagnostics_provider_mock.go b/testing/mocks/internal_/pkg/agent/application/actions/handlers/diagnostics_provider_mock.go index 7a166506eaf..56409b31b2b 100644 --- a/testing/mocks/internal_/pkg/agent/application/actions/handlers/diagnostics_provider_mock.go +++ b/testing/mocks/internal_/pkg/agent/application/actions/handlers/diagnostics_provider_mock.go @@ -2,7 +2,7 @@ // or more contributor license agreements. Licensed under the Elastic License 2.0; // you may not use this file except in compliance with the Elastic License 2.0. -// Code generated by mockery v2.51.1. DO NOT EDIT. +// Code generated by mockery v2.53.4. DO NOT EDIT. package handlers diff --git a/testing/mocks/internal_/pkg/agent/application/actions/handlers/log_level_setter_mock.go b/testing/mocks/internal_/pkg/agent/application/actions/handlers/log_level_setter_mock.go index c937315d8a0..1fa190652b2 100644 --- a/testing/mocks/internal_/pkg/agent/application/actions/handlers/log_level_setter_mock.go +++ b/testing/mocks/internal_/pkg/agent/application/actions/handlers/log_level_setter_mock.go @@ -2,7 +2,7 @@ // or more contributor license agreements. Licensed under the Elastic License 2.0; // you may not use this file except in compliance with the Elastic License 2.0. -// Code generated by mockery v2.51.1. DO NOT EDIT. +// Code generated by mockery v2.53.4. DO NOT EDIT. package handlers diff --git a/testing/mocks/internal_/pkg/agent/application/actions/handlers/uploader_mock.go b/testing/mocks/internal_/pkg/agent/application/actions/handlers/uploader_mock.go index f89f8b85641..76b66ad5513 100644 --- a/testing/mocks/internal_/pkg/agent/application/actions/handlers/uploader_mock.go +++ b/testing/mocks/internal_/pkg/agent/application/actions/handlers/uploader_mock.go @@ -2,7 +2,7 @@ // or more contributor license agreements. Licensed under the Elastic License 2.0; // you may not use this file except in compliance with the Elastic License 2.0. -// Code generated by mockery v2.51.1. DO NOT EDIT. +// Code generated by mockery v2.53.4. DO NOT EDIT. package handlers diff --git a/testing/mocks/internal_/pkg/agent/application/info/agent_mock.go b/testing/mocks/internal_/pkg/agent/application/info/agent_mock.go index 32d403a7d5c..3c69cd9095e 100644 --- a/testing/mocks/internal_/pkg/agent/application/info/agent_mock.go +++ b/testing/mocks/internal_/pkg/agent/application/info/agent_mock.go @@ -2,7 +2,7 @@ // or more contributor license agreements. Licensed under the Elastic License 2.0; // you may not use this file except in compliance with the Elastic License 2.0. -// Code generated by mockery v2.53.0. DO NOT EDIT. +// Code generated by mockery v2.53.4. DO NOT EDIT. package info diff --git a/testing/mocks/internal_/pkg/agent/cmd/agent_watcher_mock.go b/testing/mocks/internal_/pkg/agent/cmd/agent_watcher_mock.go new file mode 100644 index 00000000000..4541e5d9aa0 --- /dev/null +++ b/testing/mocks/internal_/pkg/agent/cmd/agent_watcher_mock.go @@ -0,0 +1,93 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License 2.0; +// you may not use this file except in compliance with the Elastic License 2.0. + +// Code generated by mockery v2.53.4. DO NOT EDIT. + +package cmd + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + logp "github.com/elastic/elastic-agent-libs/logp" + + time "time" +) + +// AgentWatcher is an autogenerated mock type for the agentWatcher type +type AgentWatcher struct { + mock.Mock +} + +type AgentWatcher_Expecter struct { + mock *mock.Mock +} + +func (_m *AgentWatcher) EXPECT() *AgentWatcher_Expecter { + return &AgentWatcher_Expecter{mock: &_m.Mock} +} + +// Watch provides a mock function with given fields: ctx, tilGrace, errorCheckInterval, log +func (_m *AgentWatcher) Watch(ctx context.Context, tilGrace time.Duration, errorCheckInterval time.Duration, log *logp.Logger) error { + ret := _m.Called(ctx, tilGrace, errorCheckInterval, log) + + if len(ret) == 0 { + panic("no return value specified for Watch") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, time.Duration, time.Duration, *logp.Logger) error); ok { + r0 = rf(ctx, tilGrace, errorCheckInterval, log) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// AgentWatcher_Watch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Watch' +type AgentWatcher_Watch_Call struct { + *mock.Call +} + +// Watch is a helper method to define mock.On call +// - ctx context.Context +// - tilGrace time.Duration +// - errorCheckInterval time.Duration +// - log *logp.Logger +func (_e *AgentWatcher_Expecter) Watch(ctx interface{}, tilGrace interface{}, errorCheckInterval interface{}, log interface{}) *AgentWatcher_Watch_Call { + return &AgentWatcher_Watch_Call{Call: _e.mock.On("Watch", ctx, tilGrace, errorCheckInterval, log)} +} + +func (_c *AgentWatcher_Watch_Call) Run(run func(ctx context.Context, tilGrace time.Duration, errorCheckInterval time.Duration, log *logp.Logger)) *AgentWatcher_Watch_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(time.Duration), args[2].(time.Duration), args[3].(*logp.Logger)) + }) + return _c +} + +func (_c *AgentWatcher_Watch_Call) Return(_a0 error) *AgentWatcher_Watch_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *AgentWatcher_Watch_Call) RunAndReturn(run func(context.Context, time.Duration, time.Duration, *logp.Logger) error) *AgentWatcher_Watch_Call { + _c.Call.Return(run) + return _c +} + +// NewAgentWatcher creates a new instance of AgentWatcher. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewAgentWatcher(t interface { + mock.TestingT + Cleanup(func()) +}) *AgentWatcher { + mock := &AgentWatcher{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/testing/mocks/internal_/pkg/agent/cmd/installation_modifier_mock.go b/testing/mocks/internal_/pkg/agent/cmd/installation_modifier_mock.go new file mode 100644 index 00000000000..14f73d912a8 --- /dev/null +++ b/testing/mocks/internal_/pkg/agent/cmd/installation_modifier_mock.go @@ -0,0 +1,146 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License 2.0; +// you may not use this file except in compliance with the Elastic License 2.0. + +// Code generated by mockery v2.53.4. DO NOT EDIT. + +package cmd + +import ( + client "github.com/elastic/elastic-agent/pkg/control/v2/client" + + context "context" + + logp "github.com/elastic/elastic-agent-libs/logp" + + mock "github.com/stretchr/testify/mock" +) + +// InstallationModifier is an autogenerated mock type for the installationModifier type +type InstallationModifier struct { + mock.Mock +} + +type InstallationModifier_Expecter struct { + mock *mock.Mock +} + +func (_m *InstallationModifier) EXPECT() *InstallationModifier_Expecter { + return &InstallationModifier_Expecter{mock: &_m.Mock} +} + +// Cleanup provides a mock function with given fields: log, topDirPath, currentVersionedHome, currentHash, removeMarker, keepLogs +func (_m *InstallationModifier) Cleanup(log *logp.Logger, topDirPath string, currentVersionedHome string, currentHash string, removeMarker bool, keepLogs bool) error { + ret := _m.Called(log, topDirPath, currentVersionedHome, currentHash, removeMarker, keepLogs) + + if len(ret) == 0 { + panic("no return value specified for Cleanup") + } + + var r0 error + if rf, ok := ret.Get(0).(func(*logp.Logger, string, string, string, bool, bool) error); ok { + r0 = rf(log, topDirPath, currentVersionedHome, currentHash, removeMarker, keepLogs) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// InstallationModifier_Cleanup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Cleanup' +type InstallationModifier_Cleanup_Call struct { + *mock.Call +} + +// Cleanup is a helper method to define mock.On call +// - log *logp.Logger +// - topDirPath string +// - currentVersionedHome string +// - currentHash string +// - removeMarker bool +// - keepLogs bool +func (_e *InstallationModifier_Expecter) Cleanup(log interface{}, topDirPath interface{}, currentVersionedHome interface{}, currentHash interface{}, removeMarker interface{}, keepLogs interface{}) *InstallationModifier_Cleanup_Call { + return &InstallationModifier_Cleanup_Call{Call: _e.mock.On("Cleanup", log, topDirPath, currentVersionedHome, currentHash, removeMarker, keepLogs)} +} + +func (_c *InstallationModifier_Cleanup_Call) Run(run func(log *logp.Logger, topDirPath string, currentVersionedHome string, currentHash string, removeMarker bool, keepLogs bool)) *InstallationModifier_Cleanup_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(*logp.Logger), args[1].(string), args[2].(string), args[3].(string), args[4].(bool), args[5].(bool)) + }) + return _c +} + +func (_c *InstallationModifier_Cleanup_Call) Return(_a0 error) *InstallationModifier_Cleanup_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *InstallationModifier_Cleanup_Call) RunAndReturn(run func(*logp.Logger, string, string, string, bool, bool) error) *InstallationModifier_Cleanup_Call { + _c.Call.Return(run) + return _c +} + +// Rollback provides a mock function with given fields: ctx, log, c, topDirPath, prevVersionedHome, prevHash +func (_m *InstallationModifier) Rollback(ctx context.Context, log *logp.Logger, c client.Client, topDirPath string, prevVersionedHome string, prevHash string) error { + ret := _m.Called(ctx, log, c, topDirPath, prevVersionedHome, prevHash) + + if len(ret) == 0 { + panic("no return value specified for Rollback") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *logp.Logger, client.Client, string, string, string) error); ok { + r0 = rf(ctx, log, c, topDirPath, prevVersionedHome, prevHash) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// InstallationModifier_Rollback_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Rollback' +type InstallationModifier_Rollback_Call struct { + *mock.Call +} + +// Rollback is a helper method to define mock.On call +// - ctx context.Context +// - log *logp.Logger +// - c client.Client +// - topDirPath string +// - prevVersionedHome string +// - prevHash string +func (_e *InstallationModifier_Expecter) Rollback(ctx interface{}, log interface{}, c interface{}, topDirPath interface{}, prevVersionedHome interface{}, prevHash interface{}) *InstallationModifier_Rollback_Call { + return &InstallationModifier_Rollback_Call{Call: _e.mock.On("Rollback", ctx, log, c, topDirPath, prevVersionedHome, prevHash)} +} + +func (_c *InstallationModifier_Rollback_Call) Run(run func(ctx context.Context, log *logp.Logger, c client.Client, topDirPath string, prevVersionedHome string, prevHash string)) *InstallationModifier_Rollback_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*logp.Logger), args[2].(client.Client), args[3].(string), args[4].(string), args[5].(string)) + }) + return _c +} + +func (_c *InstallationModifier_Rollback_Call) Return(_a0 error) *InstallationModifier_Rollback_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *InstallationModifier_Rollback_Call) RunAndReturn(run func(context.Context, *logp.Logger, client.Client, string, string, string) error) *InstallationModifier_Rollback_Call { + _c.Call.Return(run) + return _c +} + +// NewInstallationModifier creates a new instance of InstallationModifier. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewInstallationModifier(t interface { + mock.TestingT + Cleanup(func()) +}) *InstallationModifier { + mock := &InstallationModifier{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/testing/mocks/internal_/pkg/agent/storage/storage_mock.go b/testing/mocks/internal_/pkg/agent/storage/storage_mock.go index 44f62085db7..2bec6c96cbb 100644 --- a/testing/mocks/internal_/pkg/agent/storage/storage_mock.go +++ b/testing/mocks/internal_/pkg/agent/storage/storage_mock.go @@ -2,7 +2,7 @@ // or more contributor license agreements. Licensed under the Elastic License 2.0; // you may not use this file except in compliance with the Elastic License 2.0. -// Code generated by mockery v2.51.1. DO NOT EDIT. +// Code generated by mockery v2.53.4. DO NOT EDIT. package storage diff --git a/testing/mocks/internal_/pkg/fleetapi/acker/acker_mock.go b/testing/mocks/internal_/pkg/fleetapi/acker/acker_mock.go index 79446571246..cdeb18b9d57 100644 --- a/testing/mocks/internal_/pkg/fleetapi/acker/acker_mock.go +++ b/testing/mocks/internal_/pkg/fleetapi/acker/acker_mock.go @@ -2,7 +2,7 @@ // or more contributor license agreements. Licensed under the Elastic License 2.0; // you may not use this file except in compliance with the Elastic License 2.0. -// Code generated by mockery v2.51.1. DO NOT EDIT. +// Code generated by mockery v2.53.4. DO NOT EDIT. package acker diff --git a/testing/mocks/internal_/pkg/fleetapi/client/sender_mock.go b/testing/mocks/internal_/pkg/fleetapi/client/sender_mock.go index ff40afa3ee1..68d4999a3a7 100644 --- a/testing/mocks/internal_/pkg/fleetapi/client/sender_mock.go +++ b/testing/mocks/internal_/pkg/fleetapi/client/sender_mock.go @@ -2,7 +2,7 @@ // or more contributor license agreements. Licensed under the Elastic License 2.0; // you may not use this file except in compliance with the Elastic License 2.0. -// Code generated by mockery v2.51.1. DO NOT EDIT. +// Code generated by mockery v2.53.4. DO NOT EDIT. package client diff --git a/testing/mocks/pkg/control/v2/client/client_mock.go b/testing/mocks/pkg/control/v2/client/client_mock.go index 281086ad6f3..c408984634b 100644 --- a/testing/mocks/pkg/control/v2/client/client_mock.go +++ b/testing/mocks/pkg/control/v2/client/client_mock.go @@ -2,7 +2,7 @@ // or more contributor license agreements. Licensed under the Elastic License 2.0; // you may not use this file except in compliance with the Elastic License 2.0. -// Code generated by mockery v2.51.1. DO NOT EDIT. +// Code generated by mockery v2.53.4. DO NOT EDIT. package client diff --git a/testing/upgradetest/upgrader.go b/testing/upgradetest/upgrader.go index 49573301591..1a1076370cc 100644 --- a/testing/upgradetest/upgrader.go +++ b/testing/upgradetest/upgrader.go @@ -41,7 +41,7 @@ type UpgradeOpts struct { skipVerify bool skipDefaultPgp bool customPgp *CustomPGP - customWatcherCfg string + CustomWatcherCfg string installServers bool // Used to disable upgrade details checks for versions that don't support them, like 7.17.x. @@ -54,7 +54,7 @@ type UpgradeOpts struct { preInstallHook func() error postInstallHook func() error preUpgradeHook func() error - postUpgradeHook func() error + PostUpgradeHook func() error PostWatcherSuccessHook func(context.Context, *atesting.Fixture) error } @@ -119,10 +119,10 @@ func WithPreUpgradeHook(hook func() error) UpgradeOpt { } } -// WithPostUpgradeHook sets a hook to be called before install. +// WithPostUpgradeHook sets a hook to be called after install. func WithPostUpgradeHook(hook func() error) UpgradeOpt { return func(opts *UpgradeOpts) { - opts.postUpgradeHook = hook + opts.PostUpgradeHook = hook } } @@ -137,7 +137,7 @@ func WithPostWatcherSuccessHook(hook func(context.Context, *atesting.Fixture) er // WithCustomWatcherConfig sets a custom watcher configuration to use. func WithCustomWatcherConfig(cfg string) UpgradeOpt { return func(opts *UpgradeOpts) { - opts.customWatcherCfg = cfg + opts.CustomWatcherCfg = cfg } } @@ -193,8 +193,8 @@ func PerformUpgrade( } // start fixture gets the agent configured to use a faster watcher - if upgradeOpts.customWatcherCfg != "" { - err = startFixture.Configure(ctx, []byte(upgradeOpts.customWatcherCfg)) + if upgradeOpts.CustomWatcherCfg != "" { + err = startFixture.Configure(ctx, []byte(upgradeOpts.CustomWatcherCfg)) } else { err = ConfigureFastWatcher(ctx, startFixture) } @@ -387,8 +387,8 @@ func PerformUpgrade( } } - if upgradeOpts.postUpgradeHook != nil { - if err := upgradeOpts.postUpgradeHook(); err != nil { + if upgradeOpts.PostUpgradeHook != nil { + if err := upgradeOpts.PostUpgradeHook(); err != nil { return fmt.Errorf("post upgrade hook failed: %w", err) } } diff --git a/testing/upgradetest/versions.go b/testing/upgradetest/versions.go index d8006108188..35a1189e737 100644 --- a/testing/upgradetest/versions.go +++ b/testing/upgradetest/versions.go @@ -46,6 +46,7 @@ var ( Version_8_19_0_SNAPSHOT = version.NewParsedSemVer(8, 19, 0, "SNAPSHOT", "") // Version_9_1_0_SNAPSHOT is a FIPS-capable artifact. + // Version_9_1_0_SNAPSHOT is the minimum version for manual rollback and rollback reason Version_9_1_0_SNAPSHOT = version.NewParsedSemVer(9, 1, 0, "SNAPSHOT", "") // ErrNoSnapshot is returned when a requested snapshot is not on the version list.