diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 8feed628a2a..87356de2458 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -25,10 +25,10 @@ jobs: go-version-file: .go-version - name: golangci-lint - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v6.1.1 with: # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version - version: v1.55.2 + version: v1.61.0 # Give the job more time to execute. # Regarding `--whole-files`, the linter is supposed to support linting of changed a patch only but, diff --git a/NOTICE.txt b/NOTICE.txt index 19d663c021a..95b45b8f680 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -6066,6 +6066,217 @@ Contents of probable licence file $GOMODCACHE/github.com/open-telemetry/opentele limitations under the License. +-------------------------------------------------------------------------------- +Dependency : github.com/open-telemetry/opentelemetry-collector-contrib/pkg/status +Version: v0.113.0 +Licence type (autodetected): Apache-2.0 +-------------------------------------------------------------------------------- + +Contents of probable licence file $GOMODCACHE/github.com/open-telemetry/opentelemetry-collector-contrib/pkg/status@v0.113.0/LICENSE: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + -------------------------------------------------------------------------------- Dependency : github.com/open-telemetry/opentelemetry-collector-contrib/processor/attributesprocessor Version: v0.113.0 @@ -11156,12 +11367,12 @@ Contents of probable licence file $GOMODCACHE/go.opentelemetry.io/collector/comp -------------------------------------------------------------------------------- -Dependency : go.opentelemetry.io/collector/confmap -Version: v1.19.0 +Dependency : go.opentelemetry.io/collector/component/componentstatus +Version: v0.113.0 Licence type (autodetected): Apache-2.0 -------------------------------------------------------------------------------- -Contents of probable licence file $GOMODCACHE/go.opentelemetry.io/collector/confmap@v1.19.0/LICENSE: +Contents of probable licence file $GOMODCACHE/go.opentelemetry.io/collector/component/componentstatus@v0.113.0/LICENSE: Apache License @@ -11368,12 +11579,12 @@ Contents of probable licence file $GOMODCACHE/go.opentelemetry.io/collector/conf -------------------------------------------------------------------------------- -Dependency : go.opentelemetry.io/collector/confmap/converter/expandconverter -Version: v0.113.0 +Dependency : go.opentelemetry.io/collector/confmap +Version: v1.19.0 Licence type (autodetected): Apache-2.0 -------------------------------------------------------------------------------- -Contents of probable licence file $GOMODCACHE/go.opentelemetry.io/collector/confmap/converter/expandconverter@v0.113.0/LICENSE: +Contents of probable licence file $GOMODCACHE/go.opentelemetry.io/collector/confmap@v1.19.0/LICENSE: Apache License @@ -79354,218 +79565,6 @@ Contents of probable licence file $GOMODCACHE/go.opentelemetry.io/collector/clie limitations under the License. --------------------------------------------------------------------------------- -Dependency : go.opentelemetry.io/collector/component/componentstatus -Version: v0.113.0 -Licence type (autodetected): Apache-2.0 --------------------------------------------------------------------------------- - -Contents of probable licence file $GOMODCACHE/go.opentelemetry.io/collector/component/componentstatus@v0.113.0/LICENSE: - - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - -------------------------------------------------------------------------------- Dependency : go.opentelemetry.io/collector/config/configauth Version: v0.113.0 diff --git a/changelog/fragments/1729011748-Add-EDOT-hybrid-mode.yaml b/changelog/fragments/1729011748-Add-EDOT-hybrid-mode.yaml new file mode 100644 index 00000000000..f7ed858e654 --- /dev/null +++ b/changelog/fragments/1729011748-Add-EDOT-hybrid-mode.yaml @@ -0,0 +1,32 @@ +# Kind can be one of: +# - breaking-change: a change to previously-documented behavior +# - deprecation: functionality that is being removed in a later release +# - bug-fix: fixes a problem in a previous version +# - enhancement: extends functionality but does not break or fix existing behavior +# - feature: new functionality +# - known-issue: problems that we are aware of in a given version +# - security: impacts on the security of a product or a user’s deployment. +# - upgrade: important information for someone upgrading from a prior version +# - other: does not fit into any of the other categories +kind: feature + +# Change summary; a 80ish characters long description of the change. +summary: Add ability to run Elastic Distribution of OTel Collector at the same time as other inputs + +# Long description; in case the summary is not enough to describe the change +# this field accommodate a description without length limits. +# NOTE: This field will be rendered only for breaking-change and known-issue kinds at the moment. +#description: + +# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc. +component: elastic-agent + +# PR URL; optional; the PR number that added the changeset. +# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added. +# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number. +# Please provide it if you are adding a fragment for a different PR. +pr: https://github.com/elastic/elastic-agent/pull/5767 + +# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of). +# If not present is automatically filled by the tooling with the issue linked to the PR number. +#issue: https://github.com/owner/repo/1234 diff --git a/control_v2.proto b/control_v2.proto index 3437fa39e59..ad8efdfe7f8 100644 --- a/control_v2.proto +++ b/control_v2.proto @@ -23,6 +23,18 @@ enum State { ROLLBACK = 8; } +// CollectorComponentStatus used for OTel collector components. +enum CollectorComponentStatus { + StatusNone = 0; + StatusStarting = 1; + StatusOK = 2; + StatusRecoverableError = 3; + StatusPermanentError = 4; + StatusFatalError = 5; + StatusStopping = 6; + StatusStopped = 7; +} + // Unit Type running inside a component. enum UnitType { INPUT = 0; @@ -173,6 +185,18 @@ message StateAgentInfo { bool isManaged = 8; } +// CollectorComponent is the status of an OTel collector component. +message CollectorComponent { + // Status of the component. + CollectorComponentStatus status = 1; + // Error is set to the reported error. + string error = 2; + // Timestamp of status. + string timestamp = 3; + // Status information for sub-components of this component. + map ComponentStatusMap = 4; +} + // StateResponse is the current state of Elastic Agent. // Next unused id: 8 message StateResponse { @@ -194,6 +218,9 @@ message StateResponse { // Upgrade details UpgradeDetails upgrade_details = 7; + + // OTel collector component status information. + CollectorComponent collector = 8; } // UpgradeDetails captures the details of an ongoing Agent upgrade. diff --git a/go.mod b/go.mod index 232bfcb0a68..0b64c0408ba 100644 --- a/go.mod +++ b/go.mod @@ -44,6 +44,7 @@ require ( github.com/oklog/ulid/v2 v2.1.0 github.com/open-telemetry/opentelemetry-collector-contrib/extension/healthcheckextension v0.113.0 github.com/open-telemetry/opentelemetry-collector-contrib/extension/pprofextension v0.113.0 + github.com/open-telemetry/opentelemetry-collector-contrib/pkg/status v0.113.0 github.com/open-telemetry/opentelemetry-collector-contrib/receiver/jaegerreceiver v0.113.0 github.com/open-telemetry/opentelemetry-collector-contrib/receiver/prometheusreceiver v0.113.0 github.com/open-telemetry/opentelemetry-collector-contrib/receiver/zipkinreceiver v0.113.0 @@ -62,6 +63,7 @@ require ( go.elastic.co/apm/v2 v2.6.0 go.elastic.co/ecszap v1.0.2 go.elastic.co/go-licence-detector v0.7.0 + go.opentelemetry.io/collector/component/componentstatus v0.113.0 go.opentelemetry.io/collector/processor/memorylimiterprocessor v0.113.0 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.28.0 @@ -109,7 +111,6 @@ require ( github.com/open-telemetry/opentelemetry-collector-contrib/receiver/kubeletstatsreceiver v0.113.0 go.opentelemetry.io/collector/component v0.113.0 go.opentelemetry.io/collector/confmap v1.19.0 - go.opentelemetry.io/collector/confmap/converter/expandconverter v0.113.0 go.opentelemetry.io/collector/confmap/provider/envprovider v1.19.0 go.opentelemetry.io/collector/confmap/provider/fileprovider v1.19.0 go.opentelemetry.io/collector/confmap/provider/httpprovider v1.19.0 @@ -497,7 +498,6 @@ require ( go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/collector v0.113.0 // indirect go.opentelemetry.io/collector/client v1.19.0 // indirect - go.opentelemetry.io/collector/component/componentstatus v0.113.0 // indirect go.opentelemetry.io/collector/config/configauth v0.113.0 // indirect go.opentelemetry.io/collector/config/configcompression v1.19.0 // indirect go.opentelemetry.io/collector/config/configgrpc v0.113.0 // indirect diff --git a/go.sum b/go.sum index 35d29a7ed39..cf15f3814a5 100644 --- a/go.sum +++ b/go.sum @@ -1153,6 +1153,8 @@ github.com/open-telemetry/opentelemetry-collector-contrib/pkg/sampling v0.113.0 github.com/open-telemetry/opentelemetry-collector-contrib/pkg/sampling v0.113.0/go.mod h1:X25Nhlw6xhuNSd/C0FeEwmD4PGmcXDl7pa2jR0UREkU= github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza v0.113.0 h1:G8w+wg4nnqBqe297fBWnjJ5Tg2OYDVEMsdWA9/3ozxQ= github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza v0.113.0/go.mod h1:m3hDVsXPQzQfeji3+hn7NYJPHDRlHhQRNd5T7N5wZqc= +github.com/open-telemetry/opentelemetry-collector-contrib/pkg/status v0.113.0 h1:3cHaq0xbqzoEBxcHDl8h0YpUZ1W3St7UG5YQ8f9qCxw= +github.com/open-telemetry/opentelemetry-collector-contrib/pkg/status v0.113.0/go.mod h1:aJlolKULr8dNC4PlPkqpnBYGHrbanp4+cODG/8V/GW8= github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/jaeger v0.113.0 h1:mFYOvag34kGXceVj29k0ZpBUyjEX7VZq+KctUSfNiG0= github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/jaeger v0.113.0/go.mod h1:54P38b2i1CgHvZLxD3EAzVccqreamGEz2U4pqy9DuHw= github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/prometheus v0.113.0 h1:vKtNSM3VQBTJx1ecf+I1iqn4kj7fKif1SpBLQ+numf8= @@ -1519,8 +1521,6 @@ go.opentelemetry.io/collector/config/internal v0.113.0 h1:9RAzH8v7ItFT1npHpvP0Sv go.opentelemetry.io/collector/config/internal v0.113.0/go.mod h1:yC7E4h1Uj0SubxcFImh6OvBHFTjMh99+A5PuyIgDWqc= go.opentelemetry.io/collector/confmap v1.19.0 h1:TQ0lZpAKqgsE0EKk+u4JA+uBbPYeFRmWP3GH43w40CY= go.opentelemetry.io/collector/confmap v1.19.0/go.mod h1:GgNu1ElPGmLn9govqIfjaopvdspw4PJ9KeDtWC4E2Q4= -go.opentelemetry.io/collector/confmap/converter/expandconverter v0.113.0 h1:DBjWXlVzdwVbs1ZOH+k1vmoBt3TLx8NvRK8ZK3nKdmo= -go.opentelemetry.io/collector/confmap/converter/expandconverter v0.113.0/go.mod h1:/YDWibNLalyfd0BR0V5ixiParsNCvVKkA58f3bcu/AA= go.opentelemetry.io/collector/confmap/provider/envprovider v1.19.0 h1:f8O/I5pVRN86Gx5mHekNx92S6fGdOS4VcooRJKWe6Bs= go.opentelemetry.io/collector/confmap/provider/envprovider v1.19.0/go.mod h1:AiaW5YW1LD0/WlZuc8eZuZPBH6PA9QqsiAYRX1iC6T0= go.opentelemetry.io/collector/confmap/provider/fileprovider v1.19.0 h1:TYwyk4ea3U+5MYcEjrzZAaonBcLlabQu8CZeB7ekAYY= diff --git a/internal/pkg/agent/application/actions/handlers/handler_action_upgrade_test.go b/internal/pkg/agent/application/actions/handlers/handler_action_upgrade_test.go index fccffb46b70..3a930771cbd 100644 --- a/internal/pkg/agent/application/actions/handlers/handler_action_upgrade_test.go +++ b/internal/pkg/agent/application/actions/handlers/handler_action_upgrade_test.go @@ -110,7 +110,7 @@ func TestUpgradeHandler(t *testing.T) { return nil, nil }, }, - nil, nil, nil, nil, nil, false) + nil, nil, nil, nil, nil, false, nil) //nolint:errcheck // We don't need the termination state of the Coordinator go c.Run(ctx) @@ -169,7 +169,7 @@ func TestUpgradeHandlerSameVersion(t *testing.T) { return nil, err }, }, - nil, nil, nil, nil, nil, false) + nil, nil, nil, nil, nil, false, nil) //nolint:errcheck // We don't need the termination state of the Coordinator go c.Run(ctx) @@ -230,7 +230,7 @@ func TestUpgradeHandlerNewVersion(t *testing.T) { return nil, nil }, }, - nil, nil, nil, nil, nil, false) + nil, nil, nil, nil, nil, false, nil) //nolint:errcheck // We don't need the termination state of the Coordinator go c.Run(ctx) diff --git a/internal/pkg/agent/application/apm_config_modifier.go b/internal/pkg/agent/application/apm_config_modifier.go index cf5f664d2a2..c396b6567ae 100644 --- a/internal/pkg/agent/application/apm_config_modifier.go +++ b/internal/pkg/agent/application/apm_config_modifier.go @@ -97,7 +97,7 @@ func getAPMConfigFromMap(cfg map[string]any) (*monitoringcfg.APMConfig, error) { } monitoringConfig := new(monitoringcfg.APMConfig) - err = newConfigFrom.Unpack(monitoringConfig) + err = newConfigFrom.UnpackTo(monitoringConfig) if err != nil { return nil, fmt.Errorf("error unpacking apm config: %w", err) } diff --git a/internal/pkg/agent/application/application.go b/internal/pkg/agent/application/application.go index b72ff5664b5..4e42ccca917 100644 --- a/internal/pkg/agent/application/application.go +++ b/internal/pkg/agent/application/application.go @@ -9,10 +9,6 @@ import ( "fmt" "time" - "github.com/elastic/elastic-agent/pkg/features" - "github.com/elastic/elastic-agent/pkg/limits" - "github.com/elastic/elastic-agent/version" - "go.elastic.co/apm/v2" "github.com/elastic/elastic-agent-libs/logp" @@ -28,10 +24,14 @@ import ( "github.com/elastic/elastic-agent/internal/pkg/capabilities" "github.com/elastic/elastic-agent/internal/pkg/composable" "github.com/elastic/elastic-agent/internal/pkg/config" + otelmanager "github.com/elastic/elastic-agent/internal/pkg/otel/manager" "github.com/elastic/elastic-agent/internal/pkg/release" "github.com/elastic/elastic-agent/pkg/component" "github.com/elastic/elastic-agent/pkg/component/runtime" "github.com/elastic/elastic-agent/pkg/core/logger" + "github.com/elastic/elastic-agent/pkg/features" + "github.com/elastic/elastic-agent/pkg/limits" + "github.com/elastic/elastic-agent/version" ) // New creates a new Agent and bootstrap the required subsystem. @@ -176,13 +176,13 @@ func New( } } - // no need for vars in otel mode varsManager, err := composable.New(log, rawConfig, composableManaged) if err != nil { return nil, nil, nil, errors.New(err, "failed to initialize composable controller") } - coord := coordinator.New(log, cfg, logLevel, agentInfo, specs, reexec, upgrader, runtime, configMgr, varsManager, caps, monitor, isManaged, compModifiers...) + otelManager := otelmanager.NewOTelManager(log.Named("otel_manager")) + coord := coordinator.New(log, cfg, logLevel, agentInfo, specs, reexec, upgrader, runtime, configMgr, varsManager, caps, monitor, isManaged, otelManager, compModifiers...) if managed != nil { // the coordinator requires the config manager as well as in managed-mode the config manager requires the // coordinator, so it must be set here once the coordinator is created diff --git a/internal/pkg/agent/application/config_test.go b/internal/pkg/agent/application/config_test.go index c2adbbc76de..ee74bca2f63 100644 --- a/internal/pkg/agent/application/config_test.go +++ b/internal/pkg/agent/application/config_test.go @@ -24,7 +24,7 @@ func testMgmtMode(t *testing.T) { t.Run("succeed when local mode is selected", func(t *testing.T) { c := mustWithConfigMode(true) m := localConfig{} - err := c.Unpack(&m) + err := c.UnpackTo(&m) require.NoError(t, err) assert.Equal(t, false, m.Fleet.Enabled) assert.Equal(t, true, configuration.IsStandalone(m.Fleet)) @@ -34,7 +34,7 @@ func testMgmtMode(t *testing.T) { t.Run("succeed when fleet mode is selected", func(t *testing.T) { c := mustWithConfigMode(false) m := localConfig{} - err := c.Unpack(&m) + err := c.UnpackTo(&m) require.NoError(t, err) assert.Equal(t, true, m.Fleet.Enabled) assert.Equal(t, false, configuration.IsStandalone(m.Fleet)) @@ -49,7 +49,7 @@ func testLocalConfig(t *testing.T) { }) m := configuration.ReloadConfig{} - err := c.Unpack(&m) + err := c.UnpackTo(&m) assert.Error(t, err) c = config.MustNewConfigFrom(map[string]interface{}{ @@ -57,7 +57,7 @@ func testLocalConfig(t *testing.T) { "period": 1, }) - err = c.Unpack(&m) + err = c.UnpackTo(&m) assert.NoError(t, err) assert.Equal(t, 1*time.Second, m.Period) }) diff --git a/internal/pkg/agent/application/coordinator/coordinator.go b/internal/pkg/agent/application/coordinator/coordinator.go index d54fe652018..7198f45bb04 100644 --- a/internal/pkg/agent/application/coordinator/coordinator.go +++ b/internal/pkg/agent/application/coordinator/coordinator.go @@ -13,7 +13,12 @@ import ( "sync/atomic" "time" + "go.opentelemetry.io/collector/component/componentstatus" + + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/status" + "go.elastic.co/apm/v2" + "go.opentelemetry.io/collector/confmap" "gopkg.in/yaml.v2" "github.com/elastic/elastic-agent-client/v7/pkg/client" @@ -122,6 +127,17 @@ type RuntimeManager interface { PerformComponentDiagnostics(ctx context.Context, additionalMetrics []cproto.AdditionalDiagnosticRequest, req ...component.Component) ([]runtime.ComponentDiagnostic, error) } +// OTelManager provides an interface to run and update the runtime. +type OTelManager interface { + Runner + + // Update updates the current configuration for OTel. + Update(cfg *confmap.Conf) + + // Watch returns the chanel to watch for configuration changes. + Watch() <-chan *status.AggregateStatus +} + // ConfigChange provides an interface for receiving a new configuration. // // Ack must be called if the configuration change was accepted and Fail should be called if it fails to be accepted. @@ -193,6 +209,9 @@ type Coordinator struct { configMgr ConfigManager varsMgr VarsManager + otelMgr OTelManager + otelCfg *confmap.Conf + caps capabilities.Capabilities modifiers []ComponentsModifier @@ -264,6 +283,7 @@ type Coordinator struct { configErr error componentGenErr error runtimeUpdateErr error + otelErr error // The raw policy before spec lookup or variable substitution ast *transpiler.AST @@ -313,6 +333,9 @@ type managerChans struct { varsManagerUpdate <-chan []*transpiler.Vars varsManagerError <-chan error + otelManagerUpdate <-chan *status.AggregateStatus + otelManagerError <-chan error + upgradeMarkerUpdate <-chan upgrade.UpdateMarker } @@ -339,7 +362,7 @@ type UpdateComponentChange struct { } // New creates a new coordinator. -func New(logger *logger.Logger, cfg *configuration.Configuration, logLevel logp.Level, agentInfo info.Agent, specs component.RuntimeSpecs, reexecMgr ReExecManager, upgradeMgr UpgradeManager, runtimeMgr RuntimeManager, configMgr ConfigManager, varsMgr VarsManager, caps capabilities.Capabilities, monitorMgr MonitorManager, isManaged bool, modifiers ...ComponentsModifier) *Coordinator { +func New(logger *logger.Logger, cfg *configuration.Configuration, logLevel logp.Level, agentInfo info.Agent, specs component.RuntimeSpecs, reexecMgr ReExecManager, upgradeMgr UpgradeManager, runtimeMgr RuntimeManager, configMgr ConfigManager, varsMgr VarsManager, caps capabilities.Capabilities, monitorMgr MonitorManager, isManaged bool, otelMgr OTelManager, modifiers ...ComponentsModifier) *Coordinator { var fleetState cproto.State var fleetMessage string if !isManaged { @@ -366,6 +389,7 @@ func New(logger *logger.Logger, cfg *configuration.Configuration, logLevel logp. runtimeMgr: runtimeMgr, configMgr: configMgr, varsMgr: varsMgr, + otelMgr: otelMgr, caps: caps, modifiers: modifiers, state: state, @@ -420,6 +444,10 @@ func New(logger *logger.Logger, cfg *configuration.Configuration, logLevel logp. c.managerChans.varsManagerUpdate = varsMgr.Watch() c.managerChans.varsManagerError = varsMgr.Errors() } + if otelMgr != nil { + c.managerChans.otelManagerUpdate = otelMgr.Watch() + c.managerChans.otelManagerError = otelMgr.Errors() + } if upgradeMgr != nil && upgradeMgr.MarkerWatcher() != nil { c.managerChans.upgradeMarkerUpdate = upgradeMgr.MarkerWatcher().Watch() } @@ -894,6 +922,12 @@ func (c *Coordinator) DiagnosticHooks() diagnostics.Hooks { ID string `yaml:"id"` State runtime.ComponentState `yaml:"state"` } + type StateCollectorStatus struct { + Status componentstatus.Status `yaml:"status"` + Err string `yaml:"error,omitempty"` + Timestamp string `yaml:"timestamp"` + Components map[string]*StateCollectorStatus `yaml:"components,omitempty"` + } type StateHookOutput struct { State agentclient.State `yaml:"state"` Message string `yaml:"message"` @@ -901,9 +935,29 @@ func (c *Coordinator) DiagnosticHooks() diagnostics.Hooks { FleetMessage string `yaml:"fleet_message"` LogLevel logp.Level `yaml:"log_level"` Components []StateComponentOutput `yaml:"components"` + Collector *StateCollectorStatus `yaml:"collector,omitempty"` UpgradeDetails *details.Details `yaml:"upgrade_details,omitempty"` } + var toCollectorStatus func(status *status.AggregateStatus) *StateCollectorStatus + toCollectorStatus = func(status *status.AggregateStatus) *StateCollectorStatus { + s := &StateCollectorStatus{ + Status: status.Status(), + Timestamp: status.Timestamp().Format(time.RFC3339Nano), + } + statusErr := status.Err() + if statusErr != nil { + s.Err = statusErr.Error() + } + if len(status.ComponentStatusMap) > 0 { + s.Components = make(map[string]*StateCollectorStatus, len(status.ComponentStatusMap)) + for k, v := range status.ComponentStatusMap { + s.Components[k] = toCollectorStatus(v) + } + } + return s + } + s := c.State() n := len(s.Components) compStates := make([]StateComponentOutput, n) @@ -913,6 +967,10 @@ func (c *Coordinator) DiagnosticHooks() diagnostics.Hooks { State: s.Components[i].State, } } + var collectorStatus *StateCollectorStatus + if s.Collector != nil { + collectorStatus = toCollectorStatus(s.Collector) + } output := StateHookOutput{ State: s.State, Message: s.Message, @@ -920,6 +978,7 @@ func (c *Coordinator) DiagnosticHooks() diagnostics.Hooks { FleetMessage: s.FleetMessage, LogLevel: s.LogLevel, Components: compStates, + Collector: collectorStatus, UpgradeDetails: s.UpgradeDetails, } o, err := yaml.Marshal(output) @@ -929,6 +988,22 @@ func (c *Coordinator) DiagnosticHooks() diagnostics.Hooks { return o }, }, + { + Name: "otel", + Filename: "otel.yaml", + Description: "current otel configuration used by the Elastic Agent", + ContentType: "application/yaml", + Hook: func(_ context.Context) []byte { + if c.otelCfg == nil { + return []byte("no active OTel configuration") + } + o, err := yaml.Marshal(c.otelCfg.ToStringMap()) + if err != nil { + return []byte(fmt.Sprintf("error: failed to convert to yaml: %v", err)) + } + return o + }, + }, } } @@ -981,6 +1056,17 @@ func (c *Coordinator) runner(ctx context.Context) error { varsErrCh <- nil } + otelErrCh := make(chan error, 1) + if c.otelMgr != nil { + go func() { + err := c.otelMgr.Run(ctx) + cancel() + otelErrCh <- err + }() + } else { + otelErrCh <- nil + } + upgradeMarkerWatcherErrCh := make(chan error, 1) if c.upgradeMgr != nil && c.upgradeMgr.MarkerWatcher() != nil { err := c.upgradeMgr.MarkerWatcher().Run(ctx) @@ -1000,7 +1086,7 @@ func (c *Coordinator) runner(ctx context.Context) error { // If we got fatal errors from any of the managers, return them. // Otherwise, just return the context's closing error. - err := collectManagerErrors(managerShutdownTimeout, varsErrCh, runtimeErrCh, configErrCh, upgradeMarkerWatcherErrCh) + err := collectManagerErrors(managerShutdownTimeout, varsErrCh, runtimeErrCh, configErrCh, otelErrCh, upgradeMarkerWatcherErrCh) if err != nil { c.logger.Debugf("Manager errors on Coordinator shutdown: %v", err.Error()) return err @@ -1046,6 +1132,9 @@ func (c *Coordinator) runLoopIteration(ctx context.Context) { case varsErr := <-c.managerChans.varsManagerError: c.setVarsManagerError(varsErr) + case otelErr := <-c.managerChans.otelManagerError: + c.setOTelError(otelErr) + case overrideState := <-c.overrideStateChan: c.setOverrideState(overrideState) @@ -1101,6 +1190,10 @@ func (c *Coordinator) runLoopIteration(ctx context.Context) { c.processVars(ctx, vars) } + case collector := <-c.managerChans.otelManagerUpdate: + c.state.Collector = collector + c.stateNeedsRefresh = true + case ll := <-c.logLevelCh: if ctx.Err() == nil { c.processLogLevel(ctx, ll) @@ -1121,6 +1214,15 @@ func (c *Coordinator) runLoopIteration(ctx context.Context) { // Always called on the main Coordinator goroutine. func (c *Coordinator) processConfig(ctx context.Context, cfg *config.Config) (err error) { + if c.otelMgr != nil { + c.otelCfg = cfg.OTel + c.otelMgr.Update(cfg.OTel) + } + return c.processConfigAgent(ctx, cfg) +} + +// Always called on the main Coordinator goroutine. +func (c *Coordinator) processConfigAgent(ctx context.Context, cfg *config.Config) (err error) { span, ctx := apm.StartSpan(ctx, "config", "app.internal") defer func() { apm.CaptureError(ctx, err).Send() @@ -1547,9 +1649,9 @@ func diffUnitList(old, new []component.Unit) map[string]diffCheck { // It returns any resulting errors as a multierror, or nil if no errors // were reported. // Called on the main Coordinator goroutine. -func collectManagerErrors(timeout time.Duration, varsErrCh, runtimeErrCh, configErrCh, upgradeMarkerWatcherErrCh chan error) error { - var runtimeErr, configErr, varsErr, upgradeMarkerWatcherErr error - var returnedRuntime, returnedConfig, returnedVars, returnedUpgradeMarkerWatcher bool +func collectManagerErrors(timeout time.Duration, varsErrCh, runtimeErrCh, configErrCh, otelErrCh, upgradeMarkerWatcherErrCh chan error) error { + var runtimeErr, configErr, varsErr, otelErr, upgradeMarkerWatcherErr error + var returnedRuntime, returnedConfig, returnedVars, returnedOtel, returnedUpgradeMarkerWatcher bool // in case other components are locked up, let us time out timeoutWait := time.NewTimer(timeout) @@ -1571,7 +1673,7 @@ func collectManagerErrors(timeout time.Duration, varsErrCh, runtimeErrCh, config var errs []error waitLoop: - for !returnedRuntime || !returnedConfig || !returnedVars || !returnedUpgradeMarkerWatcher { + for !returnedRuntime || !returnedConfig || !returnedVars || !returnedOtel || !returnedUpgradeMarkerWatcher { select { case runtimeErr = <-runtimeErrCh: returnedRuntime = true @@ -1579,6 +1681,8 @@ waitLoop: returnedConfig = true case varsErr = <-varsErrCh: returnedVars = true + case otelErr = <-otelErrCh: + returnedOtel = true case upgradeMarkerWatcherErr = <-upgradeMarkerWatcherErrCh: returnedUpgradeMarkerWatcher = true case <-timeoutWait.C: @@ -1592,6 +1696,9 @@ waitLoop: if !returnedVars { timeouts = append(timeouts, "no response from vars manager") } + if !returnedOtel { + timeouts = append(timeouts, "no response from otel manager") + } if !returnedUpgradeMarkerWatcher { timeouts = append(timeouts, "no response from upgrade marker watcher") } @@ -1609,6 +1716,9 @@ waitLoop: if varsErr != nil && !errors.Is(varsErr, context.Canceled) { errs = append(errs, fmt.Errorf("vars manager: %w", varsErr)) } + if otelErr != nil && !errors.Is(otelErr, context.Canceled) { + errs = append(errs, fmt.Errorf("otel manager: %w", otelErr)) + } if upgradeMarkerWatcherErr != nil && !errors.Is(upgradeMarkerWatcherErr, context.Canceled) { errs = append(errs, fmt.Errorf("upgrade marker watcher: %w", upgradeMarkerWatcherErr)) } diff --git a/internal/pkg/agent/application/coordinator/coordinator_state.go b/internal/pkg/agent/application/coordinator/coordinator_state.go index e6e030d7b39..974c1c539f9 100644 --- a/internal/pkg/agent/application/coordinator/coordinator_state.go +++ b/internal/pkg/agent/application/coordinator/coordinator_state.go @@ -7,11 +7,14 @@ package coordinator import ( "fmt" - "github.com/elastic/elastic-agent-client/v7/pkg/client" + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/status" + "go.opentelemetry.io/collector/component/componentstatus" + "github.com/elastic/elastic-agent-client/v7/pkg/client" "github.com/elastic/elastic-agent-libs/logp" "github.com/elastic/elastic-agent/internal/pkg/agent/application/upgrade/details" + "github.com/elastic/elastic-agent/internal/pkg/otel/otelhelpers" "github.com/elastic/elastic-agent/pkg/component/runtime" agentclient "github.com/elastic/elastic-agent/pkg/control/v2/client" ) @@ -33,6 +36,8 @@ type State struct { Components []runtime.ComponentComponentState `yaml:"components"` LogLevel logp.Level `yaml:"log_level"` + Collector *status.AggregateStatus + UpgradeDetails *details.Details `yaml:"upgrade_details,omitempty"` } @@ -70,6 +75,13 @@ func (c *Coordinator) setRuntimeUpdateError(err error) { c.stateNeedsRefresh = true } +// setOTelError reports a failed error for otel manager. +// Called on the main Coordinator goroutine. +func (c *Coordinator) setOTelError(err error) { + c.otelErr = err + c.stateNeedsRefresh = true +} + // setConfigManagerError updates the error state for the config manager. // Called on the main Coordinator goroutine. func (c *Coordinator) setConfigManagerError(err error) { @@ -183,6 +195,10 @@ func (c *Coordinator) generateReportableState() (s State) { s.UpgradeDetails = c.state.UpgradeDetails s.Components = make([]runtime.ComponentComponentState, len(c.state.Components)) copy(s.Components, c.state.Components) + if c.state.Collector != nil { + // copy the contents + s.Collector = copyOTelStatus(c.state.Collector) + } // Ordering of state aggregation: // - Override state, if present @@ -202,6 +218,9 @@ func (c *Coordinator) generateReportableState() (s State) { } else if c.runtimeUpdateErr != nil { s.State = agentclient.Failed s.Message = fmt.Sprintf("Runtime update failed: %s", c.runtimeUpdateErr.Error()) + } else if c.otelErr != nil { + s.State = agentclient.Failed + s.Message = fmt.Sprintf("OTel manager failed: %s", c.otelErr.Error()) } else if c.configMgrErr != nil { s.State = agentclient.Failed s.Message = fmt.Sprintf("Config manager: %s", c.configMgrErr.Error()) @@ -211,10 +230,10 @@ func (c *Coordinator) generateReportableState() (s State) { } else if c.varsMgrErr != nil { s.State = agentclient.Failed s.Message = fmt.Sprintf("Vars manager: %s", c.varsMgrErr.Error()) - } else if hasState(s.Components, client.UnitStateFailed) { + } else if hasState(s.Components, client.UnitStateFailed) || otelhelpers.HasStatus(s.Collector, componentstatus.StatusFatalError) || otelhelpers.HasStatus(s.Collector, componentstatus.StatusPermanentError) { s.State = agentclient.Degraded s.Message = "1 or more components/units in a failed state" - } else if hasState(s.Components, client.UnitStateDegraded) { + } else if hasState(s.Components, client.UnitStateDegraded) || otelhelpers.HasStatus(s.Collector, componentstatus.StatusRecoverableError) { s.State = agentclient.Degraded s.Message = "1 or more components/units in a degraded state" } else { @@ -264,3 +283,14 @@ func hasState(components []runtime.ComponentComponentState, state client.UnitSta } return false } + +func copyOTelStatus(copy *status.AggregateStatus) *status.AggregateStatus { + dest := &status.AggregateStatus{ + Event: copy.Event, + ComponentStatusMap: make(map[string]*status.AggregateStatus, len(copy.ComponentStatusMap)), + } + for k, v := range copy.ComponentStatusMap { + dest.ComponentStatusMap[k] = copyOTelStatus(v) + } + return dest +} diff --git a/internal/pkg/agent/application/coordinator/coordinator_test.go b/internal/pkg/agent/application/coordinator/coordinator_test.go index 23f323fee54..36f788d6f24 100644 --- a/internal/pkg/agent/application/coordinator/coordinator_test.go +++ b/internal/pkg/agent/application/coordinator/coordinator_test.go @@ -15,12 +15,14 @@ import ( "testing" "time" - "google.golang.org/protobuf/types/known/structpb" + otelmanager "github.com/elastic/elastic-agent/internal/pkg/otel/manager" + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/status" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.elastic.co/apm/v2/apmtest" + "go.opentelemetry.io/collector/confmap" + "google.golang.org/protobuf/types/known/structpb" "github.com/elastic/elastic-agent-libs/logp" @@ -94,7 +96,7 @@ func waitForState( return } case <-timeoutChan: - assert.Fail(t, "timed out waiting for expected state") + require.Fail(t, "timed out waiting for expected state") return } } @@ -633,7 +635,7 @@ func TestCoordinator_StateSubscribeIsolatedUnits(t *testing.T) { } func TestCollectManagerErrorsTimeout(t *testing.T) { - handlerChan, _, _, _, _ := setupManagerShutdownChannels(time.Millisecond) + handlerChan, _, _, _, _, _ := setupManagerShutdownChannels(time.Millisecond) // Don't send anything to the shutdown channels, causing a timeout // in collectManagerErrors waitAndTestError(t, func(err error) bool { @@ -643,7 +645,7 @@ func TestCollectManagerErrorsTimeout(t *testing.T) { } func TestCollectManagerErrorsOneResponse(t *testing.T) { - handlerChan, _, _, config, _ := setupManagerShutdownChannels(10 * time.Millisecond) + handlerChan, _, _, config, _, _ := setupManagerShutdownChannels(10 * time.Millisecond) // Send an error for the config manager -- we should also get a // timeout error since we don't send anything on the other two channels. @@ -658,28 +660,32 @@ func TestCollectManagerErrorsOneResponse(t *testing.T) { } func TestCollectManagerErrorsAllResponses(t *testing.T) { - handlerChan, runtime, varWatcher, config, upgradeMarkerWatcher := setupManagerShutdownChannels(5 * time.Second) + handlerChan, runtime, varWatcher, config, otel, upgradeMarkerWatcher := setupManagerShutdownChannels(5 * time.Second) runtimeErrStr := "runtime error" varsErrStr := "vars error" + otelErrStr := "otel error" upgradeMarkerWatcherErrStr := "upgrade marker watcher error" runtime <- errors.New(runtimeErrStr) varWatcher <- errors.New(varsErrStr) config <- nil + otel <- errors.New(otelErrStr) upgradeMarkerWatcher <- errors.New(upgradeMarkerWatcherErrStr) waitAndTestError(t, func(err error) bool { return err != nil && strings.Contains(err.Error(), runtimeErrStr) && strings.Contains(err.Error(), varsErrStr) && + strings.Contains(err.Error(), otelErrStr) && strings.Contains(err.Error(), upgradeMarkerWatcherErrStr) }, handlerChan) } func TestCollectManagerErrorsAllResponsesNoErrors(t *testing.T) { - handlerChan, runtime, varWatcher, config, upgradeMarkerWatcher := setupManagerShutdownChannels(5 * time.Second) + handlerChan, runtime, varWatcher, config, otel, upgradeMarkerWatcher := setupManagerShutdownChannels(5 * time.Second) runtime <- nil varWatcher <- nil config <- context.Canceled + otel <- nil upgradeMarkerWatcher <- nil // All errors are nil or context.Canceled, so collectManagerErrors @@ -710,19 +716,20 @@ func waitAndTestError(t *testing.T, check func(error) bool, handlerErr chan erro } } -func setupManagerShutdownChannels(timeout time.Duration) (chan error, chan error, chan error, chan error, chan error) { +func setupManagerShutdownChannels(timeout time.Duration) (chan error, chan error, chan error, chan error, chan error, chan error) { runtime := make(chan error) varWatcher := make(chan error) config := make(chan error) + otelWatcher := make(chan error) upgradeMarkerWatcher := make(chan error) handlerChan := make(chan error) go func() { - handlerErr := collectManagerErrors(timeout, varWatcher, runtime, config, upgradeMarkerWatcher) + handlerErr := collectManagerErrors(timeout, varWatcher, runtime, config, otelWatcher, upgradeMarkerWatcher) handlerChan <- handlerErr }() - return handlerChan, runtime, varWatcher, config, upgradeMarkerWatcher + return handlerChan, runtime, varWatcher, config, otelWatcher, upgradeMarkerWatcher } func TestCoordinator_ReExec(t *testing.T) { @@ -897,6 +904,7 @@ func createCoordinator(t *testing.T, ctx context.Context, opts ...CoordinatorOpt cfg.Port = 0 rm, err := runtime.NewManager(l, l, ai, apmtest.DiscardTracer, monitoringMgr, cfg) require.NoError(t, err) + otelMgr := otelmanager.NewOTelManager(l) caps, err := capabilities.LoadFile(paths.AgentCapabilitiesPath(), l) require.NoError(t, err) @@ -909,7 +917,7 @@ func createCoordinator(t *testing.T, ctx context.Context, opts ...CoordinatorOpt upgradeManager = &fakeUpgradeManager{} } - coord := New(l, nil, logp.DebugLevel, ai, specs, &fakeReExecManager{}, upgradeManager, rm, cfgMgr, varsMgr, caps, monitoringMgr, o.managed) + coord := New(l, nil, logp.DebugLevel, ai, specs, &fakeReExecManager{}, upgradeManager, rm, cfgMgr, varsMgr, caps, monitoringMgr, o.managed, otelMgr) return coord, cfgMgr, varsMgr } @@ -1100,6 +1108,36 @@ func (f *fakeVarsManager) Vars(ctx context.Context, vars []*transpiler.Vars) { } } +type fakeOTelManager struct { + updateCallback func(*confmap.Conf) error + result error + errChan chan error +} + +func (f *fakeOTelManager) Run(ctx context.Context) error { + <-ctx.Done() + return ctx.Err() +} + +func (f *fakeOTelManager) Errors() <-chan error { + return nil +} + +func (f *fakeOTelManager) Update(cfg *confmap.Conf) { + f.result = nil + if f.updateCallback != nil { + f.result = f.updateCallback(cfg) + } + if f.errChan != nil { + // If a reporting channel is set, send the result to it + f.errChan <- f.result + } +} + +func (f *fakeOTelManager) Watch() <-chan *status.AggregateStatus { + return nil +} + // An implementation of the RuntimeManager interface for use in testing. type fakeRuntimeManager struct { state []runtime.ComponentComponentState diff --git a/internal/pkg/agent/application/coordinator/coordinator_unit_test.go b/internal/pkg/agent/application/coordinator/coordinator_unit_test.go index 9ebc2a26f43..a4f998bd82b 100644 --- a/internal/pkg/agent/application/coordinator/coordinator_unit_test.go +++ b/internal/pkg/agent/application/coordinator/coordinator_unit_test.go @@ -13,11 +13,14 @@ package coordinator import ( "context" "errors" - "fmt" "net" "testing" "time" + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/status" + "go.opentelemetry.io/collector/component/componentstatus" + "go.opentelemetry.io/collector/confmap" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -162,6 +165,94 @@ func TestCoordinatorReportsUnhealthyComponents(t *testing.T) { } } +func TestCoordinatorReportsUnhealthyOTelComponents(t *testing.T) { + // Set a one-second timeout -- nothing here should block, but if it + // does let's report a failure instead of timing out the test runner. + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + // Channels have buffer length 1 so we don't have to run on multiple + // goroutines. + stateChan := make(chan State, 1) + otelChan := make(chan *status.AggregateStatus, 1) + coord := &Coordinator{ + state: State{ + CoordinatorState: agentclient.Healthy, + CoordinatorMessage: "Running", + }, + stateBroadcaster: &broadcaster.Broadcaster[State]{ + InputChan: stateChan, + }, + managerChans: managerChans{ + otelManagerUpdate: otelChan, + }, + componentPIDTicker: time.NewTicker(time.Second * 30), + } + + unhealthyOTel := &status.AggregateStatus{ + Event: componentstatus.NewEvent(componentstatus.StatusRecoverableError), + ComponentStatusMap: map[string]*status.AggregateStatus{ + "test-component-1": { + Event: componentstatus.NewRecoverableErrorEvent(errors.New("test message")), + }, + }, + } + + // Send the otel component state to the Coordinator channel and let it run for an + // iteration to update + otelChan <- unhealthyOTel + coord.runLoopIteration(ctx) + select { + case state := <-stateChan: + assert.Equal(t, agentclient.Degraded, state.State, "Degraded component state should cause degraded Coordinator state") + assert.Equal(t, "1 or more components/units in a degraded state", state.Message, "state message should reflect degraded component") + default: + assert.Fail(t, "Coordinator's state didn't change") + } + + // Try again, escalating the component's state to Failed. + // The state message should change slightly, but the overall Coordinator + // state should still just be Degraded -- components / units can't cause + // a Failed state, only errors in the managers can do that. + unhealthyOTel = &status.AggregateStatus{ + Event: componentstatus.NewEvent(componentstatus.StatusFatalError), + ComponentStatusMap: map[string]*status.AggregateStatus{ + "test-component-1": { + Event: componentstatus.NewFatalErrorEvent(errors.New("test message")), + }, + }, + } + otelChan <- unhealthyOTel + coord.runLoopIteration(ctx) + select { + case state := <-stateChan: + assert.Equal(t, agentclient.Degraded, state.State, "Failed component state should cause degraded Coordinator state") + assert.Equal(t, "1 or more components/units in a failed state", state.Message, "state message should reflect failed component") + default: + assert.Fail(t, "Coordinator's state didn't change") + } + + // Reset component state to UnitStateStarting and verify the + // Coordinator recovers + unhealthyOTel = &status.AggregateStatus{ + Event: componentstatus.NewEvent(componentstatus.StatusStarting), + ComponentStatusMap: map[string]*status.AggregateStatus{ + "test-component-1": { + Event: componentstatus.NewEvent(componentstatus.StatusStarting), + }, + }, + } + otelChan <- unhealthyOTel + coord.runLoopIteration(ctx) + select { + case state := <-stateChan: + assert.Equal(t, agentclient.Healthy, state.State, "Starting component state should cause healthy Coordinator state") + assert.Equal(t, "Running", state.Message, "Healthy coordinator should return to baseline state message") + default: + assert.Fail(t, "Coordinator's state didn't change") + } +} + func TestCoordinatorComponentStatesAreSeparate(t *testing.T) { // Report two healthy components, set one to unhealthy, then verify that // healthy state reports from the second component don't restore the @@ -686,7 +777,7 @@ inputs: assert.True(t, monitoringServer.isRunning) } -func TestCoordinatorPolicyChangeUpdatesRuntimeManager(t *testing.T) { +func TestCoordinatorPolicyChangeUpdatesRuntimeAndOTelManager(t *testing.T) { // Send a test policy to the Coordinator as a Config Manager update, // verify it generates the right component model and sends it to the // runtime manager, then send an empty policy and verify it calls @@ -710,6 +801,15 @@ func TestCoordinatorPolicyChangeUpdatesRuntimeManager(t *testing.T) { return nil }, } + var otelUpdated bool // Set by otel manager callback + var otelConfig *confmap.Conf // Set by otel manager callback + otelManager := &fakeOTelManager{ + updateCallback: func(cfg *confmap.Conf) error { + otelUpdated = true + otelConfig = cfg + return nil + }, + } coord := &Coordinator{ logger: logger, @@ -719,11 +819,12 @@ func TestCoordinatorPolicyChangeUpdatesRuntimeManager(t *testing.T) { configManagerUpdate: configChan, }, runtimeMgr: runtimeManager, + otelMgr: otelManager, vars: emptyVars(t), componentPIDTicker: time.NewTicker(time.Second * 30), } - // Create a policy with one input and one output + // Create a policy with one input and one output (no otel configuration) cfg := config.MustNewConfigFrom(` outputs: default: @@ -746,6 +847,8 @@ inputs: // manually (sorry). assert.True(t, updated, "Runtime manager should be updated after a policy change") require.Equal(t, 1, len(components), "Test policy should generate one component") + assert.True(t, otelUpdated, "OTel manager should be updated after a policy change") + require.Nil(t, otelConfig, "OTel manager should not have any config") component := components[0] assert.Equal(t, "filestream-default", component.ID) @@ -765,16 +868,57 @@ inputs: assert.Equal(t, client.UnitTypeOutput, units[1].Type) assert.Equal(t, "elasticsearch", units[1].Config.Type) + // Send a new config update that includes otel configuration + updated = false + components = nil + otelUpdated = false + otelConfig = nil + cfg = config.MustNewConfigFrom(` +outputs: + default: + type: elasticsearch +inputs: + - id: test-input + type: filestream + use_output: default +receivers: + otlp: +processors: + batch: +exporters: + otlp: +service: + pipelines: + traces: + receivers: + - otlp + exporters: + - otlp +`) + cfgChange = &configChange{cfg: cfg} + configChan <- cfgChange + coord.runLoopIteration(ctx) + + // Validate that the runtime manager and otel manager got the updated configuration + assert.True(t, updated, "Runtime manager should be updated after a policy change") + require.Equal(t, 1, len(components), "Test policy should generate one component") + assert.True(t, otelUpdated, "OTel manager should be updated after a policy change") + require.NotNil(t, otelConfig, "OTel manager should have a config") + // Send a new empty config update and make sure the runtime manager - // receives that as well. + // and otel manager receives that as well. updated = false components = nil + otelUpdated = false + otelConfig = nil cfgChange = &configChange{cfg: config.MustNewConfigFrom(nil)} configChan <- cfgChange coord.runLoopIteration(ctx) assert.True(t, cfgChange.acked, "empty policy should be acknowledged") assert.True(t, updated, "empty policy should cause runtime manager update") assert.Empty(t, components, "empty policy should produce empty component model") + assert.True(t, otelUpdated, "empty policy should cause otel manager update") + assert.Nil(t, otelConfig, "empty policy should cause otel manager to get nil config") } func TestCoordinatorReportsRuntimeManagerUpdateFailure(t *testing.T) { @@ -791,7 +935,7 @@ func TestCoordinatorReportsRuntimeManagerUpdateFailure(t *testing.T) { // Create a mocked runtime manager that always reports an error runtimeManager := &fakeRuntimeManager{ updateCallback: func(comp []component.Component) error { - return fmt.Errorf(errorStr) + return errors.New(errorStr) }, errChan: updateErrChan, } @@ -836,6 +980,67 @@ func TestCoordinatorReportsRuntimeManagerUpdateFailure(t *testing.T) { assert.Contains(t, state.Message, errorStr, "Failed policy update should be reported in Coordinator state message") } +func TestCoordinatorReportsOTelManagerUpdateFailure(t *testing.T) { + // Set a one-second timeout -- nothing here should block, but if it + // does let's report a failure instead of timing out the test runner. + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + logger := logp.NewLogger("testing") + + configChan := make(chan ConfigChange, 1) + updateErrChan := make(chan error, 1) + + // Create a mocked otel manager that always reports an error + const errorStr = "update failed for testing reasons" + runtimeManager := &fakeRuntimeManager{} + otelManager := &fakeOTelManager{ + updateCallback: func(retrieved *confmap.Conf) error { + return errors.New(errorStr) + }, + errChan: updateErrChan, + } + + coord := &Coordinator{ + logger: logger, + agentInfo: &info.AgentInfo{}, + stateBroadcaster: broadcaster.New(State{}, 0, 0), + managerChans: managerChans{ + configManagerUpdate: configChan, + // Give coordinator the same error channel we set on the otel + // manager, so it receives the update result. + otelManagerError: updateErrChan, + }, + runtimeMgr: runtimeManager, + otelMgr: otelManager, + vars: emptyVars(t), + componentPIDTicker: time.NewTicker(time.Second * 30), + } + + // Send an empty policy which should forward an empty component model to + // the otel manager (which we have set up to report an error). + cfg := config.MustNewConfigFrom(nil) + configChange := &configChange{cfg: cfg} + configChan <- configChange + coord.runLoopIteration(ctx) + + // Make sure the config change was acknowledged to the config manager + // (the failure is not reported here since it happens asynchronously; it + // will appear in the coordinator state afterwards.) + assert.True(t, configChange.acked, "Config change should be acknowledged to the config manager") + assert.NoError(t, configChange.err, "Config change with async error should succeed") + + // Now do another run loop iteration to let the update error propagate, + // and make sure it is reported correctly. + coord.runLoopIteration(ctx) + require.Error(t, coord.otelErr, "OTel update failure should be saved in otelErr") + assert.Equal(t, errorStr, coord.otelErr.Error(), "otelErr should match the error reported by the otel manager") + + // Make sure the error appears in the Coordinator state. + state := coord.State() + assert.Equal(t, agentclient.Failed, state.State, "Failed policy update should cause failed Coordinator") + assert.Contains(t, state.Message, errorStr, "Failed policy update should be reported in Coordinator state message") +} + func TestCoordinatorAppliesVarsToPolicy(t *testing.T) { // Make sure: // - An input unit that depends on an undefined variable is not created diff --git a/internal/pkg/agent/application/coordinator/diagnostics_test.go b/internal/pkg/agent/application/coordinator/diagnostics_test.go index 9c72400e8e8..346999e8f86 100644 --- a/internal/pkg/agent/application/coordinator/diagnostics_test.go +++ b/internal/pkg/agent/application/coordinator/diagnostics_test.go @@ -41,6 +41,7 @@ func TestCoordinatorExpectedDiagnosticHooks(t *testing.T) { "components-expected", "components-actual", "state", + "otel", } coord := &Coordinator{} diff --git a/internal/pkg/agent/application/gateway/fleet/fleet_gateway.go b/internal/pkg/agent/application/gateway/fleet/fleet_gateway.go index c36b37cd017..a369282fd68 100644 --- a/internal/pkg/agent/application/gateway/fleet/fleet_gateway.go +++ b/internal/pkg/agent/application/gateway/fleet/fleet_gateway.go @@ -8,6 +8,8 @@ import ( "context" "time" + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/status" + agentclient "github.com/elastic/elastic-agent/pkg/control/v2/client" eaclient "github.com/elastic/elastic-agent-client/v7/pkg/client" @@ -18,6 +20,7 @@ import ( "github.com/elastic/elastic-agent/internal/pkg/fleetapi" "github.com/elastic/elastic-agent/internal/pkg/fleetapi/acker" "github.com/elastic/elastic-agent/internal/pkg/fleetapi/client" + "github.com/elastic/elastic-agent/internal/pkg/otel/otelhelpers" "github.com/elastic/elastic-agent/internal/pkg/scheduler" "github.com/elastic/elastic-agent/pkg/component/runtime" "github.com/elastic/elastic-agent/pkg/core/logger" @@ -232,7 +235,7 @@ func (f *FleetGateway) doExecute(ctx context.Context, bo backoff.Backoff) (*flee return nil, ctx.Err() } -func (f *FleetGateway) convertToCheckinComponents(components []runtime.ComponentComponentState) []fleetapi.CheckinComponent { +func (f *FleetGateway) convertToCheckinComponents(components []runtime.ComponentComponentState, collector *status.AggregateStatus) []fleetapi.CheckinComponent { if components == nil { return nil } @@ -250,7 +253,11 @@ func (f *FleetGateway) convertToCheckinComponents(components []runtime.Component return "" } - checkinComponents := make([]fleetapi.CheckinComponent, 0, len(components)) + size := len(components) + if collector != nil { + size += len(collector.ComponentStatusMap) + } + checkinComponents := make([]fleetapi.CheckinComponent, 0, size) for _, item := range components { component := item.Component @@ -280,6 +287,36 @@ func (f *FleetGateway) convertToCheckinComponents(components []runtime.Component checkinComponents = append(checkinComponents, checkinComponent) } + // OTel status is placed as a component for each top-level component in OTel + // and each subcomponent is a unit. + if collector != nil { + for id, item := range collector.ComponentStatusMap { + state, msg := otelhelpers.StateWithMessage(item) + + checkinComponent := fleetapi.CheckinComponent{ + ID: id, + Type: "otel", + Status: stateString(state), + Message: msg, + } + + if len(item.ComponentStatusMap) > 0 { + units := make([]fleetapi.CheckinUnit, 0, len(item.ComponentStatusMap)) + for unitId, unitItem := range item.ComponentStatusMap { + unitState, unitMsg := otelhelpers.StateWithMessage(unitItem) + units = append(units, fleetapi.CheckinUnit{ + ID: unitId, + Status: stateString(unitState), + Message: unitMsg, + }) + } + checkinComponent.Units = units + } + + checkinComponents = append(checkinComponents, checkinComponent) + } + } + return checkinComponents } @@ -299,7 +336,7 @@ func (f *FleetGateway) execute(ctx context.Context) (*fleetapi.CheckinResponse, state := f.stateFetcher() // convert components into checkin components structure - components := f.convertToCheckinComponents(state.Components) + components := f.convertToCheckinComponents(state.Components, state.Collector) f.log.Debugf("correcting agent loglevel from %s to %s using coordinator state", ecsMeta.Elastic.Agent.LogLevel, state.LogLevel.String()) // Fix loglevel with the current log level used by coordinator diff --git a/internal/pkg/agent/application/info/agent_id.go b/internal/pkg/agent/application/info/agent_id.go index 348c428a26e..31435d02cd5 100644 --- a/internal/pkg/agent/application/info/agent_id.go +++ b/internal/pkg/agent/application/info/agent_id.go @@ -127,7 +127,7 @@ func getInfoFromStore(s ioStore, logLevel string) (*persistentAgentInfo, bool, e LogLevel: logLevel, MonitoringHTTP: monitoringConfig.DefaultConfig().HTTP, } - if err := cc.Unpack(&pid); err != nil { + if err := cc.UnpackTo(&pid); err != nil { return nil, false, errors.New(err, "failed to unpack stored config to map") } @@ -152,7 +152,7 @@ func updateAgentInfo(s ioStore, agentInfo *persistentAgentInfo) error { } configMap := make(map[string]interface{}) - if err := cfg.Unpack(&configMap); err != nil { + if err := cfg.UnpackTo(&configMap); err != nil { return errors.New(err, "failed to unpack stored config to map") } @@ -160,7 +160,7 @@ func updateAgentInfo(s ioStore, agentInfo *persistentAgentInfo) error { if agentInfoSubMap, found := configMap[agentInfoKey]; found { if cc, err := config.NewConfigFrom(agentInfoSubMap); err == nil { pid := &persistentAgentInfo{} - err := cc.Unpack(&pid) + err := cc.UnpackTo(&pid) if err == nil && pid.ID != agentInfo.ID { // if our id is different (we just generated it) // keep the one present in the file diff --git a/internal/pkg/agent/application/monitoring/liveness.go b/internal/pkg/agent/application/monitoring/liveness.go index f0d75b019d9..a0ffdb43feb 100644 --- a/internal/pkg/agent/application/monitoring/liveness.go +++ b/internal/pkg/agent/application/monitoring/liveness.go @@ -9,7 +9,11 @@ import ( "net/http" "time" + "go.opentelemetry.io/collector/component/componentstatus" + "github.com/elastic/elastic-agent-client/v7/pkg/client" + + "github.com/elastic/elastic-agent/internal/pkg/otel/otelhelpers" ) const formValueKey = "failon" @@ -79,6 +83,11 @@ func livenessHandler(coord CoordinatorState) func(http.ResponseWriter, *http.Req unhealthyComponent = true } } + if state.Collector != nil { + if (failConfig.Failed && (otelhelpers.HasStatus(state.Collector, componentstatus.StatusFatalError) || otelhelpers.HasStatus(state.Collector, componentstatus.StatusPermanentError))) || (failConfig.Degraded && otelhelpers.HasStatus(state.Collector, componentstatus.StatusRecoverableError)) { + unhealthyComponent = true + } + } // bias towards the coordinator check, since it can be otherwise harder to diagnose if unhealthyComponent { w.WriteHeader(http.StatusInternalServerError) diff --git a/internal/pkg/agent/application/monitoring/liveness_test.go b/internal/pkg/agent/application/monitoring/liveness_test.go index 514df72fed6..abe3bb94cc9 100644 --- a/internal/pkg/agent/application/monitoring/liveness_test.go +++ b/internal/pkg/agent/application/monitoring/liveness_test.go @@ -12,6 +12,9 @@ import ( "testing" "time" + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/status" + "go.opentelemetry.io/collector/component/componentstatus" + "github.com/stretchr/testify/require" "github.com/elastic/elastic-agent-client/v7/pkg/client" @@ -352,6 +355,50 @@ func TestProcessHTTPHandler(t *testing.T) { liveness: true, failon: "degraded", }, + { + name: "healthy-liveness-off-otel", + coord: mockCoordinator{ + isUp: true, + state: coordinator.State{ + Collector: &status.AggregateStatus{ + Event: componentstatus.NewEvent(componentstatus.StatusOK), + ComponentStatusMap: map[string]*status.AggregateStatus{ + "test-component": &status.AggregateStatus{ + Event: componentstatus.NewEvent(componentstatus.StatusOK), + }, + "test-component2": &status.AggregateStatus{ + Event: componentstatus.NewEvent(componentstatus.StatusOK), + }, + }, + }, + }, + }, + expectedCode: 200, + liveness: false, + failon: "degraded", + }, + { + name: "degraded-and-healthy-otel", + coord: mockCoordinator{ + isUp: true, + state: coordinator.State{ + Collector: &status.AggregateStatus{ + Event: componentstatus.NewEvent(componentstatus.StatusRecoverableError), + ComponentStatusMap: map[string]*status.AggregateStatus{ + "test-component": &status.AggregateStatus{ + Event: componentstatus.NewEvent(componentstatus.StatusRecoverableError), + }, + "test-component2": &status.AggregateStatus{ + Event: componentstatus.NewEvent(componentstatus.StatusOK), + }, + }, + }, + }, + }, + expectedCode: 500, + liveness: true, + failon: "degraded", + }, } // test with processesHandler diff --git a/internal/pkg/agent/application/monitoring/reload/reload.go b/internal/pkg/agent/application/monitoring/reload/reload.go index 9247f7aea45..d012e1f3cd8 100644 --- a/internal/pkg/agent/application/monitoring/reload/reload.go +++ b/internal/pkg/agent/application/monitoring/reload/reload.go @@ -94,7 +94,7 @@ func (sr *ServerReloader) Addr() net.Addr { func (sr *ServerReloader) Reload(rawConfig *aConfig.Config) error { newConfig := configuration.DefaultConfiguration() - if err := rawConfig.Unpack(&newConfig); err != nil { + if err := rawConfig.UnpackTo(&newConfig); err != nil { return errors.New(err, "failed to unpack monitoring config during reload") } diff --git a/internal/pkg/agent/application/monitoring/v1_monitor.go b/internal/pkg/agent/application/monitoring/v1_monitor.go index 7512b989a98..0d219f849e8 100644 --- a/internal/pkg/agent/application/monitoring/v1_monitor.go +++ b/internal/pkg/agent/application/monitoring/v1_monitor.go @@ -105,7 +105,7 @@ func (b *BeatsMonitor) Reload(rawConfig *config.Config) error { return nil } - if err := rawConfig.Unpack(&b.config); err != nil { + if err := rawConfig.UnpackTo(&b.config); err != nil { return errors.New(err, "failed to unpack monitoring config during reload") } return nil diff --git a/internal/pkg/agent/application/upgrade/artifact/config.go b/internal/pkg/agent/application/upgrade/artifact/config.go index 07be58fb123..d43ea6bb60e 100644 --- a/internal/pkg/agent/application/upgrade/artifact/config.go +++ b/internal/pkg/agent/application/upgrade/artifact/config.go @@ -133,7 +133,7 @@ func (r *Reloader) reloadConfig(rawConfig *config.Config) error { tmp := &reloadConfig{ C: DefaultConfig(), } - if err := rawConfig.Unpack(&tmp); err != nil { + if err := rawConfig.UnpackTo(&tmp); err != nil { return err } @@ -160,7 +160,7 @@ func (r *Reloader) reloadSourceURI(rawConfig *config.Config) error { FleetSourceURI string `json:"agent.download.source_uri" config:"agent.download.source_uri"` } cfg := &reloadConfig{} - if err := rawConfig.Unpack(&cfg); err != nil { + if err := rawConfig.UnpackTo(&cfg); err != nil { return errors.New(err, "failed to unpack config during reload") } @@ -274,7 +274,7 @@ func (c *Config) Unpack(cfg *c.C) error { return err } // HTTPTransportSettings.Proxy.Headers defaults to empty. If absent in cfg, - // Unpack will set it to nil. To ensure consistency, we reset it to empty. + // UnpackTo will set it to nil. To ensure consistency, we reset it to empty. if transport.Proxy.Headers == nil { transport.Proxy.Headers = map[string]string{} } diff --git a/internal/pkg/agent/application/upgrade/artifact/config_test.go b/internal/pkg/agent/application/upgrade/artifact/config_test.go index 70ff03559c0..32bf307b870 100644 --- a/internal/pkg/agent/application/upgrade/artifact/config_test.go +++ b/internal/pkg/agent/application/upgrade/artifact/config_test.go @@ -307,6 +307,6 @@ func TestConfig_Unpack(t *testing.T) { require.NoError(t, err, "could not create config from empty string") err = defaultcfg.Unpack(emptycgf) - require.NoError(t, err, "Unpack failed") + require.NoError(t, err, "UnpackTo failed") assert.Equal(t, DefaultConfig(), defaultcfg) } diff --git a/internal/pkg/agent/application/upgrade/upgrade.go b/internal/pkg/agent/application/upgrade/upgrade.go index b2eda395ed7..43702d44f83 100644 --- a/internal/pkg/agent/application/upgrade/upgrade.go +++ b/internal/pkg/agent/application/upgrade/upgrade.go @@ -110,7 +110,7 @@ func (u *Upgrader) Reload(rawConfig *config.Config) error { FleetSourceURI string `json:"agent.download.source_uri" config:"agent.download.source_uri"` } fleetSourceURI := &fleetCfg{} - if err := rawConfig.Unpack(&fleetSourceURI); err != nil { + if err := rawConfig.UnpackTo(&fleetSourceURI); err != nil { return errors.New(err, "failed to unpack config during reload") } diff --git a/internal/pkg/agent/cmd/container.go b/internal/pkg/agent/cmd/container.go index b0a3ad63e5a..b807c8ec162 100644 --- a/internal/pkg/agent/cmd/container.go +++ b/internal/pkg/agent/cmd/container.go @@ -206,7 +206,7 @@ func containerCmd(streams *cli.IOStreams) error { return fmt.Errorf("parsing config file(%s): %w", f, err) } if c != nil { - err = c.Unpack(&cfg) + err = c.UnpackTo(&cfg) if err != nil { return fmt.Errorf("unpacking config file(%s): %w", f, err) } @@ -899,7 +899,7 @@ func tryContainerLoadPaths() error { return fmt.Errorf("failed to load %s: %w", pathFile, err) } var paths containerPaths - err = cfg.Unpack(&paths) + err = cfg.UnpackTo(&paths) if err != nil { return fmt.Errorf("failed to unpack %s: %w", pathFile, err) } diff --git a/internal/pkg/agent/cmd/container_test.go b/internal/pkg/agent/cmd/container_test.go index 6afc9ba88ce..3ada160fee8 100644 --- a/internal/pkg/agent/cmd/container_test.go +++ b/internal/pkg/agent/cmd/container_test.go @@ -85,7 +85,7 @@ func TestContainerTestPaths(t *testing.T) { require.NoError(t, err) var paths containerPaths - err = cfg.Unpack(&paths) + err = cfg.UnpackTo(&paths) require.NoError(t, err) require.Equal(t, c.expected, paths) diff --git a/internal/pkg/agent/cmd/enroll_cmd.go b/internal/pkg/agent/cmd/enroll_cmd.go index 2bb07dde414..91f652c9ef5 100644 --- a/internal/pkg/agent/cmd/enroll_cmd.go +++ b/internal/pkg/agent/cmd/enroll_cmd.go @@ -1076,7 +1076,7 @@ func getPersistentConfig(pathConfigFile string) (map[string]interface{}, error) MonitoringHTTP: monitoringConfig.DefaultConfig().HTTP, } - if err := rawConfig.Unpack(&pc); err != nil { + if err := rawConfig.UnpackTo(&pc); err != nil { return nil, err } diff --git a/internal/pkg/agent/cmd/enroll_cmd_test.go b/internal/pkg/agent/cmd/enroll_cmd_test.go index c175240abfa..6131c1de959 100644 --- a/internal/pkg/agent/cmd/enroll_cmd_test.go +++ b/internal/pkg/agent/cmd/enroll_cmd_test.go @@ -870,7 +870,7 @@ func readConfig(raw []byte) (*configuration.FleetAgentConfig, error) { } cfg := configuration.DefaultConfiguration() - if err := config.Unpack(cfg); err != nil { + if err := config.UnpackTo(cfg); err != nil { return nil, err } return cfg.Fleet, nil diff --git a/internal/pkg/agent/cmd/inspect.go b/internal/pkg/agent/cmd/inspect.go index 600b4ba4985..c5826d64a43 100644 --- a/internal/pkg/agent/cmd/inspect.go +++ b/internal/pkg/agent/cmd/inspect.go @@ -389,7 +389,7 @@ func getMonitoringFn(ctx context.Context, cfg map[string]interface{}) (component } agentCfg := configuration.DefaultConfiguration() - if err := config.Unpack(agentCfg); err != nil { + if err := config.UnpackTo(agentCfg); err != nil { return nil, err } diff --git a/internal/pkg/agent/cmd/otel.go b/internal/pkg/agent/cmd/otel.go index 8007f9512b8..3ce0d3db269 100644 --- a/internal/pkg/agent/cmd/otel.go +++ b/internal/pkg/agent/cmd/otel.go @@ -2,8 +2,6 @@ // or more contributor license agreements. Licensed under the Elastic License 2.0; // you may not use this file except in compliance with the Elastic License 2.0. -//go:build !windows - package cmd import ( diff --git a/internal/pkg/agent/cmd/otel_flags.go b/internal/pkg/agent/cmd/otel_flags.go index 8cf66dd5e14..57cae05884e 100644 --- a/internal/pkg/agent/cmd/otel_flags.go +++ b/internal/pkg/agent/cmd/otel_flags.go @@ -2,8 +2,6 @@ // or more contributor license agreements. Licensed under the Elastic License 2.0; // you may not use this file except in compliance with the Elastic License 2.0. -//go:build !windows - package cmd import ( diff --git a/internal/pkg/agent/cmd/otel_flags_test.go b/internal/pkg/agent/cmd/otel_flags_test.go index e1fc06bb63d..24fe30e2afd 100644 --- a/internal/pkg/agent/cmd/otel_flags_test.go +++ b/internal/pkg/agent/cmd/otel_flags_test.go @@ -2,8 +2,6 @@ // or more contributor license agreements. Licensed under the Elastic License 2.0; // you may not use this file except in compliance with the Elastic License 2.0. -//go:build !windows - package cmd import ( diff --git a/internal/pkg/agent/cmd/otel_windows.go b/internal/pkg/agent/cmd/otel_windows.go deleted file mode 100644 index d914ab1c963..00000000000 --- a/internal/pkg/agent/cmd/otel_windows.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -// or more contributor license agreements. Licensed under the Elastic License 2.0; -// you may not use this file except in compliance with the Elastic License 2.0. - -//go:build windows - -package cmd - -import ( - "github.com/spf13/cobra" - - "github.com/elastic/elastic-agent/internal/pkg/cli" -) - -func newOtelCommandWithArgs(args []string, streams *cli.IOStreams) *cobra.Command { - return nil -} diff --git a/internal/pkg/agent/cmd/otel_windows_test.go b/internal/pkg/agent/cmd/otel_windows_test.go deleted file mode 100644 index 0c12c121576..00000000000 --- a/internal/pkg/agent/cmd/otel_windows_test.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -// or more contributor license agreements. Licensed under the Elastic License 2.0; -// you may not use this file except in compliance with the Elastic License 2.0. - -//go:build windows - -package cmd - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestOtelCommandIsNil(t *testing.T) { - require.Nil(t, newOtelCommandWithArgs(nil, nil)) -} diff --git a/internal/pkg/agent/cmd/status.go b/internal/pkg/agent/cmd/status.go index 2288c11b463..d9c82359462 100644 --- a/internal/pkg/agent/cmd/status.go +++ b/internal/pkg/agent/cmd/status.go @@ -96,6 +96,13 @@ func formatStatus(state client.State, message string) string { return fmt.Sprintf("status: (%s) %s", state, message) } +func formatComponentStatus(component *client.CollectorComponent) string { + if component.Error != "" { + return fmt.Sprintf("status: %s [%s]", component.Status, component.Error) + } + return fmt.Sprintf("status: %s", component.Status) +} + func listComponentState(l list.Writer, components []client.ComponentState, all bool) { for _, c := range components { // see if any unit is not Healthy because component @@ -134,6 +141,27 @@ func listComponentState(l list.Writer, components []client.ComponentState, all b } } +func listCollectorState(l list.Writer, id string, component *client.CollectorComponent) { + if component == nil { + return + } + l.AppendItem(id) + l.Indent() + l.AppendItem(formatComponentStatus(component)) + if len(component.ComponentStatusMap) > 0 { + // list in order + keys := make([]string, 0, len(component.ComponentStatusMap)) + for k := range component.ComponentStatusMap { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + listCollectorState(l, k, component.ComponentStatusMap[k]) + } + } + l.UnIndent() +} + func listAgentState(l list.Writer, state *client.AgentState, all bool) { l.AppendItem("elastic-agent") l.Indent() @@ -148,6 +176,21 @@ func listAgentState(l list.Writer, state *client.AgentState, all bool) { } l.UnIndent() listComponentState(l, state.Components, all) + if state.Collector != nil { + l.Indent() + if len(state.Collector.ComponentStatusMap) > 0 { + // list in order + keys := make([]string, 0, len(state.Collector.ComponentStatusMap)) + for k := range state.Collector.ComponentStatusMap { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + listCollectorState(l, k, state.Collector.ComponentStatusMap[k]) + } + } + l.UnIndent() + } // Upgrade details listUpgradeDetails(l, state.UpgradeDetails) diff --git a/internal/pkg/agent/cmd/validate.go b/internal/pkg/agent/cmd/validate.go index e5f049be855..098c50dba17 100644 --- a/internal/pkg/agent/cmd/validate.go +++ b/internal/pkg/agent/cmd/validate.go @@ -2,8 +2,6 @@ // or more contributor license agreements. Licensed under the Elastic License 2.0; // you may not use this file except in compliance with the Elastic License 2.0. -//go:build !windows - package cmd import ( diff --git a/internal/pkg/agent/configuration/configuration.go b/internal/pkg/agent/configuration/configuration.go index c29908bf1d9..e1cc9ab951f 100644 --- a/internal/pkg/agent/configuration/configuration.go +++ b/internal/pkg/agent/configuration/configuration.go @@ -27,7 +27,7 @@ func DefaultConfiguration() *Configuration { // NewFromConfig creates a configuration based on common Config. func NewFromConfig(cfg *config.Config) (*Configuration, error) { c := DefaultConfiguration() - if err := cfg.Unpack(c); err != nil { + if err := cfg.UnpackTo(c); err != nil { return nil, errors.New(err, errors.TypeConfig) } @@ -38,7 +38,7 @@ func NewFromConfig(cfg *config.Config) (*Configuration, error) { func NewPartialFromConfigNoDefaults(cfg *config.Config) (*Configuration, error) { c := new(Configuration) // Validator tag set to "validate_disable" is a hack to avoid validation errors on a partial config - if err := cfg.Unpack(c, ucfg.ValidatorTag("validate_disable")); err != nil { + if err := cfg.UnpackTo(c, ucfg.ValidatorTag("validate_disable")); err != nil { return nil, errors.New(err, errors.TypeConfig) } diff --git a/internal/pkg/composable/controller.go b/internal/pkg/composable/controller.go index 73a3c49a967..7cab450ce00 100644 --- a/internal/pkg/composable/controller.go +++ b/internal/pkg/composable/controller.go @@ -55,7 +55,7 @@ func New(log *logger.Logger, c *config.Config, managed bool) (Controller, error) var providersCfg Config if c != nil { - err := c.Unpack(&providersCfg) + err := c.UnpackTo(&providersCfg) if err != nil { return nil, errors.New(err, "failed to unpack providers config", errors.TypeConfig) } diff --git a/internal/pkg/composable/providers/docker/docker.go b/internal/pkg/composable/providers/docker/docker.go index 72082a476b1..49ec74d4fdb 100644 --- a/internal/pkg/composable/providers/docker/docker.go +++ b/internal/pkg/composable/providers/docker/docker.go @@ -107,7 +107,7 @@ func DynamicProviderBuilder(logger *logger.Logger, c *config.Config, managed boo if c == nil { c = config.New() } - err := c.Unpack(&cfg) + err := c.UnpackTo(&cfg) if err != nil { return nil, errors.New(err, "failed to unpack configuration") } diff --git a/internal/pkg/composable/providers/host/host.go b/internal/pkg/composable/providers/host/host.go index efa555f7bf5..6ba4e5356d7 100644 --- a/internal/pkg/composable/providers/host/host.go +++ b/internal/pkg/composable/providers/host/host.go @@ -109,7 +109,7 @@ func ContextProviderBuilder(log *logger.Logger, c *config.Config, _ bool) (corec fetcher: getHostInfo(log), } if c != nil { - err := c.Unpack(p) + err := c.UnpackTo(p) if err != nil { return nil, fmt.Errorf("failed to unpack config: %w", err) } diff --git a/internal/pkg/composable/providers/kubernetes/kubernetes.go b/internal/pkg/composable/providers/kubernetes/kubernetes.go index 2beb701a5df..05583996920 100644 --- a/internal/pkg/composable/providers/kubernetes/kubernetes.go +++ b/internal/pkg/composable/providers/kubernetes/kubernetes.go @@ -46,7 +46,7 @@ func DynamicProviderBuilder(logger *logger.Logger, c *config.Config, managed boo if c == nil { c = config.New() } - err := c.Unpack(&cfg) + err := c.UnpackTo(&cfg) if err != nil { return nil, errors.New(err, "failed to unpack configuration") } diff --git a/internal/pkg/composable/providers/kubernetes/pod_test.go b/internal/pkg/composable/providers/kubernetes/pod_test.go index 936e2fb0479..71f08108786 100644 --- a/internal/pkg/composable/providers/kubernetes/pod_test.go +++ b/internal/pkg/composable/providers/kubernetes/pod_test.go @@ -177,7 +177,7 @@ func TestGenerateContainerPodData(t *testing.T) { logger := getLogger() var cfg Config c := config.New() - _ = c.Unpack(&cfg) + _ = c.UnpackTo(&cfg) generateContainerData( &comm, pod, @@ -307,7 +307,7 @@ func TestEphemeralContainers(t *testing.T) { logger := getLogger() var cfg Config c := config.New() - _ = c.Unpack(&cfg) + _ = c.UnpackTo(&cfg) generateContainerData( &comm, pod, diff --git a/internal/pkg/composable/providers/kubernetesleaderelection/kubernetes_leaderelection.go b/internal/pkg/composable/providers/kubernetesleaderelection/kubernetes_leaderelection.go index b88bdd39db2..6f25a57dc3d 100644 --- a/internal/pkg/composable/providers/kubernetesleaderelection/kubernetes_leaderelection.go +++ b/internal/pkg/composable/providers/kubernetesleaderelection/kubernetes_leaderelection.go @@ -42,7 +42,7 @@ func ContextProviderBuilder(logger *logger.Logger, c *config.Config, managed boo if c == nil { c = config.New() } - err := c.Unpack(&cfg) + err := c.UnpackTo(&cfg) if err != nil { return nil, errors.New(err, "failed to unpack configuration") } diff --git a/internal/pkg/composable/providers/kubernetessecrets/kubernetes_secrets.go b/internal/pkg/composable/providers/kubernetessecrets/kubernetes_secrets.go index 146a0399f6b..6921003cb30 100644 --- a/internal/pkg/composable/providers/kubernetessecrets/kubernetes_secrets.go +++ b/internal/pkg/composable/providers/kubernetessecrets/kubernetes_secrets.go @@ -50,7 +50,7 @@ func ContextProviderBuilder(logger *logger.Logger, c *config.Config, _ bool) (co if c == nil { c = config.New() } - err := c.Unpack(&cfg) + err := c.UnpackTo(&cfg) if err != nil { return nil, errors.New(err, "failed to unpack configuration") } diff --git a/internal/pkg/composable/providers/local/local.go b/internal/pkg/composable/providers/local/local.go index 7a737dec220..470d6c0a312 100644 --- a/internal/pkg/composable/providers/local/local.go +++ b/internal/pkg/composable/providers/local/local.go @@ -36,7 +36,7 @@ func (c *contextProvider) Run(ctx context.Context, comm corecomp.ContextProvider func ContextProviderBuilder(_ *logger.Logger, c *config.Config, _ bool) (corecomp.ContextProvider, error) { p := &contextProvider{} if c != nil { - err := c.Unpack(p) + err := c.UnpackTo(p) if err != nil { return nil, fmt.Errorf("failed to unpack vars: %w", err) } diff --git a/internal/pkg/composable/providers/localdynamic/localdynamic.go b/internal/pkg/composable/providers/localdynamic/localdynamic.go index 2296d93c0fc..6bef79633b7 100644 --- a/internal/pkg/composable/providers/localdynamic/localdynamic.go +++ b/internal/pkg/composable/providers/localdynamic/localdynamic.go @@ -44,7 +44,7 @@ func (c *dynamicProvider) Run(comm composable.DynamicProviderComm) error { func DynamicProviderBuilder(_ *logger.Logger, c *config.Config, _ bool) (composable.DynamicProvider, error) { p := &dynamicProvider{} if c != nil { - err := c.Unpack(p) + err := c.UnpackTo(p) if err != nil { return nil, fmt.Errorf("failed to unpack vars: %w", err) } diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go index 5a10ce6541b..b57a36eca88 100644 --- a/internal/pkg/config/config.go +++ b/internal/pkg/config/config.go @@ -7,8 +7,10 @@ package config import ( "fmt" "io" + "maps" "os" + "go.opentelemetry.io/collector/confmap" "gopkg.in/yaml.v2" "github.com/elastic/go-ucfg" @@ -17,12 +19,22 @@ import ( // options hold the specified options type options struct { + otelKeys []string skipKeys []string } // Option is an option type that modifies how loading configs work type Option func(*options) +// OTelKeys maps top-level keys to OTel specific configuration. +// +// The provided keys only skip if the keys are top-level keys. +func OTelKeys(keys ...string) Option { + return func(opts *options) { + opts.otelKeys = keys + } +} + // VarSkipKeys prevents variable expansion for these keys. // // The provided keys only skip if the keys are top-level keys. @@ -39,14 +51,20 @@ var DefaultOptions = []interface{}{ ucfg.VarExp, VarSkipKeys("inputs"), ucfg.IgnoreCommas, + OTelKeys("connectors", "receivers", "processors", "exporters", "extensions", "service"), } -// Config custom type over a ucfg.Config to add new methods on the object. -type Config ucfg.Config +// Config custom type that can provide both an Agent configuration alongside of an optional OTel configuration. +type Config struct { + // Agent configuration + Agent *ucfg.Config + // OTel configuration (YAML bytes raw) + OTel *confmap.Conf +} // New creates a new empty config. func New() *Config { - return newConfigFrom(ucfg.New()) + return newConfigFrom(ucfg.New(), nil) } // NewConfigFrom takes a interface and read the configuration like it was YAML. @@ -83,10 +101,20 @@ func NewConfigFrom(from interface{}, opts ...interface{}) (*Config, error) { return nil, err } } else if contents, ok := from.(map[string]interface{}); ok { - data = contents + // don't modify the incoming contents + data = maps.Clone(contents) } else { c, err := ucfg.NewFrom(from, ucfgOpts...) - return newConfigFrom(c), err + return newConfigFrom(c, nil), err + } + + otelKeys := map[string]interface{}{} + for _, skip := range local.otelKeys { + val, ok := data[skip] + if ok { + otelKeys[skip] = val + delete(data, skip) + } } skippedKeys := map[string]interface{}{} @@ -103,14 +131,17 @@ func NewConfigFrom(from interface{}, opts ...interface{}) (*Config, error) { } if len(skippedKeys) > 0 { err = cfg.Merge(skippedKeys, ucfg.ResolveNOOP) - - // we modified incoming object - // cleanup so skipped keys are not missing - for k, v := range skippedKeys { - data[k] = v + if err != nil { + return nil, err } } - return newConfigFrom(cfg), err + + var otelCfg *confmap.Conf + if len(otelKeys) > 0 { + otelCfg = confmap.NewFromStringMap(otelKeys) + } + + return newConfigFrom(cfg, otelCfg), nil } // MustNewConfigFrom try to create a configuration based on the type passed as arguments and panic @@ -123,12 +154,23 @@ func MustNewConfigFrom(from interface{}) *Config { return c } -func newConfigFrom(in *ucfg.Config) *Config { - return (*Config)(in) +func newConfigFrom(in *ucfg.Config, otel *confmap.Conf) *Config { + return &Config{ + Agent: in, + OTel: otel, + } +} + +// Unpack implements the ucfg.Unpacker interface. +func (c *Config) Unpack(val interface{}) error { + if c.Agent == nil { + c.Agent = ucfg.New() + } + return c.Agent.Merge(val) } -// Unpack unpacks a struct to Config. -func (c *Config) Unpack(to interface{}, opts ...interface{}) error { +// UnpackTo unpacks this config into to with the given options. +func (c *Config) UnpackTo(to interface{}, opts ...interface{}) error { ucfgOpts, _, err := getOptions(opts...) if err != nil { return err @@ -137,7 +179,7 @@ func (c *Config) Unpack(to interface{}, opts ...interface{}) error { } func (c *Config) access() *ucfg.Config { - return (*ucfg.Config)(c) + return c.Agent } // Merge merges two configuration together. @@ -146,6 +188,24 @@ func (c *Config) Merge(from interface{}, opts ...interface{}) error { if err != nil { return err } + cfg, ok := from.(*Config) + if ok { + // can merge both together + err = c.access().Merge(cfg.Agent, ucfgOpts...) + if err != nil { + return err + } + if c.OTel == nil && cfg.OTel != nil { + // simple, update to other retrieved configuration + c.OTel = cfg.OTel + } else if cfg.OTel != nil { + err = c.OTel.Merge(cfg.OTel) + if err != nil { + return err + } + } + return nil + } return c.access().Merge(from, ucfgOpts...) } @@ -223,7 +283,7 @@ func (c *Config) Enabled() bool { if c == nil { return false } - if err := c.Unpack(&testEnabled); err != nil { + if err := c.UnpackTo(&testEnabled); err != nil { // if unpacking fails, expect 'enabled' being set to default value return true } @@ -248,7 +308,7 @@ func LoadFiles(paths ...string) (*Config, error) { return nil, err } } - return newConfigFrom(merger.Config()), nil + return newConfigFrom(merger.Config(), nil), nil } func getOptions(opts ...interface{}) ([]ucfg.Option, options, error) { diff --git a/internal/pkg/config/config_test.go b/internal/pkg/config/config_test.go index cfbf8c76394..55f5d60e7c8 100644 --- a/internal/pkg/config/config_test.go +++ b/internal/pkg/config/config_test.go @@ -12,7 +12,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/confmap" "gopkg.in/yaml.v2" + + "github.com/elastic/go-ucfg" ) func TestConfig(t *testing.T) { @@ -154,3 +157,214 @@ func TestDollarSignsInInputs(t *testing.T) { assert.NoError(t, err) assert.Equal(t, in, out) } + +func TestConfigUnpack(t *testing.T) { + c := &struct { + // go-ucfg will call the Unpacker interface + Inner *Config + }{} + in := map[string]interface{}{ + "inner": map[string]interface{}{ + "key": "value", + }, + } + cfg := MustNewConfigFrom(in) + require.NoError(t, cfg.UnpackTo(c)) + + require.NotNil(t, c.Inner.Agent) + val, err := c.Inner.Agent.String("key", 0) + require.NoError(t, err) + assert.Equal(t, "value", val) +} + +func TestConfigOTelNNil(t *testing.T) { + c, err := NewConfigFrom(map[string]interface{}{ + "outputs": map[string]interface{}{ + "default": map[string]interface{}{ + "type": "elasticsearch", + "hosts": []interface{}{"127.0.0.1:9200"}, + "username": "elastic", + "password": "changeme", + }, + }, + "inputs": []interface{}{ + map[string]interface{}{ + "type": "logfile", + "streams": []interface{}{ + map[string]interface{}{ + "paths": []interface{}{"/var/log/syslog"}, + }, + }, + }, + }, + }) + require.NoError(t, err) + assert.NotNil(t, c.Agent) + assert.Nil(t, c.OTel) +} + +func TestConfigOTelNotNil(t *testing.T) { + c, err := NewConfigFrom(map[string]interface{}{ + "outputs": map[string]interface{}{ + "default": map[string]interface{}{ + "type": "elasticsearch", + "hosts": []interface{}{"127.0.0.1:9200"}, + "username": "elastic", + "password": "changeme", + }, + }, + "inputs": []interface{}{ + map[string]interface{}{ + "type": "logfile", + "streams": []interface{}{ + map[string]interface{}{ + "paths": []interface{}{"/var/log/syslog"}, + }, + }, + }, + }, + "connectors": map[string]interface{}{ + "count": map[string]interface{}{}, + }, + "receivers": map[string]interface{}{ + "otlp": map[string]interface{}{ + "protocols": map[string]interface{}{ + "grpc": map[string]interface{}{ + "endpoint": "0.0.0.0:4317", + }, + }, + }, + }, + "processors": map[string]interface{}{ + "batch": map[string]interface{}{}, + }, + "exporters": map[string]interface{}{ + "otlp": map[string]interface{}{ + "endpoint": "otelcol:4317", + }, + }, + "extensions": map[string]interface{}{ + "health_check": map[string]interface{}{}, + "pprof": map[string]interface{}{}, + }, + "service": map[string]interface{}{ + "extensions": []string{"health_check", "pprof"}, + "pipelines": map[string]interface{}{ + "traces": map[string]interface{}{ + "receivers": []string{"otlp"}, + "processors": []string{"batch"}, + "exporters": []string{"otlp"}, + }, + "metrics": map[string]interface{}{ + "receivers": []string{"otlp"}, + "processors": []string{"batch"}, + "exporters": []string{"otlp"}, + }, + "logs": map[string]interface{}{ + "receivers": []string{"otlp"}, + "processors": []string{"batch"}, + "exporters": []string{"otlp"}, + }, + }, + }, + }) + require.NoError(t, err) + require.NotNil(t, c.Agent) + + require.NotNil(t, c.OTel) + assert.NotNil(t, c.OTel.Get("connectors")) + assert.NotNil(t, c.OTel.Get("receivers")) + assert.NotNil(t, c.OTel.Get("processors")) + assert.NotNil(t, c.OTel.Get("exporters")) + assert.NotNil(t, c.OTel.Get("extensions")) + assert.NotNil(t, c.OTel.Get("service")) +} + +func TestConfigMerge(t *testing.T) { + scenarios := []struct { + Name string + Into *Config + From *Config + Result *Config + }{ + { + Name: "no otel", + Into: newConfigFrom(ucfg.MustNewFrom(map[string]interface{}{ + "agent": map[string]interface{}{ + "a": "value-a", + }, + }), nil), + From: newConfigFrom(ucfg.MustNewFrom(map[string]interface{}{ + "agent": map[string]interface{}{ + "b": "value-b", + }, + }), nil), + Result: newConfigFrom(ucfg.MustNewFrom(map[string]interface{}{ + "agent": map[string]interface{}{ + "a": "value-a", + "b": "value-b", + }, + }), nil), + }, + { + Name: "otel set", + Into: newConfigFrom(ucfg.MustNewFrom(map[string]interface{}{ + "agent": map[string]interface{}{ + "a": "value-a", + }, + }), nil), + From: newConfigFrom(ucfg.MustNewFrom(map[string]interface{}{ + "agent": map[string]interface{}{ + "b": "value-b", + }, + }), confmap.NewFromStringMap(map[string]interface{}{ + "extensions": []interface{}{"health_check", "pprof"}, + })), + Result: newConfigFrom(ucfg.MustNewFrom(map[string]interface{}{ + "agent": map[string]interface{}{ + "a": "value-a", + "b": "value-b", + }, + }), confmap.NewFromStringMap(map[string]interface{}{ + "extensions": []interface{}{"health_check", "pprof"}, + })), + }, + { + Name: "otel merge", + Into: newConfigFrom(ucfg.MustNewFrom(map[string]interface{}{ + "agent": map[string]interface{}{ + "a": "value-a", + }, + }), confmap.NewFromStringMap(map[string]interface{}{ + "extensions": []interface{}{"health_check", "pprof"}, + })), + From: newConfigFrom(ucfg.MustNewFrom(map[string]interface{}{ + "agent": map[string]interface{}{ + "b": "value-b", + }, + }), confmap.NewFromStringMap(map[string]interface{}{ + "receivers": map[string]interface{}{ + "filelog": map[string]interface{}{}, + }, + })), + Result: newConfigFrom(ucfg.MustNewFrom(map[string]interface{}{ + "agent": map[string]interface{}{ + "a": "value-a", + "b": "value-b", + }, + }), confmap.NewFromStringMap(map[string]interface{}{ + "extensions": []interface{}{"health_check", "pprof"}, + "receivers": map[string]interface{}{ + "filelog": map[string]interface{}{}, + }, + })), + }, + } + for _, s := range scenarios { + t.Run(s.Name, func(t *testing.T) { + err := s.Into.Merge(s.From) + require.NoError(t, err) + assert.Equal(t, s.Result, s.Into) + }) + } +} diff --git a/internal/pkg/config/loader.go b/internal/pkg/config/loader.go index 4591684a301..00b6552b1e2 100644 --- a/internal/pkg/config/loader.go +++ b/internal/pkg/config/loader.go @@ -8,6 +8,8 @@ import ( "fmt" "path/filepath" + "go.opentelemetry.io/collector/confmap" + "github.com/elastic/elastic-agent/pkg/core/logger" "github.com/elastic/go-ucfg" "github.com/elastic/go-ucfg/cfgutil" @@ -34,6 +36,7 @@ func NewLoader(logger *logger.Logger, inputsFolder string) *Loader { func (l *Loader) Load(files []string) (*Config, error) { inputsList := make([]*ucfg.Config, 0) merger := cfgutil.NewCollector(nil) + var otelCfg *confmap.Conf for _, f := range files { cfg, err := LoadFile(f) if err != nil { @@ -55,6 +58,9 @@ func (l *Loader) Load(files []string) (*Config, error) { return nil, fmt.Errorf("failed to merge configuration file '%s' to existing one: %w", f, err) } l.logger.Debugf("Merged configuration from %s into result", f) + if cfg.OTel != nil { + otelCfg = cfg.OTel + } } } config := merger.Config() @@ -62,7 +68,7 @@ func (l *Loader) Load(files []string) (*Config, error) { // if there is no input configuration, return what we have collected. if len(inputsList) == 0 { l.logger.Debugf("Merged all configuration files from %v, no external input files", files) - return newConfigFrom(config), nil + return newConfigFrom(config, otelCfg), nil } // merge inputs sections from the last standalone configuration @@ -82,7 +88,7 @@ func (l *Loader) Load(files []string) (*Config, error) { } l.logger.Debugf("Merged all configuration files from %v, with external input files", files) - return newConfigFrom(config), nil + return newConfigFrom(config, otelCfg), nil } func getInput(c *Config) ([]*ucfg.Config, error) { @@ -90,7 +96,7 @@ func getInput(c *Config) ([]*ucfg.Config, error) { Inputs []*ucfg.Config `config:"inputs"` }{make([]*ucfg.Config, 0)} - if err := c.Unpack(&tmpConfig); err != nil { + if err := c.UnpackTo(&tmpConfig); err != nil { return nil, fmt.Errorf("failed to parse inputs section from configuration: %w", err) } return tmpConfig.Inputs, nil diff --git a/internal/pkg/core/monitoring/config/config_test.go b/internal/pkg/core/monitoring/config/config_test.go index 47006777b46..efe490ab723 100644 --- a/internal/pkg/core/monitoring/config/config_test.go +++ b/internal/pkg/core/monitoring/config/config_test.go @@ -99,7 +99,7 @@ metrics: true`, require.NoError(t, err, "failed to create config") cfg := testCase.startingCfg - err = c.Unpack(&cfg) + err = c.UnpackTo(&cfg) require.NoError(t, err, "failed to unpack config") assert.Equal(t, testCase.expectedEnabled, cfg.HTTP.Enabled, "enabled incorrect") @@ -151,7 +151,7 @@ http: require.NoError(t, err, "failed to create config") cfg := DefaultConfig() - err = c.Unpack(&cfg) + err = c.UnpackTo(&cfg) require.NoError(t, err, "failed to unpack config") require.Equal(t, tc.expectedHost, cfg.HTTP.Host) @@ -222,7 +222,7 @@ func TestAPMConfig(t *testing.T) { require.NoError(t, err) cfg := DefaultConfig() - require.NoError(t, in.Unpack(cfg)) + require.NoError(t, in.UnpackTo(cfg)) require.NotNil(t, cfg) assert.Equal(t, tc.out, cfg.APM) diff --git a/internal/pkg/otel/agentprovider/provider.go b/internal/pkg/otel/agentprovider/provider.go new file mode 100644 index 00000000000..02cf3e3b399 --- /dev/null +++ b/internal/pkg/otel/agentprovider/provider.go @@ -0,0 +1,112 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License 2.0; +// you may not use this file except in compliance with the Elastic License 2.0. + +package agentprovider + +import ( + "context" + "fmt" + "sync" + + "github.com/gofrs/uuid/v5" + "go.opentelemetry.io/collector/confmap" +) + +const schemeName = "elasticagent" + +// Provider is a fixed provider that has a factory but only returns the same provider. +type Provider struct { + uri string + + cfg *confmap.Conf + cfgMu sync.RWMutex + updated chan struct{} + + canceller context.CancelFunc + cancelledMu sync.Mutex +} + +// NewProvider creates a `agentprovider.Provider`. +func NewProvider(cfg *confmap.Conf) *Provider { + uri := fmt.Sprintf("%s:%s", schemeName, uuid.Must(uuid.NewV4()).String()) + return &Provider{ + uri: uri, + cfg: cfg, + updated: make(chan struct{}, 1), // buffer of 1, stores the updated state + } +} + +// NewFactory provides a factory. +// +// This factory doesn't create a new provider on each call. It always returns the same provider. +func (p *Provider) NewFactory() confmap.ProviderFactory { + return confmap.NewProviderFactory(func(_ confmap.ProviderSettings) confmap.Provider { + return p + }) +} + +// Update updates the latest configuration in the provider. +func (p *Provider) Update(cfg *confmap.Conf) { + p.cfgMu.Lock() + p.cfg = cfg + p.cfgMu.Unlock() + select { + case p.updated <- struct{}{}: + default: + // already has an updated state + } +} + +// Retrieve returns the latest configuration. +func (p *Provider) Retrieve(ctx context.Context, uri string, watcher confmap.WatcherFunc) (*confmap.Retrieved, error) { + if uri != p.uri { + return nil, fmt.Errorf("%q uri doesn't equal defined %q provider", uri, schemeName) + } + + // get latest cfg at time of call + p.cfgMu.RLock() + cfg := p.cfg + p.cfgMu.RUnlock() + + // don't use passed in context, as the cancel comes from Shutdown + ctx, cancel := context.WithCancel(context.Background()) + p.replaceCanceller(cancel) + go func() { + defer p.replaceCanceller(nil) // ensure the context is always cleaned up + select { + case <-ctx.Done(): + return + case <-p.updated: + watcher(&confmap.ChangeEvent{}) + } + }() + + return confmap.NewRetrieved(cfg.ToStringMap()) +} + +// Scheme is the scheme for this provider. +func (p *Provider) Scheme() string { + return schemeName +} + +// Shutdown called by collect when stopping. +func (p *Provider) Shutdown(ctx context.Context) error { + p.replaceCanceller(nil) + return nil +} + +// URI returns the URI to be used for this provider. +func (p *Provider) URI() string { + return p.uri +} + +func (p *Provider) replaceCanceller(replace context.CancelFunc) { + p.cancelledMu.Lock() + canceller := p.canceller + p.canceller = replace + p.cancelledMu.Unlock() + if canceller != nil { + canceller() + } +} diff --git a/internal/pkg/otel/agentprovider/provider_test.go b/internal/pkg/otel/agentprovider/provider_test.go new file mode 100644 index 00000000000..5b6cea50439 --- /dev/null +++ b/internal/pkg/otel/agentprovider/provider_test.go @@ -0,0 +1,99 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License 2.0; +// you may not use this file except in compliance with the Elastic License 2.0. + +package agentprovider + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/confmap" +) + +func TestProvider_NewFactory(t *testing.T) { + p := NewProvider(nil) + assert.Equal(t, p, p.NewFactory().Create(confmap.ProviderSettings{})) +} + +func TestProvider_Schema(t *testing.T) { + p := NewProvider(nil) + assert.Equal(t, schemeName, p.Scheme()) +} + +func TestProvider_URI(t *testing.T) { + p := NewProvider(nil) + assert.Equal(t, p.uri, p.URI()) +} + +func TestProvider_Update(t *testing.T) { + cfg := confmap.New() + cfg2 := confmap.New() + cfg3 := confmap.New() + + p := NewProvider(cfg) + p.Update(cfg2) // should not block + p.Update(cfg3) // should not block +} + +func TestProvider_Retrieve(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + cfg := confmap.New() + + p := NewProvider(cfg) + ret, err := p.Retrieve(ctx, p.URI(), func(event *confmap.ChangeEvent) {}) + require.NoError(t, err) + retCfg, err := ret.AsConf() + require.NoError(t, err) + require.Equal(t, cfg, retCfg) +} + +func TestProvider_Retrieve_Update(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + cfg := confmap.New() + cfg2 := confmap.New() + + ch := make(chan *confmap.ChangeEvent, 1) + + p := NewProvider(cfg) + ret, err := p.Retrieve(ctx, p.URI(), func(event *confmap.ChangeEvent) { + ch <- event + }) + require.NoError(t, err) + retCfg, err := ret.AsConf() + require.NoError(t, err) + require.Equal(t, cfg, retCfg) + + p.Update(cfg2) + evt := <-ch + require.NotNil(t, evt) + + ret2, err := p.Retrieve(ctx, p.URI(), func(event *confmap.ChangeEvent) {}) + require.NoError(t, err) + retCfg2, err := ret2.AsConf() + require.NoError(t, err) + assert.Equal(t, cfg2, retCfg2) +} + +func TestProvider_Shutdown(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + cfg := confmap.New() + + p := NewProvider(cfg) + ret, err := p.Retrieve(ctx, p.URI(), func(event *confmap.ChangeEvent) {}) + require.NoError(t, err) + retCfg, err := ret.AsConf() + require.NoError(t, err) + require.Equal(t, cfg, retCfg) + + err = p.Shutdown(ctx) + require.NoError(t, err) +} diff --git a/internal/pkg/otel/components.go b/internal/pkg/otel/components.go index 42cf27581bc..06e4c89c239 100644 --- a/internal/pkg/otel/components.go +++ b/internal/pkg/otel/components.go @@ -2,8 +2,6 @@ // or more contributor license agreements. Licensed under the Elastic License 2.0; // you may not use this file except in compliance with the Elastic License 2.0. -//go:build !windows - package otel import ( @@ -62,75 +60,80 @@ import ( "github.com/elastic/opentelemetry-collector-components/connector/signaltometricsconnector" ) -func components() (otelcol.Factories, error) { - var err error - factories := otelcol.Factories{} - - // Receivers - factories.Receivers, err = receiver.MakeFactoryMap( - otlpreceiver.NewFactory(), - filelogreceiver.NewFactory(), - kubeletstatsreceiver.NewFactory(), - k8sclusterreceiver.NewFactory(), - hostmetricsreceiver.NewFactory(), - httpcheckreceiver.NewFactory(), - k8sobjectsreceiver.NewFactory(), - prometheusreceiver.NewFactory(), - jaegerreceiver.NewFactory(), - zipkinreceiver.NewFactory(), - fbreceiver.NewFactory(), - ) - if err != nil { - return otelcol.Factories{}, err - } - - // Processors - factories.Processors, err = processor.MakeFactoryMap( - batchprocessor.NewFactory(), - resourceprocessor.NewFactory(), - attributesprocessor.NewFactory(), - transformprocessor.NewFactory(), - filterprocessor.NewFactory(), - k8sattributesprocessor.NewFactory(), - elasticinframetricsprocessor.NewFactory(), - resourcedetectionprocessor.NewFactory(), - memorylimiterprocessor.NewFactory(), - lsmintervalprocessor.NewFactory(), - elastictraceprocessor.NewFactory(), - ) - if err != nil { - return otelcol.Factories{}, err - } - - // Exporters - factories.Exporters, err = exporter.MakeFactoryMap( - otlpexporter.NewFactory(), - debugexporter.NewFactory(), - fileexporter.NewFactory(), - elasticsearchexporter.NewFactory(), - otlphttpexporter.NewFactory(), - ) - if err != nil { - return otelcol.Factories{}, err +func components(extensionFactories ...extension.Factory) func() (otelcol.Factories, error) { + return func() (otelcol.Factories, error) { + + var err error + factories := otelcol.Factories{} + + // Receivers + factories.Receivers, err = receiver.MakeFactoryMap( + otlpreceiver.NewFactory(), + filelogreceiver.NewFactory(), + kubeletstatsreceiver.NewFactory(), + k8sclusterreceiver.NewFactory(), + hostmetricsreceiver.NewFactory(), + httpcheckreceiver.NewFactory(), + k8sobjectsreceiver.NewFactory(), + prometheusreceiver.NewFactory(), + jaegerreceiver.NewFactory(), + zipkinreceiver.NewFactory(), + fbreceiver.NewFactory(), + ) + if err != nil { + return otelcol.Factories{}, err + } + + // Processors + factories.Processors, err = processor.MakeFactoryMap( + batchprocessor.NewFactory(), + resourceprocessor.NewFactory(), + attributesprocessor.NewFactory(), + transformprocessor.NewFactory(), + filterprocessor.NewFactory(), + k8sattributesprocessor.NewFactory(), + elasticinframetricsprocessor.NewFactory(), + resourcedetectionprocessor.NewFactory(), + memorylimiterprocessor.NewFactory(), + lsmintervalprocessor.NewFactory(), + elastictraceprocessor.NewFactory(), + ) + if err != nil { + return otelcol.Factories{}, err + } + + // Exporters + factories.Exporters, err = exporter.MakeFactoryMap( + otlpexporter.NewFactory(), + debugexporter.NewFactory(), + fileexporter.NewFactory(), + elasticsearchexporter.NewFactory(), + otlphttpexporter.NewFactory(), + ) + if err != nil { + return otelcol.Factories{}, err + } + + factories.Connectors, err = connector.MakeFactoryMap( + spanmetricsconnector.NewFactory(), + signaltometricsconnector.NewFactory(), + ) + if err != nil { + return otelcol.Factories{}, err + } + + extensions := []extension.Factory{ + memorylimiterextension.NewFactory(), + filestorage.NewFactory(), + healthcheckextension.NewFactory(), + pprofextension.NewFactory(), + } + extensions = append(extensions, extensionFactories...) + factories.Extensions, err = extension.MakeFactoryMap(extensions...) + if err != nil { + return otelcol.Factories{}, err + } + + return factories, err } - - factories.Connectors, err = connector.MakeFactoryMap( - spanmetricsconnector.NewFactory(), - signaltometricsconnector.NewFactory(), - ) - if err != nil { - return otelcol.Factories{}, err - } - - factories.Extensions, err = extension.MakeFactoryMap( - memorylimiterextension.NewFactory(), - filestorage.NewFactory(), - healthcheckextension.NewFactory(), - pprofextension.NewFactory(), - ) - if err != nil { - return otelcol.Factories{}, err - } - - return factories, err } diff --git a/internal/pkg/otel/config_file_provider.go b/internal/pkg/otel/config_file_provider.go deleted file mode 100644 index 7a85f532986..00000000000 --- a/internal/pkg/otel/config_file_provider.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -// or more contributor license agreements. Licensed under the Elastic License 2.0; -// you may not use this file except in compliance with the Elastic License 2.0. - -//go:build !windows - -package otel - -import ( - "context" - "fmt" - "os" - "path/filepath" - "strings" - - "go.opentelemetry.io/collector/confmap" - - "github.com/elastic/elastic-agent/internal/pkg/config" -) - -const schemeName = "file" - -type provider struct{} - -func NewFileProviderWithDefaults() confmap.Provider { - return &provider{} -} - -func (fmp *provider) Retrieve(_ context.Context, uri string, _ confmap.WatcherFunc) (*confmap.Retrieved, error) { - if !strings.HasPrefix(uri, schemeName+":") { - return nil, fmt.Errorf("%q uri is not supported by %q provider", uri, schemeName) - } - - // Clean the path before using it. - content, err := os.ReadFile(filepath.Clean(uri[len(schemeName)+1:])) - if err != nil { - return nil, fmt.Errorf("unable to read the file %v: %w", uri, err) - } - - config, err := config.NewConfigFrom(content) - if err != nil { - return nil, err - } - - rawConf := defaultOtelConfig() - if err := config.Unpack(rawConf); err != nil { - return nil, err - } - return confmap.NewRetrieved(rawConf) -} - -func (*provider) Scheme() string { - return schemeName -} - -func (*provider) Shutdown(context.Context) error { - return nil -} - -func defaultOtelConfig() map[string]any { - defaultConfig := map[string]any{ - "service": map[string]any{ - "telemetry": map[string]any{ - "logs": map[string]any{ - "output_paths": []string{"stdout"}, - }, - }, - }, - } - - return defaultConfig -} diff --git a/internal/pkg/otel/config_file_provider_test.go b/internal/pkg/otel/config_file_provider_test.go deleted file mode 100644 index d20533bdf5b..00000000000 --- a/internal/pkg/otel/config_file_provider_test.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -// or more contributor license agreements. Licensed under the Elastic License 2.0; -// you may not use this file except in compliance with the Elastic License 2.0. - -//go:build !windows - -package otel - -import ( - "context" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestContentFileProviderOutput(t *testing.T) { - testCases := []struct { - name string - configFile string - expectedOutputs []string - }{ - {"default", "otel.yml", []string{"stdout"}}, - {"stderr", "otlp.yml", []string{"stderr"}}, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - cp := NewFileProviderWithDefaults() - confMap, err := cp.Retrieve(context.TODO(), "file:"+filepath.Join(".", "testdata", tc.configFile), nil) - require.NoError(t, err) - - conf, err := confMap.AsConf() - require.NoError(t, err) - val := conf.Get("service::telemetry::logs::output_paths") - require.NotNil(t, val) - - valStrArray, ok := val.([]string) - require.True(t, ok) - require.EqualValues(t, tc.expectedOutputs, valStrArray) - }) - } -} diff --git a/internal/pkg/otel/config_manager.go b/internal/pkg/otel/config_manager.go deleted file mode 100644 index 3af1c845ca2..00000000000 --- a/internal/pkg/otel/config_manager.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one -// or more contributor license agreements. Licensed under the Elastic License 2.0; -// you may not use this file except in compliance with the Elastic License 2.0. - -package otel - -import ( - "context" - - "github.com/elastic/elastic-agent/internal/pkg/agent/application/coordinator" - "github.com/elastic/elastic-agent/internal/pkg/config" -) - -// OtelModeConfigManager serves as a config manager for OTel use cases -// In this case agent should ignore all configuration coming from elastic-agent.yml file -// or other sources. -type OtelModeConfigManager struct { - ch chan coordinator.ConfigChange - errCh chan error -} - -// NewOtelModeConfigManager creates new OtelModeConfigManager ignoring -// configuration coming from other sources. -func NewOtelModeConfigManager() *OtelModeConfigManager { - return &OtelModeConfigManager{ - ch: make(chan coordinator.ConfigChange), - errCh: make(chan error), - } -} - -func (t *OtelModeConfigManager) Run(ctx context.Context) error { - // send config to transition from STARTING to HEALTHY - select { - case t.ch <- &otelConfigChange{}: - case <-ctx.Done(): - } - <-ctx.Done() - return ctx.Err() -} - -func (t *OtelModeConfigManager) Errors() <-chan error { - return t.errCh -} - -// ActionErrors returns the error channel for actions. -// Returns nil channel. -func (t *OtelModeConfigManager) ActionErrors() <-chan error { - return nil -} - -func (t *OtelModeConfigManager) Watch() <-chan coordinator.ConfigChange { - return t.ch -} - -type otelConfigChange struct { -} - -func (l *otelConfigChange) Config() *config.Config { - return config.New() -} - -func (l *otelConfigChange) Ack() error { - // do nothing - return nil -} - -func (l *otelConfigChange) Fail(_ error) { - // do nothing -} diff --git a/internal/pkg/otel/manager/extension.go b/internal/pkg/otel/manager/extension.go new file mode 100644 index 00000000000..59d968df053 --- /dev/null +++ b/internal/pkg/otel/manager/extension.go @@ -0,0 +1,188 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License 2.0; +// you may not use this file except in compliance with the Elastic License 2.0. + +package manager + +import ( + "context" + + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/status" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componentstatus" + "go.opentelemetry.io/collector/extension" + "go.uber.org/zap" +) + +var AgentStatusExtensionType = component.MustNewType("agent_status") + +type evtPair struct { + source *componentstatus.InstanceID + event *componentstatus.Event +} + +type AgentStatusExtension struct { + mgr *OTelManager + telemetry component.TelemetrySettings + aggregator *status.Aggregator + eventCh chan *evtPair + readyCh chan struct{} + kickCh chan struct{} + ctx context.Context + canceller context.CancelFunc + host component.Host +} + +// validate that the extension implements the required interfaces +var _ component.Component = (*AgentStatusExtension)(nil) +var _ componentstatus.Watcher = (*AgentStatusExtension)(nil) + +// NewAgentStatusExtension returns the agent_status extension to be used by the +// OTel collector when running in hybrid mode. +func NewAgentStatusExtension(ctx context.Context, set extension.Settings, mgr *OTelManager) *AgentStatusExtension { + ctx, cancel := context.WithCancel(ctx) + aggregator := status.NewAggregator(status.PriorityRecoverable) + as := &AgentStatusExtension{ + mgr: mgr, + telemetry: set.TelemetrySettings, + aggregator: aggregator, + eventCh: make(chan *evtPair), + readyCh: make(chan struct{}), + kickCh: make(chan struct{}, 1), + ctx: ctx, + canceller: cancel, + } + + // start processing early as ComponentStatusChanged will be called before Start is called + go as.eventLoop(ctx) + + return as +} + +// NewAgentStatusFactory provides a factory for creating the AgentStatusExtension. +func NewAgentStatusFactory(mgr *OTelManager) extension.Factory { + return extension.NewFactory( + AgentStatusExtensionType, + func() component.Config { + return nil + }, + func(ctx context.Context, set extension.Settings, cfg component.Config) (extension.Extension, error) { + return NewAgentStatusExtension(ctx, set, mgr), nil + }, + component.StabilityLevelDevelopment, + ) +} + +// Start implements the component.Component interface. +func (as *AgentStatusExtension) Start(ctx context.Context, host component.Host) error { + as.telemetry.Logger.Debug("Starting agent status extension") + as.host = host + return nil +} + +// Shutdown implements the component.Component interface. +func (as *AgentStatusExtension) Shutdown(ctx context.Context) error { + // preemptively send the stopped event, so it can be exported before shutdown + componentstatus.ReportStatus(as.host, componentstatus.NewEvent(componentstatus.StatusStopped)) + as.canceller() + return nil +} + +// Ready implements the extension.PipelineWatcher interface. +func (as *AgentStatusExtension) Ready() error { + close(as.readyCh) + return nil +} + +// NotReady implements the extension.PipelineWatcher interface. +func (as *AgentStatusExtension) NotReady() error { + return nil +} + +// ComponentStatusChanged implements the extension.StatusWatcher interface. +func (as *AgentStatusExtension) ComponentStatusChanged( + source *componentstatus.InstanceID, + event *componentstatus.Event, +) { + // this extension is always force loaded and not by the user, so status + // information should be hidden as they didn't directly enable it + if source.ComponentID().String() == AgentStatusExtensionType.String() { + return + } + // possible that even after Shutdown is called that this function is still + // called by the coordinator + defer func() { + if r := recover(); r != nil { + as.telemetry.Logger.Info( + "discarding event received after shutdown", + zap.Any("source", source), + zap.Any("event", event), + ) + } + }() + select { + case as.eventCh <- &evtPair{source: source, event: event}: + case <-as.ctx.Done(): + } +} + +func (as *AgentStatusExtension) eventLoop(ctx context.Context) { + // prevent aggregate statuses from flapping between StatusStarting and StatusOK + // as components are started individually by the service. + // + // follows the same pattern that is being used by the healthcheckv2extension + // https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/extension/healthcheckv2extension/extension.go#L168 + var eventQueue []*evtPair + +LOOP: + for { + select { + case esp := <-as.eventCh: + if esp.event.Status() != componentstatus.StatusStarting { + eventQueue = append(eventQueue, esp) + continue + } + as.aggregator.RecordStatus(esp.source, esp.event) + as.triggerKickCh() + case <-as.readyCh: + if len(eventQueue) > 0 { + for _, esp := range eventQueue { + as.aggregator.RecordStatus(esp.source, esp.event) + } + as.triggerKickCh() + } + break LOOP + case <-as.kickCh: + as.publishStatus() + case <-ctx.Done(): + as.aggregator.Close() + return + } + } + + // After PipelineWatcher.Ready, record statuses as they are received. + for { + select { + case esp := <-as.eventCh: + as.aggregator.RecordStatus(esp.source, esp.event) + as.triggerKickCh() + case <-as.kickCh: + as.publishStatus() + case <-ctx.Done(): + as.aggregator.Close() + return + } + } +} + +func (as *AgentStatusExtension) triggerKickCh() { + select { + case as.kickCh <- struct{}{}: + default: + } +} + +func (as *AgentStatusExtension) publishStatus() { + current, _ := as.aggregator.AggregateStatus(status.ScopeAll, status.Verbose) + as.mgr.statusCh <- current +} diff --git a/internal/pkg/otel/manager/force_extension_converter.go b/internal/pkg/otel/manager/force_extension_converter.go new file mode 100644 index 00000000000..11732d42229 --- /dev/null +++ b/internal/pkg/otel/manager/force_extension_converter.go @@ -0,0 +1,81 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License 2.0; +// you may not use this file except in compliance with the Elastic License 2.0. + +package manager + +import ( + "context" + "fmt" + + "go.opentelemetry.io/collector/confmap" +) + +// forceExtension is a Converter that forces that an extension is enabled in the OTel configuration. +type forceExtension struct { + name string +} + +func (fe *forceExtension) Convert(ctx context.Context, conf *confmap.Conf) error { + err := func() error { + err := conf.Merge(confmap.NewFromStringMap(map[string]interface{}{ + "extensions": map[string]interface{}{ + fe.name: nil, + }, + })) + if err != nil { + return fmt.Errorf("merge into extensions failed: %w", err) + } + serviceConf, err := conf.Sub("service") + if err != nil { + //nolint:nilerr // ignore the error, no service defined in the configuration + // this is going to error by the collector any way no reason to pollute with more + // error information that is not really related to the issue at the moment + return nil + } + extensionsRaw := serviceConf.Get("extensions") + if extensionsRaw == nil { + // no extensions defined on service (easily add it) + err = conf.Merge(confmap.NewFromStringMap(map[string]interface{}{ + "service": map[string]interface{}{ + "extensions": []interface{}{fe.name}, + }, + })) + if err != nil { + return fmt.Errorf("merge into service::extensions failed: %w", err) + } + return nil + } + extensionsSlice, ok := extensionsRaw.([]interface{}) + if !ok { + return fmt.Errorf("merge into service::extensions failed: expected []interface{}, got %T", extensionsRaw) + } + for _, extensionRaw := range extensionsSlice { + extension, ok := extensionRaw.(string) + if ok && extension == fe.name { + // already present, nothing to do + return nil + } + } + extensionsSlice = append(extensionsSlice, fe.name) + err = conf.Merge(confmap.NewFromStringMap(map[string]interface{}{ + "service": map[string]interface{}{ + "extensions": extensionsSlice, + }, + })) + if err != nil { + return fmt.Errorf("merge into service::extensions failed: %w", err) + } + return nil + }() + if err != nil { + return fmt.Errorf("failed to force enable %s extension: %w", fe.name, err) + } + return nil +} + +func NewForceExtensionConverterFactory(name string) confmap.ConverterFactory { + return confmap.NewConverterFactory(func(_ confmap.ConverterSettings) confmap.Converter { + return &forceExtension{name} + }) +} diff --git a/internal/pkg/otel/manager/manager.go b/internal/pkg/otel/manager/manager.go new file mode 100644 index 00000000000..af5a8052cec --- /dev/null +++ b/internal/pkg/otel/manager/manager.go @@ -0,0 +1,228 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License 2.0; +// you may not use this file except in compliance with the Elastic License 2.0. + +package manager + +import ( + "context" + "errors" + + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/status" + "go.opentelemetry.io/collector/confmap" + "go.opentelemetry.io/collector/otelcol" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/elastic/elastic-agent/internal/pkg/otel" + "github.com/elastic/elastic-agent/internal/pkg/otel/agentprovider" + "github.com/elastic/elastic-agent/internal/pkg/release" + "github.com/elastic/elastic-agent/pkg/core/logger" +) + +// OTelManager is a manager that manages the lifecycle of the OTel collector inside of the Elastic Agent. +type OTelManager struct { + logger *logger.Logger + errCh chan error + + // The current configuration that the OTel collector is using. In the case that + // the cfg is nil then the collector is not running. + cfg *confmap.Conf + + // cfg is changed by sending its new value on cfgCh, where it is + // handled by (*OTelManager).Run. + cfgCh chan *confmap.Conf + + // stateCh passes the state information of the collector. + statusCh chan *status.AggregateStatus + + // doneChan is closed when Run is stopped to signal that any + // pending update calls should be ignored. + doneChan chan struct{} +} + +// NewOTelManager returns a OTelManager. +func NewOTelManager(logger *logger.Logger) *OTelManager { + return &OTelManager{ + logger: logger, + errCh: make(chan error), + cfgCh: make(chan *confmap.Conf), + statusCh: make(chan *status.AggregateStatus), + doneChan: make(chan struct{}), + } +} + +// Run runs the lifecycle of the manager. +func (m *OTelManager) Run(ctx context.Context) error { + var err error + var cancel context.CancelFunc + var provider *agentprovider.Provider + + // signal that the run loop is ended to unblock any incoming update calls + defer close(m.doneChan) + + runErrCh := make(chan error) + for { + select { + case <-ctx.Done(): + if cancel != nil { + cancel() + <-runErrCh // wait for collector to be stopped + } + return ctx.Err() + case err = <-runErrCh: + if err == nil { + // err is nil but there is a configuration + // + // in this rare case the collector stopped running but a configuration was + // provided and the collector stopped with a clean exit + cancel() + cancel, provider, err = m.startCollector(m.cfg, runErrCh) + if err != nil { + // failed to create the collector (this is different then + // it's failing to run). we do not retry creation on failure + // as it will always fail a new configuration is required for + // it not to fail (a new configuration will result in the retry) + select { + case m.errCh <- err: + case <-ctx.Done(): + } + } else { + // all good at the moment (possible that it will fail) + select { + case m.errCh <- nil: + case <-ctx.Done(): + } + } + } else { + // error occurred while running the collector, this occurs in the + // case that the configuration is invalid once the collector is started + // or the context for running the collector is cancelled. + // + // in the case that the configuration is invalid there is no reason to + // try again as it will keep failing so we do not trigger a restart + if cancel != nil { + cancel() + cancel = nil + provider = nil + // don't wait here for <-runErrCh, already occurred + // clear status, no longer running + select { + case m.statusCh <- nil: + case <-ctx.Done(): + } + } + // pass the error to the errCh so the coordinator, unless it's a cancel error + if !errors.Is(err, context.Canceled) { + select { + case m.errCh <- nil: + case <-ctx.Done(): + } + } + } + case cfg := <-m.cfgCh: + m.cfg = cfg + if cfg == nil { + // no configuration then the collector should not be + // running. if a cancel exists then it is running + // this cancels the context that will stop the running + // collector (this configuration does not get passed + // to the agent provider as an update) + if cancel != nil { + cancel() + cancel = nil + provider = nil + <-runErrCh // wait for collector to be stopped + // clear status, no longer running + select { + case m.statusCh <- nil: + case <-ctx.Done(): + } + } + // ensure that the coordinator knows that there is no error + // as the collector is not running anymore + select { + case m.errCh <- nil: + case <-ctx.Done(): + } + } else { + // either a new configuration or the first configuration + // that results in the collector being started + if cancel == nil { + // no cancel exists so the collector has not been + // started. start the collector with this configuration + cancel, provider, err = m.startCollector(m.cfg, runErrCh) + if err != nil { + // failed to create the collector (this is different then + // it's failing to run). we do not retry creation on failure + // as it will always fail a new configuration is required for + // it not to fail (a new configuration will result in the retry) + select { + case m.errCh <- err: + case <-ctx.Done(): + } + } else { + // all good at the moment (possible that it will fail) + select { + case m.errCh <- nil: + case <-ctx.Done(): + } + } + } else { + // collector is already running so only the configuration + // needs to be updated in the collector + provider.Update(m.cfg) + } + } + } + } +} + +// Errors returns channel that can send an error that affects the state of the running agent. +func (m *OTelManager) Errors() <-chan error { + return m.errCh +} + +// Update updates the configuration. +// +// When nil is passed for the cfg, then the collector is stopped. +func (m *OTelManager) Update(cfg *confmap.Conf) { + select { + case m.cfgCh <- cfg: + case <-m.doneChan: + // shutting down, ignore the update + } +} + +// Watch returns a channel to watch for state information. +// +// This must be called and the channel must be read from, or it will block this manager. +func (m *OTelManager) Watch() <-chan *status.AggregateStatus { + return m.statusCh +} + +func (m *OTelManager) startCollector(cfg *confmap.Conf, errCh chan error) (context.CancelFunc, *agentprovider.Provider, error) { + ctx, cancel := context.WithCancel(context.Background()) + ap := agentprovider.NewProvider(cfg) + + // NewForceExtensionConverterFactory is used to ensure that the agent_status extension is always enabled. + // It is required for the Elastic Agent to extract the status out of the OTel collector. + settings := otel.NewSettings( + release.Version(), []string{ap.URI()}, + otel.WithConfigProviderFactory(ap.NewFactory()), + otel.WithConfigConvertorFactory(NewForceExtensionConverterFactory(AgentStatusExtensionType.String())), + otel.WithExtensionFactory(NewAgentStatusFactory(m))) + settings.DisableGracefulShutdown = true // managed by this manager + settings.LoggingOptions = []zap.Option{zap.WrapCore(func(zapcore.Core) zapcore.Core { + return m.logger.Core() // use same zap as agent + })} + svc, err := otelcol.NewCollector(*settings) + if err != nil { + cancel() + return nil, nil, err + } + go func() { + errCh <- svc.Run(ctx) + }() + return cancel, ap, nil +} diff --git a/internal/pkg/otel/manager/manager_test.go b/internal/pkg/otel/manager/manager_test.go new file mode 100644 index 00000000000..1b105e47d2c --- /dev/null +++ b/internal/pkg/otel/manager/manager_test.go @@ -0,0 +1,184 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License 2.0; +// you may not use this file except in compliance with the Elastic License 2.0. + +package manager + +import ( + "context" + "errors" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/status" + "go.opentelemetry.io/collector/component/componentstatus" + "go.opentelemetry.io/collector/confmap" + + "github.com/elastic/elastic-agent/pkg/core/logger/loggertest" +) + +var ( + testConfig = map[string]interface{}{ + "receivers": map[string]interface{}{ + "otlp": map[string]interface{}{ + "protocols": map[string]interface{}{ + "grpc": map[string]interface{}{ + "endpoint": "0.0.0.0:4317", + }, + }, + }, + }, + "processors": map[string]interface{}{ + "batch": map[string]interface{}{}, + }, + "exporters": map[string]interface{}{ + "otlp": map[string]interface{}{ + "endpoint": "otelcol:4317", + }, + }, + "service": map[string]interface{}{ + "pipelines": map[string]interface{}{ + "traces": map[string]interface{}{ + "receivers": []string{"otlp"}, + "processors": []string{"batch"}, + "exporters": []string{"otlp"}, + }, + "metrics": map[string]interface{}{ + "receivers": []string{"otlp"}, + "processors": []string{"batch"}, + "exporters": []string{"otlp"}, + }, + "logs": map[string]interface{}{ + "receivers": []string{"otlp"}, + "processors": []string{"batch"}, + "exporters": []string{"otlp"}, + }, + }, + }, + } +) + +func TestOTelManager_Run(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + l, _ := loggertest.New("otel") + m := NewOTelManager(l) + + var errMx sync.Mutex + var err error + go func() { + for { + select { + case <-ctx.Done(): + return + case e := <-m.Errors(): + if e != nil { + // no error should be produced (any error is a failure) + errMx.Lock() + err = e + errMx.Unlock() + } + } + } + }() + getLatestErr := func() error { + errMx.Lock() + defer errMx.Unlock() + return err + } + + var latestMx sync.Mutex + var latest *status.AggregateStatus + go func() { + for { + select { + case <-ctx.Done(): + return + case c := <-m.Watch(): + latestMx.Lock() + latest = c + latestMx.Unlock() + } + } + }() + getLatestStatus := func() *status.AggregateStatus { + latestMx.Lock() + defer latestMx.Unlock() + return latest + } + + var runWg sync.WaitGroup + var runErr error + runWg.Add(1) + go func() { + defer runWg.Done() + runErr = m.Run(ctx) + }() + + ensureHealthy := func() { + if !assert.Eventuallyf(t, func() bool { + err := getLatestErr() + if err != nil { + // return now (but not for the correct reasons) + return true + } + latest := getLatestStatus() + if latest == nil || latest.Event.Status() != componentstatus.StatusOK { + return false + } + return true + }, 5*time.Minute, 1*time.Second, "otel collector never got healthy") { + lastStatus := getLatestStatus() + lastErr := getLatestErr() + + // never got healthy, stop the manager and wait for it to end + cancel() + runWg.Wait() + + // if a run error happened then report that + if !errors.Is(runErr, context.Canceled) { + t.Fatalf("otel manager never got healthy and the otel manager returned unexpected error: %v (latest status: %+v) (latest err: %v)", runErr, lastStatus, lastErr) + } + t.Fatalf("otel collector never got healthy: %+v (latest err: %v)", lastStatus, lastErr) + } + latestErr := getLatestErr() + require.NoError(t, latestErr, "runtime errored") + } + + ensureOff := func() { + require.Eventuallyf(t, func() bool { + err := getLatestErr() + if err != nil { + // return now (but not for the correct reasons) + return true + } + latest := getLatestStatus() + return latest == nil + }, 5*time.Minute, 1*time.Second, "otel collector never stopped") + latestErr := getLatestErr() + require.NoError(t, latestErr, "runtime errored") + } + + // ensure that it got healthy + cfg := confmap.NewFromStringMap(testConfig) + m.Update(cfg) + ensureHealthy() + + // trigger update (no config compare is due externally to otel collector) + m.Update(cfg) + ensureHealthy() + + // no configuration should stop the runner + m.Update(nil) + ensureOff() + + cancel() + runWg.Wait() + if !errors.Is(runErr, context.Canceled) { + t.Errorf("otel manager returned unexpected error: %v", runErr) + } +} diff --git a/internal/pkg/otel/otelhelpers/status.go b/internal/pkg/otel/otelhelpers/status.go new file mode 100644 index 00000000000..f11b4d9744c --- /dev/null +++ b/internal/pkg/otel/otelhelpers/status.go @@ -0,0 +1,64 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License 2.0; +// you may not use this file except in compliance with the Elastic License 2.0. + +package otelhelpers + +import ( + "fmt" + + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/status" + "go.opentelemetry.io/collector/component/componentstatus" + + "github.com/elastic/elastic-agent-client/v7/pkg/client" +) + +// HasStatus returns true when the status contains that component status. +func HasStatus(current *status.AggregateStatus, s componentstatus.Status) bool { + if current == nil { + return false + } + if current.Status() == s { + return true + } + for _, comp := range current.ComponentStatusMap { + return HasStatus(comp, s) + } + return false +} + +// StateWithMessage returns a `client.UnitState` and message for the current status. +func StateWithMessage(current *status.AggregateStatus) (client.UnitState, string) { + s := current.Status() + switch s { + case componentstatus.StatusNone: + // didn't report a status, we assume with no status + // that it is healthy + return client.UnitStateHealthy, "Healthy" + case componentstatus.StatusStarting: + return client.UnitStateStarting, "Starting" + case componentstatus.StatusOK: + return client.UnitStateHealthy, "Healthy" + case componentstatus.StatusRecoverableError: + if current.Err() != nil { + return client.UnitStateDegraded, fmt.Sprintf("Recoverable: %s", current.Err()) + } + return client.UnitStateDegraded, "Unknown recoverable error" + case componentstatus.StatusPermanentError: + if current.Err() != nil { + return client.UnitStateFailed, fmt.Sprintf("Permanent: %s", current.Err()) + } + return client.UnitStateFailed, "Unknown permanent error" + case componentstatus.StatusFatalError: + if current.Err() != nil { + return client.UnitStateFailed, fmt.Sprintf("Fatal: %s", current.Err()) + } + return client.UnitStateFailed, "Unknown fatal error" + case componentstatus.StatusStopping: + return client.UnitStateStopping, "Stopping" + case componentstatus.StatusStopped: + return client.UnitStateStopped, "Stopped" + } + // if we hit this case, then a new status was added that we don't know about + return client.UnitStateFailed, fmt.Sprintf("Unknown component status: %s", s) +} diff --git a/internal/pkg/otel/otelhelpers/status_test.go b/internal/pkg/otel/otelhelpers/status_test.go new file mode 100644 index 00000000000..f1858109596 --- /dev/null +++ b/internal/pkg/otel/otelhelpers/status_test.go @@ -0,0 +1,78 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License 2.0; +// you may not use this file except in compliance with the Elastic License 2.0. + +package otelhelpers + +import ( + "testing" + + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/status" + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/collector/component/componentstatus" +) + +func TestHasStatus(t *testing.T) { + scenarios := []struct { + Name string + Result bool + Has componentstatus.Status + Status *status.AggregateStatus + }{ + { + Name: "empty", + Result: false, + Has: componentstatus.StatusOK, + Status: nil, + }, + { + Name: "has status", + Result: true, + Has: componentstatus.StatusOK, + Status: &status.AggregateStatus{ + Event: componentstatus.NewEvent(componentstatus.StatusOK), + }, + }, + { + Name: "doesn't have status", + Result: false, + Has: componentstatus.StatusRecoverableError, + Status: &status.AggregateStatus{ + Event: componentstatus.NewEvent(componentstatus.StatusOK), + }, + }, + { + Name: "sub-component has status", + Result: true, + Has: componentstatus.StatusRecoverableError, + Status: &status.AggregateStatus{ + Event: componentstatus.NewEvent(componentstatus.StatusOK), + ComponentStatusMap: map[string]*status.AggregateStatus{ + "test-component": &status.AggregateStatus{ + Event: componentstatus.NewEvent(componentstatus.StatusRecoverableError), + }, + }, + }, + }, + { + Name: "sub-component doesn't have status", + Result: false, + Has: componentstatus.StatusPermanentError, + Status: &status.AggregateStatus{ + Event: componentstatus.NewEvent(componentstatus.StatusRecoverableError), + ComponentStatusMap: map[string]*status.AggregateStatus{ + "test-component": &status.AggregateStatus{ + Event: componentstatus.NewEvent(componentstatus.StatusRecoverableError), + }, + }, + }, + }, + } + + for _, scenario := range scenarios { + t.Run(scenario.Name, func(t *testing.T) { + observed := HasStatus(scenario.Status, scenario.Has) + assert.Equal(t, scenario.Result, observed) + }) + } +} diff --git a/internal/pkg/otel/run.go b/internal/pkg/otel/run.go index 2beb819c21c..c647810bba1 100644 --- a/internal/pkg/otel/run.go +++ b/internal/pkg/otel/run.go @@ -2,8 +2,6 @@ // or more contributor license agreements. Licensed under the Elastic License 2.0; // you may not use this file except in compliance with the Elastic License 2.0. -//go:build !windows - package otel import ( @@ -11,14 +9,15 @@ import ( "fmt" "os" - "go.opentelemetry.io/collector/component" - "go.opentelemetry.io/collector/confmap" - "go.opentelemetry.io/collector/confmap/converter/expandconverter" "go.opentelemetry.io/collector/confmap/provider/envprovider" "go.opentelemetry.io/collector/confmap/provider/fileprovider" "go.opentelemetry.io/collector/confmap/provider/httpprovider" "go.opentelemetry.io/collector/confmap/provider/httpsprovider" "go.opentelemetry.io/collector/confmap/provider/yamlprovider" + "go.opentelemetry.io/collector/extension" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/confmap" "go.opentelemetry.io/collector/otelcol" "github.com/elastic/elastic-agent/internal/pkg/release" @@ -28,11 +27,7 @@ const buildDescription = "Elastic opentelemetry-collector distribution" func Run(ctx context.Context, stop chan bool, configFiles []string) error { fmt.Fprintln(os.Stdout, "Starting in otel mode") - settings, err := newSettings(release.Version(), configFiles) - if err != nil { - return err - } - + settings := NewSettings(release.Version(), configFiles) svc, err := otelcol.NewCollector(*settings) if err != nil { return err @@ -49,34 +44,69 @@ func Run(ctx context.Context, stop chan bool, configFiles []string) error { return svc.Run(cancelCtx) } -func newSettings(version string, configPaths []string) (*otelcol.CollectorSettings, error) { +type options struct { + resolverConfigProviders []confmap.ProviderFactory + resolverConverterFactories []confmap.ConverterFactory + extensionFactories []extension.Factory +} + +type SettingOpt func(o *options) + +func WithConfigProviderFactory(provider confmap.ProviderFactory) SettingOpt { + return func(o *options) { + o.resolverConfigProviders = append(o.resolverConfigProviders, provider) + } +} + +func WithConfigConvertorFactory(converter confmap.ConverterFactory) SettingOpt { + return func(o *options) { + o.resolverConverterFactories = append(o.resolverConverterFactories, converter) + } +} + +func WithExtensionFactory(factory extension.Factory) SettingOpt { + return func(o *options) { + o.extensionFactories = append(o.extensionFactories, factory) + } +} + +func NewSettings(version string, configPaths []string, opts ...SettingOpt) *otelcol.CollectorSettings { buildInfo := component.BuildInfo{ Command: os.Args[0], Description: buildDescription, Version: version, } + + var o options + for _, opt := range opts { + opt(&o) + } + + providerFactories := []confmap.ProviderFactory{ + fileprovider.NewFactory(), + envprovider.NewFactory(), + yamlprovider.NewFactory(), + httpprovider.NewFactory(), + httpsprovider.NewFactory(), + } + providerFactories = append(providerFactories, o.resolverConfigProviders...) + var converterFactories []confmap.ConverterFactory + converterFactories = append(converterFactories, o.resolverConverterFactories...) configProviderSettings := otelcol.ConfigProviderSettings{ ResolverSettings: confmap.ResolverSettings{ - URIs: configPaths, - ProviderFactories: []confmap.ProviderFactory{ - fileprovider.NewFactory(), - envprovider.NewFactory(), - yamlprovider.NewFactory(), - httpprovider.NewFactory(), - httpsprovider.NewFactory(), - }, - ConverterFactories: []confmap.ConverterFactory{ - expandconverter.NewFactory(), - }, + URIs: configPaths, + ProviderFactories: providerFactories, + DefaultScheme: "env", + ConverterFactories: converterFactories, }, } return &otelcol.CollectorSettings{ - Factories: components, + Factories: components(o.extensionFactories...), BuildInfo: buildInfo, ConfigProviderSettings: configProviderSettings, // we're handling DisableGracefulShutdown via the cancelCtx being passed // to the collector's Run method in the Run function DisableGracefulShutdown: true, - }, nil + } } diff --git a/internal/pkg/otel/run_test.go b/internal/pkg/otel/run_test.go index 1ab5594e82a..45adc51af85 100644 --- a/internal/pkg/otel/run_test.go +++ b/internal/pkg/otel/run_test.go @@ -2,8 +2,6 @@ // or more contributor license agreements. Licensed under the Elastic License 2.0; // you may not use this file except in compliance with the Elastic License 2.0. -//go:build !windows - package otel import ( @@ -39,8 +37,7 @@ func TestStartCollector(t *testing.T) { for _, tc := range testCases { t.Run(tc.configFile, func(t *testing.T) { configFiles := getConfigFiles(tc.configFile) - settings, err := newSettings("test", configFiles) - require.NoError(t, err) + settings := NewSettings("test", configFiles) collector, err := otelcol.NewCollector(*settings) require.NoError(t, err) diff --git a/internal/pkg/otel/validate.go b/internal/pkg/otel/validate.go index 543d9aad9f6..eedb0d07454 100644 --- a/internal/pkg/otel/validate.go +++ b/internal/pkg/otel/validate.go @@ -2,8 +2,6 @@ // or more contributor license agreements. Licensed under the Elastic License 2.0; // you may not use this file except in compliance with the Elastic License 2.0. -//go:build !windows - package otel import ( @@ -15,15 +13,10 @@ import ( ) func Validate(ctx context.Context, configPaths []string) error { - settings, err := newSettings(release.Version(), configPaths) - if err != nil { - return err - } - + settings := NewSettings(release.Version(), configPaths) col, err := otelcol.NewCollector(*settings) if err != nil { return err } return col.DryRun(ctx) - } diff --git a/internal/pkg/remote/client.go b/internal/pkg/remote/client.go index bd6ff7d26ad..3f31b25078b 100644 --- a/internal/pkg/remote/client.go +++ b/internal/pkg/remote/client.go @@ -87,7 +87,7 @@ func NewWithRawConfig(log *logger.Logger, config *config.Config, wrapper wrapper } cfg := Config{} - if err := config.Unpack(&cfg); err != nil { + if err := config.UnpackTo(&cfg); err != nil { return nil, fmt.Errorf("invalidate configuration: %w", err) } diff --git a/pkg/component/config.go b/pkg/component/config.go index fc84c423b71..3ff4ee787a4 100644 --- a/pkg/component/config.go +++ b/pkg/component/config.go @@ -45,7 +45,7 @@ func (c ComponentLimits) AsProto() *proto.ComponentLimits { } return &proto.ComponentLimits{ - GoMaxProcs: uint64(c.GoMaxProcs), + GoMaxProcs: uint64(c.GoMaxProcs), //nolint:gosec // will never be negative Source: source, } } @@ -119,8 +119,8 @@ func deDotDataStream(ds *proto.DataStream, source *structpb.Struct) (*proto.Data } // Create a temporary struct to unpack the configuration. - // Unpack correctly handles any flattened fields like - // data_stream.type. So all we need to do is to call Unpack, + // UnpackTo correctly handles any flattened fields like + // data_stream.type. So all we need to do is to call UnpackTo, // ensure the DataStream does not have a different value, // them merge them both. tmp := struct { diff --git a/pkg/control/v1/proto/control_v1.pb.go b/pkg/control/v1/proto/control_v1.pb.go index 0a79a2912cb..20321c5e0f0 100644 --- a/pkg/control/v1/proto/control_v1.pb.go +++ b/pkg/control/v1/proto/control_v1.pb.go @@ -5,7 +5,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.1 -// protoc v5.27.1 +// protoc v4.25.3 // source: control_v1.proto // proto namespace/package name is shared with elastic-agent-client diff --git a/pkg/control/v1/proto/control_v1_grpc.pb.go b/pkg/control/v1/proto/control_v1_grpc.pb.go index b0e8f51b1e8..4af413bfec7 100644 --- a/pkg/control/v1/proto/control_v1_grpc.pb.go +++ b/pkg/control/v1/proto/control_v1_grpc.pb.go @@ -5,7 +5,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.2.0 -// - protoc v5.27.1 +// - protoc v4.25.3 // source: control_v1.proto package proto diff --git a/pkg/control/v2/client/client.go b/pkg/control/v2/client/client.go index 35b9b7e6a8e..8e2b56f5842 100644 --- a/pkg/control/v2/client/client.go +++ b/pkg/control/v2/client/client.go @@ -26,6 +26,9 @@ type UnitType = cproto.UnitType // State is the state codes type State = cproto.State +// CollectorComponentStatus is the status of a collector component +type CollectorComponentStatus = cproto.CollectorComponentStatus + // AdditionalMetrics is the type for additional diagnostic requests type AdditionalMetrics = cproto.AdditionalDiagnosticRequest @@ -57,6 +60,25 @@ const ( Rollback State = cproto.State_ROLLBACK ) +const ( + // CollectorComponentStatusNone is when the collector component doesn't report a status. + CollectorComponentStatusNone CollectorComponentStatus = cproto.CollectorComponentStatus_StatusNone + // CollectorComponentStatusStarting is when the collector component is starting. + CollectorComponentStatusStarting CollectorComponentStatus = cproto.CollectorComponentStatus_StatusStarting + // CollectorComponentStatusOK is when the collector component is in good health. + CollectorComponentStatusOK CollectorComponentStatus = cproto.CollectorComponentStatus_StatusOK + // CollectorComponentStatusRecoverableError is when the collector component had an error but can recover. + CollectorComponentStatusRecoverableError CollectorComponentStatus = cproto.CollectorComponentStatus_StatusRecoverableError + // CollectorComponentStatusPermanentError is when the collector component had a permanent error. + CollectorComponentStatusPermanentError CollectorComponentStatus = cproto.CollectorComponentStatus_StatusPermanentError + // CollectorComponentStatusFatalError is when the collector component had a fatal error. + CollectorComponentStatusFatalError CollectorComponentStatus = cproto.CollectorComponentStatus_StatusFatalError + // CollectorComponentStatusStopping is when the collector component is stopping. + CollectorComponentStatusStopping CollectorComponentStatus = cproto.CollectorComponentStatus_StatusStopping + // CollectorComponentStatusStopped is when the collector component is stopped. + CollectorComponentStatusStopped CollectorComponentStatus = cproto.CollectorComponentStatus_StatusStopped +) + const ( // CPU requests additional CPU diagnostics CPU AdditionalMetrics = cproto.AdditionalDiagnosticRequest_CPU @@ -97,6 +119,14 @@ type ComponentState struct { VersionInfo ComponentVersionInfo `json:"version_info" yaml:"version_info"` } +// CollectorComponent is a state of a collector component managed by the Elastic Agent. +type CollectorComponent struct { + Status CollectorComponentStatus `json:"status" yaml:"status"` + Error string `json:"error,omitempty" yaml:"error,omitempty"` + Timestamp time.Time `json:"timestamp,omitempty" yaml:"timestamp,omitempty"` + ComponentStatusMap map[string]*CollectorComponent `json:"components,omitempty" yaml:"components,omitempty"` +} + // AgentStateInfo is the overall information about the Elastic Agent. type AgentStateInfo struct { ID string `json:"id" yaml:"id"` @@ -118,6 +148,7 @@ type AgentState struct { FleetState State `yaml:"fleet_state"` FleetMessage string `yaml:"fleet_message"` UpgradeDetails *cproto.UpgradeDetails `json:"upgrade_details,omitempty" yaml:"upgrade_details,omitempty"` + Collector *CollectorComponent `json:"collector,omitempty" yaml:"collector,omitempty"` } // DiagnosticFileResult is a diagnostic file result. @@ -516,5 +547,39 @@ func toState(res *cproto.StateResponse) (*AgentState, error) { } s.Components = append(s.Components, cs) } + if res.Collector != nil { + cs, err := collectorToState(res.Collector) + if err != nil { + return nil, err + } + s.Collector = cs + } return s, nil } + +func collectorToState(res *cproto.CollectorComponent) (*CollectorComponent, error) { + var t time.Time + var err error + if res.Timestamp != "" { + t, err = time.Parse(time.RFC3339Nano, res.Timestamp) + if err != nil { + return nil, err + } + } + cc := &CollectorComponent{ + Status: res.Status, + Error: res.Error, + Timestamp: t, + } + if res.ComponentStatusMap != nil { + cc.ComponentStatusMap = make(map[string]*CollectorComponent, len(res.ComponentStatusMap)) + for id, compStatus := range res.ComponentStatusMap { + cs, err := collectorToState(compStatus) + if err != nil { + return nil, err + } + cc.ComponentStatusMap[id] = cs + } + } + return cc, nil +} diff --git a/pkg/control/v2/cproto/control_v2.pb.go b/pkg/control/v2/cproto/control_v2.pb.go index c5958f67981..802b79aed16 100644 --- a/pkg/control/v2/cproto/control_v2.pb.go +++ b/pkg/control/v2/cproto/control_v2.pb.go @@ -5,7 +5,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.1 -// protoc v5.27.1 +// protoc v4.25.3 // source: control_v2.proto package cproto @@ -94,6 +94,71 @@ func (State) EnumDescriptor() ([]byte, []int) { return file_control_v2_proto_rawDescGZIP(), []int{0} } +// CollectorComponentStatus used for OTel collector components. +type CollectorComponentStatus int32 + +const ( + CollectorComponentStatus_StatusNone CollectorComponentStatus = 0 + CollectorComponentStatus_StatusStarting CollectorComponentStatus = 1 + CollectorComponentStatus_StatusOK CollectorComponentStatus = 2 + CollectorComponentStatus_StatusRecoverableError CollectorComponentStatus = 3 + CollectorComponentStatus_StatusPermanentError CollectorComponentStatus = 4 + CollectorComponentStatus_StatusFatalError CollectorComponentStatus = 5 + CollectorComponentStatus_StatusStopping CollectorComponentStatus = 6 + CollectorComponentStatus_StatusStopped CollectorComponentStatus = 7 +) + +// Enum value maps for CollectorComponentStatus. +var ( + CollectorComponentStatus_name = map[int32]string{ + 0: "StatusNone", + 1: "StatusStarting", + 2: "StatusOK", + 3: "StatusRecoverableError", + 4: "StatusPermanentError", + 5: "StatusFatalError", + 6: "StatusStopping", + 7: "StatusStopped", + } + CollectorComponentStatus_value = map[string]int32{ + "StatusNone": 0, + "StatusStarting": 1, + "StatusOK": 2, + "StatusRecoverableError": 3, + "StatusPermanentError": 4, + "StatusFatalError": 5, + "StatusStopping": 6, + "StatusStopped": 7, + } +) + +func (x CollectorComponentStatus) Enum() *CollectorComponentStatus { + p := new(CollectorComponentStatus) + *p = x + return p +} + +func (x CollectorComponentStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (CollectorComponentStatus) Descriptor() protoreflect.EnumDescriptor { + return file_control_v2_proto_enumTypes[1].Descriptor() +} + +func (CollectorComponentStatus) Type() protoreflect.EnumType { + return &file_control_v2_proto_enumTypes[1] +} + +func (x CollectorComponentStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use CollectorComponentStatus.Descriptor instead. +func (CollectorComponentStatus) EnumDescriptor() ([]byte, []int) { + return file_control_v2_proto_rawDescGZIP(), []int{1} +} + // Unit Type running inside a component. type UnitType int32 @@ -125,11 +190,11 @@ func (x UnitType) String() string { } func (UnitType) Descriptor() protoreflect.EnumDescriptor { - return file_control_v2_proto_enumTypes[1].Descriptor() + return file_control_v2_proto_enumTypes[2].Descriptor() } func (UnitType) Type() protoreflect.EnumType { - return &file_control_v2_proto_enumTypes[1] + return &file_control_v2_proto_enumTypes[2] } func (x UnitType) Number() protoreflect.EnumNumber { @@ -138,7 +203,7 @@ func (x UnitType) Number() protoreflect.EnumNumber { // Deprecated: Use UnitType.Descriptor instead. func (UnitType) EnumDescriptor() ([]byte, []int) { - return file_control_v2_proto_rawDescGZIP(), []int{1} + return file_control_v2_proto_rawDescGZIP(), []int{2} } // Action status codes for restart and upgrade response. @@ -174,11 +239,11 @@ func (x ActionStatus) String() string { } func (ActionStatus) Descriptor() protoreflect.EnumDescriptor { - return file_control_v2_proto_enumTypes[2].Descriptor() + return file_control_v2_proto_enumTypes[3].Descriptor() } func (ActionStatus) Type() protoreflect.EnumType { - return &file_control_v2_proto_enumTypes[2] + return &file_control_v2_proto_enumTypes[3] } func (x ActionStatus) Number() protoreflect.EnumNumber { @@ -187,7 +252,7 @@ func (x ActionStatus) Number() protoreflect.EnumNumber { // Deprecated: Use ActionStatus.Descriptor instead. func (ActionStatus) EnumDescriptor() ([]byte, []int) { - return file_control_v2_proto_rawDescGZIP(), []int{2} + return file_control_v2_proto_rawDescGZIP(), []int{3} } // pprof endpoint that can be requested. @@ -242,11 +307,11 @@ func (x PprofOption) String() string { } func (PprofOption) Descriptor() protoreflect.EnumDescriptor { - return file_control_v2_proto_enumTypes[3].Descriptor() + return file_control_v2_proto_enumTypes[4].Descriptor() } func (PprofOption) Type() protoreflect.EnumType { - return &file_control_v2_proto_enumTypes[3] + return &file_control_v2_proto_enumTypes[4] } func (x PprofOption) Number() protoreflect.EnumNumber { @@ -255,7 +320,7 @@ func (x PprofOption) Number() protoreflect.EnumNumber { // Deprecated: Use PprofOption.Descriptor instead. func (PprofOption) EnumDescriptor() ([]byte, []int) { - return file_control_v2_proto_rawDescGZIP(), []int{3} + return file_control_v2_proto_rawDescGZIP(), []int{4} } // DiagnosticAgentRequestAdditional is an enum of additional diagnostic metrics that can be requested from Elastic Agent. @@ -289,11 +354,11 @@ func (x AdditionalDiagnosticRequest) String() string { } func (AdditionalDiagnosticRequest) Descriptor() protoreflect.EnumDescriptor { - return file_control_v2_proto_enumTypes[4].Descriptor() + return file_control_v2_proto_enumTypes[5].Descriptor() } func (AdditionalDiagnosticRequest) Type() protoreflect.EnumType { - return &file_control_v2_proto_enumTypes[4] + return &file_control_v2_proto_enumTypes[5] } func (x AdditionalDiagnosticRequest) Number() protoreflect.EnumNumber { @@ -302,7 +367,7 @@ func (x AdditionalDiagnosticRequest) Number() protoreflect.EnumNumber { // Deprecated: Use AdditionalDiagnosticRequest.Descriptor instead. func (AdditionalDiagnosticRequest) EnumDescriptor() ([]byte, []int) { - return file_control_v2_proto_rawDescGZIP(), []int{4} + return file_control_v2_proto_rawDescGZIP(), []int{5} } // Empty message. @@ -990,6 +1055,82 @@ func (x *StateAgentInfo) GetIsManaged() bool { return false } +// CollectorComponent is the status of an OTel collector component. +type CollectorComponent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Status of the component. + Status CollectorComponentStatus `protobuf:"varint,1,opt,name=status,proto3,enum=cproto.CollectorComponentStatus" json:"status,omitempty"` + // Error is set to the reported error. + Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` + // Timestamp of status. + Timestamp string `protobuf:"bytes,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + // Status information for sub-components of this component. + ComponentStatusMap map[string]*CollectorComponent `protobuf:"bytes,4,rep,name=ComponentStatusMap,proto3" json:"ComponentStatusMap,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *CollectorComponent) Reset() { + *x = CollectorComponent{} + if protoimpl.UnsafeEnabled { + mi := &file_control_v2_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CollectorComponent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CollectorComponent) ProtoMessage() {} + +func (x *CollectorComponent) ProtoReflect() protoreflect.Message { + mi := &file_control_v2_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CollectorComponent.ProtoReflect.Descriptor instead. +func (*CollectorComponent) Descriptor() ([]byte, []int) { + return file_control_v2_proto_rawDescGZIP(), []int{9} +} + +func (x *CollectorComponent) GetStatus() CollectorComponentStatus { + if x != nil { + return x.Status + } + return CollectorComponentStatus_StatusNone +} + +func (x *CollectorComponent) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +func (x *CollectorComponent) GetTimestamp() string { + if x != nil { + return x.Timestamp + } + return "" +} + +func (x *CollectorComponent) GetComponentStatusMap() map[string]*CollectorComponent { + if x != nil { + return x.ComponentStatusMap + } + return nil +} + // StateResponse is the current state of Elastic Agent. // Next unused id: 8 type StateResponse struct { @@ -1011,12 +1152,14 @@ type StateResponse struct { Components []*ComponentState `protobuf:"bytes,4,rep,name=components,proto3" json:"components,omitempty"` // Upgrade details UpgradeDetails *UpgradeDetails `protobuf:"bytes,7,opt,name=upgrade_details,json=upgradeDetails,proto3" json:"upgrade_details,omitempty"` + // OTel collector component status information. + Collector *CollectorComponent `protobuf:"bytes,8,opt,name=collector,proto3" json:"collector,omitempty"` } func (x *StateResponse) Reset() { *x = StateResponse{} if protoimpl.UnsafeEnabled { - mi := &file_control_v2_proto_msgTypes[9] + mi := &file_control_v2_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1029,7 +1172,7 @@ func (x *StateResponse) String() string { func (*StateResponse) ProtoMessage() {} func (x *StateResponse) ProtoReflect() protoreflect.Message { - mi := &file_control_v2_proto_msgTypes[9] + mi := &file_control_v2_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1042,7 +1185,7 @@ func (x *StateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StateResponse.ProtoReflect.Descriptor instead. func (*StateResponse) Descriptor() ([]byte, []int) { - return file_control_v2_proto_rawDescGZIP(), []int{9} + return file_control_v2_proto_rawDescGZIP(), []int{10} } func (x *StateResponse) GetInfo() *StateAgentInfo { @@ -1094,6 +1237,13 @@ func (x *StateResponse) GetUpgradeDetails() *UpgradeDetails { return nil } +func (x *StateResponse) GetCollector() *CollectorComponent { + if x != nil { + return x.Collector + } + return nil +} + // UpgradeDetails captures the details of an ongoing Agent upgrade. type UpgradeDetails struct { state protoimpl.MessageState @@ -1113,7 +1263,7 @@ type UpgradeDetails struct { func (x *UpgradeDetails) Reset() { *x = UpgradeDetails{} if protoimpl.UnsafeEnabled { - mi := &file_control_v2_proto_msgTypes[10] + mi := &file_control_v2_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1126,7 +1276,7 @@ func (x *UpgradeDetails) String() string { func (*UpgradeDetails) ProtoMessage() {} func (x *UpgradeDetails) ProtoReflect() protoreflect.Message { - mi := &file_control_v2_proto_msgTypes[10] + mi := &file_control_v2_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1139,7 +1289,7 @@ func (x *UpgradeDetails) ProtoReflect() protoreflect.Message { // Deprecated: Use UpgradeDetails.ProtoReflect.Descriptor instead. func (*UpgradeDetails) Descriptor() ([]byte, []int) { - return file_control_v2_proto_rawDescGZIP(), []int{10} + return file_control_v2_proto_rawDescGZIP(), []int{11} } func (x *UpgradeDetails) GetTargetVersion() string { @@ -1199,7 +1349,7 @@ type UpgradeDetailsMetadata struct { func (x *UpgradeDetailsMetadata) Reset() { *x = UpgradeDetailsMetadata{} if protoimpl.UnsafeEnabled { - mi := &file_control_v2_proto_msgTypes[11] + mi := &file_control_v2_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1212,7 +1362,7 @@ func (x *UpgradeDetailsMetadata) String() string { func (*UpgradeDetailsMetadata) ProtoMessage() {} func (x *UpgradeDetailsMetadata) ProtoReflect() protoreflect.Message { - mi := &file_control_v2_proto_msgTypes[11] + mi := &file_control_v2_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1225,7 +1375,7 @@ func (x *UpgradeDetailsMetadata) ProtoReflect() protoreflect.Message { // Deprecated: Use UpgradeDetailsMetadata.ProtoReflect.Descriptor instead. func (*UpgradeDetailsMetadata) Descriptor() ([]byte, []int) { - return file_control_v2_proto_rawDescGZIP(), []int{11} + return file_control_v2_proto_rawDescGZIP(), []int{12} } func (x *UpgradeDetailsMetadata) GetScheduledAt() string { @@ -1293,7 +1443,7 @@ type DiagnosticFileResult struct { func (x *DiagnosticFileResult) Reset() { *x = DiagnosticFileResult{} if protoimpl.UnsafeEnabled { - mi := &file_control_v2_proto_msgTypes[12] + mi := &file_control_v2_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1306,7 +1456,7 @@ func (x *DiagnosticFileResult) String() string { func (*DiagnosticFileResult) ProtoMessage() {} func (x *DiagnosticFileResult) ProtoReflect() protoreflect.Message { - mi := &file_control_v2_proto_msgTypes[12] + mi := &file_control_v2_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1319,7 +1469,7 @@ func (x *DiagnosticFileResult) ProtoReflect() protoreflect.Message { // Deprecated: Use DiagnosticFileResult.ProtoReflect.Descriptor instead. func (*DiagnosticFileResult) Descriptor() ([]byte, []int) { - return file_control_v2_proto_rawDescGZIP(), []int{12} + return file_control_v2_proto_rawDescGZIP(), []int{13} } func (x *DiagnosticFileResult) GetName() string { @@ -1376,7 +1526,7 @@ type DiagnosticAgentRequest struct { func (x *DiagnosticAgentRequest) Reset() { *x = DiagnosticAgentRequest{} if protoimpl.UnsafeEnabled { - mi := &file_control_v2_proto_msgTypes[13] + mi := &file_control_v2_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1389,7 +1539,7 @@ func (x *DiagnosticAgentRequest) String() string { func (*DiagnosticAgentRequest) ProtoMessage() {} func (x *DiagnosticAgentRequest) ProtoReflect() protoreflect.Message { - mi := &file_control_v2_proto_msgTypes[13] + mi := &file_control_v2_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1402,7 +1552,7 @@ func (x *DiagnosticAgentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DiagnosticAgentRequest.ProtoReflect.Descriptor instead. func (*DiagnosticAgentRequest) Descriptor() ([]byte, []int) { - return file_control_v2_proto_rawDescGZIP(), []int{13} + return file_control_v2_proto_rawDescGZIP(), []int{14} } func (x *DiagnosticAgentRequest) GetAdditionalMetrics() []AdditionalDiagnosticRequest { @@ -1425,7 +1575,7 @@ type DiagnosticComponentsRequest struct { func (x *DiagnosticComponentsRequest) Reset() { *x = DiagnosticComponentsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_control_v2_proto_msgTypes[14] + mi := &file_control_v2_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1438,7 +1588,7 @@ func (x *DiagnosticComponentsRequest) String() string { func (*DiagnosticComponentsRequest) ProtoMessage() {} func (x *DiagnosticComponentsRequest) ProtoReflect() protoreflect.Message { - mi := &file_control_v2_proto_msgTypes[14] + mi := &file_control_v2_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1451,7 +1601,7 @@ func (x *DiagnosticComponentsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DiagnosticComponentsRequest.ProtoReflect.Descriptor instead. func (*DiagnosticComponentsRequest) Descriptor() ([]byte, []int) { - return file_control_v2_proto_rawDescGZIP(), []int{14} + return file_control_v2_proto_rawDescGZIP(), []int{15} } func (x *DiagnosticComponentsRequest) GetComponents() []*DiagnosticComponentRequest { @@ -1481,7 +1631,7 @@ type DiagnosticComponentRequest struct { func (x *DiagnosticComponentRequest) Reset() { *x = DiagnosticComponentRequest{} if protoimpl.UnsafeEnabled { - mi := &file_control_v2_proto_msgTypes[15] + mi := &file_control_v2_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1494,7 +1644,7 @@ func (x *DiagnosticComponentRequest) String() string { func (*DiagnosticComponentRequest) ProtoMessage() {} func (x *DiagnosticComponentRequest) ProtoReflect() protoreflect.Message { - mi := &file_control_v2_proto_msgTypes[15] + mi := &file_control_v2_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1507,7 +1657,7 @@ func (x *DiagnosticComponentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DiagnosticComponentRequest.ProtoReflect.Descriptor instead. func (*DiagnosticComponentRequest) Descriptor() ([]byte, []int) { - return file_control_v2_proto_rawDescGZIP(), []int{15} + return file_control_v2_proto_rawDescGZIP(), []int{16} } func (x *DiagnosticComponentRequest) GetComponentId() string { @@ -1530,7 +1680,7 @@ type DiagnosticAgentResponse struct { func (x *DiagnosticAgentResponse) Reset() { *x = DiagnosticAgentResponse{} if protoimpl.UnsafeEnabled { - mi := &file_control_v2_proto_msgTypes[16] + mi := &file_control_v2_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1543,7 +1693,7 @@ func (x *DiagnosticAgentResponse) String() string { func (*DiagnosticAgentResponse) ProtoMessage() {} func (x *DiagnosticAgentResponse) ProtoReflect() protoreflect.Message { - mi := &file_control_v2_proto_msgTypes[16] + mi := &file_control_v2_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1556,7 +1706,7 @@ func (x *DiagnosticAgentResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DiagnosticAgentResponse.ProtoReflect.Descriptor instead. func (*DiagnosticAgentResponse) Descriptor() ([]byte, []int) { - return file_control_v2_proto_rawDescGZIP(), []int{16} + return file_control_v2_proto_rawDescGZIP(), []int{17} } func (x *DiagnosticAgentResponse) GetResults() []*DiagnosticFileResult { @@ -1583,7 +1733,7 @@ type DiagnosticUnitRequest struct { func (x *DiagnosticUnitRequest) Reset() { *x = DiagnosticUnitRequest{} if protoimpl.UnsafeEnabled { - mi := &file_control_v2_proto_msgTypes[17] + mi := &file_control_v2_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1596,7 +1746,7 @@ func (x *DiagnosticUnitRequest) String() string { func (*DiagnosticUnitRequest) ProtoMessage() {} func (x *DiagnosticUnitRequest) ProtoReflect() protoreflect.Message { - mi := &file_control_v2_proto_msgTypes[17] + mi := &file_control_v2_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1609,7 +1759,7 @@ func (x *DiagnosticUnitRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DiagnosticUnitRequest.ProtoReflect.Descriptor instead. func (*DiagnosticUnitRequest) Descriptor() ([]byte, []int) { - return file_control_v2_proto_rawDescGZIP(), []int{17} + return file_control_v2_proto_rawDescGZIP(), []int{18} } func (x *DiagnosticUnitRequest) GetComponentId() string { @@ -1646,7 +1796,7 @@ type DiagnosticUnitsRequest struct { func (x *DiagnosticUnitsRequest) Reset() { *x = DiagnosticUnitsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_control_v2_proto_msgTypes[18] + mi := &file_control_v2_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1659,7 +1809,7 @@ func (x *DiagnosticUnitsRequest) String() string { func (*DiagnosticUnitsRequest) ProtoMessage() {} func (x *DiagnosticUnitsRequest) ProtoReflect() protoreflect.Message { - mi := &file_control_v2_proto_msgTypes[18] + mi := &file_control_v2_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1672,7 +1822,7 @@ func (x *DiagnosticUnitsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DiagnosticUnitsRequest.ProtoReflect.Descriptor instead. func (*DiagnosticUnitsRequest) Descriptor() ([]byte, []int) { - return file_control_v2_proto_rawDescGZIP(), []int{18} + return file_control_v2_proto_rawDescGZIP(), []int{19} } func (x *DiagnosticUnitsRequest) GetUnits() []*DiagnosticUnitRequest { @@ -1703,7 +1853,7 @@ type DiagnosticUnitResponse struct { func (x *DiagnosticUnitResponse) Reset() { *x = DiagnosticUnitResponse{} if protoimpl.UnsafeEnabled { - mi := &file_control_v2_proto_msgTypes[19] + mi := &file_control_v2_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1716,7 +1866,7 @@ func (x *DiagnosticUnitResponse) String() string { func (*DiagnosticUnitResponse) ProtoMessage() {} func (x *DiagnosticUnitResponse) ProtoReflect() protoreflect.Message { - mi := &file_control_v2_proto_msgTypes[19] + mi := &file_control_v2_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1729,7 +1879,7 @@ func (x *DiagnosticUnitResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DiagnosticUnitResponse.ProtoReflect.Descriptor instead. func (*DiagnosticUnitResponse) Descriptor() ([]byte, []int) { - return file_control_v2_proto_rawDescGZIP(), []int{19} + return file_control_v2_proto_rawDescGZIP(), []int{20} } func (x *DiagnosticUnitResponse) GetComponentId() string { @@ -1784,7 +1934,7 @@ type DiagnosticComponentResponse struct { func (x *DiagnosticComponentResponse) Reset() { *x = DiagnosticComponentResponse{} if protoimpl.UnsafeEnabled { - mi := &file_control_v2_proto_msgTypes[20] + mi := &file_control_v2_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1797,7 +1947,7 @@ func (x *DiagnosticComponentResponse) String() string { func (*DiagnosticComponentResponse) ProtoMessage() {} func (x *DiagnosticComponentResponse) ProtoReflect() protoreflect.Message { - mi := &file_control_v2_proto_msgTypes[20] + mi := &file_control_v2_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1810,7 +1960,7 @@ func (x *DiagnosticComponentResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DiagnosticComponentResponse.ProtoReflect.Descriptor instead. func (*DiagnosticComponentResponse) Descriptor() ([]byte, []int) { - return file_control_v2_proto_rawDescGZIP(), []int{20} + return file_control_v2_proto_rawDescGZIP(), []int{21} } func (x *DiagnosticComponentResponse) GetComponentId() string { @@ -1847,7 +1997,7 @@ type DiagnosticUnitsResponse struct { func (x *DiagnosticUnitsResponse) Reset() { *x = DiagnosticUnitsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_control_v2_proto_msgTypes[21] + mi := &file_control_v2_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1860,7 +2010,7 @@ func (x *DiagnosticUnitsResponse) String() string { func (*DiagnosticUnitsResponse) ProtoMessage() {} func (x *DiagnosticUnitsResponse) ProtoReflect() protoreflect.Message { - mi := &file_control_v2_proto_msgTypes[21] + mi := &file_control_v2_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1873,7 +2023,7 @@ func (x *DiagnosticUnitsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DiagnosticUnitsResponse.ProtoReflect.Descriptor instead. func (*DiagnosticUnitsResponse) Descriptor() ([]byte, []int) { - return file_control_v2_proto_rawDescGZIP(), []int{21} + return file_control_v2_proto_rawDescGZIP(), []int{22} } func (x *DiagnosticUnitsResponse) GetUnits() []*DiagnosticUnitResponse { @@ -1896,7 +2046,7 @@ type ConfigureRequest struct { func (x *ConfigureRequest) Reset() { *x = ConfigureRequest{} if protoimpl.UnsafeEnabled { - mi := &file_control_v2_proto_msgTypes[22] + mi := &file_control_v2_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1909,7 +2059,7 @@ func (x *ConfigureRequest) String() string { func (*ConfigureRequest) ProtoMessage() {} func (x *ConfigureRequest) ProtoReflect() protoreflect.Message { - mi := &file_control_v2_proto_msgTypes[22] + mi := &file_control_v2_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1922,7 +2072,7 @@ func (x *ConfigureRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ConfigureRequest.ProtoReflect.Descriptor instead. func (*ConfigureRequest) Descriptor() ([]byte, []int) { - return file_control_v2_proto_rawDescGZIP(), []int{22} + return file_control_v2_proto_rawDescGZIP(), []int{23} } func (x *ConfigureRequest) GetConfig() string { @@ -2021,204 +2171,240 @@ var file_control_v2_proto_rawDesc = []byte{ 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x75, 0x6e, 0x70, 0x72, 0x69, 0x76, 0x69, 0x6c, 0x65, 0x67, 0x65, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x73, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, - 0x22, 0xc6, 0x02, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x16, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x41, - 0x67, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x23, - 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, - 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, - 0x61, 0x74, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2d, 0x0a, - 0x0a, 0x66, 0x6c, 0x65, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x0d, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, - 0x52, 0x0a, 0x66, 0x6c, 0x65, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x0c, - 0x66, 0x6c, 0x65, 0x65, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0c, 0x66, 0x6c, 0x65, 0x65, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x12, 0x36, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x04, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, - 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0a, 0x63, 0x6f, - 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x3f, 0x0a, 0x0f, 0x75, 0x70, 0x67, 0x72, - 0x61, 0x64, 0x65, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55, 0x70, 0x67, 0x72, 0x61, - 0x64, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x0e, 0x75, 0x70, 0x67, 0x72, 0x61, - 0x64, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0xa6, 0x01, 0x0a, 0x0e, 0x55, 0x70, - 0x67, 0x72, 0x61, 0x64, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x25, 0x0a, 0x0e, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x3a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x22, 0xef, 0x01, 0x0a, 0x16, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x44, 0x65, - 0x74, 0x61, 0x69, 0x6c, 0x73, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x21, 0x0a, - 0x0c, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x64, 0x41, 0x74, - 0x12, 0x29, 0x0a, 0x10, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x70, 0x65, 0x72, - 0x63, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, 0x52, 0x0f, 0x64, 0x6f, 0x77, 0x6e, - 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x66, - 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1b, - 0x0a, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x73, 0x67, 0x12, 0x26, 0x0a, 0x0f, 0x72, - 0x65, 0x74, 0x72, 0x79, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x74, 0x72, 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x4d, 0x73, 0x67, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x74, 0x72, 0x79, 0x5f, 0x75, 0x6e, 0x74, - 0x69, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x74, 0x72, 0x79, 0x55, - 0x6e, 0x74, 0x69, 0x6c, 0x22, 0xdf, 0x01, 0x0a, 0x14, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, - 0x74, 0x69, 0x63, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x12, 0x0a, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, - 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, 0x38, 0x0a, 0x09, - 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x67, 0x65, 0x6e, - 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x22, 0x6c, 0x0a, 0x16, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, - 0x73, 0x74, 0x69, 0x63, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x52, 0x0a, 0x12, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x6d, - 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x63, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, - 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x52, 0x11, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x4d, 0x65, 0x74, - 0x72, 0x69, 0x63, 0x73, 0x22, 0xb5, 0x01, 0x0a, 0x1b, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, - 0x74, 0x69, 0x63, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, - 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x43, 0x6f, 0x6d, 0x70, - 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x0a, 0x63, 0x6f, - 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x52, 0x0a, 0x12, 0x61, 0x64, 0x64, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x02, - 0x20, 0x03, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x64, - 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, - 0x69, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x11, 0x61, 0x64, 0x64, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x22, 0x3f, 0x0a, 0x1a, - 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, - 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, - 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x51, 0x0a, - 0x17, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x41, 0x67, 0x65, 0x6e, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x46, 0x69, 0x6c, - 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, - 0x22, 0x82, 0x01, 0x0a, 0x15, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x55, - 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, - 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x2d, 0x0a, - 0x09, 0x75, 0x6e, 0x69, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x10, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55, 0x6e, 0x69, 0x74, 0x54, 0x79, - 0x70, 0x65, 0x52, 0x08, 0x75, 0x6e, 0x69, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x07, - 0x75, 0x6e, 0x69, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, - 0x6e, 0x69, 0x74, 0x49, 0x64, 0x22, 0x4d, 0x0a, 0x16, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, - 0x74, 0x69, 0x63, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x33, 0x0a, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, + 0x22, 0xc9, 0x02, 0x0a, 0x12, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x43, 0x6f, + 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x12, 0x38, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, + 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x62, 0x0a, 0x12, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, + 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x4d, 0x61, 0x70, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x32, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, + 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x4d, 0x61, 0x70, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x12, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x4d, 0x61, 0x70, 0x1a, 0x61, 0x0a, 0x17, 0x43, 0x6f, 0x6d, + 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x4d, 0x61, 0x70, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, + 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, + 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x80, 0x03, 0x0a, + 0x0d, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, + 0x0a, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, + 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x23, 0x0a, 0x05, 0x73, 0x74, + 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x63, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, + 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2d, 0x0a, 0x0a, 0x66, 0x6c, 0x65, + 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, + 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0a, 0x66, 0x6c, + 0x65, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x66, 0x6c, 0x65, 0x65, + 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x66, 0x6c, 0x65, 0x65, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x36, 0x0a, 0x0a, + 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x16, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, + 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, + 0x65, 0x6e, 0x74, 0x73, 0x12, 0x3f, 0x0a, 0x0f, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x5f, + 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, + 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x44, 0x65, + 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x0e, 0x75, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x44, 0x65, + 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x38, 0x0a, 0x09, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x43, 0x6f, 0x6d, 0x70, 0x6f, + 0x6e, 0x65, 0x6e, 0x74, 0x52, 0x09, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, + 0xa6, 0x01, 0x0a, 0x0e, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, + 0x6c, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, + 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, + 0x1b, 0x0a, 0x09, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x3a, 0x0a, 0x08, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, + 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x44, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0xef, 0x01, 0x0a, 0x16, 0x55, 0x70, 0x67, + 0x72, 0x61, 0x64, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x4d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x64, + 0x5f, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x63, 0x68, 0x65, 0x64, + 0x75, 0x6c, 0x65, 0x64, 0x41, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, + 0x61, 0x64, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, + 0x52, 0x0f, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, + 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x73, + 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x73, + 0x67, 0x12, 0x26, 0x0a, 0x0f, 0x72, 0x65, 0x74, 0x72, 0x79, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x74, 0x72, + 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x73, 0x67, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x74, + 0x72, 0x79, 0x5f, 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x72, 0x65, 0x74, 0x72, 0x79, 0x55, 0x6e, 0x74, 0x69, 0x6c, 0x22, 0xdf, 0x01, 0x0a, 0x14, 0x44, + 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, + 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x09, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x22, 0x6c, 0x0a, 0x16, + 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x52, 0x0a, 0x12, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x64, 0x64, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x11, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x61, 0x6c, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x22, 0xb5, 0x01, 0x0a, 0x1b, 0x44, + 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, + 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x0a, 0x63, 0x6f, + 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, - 0x69, 0x63, 0x55, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x75, - 0x6e, 0x69, 0x74, 0x73, 0x22, 0xd1, 0x01, 0x0a, 0x16, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, - 0x74, 0x69, 0x63, 0x55, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, - 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x09, 0x75, 0x6e, 0x69, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55, - 0x6e, 0x69, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x75, 0x6e, 0x69, 0x74, 0x54, 0x79, 0x70, - 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x6e, 0x69, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x75, 0x6e, 0x69, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x12, 0x36, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, - 0x6f, 0x73, 0x74, 0x69, 0x63, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, - 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x8e, 0x01, 0x0a, 0x1b, 0x44, 0x69, 0x61, - 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x70, - 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, - 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, - 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, - 0x72, 0x12, 0x36, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x05, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, - 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x4f, 0x0a, 0x17, 0x44, 0x69, 0x61, - 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, - 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x55, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x52, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x22, 0x2a, 0x0a, 0x10, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, - 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2a, 0x85, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, - 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x54, 0x41, 0x52, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x0f, - 0x0a, 0x0b, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x55, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, - 0x0b, 0x0a, 0x07, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, - 0x44, 0x45, 0x47, 0x52, 0x41, 0x44, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, - 0x49, 0x4c, 0x45, 0x44, 0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x54, 0x4f, 0x50, 0x50, 0x49, - 0x4e, 0x47, 0x10, 0x05, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x54, 0x4f, 0x50, 0x50, 0x45, 0x44, 0x10, - 0x06, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x50, 0x47, 0x52, 0x41, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x07, - 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x4f, 0x4c, 0x4c, 0x42, 0x41, 0x43, 0x4b, 0x10, 0x08, 0x2a, 0x21, - 0x0a, 0x08, 0x55, 0x6e, 0x69, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x49, 0x4e, - 0x50, 0x55, 0x54, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4f, 0x55, 0x54, 0x50, 0x55, 0x54, 0x10, - 0x01, 0x2a, 0x28, 0x0a, 0x0c, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x0b, - 0x0a, 0x07, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, 0x45, 0x10, 0x01, 0x2a, 0x7f, 0x0a, 0x0b, 0x50, - 0x70, 0x72, 0x6f, 0x66, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x4c, - 0x4c, 0x4f, 0x43, 0x53, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x42, 0x4c, 0x4f, 0x43, 0x4b, 0x10, - 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4d, 0x44, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x02, 0x12, 0x0d, - 0x0a, 0x09, 0x47, 0x4f, 0x52, 0x4f, 0x55, 0x54, 0x49, 0x4e, 0x45, 0x10, 0x03, 0x12, 0x08, 0x0a, - 0x04, 0x48, 0x45, 0x41, 0x50, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x4d, 0x55, 0x54, 0x45, 0x58, - 0x10, 0x05, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x52, 0x4f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x06, 0x12, - 0x10, 0x0a, 0x0c, 0x54, 0x48, 0x52, 0x45, 0x41, 0x44, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, - 0x07, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x08, 0x2a, 0x30, 0x0a, 0x1b, - 0x41, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, - 0x73, 0x74, 0x69, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x07, 0x0a, 0x03, 0x43, - 0x50, 0x55, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x43, 0x4f, 0x4e, 0x4e, 0x10, 0x01, 0x32, 0xdf, - 0x04, 0x0a, 0x13, 0x45, 0x6c, 0x61, 0x73, 0x74, 0x69, 0x63, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, - 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x31, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x0d, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x1a, 0x17, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x05, 0x53, 0x74, 0x61, - 0x74, 0x65, 0x12, 0x0d, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x1a, 0x15, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x0a, 0x53, 0x74, 0x61, 0x74, - 0x65, 0x57, 0x61, 0x74, 0x63, 0x68, 0x12, 0x0d, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, - 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x31, - 0x0a, 0x07, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x0d, 0x2e, 0x63, 0x70, 0x72, 0x6f, + 0x69, 0x63, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x52, 0x0a, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x52, + 0x0a, 0x12, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x6d, 0x65, 0x74, + 0x72, 0x69, 0x63, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x63, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x44, 0x69, + 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, + 0x11, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x4d, 0x65, 0x74, 0x72, 0x69, + 0x63, 0x73, 0x22, 0x3f, 0x0a, 0x1a, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, + 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, + 0x74, 0x49, 0x64, 0x22, 0x51, 0x0a, 0x17, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, + 0x63, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, + 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x1c, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, + 0x74, 0x69, 0x63, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x82, 0x01, 0x0a, 0x15, 0x44, 0x69, 0x61, 0x67, 0x6e, + 0x6f, 0x73, 0x74, 0x69, 0x63, 0x55, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, + 0x74, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x09, 0x75, 0x6e, 0x69, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x55, 0x6e, 0x69, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x75, 0x6e, 0x69, 0x74, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x6e, 0x69, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x6e, 0x69, 0x74, 0x49, 0x64, 0x22, 0x4d, 0x0a, 0x16, 0x44, + 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x33, 0x0a, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, + 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x55, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x52, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, 0x22, 0xd1, 0x01, 0x0a, 0x16, 0x44, + 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x55, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, + 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6d, + 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x09, 0x75, 0x6e, 0x69, 0x74, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x63, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55, 0x6e, 0x69, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x75, + 0x6e, 0x69, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x6e, 0x69, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x6e, 0x69, 0x74, 0x49, 0x64, + 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x36, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x46, 0x69, 0x6c, 0x65, 0x52, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x8e, + 0x01, 0x0a, 0x1b, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x43, 0x6f, 0x6d, + 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, + 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x49, + 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x36, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x46, 0x69, 0x6c, 0x65, + 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, + 0x4f, 0x0a, 0x17, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x55, 0x6e, 0x69, + 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x05, 0x75, 0x6e, + 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x63, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x55, 0x6e, 0x69, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x05, 0x75, 0x6e, 0x69, 0x74, 0x73, + 0x22, 0x2a, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2a, 0x85, 0x01, 0x0a, + 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x54, 0x41, 0x52, 0x54, 0x49, + 0x4e, 0x47, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x55, 0x52, + 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, + 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x45, 0x47, 0x52, 0x41, 0x44, 0x45, 0x44, 0x10, 0x03, + 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, + 0x53, 0x54, 0x4f, 0x50, 0x50, 0x49, 0x4e, 0x47, 0x10, 0x05, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x54, + 0x4f, 0x50, 0x50, 0x45, 0x44, 0x10, 0x06, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x50, 0x47, 0x52, 0x41, + 0x44, 0x49, 0x4e, 0x47, 0x10, 0x07, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x4f, 0x4c, 0x4c, 0x42, 0x41, + 0x43, 0x4b, 0x10, 0x08, 0x2a, 0xbf, 0x01, 0x0a, 0x18, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x4e, 0x6f, 0x6e, 0x65, 0x10, + 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x53, 0x74, 0x61, 0x72, 0x74, + 0x69, 0x6e, 0x67, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x4f, + 0x4b, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x63, + 0x6f, 0x76, 0x65, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0x03, 0x12, + 0x18, 0x0a, 0x14, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x50, 0x65, 0x72, 0x6d, 0x61, 0x6e, 0x65, + 0x6e, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0x04, 0x12, 0x14, 0x0a, 0x10, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x46, 0x61, 0x74, 0x61, 0x6c, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x10, 0x05, 0x12, + 0x12, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x53, 0x74, 0x6f, 0x70, 0x70, 0x69, 0x6e, + 0x67, 0x10, 0x06, 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x53, 0x74, 0x6f, + 0x70, 0x70, 0x65, 0x64, 0x10, 0x07, 0x2a, 0x21, 0x0a, 0x08, 0x55, 0x6e, 0x69, 0x74, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x49, 0x4e, 0x50, 0x55, 0x54, 0x10, 0x00, 0x12, 0x0a, 0x0a, + 0x06, 0x4f, 0x55, 0x54, 0x50, 0x55, 0x54, 0x10, 0x01, 0x2a, 0x28, 0x0a, 0x0c, 0x41, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x55, 0x43, + 0x43, 0x45, 0x53, 0x53, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x41, 0x49, 0x4c, 0x55, 0x52, + 0x45, 0x10, 0x01, 0x2a, 0x7f, 0x0a, 0x0b, 0x50, 0x70, 0x72, 0x6f, 0x66, 0x4f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x4c, 0x4c, 0x4f, 0x43, 0x53, 0x10, 0x00, 0x12, 0x09, + 0x0a, 0x05, 0x42, 0x4c, 0x4f, 0x43, 0x4b, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4d, 0x44, + 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x47, 0x4f, 0x52, 0x4f, 0x55, 0x54, + 0x49, 0x4e, 0x45, 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x45, 0x41, 0x50, 0x10, 0x04, 0x12, + 0x09, 0x0a, 0x05, 0x4d, 0x55, 0x54, 0x45, 0x58, 0x10, 0x05, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x52, + 0x4f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x06, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x48, 0x52, 0x45, 0x41, + 0x44, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, 0x07, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, + 0x43, 0x45, 0x10, 0x08, 0x2a, 0x30, 0x0a, 0x1b, 0x41, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x61, 0x6c, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x07, 0x0a, 0x03, 0x43, 0x50, 0x55, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, + 0x43, 0x4f, 0x4e, 0x4e, 0x10, 0x01, 0x32, 0xdf, 0x04, 0x0a, 0x13, 0x45, 0x6c, 0x61, 0x73, 0x74, + 0x69, 0x63, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x12, 0x31, + 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0d, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x17, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x3a, 0x0a, 0x07, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x12, 0x16, 0x2e, 0x63, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55, 0x70, - 0x67, 0x72, 0x61, 0x64, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, - 0x0f, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x41, 0x67, 0x65, 0x6e, 0x74, - 0x12, 0x1e, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, - 0x73, 0x74, 0x69, 0x63, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1f, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, - 0x73, 0x74, 0x69, 0x63, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x53, 0x0a, 0x0f, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x55, - 0x6e, 0x69, 0x74, 0x73, 0x12, 0x1e, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, - 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, - 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x55, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x62, 0x0a, 0x14, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, - 0x73, 0x74, 0x69, 0x63, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x23, - 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, - 0x69, 0x63, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, - 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x34, 0x0a, 0x09, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x12, 0x18, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x0d, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x42, 0x29, 0x5a, 0x24, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6b, 0x67, - 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x2f, 0x76, - 0x32, 0x2f, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0xf8, 0x01, 0x01, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x6f, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x2d, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0d, 0x2e, 0x63, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x63, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x34, 0x0a, 0x0a, 0x53, 0x74, 0x61, 0x74, 0x65, 0x57, 0x61, 0x74, 0x63, 0x68, 0x12, 0x0d, + 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x15, 0x2e, + 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x31, 0x0a, 0x07, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x12, 0x0d, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x1a, 0x17, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x07, 0x55, 0x70, 0x67, + 0x72, 0x61, 0x64, 0x65, 0x12, 0x16, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55, 0x70, + 0x67, 0x72, 0x61, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x63, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x52, 0x0a, 0x0f, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, + 0x74, 0x69, 0x63, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x41, 0x67, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x41, 0x67, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x0f, 0x44, 0x69, 0x61, + 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x55, 0x6e, 0x69, 0x74, 0x73, 0x12, 0x1e, 0x2e, 0x63, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, + 0x55, 0x6e, 0x69, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x63, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, + 0x55, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x62, + 0x0a, 0x14, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x43, 0x6f, 0x6d, 0x70, + 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x23, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, + 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x43, + 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x30, 0x01, 0x12, 0x34, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x12, + 0x18, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, + 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0d, 0x2e, 0x63, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x29, 0x5a, 0x24, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x2f, 0x76, 0x32, 0x2f, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0xf8, 0x01, 0x01, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -2233,89 +2419,96 @@ func file_control_v2_proto_rawDescGZIP() []byte { return file_control_v2_proto_rawDescData } -var file_control_v2_proto_enumTypes = make([]protoimpl.EnumInfo, 5) -var file_control_v2_proto_msgTypes = make([]protoimpl.MessageInfo, 24) +var file_control_v2_proto_enumTypes = make([]protoimpl.EnumInfo, 6) +var file_control_v2_proto_msgTypes = make([]protoimpl.MessageInfo, 26) var file_control_v2_proto_goTypes = []interface{}{ (State)(0), // 0: cproto.State - (UnitType)(0), // 1: cproto.UnitType - (ActionStatus)(0), // 2: cproto.ActionStatus - (PprofOption)(0), // 3: cproto.PprofOption - (AdditionalDiagnosticRequest)(0), // 4: cproto.AdditionalDiagnosticRequest - (*Empty)(nil), // 5: cproto.Empty - (*VersionResponse)(nil), // 6: cproto.VersionResponse - (*RestartResponse)(nil), // 7: cproto.RestartResponse - (*UpgradeRequest)(nil), // 8: cproto.UpgradeRequest - (*UpgradeResponse)(nil), // 9: cproto.UpgradeResponse - (*ComponentUnitState)(nil), // 10: cproto.ComponentUnitState - (*ComponentVersionInfo)(nil), // 11: cproto.ComponentVersionInfo - (*ComponentState)(nil), // 12: cproto.ComponentState - (*StateAgentInfo)(nil), // 13: cproto.StateAgentInfo - (*StateResponse)(nil), // 14: cproto.StateResponse - (*UpgradeDetails)(nil), // 15: cproto.UpgradeDetails - (*UpgradeDetailsMetadata)(nil), // 16: cproto.UpgradeDetailsMetadata - (*DiagnosticFileResult)(nil), // 17: cproto.DiagnosticFileResult - (*DiagnosticAgentRequest)(nil), // 18: cproto.DiagnosticAgentRequest - (*DiagnosticComponentsRequest)(nil), // 19: cproto.DiagnosticComponentsRequest - (*DiagnosticComponentRequest)(nil), // 20: cproto.DiagnosticComponentRequest - (*DiagnosticAgentResponse)(nil), // 21: cproto.DiagnosticAgentResponse - (*DiagnosticUnitRequest)(nil), // 22: cproto.DiagnosticUnitRequest - (*DiagnosticUnitsRequest)(nil), // 23: cproto.DiagnosticUnitsRequest - (*DiagnosticUnitResponse)(nil), // 24: cproto.DiagnosticUnitResponse - (*DiagnosticComponentResponse)(nil), // 25: cproto.DiagnosticComponentResponse - (*DiagnosticUnitsResponse)(nil), // 26: cproto.DiagnosticUnitsResponse - (*ConfigureRequest)(nil), // 27: cproto.ConfigureRequest - nil, // 28: cproto.ComponentVersionInfo.MetaEntry - (*timestamppb.Timestamp)(nil), // 29: google.protobuf.Timestamp + (CollectorComponentStatus)(0), // 1: cproto.CollectorComponentStatus + (UnitType)(0), // 2: cproto.UnitType + (ActionStatus)(0), // 3: cproto.ActionStatus + (PprofOption)(0), // 4: cproto.PprofOption + (AdditionalDiagnosticRequest)(0), // 5: cproto.AdditionalDiagnosticRequest + (*Empty)(nil), // 6: cproto.Empty + (*VersionResponse)(nil), // 7: cproto.VersionResponse + (*RestartResponse)(nil), // 8: cproto.RestartResponse + (*UpgradeRequest)(nil), // 9: cproto.UpgradeRequest + (*UpgradeResponse)(nil), // 10: cproto.UpgradeResponse + (*ComponentUnitState)(nil), // 11: cproto.ComponentUnitState + (*ComponentVersionInfo)(nil), // 12: cproto.ComponentVersionInfo + (*ComponentState)(nil), // 13: cproto.ComponentState + (*StateAgentInfo)(nil), // 14: cproto.StateAgentInfo + (*CollectorComponent)(nil), // 15: cproto.CollectorComponent + (*StateResponse)(nil), // 16: cproto.StateResponse + (*UpgradeDetails)(nil), // 17: cproto.UpgradeDetails + (*UpgradeDetailsMetadata)(nil), // 18: cproto.UpgradeDetailsMetadata + (*DiagnosticFileResult)(nil), // 19: cproto.DiagnosticFileResult + (*DiagnosticAgentRequest)(nil), // 20: cproto.DiagnosticAgentRequest + (*DiagnosticComponentsRequest)(nil), // 21: cproto.DiagnosticComponentsRequest + (*DiagnosticComponentRequest)(nil), // 22: cproto.DiagnosticComponentRequest + (*DiagnosticAgentResponse)(nil), // 23: cproto.DiagnosticAgentResponse + (*DiagnosticUnitRequest)(nil), // 24: cproto.DiagnosticUnitRequest + (*DiagnosticUnitsRequest)(nil), // 25: cproto.DiagnosticUnitsRequest + (*DiagnosticUnitResponse)(nil), // 26: cproto.DiagnosticUnitResponse + (*DiagnosticComponentResponse)(nil), // 27: cproto.DiagnosticComponentResponse + (*DiagnosticUnitsResponse)(nil), // 28: cproto.DiagnosticUnitsResponse + (*ConfigureRequest)(nil), // 29: cproto.ConfigureRequest + nil, // 30: cproto.ComponentVersionInfo.MetaEntry + nil, // 31: cproto.CollectorComponent.ComponentStatusMapEntry + (*timestamppb.Timestamp)(nil), // 32: google.protobuf.Timestamp } var file_control_v2_proto_depIdxs = []int32{ - 2, // 0: cproto.RestartResponse.status:type_name -> cproto.ActionStatus - 2, // 1: cproto.UpgradeResponse.status:type_name -> cproto.ActionStatus - 1, // 2: cproto.ComponentUnitState.unit_type:type_name -> cproto.UnitType + 3, // 0: cproto.RestartResponse.status:type_name -> cproto.ActionStatus + 3, // 1: cproto.UpgradeResponse.status:type_name -> cproto.ActionStatus + 2, // 2: cproto.ComponentUnitState.unit_type:type_name -> cproto.UnitType 0, // 3: cproto.ComponentUnitState.state:type_name -> cproto.State - 28, // 4: cproto.ComponentVersionInfo.meta:type_name -> cproto.ComponentVersionInfo.MetaEntry + 30, // 4: cproto.ComponentVersionInfo.meta:type_name -> cproto.ComponentVersionInfo.MetaEntry 0, // 5: cproto.ComponentState.state:type_name -> cproto.State - 10, // 6: cproto.ComponentState.units:type_name -> cproto.ComponentUnitState - 11, // 7: cproto.ComponentState.version_info:type_name -> cproto.ComponentVersionInfo - 13, // 8: cproto.StateResponse.info:type_name -> cproto.StateAgentInfo - 0, // 9: cproto.StateResponse.state:type_name -> cproto.State - 0, // 10: cproto.StateResponse.fleetState:type_name -> cproto.State - 12, // 11: cproto.StateResponse.components:type_name -> cproto.ComponentState - 15, // 12: cproto.StateResponse.upgrade_details:type_name -> cproto.UpgradeDetails - 16, // 13: cproto.UpgradeDetails.metadata:type_name -> cproto.UpgradeDetailsMetadata - 29, // 14: cproto.DiagnosticFileResult.generated:type_name -> google.protobuf.Timestamp - 4, // 15: cproto.DiagnosticAgentRequest.additional_metrics:type_name -> cproto.AdditionalDiagnosticRequest - 20, // 16: cproto.DiagnosticComponentsRequest.components:type_name -> cproto.DiagnosticComponentRequest - 4, // 17: cproto.DiagnosticComponentsRequest.additional_metrics:type_name -> cproto.AdditionalDiagnosticRequest - 17, // 18: cproto.DiagnosticAgentResponse.results:type_name -> cproto.DiagnosticFileResult - 1, // 19: cproto.DiagnosticUnitRequest.unit_type:type_name -> cproto.UnitType - 22, // 20: cproto.DiagnosticUnitsRequest.units:type_name -> cproto.DiagnosticUnitRequest - 1, // 21: cproto.DiagnosticUnitResponse.unit_type:type_name -> cproto.UnitType - 17, // 22: cproto.DiagnosticUnitResponse.results:type_name -> cproto.DiagnosticFileResult - 17, // 23: cproto.DiagnosticComponentResponse.results:type_name -> cproto.DiagnosticFileResult - 24, // 24: cproto.DiagnosticUnitsResponse.units:type_name -> cproto.DiagnosticUnitResponse - 5, // 25: cproto.ElasticAgentControl.Version:input_type -> cproto.Empty - 5, // 26: cproto.ElasticAgentControl.State:input_type -> cproto.Empty - 5, // 27: cproto.ElasticAgentControl.StateWatch:input_type -> cproto.Empty - 5, // 28: cproto.ElasticAgentControl.Restart:input_type -> cproto.Empty - 8, // 29: cproto.ElasticAgentControl.Upgrade:input_type -> cproto.UpgradeRequest - 18, // 30: cproto.ElasticAgentControl.DiagnosticAgent:input_type -> cproto.DiagnosticAgentRequest - 23, // 31: cproto.ElasticAgentControl.DiagnosticUnits:input_type -> cproto.DiagnosticUnitsRequest - 19, // 32: cproto.ElasticAgentControl.DiagnosticComponents:input_type -> cproto.DiagnosticComponentsRequest - 27, // 33: cproto.ElasticAgentControl.Configure:input_type -> cproto.ConfigureRequest - 6, // 34: cproto.ElasticAgentControl.Version:output_type -> cproto.VersionResponse - 14, // 35: cproto.ElasticAgentControl.State:output_type -> cproto.StateResponse - 14, // 36: cproto.ElasticAgentControl.StateWatch:output_type -> cproto.StateResponse - 7, // 37: cproto.ElasticAgentControl.Restart:output_type -> cproto.RestartResponse - 9, // 38: cproto.ElasticAgentControl.Upgrade:output_type -> cproto.UpgradeResponse - 21, // 39: cproto.ElasticAgentControl.DiagnosticAgent:output_type -> cproto.DiagnosticAgentResponse - 24, // 40: cproto.ElasticAgentControl.DiagnosticUnits:output_type -> cproto.DiagnosticUnitResponse - 25, // 41: cproto.ElasticAgentControl.DiagnosticComponents:output_type -> cproto.DiagnosticComponentResponse - 5, // 42: cproto.ElasticAgentControl.Configure:output_type -> cproto.Empty - 34, // [34:43] is the sub-list for method output_type - 25, // [25:34] is the sub-list for method input_type - 25, // [25:25] is the sub-list for extension type_name - 25, // [25:25] is the sub-list for extension extendee - 0, // [0:25] is the sub-list for field type_name + 11, // 6: cproto.ComponentState.units:type_name -> cproto.ComponentUnitState + 12, // 7: cproto.ComponentState.version_info:type_name -> cproto.ComponentVersionInfo + 1, // 8: cproto.CollectorComponent.status:type_name -> cproto.CollectorComponentStatus + 31, // 9: cproto.CollectorComponent.ComponentStatusMap:type_name -> cproto.CollectorComponent.ComponentStatusMapEntry + 14, // 10: cproto.StateResponse.info:type_name -> cproto.StateAgentInfo + 0, // 11: cproto.StateResponse.state:type_name -> cproto.State + 0, // 12: cproto.StateResponse.fleetState:type_name -> cproto.State + 13, // 13: cproto.StateResponse.components:type_name -> cproto.ComponentState + 17, // 14: cproto.StateResponse.upgrade_details:type_name -> cproto.UpgradeDetails + 15, // 15: cproto.StateResponse.collector:type_name -> cproto.CollectorComponent + 18, // 16: cproto.UpgradeDetails.metadata:type_name -> cproto.UpgradeDetailsMetadata + 32, // 17: cproto.DiagnosticFileResult.generated:type_name -> google.protobuf.Timestamp + 5, // 18: cproto.DiagnosticAgentRequest.additional_metrics:type_name -> cproto.AdditionalDiagnosticRequest + 22, // 19: cproto.DiagnosticComponentsRequest.components:type_name -> cproto.DiagnosticComponentRequest + 5, // 20: cproto.DiagnosticComponentsRequest.additional_metrics:type_name -> cproto.AdditionalDiagnosticRequest + 19, // 21: cproto.DiagnosticAgentResponse.results:type_name -> cproto.DiagnosticFileResult + 2, // 22: cproto.DiagnosticUnitRequest.unit_type:type_name -> cproto.UnitType + 24, // 23: cproto.DiagnosticUnitsRequest.units:type_name -> cproto.DiagnosticUnitRequest + 2, // 24: cproto.DiagnosticUnitResponse.unit_type:type_name -> cproto.UnitType + 19, // 25: cproto.DiagnosticUnitResponse.results:type_name -> cproto.DiagnosticFileResult + 19, // 26: cproto.DiagnosticComponentResponse.results:type_name -> cproto.DiagnosticFileResult + 26, // 27: cproto.DiagnosticUnitsResponse.units:type_name -> cproto.DiagnosticUnitResponse + 15, // 28: cproto.CollectorComponent.ComponentStatusMapEntry.value:type_name -> cproto.CollectorComponent + 6, // 29: cproto.ElasticAgentControl.Version:input_type -> cproto.Empty + 6, // 30: cproto.ElasticAgentControl.State:input_type -> cproto.Empty + 6, // 31: cproto.ElasticAgentControl.StateWatch:input_type -> cproto.Empty + 6, // 32: cproto.ElasticAgentControl.Restart:input_type -> cproto.Empty + 9, // 33: cproto.ElasticAgentControl.Upgrade:input_type -> cproto.UpgradeRequest + 20, // 34: cproto.ElasticAgentControl.DiagnosticAgent:input_type -> cproto.DiagnosticAgentRequest + 25, // 35: cproto.ElasticAgentControl.DiagnosticUnits:input_type -> cproto.DiagnosticUnitsRequest + 21, // 36: cproto.ElasticAgentControl.DiagnosticComponents:input_type -> cproto.DiagnosticComponentsRequest + 29, // 37: cproto.ElasticAgentControl.Configure:input_type -> cproto.ConfigureRequest + 7, // 38: cproto.ElasticAgentControl.Version:output_type -> cproto.VersionResponse + 16, // 39: cproto.ElasticAgentControl.State:output_type -> cproto.StateResponse + 16, // 40: cproto.ElasticAgentControl.StateWatch:output_type -> cproto.StateResponse + 8, // 41: cproto.ElasticAgentControl.Restart:output_type -> cproto.RestartResponse + 10, // 42: cproto.ElasticAgentControl.Upgrade:output_type -> cproto.UpgradeResponse + 23, // 43: cproto.ElasticAgentControl.DiagnosticAgent:output_type -> cproto.DiagnosticAgentResponse + 26, // 44: cproto.ElasticAgentControl.DiagnosticUnits:output_type -> cproto.DiagnosticUnitResponse + 27, // 45: cproto.ElasticAgentControl.DiagnosticComponents:output_type -> cproto.DiagnosticComponentResponse + 6, // 46: cproto.ElasticAgentControl.Configure:output_type -> cproto.Empty + 38, // [38:47] is the sub-list for method output_type + 29, // [29:38] is the sub-list for method input_type + 29, // [29:29] is the sub-list for extension type_name + 29, // [29:29] is the sub-list for extension extendee + 0, // [0:29] is the sub-list for field type_name } func init() { file_control_v2_proto_init() } @@ -2433,7 +2626,7 @@ func file_control_v2_proto_init() { } } file_control_v2_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StateResponse); i { + switch v := v.(*CollectorComponent); i { case 0: return &v.state case 1: @@ -2445,7 +2638,7 @@ func file_control_v2_proto_init() { } } file_control_v2_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpgradeDetails); i { + switch v := v.(*StateResponse); i { case 0: return &v.state case 1: @@ -2457,7 +2650,7 @@ func file_control_v2_proto_init() { } } file_control_v2_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpgradeDetailsMetadata); i { + switch v := v.(*UpgradeDetails); i { case 0: return &v.state case 1: @@ -2469,7 +2662,7 @@ func file_control_v2_proto_init() { } } file_control_v2_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DiagnosticFileResult); i { + switch v := v.(*UpgradeDetailsMetadata); i { case 0: return &v.state case 1: @@ -2481,7 +2674,7 @@ func file_control_v2_proto_init() { } } file_control_v2_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DiagnosticAgentRequest); i { + switch v := v.(*DiagnosticFileResult); i { case 0: return &v.state case 1: @@ -2493,7 +2686,7 @@ func file_control_v2_proto_init() { } } file_control_v2_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DiagnosticComponentsRequest); i { + switch v := v.(*DiagnosticAgentRequest); i { case 0: return &v.state case 1: @@ -2505,7 +2698,7 @@ func file_control_v2_proto_init() { } } file_control_v2_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DiagnosticComponentRequest); i { + switch v := v.(*DiagnosticComponentsRequest); i { case 0: return &v.state case 1: @@ -2517,7 +2710,7 @@ func file_control_v2_proto_init() { } } file_control_v2_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DiagnosticAgentResponse); i { + switch v := v.(*DiagnosticComponentRequest); i { case 0: return &v.state case 1: @@ -2529,7 +2722,7 @@ func file_control_v2_proto_init() { } } file_control_v2_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DiagnosticUnitRequest); i { + switch v := v.(*DiagnosticAgentResponse); i { case 0: return &v.state case 1: @@ -2541,7 +2734,7 @@ func file_control_v2_proto_init() { } } file_control_v2_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DiagnosticUnitsRequest); i { + switch v := v.(*DiagnosticUnitRequest); i { case 0: return &v.state case 1: @@ -2553,7 +2746,7 @@ func file_control_v2_proto_init() { } } file_control_v2_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DiagnosticUnitResponse); i { + switch v := v.(*DiagnosticUnitsRequest); i { case 0: return &v.state case 1: @@ -2565,7 +2758,7 @@ func file_control_v2_proto_init() { } } file_control_v2_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DiagnosticComponentResponse); i { + switch v := v.(*DiagnosticUnitResponse); i { case 0: return &v.state case 1: @@ -2577,7 +2770,7 @@ func file_control_v2_proto_init() { } } file_control_v2_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DiagnosticUnitsResponse); i { + switch v := v.(*DiagnosticComponentResponse); i { case 0: return &v.state case 1: @@ -2589,6 +2782,18 @@ func file_control_v2_proto_init() { } } file_control_v2_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DiagnosticUnitsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_control_v2_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ConfigureRequest); i { case 0: return &v.state @@ -2606,8 +2811,8 @@ func file_control_v2_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_control_v2_proto_rawDesc, - NumEnums: 5, - NumMessages: 24, + NumEnums: 6, + NumMessages: 26, NumExtensions: 0, NumServices: 1, }, diff --git a/pkg/control/v2/cproto/control_v2_grpc.pb.go b/pkg/control/v2/cproto/control_v2_grpc.pb.go index a631e031cdb..90936b35785 100644 --- a/pkg/control/v2/cproto/control_v2_grpc.pb.go +++ b/pkg/control/v2/cproto/control_v2_grpc.pb.go @@ -5,7 +5,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.2.0 -// - protoc v5.27.1 +// - protoc v4.25.3 // source: control_v2.proto package cproto diff --git a/pkg/control/v2/server/server.go b/pkg/control/v2/server/server.go index 866862f65bd..c6a1ad8cbd5 100644 --- a/pkg/control/v2/server/server.go +++ b/pkg/control/v2/server/server.go @@ -13,6 +13,9 @@ import ( "os" "time" + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/status" + "go.opentelemetry.io/collector/component/componentstatus" + "github.com/elastic/elastic-agent/pkg/control" "github.com/elastic/elastic-agent/pkg/control/v1/proto" v1server "github.com/elastic/elastic-agent/pkg/control/v1/server" @@ -395,7 +398,7 @@ func stateToProto(state *coordinator.State, agentInfo info.Agent) (*cproto.State Commit: release.Commit(), BuildTime: release.BuildTime().Format(control.TimeFormat()), Snapshot: release.Snapshot(), - Pid: int32(os.Getpid()), + Pid: int32(os.Getpid()), //nolint:gosec // not going to have a pid greater than 32bit integer Unprivileged: agentInfo.Unprivileged(), IsManaged: !agentInfo.IsStandalone(), }, @@ -405,5 +408,48 @@ func stateToProto(state *coordinator.State, agentInfo info.Agent) (*cproto.State FleetMessage: state.FleetMessage, Components: components, UpgradeDetails: upgradeDetails, + Collector: collectorToProto(state.Collector), }, nil } + +func collectorToProto(s *status.AggregateStatus) *cproto.CollectorComponent { + if s == nil { + return nil + } + r := &cproto.CollectorComponent{ + Status: otelComponentStatusToProto(s.Status()), + Timestamp: s.Timestamp().Format(time.RFC3339Nano), + } + if s.Err() != nil { + r.Error = s.Err().Error() + } + if len(s.ComponentStatusMap) > 0 { + r.ComponentStatusMap = make(map[string]*cproto.CollectorComponent, len(s.ComponentStatusMap)) + for id, nested := range s.ComponentStatusMap { + r.ComponentStatusMap[id] = collectorToProto(nested) + } + } + return r +} + +func otelComponentStatusToProto(s componentstatus.Status) cproto.CollectorComponentStatus { + switch s { + case componentstatus.StatusNone: + return cproto.CollectorComponentStatus_StatusNone + case componentstatus.StatusStarting: + return cproto.CollectorComponentStatus_StatusStarting + case componentstatus.StatusOK: + return cproto.CollectorComponentStatus_StatusOK + case componentstatus.StatusRecoverableError: + return cproto.CollectorComponentStatus_StatusRecoverableError + case componentstatus.StatusPermanentError: + return cproto.CollectorComponentStatus_StatusPermanentError + case componentstatus.StatusFatalError: + return cproto.CollectorComponentStatus_StatusFatalError + case componentstatus.StatusStopping: + return cproto.CollectorComponentStatus_StatusStopping + case componentstatus.StatusStopped: + return cproto.CollectorComponentStatus_StatusStopped + } + return cproto.CollectorComponentStatus_StatusNone +} diff --git a/pkg/control/v2/server/server_test.go b/pkg/control/v2/server/server_test.go index abfc63e68a3..c6059575b46 100644 --- a/pkg/control/v2/server/server_test.go +++ b/pkg/control/v2/server/server_test.go @@ -8,6 +8,9 @@ import ( "testing" "time" + "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/status" + "go.opentelemetry.io/collector/component/componentstatus" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -122,6 +125,19 @@ func TestStateMapping(t *testing.T) { }, }, }, + Collector: &status.AggregateStatus{ + Event: componentstatus.NewEvent(componentstatus.StatusOK), + ComponentStatusMap: map[string]*status.AggregateStatus{ + "some-pipeline": &status.AggregateStatus{ + Event: componentstatus.NewEvent(componentstatus.StatusOK), + ComponentStatusMap: map[string]*status.AggregateStatus{ + "receiver": &status.AggregateStatus{ + Event: componentstatus.NewEvent(componentstatus.StatusOK), + }, + }, + }, + }, + }, } if tc.upgradeDetails != nil { @@ -164,6 +180,19 @@ func TestStateMapping(t *testing.T) { } assert.Equal(t, expectedCompState, stateResponse.Components[0]) } + if assert.NotNil(t, stateResponse.Collector) { + assert.Equal(t, cproto.CollectorComponentStatus_StatusOK, stateResponse.Collector.Status) + if assert.Contains(t, stateResponse.Collector.ComponentStatusMap, "some-pipeline") { + observed := stateResponse.Collector.ComponentStatusMap["some-pipeline"] + assert.Equal(t, cproto.CollectorComponentStatus_StatusOK, observed.Status) + assert.NotEmpty(t, observed.Timestamp) + if assert.Contains(t, observed.ComponentStatusMap, "receiver") { + observedReceiver := observed.ComponentStatusMap["receiver"] + assert.Equal(t, cproto.CollectorComponentStatus_StatusOK, observedReceiver.Status) + assert.NotEmpty(t, observedReceiver.Timestamp) + } + } + } if tc.upgradeDetails != nil { expectedMetadata := &cproto.UpgradeDetailsMetadata{ diff --git a/pkg/features/features.go b/pkg/features/features.go index f079435cd99..1e547e73a47 100644 --- a/pkg/features/features.go +++ b/pkg/features/features.go @@ -172,7 +172,7 @@ func Parse(policy any) (*Flags, error) { } parsedFlags := cfg{} - if err := c.Unpack(&parsedFlags); err != nil { + if err := c.UnpackTo(&parsedFlags); err != nil { return nil, fmt.Errorf("could not umpack features config: %w", err) } diff --git a/pkg/limits/limits.go b/pkg/limits/limits.go index f28a67a3608..9d76e2cf129 100644 --- a/pkg/limits/limits.go +++ b/pkg/limits/limits.go @@ -128,7 +128,7 @@ func Parse(policy any) (*LimitsConfig, error) { } parsedConfig := rootConfig{} - if err := c.Unpack(&parsedConfig); err != nil { + if err := c.UnpackTo(&parsedConfig); err != nil { return nil, fmt.Errorf("could not unpack limits config: %w", err) } diff --git a/pkg/testing/fixture.go b/pkg/testing/fixture.go index 08bd20a8c7c..7d77409048d 100644 --- a/pkg/testing/fixture.go +++ b/pkg/testing/fixture.go @@ -810,7 +810,7 @@ func (f *Fixture) IsHealthy(ctx context.Context, opts ...process.CmdOption) erro if status.State != int(cproto.State_HEALTHY) { return fmt.Errorf("agent isn't healthy, current status: %s", - client.State(status.State)) + client.State(status.State)) //nolint:gosec // value will never be over 32-bit } return nil @@ -1287,6 +1287,13 @@ func createTempDir(t *testing.T) string { return tempDir } +type AgentStatusCollectorOutput struct { + Status int `json:"status"` + Error string `json:"error"` + Timestamp string `json:"timestamp"` + ComponentStatusMap map[string]*AgentStatusCollectorOutput `json:"components"` +} + type AgentStatusOutput struct { Info struct { ID string `json:"id"` @@ -1322,9 +1329,10 @@ type AgentStatusOutput struct { } `json:"meta"` } `json:"version_info,omitempty"` } `json:"components"` - FleetState int `json:"FleetState"` - FleetMessage string `json:"FleetMessage"` - UpgradeDetails *details.Details `json:"upgrade_details"` + Collector *AgentStatusCollectorOutput `json:"collector"` + FleetState int `json:"FleetState"` + FleetMessage string `json:"FleetMessage"` + UpgradeDetails *details.Details `json:"upgrade_details"` } func (aso *AgentStatusOutput) IsZero() bool { diff --git a/testing/integration/diagnostics_test.go b/testing/integration/diagnostics_test.go index 26e53c3d68e..d43dd3f8aa7 100644 --- a/testing/integration/diagnostics_test.go +++ b/testing/integration/diagnostics_test.go @@ -44,6 +44,7 @@ var diagnosticsFiles = []string{ "heap.pprof.gz", "local-config.yaml", "mutex.pprof.gz", + "otel.yaml", "pre-config.yaml", "local-config.yaml", "state.yaml", diff --git a/testing/integration/otel_test.go b/testing/integration/otel_test.go index 03edb8954be..3ff827b164e 100644 --- a/testing/integration/otel_test.go +++ b/testing/integration/otel_test.go @@ -20,8 +20,10 @@ import ( "text/template" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/elastic/elastic-agent/pkg/control/v2/client" aTesting "github.com/elastic/elastic-agent/pkg/testing" "github.com/elastic/elastic-agent/pkg/testing/define" "github.com/elastic/elastic-agent/pkg/testing/tools/estools" @@ -205,6 +207,131 @@ service: require.True(t, err == nil || err == context.Canceled || err == context.DeadlineExceeded, "Retrieved unexpected error: %s", err.Error()) } +func TestOtelHybridFileProcessing(t *testing.T) { + define.Require(t, define.Requirements{ + Group: Default, + Local: true, + OS: []define.OS{ + // input path missing on windows + {Type: define.Linux}, + {Type: define.Darwin}, + }, + }) + + // otel mode should be detected automatically + tmpDir := t.TempDir() + // create input file + numEvents := 50 + inputFile, err := os.CreateTemp(tmpDir, "input.txt") + require.NoError(t, err, "failed to create temp file to hold data to ingest") + inputFilePath := inputFile.Name() + for i := 0; i < numEvents; i++ { + _, err = inputFile.Write([]byte(fmt.Sprintf("Line %d\n", i))) + require.NoErrorf(t, err, "failed to write line %d to temp file", i) + } + err = inputFile.Close() + require.NoError(t, err, "failed to close data temp file") + t.Cleanup(func() { + if t.Failed() { + contents, err := os.ReadFile(inputFilePath) + if err != nil { + t.Logf("no data file to import at %s", inputFilePath) + return + } + t.Logf("contents of import file:\n%s\n", string(contents)) + } + }) + // create output filename + outputFilePath := filepath.Join(tmpDir, "output.txt") + t.Cleanup(func() { + if t.Failed() { + contents, err := os.ReadFile(outputFilePath) + if err != nil { + t.Logf("no output data at %s", inputFilePath) + return + } + t.Logf("contents of output file:\n%s\n", string(contents)) + } + }) + // create the otel config with input and output + type otelConfigOptions struct { + InputPath string + OutputPath string + } + otelConfigTemplate := `receivers: + filelog: + include: + - {{.InputPath}} + start_at: beginning + +exporters: + file: + path: {{.OutputPath}} +service: + pipelines: + logs: + receivers: + - filelog + exporters: + - file +` + var otelConfigBuffer bytes.Buffer + require.NoError(t, + template.Must(template.New("otelConfig").Parse(otelConfigTemplate)).Execute(&otelConfigBuffer, + otelConfigOptions{ + InputPath: inputFilePath, + OutputPath: outputFilePath, + })) + + fixture, err := define.NewFixtureFromLocalBuild(t, define.Version()) + require.NoError(t, err) + + ctx, cancel := testcontext.WithDeadline(t, context.Background(), time.Now().Add(10*time.Minute)) + defer cancel() + err = fixture.Prepare(ctx, fakeComponent) + require.NoError(t, err) + + var fixtureWg sync.WaitGroup + fixtureWg.Add(1) + go func() { + defer fixtureWg.Done() + err = fixture.Run(ctx, aTesting.State{ + Configure: otelConfigBuffer.String(), + Reached: func(state *client.AgentState) bool { + // keep running (context cancel will stop it) + return false + }, + }) + }() + + var content []byte + require.Eventually(t, + func() bool { + // verify file exists + content, err = os.ReadFile(outputFilePath) + if err != nil || len(content) == 0 { + return false + } + + found := bytes.Count(content, []byte(filepath.Base(inputFilePath))) + return found == numEvents + }, + 3*time.Minute, 500*time.Millisecond, + fmt.Sprintf("there should be exported logs by now")) + + statusCtx, statusCancel := context.WithTimeout(ctx, 5*time.Second) + defer statusCancel() + output, err := fixture.ExecStatus(statusCtx) + require.NoError(t, err, "status command failed") + + cancel() + fixtureWg.Wait() + require.True(t, err == nil || err == context.Canceled || err == context.DeadlineExceeded, "Retrieved unexpected error: %s", err.Error()) + + assert.NotNil(t, output.Collector) + assert.Equal(t, 2, output.Collector.Status, "collector status should have been StatusOK") +} + func validateCommandIsWorking(t *testing.T, ctx context.Context, fixture *aTesting.Fixture, tempDir string) { fileProcessingConfig := []byte(`receivers: filelog: