Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

filestream: validate input id on startup #41731

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

AndersonQ
Copy link
Member

@AndersonQ AndersonQ commented Nov 21, 2024

During startup filebeat now validates the filestream inputs and fails to start if there are inputs without ID or with duplicated IDs. Duplicated IDs might cause data duplication therefore now it's mandatory to have unique and non-empty ID for each filestream input.

Proposed commit message

filestream: validate input id on startup

During startup filebeat now validates the filestream inputs and fails to start if there are with duplicated IDs. Duplicated IDs might cause data duplication therefore now it's mandatory to have unique for each filestream input. 

Disruptive User Impact
Impact: Users who have not configured unique IDs for their filestream inputs will need to update their configurations to include unique IDs for each input. Failure to do so will prevent Filebeat from starting.

Previously: Filebeat would only log an error if filestream would find inputs with duplicated IDs, potentially leading to data duplication.

Now: Filebeat will fail to start if any filestream input has a duplicated ID.

Checklist

  • My code follows the style guidelines of this project
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • [ ] I have made corresponding change to the default configuration files
  • I have added tests that prove my fix is effective or that my feature works
  • I have added an entry in CHANGELOG.next.asciidoc or CHANGELOG-developer.next.asciidoc.

Disruptive User Impact

Impact: Users who have not configured unique IDs for their filestream inputs will need to update their configurations to include unique IDs for each input. Failure to do so will prevent Filebeat from starting.

Previously: Filebeat would only log an error if filestream would find inputs had missing or duplicated IDs, potentially leading to data duplication.

Now: Filebeat will fail to start if any filestream input lacks an ID or has a duplicated ID.

How to test this PR locally

  • build filebeat
cd filebeat
go build .
  • use the following as filebeat.yml. Change as necessary to simulate" only unique IDs, valid empty ID, duplicated IDs, duplicated empty ID.
