Skip to content

Commit

Permalink
Merge branch 'main' into bump/go1.23
Browse files Browse the repository at this point in the history
  • Loading branch information
kruskall authored Sep 13, 2024
2 parents 1049529 + d99b09b commit 09dfd99
Show file tree
Hide file tree
Showing 10 changed files with 400 additions and 95 deletions.
7 changes: 4 additions & 3 deletions .mergify.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
queue_rules:
- name: default
merge_method: squash
conditions:
- check-success=fleet-ci/pr-merge
pull_request_rules:
Expand Down Expand Up @@ -85,7 +86,6 @@ pull_request_rules:
- "#approved-reviews-by>=1"
actions:
queue:
method: squash
name: default
- name: delete upstream branch with changes on ^.mergify.yml that has been merged or closed
conditions:
Expand Down Expand Up @@ -123,16 +123,17 @@ pull_request_rules:
To fixup this pull request, you need to add the backport labels for the needed
branches, such as:
* `backport-./d./d` is the label to automatically backport to the `8./d` branch. `/d` is the digit
- name: add backport-8.x label for main only
- name: add backport-8.x label for main only if no skipped or assigned already
conditions:
- -label~=^backport-8.x
- -label~=^(backport-skip|backport-8.x)$
- base=main
- -merged
- -closed
actions:
comment:
message: |
`backport-v8.x` has been added to help with the transition to the new branch `8.x`.
If you don't need it please use `backport-skip` label and remove the `backport-8.x` label.
label:
add:
- backport-8.x
Expand Down
136 changes: 79 additions & 57 deletions docs/test-framework-dev-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,60 +141,118 @@ when running them manually, such as `ELASTICSEARCH_HOST`, `ELASTICSEARCH_USERNAM

### Debugging tests

#### Manually debugging tests on VMs
Many of the integration tests will install the Elastic-Agent and/or
require root to run, which makes it hard to just run them on our work
machines, the best way to circumvent that is to debug the tests
directly on a VM. `mage integration:DeployDebugTools` will show a menu
to select a VM and then install the common debugging tools: Delve,
Mage and Docker. It will also create the `~/elastic-agent` folder
containing the Git repository (required to package from within the VM)
#### Connecting to VMs
All VMs (including Windows) support connections via SSH, the framework
generates and stores the necessary SSH keys to access the VMs, the
easiest way to connect to them is using the SSH command returned by
`mage integration:SSH`. It will list the VMs and ask to select
one.

On a Unix shell you can run `$(mage integration:SSH)`, the menu is
printed to stderr and the SSH command to stdout. After selecting the
VM you will have shell connected to it.

#### Credentials for cloud stack/projects
All cloud deployments and projects can be listed with `mage
integration:listStacks`, they can be used to manually connect to
Kibana and Elasticsearch.

If you need to manually run tests against any deployments, `mage
integration:GenerateEnvFile` will generate a file called `env.sh` that
exports environment variables for Unix compatible shells, you can load
them into your shell by running `source ./env.sh`.

To easily deploy the credentials to any VM, just run `mage
integration:DeployEnvFile`. A menu will ask for the desired Stack and
VM.

#### Manually running the tests (using `go test`)
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/
```

You will need the environment variables containing the stack URL and
credentials for the tests to succeed.

#### Installing debug/build tools
`mage integration:DeployDebugTools` will install a few tools necessary
to build the Elastic-Agent in the VM and debug tests:
- Docker
- Delve
- Mage

When called, it will show a menu to select a VM and then install the
tools listed above. It will also create the `~/elastic-agent` folder
containing the Git repository (required o package from within the VM)
and the last version of the code uploaded to the VM. This allows you
to easily build/package the Elastic-Agent from within the VM as well
as run any tests.

