Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add otel-agent status subcommand #33556

Merged
merged 26 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/otel-agent/command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (

"github.com/DataDog/datadog-agent/cmd/otel-agent/subcommands"
"github.com/DataDog/datadog-agent/cmd/otel-agent/subcommands/run"
"github.com/DataDog/datadog-agent/cmd/otel-agent/subcommands/status"
"github.com/DataDog/datadog-agent/pkg/cli/subcommands/version"
"go.opentelemetry.io/collector/featuregate"
)
Expand Down Expand Up @@ -49,6 +50,7 @@ func makeCommands(globalParams *subcommands.GlobalParams) *cobra.Command {
commands := []*cobra.Command{
run.MakeCommand(globalConfGetter),
version.MakeCommand("otel-agent"),
status.MakeCommand(globalConfGetter),
}

otelAgentCmd := *commands[0] // root cmd is `run()`; indexed at 0
Expand Down
72 changes: 72 additions & 0 deletions cmd/otel-agent/subcommands/status/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2025-present Datadog, Inc.

// Package status implements the core status component information provider interface
package status

import (
"os"

"github.com/spf13/cobra"
"go.uber.org/fx"

"github.com/DataDog/datadog-agent/cmd/otel-agent/subcommands"
"github.com/DataDog/datadog-agent/comp/api/authtoken/fetchonlyimpl"
coreconfig "github.com/DataDog/datadog-agent/comp/core/config"
log "github.com/DataDog/datadog-agent/comp/core/log/def"
logfx "github.com/DataDog/datadog-agent/comp/core/log/fx"
"github.com/DataDog/datadog-agent/comp/core/secrets"
"github.com/DataDog/datadog-agent/comp/core/secrets/secretsimpl"
status "github.com/DataDog/datadog-agent/comp/otelcol/status/def"
otelagentStatusfx "github.com/DataDog/datadog-agent/comp/otelcol/status/fx"
"github.com/DataDog/datadog-agent/pkg/util/fxutil"
"github.com/DataDog/datadog-agent/pkg/util/option"
)

type dependencies struct {
fx.In

Status status.Component
}

const headerText = "==========\nOTel Agent\n==========\n"

// MakeCommand returns a `status` command to be used by agent binaries.
func MakeCommand(globalConfGetter func() *subcommands.GlobalParams) *cobra.Command {
cmd := &cobra.Command{
Use: "status",
Short: "Print the current status",
Long: ``,
RunE: func(*cobra.Command, []string) error {
globalParams := globalConfGetter()
return fxutil.OneShot(
runStatus,
fx.Supply(coreconfig.NewAgentParams(globalParams.CoreConfPath, coreconfig.WithExtraConfFiles(globalParams.ConfPaths))),
fx.Supply(option.None[secrets.Component]()),
fx.Supply(secrets.NewEnabledParams()),
fx.Supply(log.ForOneShot(globalParams.LoggerName, "off", true)),
coreconfig.Module(),
secretsimpl.Module(),
logfx.Module(),
fetchonlyimpl.Module(),
otelagentStatusfx.Module(),
)
},
}

return cmd
}

func runStatus(deps dependencies) error {
statusText, err := deps.Status.GetStatus()
if err != nil {
return err
}
_, err = os.Stdout.Write([]byte(headerText + statusText))
if err != nil {
return err
}
return nil
}
40 changes: 40 additions & 0 deletions cmd/otel-agent/subcommands/status/command_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2025-present Datadog, Inc.

package status
mackjmr marked this conversation as resolved.
Show resolved Hide resolved

import (
"os"
"path"
"testing"

"github.com/spf13/cobra"
"github.com/stretchr/testify/require"

"github.com/DataDog/datadog-agent/cmd/otel-agent/subcommands"
"github.com/DataDog/datadog-agent/pkg/util/fxutil"
)

func newGlobalParamsTest(t *testing.T) *subcommands.GlobalParams {
config := path.Join(t.TempDir(), "datadog.yaml")
err := os.WriteFile(config, []byte("hostname: test"), 0644)
require.NoError(t, err)
return &subcommands.GlobalParams{
CoreConfPath: config,
ConfPaths: []string{"test_config.yaml"},
}
}

func TestStatusCommand(t *testing.T) {
globalConfGetter := func() *subcommands.GlobalParams {
return newGlobalParamsTest(t)
}
fxutil.TestOneShotSubcommand(t,
[]*cobra.Command{MakeCommand(globalConfGetter)},
[]string{"status"},
runStatus,
func() {},
)
}
21 changes: 21 additions & 0 deletions cmd/otel-agent/subcommands/status/test_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
receivers:
otlp:
protocols:
http:
endpoint: "localhost:4318"
grpc:
endpoint: "localhost:4317"

exporters:
datadog:
api:
key: "abc"

service:
pipelines:
traces:
receivers: [otlp]
exporters: [datadog]
telemetry:
metrics:
address: 127.0.0.1:8888
2 changes: 2 additions & 0 deletions comp/otelcol/status/def/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ package status

// Component is the status interface.
type Component interface {
// GetStatus returns the OTel Agent status in string form
GetStatus() (string, error)
}
27 changes: 23 additions & 4 deletions comp/otelcol/status/impl/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
package statusimpl

import (
"bytes"
"embed"
"encoding/json"
"fmt"
"gopkg.in/yaml.v3"
"io"
"net/http"

"github.com/DataDog/datadog-agent/comp/api/authtoken"
"github.com/DataDog/datadog-agent/comp/core/config"
statusComponent "github.com/DataDog/datadog-agent/comp/core/status"
ddflareextension "github.com/DataDog/datadog-agent/comp/otelcol/ddflareextension/def"
Expand All @@ -27,7 +29,8 @@ var templatesFS embed.FS

// Requires defines the dependencies of the status component.
type Requires struct {
Config config.Component
Config config.Component
Authtoken authtoken.Component
}

// Provides contains components provided by status constructor.
Expand All @@ -39,6 +42,7 @@ type Provides struct {
type statusProvider struct {
Config config.Component
client *http.Client
authToken authtoken.Component
receiverStatus map[string]interface{}
exporterStatus map[string]interface{}
}
Expand All @@ -65,8 +69,9 @@ type prometheusRuntimeConfig struct {
// NewComponent creates a new status component.
func NewComponent(reqs Requires) Provides {
comp := statusProvider{
Config: reqs.Config,
client: apiutil.GetClient(false),
Config: reqs.Config,
client: apiutil.GetClient(false),
authToken: reqs.Authtoken,
receiverStatus: map[string]interface{}{
"spans": 0.0,
"metrics": 0.0,
Expand Down Expand Up @@ -100,6 +105,16 @@ func (s statusProvider) Section() string {
return "OTel Agent"
}

// GetStatus returns the OTel Agent status in string form
func (s statusProvider) GetStatus() (string, error) {
buf := new(bytes.Buffer)
err := s.Text(false, buf)
if err != nil {
return "", err
}
return buf.String(), nil
}

func (s statusProvider) getStatusInfo() map[string]interface{} {
statusInfo := make(map[string]interface{})

Expand Down Expand Up @@ -172,7 +187,11 @@ func (s statusProvider) populatePrometheusStatus(prometheusURL string) error {

func (s statusProvider) populateStatus() map[string]interface{} {
extensionURL := s.Config.GetString("otelcollector.extension_url")
resp, err := apiutil.DoGet(s.client, extensionURL, apiutil.CloseConnection)
options := apiutil.ReqOptions{
Conn: apiutil.CloseConnection,
Authtoken: s.authToken.Get(),
}
resp, err := apiutil.DoGetWithOptions(s.client, extensionURL, &options)
if err != nil {
return map[string]interface{}{
"url": extensionURL,
Expand Down
5 changes: 4 additions & 1 deletion comp/otelcol/status/impl/status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (

"github.com/stretchr/testify/assert"

"github.com/DataDog/datadog-agent/comp/api/authtoken"
"github.com/DataDog/datadog-agent/comp/api/authtoken/fetchonlyimpl"
"github.com/DataDog/datadog-agent/comp/core/config"
"github.com/DataDog/datadog-agent/comp/core/status"
)
Expand Down Expand Up @@ -47,7 +49,8 @@ func TestStatusOut(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
provides := NewComponent(Requires{
Config: config.NewMock(t),
Config: config.NewMock(t),
Authtoken: authtoken.Component(&fetchonlyimpl.MockFetchOnly{}),
})
headerProvider := provides.StatusProvider.Provider
test.assertFunc(t, headerProvider)
Expand Down
8 changes: 8 additions & 0 deletions test/new-e2e/tests/otel/otel-agent/minimal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,11 @@ func (s *minimalTestSuite) TestOTelFlareFiles() {
func (s *minimalTestSuite) TestOTelRemoteConfigPayload() {
utils.TestOTelRemoteConfigPayload(s, minimalProvidedConfig, minimalFullConfig)
}

func (s *minimalTestSuite) TestCoreAgentStatus() {
utils.TestCoreAgentStatusCmd(s)
}

func (s *minimalTestSuite) TestOTelAgentStatus() {
utils.TestOTelAgentStatusCmd(s)
}
40 changes: 40 additions & 0 deletions test/new-e2e/tests/otel/utils/config_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,43 @@ func validateConfigs(t *testing.T, expectedCfg string, actualCfg string) {

assert.YAMLEq(t, expectedCfg, actualCfg)
}

// TestCoreAgentStatusCmd tests the core agent status command contains the OTel Agent status as expected
func TestCoreAgentStatusCmd(s OTelTestSuite) {
err := s.Env().FakeIntake.Client().FlushServerAndResetAggregators()
require.NoError(s.T(), err)
agent := getAgentPod(s)

s.T().Log("Calling status command in core agent")
stdout, stderr, err := s.Env().KubernetesCluster.KubernetesClient.PodExec("datadog", agent.Name, "agent", []string{"agent", "status", "otel agent"})
require.NoError(s.T(), err, "Failed to execute config")
require.Empty(s.T(), stderr)
validateStatus(s.T(), stdout)
}

// TestOTelAgentStatusCmd tests the OTel Agent status subcommand returns as expected
func TestOTelAgentStatusCmd(s OTelTestSuite) {
err := s.Env().FakeIntake.Client().FlushServerAndResetAggregators()
require.NoError(s.T(), err)
agent := getAgentPod(s)

s.T().Log("Calling status command in otel agent")
stdout, stderr, err := s.Env().KubernetesCluster.KubernetesClient.PodExec("datadog", agent.Name, "otel-agent", []string{"otel-agent", "status"})
require.NoError(s.T(), err, "Failed to execute config")
require.Empty(s.T(), stderr)
validateStatus(s.T(), stdout)
}

func validateStatus(t *testing.T, status string) {
require.NotNil(t, status)
require.Contains(t, status, "OTel Agent")
require.Contains(t, status, "Status: Running")
require.Contains(t, status, "Agent Version:")
require.Contains(t, status, "Collector Version:")
require.Contains(t, status, "Spans Accepted:")
require.Contains(t, status, "Metric Points Accepted:")
require.Contains(t, status, "Log Records Accepted:")
require.Contains(t, status, "Spans Sent:")
require.Contains(t, status, "Metric Points Sent:")
require.Contains(t, status, "Log Records Sent:")
}
Loading