Skip to content
This repository has been archived by the owner on Sep 17, 2024. It is now read-only.

feat: add first scenario for Fleet Server #900

Merged
merged 30 commits into from
Apr 19, 2021
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b1931c0
chore: capture Fleet's default policy in a stronger manner
mdelapenya Mar 15, 2021
6f54550
chore: support passing the field for is_default policy
mdelapenya Mar 15, 2021
ad0d17f
chore: remove inferred type for array
mdelapenya Mar 15, 2021
e52c8b6
chore: enable fleet server in kibana config
mdelapenya Mar 15, 2021
e8bee02
chore: create fleet config struct
mdelapenya Mar 15, 2021
bb62c4f
chore: refactor enroll command logic to use the new struct
mdelapenya Mar 15, 2021
d8e57ce
chore: check if the fleet-server field exists when retrieving the policy
mdelapenya Mar 15, 2021
6189bff
chore: refactor install to support fleet-server
mdelapenya Mar 16, 2021
d9da8b0
feat: add first scenario for fleet server
mdelapenya Mar 16, 2021
830eb12
chore: add fleet server branch to the CI
mdelapenya Mar 16, 2021
eaa68f2
Merge branch 'master' into 438-fleet-server-scenarios
mdelapenya Mar 18, 2021
bc9e1ff
chore: set Then clause for the scenario
mdelapenya Mar 18, 2021
4ae1019
chore: remove step
mdelapenya Mar 18, 2021
721aa21
fix: define fallback when checking agent status
mdelapenya Mar 22, 2021
f5a9f46
chore: simplify creation of Fleet configs
mdelapenya Mar 24, 2021
eb8f013
fix: forgot to rename variable
mdelapenya Mar 24, 2021
e9edde9
Merge branch 'master' into 438-fleet-server-scenarios
mdelapenya Mar 26, 2021
356750f
WIP
mdelapenya Mar 29, 2021
f2244ad
Merge branch 'master' into 438-fleet-server-scenarios
mdelapenya Mar 29, 2021
7358928
chore: rename scenario
mdelapenya Mar 30, 2021
bcd69ee
Merge branch 'master' into 438-fleet-server-scenarios
mdelapenya Apr 6, 2021
173004f
fix: wrong merge conflicts resolution
mdelapenya Apr 6, 2021
cb57360
chore: support passing environment when running a command in a container
mdelapenya Apr 8, 2021
6865e98
chore: run elastic agent commands passing an env
mdelapenya Apr 8, 2021
6d60a6d
WIP
mdelapenya Apr 8, 2021
04c7fc8
Merge branch 'master' into 438-fleet-server-scenarios
mdelapenya Apr 15, 2021
e5ed65c
chore: separate bootstrapping an agent from connecting to a fleet ser…
mdelapenya Apr 15, 2021
09e4325
fix: use proper fleet-server flags
mdelapenya Apr 15, 2021
491eb17
Merge branch 'master' into 438-fleet-server-scenarios
adam-stokes Apr 16, 2021
a557f88
Merge branch 'master' into 438-fleet-server-scenarios
adam-stokes Apr 19, 2021
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
3 changes: 3 additions & 0 deletions .ci/.e2e-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ SUITES:
- name: "Fleet"
pullRequestFilter: " && ~debian"
tags: "fleet_mode_agent"
- name: "Fleet Server"
pullRequestFilter: " && ~debian"
tags: "fleet_server"
- name: "Endpoint Integration"
pullRequestFilter: " && ~debian"
tags: "agent_endpoint_integration"
Expand Down
1 change: 1 addition & 0 deletions e2e/_suites/fleet/configurations/kibana.config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ xpack.fleet.enabled: true
xpack.fleet.registryUrl: http://package-registry:8080
xpack.fleet.agents.enabled: true
xpack.fleet.agents.elasticsearch.host: http://elasticsearch:9200
xpack.fleet.agents.fleetServerEnabled: true
xpack.fleet.agents.kibana.host: http://kibana:5601
xpack.fleet.agents.tlsCheckDisabled: true
19 changes: 19 additions & 0 deletions e2e/_suites/fleet/features/fleet_server.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@fleet_server
Feature: Fleet Server
Scenarios for Fleet Server, where an Elasticseach and a Kibana instances are already provisioned,
so that the Agent is able to communicate with them