After deploying the debug tools, `mage integrationDeployEnvFile` will
create a `env.sh` and copy it to a selected VM, sourcing it will allow
you to any test against the Cloud Stack you selected.
In the VM there are two important folders:
- `agent`: that is created by the integration test framework and used
by `mage` to run the tests, it gets updated every time you run an
integration test from your machine.
- `elastic-agen`: that is a copy `agent` with Git information created
by `mage integration:DeployDebugTools`, the Git information there is
not a copy from your machine, but it will work if you need to
package the Elastic-Agent from the VM. Most of the time you won't
need it.

Example of how to run a test from within the VM:
```
## Run a single test
SNAPSHOT=true TEST_PLATFORMS="linux/amd64" mage integration:single TestLogIngestionFleetManaged
## Let's suppose it has failed
**Step-by-Step commands**

```shell
## Install DebugTools
mage -v integration:DeployDebugTools

## Generate and deploy env file
mage -v integration:DeployEnvFile

## SSH into the VM
$(mage integration:SSHVM)
$(mage integration:SSH)

## From inside the VM, the test needs root
sudo su
source ./env.sh
cd elastic-agent
cd agent # That's the folder the mage automation uses to run the tests

## Then run the test using `go test`

TEST_DEFINE_PREFIX=gambiarra AGENT_VERSION="8.16.0-SNAPSHOT" go test -tags=integration -v ./testing/integration/ -run TestLogIngestionFleetManaged

## Run the test using delve:
## Any flags passed to the test binary go after the '--', they also need to
## include the `test.` prefix if they're for `go test`
TEST_DEFINE_PREFIX=gambiarra dlv test ./testing/integration/ --build-flags="-tags integration" -- -test.v -test.run TestLogIngestionFleetManaged
```

**A Delve trick**
**A Delve trick:**
If you didn't build the Elastic-Agent directly on the machine you're
debugging, it is very likely the location of the source code is
different, hence delve cannot show you the code it is running. To
solve this, once on Delve shell, run:
``
config substitute-path /go/src/github.com/elastic/elastic-agent /home/ubuntu/elastic-agent`
config substitute-path /go/src/github.com/elastic/elastic-agent /home/ubuntu/agent
``
where:
- `/go/src/github.com/elastic/elastic-agent` is the path annotated in
the binary you are debugging (the one Delve shows).
- `/home/ubuntu/elastic-agent` is where Delve should read the source
- `/home/ubuntu/agent` is where Delve should read the source
code form.

#### Other useful mage targets:
- `integration:stacks` lists all stack deployments and connection
information in a human readable table.
- `integration:listInstances` lists all VMs and their connection
command in a human readable table. It also lists the URL for the
VM page on GCP, which is helpful to verify if the VM still exists
(OGC VMs are automatically deleted)
- `integration:printState` is a shortcut for running the two commands
above.

#### Auto diagnostics retrieval
When an integration test fails the testing fixture will try its best to automatically collect the diagnostic
information of the installed Elastic Agent. In the case that diagnostics is collected the test runner will
Expand Down Expand Up @@ -223,42 +281,6 @@ until it reports a failure.

- `TEST_RUN_UNTIL_FAILURE=true mage integration:single [testName]`

