From 41584ad0c56a0f4349100dd96db66fd901649aa0 Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 31 Oct 2023 19:50:29 +0100 Subject: [PATCH] Upload and publish test results (#71) * Add gh actions to publish pre-release's test results --- .github/workflows/pull_request.yml | 2 +- .github/workflows/run_e2e_tests.yml | 33 ++- .github/workflows/run_task.yml | 1 + ansible/provision-and-execute-tests/Makefile | 2 +- .../provision-and-execute-tests/playbook.yml | 25 +- .../install_nr_fluent_bit_output/README.md | 12 + .../tasks/linux.yml | 25 ++ .../tasks/main.yml | 14 + .../tasks/windows.yml | 22 ++ .../vars/linux.yml | 5 + .../vars/windows.yml | 5 + .../test-suite/fluent-bit-tests.test.js | 240 ------------------ integration-tests/test-suite/jest.config.js | 1 + integration-tests/test-suite/lib/test-util.js | 24 ++ .../test-suite/package-lock.json | 66 ----- integration-tests/test-suite/package.json | 9 +- integration-tests/test-suite/syslog.test.js | 96 +++++++ integration-tests/test-suite/systemd.test.js | 67 +++++ integration-tests/test-suite/tail.test.js | 54 ++++ integration-tests/test-suite/tcp.test.js | 76 ++++++ integration-tests/test-suite/winlog.test.js | 81 ++++++ 21 files changed, 538 insertions(+), 322 deletions(-) create mode 100644 ansible/provision-and-execute-tests/roles/install_nr_fluent_bit_output/README.md create mode 100644 ansible/provision-and-execute-tests/roles/install_nr_fluent_bit_output/tasks/linux.yml create mode 100644 ansible/provision-and-execute-tests/roles/install_nr_fluent_bit_output/tasks/main.yml create mode 100644 ansible/provision-and-execute-tests/roles/install_nr_fluent_bit_output/tasks/windows.yml create mode 100644 ansible/provision-and-execute-tests/roles/install_nr_fluent_bit_output/vars/linux.yml create mode 100644 ansible/provision-and-execute-tests/roles/install_nr_fluent_bit_output/vars/windows.yml delete mode 100644 integration-tests/test-suite/fluent-bit-tests.test.js create mode 100644 integration-tests/test-suite/lib/test-util.js create mode 100644 integration-tests/test-suite/syslog.test.js create mode 100644 integration-tests/test-suite/systemd.test.js create mode 100644 integration-tests/test-suite/tail.test.js create mode 100644 integration-tests/test-suite/tcp.test.js create mode 100644 integration-tests/test-suite/winlog.test.js diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 3e99e49d..35a37abc 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -182,7 +182,7 @@ jobs: # Runs E2E tests using the packages available in the tmp-pr-#PR prerelease run_e2e_tests: - needs: [ setup_environment, sign_suse_packages, upload_official_packages_to_prerelease] + needs: [ setup_environment, sign_suse_packages, upload_official_packages_to_prerelease ] name: Run E2E tests uses: ./.github/workflows/run_e2e_tests.yml with: diff --git a/.github/workflows/run_e2e_tests.yml b/.github/workflows/run_e2e_tests.yml index c631c428..03665f79 100644 --- a/.github/workflows/run_e2e_tests.yml +++ b/.github/workflows/run_e2e_tests.yml @@ -50,10 +50,39 @@ jobs: container_make_target: "ansible/provision-and-execute-tests PR_NUMBER=${{ github.event.pull_request.number }}" secrets: inherit + report_test_results: + name: Report results + runs-on: ubuntu-20.04 + needs: provision_and_execute_tests + env: + GH_TOKEN: ${{ github.token }} + PRE_RELEASE_NAME: tmp-pr-${{ github.event.pull_request.number }} + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Download report from pre-release + run: | + gh release download ${{ env.PRE_RELEASE_NAME }} --pattern 'tests-report.xml' + + - name: Tests Report Details + uses: dorny/test-reporter@v1 + with: + name: Tests Report Details # Name of the check run which will be created + path: tests-report.xml # Path to test results + reporter: jest-junit + + - name: Tests Report Summary + uses: EnricoMi/publish-unit-test-result-action@v2 + if: always() + with: + files: | + tests-report.xml + tear_down_test_executor_instances: name: Tear down test executor instances - needs: provision_and_execute_tests + needs: report_test_results uses: ./.github/workflows/run_task.yml with: container_make_target: "terraform-clean TERRAFORM_PROJECT=ec2-test-executors PR_NUMBER=${{ github.event.pull_request.number }}" - secrets: inherit \ No newline at end of file + secrets: inherit diff --git a/.github/workflows/run_task.yml b/.github/workflows/run_task.yml index 34e4a712..08220d20 100644 --- a/.github/workflows/run_task.yml +++ b/.github/workflows/run_task.yml @@ -20,6 +20,7 @@ jobs: uses: aws-actions/configure-aws-credentials@v2 with: role-to-assume: ${{ secrets.AWS_ROLE_ARN }} + role-duration-seconds: 10800 # default to 1 hour, which might not suffice https://github.com/aws-actions/configure-aws-credentials#credential-lifetime aws-region: us-east-2 - name: Set branch name diff --git a/ansible/provision-and-execute-tests/Makefile b/ansible/provision-and-execute-tests/Makefile index 13bf7be1..c6af40d8 100644 --- a/ansible/provision-and-execute-tests/Makefile +++ b/ansible/provision-and-execute-tests/Makefile @@ -4,7 +4,7 @@ include ../Ansible.common.mk .PHONY: provision-and-execute-tests provision-and-execute-tests: ansible/dependencies ansible/prepare-inventory - ansible-playbook $(ANSIBLE_FOLDER)/playbook.yml -i $(ANSIBLE_INVENTORY) + ansible-playbook -vvv $(ANSIBLE_FOLDER)/playbook.yml -i $(ANSIBLE_INVENTORY) .PHONY: clean clean: ansible/clean \ No newline at end of file diff --git a/ansible/provision-and-execute-tests/playbook.yml b/ansible/provision-and-execute-tests/playbook.yml index d11307d4..4df50e6c 100644 --- a/ansible/provision-and-execute-tests/playbook.yml +++ b/ansible/provision-and-execute-tests/playbook.yml @@ -24,6 +24,7 @@ monitored_systemd_unit: "{{ (tags.os_distro == 'ubuntu' or tags.os_distro == 'debian') | ternary('ssh','sshd') }}" test_reports_dir: /tmp/test-reports + nr_fb_output_plugin_version: 1.17.1 environment: NEW_RELIC_API_KEY: "{{ new_relic_api_key }}" NEW_RELIC_ACCOUNT_ID: "{{ new_relic_account_id }}" @@ -61,6 +62,13 @@ gh_prerelease_tag: "tmp-pr-{{ pr_number }}" when: pr_number is not regex('^local-.*') + - name: Install NR Fluent Bit output + ansible.builtin.include_role: + name: install_nr_fluent_bit_output + vars: + plugin_arch: "{{ (arch == 'x86_64' or arch == 'amd64') | ternary('amd64','arm64') }}" + plugin_version: "{{ nr_fb_output_plugin_version }}" + - name: Copy test suite ansible.builtin.copy: src: ../../integration-tests/test-suite @@ -122,8 +130,8 @@ monitored_file: 'C:\Users\Administrator\tail-log-file-test' monitored_tcp_port: 5170 - monitored_windows_log_name_using_winevtlog: "Application" - monitored_windows_log_name_using_winlog: "Application" + monitored_windows_log_name_using_winevtlog: 'Application' + monitored_windows_log_name_using_winlog: 'Application' test_suite_folder: 'C:\Windows\Temp\test-suite' test_suite_report_path: '{{ test_suite_folder }}\reports\tests\test-report.xml' @@ -135,6 +143,8 @@ # the PATH environment using Ansible's "environment" clause for these steps requiring nvm, node or npm to be in the PATH. nvm_path: 'C:\ProgramData\nvm' node_path: 'C:\ProgramData\nvm\v{{ node_version }}' + + nr_fb_output_plugin_version: '1.17.1' environment: NEW_RELIC_API_KEY: "{{ new_relic_api_key }}" NEW_RELIC_ACCOUNT_ID: "{{ new_relic_account_id }}" @@ -172,6 +182,13 @@ gh_prerelease_tag: "tmp-pr-{{ pr_number }}" when: pr_number is not regex('^local-.*') + - name: Install NR Fluent Bit output + ansible.builtin.include_role: + name: install_nr_fluent_bit_output + vars: + plugin_arch: "{{ (arch == 'win64') | ternary('amd64','386') }}" + plugin_version: "{{ nr_fb_output_plugin_version }}" + - name: Install NVM win_chocolatey: name: nvm.install @@ -242,7 +259,7 @@ node_version: 18.18.0 controller_scripts_folder: ../../integration-tests/controller-scripts test_reports_dir: /tmp/test-reports - combined_test_report_name: report.xml + combined_test_report_name: tests-report.xml pr_number: "{{ lookup('ansible.builtin.env', 'PR_NUMBER') }}" roles: - andrewrothstein.gh @@ -267,5 +284,5 @@ TEST_REPORT_NAME: "{{ combined_test_report_name }}" - name: Upload generated artifacts to release - command: "gh release upload tmp-pr-{{ pr_number }} {{ test_reports_dir }}/{{ combined_test_report_name }}" + command: "gh release upload tmp-pr-{{ pr_number }} {{ test_reports_dir }}/{{ combined_test_report_name }} --clobber" when: pr_number is not regex('^local-.*') diff --git a/ansible/provision-and-execute-tests/roles/install_nr_fluent_bit_output/README.md b/ansible/provision-and-execute-tests/roles/install_nr_fluent_bit_output/README.md new file mode 100644 index 00000000..dfc87b40 --- /dev/null +++ b/ansible/provision-and-execute-tests/roles/install_nr_fluent_bit_output/README.md @@ -0,0 +1,12 @@ +Role Name +========= + +This roles makes the infra agent use a specific version (plugin_arch) of the NR FB output plugin. + +Role Variables +-------------- + +The role requires the following variables: +- `plugin_arch`: The name of the plugin_architecture, matching the case of [the output plugin artifact names](https://github.com/newrelic/newrelic-fluent-bit-output/releases) + (`arm64`, `amd64`, `386`) +- `plugin_version`: The Fluent Bit package name to download from the [NR FB Output Plugin releases](https://github.com/newrelic/newrelic-fluent-bit-output/releases) diff --git a/ansible/provision-and-execute-tests/roles/install_nr_fluent_bit_output/tasks/linux.yml b/ansible/provision-and-execute-tests/roles/install_nr_fluent_bit_output/tasks/linux.yml new file mode 100644 index 00000000..e8ed3deb --- /dev/null +++ b/ansible/provision-and-execute-tests/roles/install_nr_fluent_bit_output/tasks/linux.yml @@ -0,0 +1,25 @@ +- name: Load Linux variables + ansible.builtin.include_vars: "linux.yml" + +- name: Stop newrelic-infra service + ansible.builtin.service: + name: newrelic-infra + state: stopped + become: true + +- name: Download NR FB Output Plugin + ansible.builtin.get_url: + url: "{{ nr_fb_output_plugin_url }}" + dest: "{{ nr_fb_output_plugin_download_file_path }}" + +- name: Modify Infrastructure Agent config to point to downloaded NR FB Output Plugin + ansible.builtin.lineinfile: + path: "{{ infra_agent_config_file_absolute_path }}" + line: 'fluent_bit_nr_lib_path: {{ nr_fb_output_plugin_download_file_path }}' + become: true + +- name: Start again newrelic-infra service with new NR FB Output plugin + ansible.builtin.service: + name: newrelic-infra + state: started + become: true \ No newline at end of file diff --git a/ansible/provision-and-execute-tests/roles/install_nr_fluent_bit_output/tasks/main.yml b/ansible/provision-and-execute-tests/roles/install_nr_fluent_bit_output/tasks/main.yml new file mode 100644 index 00000000..2bb1210f --- /dev/null +++ b/ansible/provision-and-execute-tests/roles/install_nr_fluent_bit_output/tasks/main.yml @@ -0,0 +1,14 @@ +--- +- name: Check mandatory variables (all distros) + assert: + that: + - plugin_arch is defined + - plugin_version is defined + +- name: Install specific NR Fluent Bit output Linux plugin + include_tasks: "linux.yml" + when: ansible_system == 'Linux' + +- name: Install specific NR Fluent Bit output Windows plugin + include_tasks: "windows.yml" + when: ansible_system != 'Linux' diff --git a/ansible/provision-and-execute-tests/roles/install_nr_fluent_bit_output/tasks/windows.yml b/ansible/provision-and-execute-tests/roles/install_nr_fluent_bit_output/tasks/windows.yml new file mode 100644 index 00000000..60ed7ad5 --- /dev/null +++ b/ansible/provision-and-execute-tests/roles/install_nr_fluent_bit_output/tasks/windows.yml @@ -0,0 +1,22 @@ +- name: Load Windows variables + ansible.builtin.include_vars: "windows.yml" + +- name: Stop newrelic-infra service + ansible.windows.win_service: + name: newrelic-infra + state: stopped + +- name: Download NR FB Output Plugin + ansible.windows.win_get_url: + url: "{{ nr_fb_output_plugin_url }}" + dest: '{{ nr_fb_output_plugin_download_file_path }}' + +- name: Modify Infrastructure Agent config to point to downloaded NR FB Output Plugin + community.windows.win_lineinfile: + path: "{{ infra_agent_config_file_absolute_path }}" + line: 'fluent_bit_nr_lib_path: {{ nr_fb_output_plugin_download_file_path }}' + +- name: Start again newrelic-infra service with new NR FB Output plugin + ansible.windows.win_service: + name: newrelic-infra + state: started \ No newline at end of file diff --git a/ansible/provision-and-execute-tests/roles/install_nr_fluent_bit_output/vars/linux.yml b/ansible/provision-and-execute-tests/roles/install_nr_fluent_bit_output/vars/linux.yml new file mode 100644 index 00000000..e84bd120 --- /dev/null +++ b/ansible/provision-and-execute-tests/roles/install_nr_fluent_bit_output/vars/linux.yml @@ -0,0 +1,5 @@ +infra_agent_config_file_absolute_path: /etc/newrelic-infra.yml +nr_fb_output_file_extension: 'so' +nr_fb_output_plugin_artifact: 'out_newrelic-linux-{{ plugin_arch }}-{{ plugin_version }}.{{ nr_fb_output_file_extension }}' +nr_fb_output_plugin_url: "https://github.com/newrelic/newrelic-fluent-bit-output/releases/download/v{{ plugin_version }}/{{ nr_fb_output_plugin_artifact }}" +nr_fb_output_plugin_download_file_path: "/tmp/out_newrelic.{{ nr_fb_output_file_extension }}'" \ No newline at end of file diff --git a/ansible/provision-and-execute-tests/roles/install_nr_fluent_bit_output/vars/windows.yml b/ansible/provision-and-execute-tests/roles/install_nr_fluent_bit_output/vars/windows.yml new file mode 100644 index 00000000..a41d2ef0 --- /dev/null +++ b/ansible/provision-and-execute-tests/roles/install_nr_fluent_bit_output/vars/windows.yml @@ -0,0 +1,5 @@ +infra_agent_config_file_absolute_path: C:\Program Files\New Relic\newrelic-infra\newrelic-infra.yml +nr_fb_output_file_extension: 'dll' +nr_fb_output_plugin_artifact: 'out_newrelic-windows-{{ plugin_arch }}-{{ plugin_version }}.{{ nr_fb_output_file_extension }}' +nr_fb_output_plugin_url: "https://github.com/newrelic/newrelic-fluent-bit-output/releases/download/v{{ plugin_version }}/{{ nr_fb_output_plugin_artifact }}" +nr_fb_output_plugin_download_file_path: 'C:\Windows\Temp\out_newrelic.{{ nr_fb_output_file_extension }}' \ No newline at end of file diff --git a/integration-tests/test-suite/fluent-bit-tests.test.js b/integration-tests/test-suite/fluent-bit-tests.test.js deleted file mode 100644 index c03e9805..00000000 --- a/integration-tests/test-suite/fluent-bit-tests.test.js +++ /dev/null @@ -1,240 +0,0 @@ -const logger = require('./lib/logger'); -const Nrdb = require('./lib/nrdb'); -const fs = require('fs'); -const { Socket } = require('net'); -const dgram = require('node:dgram'); -const { v4: uuidv4 } = require('uuid'); -const { currentTimeAsIso8601 } = require('./lib/time'); -const { spawnSync } = require('child_process'); -const { requireEnvironmentVariable } = require('./lib/environmentVariables'); - -/** - * The newline is important -- Fluent Bit will wait - * until it sees a complete line before sending a log message - */ -const addNewlineSoFluentBitDetectsLine = (line) => `${line}\n`; - -const appendTo = (file, line) => { - logger.info(`Appending to ${file}...`); - fs.appendFileSync(file, addNewlineSoFluentBitDetectsLine(line)); -}; - -const writeToTcpSocket = (port, line) => { - logger.info(`Writing to TCP socket at localhost:${port}...`); - const socket = new Socket(); - socket.on('error', console.error); - socket.connect({ port }, () => { - socket.write(addNewlineSoFluentBitDetectsLine(line)); - socket.end(); - }); -}; - -const writeToUdpSocket = (port, line) => { - logger.info(`Writing to UDP socket at localhost:${port}...`); - const socket = dgram.createSocket('udp4'); - socket.send( - addNewlineSoFluentBitDetectsLine(line), - port, - 'localhost', - (error) => { - if (error) { - console.error(error); - } - - socket.close(); - }); -}; - -const newNrdb = (configuration) => { - return new Nrdb({ - accountId: configuration.accountId, - apiKey: configuration.apiKey, - nerdGraphUrl: configuration.nerdGraphUrl, - }); -}; - -const rfc5424 = (message) => { - return `<165>1 ${currentTimeAsIso8601()} example.com su - ID47 - ${message}`; -}; - -const executeSync = (command, commandArguments, expectedExitCode) => { - const result = spawnSync(command, commandArguments); - - expect(result.status).toEqual(expectedExitCode); - logger.info(result.stdout?.toString()); - logger.error(result.stderr?.toString()); -} - -const causeJournaldMessageToBeWrittenForSsh = (uuid) => { - // This _attempts_ to make an SSH connecting using the UUID as a user. - // It will fail to connect, of course, since the UUID isn't a user, - // but the attempt will be logged to journald for sshd with the name - // of the user, so our log message will contain the UUID - const command = 'ssh'; - const commandArguments = [ - // Force pseudo-terminal allocation, even though we're not a terminal - // (otherwise SSH will not attempt to connect) - '-t', - // Don't get prompted whether we trust the host's authenticity - // (since we're not a terminal and can't approve it, then SSH - // will not attempt to connect. And anyway, it's localhost, - // so we trust it :P) - '-o', 'StrictHostKeyChecking=false', - `${uuid}@localhost`]; - - // It should return 255 because of "Permission denied" - const expectedExitCode = 255; - - executeSync(command, commandArguments, expectedExitCode); -}; - -const createWindowsEventLogSource = (logName, source) => { - const expectedExitCode = 0; - const command = `[System.Diagnostics.EventLog]::CreateEventSource("${source}", "${logName}")` - - executeSync('powershell', [command], expectedExitCode); -} - -const createWindowsEventLogMessage = (logName, source, message) => { - const expectedExitCode = 0; - const command = `Write-EventLog -LogName "${logName}" -Source "${source}" -EventID 3001 -EntryType Information -Message "${message}"` - - executeSync('powershell', [command], expectedExitCode); -} - -const causeEventToBeWrittenToWindowsApplicationLog = (logName, source, message) => { - // Create a source so that for debugging we can easily just look - // at logs created by us (this is just a nice to have, we could also - // just write as some existing source) - createWindowsEventLogSource(logName, source); - - createWindowsEventLogMessage(logName, source, message); -} - -/** - * This tests all things directly configurable from the Infrastructure Agent. - * - * See https://docs.newrelic.com/docs/logs/forward-logs/forward-your-logs-using-infrastructure-agent. - */ -describe('Infrastructure Agent Fluent Bit features', () => { - let nrdb; - - beforeAll(() => { - const accountId = requireEnvironmentVariable('ACCOUNT_ID'); - const apiKey = requireEnvironmentVariable('API_KEY'); - const nerdGraphUrl = requireEnvironmentVariable('NERD_GRAPH_URL'); - - // Read configuration - nrdb = newNrdb({ accountId, apiKey, nerdGraphUrl }); - - }); - - const waitForLogMessageContaining = async (substring) => { - return nrdb.waitToFindOne({ where: `message like '%${substring}%'` }); - } - - const testOnlyIfSet = (environmentVariableName) => { - return process.env[environmentVariableName] ? test : test.skip; - } - - testOnlyIfSet('MONITORED_FILE')('detects appending to a file', async () => { - // Create a string with a unique value in it so that we can find it later - const uuid = uuidv4(); - const line = `fluent-bit-tests: tail ${uuid}`; - - // Append that string to our test log file - const file = requireEnvironmentVariable('MONITORED_FILE'); - appendTo(file, line); - - // Wait for that log line to show up in NRDB - await waitForLogMessageContaining(uuid); - }); - - testOnlyIfSet('MONITORED_TCP_PORT')('detects writing to TCP port', async () => { - // Create a string with a unique value in it so that we can find it later - const uuid = uuidv4(); - const line = `fluent-bit-tests: tcp ${uuid}`; - - // Write that string to the TCP socket - const port = requireEnvironmentVariable('MONITORED_TCP_PORT'); - writeToTcpSocket(port, line); - - // Wait for that log line to show up in NRDB - await waitForLogMessageContaining(uuid); - }); - - testOnlyIfSet('MONITORED_SYSLOG_RFC_5424_TCP_PORT')('detects writing to a TCP socket with a syslog RFC 5424 message', async () => { - // Create a string with a unique value in it so that we can find it later - const uuid = uuidv4(); - const message = `fluent-bit-tests: syslog (TCP socket - RFC 5424) ${uuid}`; - const syslog = rfc5424(message); - - // Write that string to a TCP socket - const port = requireEnvironmentVariable('MONITORED_SYSLOG_RFC_5424_TCP_PORT'); - writeToTcpSocket(port, syslog); - - // Wait for that log line to show up in NRDB - await waitForLogMessageContaining(uuid); - }); - - testOnlyIfSet('MONITORED_SYSLOG_RFC_5424_UDP_PORT')('detects writing to a UDP socket with a syslog RFC 5424 message', async () => { - // Create a string with a unique value in it so that we can find it later - const uuid = uuidv4(); - const message = `fluent-bit-tests: syslog (UDP socket - RFC 5424) ${uuid}`; - const syslog = rfc5424(message); - - // Write that string to a UDP socket - const port = requireEnvironmentVariable('MONITORED_SYSLOG_RFC_5424_UDP_PORT'); - writeToUdpSocket(port, syslog); - - // Wait for that log line to show up in NRDB - await waitForLogMessageContaining(uuid); - }); - - testOnlyIfSet('MONITORED_SYSTEMD_UNIT')('detects a log message in systemd', async () => { - // This test currently only knows how to create ssh systemd logs - const monitoredSystemdUnit = requireEnvironmentVariable('MONITORED_SYSTEMD_UNIT'); - expect(monitoredSystemdUnit).toMatch(/^ssh|sshd$/); // This is 'ssh' in some distros, 'sshd' in others - - // Create a unique string so that we can find the log message later - const uuid = uuidv4(); - - // Cause a message with the UUID to be written to journald - // Should log something containing "input_userauth_request: invalid user ${uuid} [preauth]" - causeJournaldMessageToBeWrittenForSsh(uuid); - - // Wait for that log line to show up in NRDB - await waitForLogMessageContaining(uuid); - }); - - testOnlyIfSet('MONITORED_WINDOWS_LOG_NAME_USING_WINLOG')('detects a Windows event using "winlog" input plugin', async () => { - // Create a unique string so that we can find the log message later - const uuid = uuidv4(); - - // Cause an event containing the UUID to be written to the Windows Event Log - const monitoredLogName = requireEnvironmentVariable('MONITORED_WINDOWS_LOG_NAME_USING_WINLOG'); - const source = 'Fluent Bit Tests: winlog'; - const message = `fluent-bit-tests: winlog ${uuid}`; - causeEventToBeWrittenToWindowsApplicationLog(monitoredLogName, source, message); - - // Wait for that log line to show up in NRDB - // - // NOTE: this may take a while, since unlike winevtlog (which just reads - // new events by default), winlog will read all events in the monitored log - await waitForLogMessageContaining(message); - }); - - testOnlyIfSet('MONITORED_WINDOWS_LOG_NAME_USING_WINEVTLOG')('detects a Windows event using "winevtlog" input plugin', async () => { - // Create a unique string so that we can find the log message later - const uuid = uuidv4(); - - // Cause an event containing the UUID to be written to the Windows Event Log - const monitoredLogName = requireEnvironmentVariable('MONITORED_WINDOWS_LOG_NAME_USING_WINEVTLOG'); - const source = 'Fluent Bit Tests: winevtlog'; - const message = `fluent-bit-tests: winevtlog ${uuid}`; - causeEventToBeWrittenToWindowsApplicationLog(monitoredLogName, source, message); - - // Wait for that log line to show up in NRDB - await waitForLogMessageContaining(message); - }); -}); \ No newline at end of file diff --git a/integration-tests/test-suite/jest.config.js b/integration-tests/test-suite/jest.config.js index f1454704..cba78352 100644 --- a/integration-tests/test-suite/jest.config.js +++ b/integration-tests/test-suite/jest.config.js @@ -4,6 +4,7 @@ module.exports = { testTimeout: WAIT_FOR_TEST_COMPLETION, testFailureExitCode: 0, reporters: [ + 'default', 'jest-junit' ] }; diff --git a/integration-tests/test-suite/lib/test-util.js b/integration-tests/test-suite/lib/test-util.js new file mode 100644 index 00000000..f3d823e0 --- /dev/null +++ b/integration-tests/test-suite/lib/test-util.js @@ -0,0 +1,24 @@ +const { spawnSync } = require('child_process'); +const logger = require('./logger'); + +const testOnlyIfSet = (environmentVariableName) => { + return process.env[environmentVariableName] ? test : test.skip; +} + +const waitForLogMessageContaining = async (nrdb, substring) => { + return nrdb.waitToFindOne({ where: `message like '%${substring}%'` }); +} + +const executeSync = (command, commandArguments, expectedExitCode) => { + const result = spawnSync(command, commandArguments); + + logger.info(result.stdout?.toString()); + logger.error(result.stderr?.toString()); + expect(result.status).toEqual(expectedExitCode); +} + +module.exports = { + testOnlyIfSet, + waitForLogMessageContaining, + executeSync +} \ No newline at end of file diff --git a/integration-tests/test-suite/package-lock.json b/integration-tests/test-suite/package-lock.json index 8a63f9ca..3209f460 100644 --- a/integration-tests/test-suite/package-lock.json +++ b/integration-tests/test-suite/package-lock.json @@ -15,7 +15,6 @@ "eslint-plugin-jest": "^27.2.1", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.4.2", - "jest-html-reporter": "^3.7.0", "jest-junit": "^15.0.0", "prettier": "^2.8.4", "winston": "^3.8.2" @@ -2123,15 +2122,6 @@ "node": ">= 8" } }, - "node_modules/dateformat": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.2.tgz", - "integrity": "sha512-EelsCzH0gMC2YmXuMeaZ3c6md1sUJQxyb1XXc4xaisi/K6qKukqZhKPrEQyRkdNIncgYyLoDTReq0nNyuKerTg==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -3367,27 +3357,6 @@ "fsevents": "^2.3.2" } }, - "node_modules/jest-html-reporter": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/jest-html-reporter/-/jest-html-reporter-3.10.2.tgz", - "integrity": "sha512-XRBa5ylHPUQoo8aJXEEdKsTruieTdlPbRktMx9WG9evMTxzJEKGFMaw5x+sQxJuClWdNR72GGwbOaz+6HIlksA==", - "dev": true, - "dependencies": { - "@jest/test-result": "^29.0.2", - "@jest/types": "^29.0.2", - "dateformat": "3.0.2", - "mkdirp": "^1.0.3", - "strip-ansi": "6.0.1", - "xmlbuilder": "15.0.0" - }, - "engines": { - "node": ">=4.8.3" - }, - "peerDependencies": { - "jest": "19.x - 29.x", - "typescript": "^3.7.x || ^4.3.x || ^5.x" - } - }, "node_modules/jest-junit": { "version": "15.0.0", "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-15.0.0.tgz", @@ -5121,15 +5090,6 @@ "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", "dev": true }, - "node_modules/xmlbuilder": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.0.0.tgz", - "integrity": "sha512-KLu/G0DoWhkncQ9eHSI6s0/w+T4TM7rQaLhtCaL6tORv8jFlJPlnGumsgTcGfYeS1qZ/IHqrvDG7zJZ4d7e+nw==", - "dev": true, - "engines": { - "node": ">=8.0" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -6801,12 +6761,6 @@ "which": "^2.0.1" } }, - "dateformat": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.2.tgz", - "integrity": "sha512-EelsCzH0gMC2YmXuMeaZ3c6md1sUJQxyb1XXc4xaisi/K6qKukqZhKPrEQyRkdNIncgYyLoDTReq0nNyuKerTg==", - "dev": true - }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -7682,20 +7636,6 @@ "walker": "^1.0.8" } }, - "jest-html-reporter": { - "version": "3.10.2", - "resolved": "https://registry.npmjs.org/jest-html-reporter/-/jest-html-reporter-3.10.2.tgz", - "integrity": "sha512-XRBa5ylHPUQoo8aJXEEdKsTruieTdlPbRktMx9WG9evMTxzJEKGFMaw5x+sQxJuClWdNR72GGwbOaz+6HIlksA==", - "dev": true, - "requires": { - "@jest/test-result": "^29.0.2", - "@jest/types": "^29.0.2", - "dateformat": "3.0.2", - "mkdirp": "^1.0.3", - "strip-ansi": "6.0.1", - "xmlbuilder": "15.0.0" - } - }, "jest-junit": { "version": "15.0.0", "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-15.0.0.tgz", @@ -8977,12 +8917,6 @@ "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", "dev": true }, - "xmlbuilder": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.0.0.tgz", - "integrity": "sha512-KLu/G0DoWhkncQ9eHSI6s0/w+T4TM7rQaLhtCaL6tORv8jFlJPlnGumsgTcGfYeS1qZ/IHqrvDG7zJZ4d7e+nw==", - "dev": true - }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/integration-tests/test-suite/package.json b/integration-tests/test-suite/package.json index 153f1279..4a42f316 100644 --- a/integration-tests/test-suite/package.json +++ b/integration-tests/test-suite/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "Validates fluent bit end-to-end with several configurations", "scripts": { - "test": "jest --verbose fluent-bit-tests.test.js" + "test": "jest --verbose" }, "author": "logging-team@newrelic.com", "license": "Apache-2.0", @@ -14,7 +14,6 @@ "eslint-plugin-jest": "^27.2.1", "eslint-plugin-prettier": "^4.2.1", "jest": "^29.4.2", - "jest-html-reporter": "^3.7.0", "jest-junit": "^15.0.0", "prettier": "^2.8.4", "winston": "^3.8.2" @@ -22,11 +21,5 @@ "jest-junit": { "outputDirectory": "reports/tests", "outputName": "test-report.xml" - }, - "jest-html-reporter": { - "outputPath": "./reports/tests/test-report.html", - "includeConsoleLog": true, - "includeFailureMsg": true, - "includeSuiteFailure": true } } diff --git a/integration-tests/test-suite/syslog.test.js b/integration-tests/test-suite/syslog.test.js new file mode 100644 index 00000000..8c81e8dc --- /dev/null +++ b/integration-tests/test-suite/syslog.test.js @@ -0,0 +1,96 @@ +const logger = require('./lib/logger'); +const Nrdb = require('./lib/nrdb'); +const { Socket } = require('net'); +const dgram = require('node:dgram'); +const { v4: uuidv4 } = require('uuid'); +const { currentTimeAsIso8601 } = require('./lib/time'); +const { requireEnvironmentVariable } = require('./lib/environmentVariables'); +const { waitForLogMessageContaining, testOnlyIfSet } = require("./lib/test-util"); + +/** + * The newline is important -- Fluent Bit will wait + * until it sees a complete line before sending a log message + */ +const addNewlineSoFluentBitDetectsLine = (line) => `${line}\n`; + +const writeToTcpSocket = (port, line) => { + logger.info(`Writing to TCP socket at localhost:${port}...`); + const socket = new Socket(); + socket.on('error', console.error); + socket.connect({ port }, () => { + socket.write(addNewlineSoFluentBitDetectsLine(line)); + socket.end(); + }); +}; + +const writeToUdpSocket = (port, line) => { + logger.info(`Writing to UDP socket at localhost:${port}...`); + const socket = dgram.createSocket('udp4'); + socket.send( + addNewlineSoFluentBitDetectsLine(line), + port, + 'localhost', + (error) => { + if (error) { + console.error(error); + } + + socket.close(); + }); +}; + +const rfc5424 = (message) => { + return `<165>1 ${currentTimeAsIso8601()} example.com su - ID47 - ${message}`; +}; + +/** + * This tests all things directly configurable from the Infrastructure Agent. + * + * See https://docs.newrelic.com/docs/logs/forward-logs/forward-your-logs-using-infrastructure-agent. + */ +describe('SYSLOG tests', () => { + let nrdb; + + beforeAll(() => { + const accountId = requireEnvironmentVariable('ACCOUNT_ID'); + const apiKey = requireEnvironmentVariable('API_KEY'); + const nerdGraphUrl = requireEnvironmentVariable('NERD_GRAPH_URL'); + + // Read configuration + nrdb = new Nrdb({ + accountId, + apiKey, + nerdGraphUrl, + }); + + }); + + testOnlyIfSet('MONITORED_SYSLOG_RFC_5424_TCP_PORT')('detects writing to a TCP socket with a syslog RFC 5424 message', async () => { + // Create a string with a unique value in it so that we can find it later + const uuid = uuidv4(); + const message = `fluent-bit-tests: syslog (TCP socket - RFC 5424) ${uuid}`; + const syslog = rfc5424(message); + + // Write that string to a TCP socket + const port = requireEnvironmentVariable('MONITORED_SYSLOG_RFC_5424_TCP_PORT'); + writeToTcpSocket(port, syslog); + + // Wait for that log line to show up in NRDB + await waitForLogMessageContaining(nrdb, uuid); + }); + + testOnlyIfSet('MONITORED_SYSLOG_RFC_5424_UDP_PORT')('detects writing to a UDP socket with a syslog RFC 5424 message', async () => { + // Create a string with a unique value in it so that we can find it later + const uuid = uuidv4(); + const message = `fluent-bit-tests: syslog (UDP socket - RFC 5424) ${uuid}`; + const syslog = rfc5424(message); + + // Write that string to a UDP socket + const port = requireEnvironmentVariable('MONITORED_SYSLOG_RFC_5424_UDP_PORT'); + writeToUdpSocket(port, syslog); + + // Wait for that log line to show up in NRDB + await waitForLogMessageContaining(nrdb, uuid); + }); + +}); \ No newline at end of file diff --git a/integration-tests/test-suite/systemd.test.js b/integration-tests/test-suite/systemd.test.js new file mode 100644 index 00000000..c881ebca --- /dev/null +++ b/integration-tests/test-suite/systemd.test.js @@ -0,0 +1,67 @@ +const Nrdb = require('./lib/nrdb'); +const { v4: uuidv4 } = require('uuid'); +const { requireEnvironmentVariable } = require('./lib/environmentVariables'); +const { executeSync, testOnlyIfSet, waitForLogMessageContaining } = require("./lib/test-util"); + +const causeJournaldMessageToBeWrittenForSsh = (uuid) => { + // This _attempts_ to make an SSH connecting using the UUID as a user. + // It will fail to connect, of course, since the UUID isn't a user, + // but the attempt will be logged to journald for sshd with the name + // of the user, so our log message will contain the UUID + const command = 'ssh'; + const commandArguments = [ + // Force pseudo-terminal allocation, even though we're not a terminal + // (otherwise SSH will not attempt to connect) + '-t', + // Don't get prompted whether we trust the host's authenticity + // (since we're not a terminal and can't approve it, then SSH + // will not attempt to connect. And anyway, it's localhost, + // so we trust it :P) + '-o', 'StrictHostKeyChecking=false', + `${uuid}@localhost`]; + + // It should return 255 because of "Permission denied" + const expectedExitCode = 255; + + executeSync(command, commandArguments, expectedExitCode); +}; + +/** + * This tests all things directly configurable from the Infrastructure Agent. + * + * See https://docs.newrelic.com/docs/logs/forward-logs/forward-your-logs-using-infrastructure-agent. + */ +describe('SYSTEMD unit input', () => { + let nrdb; + + beforeAll(() => { + const accountId = requireEnvironmentVariable('ACCOUNT_ID'); + const apiKey = requireEnvironmentVariable('API_KEY'); + const nerdGraphUrl = requireEnvironmentVariable('NERD_GRAPH_URL'); + + // Read configuration + nrdb = new Nrdb({ + accountId, + apiKey, + nerdGraphUrl, + }); + + }); + + testOnlyIfSet('MONITORED_SYSTEMD_UNIT')('detects a log message in systemd', async () => { + // This test currently only knows how to create ssh systemd logs + const monitoredSystemdUnit = requireEnvironmentVariable('MONITORED_SYSTEMD_UNIT'); + expect(monitoredSystemdUnit).toMatch(/^ssh|sshd$/); // This is 'ssh' in some distros, 'sshd' in others + + // Create a unique string so that we can find the log message later + const uuid = uuidv4(); + + // Cause a message with the UUID to be written to journald + // Should log something containing "input_userauth_request: invalid user ${uuid} [preauth]" + causeJournaldMessageToBeWrittenForSsh(uuid); + + // Wait for that log line to show up in NRDB + await waitForLogMessageContaining(nrdb, uuid); + }); + +}); \ No newline at end of file diff --git a/integration-tests/test-suite/tail.test.js b/integration-tests/test-suite/tail.test.js new file mode 100644 index 00000000..80feb0cf --- /dev/null +++ b/integration-tests/test-suite/tail.test.js @@ -0,0 +1,54 @@ +const logger = require('./lib/logger'); +const Nrdb = require('./lib/nrdb'); +const fs = require('fs'); +const { v4: uuidv4 } = require('uuid'); +const { requireEnvironmentVariable } = require('./lib/environmentVariables'); +const { testOnlyIfSet, waitForLogMessageContaining } = require("./lib/test-util"); + +/** + * The newline is important -- Fluent Bit will wait + * until it sees a complete line before sending a log message + */ +const addNewlineSoFluentBitDetectsLine = (line) => `${line}\n`; + +const appendTo = (file, line) => { + logger.info(`Appending to ${file}...`); + fs.appendFileSync(file, addNewlineSoFluentBitDetectsLine(line)); +}; + +/** + * This tests all things directly configurable from the Infrastructure Agent. + * + * See https://docs.newrelic.com/docs/logs/forward-logs/forward-your-logs-using-infrastructure-agent. + */ +describe('TAIL input', () => { + let nrdb; + + beforeAll(() => { + const accountId = requireEnvironmentVariable('ACCOUNT_ID'); + const apiKey = requireEnvironmentVariable('API_KEY'); + const nerdGraphUrl = requireEnvironmentVariable('NERD_GRAPH_URL'); + + // Read configuration + nrdb = new Nrdb({ + accountId, + apiKey, + nerdGraphUrl, + }); + + }); + + testOnlyIfSet('MONITORED_FILE')('detects appending to a file', async () => { + // Create a string with a unique value in it so that we can find it later + const uuid = uuidv4(); + const line = `fluent-bit-tests: tail ${uuid}`; + + // Append that string to our test log file + const file = requireEnvironmentVariable('MONITORED_FILE'); + appendTo(file, line); + + // Wait for that log line to show up in NRDB + await waitForLogMessageContaining(nrdb, uuid); + }); + +}); \ No newline at end of file diff --git a/integration-tests/test-suite/tcp.test.js b/integration-tests/test-suite/tcp.test.js new file mode 100644 index 00000000..573648e3 --- /dev/null +++ b/integration-tests/test-suite/tcp.test.js @@ -0,0 +1,76 @@ +const logger = require('./lib/logger'); +const Nrdb = require('./lib/nrdb'); +const { Socket } = require('net'); +const dgram = require('node:dgram'); +const { v4: uuidv4 } = require('uuid'); +const { requireEnvironmentVariable } = require('./lib/environmentVariables'); +const { testOnlyIfSet, waitForLogMessageContaining } = require("./lib/test-util"); + +/** + * The newline is important -- Fluent Bit will wait + * until it sees a complete line before sending a log message + */ +const addNewlineSoFluentBitDetectsLine = (line) => `${line}\n`; + +const writeToTcpSocket = (port, line) => { + logger.info(`Writing to TCP socket at localhost:${port}...`); + const socket = new Socket(); + socket.on('error', console.error); + socket.connect({ port }, () => { + socket.write(addNewlineSoFluentBitDetectsLine(line)); + socket.end(); + }); +}; + +const writeToUdpSocket = (port, line) => { + logger.info(`Writing to UDP socket at localhost:${port}...`); + const socket = dgram.createSocket('udp4'); + socket.send( + addNewlineSoFluentBitDetectsLine(line), + port, + 'localhost', + (error) => { + if (error) { + console.error(error); + } + + socket.close(); + }); +}; + +/** + * This tests all things directly configurable from the Infrastructure Agent. + * + * See https://docs.newrelic.com/docs/logs/forward-logs/forward-your-logs-using-infrastructure-agent. + */ +describe('TCP input', () => { + let nrdb; + + beforeAll(() => { + const accountId = requireEnvironmentVariable('ACCOUNT_ID'); + const apiKey = requireEnvironmentVariable('API_KEY'); + const nerdGraphUrl = requireEnvironmentVariable('NERD_GRAPH_URL'); + + // Read configuration + nrdb = new Nrdb({ + accountId, + apiKey, + nerdGraphUrl, + }); + + }); + + testOnlyIfSet('MONITORED_TCP_PORT')('detects writing to TCP port', async () => { + // Create a string with a unique value in it so that we can find it later + const uuid = uuidv4(); + const line = `fluent-bit-tests: tcp ${uuid}`; + + // Write that string to the TCP socket + const port = requireEnvironmentVariable('MONITORED_TCP_PORT'); + writeToTcpSocket(port, line); + + // Wait for that log line to show up in NRDB + await waitForLogMessageContaining(nrdb, uuid); + }); + +}); \ No newline at end of file diff --git a/integration-tests/test-suite/winlog.test.js b/integration-tests/test-suite/winlog.test.js new file mode 100644 index 00000000..f7ed75ca --- /dev/null +++ b/integration-tests/test-suite/winlog.test.js @@ -0,0 +1,81 @@ +const Nrdb = require('./lib/nrdb'); +const { v4: uuidv4 } = require('uuid'); +const { requireEnvironmentVariable } = require('./lib/environmentVariables'); +const { spawnSync} = require("child_process"); +const logger = require("./lib/logger"); +const { testOnlyIfSet, waitForLogMessageContaining, executeSync } = require("./lib/test-util"); + +const createWindowsEventLogSource = (logName, source) => { + const createEventSourceCommand = `[System.Diagnostics.EventLog]::CreateEventSource("${source}", "${logName}")` + try { + spawnSync('powershell', [createEventSourceCommand]); + } catch (err) { + logger.error('Error creating event log', err); + } +} + +const createWindowsEventLogMessage = (logName, source, message) => { + const expectedExitCode = 0; + const command = `Write-EventLog -LogName "${logName}" -Source "${source}" -EventID 3001 -EntryType Information -Message "${message}"` + + executeSync('powershell', [command], expectedExitCode); +} + +const causeEventToBeWrittenToWindowsApplicationLog = (logName, source, message) => { + // Create a source so that for debugging we can easily just look + // at logs created by us (this is just a nice to have, we could also + // just write as some existing source) + createWindowsEventLogSource(logName, source); + + createWindowsEventLogMessage(logName, source, message); +} + +/** + * This tests all things directly configurable from the Infrastructure Agent. + * + * See https://docs.newrelic.com/docs/logs/forward-logs/forward-your-logs-using-infrastructure-agent. + */ +describe('WINLOG & WINEVTLOG inputs', () => { + let nrdb; + + beforeAll(() => { + const accountId = requireEnvironmentVariable('ACCOUNT_ID'); + const apiKey = requireEnvironmentVariable('API_KEY'); + const nerdGraphUrl = requireEnvironmentVariable('NERD_GRAPH_URL'); + + // Read configuration + nrdb = new Nrdb({ accountId, apiKey, nerdGraphUrl }); + + }); + + testOnlyIfSet('MONITORED_WINDOWS_LOG_NAME_USING_WINLOG')('detects a Windows event using "winlog" input plugin', async () => { + // Create a unique string so that we can find the log message later + const uuid = uuidv4(); + + // Cause an event containing the UUID to be written to the Windows Event Log + const monitoredLogName = requireEnvironmentVariable('MONITORED_WINDOWS_LOG_NAME_USING_WINLOG'); + const source = 'Fluent Bit Tests: winlog'; + const message = `fluent-bit-tests: winlog ${uuid}`; + await causeEventToBeWrittenToWindowsApplicationLog(monitoredLogName, source, message); + + // Wait for that log line to show up in NRDB + // + // NOTE: this may take a while, since unlike winevtlog (which just reads + // new events by default), winlog will read all events in the monitored log + await waitForLogMessageContaining(nrdb, message); + }); + + testOnlyIfSet('MONITORED_WINDOWS_LOG_NAME_USING_WINEVTLOG')('detects a Windows event using "winevtlog" input plugin', async () => { + // Create a unique string so that we can find the log message later + const uuid = uuidv4(); + + // Cause an event containing the UUID to be written to the Windows Event Log + const monitoredLogName = requireEnvironmentVariable('MONITORED_WINDOWS_LOG_NAME_USING_WINEVTLOG'); + const source = 'Fluent Bit Tests: winevtlog'; + const message = `fluent-bit-tests: winevtlog ${uuid}`; + await causeEventToBeWrittenToWindowsApplicationLog(monitoredLogName, source, message); + + // Wait for that log line to show up in NRDB + await waitForLogMessageContaining(nrdb, message); + }); +}); \ No newline at end of file