@start-fleet-server
Scenario Outline: Deploying the <os> fleet-server agent
When a "<os>" agent is deployed to Fleet with "tar" installer in fleet-server mode
mdelapenya marked this conversation as resolved.
Show resolved Hide resolved
Then Fleet server is enabled
Copy link
Contributor Author

@mdelapenya mdelapenya Mar 16, 2021

Choose a reason for hiding this comment

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

@EricDavisX @blakerouse I wrote this Then clause, but I'd like to know if there is another assertion that should be done here. Something like: the elastic-agent process is started, or the Fleet app in Kibana shows "FooBar" in the Fleet page, or elasticsearch contains THIS doc in THAT index. Preferredly queried by an API call.

Could you help me here in writing the right expected behaviour?

Choose a reason for hiding this comment

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

I am not too familiar with the structure of these files so I might not be fully understanding the context.

I believe your asking if its possible to known if the Fleet Server is running correct. The simplest way is to check that the Agent is reported Healthy in Kibana. That might seem to simple but the only way for the Agent running a Fleet Server to show in Kibana as healthy is if it can communicate to its local Fleet Server and that Fleet Server can write to elasticsearch.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Never mind about the code structure yet, I'm still interested in the behavior of the product without considering internal details/implementations.

With that in mind:

  • what ES query should we write to verify that?
  • is it enough to check the healthy status for the promoted-to-server agent?

Copy link
Contributor

Choose a reason for hiding this comment

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

The check can be the same as for all Agents, that it is listed as 'healthy' in the Agents API list call. A secondary check could be to assess if the Fleet Server process is running on the host, as noted in this example:

edavis-mbp:elastic-agent-8.0.0-SNAPSHOT-darwin-x86_64-infra-build edavis$ ps ax | grep elastic
SNAPSHOT-darwin-x86_64/fleet-server --agent-mode -E logging.level=info -E http.enabled=true -E http.host=unix:///Library/Elastic/Agent/data/tmp/default/fleet-server/fleet-server.sock -E logging.json=true -E logging.ecs=true -E logging.files.path=/Library/Elastic/Agent/data/elastic-agent-53d75c/logs/default -E logging.files.name=fleet-server-json.log -E logging.files.keepfiles=7 -E logging.files.permission=0640 -E logging.files.interval=1h -E path.data=/Library/Elastic/Agent/data/elastic-agent-53d75c/run/default/fleet-server--8.0.0-SNAPSHOT

This example is from macOS, but the the process name is the same.


@centos
Examples: Centos
| os |
| centos |

