Skip to content
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
1 change: 1 addition & 0 deletions docs/howto/system_testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@ for system tests.
| Option | Type | Required | Description |
|---|---|---|---|
| data_stream.vars | dictionary | | Data stream level variables to set (i.e. declared in `package_root/data_stream/$data_stream/manifest.yml`). If not specified the defaults from the manifest are used. |
| ignore_service_error | boolean | no | If `true`, it will ignore any failures in the deployed test services. Defaults to `false`. |
| input | string | yes | Input type to test (e.g. logfile, httpjson, etc). Defaults to the input used by the first stream in the data stream manifest. |
| numeric_keyword_fields | []string | | List of fields to ignore during validation that are mapped as `keyword` in Elasticsearch, but their JSON data type is a number. |
| policy_template | string | | Name of policy template associated with the data stream and input. Required when multiple policy templates include the input being tested. |
Expand Down
37 changes: 35 additions & 2 deletions internal/compose/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,7 @@ func (p *Project) Logs(opts CommandOptions) ([]byte, error) {
func (p *Project) WaitForHealthy(opts CommandOptions) error {
// Read container IDs
args := p.baseArgs()
args = append(args, "ps")
args = append(args, "-q")
args = append(args, "ps", "-a", "-q")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I encountered false positive failures for service containers that successfully finished but did so before reaching this point, so they would not be listed without -a. Since exited but with 0 is considered healthy down the line, this is a requirement for these containers to show.


var b bytes.Buffer
if err := p.runDockerComposeCmd(dockerComposeOptions{args: args, env: opts.Env, stdout: &b}); err != nil {
Expand Down Expand Up @@ -387,6 +386,40 @@ func (p *Project) WaitForHealthy(opts CommandOptions) error {
return nil
}

// ServiceExitCode returns true if the specified service is exited with an error.
func (p *Project) ServiceExitCode(service string, opts CommandOptions) (bool, int, error) {
// Read container IDs
args := p.baseArgs()
args = append(args, "ps", "-a", "-q", service)

var b bytes.Buffer
if err := p.runDockerComposeCmd(dockerComposeOptions{args: args, env: opts.Env, stdout: &b}); err != nil {
return false, -1, err
}

containerIDs := strings.Fields(b.String())
if len(containerIDs) != 1 {
return false, -1, fmt.Errorf("expected to find one service container named: %s, found: %d", service, len(containerIDs))
}
containerID := containerIDs[0]

containerDescriptions, err := docker.InspectContainers(containerID)
if err != nil {
return false, -1, err
}
if len(containerDescriptions) != 1 {
return false, -1, fmt.Errorf("expected to get one service status, found: %d", len(containerIDs))
}
containerDescription := containerDescriptions[0]

// Container exited with code > 0
if containerDescription.State.Status == "exited" {
return true, containerDescription.State.ExitCode, nil
}

return false, -1, nil
}

func (p *Project) baseArgs() []string {
var args []string
for _, path := range p.composeFilePaths {
Expand Down
16 changes: 16 additions & 0 deletions internal/servicedeployer/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,22 @@ func (s *dockerComposeDeployedService) Signal(signal string) error {
return nil
}

// ExitCode returns true if the service is exited and its exit code.
func (s *dockerComposeDeployedService) ExitCode(service string) (bool, int, error) {
p, err := compose.NewProject(s.project, s.ymlPaths...)
if err != nil {
return false, -1, fmt.Errorf("could not create Docker Compose project for service: %w", err)
}

opts := compose.CommandOptions{
Env: append(
s.env,
s.variant.Env...),
}

return p.ServiceExitCode(service, opts)
}

// TearDown tears down the service.
func (s *dockerComposeDeployedService) TearDown() error {
logger.Debugf("tearing down service using Docker Compose runner")
Expand Down
7 changes: 7 additions & 0 deletions internal/servicedeployer/deployed_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

package servicedeployer

import "errors"

var ErrNotSupported error = errors.New("not supported")

// DeployedService defines the interface for interacting with a service that has been deployed.
type DeployedService interface {
// TearDown implements the logic for tearing down a service.
Expand All @@ -17,4 +21,7 @@ type DeployedService interface {

// SetContext sets the current context for the service.
SetContext(str ServiceContext) error

// ExitCode returns true if the service is exited and its exit code.
ExitCode(service string) (bool, int, error)
}
7 changes: 5 additions & 2 deletions internal/servicedeployer/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"bytes"
_ "embed"
"encoding/base64"
"errors"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -57,7 +56,11 @@ func (s kubernetesDeployedService) TearDown() error {
}

func (s kubernetesDeployedService) Signal(_ string) error {
return errors.New("signal is not supported")
return ErrNotSupported
}

func (s kubernetesDeployedService) ExitCode(_ string) (bool, int, error) {
return false, -1, ErrNotSupported
}

func (s kubernetesDeployedService) Context() ServiceContext {
Expand Down
10 changes: 10 additions & 0 deletions internal/testrunner/runners/system/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,16 @@ func (r *runner) runTest(config *testConfig, ctxt servicedeployer.ServiceContext
return result.WithError(err)
}

if config.Service != "" && !config.IgnoreServiceError {
exited, code, err := service.ExitCode(config.Service)
if err != nil && !errors.Is(err, servicedeployer.ErrNotSupported) {
return result.WithError(err)
}
if exited && code > 0 {
result.FailureMsg = fmt.Sprintf("the test service %s unexpectedly exited with code %d", config.Service, code)
}
}

return result.WithSuccess()
}

Expand Down
1 change: 1 addition & 0 deletions internal/testrunner/runners/system/test_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type testConfig struct {
PolicyTemplate string `config:"policy_template"` // Policy template associated with input. Required when multiple policy templates include the input being tested.
Service string `config:"service"`
ServiceNotifySignal string `config:"service_notify_signal"` // Signal to send when the agent policy is applied.
IgnoreServiceError bool `config:"ignore_service_error"`
WaitForDataTimeout time.Duration `config:"wait_for_data_timeout"`

Vars common.MapStr `config:"vars"`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<testcase name=\"system test: fail\" classname=\"docker_failing_test_service.log\" time=\".*\"> * <failure>the test service failing unexpectedly exited with code 1</failure>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dependencies:
ecs:
reference: git@v8.9.0
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Test integration
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# newer versions go on top
- version: "999.999.999"
changes:
- description: Test
type: enhancement
link: https://github.com/elastic/integrations/pull/99999
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: '2.3'
services:
failing:
image: alpine
volumes:
- ./logs:/logs:ro
- ${SERVICE_LOGS_DIR}:/var/log
command: /bin/sh -c "echo \"Sleep 10s...\"; sleep 10; echo \"Copying files...\"; cp /logs/* /var/log/; echo \"Done.\"; sleep 5; exit 1"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
foo
bar
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
input: logfile
service: failing
ignore_service_error: false
data_stream:
vars:
paths:
- "{{SERVICE_LOGS_DIR}}/*.log"
assert:
hit_count: 2
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
paths:
{{#each paths as |path i|}}
- {{path}}
{{/each}}
exclude_files: [".gz$"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
description: "Pipeline for Cisco ASA logs"
processors:
- set:
field: ecs.version
value: '8.9.0'
Loading