Skip to content

Commit

Permalink
Include upgrade details in output of elastic-agent status during on…
Browse files Browse the repository at this point in the history
…going upgrade (#3615)

* Remove context and handle cancellation internally instead

* More optimizations

* Add back context

* Adding FSM for upgrades

* Implementing TODO

* WIP

* WIP

* Reorganizing imports

* Running go mod tidy

* Add unit tests

* Remove Fleet changes

* Fixing booboos introduced during conflict resolution

* Add nil guard

* Setting logger in test

* Adding upgrade details to V2 control protocol

* Regenerating protobuf implementation files

* Including upgrade details in Agent state

* Adding test case

* Adding CHANGELOG entry

* Newline fixes

* Fix data types

* Generating protobuf implementations

* Running mage update

* Add generated protobuf code files to sonar exclusions list

* Try removing comments

* Include upgrade details in elastic-agent status output --full

* Increase test coverage
  • Loading branch information
ycombinator authored Nov 3, 2023
1 parent 38bdc91 commit e660098
Show file tree
Hide file tree
Showing 13 changed files with 740 additions and 268 deletions.
32 changes: 32 additions & 0 deletions changelog/fragments/1697508156-upgrade-details-in-status.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# 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: enhancement

# Change summary; a 80ish characters long description of the change.
summary: Include upgrade details in output of `elastic-agent status`.

# 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:

# 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/pull/3615

# 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
39 changes: 38 additions & 1 deletion control_v2.proto
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ message StateAgentInfo {
}

// StateResponse is the current state of Elastic Agent.
// Next unused id: 7
// Next unused id: 8
message StateResponse {
// Overall information of Elastic Agent.
StateAgentInfo info = 1;
Expand All @@ -188,6 +188,43 @@ message StateResponse {

// State of each component in Elastic Agent.
repeated ComponentState components = 4;

// Upgrade details
UpgradeDetails upgrade_details = 7;
}

// UpgradeDetails captures the details of an ongoing Agent upgrade.
message UpgradeDetails {
// Version the Agent is being upgraded to.
string target_version = 1;

// Current state of the upgrade process.
string state = 2;

// Fleet Action ID that initiated the upgrade, if in managed mode.
string action_id = 3;

// Metadata about the upgrade process.
UpgradeDetailsMetadata metadata = 4;
}

// UpgradeDetailsMetadata has additional information about an Agent's
// ongoing upgrade.
message UpgradeDetailsMetadata {
// If the upgrade is a scheduled upgrade, the timestamp of when the
// upgrade is expected to start.
google.protobuf.Timestamp scheduled_at = 1;

// If the upgrade is in the UPG_DOWNLOADING state, the percentage of
// the Elastic Agent artifact that has already been downloaded, to
// serve as an indicator of download progress.
float download_percent = 2;

// If the upgrade has failed, what upgrade state failed.
string failed_state = 3;

// Any error encountered during the upgrade process.
string error_msg = 4;
}

// DiagnosticFileResult is a file result from a diagnostic result.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,4 @@ func TestDetailsDownloadRateJSON(t *testing.T) {
require.Equal(t, math.Inf(1), float64(unmarshalledDetails.Metadata.DownloadRate))
require.Equal(t, 0.99, unmarshalledDetails.Metadata.DownloadPercent)
})

}
39 changes: 39 additions & 0 deletions internal/pkg/agent/cmd/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import (
"sort"
"time"

"github.com/elastic/elastic-agent/internal/pkg/agent/application/upgrade/details"
"github.com/elastic/elastic-agent/pkg/control/v2/client"
"github.com/elastic/elastic-agent/pkg/control/v2/cproto"

"gopkg.in/yaml.v2"

Expand Down Expand Up @@ -145,6 +147,43 @@ func listAgentState(l list.Writer, state *client.AgentState, all bool) {
}
l.UnIndent()
listComponentState(l, state.Components, all)

// Upgrade details
listUpgradeDetails(l, state.UpgradeDetails)
}

func listUpgradeDetails(l list.Writer, upgradeDetails *cproto.UpgradeDetails) {
if upgradeDetails == nil {
return
}

l.AppendItem("upgrade_details")
l.Indent()
l.AppendItem("target_version: " + upgradeDetails.TargetVersion)
l.AppendItem("state: " + upgradeDetails.State)
if upgradeDetails.ActionId != "" {
l.AppendItem("action_id: " + upgradeDetails.ActionId)
}

if upgradeDetails.Metadata != nil {
l.AppendItem("metadata")
l.Indent()
if upgradeDetails.Metadata.ScheduledAt != nil && !upgradeDetails.Metadata.ScheduledAt.AsTime().IsZero() {
l.AppendItem("scheduled_at: " + upgradeDetails.Metadata.ScheduledAt.AsTime().UTC().Format(time.RFC3339))
}
if upgradeDetails.Metadata.FailedState != "" {
l.AppendItem("failed_state: " + upgradeDetails.Metadata.FailedState)
}
if upgradeDetails.Metadata.ErrorMsg != "" {
l.AppendItem("error_msg: " + upgradeDetails.Metadata.ErrorMsg)
}
if upgradeDetails.State == string(details.StateDownloading) {
l.AppendItem(fmt.Sprintf("download_percent: %.2f%%", upgradeDetails.Metadata.DownloadPercent*100))
}
l.UnIndent()
}

l.UnIndent()
}

func listFleetState(l list.Writer, state *client.AgentState, all bool) {
Expand Down
83 changes: 83 additions & 0 deletions internal/pkg/agent/cmd/status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,20 @@ package cmd

import (
"bytes"
"fmt"
"os"
"path/filepath"
"testing"
"time"

"google.golang.org/protobuf/types/known/timestamppb"

"github.com/jedib0t/go-pretty/v6/list"

"github.com/stretchr/testify/require"

"github.com/elastic/elastic-agent/pkg/control/v2/client"
"github.com/elastic/elastic-agent/pkg/control/v2/cproto"
)

func TestHumanOutput(t *testing.T) {
Expand Down Expand Up @@ -148,3 +155,79 @@ func TestHumanOutput(t *testing.T) {
require.Equalf(t, string(expected), b.String(), "unexpected input with output: %s, state: %s", test.output, test.state_name)
}
}

func TestListUpgradeDetails(t *testing.T) {
now := time.Now().UTC()
cases := map[string]struct {
upgradeDetails *cproto.UpgradeDetails
expectedOutput string
}{
"no_details": {
upgradeDetails: nil,
expectedOutput: "",
},
"no_metadata": {
upgradeDetails: &cproto.UpgradeDetails{
TargetVersion: "8.12.0",
State: "UPG_REQUESTED",
ActionId: "foobar",
},
expectedOutput: `── upgrade_details
├─ target_version: 8.12.0
├─ state: UPG_REQUESTED
└─ action_id: foobar`,
},
"no_action_id": {
upgradeDetails: &cproto.UpgradeDetails{
TargetVersion: "8.12.0",
State: "UPG_REQUESTED",
},
expectedOutput: `── upgrade_details
├─ target_version: 8.12.0
└─ state: UPG_REQUESTED`,
},
"no_scheduled_at": {
upgradeDetails: &cproto.UpgradeDetails{
TargetVersion: "8.12.0",
State: "UPG_FAILED",
Metadata: &cproto.UpgradeDetailsMetadata{
FailedState: "UPG_DOWNLOADING",
ErrorMsg: "error downloading",
DownloadPercent: 0.104,
},
},
expectedOutput: `── upgrade_details
├─ target_version: 8.12.0
├─ state: UPG_FAILED
└─ metadata
├─ failed_state: UPG_DOWNLOADING
└─ error_msg: error downloading`,
},
"no_failed_state": {
upgradeDetails: &cproto.UpgradeDetails{
TargetVersion: "8.12.0",
State: "UPG_DOWNLOADING",
Metadata: &cproto.UpgradeDetailsMetadata{
ScheduledAt: timestamppb.New(now),
DownloadPercent: 0.17679,
},
},
expectedOutput: fmt.Sprintf(`── upgrade_details
├─ target_version: 8.12.0
├─ state: UPG_DOWNLOADING
└─ metadata
├─ scheduled_at: %s
└─ download_percent: 17.68%%`, now.Format(time.RFC3339)),
}}

for name, test := range cases {
t.Run(name, func(t *testing.T) {
l := list.NewWriter()
l.SetStyle(list.StyleConnectedLight)

listUpgradeDetails(l, test.upgradeDetails)
actualOutput := l.Render()
require.Equal(t, test.expectedOutput, actualOutput)
})
}
}
2 changes: 1 addition & 1 deletion pkg/control/v1/proto/control_v1.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pkg/control/v1/proto/control_v1_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 12 additions & 10 deletions pkg/control/v2/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,13 @@ type AgentStateInfo struct {

// AgentState is the current state of the Elastic Agent.
type AgentState struct {
Info AgentStateInfo `json:"info" yaml:"info"`
State State `json:"state" yaml:"state"`
Message string `json:"message" yaml:"message"`
Components []ComponentState `json:"components" yaml:"components"`
FleetState State `yaml:"fleet_state"`
FleetMessage string `yaml:"fleet_message"`
Info AgentStateInfo `json:"info" yaml:"info"`
State State `json:"state" yaml:"state"`
Message string `json:"message" yaml:"message"`
Components []ComponentState `json:"components" yaml:"components"`
FleetState State `yaml:"fleet_state"`
FleetMessage string `yaml:"fleet_message"`
UpgradeDetails *cproto.UpgradeDetails `json:"upgrade_details,omitempty" yaml:"upgrade_details,omitempty"`
}

// DiagnosticFileResult is a diagnostic file result.
Expand Down Expand Up @@ -475,10 +476,11 @@ func toState(res *cproto.StateResponse) (*AgentState, error) {
Snapshot: res.Info.Snapshot,
PID: res.Info.Pid,
},
State: res.State,
Message: res.Message,
FleetState: res.FleetState,
FleetMessage: res.FleetMessage,
State: res.State,
Message: res.Message,
FleetState: res.FleetState,
FleetMessage: res.FleetMessage,
UpgradeDetails: res.UpgradeDetails,

Components: make([]ComponentState, 0, len(res.Components)),
}
Expand Down
Loading

0 comments on commit e660098

Please sign in to comment.