@debian
Examples: Debian
| os |
| debian |
77 changes: 56 additions & 21 deletions e2e/_suites/fleet/fleet.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func (fts *FleetTestSuite) beforeScenario() {
fts.Version = agentVersion

// create policy with system monitoring enabled
defaultPolicy, err := getAgentDefaultPolicy()
defaultPolicy, err := getAgentDefaultPolicy("is_default")
if err != nil {
log.WithFields(log.Fields{
"err": err,
Expand Down Expand Up @@ -158,6 +158,10 @@ func (fts *FleetTestSuite) contributeSteps(s *godog.ScenarioContext) {
s.Step(`^the policy response will be shown in the Security App$`, fts.thePolicyResponseWillBeShownInTheSecurityApp)
s.Step(`^the policy is updated to have "([^"]*)" in "([^"]*)" mode$`, fts.thePolicyIsUpdatedToHaveMode)
s.Step(`^the policy will reflect the change in the Security App$`, fts.thePolicyWillReflectTheChangeInTheSecurityApp)

// fleet server steps
s.Step(`^a "([^"]*)" agent is deployed to Fleet with "([^"]*)" installer in fleet-server mode$`, fts.anAgentIsDeployedToFleetWithInstallerInFleetMode)
s.Step(`^Fleet server is enabled$`, fts.fleetServerIsEnabled)
}

func (fts *FleetTestSuite) anStaleAgentIsDeployedToFleetWithInstaller(image, version, installerType string) error {
Expand Down Expand Up @@ -271,10 +275,15 @@ func (fts *FleetTestSuite) agentInVersion(version string) error {

// supported installers: tar, systemd
func (fts *FleetTestSuite) anAgentIsDeployedToFleetWithInstaller(image string, installerType string) error {
return fts.anAgentIsDeployedToFleetWithInstallerAndFleetServer(image, installerType, false)
}

func (fts *FleetTestSuite) anAgentIsDeployedToFleetWithInstallerAndFleetServer(image string, installerType string, isFleetServer bool) error {
log.WithFields(log.Fields{
"image": image,
"installer": installerType,
}).Trace("Deploying an agent to Fleet with base image")
"fleetServer": isFleetServer,
"image": image,
"installer": installerType,
}).Trace("Deploying an agent to Fleet with base image and fleet server")

fts.Image = image
fts.InstallerType = installerType
Expand All @@ -296,15 +305,16 @@ func (fts *FleetTestSuite) anAgentIsDeployedToFleetWithInstaller(image string, i
fts.CurrentToken = tokenJSONObject.Path("api_key").Data().(string)
fts.CurrentTokenID = tokenJSONObject.Path("id").Data().(string)

err = deployAgentToFleet(installer, containerName, fts.CurrentToken)
var fleetConfig *FleetConfig
fleetConfig, err = deployAgentToFleet(installer, containerName, fts.CurrentToken, isFleetServer)
fts.Cleanup = true
if err != nil {
return err
}

// the installation process for TAR includes the enrollment
if installer.installerType != "tar" {
err = installer.EnrollFn(fts.CurrentToken)
err = installer.EnrollFn(fleetConfig)
if err != nil {
return err
}
Expand Down Expand Up @@ -593,7 +603,7 @@ func (fts *FleetTestSuite) theAgentIsReenrolledOnTheHost() error {

installer := fts.getInstaller()

err := installer.EnrollFn(fts.CurrentToken)
err := installer.EnrollFn(NewFleetConfig(fts.CurrentToken))
if err != nil {
return err
}
Expand Down Expand Up @@ -638,7 +648,7 @@ func (fts *FleetTestSuite) thePolicyShowsTheDatasourceAdded(packageName string)
fts.Integration = integration

configurationIsPresentFn := func() error {
defaultPolicy, err := getAgentDefaultPolicy()
defaultPolicy, err := getAgentDefaultPolicy("is_default")
if err != nil {
log.WithFields(log.Fields{
"error": err,
Expand Down Expand Up @@ -1009,14 +1019,14 @@ func (fts *FleetTestSuite) anAttemptToEnrollANewAgentFails() error {

containerName := fmt.Sprintf("%s_%s_%s_%d", profile, fts.Image+"-systemd", ElasticAgentServiceName, 2) // name of the new container

err := deployAgentToFleet(installer, containerName, fts.CurrentToken)
fleetConfig, err := deployAgentToFleet(installer, containerName, fts.CurrentToken, false)
// the installation process for TAR includes the enrollment
if installer.installerType != "tar" {
if err != nil {
return err
}

err = installer.EnrollFn(fts.CurrentToken)
err = installer.EnrollFn(fleetConfig)
if err == nil {
err = fmt.Errorf("The agent was enrolled although the token was previously revoked")

Expand Down Expand Up @@ -1236,7 +1246,7 @@ func createFleetToken(name string, policyID string) (*gabs.Container, error) {
return tokenItem, nil
}

func deployAgentToFleet(installer ElasticAgentInstaller, containerName string, token string) error {
func deployAgentToFleet(installer ElasticAgentInstaller, containerName string, token string, isFleetServer bool) (*FleetConfig, error) {
profile := installer.profile // name of the runtime dependencies compose file
service := installer.service // name of the service
serviceTag := installer.tag // docker tag of the service
Expand All @@ -1259,24 +1269,38 @@ func deployAgentToFleet(installer ElasticAgentInstaller, containerName string, t
"service": service,
"tag": serviceTag,
}).Error("Could not run the target box")
return err
return nil, err
}

err = installer.PreInstallFn()
if err != nil {
return err
return nil, err
}

err = installer.InstallFn(containerName, token)
var fleetConfig *FleetConfig
Copy link
Contributor

Choose a reason for hiding this comment

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

Didn't actually tried, but I think it would be a bit simpler to have just 1 kind of config and 1 way of creating it, eg: func NewAgentConfig(token, fleetServerMode bool) (*AgentConfig, error)

Credentials, url and port can also be hardcoded in the only place they are used.

Copy link
Contributor Author

@mdelapenya mdelapenya Mar 24, 2021

Choose a reason for hiding this comment

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

Sure, let me send a follow-up commit with that, thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Implemented in f5a9f46

if isFleetServer {
cfg, cfgError := NewFleetServerConfig(token)
if cfgError != nil {
return nil, cfgError
}
fleetConfig = cfg
} else {
fleetConfig = NewFleetConfig(token)
}

err = installer.InstallFn(fleetConfig)

if err != nil {
return err
return nil, err
}

return installer.PostInstallFn()
return fleetConfig, installer.PostInstallFn()
}

// getAgentDefaultPolicy sends a GET request to Fleet for the existing default policy
func getAgentDefaultPolicy() (*gabs.Container, error) {
// getAgentDefaultPolicy sends a GET request to Fleet for the existing default policy, using the
// "defaultPolicyFieldName" passed as parameter as field to be used to find the policy in list
// of fleet policies
func getAgentDefaultPolicy(defaultPolicyFieldName string) (*gabs.Container, error) {
r := createDefaultHTTPRequest(ingestManagerAgentPoliciesURL)
body, err := curl.Get(r)
if err != nil {
Expand Down Expand Up @@ -1304,10 +1328,21 @@ func getAgentDefaultPolicy() (*gabs.Container, error) {
"count": len(policies.Children()),
}).Trace("Fleet policies retrieved")

// TODO: perform a strong check to capture default policy
defaultPolicy := policies.Index(0)
for _, policy := range policies.Children() {
if !policy.Exists(defaultPolicyFieldName) {
continue
}

if policy.Path(defaultPolicyFieldName).Data().(bool) {
log.WithFields(log.Fields{
"field": defaultPolicyFieldName,
"policy": policy,
}).Trace("Default Policy was found")
return policy, nil
}
}

return defaultPolicy, nil
return nil, fmt.Errorf("Default policy was not found with '%s' field equals to 'true'", defaultPolicyFieldName)
}

func getAgentEvents(applicationName string, agentID string, packagePolicyID string, updatedAt string) error {
Expand Down
74 changes: 74 additions & 0 deletions e2e/_suites/fleet/fleet_server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

package main

import (
"fmt"

"github.com/cucumber/godog"
log "github.com/sirupsen/logrus"
)

// FleetConfig represents the configuration for Fleet Server when building the enrollment command
type FleetConfig struct {
EnrollmentToken string
ElasticsearchPort int
ElasticsearchURI string
ElasticsearchCredentials string
// server
ServerPolicyID string
}

// NewFleetConfig builds a new configuration for the fleet agent, defaulting ES credentials, URI and port
func NewFleetConfig(token string) *FleetConfig {
return &FleetConfig{
EnrollmentToken: token,
ElasticsearchCredentials: "elastic:changeme",
ElasticsearchPort: 9200,
ElasticsearchURI: "elasticsearch",
}
}

// NewFleetServerConfig builds a new configuration for the fleet server agent, defaulting credentials and port,
// also retrieving the default policy ID for fleet server
func NewFleetServerConfig(token string) (*FleetConfig, error) {
cfg := NewFleetConfig(token)

defaultFleetServerPolicy, err := getAgentDefaultPolicy("is_default_fleet_server")
if err != nil {
return nil, err
}

cfg.ServerPolicyID = defaultFleetServerPolicy.Path("id").Data().(string)

log.WithFields(log.Fields{
"elasticsearch": cfg.ElasticsearchURI,
"elasticsearchPort": cfg.ElasticsearchPort,
"policyID": cfg.ServerPolicyID,
"token": cfg.EnrollmentToken,
}).Debug("Fleet Server config created")

return cfg, nil
}

func (cfg FleetConfig) flags() []string {
baseFlags := []string{"--force", "--insecure", "--enrollment-token=" + cfg.EnrollmentToken}

if cfg.ServerPolicyID != "" {
return append(baseFlags, "--fleet-server", fmt.Sprintf("http://%s@%s:%d", cfg.ElasticsearchCredentials, cfg.ElasticsearchURI, cfg.ElasticsearchPort), "--fleet-server-policy", cfg.ServerPolicyID)
}

return append(baseFlags, "--kibana-url", "http://kibana:5601")
}

func (fts *FleetTestSuite) anAgentIsDeployedToFleetWithInstallerInFleetMode(image string, installerType string) error {
fts.ElasticAgentStopped = true
return fts.anAgentIsDeployedToFleetWithInstallerAndFleetServer(image, installerType, true)
}

func (fts *FleetTestSuite) fleetServerIsEnabled() error {
log.Debug("Fleet server is enabled")
return godog.ErrPending
mdelapenya marked this conversation as resolved.
Show resolved Hide resolved
}
17 changes: 9 additions & 8 deletions e2e/_suites/fleet/installers.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (

// InstallerPackage represents the operations that can be performed by an installer package type
type InstallerPackage interface {
Install(containerName string, token string) error
Install(cfg *FleetConfig) error
InstallCerts() error
PrintLogs(containerName string) error
Postinstall() error
Expand Down Expand Up @@ -119,7 +119,7 @@ func NewDEBPackage(binaryName string, profile string, image string, service stri
}

// Install installs a DEB package
func (i *DEBPackage) Install(containerName string, token string) error {
func (i *DEBPackage) Install(cfg *FleetConfig) error {
return i.extractPackage([]string{"apt", "install", "/" + i.binaryName, "-y"})
}

Expand Down Expand Up @@ -182,7 +182,7 @@ func NewDockerPackage(binaryName string, profile string, image string, service s
}

// Install installs a Docker package
func (i *DockerPackage) Install(containerName string, token string) error {
func (i *DockerPackage) Install(cfg *FleetConfig) error {
log.Trace("No install commands for Docker packages")
return nil
}
Expand Down Expand Up @@ -267,7 +267,7 @@ func NewRPMPackage(binaryName string, profile string, image string, service stri
}

// Install installs a RPM package
func (i *RPMPackage) Install(containerName string, token string) error {
func (i *RPMPackage) Install(cfg *FleetConfig) error {
return i.extractPackage([]string{"yum", "localinstall", "/" + i.binaryName, "-y"})
}

Expand Down Expand Up @@ -329,10 +329,11 @@ func NewTARPackage(binaryName string, profile string, image string, service stri
}

// Install installs a TAR package
func (i *TARPackage) Install(containerName string, token string) error {
func (i *TARPackage) Install(cfg *FleetConfig) error {
// install the elastic-agent to /usr/bin/elastic-agent using command
binary := fmt.Sprintf("/elastic-agent/%s", i.artifact)
args := []string{"--force", "--insecure", "--enrollment-token=" + token, "--kibana-url", "http://kibana:5601"}

args := cfg.flags()

err := runElasticAgentCommand(i.profile, i.image, i.service, binary, "install", args)
if err != nil {
Expand Down Expand Up @@ -374,8 +375,8 @@ func (i *TARPackage) Preinstall() error {

// simplify layout
cmds := [][]string{
[]string{"rm", "-fr", "/elastic-agent"},
[]string{"mv", fmt.Sprintf("/%s-%s-%s-%s", i.artifact, i.version, i.OS, i.arch), "/elastic-agent"},
{"rm", "-fr", "/elastic-agent"},
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Remove some leftovers: the type is automatically inferred by the Go compiler

{"mv", fmt.Sprintf("/%s-%s-%s-%s", i.artifact, i.version, i.OS, i.arch), "/elastic-agent"},
}
for _, cmd := range cmds {
err = execCommandInService(i.profile, i.image, i.service, cmd, false)
Expand Down
Loading