Skip to content

Commit

Permalink
[8.9](backport #2834 and #3164) Enhance mock fleet-server and add --p…
Browse files Browse the repository at this point in the history
…roxy-url integration test and disable tests (#3147)

* enhance mock fleet-server and add --proxy-url integration test  (#2834)

* enhance test fleet-server
Now a almost fully functional mock fleet-server can be instantiated with a single call to fleetservertest.NewServerWithHandlers. The only missing handlers are the upload handlers.
testing/integration/proxy_url_test.go works as example on how to use the new features of  the test fleet-server

* add proxytest.Proxy:
A naive proxy to be used on tests which allows to configure URL rewrites and check all the calls made to it.
check the tests on testing/integration/proxy_url_test.go to see how to use proxytest.Proxy.

* add integration tests for the fleet-server proxy
The tests cover defining a proxy through --proxy-url and in the policy and the correct priority is respected

* fix the Go version used on the Github workflows
now it reads the version from the .go-version and not from the go.mod anymore.

* increase the memory allocated to the elastic-agent vagrant box

* allow -SNAPHOT versions to be passed to define.NewFixture

* add more helper methods to testing.Fixture and Install accepts more flags

* add version.Agent, a exported constant with the agent version

(cherry picked from commit 732d7c0)

* Disable proxy tests while flakiness is addressed. (#3164)

All of the proxy tests are currently flaky and fail regularly, see
#3154

Disabling them while we investigate a fix to unblock CI.

(cherry picked from commit 08d24b9)
---------

Co-authored-by: Anderson Queiroz <[email protected]>
Co-authored-by: Craig MacKenzie <[email protected]>
  • Loading branch information
3 people authored Aug 16, 2023
1 parent 72c317d commit 05c5479
Show file tree
Hide file tree
Showing 21 changed files with 2,763 additions and 348 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ jobs:
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis

- name: Extract Go version from .go-version
run: echo "GO_VERSION=$(cat .go-version)" >> $GITHUB_ENV
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version-file: 'go.mod'
go-version: "${{ env.GO_VERSION }}"
cache: true

- name: Go cache
Expand Down
2 changes: 1 addition & 1 deletion Vagrantfile
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ Vagrant.configure("2") do |config|
vb.gui = false
vb.customize ["modifyvm", :id, "--vram", "128"]
# Customize the amount of memory on the VM:
vb.memory = "2048"
vb.memory = "4096"
end

nodeconfig.vm.provision "shell", inline: <<-SHELL
Expand Down
14 changes: 14 additions & 0 deletions docs/test-framework-dev-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,20 @@ between, and it can be very specific or not very specific.
> **_NOTE:_** This only filters down the tests based on the platform. It will not execute a tests on a platform unless
> the test defines as supporting it.
### Manually running the tests

If you want to run the tests manually, skipping the test runner, set the
`TEST_DEFINE_PREFIX` environment variable to any value and run your tests normally
with `go test`. E.g.:

```shell
TEST_DEFINE_PREFIX=gambiarra go test -v -tags integration -run TestProxyURL ./testing/integration/
```

Tests with external dependencies might need more environment variables to be set
when running them manually, such as `ELASTICSEARCH_HOST`, `ELASTICSEARCH_USERNAME`,
`ELASTICSEARCH_PASSWORD`, `KIBANA_HOST`, `KIBANA_USERNAME`, and `KIBANA_PASSWORD`.

#### Passing additional go test flags

When running the tests we can pass additional go test flag using the env variable `GOTEST_FLAGS`.
Expand Down
18 changes: 9 additions & 9 deletions magefile.go
Original file line number Diff line number Diff line change
Expand Up @@ -1290,7 +1290,7 @@ func majorMinor() string {
return ""
}

// cleans up the integration testing leftovers
// Clean cleans up the integration testing leftovers
func (Integration) Clean() error {
_ = os.RemoveAll(".agent-testing")

Expand All @@ -1314,13 +1314,13 @@ func (Integration) Clean() error {
return nil
}

// checks that integration tests are using define.Require
// Check checks that integration tests are using define.Require
func (Integration) Check() error {
fmt.Println(">> check: Checking for define.Require in integration tests") // nolint:forbidigo // it's ok to use fmt.println in mage
return define.ValidateDir("testing/integration")
}

// runs only the integration tests that support local mode
// Local runs only the integration tests that support local mode
// it takes as argument the test name to run or all if we want to run them all.
func (Integration) Local(ctx context.Context, testName string) error {
if shouldBuildAgent() {
Expand Down Expand Up @@ -1349,7 +1349,7 @@ func (Integration) Local(ctx context.Context, testName string) error {
return devtools.GoTest(ctx, params)
}

// authenticates users who run it to various IaaS CSPs and ESS
// Auth authenticates users who run it to various IaaS CSPs and ESS
func (Integration) Auth(ctx context.Context) error {
if err := authGCP(ctx); err != nil {
return fmt.Errorf("unable to authenticate to GCP: %w", err)
Expand All @@ -1368,27 +1368,27 @@ func (Integration) Auth(ctx context.Context) error {
return nil
}

// run integration tests on remote hosts
// Test runs integration tests on remote hosts
func (Integration) Test(ctx context.Context) error {
return integRunner(ctx, false, "")
}

// run integration tests on a matrix of all supported remote hosts
// Matrix runs integration tests on a matrix of all supported remote hosts
func (Integration) Matrix(ctx context.Context) error {
return integRunner(ctx, true, "")
}

// run single integration test on remote host
// Single runs single integration test on remote host
func (Integration) Single(ctx context.Context, testName string) error {
return integRunner(ctx, false, testName)
}

// don't call locally (called on remote host to prepare it for testing)
// PrepareOnRemote shouldn't be called locally (called on remote host to prepare it for testing)
func (Integration) PrepareOnRemote() {
mg.Deps(mage.InstallGoTestTools)
}

// don't call locally (called on remote host to perform testing)
// TestOnRemote shouldn't be called locally (called on remote host to perform testing)
func (Integration) TestOnRemote(ctx context.Context) error {
mg.Deps(Build.TestBinaries)
version := os.Getenv("AGENT_VERSION")
Expand Down
14 changes: 13 additions & 1 deletion pkg/testing/define/define.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

atesting "github.com/elastic/elastic-agent/pkg/testing"
"github.com/elastic/elastic-agent/pkg/utils"
semver "github.com/elastic/elastic-agent/pkg/version"
"github.com/elastic/elastic-agent/version"
)

Expand Down Expand Up @@ -77,7 +78,18 @@ func NewFixture(t *testing.T, version string, opts ...atesting.FixtureOpt) (*ate
buildsDir = filepath.Join(projectDir, "build", "distributions")
}

f := atesting.LocalFetcher(buildsDir)
ver, err := semver.ParseVersion(version)
if err != nil {
return nil, fmt.Errorf("%q is an invalid agent version: %w", version, err)
}

var f atesting.Fetcher
if ver.IsSnapshot() {
f = atesting.LocalFetcher(buildsDir, atesting.WithLocalSnapshotOnly())
} else {
f = atesting.LocalFetcher(buildsDir)
}

opts = append(opts, atesting.WithFetcher(f), atesting.WithLogOutput())
return atesting.NewFixture(t, version, opts...)
}
Expand Down
214 changes: 212 additions & 2 deletions pkg/testing/fixture.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package testing

import (
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
Expand All @@ -18,14 +19,15 @@ import (
"testing"
"time"

"github.com/elastic/elastic-agent/internal/pkg/agent/application/paths"

"github.com/hashicorp/go-multierror"
"github.com/otiai10/copy"
"gopkg.in/yaml.v2"

"github.com/elastic/elastic-agent/internal/pkg/agent/application/paths"
"github.com/elastic/elastic-agent/pkg/component"
"github.com/elastic/elastic-agent/pkg/control"
"github.com/elastic/elastic-agent/pkg/control/v2/client"
"github.com/elastic/elastic-agent/pkg/control/v2/cproto"
"github.com/elastic/elastic-agent/pkg/core/process"
)

Expand Down Expand Up @@ -360,6 +362,91 @@ func (f *Fixture) Exec(ctx context.Context, args []string, opts ...process.CmdOp
return cmd.CombinedOutput()
}

type ExecErr struct {
err error
Output []byte
}

func (e *ExecErr) Error() string {
return e.err.Error()
}

func (e *ExecErr) String() string {
return fmt.Sprintf("error: %v, output: %s", e.err, e.Output)
}

func (e *ExecErr) As(target any) bool {
switch target.(type) {
case *ExecErr:
target = e
return true
case ExecErr:
target = *e
return true
default:
return errors.As(e.err, &target)
}
}

func (e *ExecErr) Unwrap() error {
return e.err
}

// ExecStatus executes the status subcommand on the prepared Elastic Agent binary.
// It returns the parsed output and the error from the execution. Keep in mind
// the agent exits with status 1 if it's unhealthy, but it still outputs the
// status successfully. Therefore, a not empty AgentStatusOutput is valid
// regardless of the error. An empty AgentStatusOutput and non nil error
// means the output could not be parsed.
// It should work with any 8.6+ agent
func (f *Fixture) ExecStatus(ctx context.Context, opts ...process.CmdOption) (AgentStatusOutput, error) {
out, err := f.Exec(ctx, []string{"status", "--output", "json"}, opts...)
status := AgentStatusOutput{}
if uerr := json.Unmarshal(out, &status); uerr != nil {
return AgentStatusOutput{}, multierror.Append(
fmt.Errorf("could not unmarshal agent status output: %w", uerr),
&ExecErr{
err: err,
Output: out,
})
}

return status, err
}

// ExecInspect executes to inspect subcommand on the prepared Elastic Agent binary.
// It returns the parsed output and the error from the execution or an empty
// AgentInspectOutput and the unmarshalling error if it cannot unmarshal the
// output.
// It should work with any 8.6+ agent
func (f *Fixture) ExecInspect(ctx context.Context, opts ...process.CmdOption) (AgentInspectOutput, error) {
out, err := f.Exec(ctx, []string{"inspect"}, opts...)
inspect := AgentInspectOutput{}
if uerr := yaml.Unmarshal(out, &inspect); uerr != nil {
return AgentInspectOutput{},
multierror.Append(
fmt.Errorf("could not unmarshal agent inspect output: %w", uerr),
&ExecErr{
err: err,
Output: out,
})
}

return inspect, err
}

// IsHealthy returns if the prepared Elastic Agent reports itself as healthy.
// It returns false, err if it cannot determine the state of the agent.
// It should work with any 8.6+ agent
func (f *Fixture) IsHealthy(ctx context.Context, opts ...process.CmdOption) (bool, error) {
status, err := f.ExecStatus(ctx, opts...)
if err != nil {
return false, fmt.Errorf("agent status returned and error: %w", err)
}

return status.State == int(cproto.State_HEALTHY), nil
}

func (f *Fixture) ensurePrepared(ctx context.Context) error {
if f.workDir == "" {
return f.Prepare(ctx)
Expand Down Expand Up @@ -685,3 +772,126 @@ func performConfigure(ctx context.Context, c client.Client, cfg string, timeout
}
return nil
}

type AgentStatusOutput struct {
Info struct {
ID string `json:"id"`
Version string `json:"version"`
Commit string `json:"commit"`
BuildTime string `json:"build_time"`
Snapshot bool `json:"snapshot"`
} `json:"info"`
State int `json:"state"`
Message string `json:"message"`
Components []struct {
ID string `json:"id"`
Name string `json:"name"`
State int `json:"state"`
Message string `json:"message"`
Units []struct {
UnitID string `json:"unit_id"`
UnitType int `json:"unit_type"`
State int `json:"state"`
Message string `json:"message"`
Payload struct {
OsqueryVersion string `json:"osquery_version"`
} `json:"payload"`
} `json:"units"`
VersionInfo struct {
Name string `json:"name"`
Version string `json:"version"`
Meta struct {
BuildTime string `json:"build_time"`
Commit string `json:"commit"`
} `json:"meta"`
} `json:"version_info,omitempty"`
} `json:"components"`
FleetState int `json:"FleetState"`
FleetMessage string `json:"FleetMessage"`
}

type AgentInspectOutput struct {
Agent struct {
Download struct {
SourceURI string `yaml:"sourceURI"`
} `yaml:"download"`
Features interface{} `yaml:"features"`
Headers interface{} `yaml:"headers"`
ID string `yaml:"id"`
Logging struct {
Level string `yaml:"level"`
} `yaml:"logging"`
Monitoring struct {
Enabled bool `yaml:"enabled"`
HTTP struct {
Buffer interface{} `yaml:"buffer"`
Enabled bool `yaml:"enabled"`
Host string `yaml:"host"`
Port int `yaml:"port"`
} `yaml:"http"`
Logs bool `yaml:"logs"`
Metrics bool `yaml:"metrics"`
Namespace string `yaml:"namespace"`
UseOutput string `yaml:"use_output"`
} `yaml:"monitoring"`
Protection struct {
Enabled bool `yaml:"enabled"`
SigningKey string `yaml:"signing_key"`
UninstallTokenHash string `yaml:"uninstall_token_hash"`
} `yaml:"protection"`
} `yaml:"agent"`
Fleet struct {
AccessAPIKey string `yaml:"access_api_key"`
Agent struct {
ID string `yaml:"id"`
} `yaml:"agent"`
Enabled bool `yaml:"enabled"`
Host string `yaml:"host"`
Hosts []string `yaml:"hosts"`
Protocol string `yaml:"protocol"`
ProxyURL string `yaml:"proxy_url"`
Reporting struct {
CheckFrequencySec int `yaml:"check_frequency_sec"`
Threshold int `yaml:"threshold"`
} `yaml:"reporting"`
Ssl struct {
Renegotiation string `yaml:"renegotiation"`
VerificationMode string `yaml:"verification_mode"`
} `yaml:"ssl"`
Timeout string `yaml:"timeout"`
} `yaml:"fleet"`
Host struct {
ID string `yaml:"id"`
} `yaml:"host"`
ID string `yaml:"id"`
Inputs interface{} `yaml:"inputs"`
Outputs struct {
Default struct {
APIKey string `yaml:"api_key"`
Hosts []string `yaml:"hosts"`
Type string `yaml:"type"`
} `yaml:"default"`
} `yaml:"outputs"`
Path struct {
Config string `yaml:"config"`
Data string `yaml:"data"`
Home string `yaml:"home"`
Logs string `yaml:"logs"`
} `yaml:"path"`
Revision int `yaml:"revision"`
Runtime struct {
Arch string `yaml:"arch"`
Os string `yaml:"os"`
Osinfo struct {
Family string `yaml:"family"`
Major int `yaml:"major"`
Minor int `yaml:"minor"`
Patch int `yaml:"patch"`
Type string `yaml:"type"`
Version string `yaml:"version"`
} `yaml:"osinfo"`
} `yaml:"runtime"`
Signed struct {
Data string `yaml:"data"`
} `yaml:"signed"`
}
Loading

0 comments on commit 05c5479

Please sign in to comment.