filebeat.inputs:
  - type: filestream
    enabled: true
    paths:
      - /tmp/noID-1/*.log
  - type: filestream
    enabled: true
    paths:
      - /tmp/noID-2/*.log
  - type: filestream
    id: duplicated-id
    enabled: true
    paths:
      - /tmp/duplicated-id-1/*.log
  - type: filestream
    id: duplicated-id
    enabled: true
    paths:
      - /tmp/duplicated-id-2/*.log

output.discard.enabled: true
logging:
  level: info
  metrics:
    enabled: false
  • run filebeat:
./filebeat -e 2>&1 | grep message

verify filebeat:

  • will exit with error
  • will produce a log with all the invalid input configuration:
{"log.level":"error","@timestamp":"2024-11-25T10:44:19.062+0100","log.logger":"filestream","log.origin":{"function":"github.com/elastic/beats/v7/filebeat/input/filestream.ValidateInputIDs","file.name":"filestream/config.go","file.line":174},"message":"filestream inputs with duplicated IDs","service.name":"filebeat","inputs":[{"enabled":true,"paths":["/tmp/noID-1/*.log"],"type":"filestream"},{"enabled":true,"paths":["/tmp/noID-2/*.log"],"type":"filestream"},{"enabled":true,"id":"duplicated-id","paths":["/tmp/duplicated-id-1/*.log"],"type":"filestream"},{"enabled":true,"id":"duplicated-id","paths":["/tmp/duplicated-id-2/*.log"],"type":"filestream"}],"ecs.version":"1.6.0"}
  • will produce a log pointing out the issues:
{"log.level":"error","@timestamp":"2024-11-25T10:44:19.062+0100","log.origin":{"function":"github.com/elastic/beats/v7/filebeat/beater.(*Filebeat).Run","file.name":"beater/filebeat.go","file.line":297},"message":"invalid filestream configuration: filestream inputs validation error: filestream inputs with duplicated IDs: \"\",\"duplicated-id\"","service.name":"filebeat","ecs.version":"1.6.0"}
Exiting: filestream inputs validation error: filestream inputs with duplicated IDs: "","duplicated-id"

Related issues

Logs

{"log.level":"info","@timestamp":"2024-11-25T10:44:19.062+0100","log.origin":{"function":"github.com/elastic/beats/v7/libbeat/cmd/instance.(*Beat).launch","file.name":"instance/beat.go","file.line":770},"message":"filebeat start running.","service.name":"filebeat","ecs.version":"1.6.0"}
{"log.level":"error","@timestamp":"2024-11-25T10:44:19.062+0100","log.logger":"filestream","log.origin":{"function":"github.com/elastic/beats/v7/filebeat/input/filestream.ValidateInputIDs","file.name":"filestream/config.go","file.line":174},"message":"filestream inputs with duplicated IDs","service.name":"filebeat","inputs":[{"enabled":true,"paths":["/tmp/noID-1/*.log"],"type":"filestream"},{"enabled":true,"paths":["/tmp/noID-2/*.log"],"type":"filestream"},{"enabled":true,"id":"duplicated-id","paths":["/tmp/duplicated-id-1/*.log"],"type":"filestream"},{"enabled":true,"id":"duplicated-id","paths":["/tmp/duplicated-id-2/*.log"],"type":"filestream"}],"ecs.version":"1.6.0"}
{"log.level":"error","@timestamp":"2024-11-25T10:44:19.062+0100","log.origin":{"function":"github.com/elastic/beats/v7/filebeat/beater.(*Filebeat).Run","file.name":"beater/filebeat.go","file.line":297},"message":"invalid filestream configuration: filestream inputs validation error: filestream inputs with duplicated IDs: \"\",\"duplicated-id\"","service.name":"filebeat","ecs.version":"1.6.0"}
{"log.level":"info","@timestamp":"2024-11-25T10:44:19.062+0100","log.origin":{"function":"github.com/elastic/beats/v7/libbeat/cmd/instance.(*Beat).launch","file.name":"instance/beat.go","file.line":779},"message":"filebeat stopped.","service.name":"filebeat","ecs.version":"1.6.0"}
{"log.level":"error","@timestamp":"2024-11-25T10:44:19.062+0100","log.origin":{"function":"github.com/elastic/beats/v7/libbeat/cmd/instance.handleError","file.name":"instance/beat.go","file.line":1594},"message":"Exiting: filestream inputs validation error: filestream inputs with duplicated IDs: \"\",\"duplicated-id\"","service.name":"filebeat","ecs.version":"1.6.0"}
Exiting: filestream inputs validation error: filestream inputs with duplicated IDs: "","duplicated-id"

@AndersonQ AndersonQ added breaking change Team:Elastic-Agent-Data-Plane Label for the Agent Data Plane team labels Nov 21, 2024
@AndersonQ AndersonQ self-assigned this Nov 21, 2024
@botelastic botelastic bot added needs_team Indicates that the issue/PR needs a Team:* label and removed needs_team Indicates that the issue/PR needs a Team:* label labels Nov 21, 2024
Copy link
Contributor

mergify bot commented Nov 21, 2024

This pull request does not have a backport label.
If this is a bug or security fix, could you label this PR @AndersonQ? 🙏.
For such, you'll need to label your PR with:

  • The upcoming major version of the Elastic Stack
  • The upcoming minor version of the Elastic Stack (if you're not pushing a breaking change)

To fixup this pull request, you need to add the backport labels for the needed
branches, such as:

  • backport-8./d is the label to automatically backport to the 8./d branch. /d is the digit

Copy link
Contributor

mergify bot commented Nov 21, 2024

backport-8.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.

@mergify mergify bot added the backport-8.x Automated backport to the 8.x branch with mergify label Nov 21, 2024
During startup filebeat now validates the filestream inputs and fails to start if there are inputs without ID or with duplicated IDs
@AndersonQ AndersonQ force-pushed the 40540-filestream-require-unique-ids branch from 247727b to b6ecc4e Compare November 21, 2024 14:45
@AndersonQ AndersonQ marked this pull request as ready for review November 21, 2024 14:56
@AndersonQ AndersonQ requested a review from a team as a code owner November 21, 2024 14:56
@elasticmachine
Copy link
Collaborator

Pinging @elastic/elastic-agent-data-plane (Team:Elastic-Agent-Data-Plane)

@AndersonQ AndersonQ added backport-skip Skip notification from the automated backport with mergify and removed backport-8.x Automated backport to the 8.x branch with mergify labels Nov 21, 2024
@pierrehilbert pierrehilbert requested review from belimawr and rdner and removed request for khushijain21 November 21, 2024 16:29
toJson := map[string]interface{}{}
err := cfg.Unpack(&toJson)
if err != nil {
toJson["emptyID"] = fmt.Sprintf("failes to umpack config: %v", err)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
toJson["emptyID"] = fmt.Sprintf("failes to umpack config: %v", err)
toJson["emptyID"] = fmt.Sprintf("failed to unpack config: %v", err)

toJson := map[string]interface{}{}
err := dupcfgs.Unpack(&toJson)
if err != nil {
toJson[id] = fmt.Sprintf("failes to umpack config: %v", err)
Copy link
Member

Choose a reason for hiding this comment

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

Same here

Copy link
Member

@mauri870 mauri870 left a comment

Choose a reason for hiding this comment

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

Codewise looks good, left some comments about some minor spelling issues.

I tested locally following the issue description and confirmed that it validates the ids properly.

LGTM.

@AndersonQ AndersonQ enabled auto-merge (squash) November 22, 2024 14:42
filebeat/input/filestream/config.go Outdated Show resolved Hide resolved
filebeat/input/filestream/config.go Show resolved Hide resolved
filebeat/input/filestream/config_test.go Show resolved Hide resolved
@AndersonQ AndersonQ requested a review from rdner November 22, 2024 21:37
@cmacknz
Copy link
Member

cmacknz commented Nov 22, 2024

if there are inputs without ID

An empty ID is fine, the problem is if there are two inputs with an empty ID. Agent allows the empty ID as a valid ID. This avoids breaking the configuration unnecessarily where there is only a single filestream input with no ID.

Copy link
Member

@rdner rdner left a comment

Choose a reason for hiding this comment

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

All my comments have been addressed.
We might want to treat an empty ID as a regular ID value as @cmacknz pointed out. So, we can apply the duplication check to that as well.

@AndersonQ
Copy link
Member Author

I removed the bit forbidding an empty ID. It was already checking for multiple empty IDs and including them in the list of invalid inputs.

@AndersonQ AndersonQ requested a review from rdner November 25, 2024 09:49
@rdner
Copy link
Member

rdner commented Nov 25, 2024

@AndersonQ looks like the tests need adjusting?

=== FAIL: filebeat/tests/integration TestFilestreamValidationPreventsFilebeatStart/empty_ID (10.12s)
    filestream_test.go:180:
        	Error Trace:	/opt/buildkite-agent/builds/bk-agent-prod-gcp-1732528923926044677/elastic/filebeat/libbeat/tests/integration/framework.go:540
        	            				/opt/buildkite-agent/builds/bk-agent-prod-gcp-1732528923926044677/elastic/filebeat/filebeat/tests/integration/filestream_test.go:180
        	Error:      	Condition never satisfied
        	Test:       	TestFilestreamValidationPreventsFilebeatStart/empty_ID
        	Messages:   	Filebeat did log a validation error

@@ -291,6 +292,11 @@ func (fb *Filebeat) Run(b *beat.Beat) error {
}
defer stateStore.Close()

err = filestream.ValidateInputIDs(config.Inputs, logp.NewLogger("filestream"))
Copy link
Contributor

Choose a reason for hiding this comment

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

The filestream input actually uses input.filestream as the logger name, so I suggest keeping the pattern.

Suggested change
err = filestream.ValidateInputIDs(config.Inputs, logp.NewLogger("filestream"))
err = filestream.ValidateInputIDs(config.Inputs, logp.NewLogger("input.filestream"))

filebeat/input/filestream/config.go Show resolved Hide resolved
filebeat.WaitForLogs(
"filestream inputs validation error",
10*time.Second,
"Filebeat did log a validation error")
Copy link
Contributor

Choose a reason for hiding this comment

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

should it be "Filebeat did not log a validation error"?

Suggested change
"Filebeat did log a validation error")
"Filebeat did not log a validation error")

filebeat.WaitForLogs(
"Input 'filestream' starting",
10*time.Second,
"Filebeat did log a validation error")
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe something else, like "filebeat did not start the Filestream input"

Suggested change
"Filebeat did log a validation error")
"Filebeat did not start the Filestream input")

@belimawr
Copy link
Contributor

if there are inputs without ID

An empty ID is fine, the problem is if there are two inputs with an empty ID. Agent allows the empty ID as a valid ID. This avoids breaking the configuration unnecessarily where there is only a single filestream input with no ID.

Honestly, as we're already doing a breaking change to enforce ID validation, I rather not allow empty IDs, it is a corner case and Elastic-Agent/Fleet already adds IDs to all inputs, so I don't see a real need for it. It only makes for an extra corner case to maintain.

@belimawr
Copy link
Contributor

@AndersonQ, @cmacknz, Should we also cover the case of dynamic inputs like:

  • Input reload is enabled and inputs are read from inputs.d + filebeat.yml
  • Filebeat autodiscover
  • Filebeat running under Elastic-Agent

I like the way it's implemented here that gives this quick and precise feedback to the use, however it does not cover all cases.

I believe we should, at least, also cover the inputs.d if Filebeat is setup to read inputs from it, even if it's just at startup.

@AndersonQ
Copy link
Member Author

Honestly, as we're already doing a breaking change to enforce ID validation, I rather not allow empty IDs

Agreed, but also what was approved in the breaking change committee was indeed to enforce the uniqueness of IDs. Also considering the agent already allows a single empty ID, I agree with Craig it's fine to have an empty ID as long as it's just one.

@AndersonQ, @cmacknz, Should we also cover the case of dynamic inputs like:

  • Input reload is enabled and inputs are read from inputs.d + filebeat.yml
  • Filebeat autodiscover
  • Filebeat running under Elastic-Agent

I like the way it's implemented here that gives this quick and precise feedback to the use, however it does not cover all cases.

Those are different things. Here we're tacking the start up and early feedback.

In order to completely prevent an input with duplicated ID, we'd need to change it to return an error instead of just logging it. Which happens in a completely different part of the code. Having it on another issue and PR if we really want to do that is a better option.

@cmacknz
Copy link
Member

cmacknz commented Nov 25, 2024

Filebeat running under Elastic-Agent

Since 8.6.0 Elastic Agent has required that all inputs have a unique ID, and it considers an empty ID a unique ID. The constraint is that two inputs can't have the same ID, not that the ID has to be non-empty.

https://github.com/elastic/elastic-agent/blob/434986ed9fa6fbed6257557a4c6773d519742f66/pkg/component/component.go#L734-L745

func hasDuplicate(outputsMap map[string]outputI, id string) bool {
	for _, o := range outputsMap {
		for _, i := range o.inputs {
			for _, j := range i {
				if j.id == id {
					return true
				}
			}
		}
	}
	return false
}

If we were designing this from scratch we'd probably forbid empty IDs, but allowing empty IDs solves the underlying functional problem without unnecessarily breaking people who started from our default configurations that didn't specify IDs a while ago. We could log a warning that IDs should be explicit if you want.

Input reload is enabled and inputs are read from inputs.d + filebeat.yml
Filebeat autodiscover

+1 to handling these as well, autodiscovery with the ID composed from variable substitutions with non-unique values is a common way people were ending up with duplicated logs on K8s. The original issue doesn't put a constraint on the execution environment :)

This doesn't need to be fixed in this PR though, it can be done as a separate PR, but I don't think we can close #40540 until it's done, otherwise we have implemented "sometimes require unique IDs" and not "always require unique IDs".

// containing the offending input configurations and returns an error containing
// the duplicated IDs.
func ValidateInputIDs(inputs []*conf.C, logger *logp.Logger) error {
ids := make(map[string][]*conf.C)
Copy link
Contributor

Choose a reason for hiding this comment

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

[Suggestion]
Rename this map to something that better describe its contents. It is a map of inputs (or configs) grouped by ID, so ids does not seem to be the better description to it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport-skip Skip notification from the automated backport with mergify breaking change Team:Elastic-Agent-Data-Plane Label for the Agent Data Plane team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Filebeat should fail to start when multiple filestream inputs have the same input ID
6 participants