diff --git a/internal/pkg/agent/application/actions/handlers/handler_action_diagnostics_test.go b/internal/pkg/agent/application/actions/handlers/handler_action_diagnostics_test.go index 9ce91ae332a..370f99da9c9 100644 --- a/internal/pkg/agent/application/actions/handlers/handler_action_diagnostics_test.go +++ b/internal/pkg/agent/application/actions/handlers/handler_action_diagnostics_test.go @@ -395,3 +395,70 @@ func TestDiagnosticHandlerWithCPUProfile(t *testing.T) { assert.True(t, cpuCalled, "CPU profile collector was not called.") mockDiagProvider.AssertExpectations(t) } +<<<<<<< HEAD +======= + +func TestDiagnosticsHandlerWithEDOT(t *testing.T) { + tempAgentRoot := t.TempDir() + paths.SetTop(tempAgentRoot) + err := os.MkdirAll(path.Join(tempAgentRoot, "data"), 0755) + require.NoError(t, err) + called := false + s := NewMockServer(t, paths.DiagnosticsExtensionSocket(), &called, nil) + defer func() { + require.NoError(t, s.Shutdown(context.Background())) + }() + mockDiagProvider := newMockDiagnosticsProvider(t) + mockDiagProvider.EXPECT().DiagnosticHooks().Return([]diagnostics.Hook{hook1}) + mockDiagProvider.EXPECT().PerformDiagnostics(mock.Anything, mock.Anything).Return([]runtime.ComponentUnitDiagnostic{mockUnitDiagnostic}) + mockDiagProvider.EXPECT().PerformComponentDiagnostics(mock.Anything, mock.Anything).Return([]runtime.ComponentDiagnostic{mockComponentDiagnostic}, nil) + + mockAcker := acker.NewMockAcker(t) + + mockAcker.EXPECT().Ack(mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, a fleetapi.Action) error { + require.IsType(t, new(fleetapi.ActionDiagnostics), a) + assert.NoError(t, a.(*fleetapi.ActionDiagnostics).Err) + return nil + }) + mockAcker.EXPECT().Commit(mock.Anything).Return(nil) + + mockUploader := NewMockUploader(t) + mockUploader.EXPECT().UploadDiagnostics(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, s1, s2 string, i int64, r io.Reader) (string, error) { + expectedContent := map[string][]byte{ + "components/ComponentID/mock_component_diag_file.yaml": []byte("hello: component\n"), + "components/ComponentID/UnitID/mock_unit_diag_file.yaml": []byte("hello: there\n"), + "mock_global.txt": []byte("This is a mock global diagnostic content"), + } + verifyZip(t, r, expectedContent) + return "upload-id", nil + }) + + testLogger, _ := loggertest.New("diagnostic-handler-test") + handler := NewDiagnostics(testLogger, tempAgentRoot, mockDiagProvider, defaultRateLimit, mockUploader) + handler.collectDiag(t.Context(), &fleetapi.ActionDiagnostics{}, mockAcker) + require.True(t, called, "expected the mock diagnostics server to be called") +} + +func verifyZip(t *testing.T, reader io.Reader, expectedContent map[string][]byte) { + // Read all from io.Reader into a buffer + buf, err := io.ReadAll(reader) + require.NoErrorf(t, err, "failed to read the buffer: %v", err) + + // Create a zip reader from the buffer + zr, err := zip.NewReader(bytes.NewReader(buf), int64(len(buf))) + require.NoErrorf(t, err, "got error while creating reader: %v", err) + + foundContent := map[string][]byte{} + for _, f := range zr.File { + if _, ok := expectedContent[f.Name]; ok { + rc, err := f.Open() + require.NoErrorf(t, err, "failed to open the zip file at %v: %v", f.Name, err) + defer rc.Close() + content, err := io.ReadAll(rc) + require.NoErrorf(t, err, "failed to read the zip file at %v: %v", f.Name, err) + foundContent[f.Name] = content + } + } + require.Equal(t, expectedContent, foundContent) +} +>>>>>>> 7afb20069 ([edot][diagnostics] remove otel diagnostics from manager (#10415)) diff --git a/internal/pkg/agent/application/actions/handlers/mock_server.go b/internal/pkg/agent/application/actions/handlers/mock_server.go new file mode 100644 index 00000000000..8f19a2d9459 --- /dev/null +++ b/internal/pkg/agent/application/actions/handlers/mock_server.go @@ -0,0 +1,54 @@ +// 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 handlers + +import ( + "encoding/json" + "net/http" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/elastic/elastic-agent-client/v7/pkg/proto" + "github.com/elastic/elastic-agent/internal/pkg/otel/extension/elasticdiagnostics" + "github.com/elastic/elastic-agent/pkg/core/logger" + "github.com/elastic/elastic-agent/pkg/ipc" +) + +func NewMockServer(t *testing.T, host string, called *bool, response *elasticdiagnostics.Response) *http.Server { + mux := http.NewServeMux() + mux.HandleFunc("/diagnostics", func(w http.ResponseWriter, r *http.Request) { + if called != nil { + *called = true + } + resp := elasticdiagnostics.Response{ + GlobalDiagnostics: []*proto.ActionDiagnosticUnitResult{ + { + Description: "Mock Global Diagnostic", + Filename: "mock_global.txt", + ContentType: "text/plain", + Content: []byte("This is a mock global diagnostic content"), + }, + }, + } + if response != nil { + // overwrite default response + resp = *response + } + err := json.NewEncoder(w).Encode(resp) + require.NoError(t, err) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + }) + + l, err := ipc.CreateListener(logger.NewWithoutConfig(""), host) + require.NoError(t, err) + server := &http.Server{Handler: mux} //nolint:gosec // This is a test + go func() { + err := server.Serve(l) + require.ErrorIs(t, err, http.ErrServerClosed) + }() + return server +} diff --git a/internal/pkg/otel/manager/diagnostics.go b/internal/pkg/otel/manager/diagnostics.go new file mode 100644 index 00000000000..099d3f2c780 --- /dev/null +++ b/internal/pkg/otel/manager/diagnostics.go @@ -0,0 +1,134 @@ +// 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 manager + +import ( + "context" + "errors" + "fmt" + "strings" + "syscall" + + "github.com/elastic/elastic-agent/internal/pkg/otel" + + "github.com/elastic/elastic-agent/pkg/component" + "github.com/elastic/elastic-agent/pkg/component/runtime" + "github.com/elastic/elastic-agent/pkg/control/v2/cproto" +) + +// PerformDiagnostics executes the diagnostic action for the provided units. If no units are provided then +// it performs diagnostics for all current units. If a given unit does not exist in the manager, then a warning +// is logged. +func (m *OTelManager) PerformDiagnostics(ctx context.Context, req ...runtime.ComponentUnitDiagnosticRequest) []runtime.ComponentUnitDiagnostic { + var diagnostics []runtime.ComponentUnitDiagnostic + m.mx.RLock() + currentComponents := m.components + m.mx.RUnlock() + + // if no request is provided, then perform diagnostics for all units + if len(req) == 0 { + for _, comp := range currentComponents { + for _, unit := range comp.Units { + diagnostics = append(diagnostics, runtime.ComponentUnitDiagnostic{ + Component: comp, + Unit: unit, + }) + } + } + return diagnostics + } + + // create a map of unit by component and unit id, this is used to filter out units that + // do not exist in the manager + unitByID := make(map[string]map[string]*component.Unit) + for _, r := range req { + if unitByID[r.Component.ID] == nil { + unitByID[r.Component.ID] = make(map[string]*component.Unit) + } + unitByID[r.Component.ID][r.Unit.ID] = &r.Unit + } + + // create empty diagnostics for units that exist in the manager + for _, existingComp := range currentComponents { + inputComp, ok := unitByID[existingComp.ID] + if !ok { + m.logger.Warnf("requested diagnostics for component %s, but it does not exist in the manager", existingComp.ID) + continue + } + for _, unit := range existingComp.Units { + if _, ok := inputComp[unit.ID]; ok { + diagnostics = append(diagnostics, runtime.ComponentUnitDiagnostic{ + Component: existingComp, + Unit: unit, + }) + } else { + m.logger.Warnf("requested diagnostics for unit %s, but it does not exist in the manager", unit.ID) + } + } + } + + return diagnostics +} + +// PerformComponentDiagnostics executes the diagnostic action for the provided components. If no components are provided, +// then it performs the diagnostics for all current components. +func (m *OTelManager) PerformComponentDiagnostics( + ctx context.Context, additionalMetrics []cproto.AdditionalDiagnosticRequest, req ...component.Component, +) ([]runtime.ComponentDiagnostic, error) { + var diagnostics []runtime.ComponentDiagnostic + m.mx.RLock() + currentComponents := m.components + m.mx.RUnlock() + + // if no request is provided, then perform diagnostics for all components + if len(req) == 0 { + req = currentComponents + } + + // create a map of component by id, this is used to filter out components that do not exist in the manager + compByID := make(map[string]component.Component) + for _, comp := range req { + compByID[comp.ID] = comp + } + + // create empty diagnostics for components that exist in the manager + for _, existingComp := range currentComponents { + if inputComp, ok := compByID[existingComp.ID]; ok { + diagnostics = append(diagnostics, runtime.ComponentDiagnostic{ + Component: inputComp, + }) + } else { + m.logger.Warnf("requested diagnostics for component %s, but it does not exist in the manager", existingComp.ID) + } + } + + extDiagnostics, err := otel.PerformDiagnosticsExt(ctx, false) + + // We're not running the EDOT if: + // 1. Either the socket doesn't exist + // 2. It is refusing the connections. + // Return error for any other scenario. + if err != nil { + m.logger.Debugf("Couldn't fetch diagnostics from EDOT: %v", err) + if !errors.Is(err, syscall.ENOENT) && !errors.Is(err, syscall.ECONNREFUSED) { + return nil, fmt.Errorf("error fetching otel diagnostics: %w", err) + } + } + + for idx, diag := range diagnostics { + found := false + for _, extDiag := range extDiagnostics.ComponentDiagnostics { + if strings.Contains(extDiag.Name, diag.Component.ID) { + found = true + diagnostics[idx].Results = append(diagnostics[idx].Results, extDiag) + } + } + if !found { + diagnostics[idx].Err = fmt.Errorf("failed to get diagnostics for %s", diag.Component.ID) + } + } + + return diagnostics, nil +} diff --git a/internal/pkg/otel/manager/diagnostics_test.go b/internal/pkg/otel/manager/diagnostics_test.go new file mode 100644 index 00000000000..b0ac8645969 --- /dev/null +++ b/internal/pkg/otel/manager/diagnostics_test.go @@ -0,0 +1,200 @@ +// 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 manager + +import ( + "encoding/json" + "fmt" + "runtime" + "testing" + + "github.com/elastic/elastic-agent-client/v7/pkg/proto" + "github.com/elastic/elastic-agent/internal/pkg/otel/extension/elasticdiagnostics" + + "github.com/elastic/elastic-agent/internal/pkg/agent/application/actions/handlers" + "github.com/elastic/elastic-agent/internal/pkg/agent/application/paths" + + componentruntime "github.com/elastic/elastic-agent/pkg/component/runtime" + "github.com/elastic/elastic-agent/pkg/core/logger/loggertest" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/elastic/elastic-agent/pkg/component" +) + +func TestPerformComponentDiagnostics(t *testing.T) { + logger, _ := loggertest.New("test") + compID := "filebeat-comp-1" + + filebeatComp := testComponent(compID) + filebeatComp.InputSpec.Spec.Command.Args = []string{"filebeat"} + + otherComp := testComponent("other-comp") + otherComp.InputSpec.Spec.Command.Args = []string{"metricbeat"} + + m := &OTelManager{ + logger: logger, + components: []component.Component{filebeatComp, otherComp}, + } + + expectedDiags := []componentruntime.ComponentDiagnostic{ + { + Component: filebeatComp, + }, + { + Component: otherComp, + }, + } + + diags, err := m.PerformComponentDiagnostics(t.Context(), nil) + require.NoError(t, err) + for i, d := range diags { + assert.Equal(t, expectedDiags[i].Component.ID, d.Component.ID) + // we should have errors set about not being able to connect to diagnostics extension + require.NotNil(t, d.Err) + assert.ErrorContains(t, d.Err, fmt.Sprintf("failed to get diagnostics for %s", d.Component.ID)) + } +} + +func TestPerformDiagnostics(t *testing.T) { + logger, _ := loggertest.New("test") + compID := "filebeat-comp-1" + + filebeatComp := testComponent(compID) + filebeatComp.InputSpec.Spec.Command.Args = []string{"filebeat"} + + otherComp := testComponent("other-comp") + otherComp.InputSpec.Spec.Command.Args = []string{"metricbeat"} + + m := &OTelManager{ + logger: logger, + components: []component.Component{filebeatComp, otherComp}, + } + + t.Run("diagnose all units when no request is provided", func(t *testing.T) { + expectedDiags := []componentruntime.ComponentUnitDiagnostic{ + { + Component: filebeatComp, + Unit: filebeatComp.Units[0], + }, + { + Component: filebeatComp, + Unit: filebeatComp.Units[1], + }, + { + Component: otherComp, + Unit: otherComp.Units[0], + }, + { + Component: otherComp, + Unit: otherComp.Units[1], + }, + } + diags := m.PerformDiagnostics(t.Context()) + assert.Equal(t, expectedDiags, diags) + }) + + t.Run("diagnose specific unit", func(t *testing.T) { + req := componentruntime.ComponentUnitDiagnosticRequest{ + Component: filebeatComp, + Unit: filebeatComp.Units[0], + } + expectedDiags := []componentruntime.ComponentUnitDiagnostic{ + { + Component: filebeatComp, + Unit: filebeatComp.Units[0], + }, + } + diags := m.PerformDiagnostics(t.Context(), req) + assert.Equal(t, expectedDiags, diags) + }) +} + +func TestBeatMetrics(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Skip test on Windows.", + "It's technically cumbersome to set up an npipe http server.", + "And it doesn't have anything to do with the code paths being tested.", + ) + } + setTemporaryAgentPath(t) + logger, obs := loggertest.New("test") + compID := "filebeat-comp-1" + + filebeatComp := testComponent(compID) + filebeatComp.InputSpec.Spec.Command.Args = []string{"filebeat"} + + m := &OTelManager{ + logger: logger, + components: []component.Component{filebeatComp}, + } + expectedMetricData, err := json.MarshalIndent(map[string]any{"test": "test"}, "", " ") + require.NoError(t, err) + + expectedResponse := elasticdiagnostics.Response{ + ComponentDiagnostics: []*proto.ActionDiagnosticUnitResult{ + { + Name: compID, + Filename: "beat_metrics.json", + ContentType: "application/json", + Description: "Metrics from the default monitoring namespace and expvar.", + Content: expectedMetricData, + }, + { + Name: compID, + Filename: "input_metrics.json", + ContentType: "application/json", + Description: "Metrics from active inputs.", + Content: expectedMetricData, + }, + }, + } + + called := false + server := handlers.NewMockServer(t, paths.DiagnosticsExtensionSocket(), &called, &expectedResponse) + t.Cleanup(func() { + cErr := server.Close() + assert.NoError(t, cErr) + }) + + diags, err := m.PerformComponentDiagnostics(t.Context(), nil) + require.NoError(t, err) + assert.Len(t, obs.All(), 0) + require.Len(t, diags, 1) + require.True(t, called) + + diag := diags[0] + assert.Equal(t, filebeatComp, diag.Component) + // two metrics diagnostics and one filebeat registry + require.Len(t, diag.Results, 2, "expected 2 diagnostics, got error: %w", diag.Err) + + t.Run("stats beat metrics", func(t *testing.T) { + beatMetrics := diag.Results[0] + assert.Equal(t, compID, beatMetrics.Name) + assert.Equal(t, "Metrics from the default monitoring namespace and expvar.", beatMetrics.Description) + assert.Equal(t, "beat_metrics.json", beatMetrics.Filename) + assert.Equal(t, "application/json", beatMetrics.ContentType) + assert.Equal(t, expectedMetricData, beatMetrics.Content) + }) + + t.Run("input beat metrics", func(t *testing.T) { + inputMetrics := diag.Results[1] + assert.Equal(t, compID, inputMetrics.Name) + assert.Equal(t, "Metrics from active inputs.", inputMetrics.Description) + assert.Equal(t, "input_metrics.json", inputMetrics.Filename) + assert.Equal(t, "application/json", inputMetrics.ContentType) + assert.Equal(t, expectedMetricData, inputMetrics.Content) + }) +} + +func setTemporaryAgentPath(t *testing.T) { + topPath := paths.Top() + tempTopPath := t.TempDir() + paths.SetTop(tempTopPath) + t.Cleanup(func() { + paths.SetTop(topPath) + }) +} diff --git a/testing/integration/ess/diagnostics_test.go b/testing/integration/ess/diagnostics_test.go index 7a3f79b940e..9390704b858 100644 --- a/testing/integration/ess/diagnostics_test.go +++ b/testing/integration/ess/diagnostics_test.go @@ -7,7 +7,13 @@ package ess import ( + "archive/tar" "archive/zip" +<<<<<<< HEAD +======= + "bytes" + "compress/gzip" +>>>>>>> 7afb20069 ([edot][diagnostics] remove otel diagnostics from manager (#10415)) "context" "fmt" "io" @@ -323,6 +329,234 @@ func TestRedactFleetSecretPathsDiagnostics(t *testing.T) { } } +<<<<<<< HEAD +======= +func TestBeatDiagnostics(t *testing.T) { + define.Require(t, define.Requirements{ + Group: integration.Default, + Local: false, + }) + + configTemplate := ` +inputs: + - id: filestream-filebeat + type: filestream + paths: + - {{ .InputFile }} + prospector.scanner.fingerprint.enabled: false + file_identity.native: ~ + use_output: default + _runtime_experimental: {{ .Runtime }} +outputs: + default: + type: elasticsearch + hosts: [http://localhost:9200] + api_key: placeholder +agent.monitoring.enabled: false +` + + var filebeatSetup = map[string]integrationtest.ComponentState{ + "filestream-default": { + State: integrationtest.NewClientState(client.Healthy), + }, + } + + ctx, cancel := testcontext.WithDeadline(t, context.Background(), time.Now().Add(10*time.Minute)) + defer cancel() + + testCases := []struct { + name string + runtime string + expectedCompDiagnosticsFiles []string + expectedAgentState *client.State + expectedComponentState map[string]integrationtest.ComponentState + }{ + { + name: "filebeat process", + runtime: "process", + expectedCompDiagnosticsFiles: append(compDiagnosticsFiles, + "registry.tar.gz", + "input_metrics.json", + "beat_metrics.json", + "beat-rendered-config.yml", + "global_processors.txt", + ), + expectedAgentState: integrationtest.NewClientState(client.Healthy), + expectedComponentState: map[string]integrationtest.ComponentState{ + "filestream-default": { + State: integrationtest.NewClientState(client.Healthy), + Units: map[integrationtest.ComponentUnitKey]integrationtest.ComponentUnitState{ + integrationtest.ComponentUnitKey{UnitType: client.UnitTypeOutput, UnitID: "filestream-default"}: { + State: integrationtest.NewClientState(client.Healthy), + }, + integrationtest.ComponentUnitKey{UnitType: client.UnitTypeInput, UnitID: "filestream-default-filestream-filebeat"}: { + State: integrationtest.NewClientState(client.Healthy), + }, + }, + }, + }, + }, + { + name: "filebeat receiver", + runtime: "otel", + expectedCompDiagnosticsFiles: []string{ + "registry.tar.gz", + "beat_metrics.json", + "input_metrics.json", + }, + expectedAgentState: integrationtest.NewClientState(client.Degraded), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Create the fixture + f, err := define.NewFixtureFromLocalBuild(t, define.Version(), integrationtest.WithAllowErrors()) + require.NoError(t, err) + err = f.Prepare(ctx) + require.NoError(t, err) + + // Create the data file to ingest + inputFile, err := os.CreateTemp(t.TempDir(), "input.txt") + require.NoError(t, err, "failed to create temp file to hold data to ingest") + t.Cleanup(func() { + cErr := inputFile.Close() + assert.NoError(t, cErr) + }) + _, err = inputFile.WriteString("hello world\n") + require.NoError(t, err, "failed to write data to temp file") + + var configBuffer bytes.Buffer + require.NoError(t, + template.Must(template.New("config").Parse(configTemplate)).Execute(&configBuffer, map[string]any{ + "Runtime": tc.runtime, + "InputFile": inputFile.Name(), + })) + expDiagFiles := append([]string{}, diagnosticsFiles...) + if tc.runtime == "otel" { + // EDOT adds these extra files. + // TestBeatDiagnostics is quite strict about what it expects to see in the archive. + expDiagFiles = append(expDiagFiles, + "edot/goroutine.profile.gz", + "edot/heap.profile.gz", + "edot/allocs.profile.gz", + "edot/block.profile.gz", + "edot/mutex.profile.gz", + "edot/threadcreate.profile.gz", + "edot/otel-merged-actual.yaml") + } + err = f.Run(ctx, integrationtest.State{ + Configure: configBuffer.String(), + AgentState: tc.expectedAgentState, + Components: tc.expectedComponentState, + After: testDiagnosticsFactory(t, filebeatSetup, expDiagFiles, tc.expectedCompDiagnosticsFiles, f, []string{"diagnostics", "collect"}), + }) + assert.NoError(t, err) + }) + } +} + +func TestEDOTDiagnostics(t *testing.T) { + define.Require(t, define.Requirements{ + Group: integration.Default, + Local: false, + }) + + configTemplate := ` +inputs: + - id: filestream-filebeat + type: filestream + paths: + - {{ .InputFile }} + prospector.scanner.fingerprint.enabled: false + file_identity.native: ~ + use_output: default + _runtime_experimental: otel +agent.grpc: + port: 6790 +outputs: + default: + type: elasticsearch + hosts: [http://localhost:9200] + api_key: placeholder +agent.monitoring.enabled: false +` + + ctx, cancel := testcontext.WithDeadline(t, context.Background(), time.Now().Add(10*time.Minute)) + defer cancel() + + // Create the fixture + f, err := define.NewFixtureFromLocalBuild(t, define.Version(), integrationtest.WithAllowErrors()) + require.NoError(t, err) + + // Create the data file to ingest + inputFile, err := os.CreateTemp(t.TempDir(), "input.txt") + require.NoError(t, err, "failed to create temp file to hold data to ingest") + t.Cleanup(func() { + cErr := inputFile.Close() + assert.NoError(t, cErr) + }) + _, err = inputFile.WriteString("hello world\n") + require.NoError(t, err, "failed to write data to temp file") + + var configBuffer bytes.Buffer + require.NoError(t, + template.Must(template.New("config").Parse(configTemplate)).Execute(&configBuffer, map[string]any{ + "InputFile": inputFile.Name(), + })) + err = f.Prepare(ctx) + require.NoError(t, err) + + err = f.Configure(ctx, configBuffer.Bytes()) + require.NoError(t, err) + cmd, err := f.PrepareAgentCommand(ctx, []string{"-e"}) + require.NoError(t, err) + + output := strings.Builder{} + cmd.Stderr = &output + cmd.Stdout = &output + + err = cmd.Start() + require.NoError(t, err) + + require.EventuallyWithT(t, func(collect *assert.CollectT) { + err = f.IsHealthy(ctx) + require.NoErrorf(collect, err, "agent is not healthy: %s", err) + require.Containsf(collect, output.String(), "Diagnostics extension started", "expected log: %s", output.String()) + }, 30*time.Second, 1*time.Second) + + diagZip, err := f.ExecDiagnostics(ctx) + extractionDir := t.TempDir() + + stat, err := os.Stat(diagZip) + require.NoErrorf(t, err, "stat file %q failed", diagZip) + require.Greaterf(t, stat.Size(), int64(0), "file %s has incorrect size", diagZip) + + extractZipArchive(t, diagZip, extractionDir) + + expectedFiles := []string{ + "edot/otel-merged-actual.yaml", + "edot/allocs.profile.gz", + "edot/block.profile.gz", + "edot/goroutine.profile.gz", + "edot/heap.profile.gz", + "edot/mutex.profile.gz", + "edot/threadcreate.profile.gz", + "components/filestream-default/registry.tar.gz", + "components/filestream-default/beat_metrics.json", + "components/filestream-default/input_metrics.json", + } + + for _, f := range expectedFiles { + path := filepath.Join(extractionDir, f) + stat, err := os.Stat(path) + require.NoErrorf(t, err, "stat file %q failed", path) + require.Greaterf(t, stat.Size(), int64(0), "file %s has incorrect size", path) + } + verifyFilebeatRegistry(t, filepath.Join(extractionDir, "components/filestream-default/registry.tar.gz")) +} + +>>>>>>> 7afb20069 ([edot][diagnostics] remove otel diagnostics from manager (#10415)) func testDiagnosticsFactory(t *testing.T, compSetup map[string]integrationtest.ComponentState, diagFiles []string, diagCompFiles []string, fix *integrationtest.Fixture, cmd []string) func(ctx context.Context) error { return func(ctx context.Context) error { diagZip, err := fix.ExecDiagnostics(ctx, cmd...) @@ -500,3 +734,23 @@ type filePattern struct { pattern string optional bool } + +func verifyFilebeatRegistry(t *testing.T, path string) { + data, err := os.ReadFile(path) + require.NoError(t, err) + gzReader, err := gzip.NewReader(bytes.NewReader(data)) + require.NoError(t, err) + tarReader := tar.NewReader(gzReader) + hdr, err := tarReader.Next() + require.NoError(t, err) + assert.Equal(t, "registry", hdr.Name) + hdr, err = tarReader.Next() + require.NoError(t, err) + assert.Equal(t, filepath.Join("registry", "filebeat"), hdr.Name) + hdr, err = tarReader.Next() + require.NoError(t, err) + assert.Equal(t, filepath.Join("registry", "filebeat", "log.json"), hdr.Name) + hdr, err = tarReader.Next() + require.NoError(t, err) + assert.Equal(t, filepath.Join("registry", "filebeat", "meta.json"), hdr.Name) +}