## 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/
```

## Connecting to VMs and running tests
### Connecting to VMs
All VMs (including Windows) support connections via SSH, the framework
generates and stores the necessary SSH keys to access the VMs, the
easiest way to connect to them is using the SSH command returned by
`mage integration:SSHVM`. It will list the VMs and ask to select
one.

On a Unix shell you can run `$(mage integration:SSHVM)`, the menu is
printed to stderr and the SSH command to stdout. After selecting the
VM you will have shell connected to it.

### Credentials for cloud stack/projects
All cloud deployments and projects can be listed with `mage
integration:stacks`, they can be used to manually connect to
Kibana and Elasticsearch.

If you need to manually run tests against any deployments, `mage
integration:GenerateEnvFile` will generate a file called `env.sh` that
exports environment variables for Unix compatible shells, you can load
them into your shell by running `source ./env.sh`.

To easily deploy the credentials to any VM, just run `mage
integration:DeployEnvFile`. A menu will ask for the desired Stack and
VM.

## Writing tests

Write integration and E2E tests by adding them to the `testing/integration`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ func prepareTestCase(t *testing.T, a artifact.Artifact, version *agtversion.Pars
err = os.WriteFile(filePathSHA, []byte(hashContent), 0644)
require.NoErrorf(t, err, "could not write %q file", filePathSHA)

pub, sig := pgptest.Sing(t, bytes.NewReader(content))
pub, sig := pgptest.Sign(t, bytes.NewReader(content))
err = os.WriteFile(filePathASC, sig, 0644)
require.NoErrorf(t, err, "could not write %q file", filePathASC)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func getElasticCoServer(t *testing.T) (*httptest.Server, []byte) {
var resp []byte
content := []byte("anything will do")
hash := sha512.Sum512(content)
pub, sig := pgptest.Sing(t, bytes.NewReader(content))
pub, sig := pgptest.Sign(t, bytes.NewReader(content))

handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
packageName := r.URL.Path[len(sourcePattern):]
Expand Down
77 changes: 71 additions & 6 deletions pkg/testing/fixture.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ type Fixture struct {

// Uninstall token value that is needed for the agent uninstall if it's tamper protected
uninstallToken string

// fileNamePrefix is a prefix to be used when saving files from this test.
// it's set by FileNamePrefix and once it's set, FileNamePrefix will return
// its value.
fileNamePrefix string
}

// FixtureOpt is an option for the fixture.
Expand Down Expand Up @@ -1010,16 +1015,13 @@ func (f *Fixture) setClient(c client.Client) {

func (f *Fixture) DumpProcesses(suffix string) {
procs := getProcesses(f.t, `.*`)
dir, err := findProjectRoot(f.caller)
dir, err := f.DiagnosticsDir()
if err != nil {
f.t.Logf("failed to dump process; failed to find project root: %s", err)
f.t.Logf("failed to dump process: %s", err)
return
}

// Sub-test names are separated by "/" characters which are not valid filenames on Linux.
sanitizedTestName := strings.ReplaceAll(f.t.Name(), "/", "-")

filePath := filepath.Join(dir, "build", "diagnostics", fmt.Sprintf("TEST-%s-%s-%s-ProcessDump%s.json", sanitizedTestName, f.operatingSystem, f.architecture, suffix))
filePath := filepath.Join(dir, fmt.Sprintf("%s-ProcessDump%s.json", f.FileNamePrefix(), suffix))
fileDir := path.Dir(filePath)
if err := os.MkdirAll(fileDir, 0777); err != nil {
f.t.Logf("failed to dump process; failed to create directory %s: %s", fileDir, err)
Expand All @@ -1044,6 +1046,69 @@ func (f *Fixture) DumpProcesses(suffix string) {
}
}

// MoveToDiagnosticsDir moves file to 'build/diagnostics' which contents are
// available on CI if the test fails or on the agent's 'build/diagnostics'
// if the test is run locally.
// If the file name does nos start with Fixture.FileNamePrefix(), it'll be added
// to the filename when moving.
func (f *Fixture) MoveToDiagnosticsDir(file string) {
dir, err := f.DiagnosticsDir()
if err != nil {
f.t.Logf("failed to move file to diagnostcs directory: %s", err)
return
}

filename := filepath.Base(file)
if !strings.HasPrefix(filename, f.FileNamePrefix()) {
filename = fmt.Sprintf("%s-%s", f.FileNamePrefix(), filename)
}
destFile := filepath.Join(dir, filename)

f.t.Logf("moving %q to %q", file, destFile)
err = os.Rename(file, destFile)
if err != nil {
f.t.Logf("failed to move %q to %q: %v", file, destFile, err)
}
}

// FileNamePrefix returns a sanitized and unique name to be used as prefix for
// files to be kept as resources for investigation when the test fails.
func (f *Fixture) FileNamePrefix() string {
if f.fileNamePrefix != "" {
return f.fileNamePrefix
}

stamp := time.Now().Format(time.RFC3339)
// on Windows a filename cannot contain a ':' as this collides with disk
// labels (aka. C:\)
stamp = strings.ReplaceAll(stamp, ":", "-")

// Subtest names are separated by "/" characters which are not valid
// filenames on Linux.
sanitizedTestName := strings.ReplaceAll(f.t.Name(), "/", "-")
prefix := fmt.Sprintf("%s-%s", sanitizedTestName, stamp)

f.fileNamePrefix = prefix
return f.fileNamePrefix
}

// DiagnosticsDir returned {projectRoot}/build/diagnostics path. Files on this path
// are saved if any test fails. Use it to save files for further investigation.
func (f *Fixture) DiagnosticsDir() (string, error) {
dir, err := findProjectRoot(f.caller)
if err != nil {
return "", fmt.Errorf("failed to find project root: %w", err)
}

diagPath := filepath.Join(dir, "build", "diagnostics")

if err := os.MkdirAll(diagPath, 0777); err != nil {
return "", fmt.Errorf("failed to create directory %s: %w", diagPath, err)
}

return diagPath, nil
}

// validateComponents ensures that the provided UsableComponent's are valid.
func validateComponents(components ...UsableComponent) error {
for idx, comp := range components {
Expand Down
28 changes: 4 additions & 24 deletions pkg/testing/fixture_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,7 @@ func (f *Fixture) collectDiagnostics() {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()

diagPath, err := f.DiagDir()
diagPath, err := f.DiagnosticsDir()
if err != nil {
f.t.Logf("failed to collect diagnostics: %v", err)
return
Expand All @@ -661,15 +661,8 @@ func (f *Fixture) collectDiagnostics() {
return
}

stamp := time.Now().Format(time.RFC3339)
if runtime.GOOS == "windows" {
// on Windows a filename cannot contain a ':' as this collides with disk labels (aka. C:\)
stamp = strings.ReplaceAll(stamp, ":", "-")
}

// Sub-test names are separated by "/" characters which are not valid filenames on Linux.
sanitizedTestName := strings.ReplaceAll(f.t.Name(), "/", "-")
outputPath := filepath.Join(diagPath, fmt.Sprintf("%s-diagnostics-%s.zip", sanitizedTestName, stamp))
prefix := f.FileNamePrefix()
outputPath := filepath.Join(diagPath, prefix+"-diagnostics.zip")

output, err := f.Exec(ctx, []string{"diagnostics", "-f", outputPath})
if err != nil {
Expand All @@ -689,8 +682,7 @@ func (f *Fixture) collectDiagnostics() {
if err != nil {
// If collecting diagnostics fails, zip up the entire installation directory with the hope that it will contain logs.
f.t.Logf("creating zip archive of the installation directory: %s", f.workDir)
timestamp := strings.ReplaceAll(time.Now().Format(time.RFC3339), ":", "-")
zipPath := filepath.Join(diagPath, fmt.Sprintf("%s-install-directory-%s.zip", sanitizedTestName, timestamp))
zipPath := filepath.Join(diagPath, fmt.Sprintf("%s-install-directory.zip", prefix))
err = f.archiveInstallDirectory(f.workDir, zipPath)
if err != nil {
f.t.Logf("failed to zip install directory to %s: %s", zipPath, err)
Expand All @@ -699,18 +691,6 @@ func (f *Fixture) collectDiagnostics() {
}
}

// DiagDir returned {projectRoot}/build/diagnostics path. Files on this path
// are saved if any test fails. Use it to save files for further investigation.
func (f *Fixture) DiagDir() (string, error) {
dir, err := findProjectRoot(f.caller)
if err != nil {
return "", fmt.Errorf("failed to find project root: %w", err)
}

diagPath := filepath.Join(dir, "build", "diagnostics")
return diagPath, nil
}

func (f *Fixture) archiveInstallDirectory(installPath string, outputPath string) error {
file, err := os.Create(outputPath)
if err != nil {
Expand Down
Loading

0 comments on commit 09dfd99

Please sign in to comment.