From d64d704812a177d50984501bde0ab1e5e6c5fcf1 Mon Sep 17 00:00:00 2001 From: Blake Rouse Date: Thu, 19 Oct 2023 03:29:36 -0400 Subject: [PATCH] Change upgrade watcher to use StateWatch (#3622) * Add PID to status output. * Fix watcher interval. * Refactor watcher to use StateWatch removing the need for PID watching completely. * Add changelog. * Fix lint. * Run mage check. * Fix TestStandaloneUpgradeRollbackOnRestarts to use the build watcher. * Make lint happy. * Annoying lint. * Make notice. * Fix windows. * Fix unit test connection on Windows. * Adjust grpc client. * Add back PID tracking. * More watcher fixes. --- NOTICE.txt | 236 ------- ...watcher-to-no-longer-need-root-access.yaml | 32 + control_v2.proto | 4 +- go.mod | 2 - go.sum | 2 - .../application/upgrade/crash_checker.go | 177 ----- .../application/upgrade/crash_checker_test.go | 192 ------ .../application/upgrade/error_checker.go | 106 --- .../application/upgrade/rollback_darwin.go | 43 ++ .../application/upgrade/rollback_linux.go | 45 ++ .../application/upgrade/rollback_windows.go | 29 + .../pkg/agent/application/upgrade/service.go | 294 --------- .../application/upgrade/service_darwin.go | 134 ---- .../application/upgrade/service_windows.go | 70 -- .../pkg/agent/application/upgrade/watcher.go | 247 +++++++ .../agent/application/upgrade/watcher_test.go | 609 ++++++++++++++++++ internal/pkg/agent/cmd/watch.go | 25 +- internal/pkg/agent/configuration/upgrade.go | 7 - internal/pkg/agent/install/install.go | 11 + pkg/control/v1/proto/control_v1.pb.go | 4 +- pkg/control/v1/proto/control_v1_grpc.pb.go | 32 +- pkg/control/v2/client/client.go | 16 +- pkg/control/v2/client/dial.go | 12 +- pkg/control/v2/client/dial_windows.go | 12 +- pkg/control/v2/client/mocks/client.go | 48 +- pkg/control/v2/cproto/control_v2.pb.go | 346 +++++----- pkg/control/v2/cproto/control_v2_grpc.pb.go | 46 +- pkg/control/v2/server/server.go | 2 + testing/integration/upgrade_rollback_test.go | 97 ++- testing/upgradetest/upgrader.go | 20 +- testing/upgradetest/watcher.go | 2 +- 31 files changed, 1396 insertions(+), 1506 deletions(-) create mode 100644 changelog/fragments/1697554456-Improve-upgrade-watcher-to-no-longer-need-root-access.yaml delete mode 100644 internal/pkg/agent/application/upgrade/crash_checker.go delete mode 100644 internal/pkg/agent/application/upgrade/crash_checker_test.go delete mode 100644 internal/pkg/agent/application/upgrade/error_checker.go create mode 100644 internal/pkg/agent/application/upgrade/rollback_darwin.go create mode 100644 internal/pkg/agent/application/upgrade/rollback_linux.go create mode 100644 internal/pkg/agent/application/upgrade/rollback_windows.go delete mode 100644 internal/pkg/agent/application/upgrade/service.go delete mode 100644 internal/pkg/agent/application/upgrade/service_darwin.go delete mode 100644 internal/pkg/agent/application/upgrade/service_windows.go create mode 100644 internal/pkg/agent/application/upgrade/watcher.go create mode 100644 internal/pkg/agent/application/upgrade/watcher_test.go diff --git a/NOTICE.txt b/NOTICE.txt index 9bc5150e46a..2ac1bfb9926 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -204,207 +204,6 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------------------------------- -Dependency : github.com/coreos/go-systemd/v22 -Version: v22.3.3-0.20220203105225-a9a7ef127534 -Licence type (autodetected): Apache-2.0 --------------------------------------------------------------------------------- - -Contents of probable licence file $GOMODCACHE/github.com/coreos/go-systemd/v22@v22.3.3-0.20220203105225-a9a7ef127534/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: - -You must give any other recipients of the Work or Derivative Works a copy of -this License; and -You must cause any modified files to carry prominent notices stating that You -changed the files; and -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 -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/docker/go-units Version: v0.5.0 @@ -11391,41 +11190,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------------------------------- -Dependency : github.com/godbus/dbus/v5 -Version: v5.0.6 -Licence type (autodetected): BSD-2-Clause --------------------------------------------------------------------------------- - -Contents of probable licence file $GOMODCACHE/github.com/godbus/dbus/v5@v5.0.6/LICENSE: - -Copyright (c) 2013, Georg Reinke (), Google -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -1. Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED -TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -------------------------------------------------------------------------------- Dependency : github.com/gogo/protobuf Version: v1.3.2 diff --git a/changelog/fragments/1697554456-Improve-upgrade-watcher-to-no-longer-need-root-access.yaml b/changelog/fragments/1697554456-Improve-upgrade-watcher-to-no-longer-need-root-access.yaml new file mode 100644 index 00000000000..82c757c7d9c --- /dev/null +++ b/changelog/fragments/1697554456-Improve-upgrade-watcher-to-no-longer-need-root-access.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: Improve upgrade watcher to no longer need root access + +# 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: + +# 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/3622 + +# 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 06ce89bf710..a94fe8eaa9b 100644 --- a/control_v2.proto +++ b/control_v2.proto @@ -166,6 +166,8 @@ message StateAgentInfo { string buildTime = 4; // Current running version is a snapshot. bool snapshot = 5; + // Current running PID. + int32 pid = 6; } // StateResponse is the current state of Elastic Agent. @@ -214,7 +216,7 @@ enum AdditionalDiagnosticRequest { CPU = 0; } -// DiagnosticComponentsRequest is the message to request diagnostics from individual components. +// DiagnosticComponentsRequest is the message to request diagnostics from individual components. message DiagnosticComponentsRequest { repeated DiagnosticComponentRequest components = 1; repeated AdditionalDiagnosticRequest additional_metrics = 2; diff --git a/go.mod b/go.mod index 322a8ef9f3a..e6e2a1cdd67 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 github.com/cavaliercoder/go-rpm v0.0.0-20190131055624-7a9c54e3d83e github.com/cenkalti/backoff/v4 v4.1.3 - github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 github.com/docker/go-units v0.5.0 github.com/dolmen-go/contextio v0.0.0-20200217195037-68fc5150bcd5 github.com/elastic/e2e-testing v1.1.0 @@ -100,7 +99,6 @@ require ( github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/swag v0.22.3 // indirect github.com/gobuffalo/here v0.6.0 // indirect - github.com/godbus/dbus/v5 v5.0.6 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/gnostic v0.6.9 // indirect diff --git a/go.sum b/go.sum index 87f5be315fa..41b12f0955f 100644 --- a/go.sum +++ b/go.sum @@ -701,7 +701,6 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 h1:rtAn27wIbmOGUs7RIbVgPEjb31ehTVniDwPGXyMxm5U= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= @@ -939,7 +938,6 @@ github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblf github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro= github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= diff --git a/internal/pkg/agent/application/upgrade/crash_checker.go b/internal/pkg/agent/application/upgrade/crash_checker.go deleted file mode 100644 index 67bc2bf862c..00000000000 --- a/internal/pkg/agent/application/upgrade/crash_checker.go +++ /dev/null @@ -1,177 +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; -// you may not use this file except in compliance with the Elastic License. - -package upgrade - -import ( - "context" - "fmt" - "sync" - "time" - - "github.com/elastic/elastic-agent/internal/pkg/agent/errors" - "github.com/elastic/elastic-agent/pkg/core/logger" -) - -const ( - evaluatedPeriods = 6 // with 10s period this means we evaluate 60s of agent run - crashesAllowed = 2 // means that within 60s one restart is allowed, additional one is considered crash -) - -type serviceHandler interface { - PID(ctx context.Context) (int, error) - Name() string - Close() -} - -// CrashChecker checks agent for crash pattern in Elastic Agent lifecycle. -type CrashChecker struct { - notifyChan chan error - q *distinctQueue - log *logger.Logger - sc serviceHandler - checkInterval time.Duration -} - -// NewCrashChecker creates a new crash checker. -func NewCrashChecker(ctx context.Context, ch chan error, log *logger.Logger, checkInterval time.Duration) (*CrashChecker, error) { - q, err := newDistinctQueue(evaluatedPeriods) - if err != nil { - return nil, err - } - - c := &CrashChecker{ - notifyChan: ch, - q: q, - log: log, - checkInterval: checkInterval, - } - - if err := c.Init(ctx, log); err != nil { - return nil, err - } - - log.Infof("running checks using '%s' controller", c.sc.Name()) - - return c, nil -} - -// Run runs the checking loop. -func (ch *CrashChecker) Run(ctx context.Context) { - defer ch.sc.Close() - - ch.log.Info("Crash checker started") - for { - t := time.NewTimer(ch.checkInterval) - - select { - case <-ctx.Done(): - t.Stop() - return - case <-t.C: - pid, err := ch.sc.PID(ctx) - if err != nil { - ch.log.Error(err) - } - - ch.log.Infof("retrieved service PID [%d]", pid) - ch.q.Push(pid) - - // We decide if the Agent process has crashed in either of - // these two ways. - ch.checkNotRunning() - ch.checkRestarted() - } - } -} - -// checkNotRunning checks if the PID reported for the Agent process has -// remained 0 for most recent crashesAllowed times the PID was checked. -// If so, it decides that the service has crashed. -func (ch *CrashChecker) checkNotRunning() { - // If PID has remained 0 for the most recent crashesAllowed number of checks, - // we consider the Agent as having crashed. - if ch.q.Len() < crashesAllowed { - // Not enough history of PIDs yet - return - } - - recentPIDs := ch.q.Peek(crashesAllowed) - ch.log.Debugf("most recent %d service PIDs within %d evaulations: %v", crashesAllowed, evaluatedPeriods, recentPIDs) - - allZeroPIDs := true - for _, recentPID := range recentPIDs { - allZeroPIDs = allZeroPIDs && (recentPID == 0) - } - - if allZeroPIDs { - msg := fmt.Sprintf("service remained crashed (PID = 0) within '%v' seconds", ch.checkInterval.Seconds()) - ch.notifyChan <- errors.New(msg) - } -} - -// checkRestarted checks if the PID reported for the Agent process has -// changed more than crashesAllowed times. If so, it decides that the service -// has crashed. -func (ch *CrashChecker) checkRestarted() { - restarts := ch.q.Distinct() - ch.log.Debugf("service PID changed %d times within %d evaluations", restarts, evaluatedPeriods) - - if restarts > crashesAllowed { - msg := fmt.Sprintf("service restarted '%d' times within '%v' seconds", restarts, ch.checkInterval.Seconds()) - ch.notifyChan <- errors.New(msg) - } -} - -type distinctQueue struct { - q []int - size int - lock sync.Mutex -} - -func newDistinctQueue(size int) (*distinctQueue, error) { - if size < 1 { - return nil, errors.New("invalid size", errors.TypeUnexpected) - } - return &distinctQueue{ - q: make([]int, 0, size), - size: size, - }, nil -} - -func (dq *distinctQueue) Push(id int) { - dq.lock.Lock() - defer dq.lock.Unlock() - - cutIdx := len(dq.q) - if dq.size-1 < len(dq.q) { - cutIdx = dq.size - 1 - } - dq.q = append([]int{id}, dq.q[:cutIdx]...) -} - -func (dq *distinctQueue) Distinct() int { - dq.lock.Lock() - defer dq.lock.Unlock() - - dm := make(map[int]int) - - for _, id := range dq.q { - dm[id] = 1 - } - - return len(dm) -} - -func (dq *distinctQueue) Len() int { - return len(dq.q) -} - -func (dq *distinctQueue) Peek(size int) []int { - if size > len(dq.q) { - size = len(dq.q) - } - - return dq.q[:size] -} diff --git a/internal/pkg/agent/application/upgrade/crash_checker_test.go b/internal/pkg/agent/application/upgrade/crash_checker_test.go deleted file mode 100644 index 0b62f3b99a8..00000000000 --- a/internal/pkg/agent/application/upgrade/crash_checker_test.go +++ /dev/null @@ -1,192 +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; -// you may not use this file except in compliance with the Elastic License. - -package upgrade - -import ( - "context" - "sync" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/elastic/elastic-agent/pkg/core/logger" -) - -var ( - testCheckPeriod = 100 * time.Millisecond -) - -func TestChecker(t *testing.T) { - t.Run("no failure when no change", func(t *testing.T) { - pider := &testPider{pid: 111} - ch, errChan := testableChecker(t, pider) - ctx, cancel := context.WithCancel(context.Background()) - - var wg sync.WaitGroup - wg.Add(1) - go func() { - wg.Done() - ch.Run(ctx) - }() - - wg.Wait() - <-time.After(6 * testCheckPeriod) - - var err error - select { - case err = <-errChan: - default: - } - - cancel() - require.NoError(t, err) - }) - - t.Run("no failure when unfrequent change", func(t *testing.T) { - const startingPID = 222 - pider := &testPider{pid: startingPID} - ch, errChan := testableChecker(t, pider) - ctx, cancel := context.WithCancel(context.Background()) - - var wg sync.WaitGroup - wg.Add(1) - go func() { - wg.Done() - ch.Run(ctx) - }() - - wg.Wait() - for i := 0; i < 2; i++ { - <-time.After(3 * testCheckPeriod) - pider.Change(startingPID + i) - } - var err error - select { - case err = <-errChan: - default: - } - - cancel() - require.NoError(t, err) - }) - - t.Run("no failure when change lower than limit", func(t *testing.T) { - const startingPID = 333 - pider := &testPider{pid: startingPID} - ch, errChan := testableChecker(t, pider) - ctx, cancel := context.WithCancel(context.Background()) - - var wg sync.WaitGroup - wg.Add(1) - go func() { - wg.Done() - ch.Run(ctx) - }() - - wg.Wait() - for i := 0; i < 3; i++ { - <-time.After(7 * testCheckPeriod) - pider.Change(startingPID + i) - } - var err error - select { - case err = <-errChan: - default: - } - - cancel() - require.NoError(t, err) - }) - - t.Run("fails when pid changes frequently", func(t *testing.T) { - pider := &testPider{} - ch, errChan := testableChecker(t, pider) - ctx, cancel := context.WithCancel(context.Background()) - - var wg sync.WaitGroup - wg.Add(1) - go func() { - wg.Done() - ch.Run(ctx) - }() - - wg.Wait() - for i := 0; i < 12; i++ { - <-time.After(testCheckPeriod / 2) - pider.Change(i) - } - var err error - select { - case err = <-errChan: - default: - } - - cancel() - assert.ErrorContains(t, err, "service restarted '3' times within '0.1' seconds") - }) - - t.Run("fails when pid remains 0", func(t *testing.T) { - const startingPID = 0 - pider := &testPider{pid: startingPID} - ch, errChan := testableChecker(t, pider) - ctx, cancel := context.WithCancel(context.Background()) - - var wg sync.WaitGroup - wg.Add(1) - go func() { - wg.Done() - ch.Run(ctx) - }() - - wg.Wait() - for i := 0; i < 3; i++ { - <-time.After(testCheckPeriod * 3) - pider.Change(startingPID) // don't change PID - } - var err error - select { - case err = <-errChan: - default: - } - - cancel() - assert.ErrorContains(t, err, "service remained crashed (PID = 0) within '0.1' seconds") - }) -} - -func testableChecker(t *testing.T, pider *testPider) (*CrashChecker, chan error) { - errChan := make(chan error, 1) - l, _ := logger.New("", false) - ch, err := NewCrashChecker(context.Background(), errChan, l, testCheckPeriod) - require.NoError(t, err) - - ch.sc.Close() - ch.sc = pider - - return ch, errChan -} - -type testPider struct { - sync.Mutex - pid int -} - -func (p *testPider) Change(pid int) { - p.Lock() - defer p.Unlock() - p.pid = pid -} - -func (p *testPider) PID(ctx context.Context) (int, error) { - p.Lock() - defer p.Unlock() - return p.pid, nil -} - -func (p *testPider) Close() {} - -func (p *testPider) Name() string { return "testPider" } diff --git a/internal/pkg/agent/application/upgrade/error_checker.go b/internal/pkg/agent/application/upgrade/error_checker.go deleted file mode 100644 index a0a3516c94a..00000000000 --- a/internal/pkg/agent/application/upgrade/error_checker.go +++ /dev/null @@ -1,106 +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; -// you may not use this file except in compliance with the Elastic License. - -package upgrade - -import ( - "context" - "fmt" - "time" - - "github.com/elastic/elastic-agent/pkg/control" - "github.com/elastic/elastic-agent/pkg/control/v2/client" - - "github.com/hashicorp/go-multierror" - - "github.com/elastic/elastic-agent/internal/pkg/agent/errors" - "github.com/elastic/elastic-agent/pkg/core/logger" -) - -const ( - statusCheckMissesAllowed = 4 // enable 2 minute start -) - -// ErrAgentStatusFailed is returned when agent reports FAILED status. -var ErrAgentStatusFailed = errors.New("agent in a failed state", errors.TypeApplication) - -// ErrorChecker checks agent for status change and sends an error to a channel if found. -type ErrorChecker struct { - failuresCounter int - notifyChan chan error - log *logger.Logger - agentClient client.Client - checkInterval time.Duration -} - -// NewErrorChecker creates a new error checker. -func NewErrorChecker(ch chan error, log *logger.Logger, checkInterval time.Duration) (*ErrorChecker, error) { - c := client.New() - ec := &ErrorChecker{ - notifyChan: ch, - agentClient: c, - log: log, - checkInterval: checkInterval, - } - - return ec, nil -} - -// Run runs the checking loop. -func (ch *ErrorChecker) Run(ctx context.Context) { - ch.log.Info("Error checker started") - for { - t := time.NewTimer(ch.checkInterval) - select { - case <-ctx.Done(): - t.Stop() - return - case <-t.C: - err := ch.agentClient.Connect(ctx) - if err != nil { - ch.failuresCounter++ - ch.log.Error(err, "Failed communicating to running daemon", errors.TypeNetwork, errors.M("socket", control.Address())) - ch.checkFailures() - - continue - } - - state, err := ch.agentClient.State(ctx) - ch.agentClient.Disconnect() - if err != nil { - ch.log.Error("failed retrieving agent status", err) - ch.failuresCounter++ - ch.checkFailures() - - // agent is probably not running and this will be detected by pid watcher - continue - } - - // call was successful, reset counter - ch.failuresCounter = 0 - - if state.State == client.Failed { - ch.log.Error("error checker notifying failure of agent") - ch.notifyChan <- ErrAgentStatusFailed - } - - for _, comp := range state.Components { - if comp.State == client.Failed { - err = multierror.Append(err, errors.New(fmt.Sprintf("component %s[%v] failed: %s", comp.Name, comp.ID, comp.Message))) - } - } - - if err != nil { - ch.log.Error("error checker notifying failure of applications") - ch.notifyChan <- errors.New(err, "applications in a failed state", errors.TypeApplication) - } - } - } -} - -func (ch *ErrorChecker) checkFailures() { - if failures := ch.failuresCounter; failures > statusCheckMissesAllowed { - ch.notifyChan <- errors.New(fmt.Sprintf("service failed to fetch agent status '%d' times in a row", failures)) - } -} diff --git a/internal/pkg/agent/application/upgrade/rollback_darwin.go b/internal/pkg/agent/application/upgrade/rollback_darwin.go new file mode 100644 index 00000000000..5ab2ab4cd01 --- /dev/null +++ b/internal/pkg/agent/application/upgrade/rollback_darwin.go @@ -0,0 +1,43 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +//go:build darwin + +package upgrade + +import ( + "os" + "os/exec" + "syscall" + "time" + + "github.com/elastic/elastic-agent/internal/pkg/agent/application/paths" +) + +const ( + // delay after agent restart is performed to allow agent to tear down all the processes + // important mainly for windows, as it prevents removing files which are in use + afterRestartDelay = 2 * time.Second +) + +func invokeCmd() *exec.Cmd { + // #nosec G204 -- user cannot inject any parameters to this command + cmd := exec.Command(paths.TopBinaryPath(), watcherSubcommand, + "--path.config", paths.Config(), + "--path.home", paths.Top(), + ) + + var cred = &syscall.Credential{ + Uid: uint32(os.Getuid()), + Gid: uint32(os.Getgid()), + Groups: nil, + NoSetGroups: true, + } + var sysproc = &syscall.SysProcAttr{ + Credential: cred, + Setsid: true, + } + cmd.SysProcAttr = sysproc + return cmd +} diff --git a/internal/pkg/agent/application/upgrade/rollback_linux.go b/internal/pkg/agent/application/upgrade/rollback_linux.go new file mode 100644 index 00000000000..934e3953fa0 --- /dev/null +++ b/internal/pkg/agent/application/upgrade/rollback_linux.go @@ -0,0 +1,45 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +//go:build linux + +package upgrade + +import ( + "os" + "os/exec" + "syscall" + "time" + + "github.com/elastic/elastic-agent/internal/pkg/agent/application/paths" +) + +const ( + // delay after agent restart is performed to allow agent to tear down all the processes + // important mainly for windows, as it prevents removing files which are in use + afterRestartDelay = 2 * time.Second +) + +func invokeCmd() *exec.Cmd { + // #nosec G204 -- user cannot inject any parameters to this command + cmd := exec.Command(paths.TopBinaryPath(), watcherSubcommand, + "--path.config", paths.Config(), + "--path.home", paths.Top(), + ) + + var cred = &syscall.Credential{ + Uid: uint32(os.Getuid()), + Gid: uint32(os.Getgid()), + Groups: nil, + NoSetGroups: true, + } + var sysproc = &syscall.SysProcAttr{ + Credential: cred, + Setsid: true, + // propagate sigint instead of sigkill so we can ignore it + Pdeathsig: syscall.SIGINT, + } + cmd.SysProcAttr = sysproc + return cmd +} diff --git a/internal/pkg/agent/application/upgrade/rollback_windows.go b/internal/pkg/agent/application/upgrade/rollback_windows.go new file mode 100644 index 00000000000..2315202e770 --- /dev/null +++ b/internal/pkg/agent/application/upgrade/rollback_windows.go @@ -0,0 +1,29 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +//go:build windows + +package upgrade + +import ( + "os/exec" + "time" + + "github.com/elastic/elastic-agent/internal/pkg/agent/application/paths" +) + +const ( + // delay after agent restart is performed to allow agent to tear down all the processes + // important mainly for windows, as it prevents removing files which are in use + afterRestartDelay = 15 * time.Second +) + +func invokeCmd() *exec.Cmd { + // #nosec G204 -- user cannot inject any parameters to this command + cmd := exec.Command(paths.TopBinaryPath(), watcherSubcommand, + "--path.config", paths.Config(), + "--path.home", paths.Top(), + ) + return cmd +} diff --git a/internal/pkg/agent/application/upgrade/service.go b/internal/pkg/agent/application/upgrade/service.go deleted file mode 100644 index cfb01945017..00000000000 --- a/internal/pkg/agent/application/upgrade/service.go +++ /dev/null @@ -1,294 +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; -// you may not use this file except in compliance with the Elastic License. - -//go:build !darwin && !windows - -package upgrade - -import ( - "bytes" - "context" - "fmt" - "os" - "os/exec" - "path/filepath" - "regexp" - "strconv" - "strings" - "syscall" - "time" - - "github.com/coreos/go-systemd/v22/dbus" - - "github.com/elastic/elastic-agent/internal/pkg/agent/application/paths" - "github.com/elastic/elastic-agent/internal/pkg/agent/errors" - "github.com/elastic/elastic-agent/pkg/core/logger" -) - -const ( - // delay after agent restart is performed to allow agent to tear down all the processes - // important mainly for windows, as it prevents removing files which are in use - afterRestartDelay = 2 * time.Second -) - -type pidProvider interface { - Init() error - Close() - PID(ctx context.Context) (int, error) - Name() string -} - -// Init initializes os dependent properties. -func (ch *CrashChecker) Init(ctx context.Context, _ *logger.Logger) error { - pp := relevantPidProvider() - if err := pp.Init(); err != nil { - return fmt.Errorf("unable to initialize relevant PID provider: %w", err) - } - - ch.sc = pp - - return nil -} - -func relevantPidProvider() pidProvider { - var pp pidProvider - - switch { - case isSystemd(): - pp = &dbusPidProvider{} - case isUpstart(): - pp = &upstartPidProvider{} - case isSysv(): - pp = &sysvPidProvider{} - default: - // in case we're using unsupported service manager - // let other checks work - pp = &noopPidProvider{} - } - - return pp -} - -// Upstart PID Provider - -type upstartPidProvider struct{} - -func (p *upstartPidProvider) Init() error { return nil } - -func (p *upstartPidProvider) Close() {} - -func (p *upstartPidProvider) Name() string { return "Upstart" } - -func (p *upstartPidProvider) PID(ctx context.Context) (int, error) { - listCmd := exec.Command("/sbin/status", agentName) - out, err := listCmd.Output() - if err != nil { - return 0, errors.New("failed to read process id", err) - } - - // find line - pidLine := strings.TrimSpace(string(out)) - if pidLine == "" { - return 0, errors.New(fmt.Sprintf("service process not found for service '%v'", paths.ServiceName)) - } - - re := regexp.MustCompile(agentName + ` start/running, process ([0-9]+)`) - matches := re.FindStringSubmatch(pidLine) - if len(matches) != 2 { - return 0, errors.New("could not detect pid of process", pidLine, matches) - } - - pid, err := strconv.Atoi(matches[1]) - if err != nil { - return 0, errors.New(fmt.Sprintf("failed to get process id[%v]", matches[1]), err) - } - - return pid, nil -} - -// SYSV PID Provider - -type sysvPidProvider struct{} - -func (p *sysvPidProvider) Init() error { return nil } - -func (p *sysvPidProvider) Close() {} - -func (p *sysvPidProvider) Name() string { return "SysV" } - -func (p *sysvPidProvider) PID(ctx context.Context) (int, error) { - listCmd := exec.Command("service", agentName, "status") - out, err := listCmd.Output() - if err != nil { - return 0, errors.New("failed to read process id", err) - } - - // find line - statusLine := strings.TrimSpace(string(out)) - if statusLine == "" { - return 0, errors.New(fmt.Sprintf("service process not found for service '%v'", paths.ServiceName)) - } - - // sysv does not report pid, let's do best effort - if !strings.HasPrefix(statusLine, "Running") { - return 0, errors.New(fmt.Sprintf("'%v' is not running", paths.ServiceName)) - } - - cmdArgs := filepath.Join(paths.Top(), paths.BinaryName) - pidofLine, err := exec.Command("pidof", cmdArgs).Output() - if err != nil { - return 0, errors.New(fmt.Sprintf("PID not found for'%v': %v", paths.ServiceName, err)) - } - - pid, err := strconv.Atoi(strings.TrimSpace(string(pidofLine))) - if err != nil { - return 0, errors.New("PID not a number") - } - - return pid, nil -} - -// DBUS PID provider - -type dbusPidProvider struct { - dbusConn *dbus.Conn -} - -func (p *dbusPidProvider) Init() error { - dbusConn, err := dbus.NewWithContext(context.Background()) - if err != nil { - return errors.New("failed to create dbus connection", err) - } - - p.dbusConn = dbusConn - return nil -} - -func (p *dbusPidProvider) Name() string { return "DBus" } - -func (p *dbusPidProvider) Close() { - p.dbusConn.Close() -} - -func (p *dbusPidProvider) PID(ctx context.Context) (int, error) { - sn := paths.ServiceName - if !strings.HasSuffix(sn, ".service") { - sn += ".service" - } - - prop, err := p.dbusConn.GetServicePropertyContext(ctx, sn, "MainPID") - if err != nil { - return 0, errors.New("failed to read service", err) - } - - pid, ok := prop.Value.Value().(uint32) - if !ok { - return 0, errors.New("failed to get process id", prop.Value.Value()) - } - - return int(pid), nil -} - -// noop PID provider - -type noopPidProvider struct{} - -func (p *noopPidProvider) Init() error { return nil } - -func (p *noopPidProvider) Close() {} - -func (p *noopPidProvider) Name() string { return "noop" } - -func (p *noopPidProvider) PID(ctx context.Context) (int, error) { return 0, nil } - -func invokeCmd() *exec.Cmd { - // #nosec G204 -- user cannot inject any parameters to this command - cmd := exec.Command(paths.TopBinaryPath(), watcherSubcommand, - "--path.config", paths.Config(), - "--path.home", paths.Top(), - ) - - var cred = &syscall.Credential{ - Uid: uint32(os.Getuid()), - Gid: uint32(os.Getgid()), - Groups: nil, - NoSetGroups: true, - } - var sysproc = &syscall.SysProcAttr{ - Credential: cred, - Setsid: true, - // propagate sigint instead of sigkill so we can ignore it - Pdeathsig: syscall.SIGINT, - } - cmd.SysProcAttr = sysproc - return cmd -} - -func isSystemd() bool { - if _, err := os.Stat("/run/systemd/system"); err == nil { - return true - } - - if _, err := os.Stat("/proc/1/comm"); err == nil { - filerc, err := os.Open("/proc/1/comm") - if err != nil { - return false - } - defer filerc.Close() - - buf := new(bytes.Buffer) - if _, err := buf.ReadFrom(filerc); err != nil { - return false - } - contents := buf.String() - - if strings.Trim(contents, " \r\n") == "systemd" { - return true - } - } - return false -} - -func isUpstart() bool { - if _, err := os.Stat("/sbin/upstart-udev-bridge"); err == nil { - return true - } - - if _, err := os.Stat("/sbin/initctl"); err == nil { - if out, err := exec.Command("/sbin/initctl", "--version").Output(); err == nil { - if bytes.Contains(out, []byte("initctl (upstart")) { - return true - } - } - } - return false -} - -func isSysv() bool { - // PID 1 is init - out, err := exec.Command("sudo", "cat", "/proc/1/comm").Output() - if err != nil { - o, err := exec.Command("cat", "/proc/1/comm").Output() - if err != nil { - return false - } - out = o - } - - if strings.TrimSpace(string(out)) != "init" { - return false - } - - // /sbin/init is not a link - initFile, err := os.Open("/sbin/init") - if err != nil || initFile == nil { - return false - } - - fi, err := initFile.Stat() - if err != nil { - return false - } - return fi.Mode()&os.ModeSymlink != os.ModeSymlink -} diff --git a/internal/pkg/agent/application/upgrade/service_darwin.go b/internal/pkg/agent/application/upgrade/service_darwin.go deleted file mode 100644 index 8886e7b414e..00000000000 --- a/internal/pkg/agent/application/upgrade/service_darwin.go +++ /dev/null @@ -1,134 +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; -// you may not use this file except in compliance with the Elastic License. - -//go:build darwin - -package upgrade - -import ( - "bufio" - "bytes" - "context" - "fmt" - "os" - "os/exec" - "regexp" - "strconv" - "strings" - "syscall" - "time" - - "github.com/hashicorp/go-multierror" - - "github.com/elastic/elastic-agent/internal/pkg/agent/application/paths" - "github.com/elastic/elastic-agent/internal/pkg/agent/errors" - "github.com/elastic/elastic-agent/internal/pkg/release" - "github.com/elastic/elastic-agent/pkg/core/logger" -) - -const ( - // delay after agent restart is performed to allow agent to tear down all the processes - // important mainly for windows, as it prevents removing files which are in use - afterRestartDelay = 2 * time.Second -) - -// Init initializes os dependent properties. -func (ch *CrashChecker) Init(ctx context.Context, _ *logger.Logger) error { - ch.sc = &darwinPidProvider{} - - return nil -} - -type darwinPidProvider struct{} - -func (p *darwinPidProvider) Name() string { return "launchd" } - -func (p *darwinPidProvider) Close() {} - -func (p *darwinPidProvider) PID(ctx context.Context) (int, error) { - piders := []func(context.Context) (int, error){ - p.piderFromCmd("launchctl", "list", paths.ServiceName), - } - - // if release is specifically built to be upgradeable (using DEV flag) - // we dont require to run as a service and will need sudo fallback - if release.Upgradeable() { - piders = append(piders, p.piderFromCmd("sudo", "launchctl", "list", paths.ServiceName)) - } - - var pidErrors error - for _, pider := range piders { - pid, err := pider(ctx) - if err == nil { - return pid, nil - } - - pidErrors = multierror.Append(pidErrors, err) - } - - return 0, pidErrors -} - -func (p *darwinPidProvider) piderFromCmd(name string, args ...string) func(context.Context) (int, error) { - return func(context.Context) (int, error) { - listCmd := exec.Command(name, args...) - listCmd.SysProcAttr = &syscall.SysProcAttr{ - Credential: &syscall.Credential{Uid: 0, Gid: 0}, - } - out, err := listCmd.Output() - if err != nil { - return 0, errors.New("failed to read process id", err) - } - - // find line - pidLine := "" - reader := bufio.NewReader(bytes.NewReader(out)) - scanner := bufio.NewScanner(reader) - for scanner.Scan() { - line := scanner.Text() - if strings.Contains(line, `"PID" = `) { - pidLine = strings.TrimSpace(line) - break - } - } - - if pidLine == "" { - return 0, errors.New(fmt.Sprintf("service process not found for service '%v'", paths.ServiceName)) - } - - re := regexp.MustCompile(`"PID" = ([0-9]+);`) - matches := re.FindStringSubmatch(pidLine) - if len(matches) != 2 { - return 0, errors.New("could not detect pid of process", pidLine, matches) - } - - pid, err := strconv.Atoi(matches[1]) - if err != nil { - return 0, errors.New(fmt.Sprintf("failed to get process id[%v]", matches[1]), err) - } - - return pid, nil - } -} - -func invokeCmd() *exec.Cmd { - // #nosec G204 -- user cannot inject any parameters to this command - cmd := exec.Command(paths.TopBinaryPath(), watcherSubcommand, - "--path.config", paths.Config(), - "--path.home", paths.Top(), - ) - - var cred = &syscall.Credential{ - Uid: uint32(os.Getuid()), - Gid: uint32(os.Getgid()), - Groups: nil, - NoSetGroups: true, - } - var sysproc = &syscall.SysProcAttr{ - Credential: cred, - Setsid: true, - } - cmd.SysProcAttr = sysproc - return cmd -} diff --git a/internal/pkg/agent/application/upgrade/service_windows.go b/internal/pkg/agent/application/upgrade/service_windows.go deleted file mode 100644 index 52512a1843b..00000000000 --- a/internal/pkg/agent/application/upgrade/service_windows.go +++ /dev/null @@ -1,70 +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; -// you may not use this file except in compliance with the Elastic License. - -//go:build windows - -package upgrade - -import ( - "context" - "os/exec" - "time" - - "golang.org/x/sys/windows/svc/mgr" - - "github.com/elastic/elastic-agent/internal/pkg/agent/application/paths" - "github.com/elastic/elastic-agent/internal/pkg/agent/errors" - "github.com/elastic/elastic-agent/pkg/core/logger" -) - -const ( - // delay after agent restart is performed to allow agent to tear down all the processes - // important mainly for windows, as it prevents removing files which are in use - afterRestartDelay = 15 * time.Second -) - -// Init initializes os dependent properties. -func (ch *CrashChecker) Init(ctx context.Context, _ *logger.Logger) error { - mgr, err := mgr.Connect() - if err != nil { - return errors.New("failed to initiate service manager", err) - } - - ch.sc = &pidProvider{ - winManager: mgr, - } - - return nil -} - -type pidProvider struct { - winManager *mgr.Mgr -} - -func (p *pidProvider) Close() {} - -func (p *pidProvider) Name() string { return "Windows Service Manager" } - -func (p *pidProvider) PID(ctx context.Context) (int, error) { - svc, err := p.winManager.OpenService(paths.ServiceName) - if err != nil { - return 0, errors.New("failed to read windows service", err) - } - - status, err := svc.Query() - if err != nil { - return 0, errors.New("failed to read windows service PID: %v", err) - } - - return int(status.ProcessId), nil -} - -func invokeCmd() *exec.Cmd { - // #nosec G204 -- user cannot inject any parameters to this command - cmd := exec.Command(paths.TopBinaryPath(), watcherSubcommand, - "--path.config", paths.Config(), - "--path.home", paths.Top(), - ) - return cmd -} diff --git a/internal/pkg/agent/application/upgrade/watcher.go b/internal/pkg/agent/application/upgrade/watcher.go new file mode 100644 index 00000000000..637129f82dc --- /dev/null +++ b/internal/pkg/agent/application/upgrade/watcher.go @@ -0,0 +1,247 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package upgrade + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/hashicorp/go-multierror" + "google.golang.org/grpc" + + "github.com/elastic/elastic-agent/pkg/control/v2/client" + "github.com/elastic/elastic-agent/pkg/core/logger" +) + +const ( + statusCheckMissesAllowed = 4 // enable 2 minute start (30 second periods) + statusLossesAllowed = 2 // enable connection lost to agent twice + statusFailureFlipFlopsAllowed = 3 // no more than three failure flip-flops allowed +) + +var ( + // ErrCannotConnect is returned when connection cannot be made to the agent. + ErrCannotConnect = errors.New("failed to connect to agent daemon") + // ErrLostConnection is returned when connection is lost to the agent daemon. + ErrLostConnection = errors.New("lost connection to agent daemon") + // ErrAgentStatusFailed is returned when agent reports FAILED status. + ErrAgentStatusFailed = errors.New("agent reported failed state") + // ErrAgentComponentFailed is returned when agent reports FAILED status for a component + ErrAgentComponentFailed = errors.New("agent reported failed component(s) state") + // ErrAgentFlipFlopFailed is returned when agent flip-flops between failed and healthy. + ErrAgentFlipFlopFailed = errors.New("agent reported on and off failures ") +) + +// AgentWatcher watches for the ability to connect to the running Elastic Agent, if it reports any errors +// and how many times it disconnects from the Elastic Agent while running. +type AgentWatcher struct { + connectCounter int + lostCounter int + lastPid int32 + + notifyChan chan error + log *logger.Logger + agentClient client.Client + checkInterval time.Duration +} + +// NewAgentWatcher creates a new agent watcher. +func NewAgentWatcher(ch chan error, log *logger.Logger, checkInterval time.Duration) *AgentWatcher { + c := client.New() + ec := &AgentWatcher{ + notifyChan: ch, + agentClient: c, + log: log, + checkInterval: checkInterval, + } + return ec +} + +// Run runs the checking loop. +func (ch *AgentWatcher) Run(ctx context.Context) { + ch.log.Info("Agent watcher started") + + ch.connectCounter = 0 + ch.lostCounter = 0 + + // tracking of an error runs in a separate goroutine, because + // the call to `watch.Recv` blocks and a timer is needed + // to determine if an error last longer than the checkInterval. + failedReset := make(chan bool) + failedCh := make(chan error) + go func() { + failedTimer := time.NewTimer(ch.checkInterval) + failedTimer.Stop() // starts stopped + defer failedTimer.Stop() // stopped on exit always + + var flipFlopCount int + var failedErr error + for { + select { + case <-ctx.Done(): + return + case reset := <-failedReset: + if reset { + flipFlopCount = 0 + failedTimer.Stop() + } + case err := <-failedCh: + if err != nil { + if failedErr == nil { + flipFlopCount++ + failedTimer.Reset(ch.checkInterval) + ch.log.Error("Agent reported failure (starting failed timer): %s", err) + } else { + ch.log.Error("Agent reported failure (failed timer already started): %s", err) + } + } else { + if failedErr != nil { + failedTimer.Stop() + ch.log.Error("Agent reported healthy (failed timer stopped): %s", err) + } + } + failedErr = err + if flipFlopCount > statusFailureFlipFlopsAllowed { + err := fmt.Errorf("%w '%d' times in a row", ErrAgentFlipFlopFailed, flipFlopCount) + ch.log.Error(err) + ch.notifyChan <- err + } + case <-failedTimer.C: + if failedErr == nil { + // error was cleared; do nothing + continue + } + // error lasted longer than the checkInterval, notify! + ch.notifyChan <- failedErr + } + } + }() + +LOOP: + for { + ch.lastPid = -1 + connectTimer := time.NewTimer(ch.checkInterval) + select { + case <-ctx.Done(): + connectTimer.Stop() + return + case <-connectTimer.C: + ch.log.Info("Trying to connect to agent") + // block on connection, don't retry connection, and fail on temp dial errors + // always a local connection it should connect quickly so the timeout is only 1 second + connectCtx, connectCancel := context.WithTimeout(ctx, 1*time.Second) + err := ch.agentClient.Connect(connectCtx, grpc.WithBlock(), grpc.WithDisableRetry(), grpc.FailOnNonTempDialError(true)) + connectCancel() + if err != nil { + ch.connectCounter++ + ch.log.Error("Failed connecting to running daemon: ", err) + if ch.checkFailures() { + return + } + // agent is probably not running + continue + } + + stateCtx, stateCancel := context.WithCancel(ctx) + watch, err := ch.agentClient.StateWatch(stateCtx) + if err != nil { + // considered a connect error + stateCancel() + ch.agentClient.Disconnect() + ch.log.Error("Failed to start state watch: ", err) + ch.connectCounter++ + if ch.checkFailures() { + return + } + // agent is probably not running + continue + } + + ch.log.Info("Connected to agent") + + // clear the connectCounter as connection was successfully made + // we don't want a disconnect and a reconnect to be counted with + // the connectCounter that is tracked with the lostCounter + ch.connectCounter = 0 + + // failure is tracked only for the life of the connection to + // the watch streaming protocol. either an error that last longer + // than the checkInterval or to many flopping of error/non-error + // will trigger a reported failure + failedReset <- true + failedCh <- nil + + for { + state, err := watch.Recv() + if err != nil { + // agent has crashed or exited + stateCancel() + ch.agentClient.Disconnect() + ch.log.Error("Lost connection: failed reading next state: ", err) + ch.lostCounter++ + if ch.checkFailures() { + return + } + continue LOOP + } + + // gRPC is good at hiding the fact that connection was lost + // to ensure that we don't miss a restart a changed PID means + // we are now talking to a different spawned Elastic Agent + if ch.lastPid == -1 { + ch.lastPid = state.Info.PID + ch.log.Info(fmt.Sprintf("Communicating with PID %d", ch.lastPid)) + } else if ch.lastPid != state.Info.PID { + ch.log.Error(fmt.Sprintf("Communication with PID %d lost, now communicating with PID %d", ch.lastPid, state.Info.PID)) + ch.lastPid = state.Info.PID + // count the PID change as a lost connection, but allow + // the communication to continue unless has become a failure + ch.lostCounter++ + if ch.checkFailures() { + stateCancel() + ch.agentClient.Disconnect() + return + } + } + + if state.State == client.Failed { + // top-level failure (something is really wrong) + failedCh <- fmt.Errorf("%w: %s", ErrAgentStatusFailed, state.Message) + continue + } else { + // agent is healthy; but a component might not be healthy + // upgrade tracks unhealthy component as an issue with the upgrade + var compErr error + for _, comp := range state.Components { + if comp.State == client.Failed { + compErr = multierror.Append(compErr, fmt.Errorf("component %s[%v] failed: %s", comp.Name, comp.ID, comp.Message)) + } + } + if compErr != nil { + failedCh <- fmt.Errorf("%w: %w", ErrAgentComponentFailed, compErr) + continue + } + } + + // nothing is failed + failedCh <- nil + } + } + } +} + +func (ch *AgentWatcher) checkFailures() bool { + if failures := ch.connectCounter; failures > statusCheckMissesAllowed { + ch.notifyChan <- fmt.Errorf("%w '%d' times in a row", ErrCannotConnect, failures) + return true + } + if failures := ch.lostCounter; failures > statusLossesAllowed { + ch.notifyChan <- fmt.Errorf("%w '%d' times in a row", ErrLostConnection, failures) + return true + } + return false +} diff --git a/internal/pkg/agent/application/upgrade/watcher_test.go b/internal/pkg/agent/application/upgrade/watcher_test.go new file mode 100644 index 00000000000..689a051d420 --- /dev/null +++ b/internal/pkg/agent/application/upgrade/watcher_test.go @@ -0,0 +1,609 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package upgrade + +import ( + "context" + "fmt" + "net" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + + "github.com/elastic/elastic-agent/pkg/control/v2/client" + "github.com/elastic/elastic-agent/pkg/control/v2/cproto" + + "github.com/elastic/elastic-agent/pkg/core/logger" +) + +func TestWatcher_CannotConnect(t *testing.T) { + // timeout ensures that if it doesn't work; it doesn't block forever + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + errCh := make(chan error) + logger, _ := logger.NewTesting("watcher") + w := NewAgentWatcher(errCh, logger, 1*time.Millisecond) + go w.Run(ctx) + + select { + case <-ctx.Done(): + require.NoError(t, ctx.Err()) + case err := <-errCh: + assert.ErrorIs(t, err, ErrCannotConnect) + } +} + +func TestWatcher_LostConnection(t *testing.T) { + // timeout ensures that if it doesn't work; it doesn't block forever + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + errCh := make(chan error) + logger, _ := logger.NewTesting("watcher") + w := NewAgentWatcher(errCh, logger, 1*time.Millisecond) + + // error on watch (counts as lost connect) + mockHandler := func(srv cproto.ElasticAgentControl_StateWatchServer) error { + return fmt.Errorf("forced error") + } + mock := &mockDaemon{watch: mockHandler} + require.NoError(t, mock.Start()) + defer mock.Stop() + + // set client to mock; before running + w.agentClient = mock.Client() + go w.Run(ctx) + + select { + case <-ctx.Done(): + require.NoError(t, ctx.Err()) + case err := <-errCh: + assert.ErrorIs(t, err, ErrLostConnection) + } +} + +func TestWatcher_PIDChange(t *testing.T) { + // timeout ensures that if it doesn't work; it doesn't block forever + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + errCh := make(chan error) + logger, _ := logger.NewTesting("watcher") + w := NewAgentWatcher(errCh, logger, 1*time.Millisecond) + + // error on watch (counts as lost connect) + mockHandler := func(srv cproto.ElasticAgentControl_StateWatchServer) error { + // starts with PID 1 + err := srv.Send(&cproto.StateResponse{ + Info: &cproto.StateAgentInfo{ + Pid: 1, + }, + State: cproto.State_HEALTHY, + Message: "healthy", + }) + if err != nil { + return err + } + // now with PID 2 + err = srv.Send(&cproto.StateResponse{ + Info: &cproto.StateAgentInfo{ + Pid: 2, + }, + State: cproto.State_HEALTHY, + Message: "healthy", + }) + if err != nil { + return err + } + // now with PID 3 + err = srv.Send(&cproto.StateResponse{ + Info: &cproto.StateAgentInfo{ + Pid: 3, + }, + State: cproto.State_HEALTHY, + Message: "healthy", + }) + if err != nil { + return err + } + // now with PID 4 + err = srv.Send(&cproto.StateResponse{ + Info: &cproto.StateAgentInfo{ + Pid: 4, + }, + State: cproto.State_HEALTHY, + Message: "healthy", + }) + if err != nil { + return err + } + // keep open until end (exiting will count as a lost connection) + <-ctx.Done() + return nil + } + mock := &mockDaemon{watch: mockHandler} + require.NoError(t, mock.Start()) + defer mock.Stop() + + // set client to mock; before running + w.agentClient = mock.Client() + go w.Run(ctx) + + select { + case <-ctx.Done(): + require.NoError(t, ctx.Err()) + case err := <-errCh: + assert.ErrorIs(t, err, ErrLostConnection) + } +} + +func TestWatcher_PIDChangeSuccess(t *testing.T) { + // test tests for success, which only happens when no error comes in + // during this time period + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + errCh := make(chan error) + logger, _ := logger.NewTesting("watcher") + w := NewAgentWatcher(errCh, logger, 1*time.Millisecond) + + // error on watch (counts as lost connect) + mockHandler := func(srv cproto.ElasticAgentControl_StateWatchServer) error { + // starts with PID 1 + err := srv.Send(&cproto.StateResponse{ + Info: &cproto.StateAgentInfo{ + Pid: 1, + }, + State: cproto.State_HEALTHY, + Message: "healthy", + }) + if err != nil { + return err + } + // now with PID 2 + err = srv.Send(&cproto.StateResponse{ + Info: &cproto.StateAgentInfo{ + Pid: 2, + }, + State: cproto.State_HEALTHY, + Message: "healthy", + }) + if err != nil { + return err + } + // now with PID 3 + err = srv.Send(&cproto.StateResponse{ + Info: &cproto.StateAgentInfo{ + Pid: 3, + }, + State: cproto.State_HEALTHY, + Message: "healthy", + }) + if err != nil { + return err + } + // still with PID 3 + err = srv.Send(&cproto.StateResponse{ + Info: &cproto.StateAgentInfo{ + Pid: 3, + }, + State: cproto.State_HEALTHY, + Message: "healthy", + }) + if err != nil { + return err + } + // still with PID 3 + err = srv.Send(&cproto.StateResponse{ + Info: &cproto.StateAgentInfo{ + Pid: 3, + }, + State: cproto.State_HEALTHY, + Message: "healthy", + }) + if err != nil { + return err + } + // keep open until end (exiting will count as a lost connection) + <-ctx.Done() + return nil + } + mock := &mockDaemon{watch: mockHandler} + require.NoError(t, mock.Start()) + defer mock.Stop() + + // set client to mock; before running + w.agentClient = mock.Client() + go w.Run(ctx) + + select { + case <-ctx.Done(): + require.ErrorIs(t, ctx.Err(), context.DeadlineExceeded) + case err := <-errCh: + assert.NoError(t, err, "error should not have been reported") + } +} + +func TestWatcher_AgentError(t *testing.T) { + // timeout ensures that if it doesn't work; it doesn't block forever + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + errCh := make(chan error) + logger, _ := logger.NewTesting("watcher") + w := NewAgentWatcher(errCh, logger, 100*time.Millisecond) + + // reports only an error state, triggers failed + mockHandler := func(srv cproto.ElasticAgentControl_StateWatchServer) error { + err := srv.Send(&cproto.StateResponse{ + Info: &cproto.StateAgentInfo{}, + State: cproto.State_FAILED, + Message: "force failure", + }) + if err != nil { + return err + } + // keep open until end (exiting will count as a lost connection) + <-ctx.Done() + return nil + } + mock := &mockDaemon{watch: mockHandler} + require.NoError(t, mock.Start()) + defer mock.Stop() + + // set client to mock; before running + w.agentClient = mock.Client() + go w.Run(ctx) + + select { + case <-ctx.Done(): + require.NoError(t, ctx.Err()) + case err := <-errCh: + assert.ErrorIs(t, err, ErrAgentStatusFailed) + } +} + +func TestWatcher_AgentErrorQuick(t *testing.T) { + // test tests for success, which only happens when no error comes in + // during this time period + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + errCh := make(chan error) + logger, _ := logger.NewTesting("watcher") + w := NewAgentWatcher(errCh, logger, 100*time.Millisecond) + + // reports an error state, followed by a healthy state (should not error) + mockHandler := func(srv cproto.ElasticAgentControl_StateWatchServer) error { + err := srv.Send(&cproto.StateResponse{ + Info: &cproto.StateAgentInfo{}, + State: cproto.State_FAILED, + Message: "force failure", + }) + if err != nil { + return err + } + err = srv.Send(&cproto.StateResponse{ + Info: &cproto.StateAgentInfo{}, + State: cproto.State_HEALTHY, + Message: "healthy", + }) + if err != nil { + return err + } + // keep open until end (exiting will count as a lost connection) + <-ctx.Done() + return nil + } + mock := &mockDaemon{watch: mockHandler} + require.NoError(t, mock.Start()) + defer mock.Stop() + + // set client to mock; before running + w.agentClient = mock.Client() + go w.Run(ctx) + + select { + case <-ctx.Done(): + require.ErrorIs(t, ctx.Err(), context.DeadlineExceeded) + case err := <-errCh: + assert.NoError(t, err, "error should not have been reported") + } +} + +func TestWatcher_ComponentError(t *testing.T) { + // timeout ensures that if it doesn't work; it doesn't block forever + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + errCh := make(chan error) + logger, _ := logger.NewTesting("watcher") + w := NewAgentWatcher(errCh, logger, 100*time.Millisecond) + + // reports only an error state, triggers failed + mockHandler := func(srv cproto.ElasticAgentControl_StateWatchServer) error { + err := srv.Send(&cproto.StateResponse{ + Info: &cproto.StateAgentInfo{}, + State: cproto.State_HEALTHY, + Message: "healthy", + Components: []*cproto.ComponentState{ + { + Id: "component-0", + Name: "component-0", + State: cproto.State_HEALTHY, + Message: "healthy", + }, + { + Id: "component-1", + Name: "component-1", + State: cproto.State_FAILED, + Message: "force error", + }, + { + Id: "component-2", + Name: "component-2", + State: cproto.State_HEALTHY, + Message: "healthy", + }, + }, + }) + if err != nil { + return err + } + // keep open until end (exiting will count as a lost connection) + <-ctx.Done() + return nil + } + mock := &mockDaemon{watch: mockHandler} + require.NoError(t, mock.Start()) + defer mock.Stop() + + // set client to mock; before running + w.agentClient = mock.Client() + go w.Run(ctx) + + select { + case <-ctx.Done(): + require.NoError(t, ctx.Err()) + case err := <-errCh: + assert.ErrorIs(t, err, ErrAgentComponentFailed) + } +} + +func TestWatcher_ComponentErrorQuick(t *testing.T) { + // test tests for success, which only happens when no error comes in + // during this time period + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + errCh := make(chan error) + logger, _ := logger.NewTesting("watcher") + w := NewAgentWatcher(errCh, logger, 100*time.Millisecond) + + // reports an error state, followed by a healthy state (should not error) + mockHandler := func(srv cproto.ElasticAgentControl_StateWatchServer) error { + err := srv.Send(&cproto.StateResponse{ + Info: &cproto.StateAgentInfo{}, + State: cproto.State_HEALTHY, + Message: "healthy", + Components: []*cproto.ComponentState{ + { + Id: "component-0", + Name: "component-0", + State: cproto.State_HEALTHY, + Message: "healthy", + }, + { + Id: "component-1", + Name: "component-1", + State: cproto.State_FAILED, + Message: "force error", + }, + { + Id: "component-2", + Name: "component-2", + State: cproto.State_HEALTHY, + Message: "healthy", + }, + }, + }) + if err != nil { + return err + } + err = srv.Send(&cproto.StateResponse{ + Info: &cproto.StateAgentInfo{}, + State: cproto.State_HEALTHY, + Message: "healthy", + Components: []*cproto.ComponentState{ + { + Id: "component-0", + Name: "component-0", + State: cproto.State_HEALTHY, + Message: "healthy", + }, + { + Id: "component-1", + Name: "component-1", + State: cproto.State_HEALTHY, + Message: "healthy", + }, + { + Id: "component-2", + Name: "component-2", + State: cproto.State_HEALTHY, + Message: "healthy", + }, + }, + }) + if err != nil { + return err + } + // keep open until end (exiting will count as a lost connection) + <-ctx.Done() + return nil + } + mock := &mockDaemon{watch: mockHandler} + require.NoError(t, mock.Start()) + defer mock.Stop() + + // set client to mock; before running + w.agentClient = mock.Client() + go w.Run(ctx) + + select { + case <-ctx.Done(): + require.ErrorIs(t, ctx.Err(), context.DeadlineExceeded) + case err := <-errCh: + assert.NoError(t, err, "error should not have been reported") + } +} + +func TestWatcher_AgentErrorFlipFlop(t *testing.T) { + // timeout ensures that if it doesn't work; it doesn't block forever + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + errCh := make(chan error) + logger, _ := logger.NewTesting("watcher") + w := NewAgentWatcher(errCh, logger, 300*time.Millisecond) + + // reports only an error state, triggers failed + mockHandler := func(srv cproto.ElasticAgentControl_StateWatchServer) error { + err := srv.Send(&cproto.StateResponse{ + Info: &cproto.StateAgentInfo{}, + State: cproto.State_HEALTHY, + Message: "healthy", + }) + if err != nil { + return err + } + err = srv.Send(&cproto.StateResponse{ + Info: &cproto.StateAgentInfo{}, + State: cproto.State_FAILED, + Message: "force failure", + }) + if err != nil { + return err + } + err = srv.Send(&cproto.StateResponse{ + Info: &cproto.StateAgentInfo{}, + State: cproto.State_HEALTHY, + Message: "healthy", + }) + if err != nil { + return err + } + err = srv.Send(&cproto.StateResponse{ + Info: &cproto.StateAgentInfo{}, + State: cproto.State_FAILED, + Message: "force failure", + }) + if err != nil { + return err + } + err = srv.Send(&cproto.StateResponse{ + Info: &cproto.StateAgentInfo{}, + State: cproto.State_HEALTHY, + Message: "healthy", + }) + if err != nil { + return err + } + err = srv.Send(&cproto.StateResponse{ + Info: &cproto.StateAgentInfo{}, + State: cproto.State_FAILED, + Message: "force failure", + }) + if err != nil { + return err + } + err = srv.Send(&cproto.StateResponse{ + Info: &cproto.StateAgentInfo{}, + State: cproto.State_HEALTHY, + Message: "healthy", + }) + if err != nil { + return err + } + err = srv.Send(&cproto.StateResponse{ + Info: &cproto.StateAgentInfo{}, + State: cproto.State_FAILED, + Message: "force failure", + }) + if err != nil { + return err + } + err = srv.Send(&cproto.StateResponse{ + Info: &cproto.StateAgentInfo{}, + State: cproto.State_HEALTHY, + Message: "healthy", + }) + if err != nil { + return err + } + // keep open until end (exiting will count as a lost connection) + <-ctx.Done() + return nil + } + mock := &mockDaemon{watch: mockHandler} + require.NoError(t, mock.Start()) + defer mock.Stop() + + // set client to mock; before running + w.agentClient = mock.Client() + go w.Run(ctx) + + select { + case <-ctx.Done(): + require.NoError(t, ctx.Err()) + case err := <-errCh: + assert.ErrorIs(t, err, ErrAgentFlipFlopFailed) + } +} + +type mockStateWatch func(srv cproto.ElasticAgentControl_StateWatchServer) error + +type mockDaemon struct { + cproto.UnimplementedElasticAgentControlServer + + port int + server *grpc.Server + + watch mockStateWatch +} + +func (s *mockDaemon) Start(opt ...grpc.ServerOption) error { + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", s.port)) + if err != nil { + return err + } + s.port = lis.Addr().(*net.TCPAddr).Port + srv := grpc.NewServer(opt...) + s.server = srv + cproto.RegisterElasticAgentControlServer(s.server, s) + go func() { + _ = srv.Serve(lis) + }() + return nil +} + +func (s *mockDaemon) Stop() { + if s.server != nil { + s.server.Stop() + s.server = nil + } +} + +func (s *mockDaemon) Client() client.Client { + return client.New(client.WithAddress(fmt.Sprintf("http://localhost:%d", s.port))) +} + +func (s *mockDaemon) StateWatch(_ *cproto.Empty, srv cproto.ElasticAgentControl_StateWatchServer) error { + return s.watch(srv) +} diff --git a/internal/pkg/agent/cmd/watch.go b/internal/pkg/agent/cmd/watch.go index 3cf84035df9..45648752eda 100644 --- a/internal/pkg/agent/cmd/watch.go +++ b/internal/pkg/agent/cmd/watch.go @@ -105,9 +105,8 @@ func watchCmd(log *logp.Logger, cfg *configuration.Configuration) error { } errorCheckInterval := cfg.Settings.Upgrade.Watcher.ErrorCheck.Interval - crashCheckInterval := cfg.Settings.Upgrade.Watcher.CrashCheck.Interval ctx := context.Background() - if err := watch(ctx, tilGrace, errorCheckInterval, crashCheckInterval, log); err != nil { + if err := watch(ctx, tilGrace, errorCheckInterval, log); err != nil { log.Error("Error detected proceeding to rollback: %v", err) err = upgrade.Rollback(ctx, log, marker.PrevHash, marker.Hash) if err != nil { @@ -134,9 +133,8 @@ func isWindows() bool { return runtime.GOOS == "windows" } -func watch(ctx context.Context, tilGrace time.Duration, errorCheckInterval, crashCheckInterval time.Duration, log *logger.Logger) error { +func watch(ctx context.Context, tilGrace time.Duration, errorCheckInterval time.Duration, log *logger.Logger) error { errChan := make(chan error) - crashChan := make(chan error) ctx, cancel := context.WithCancel(ctx) @@ -144,21 +142,10 @@ func watch(ctx context.Context, tilGrace time.Duration, errorCheckInterval, cras defer func() { cancel() close(errChan) - close(crashChan) }() - errorChecker, err := upgrade.NewErrorChecker(errChan, log, errorCheckInterval) - if err != nil { - return err - } - - crashChecker, err := upgrade.NewCrashChecker(ctx, crashChan, log, crashCheckInterval) - if err != nil { - return err - } - - go errorChecker.Run(ctx) - go crashChecker.Run(ctx) + agentWatcher := upgrade.NewAgentWatcher(errChan, log, errorCheckInterval) + go agentWatcher.Run(ctx) signals := make(chan os.Signal, 1) signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGHUP) @@ -182,10 +169,6 @@ WATCHLOOP: case err := <-errChan: log.Errorf("Agent Error detected: %s", err.Error()) return err - // Agent keeps crashing unexpectedly - case err := <-crashChan: - log.Errorf("Agent crash detected: %s", err.Error()) - return err } } diff --git a/internal/pkg/agent/configuration/upgrade.go b/internal/pkg/agent/configuration/upgrade.go index d8528ec38f0..02fa52a695b 100644 --- a/internal/pkg/agent/configuration/upgrade.go +++ b/internal/pkg/agent/configuration/upgrade.go @@ -12,9 +12,6 @@ const ( // interval between checks for new (upgraded) Agent returning an error status. defaultStatusCheckInterval = 30 * time.Second - - // interval between checks for new (upgraded) Agent crashing. - defaultCrashCheckInterval = 10 * time.Second ) // UpgradeConfig is the configuration related to Agent upgrades. @@ -25,7 +22,6 @@ type UpgradeConfig struct { type UpgradeWatcherConfig struct { GracePeriod time.Duration `yaml:"grace_period" config:"grace_period" json:"grace_period"` ErrorCheck UpgradeWatcherCheckConfig `yaml:"error_check" config:"error_check" json:"error_check"` - CrashCheck UpgradeWatcherCheckConfig `yaml:"crash_check" config:"crash_check" json:"crash_check"` } type UpgradeWatcherCheckConfig struct { Interval time.Duration `yaml:"interval" config:"interval" json:"interval"` @@ -38,9 +34,6 @@ func DefaultUpgradeConfig() *UpgradeConfig { ErrorCheck: UpgradeWatcherCheckConfig{ Interval: defaultStatusCheckInterval, }, - CrashCheck: UpgradeWatcherCheckConfig{ - Interval: defaultCrashCheckInterval, - }, }, } } diff --git a/internal/pkg/agent/install/install.go b/internal/pkg/agent/install/install.go index 34e1405c5cd..08617db2fcc 100644 --- a/internal/pkg/agent/install/install.go +++ b/internal/pkg/agent/install/install.go @@ -11,6 +11,8 @@ import ( "runtime" "strings" + "github.com/kardianos/service" + "github.com/jaypipes/ghw" "github.com/otiai10/copy" @@ -216,6 +218,15 @@ func RestartService(topPath string) error { return nil } +// StatusService returns the status of the service. +func StatusService(topPath string) (service.Status, error) { + svc, err := newService(topPath) + if err != nil { + return service.StatusUnknown, err + } + return svc.Status() +} + // FixPermissions fixes the permissions on the installed system. func FixPermissions(topPath string) error { return fixPermissions(topPath) diff --git a/pkg/control/v1/proto/control_v1.pb.go b/pkg/control/v1/proto/control_v1.pb.go index a99fd51b1ab..59b8b4fb9f0 100644 --- a/pkg/control/v1/proto/control_v1.pb.go +++ b/pkg/control/v1/proto/control_v1.pb.go @@ -4,8 +4,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.21.9 +// protoc-gen-go v1.31.0 +// protoc v4.23.4 // 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 43e62f56985..bd4536b5578 100644 --- a/pkg/control/v1/proto/control_v1_grpc.pb.go +++ b/pkg/control/v1/proto/control_v1_grpc.pb.go @@ -4,10 +4,15 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.2.0 -// - protoc v3.21.9 +// - protoc-gen-go-grpc v1.3.0 +// - protoc v4.23.4 // source: control_v1.proto +// proto namespace/package name is shared with elastic-agent-client +// we need to be careful with modifications to avoid name collisions +// proto is here to maintain backward compatibility and cannot be changed. +// elastic-agent-client namespace is likely change after 8.6 + package proto import ( @@ -23,6 +28,13 @@ import ( // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 +const ( + ElasticAgentControl_Version_FullMethodName = "/proto.ElasticAgentControl/Version" + ElasticAgentControl_Status_FullMethodName = "/proto.ElasticAgentControl/Status" + ElasticAgentControl_Restart_FullMethodName = "/proto.ElasticAgentControl/Restart" + ElasticAgentControl_Upgrade_FullMethodName = "/proto.ElasticAgentControl/Upgrade" +) + // ElasticAgentControlClient is the client API for ElasticAgentControl service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. @@ -47,7 +59,7 @@ func NewElasticAgentControlClient(cc grpc.ClientConnInterface) ElasticAgentContr func (c *elasticAgentControlClient) Version(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VersionResponse, error) { out := new(VersionResponse) - err := c.cc.Invoke(ctx, "/proto.ElasticAgentControl/Version", in, out, opts...) + err := c.cc.Invoke(ctx, ElasticAgentControl_Version_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -56,7 +68,7 @@ func (c *elasticAgentControlClient) Version(ctx context.Context, in *Empty, opts func (c *elasticAgentControlClient) Status(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*StatusResponse, error) { out := new(StatusResponse) - err := c.cc.Invoke(ctx, "/proto.ElasticAgentControl/Status", in, out, opts...) + err := c.cc.Invoke(ctx, ElasticAgentControl_Status_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -65,7 +77,7 @@ func (c *elasticAgentControlClient) Status(ctx context.Context, in *Empty, opts func (c *elasticAgentControlClient) Restart(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*RestartResponse, error) { out := new(RestartResponse) - err := c.cc.Invoke(ctx, "/proto.ElasticAgentControl/Restart", in, out, opts...) + err := c.cc.Invoke(ctx, ElasticAgentControl_Restart_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -74,7 +86,7 @@ func (c *elasticAgentControlClient) Restart(ctx context.Context, in *Empty, opts func (c *elasticAgentControlClient) Upgrade(ctx context.Context, in *UpgradeRequest, opts ...grpc.CallOption) (*UpgradeResponse, error) { out := new(UpgradeResponse) - err := c.cc.Invoke(ctx, "/proto.ElasticAgentControl/Upgrade", in, out, opts...) + err := c.cc.Invoke(ctx, ElasticAgentControl_Upgrade_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -135,7 +147,7 @@ func _ElasticAgentControl_Version_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/proto.ElasticAgentControl/Version", + FullMethod: ElasticAgentControl_Version_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ElasticAgentControlServer).Version(ctx, req.(*Empty)) @@ -153,7 +165,7 @@ func _ElasticAgentControl_Status_Handler(srv interface{}, ctx context.Context, d } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/proto.ElasticAgentControl/Status", + FullMethod: ElasticAgentControl_Status_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ElasticAgentControlServer).Status(ctx, req.(*Empty)) @@ -171,7 +183,7 @@ func _ElasticAgentControl_Restart_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/proto.ElasticAgentControl/Restart", + FullMethod: ElasticAgentControl_Restart_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ElasticAgentControlServer).Restart(ctx, req.(*Empty)) @@ -189,7 +201,7 @@ func _ElasticAgentControl_Upgrade_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/proto.ElasticAgentControl/Upgrade", + FullMethod: ElasticAgentControl_Upgrade_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ElasticAgentControlServer).Upgrade(ctx, req.(*UpgradeRequest)) diff --git a/pkg/control/v2/client/client.go b/pkg/control/v2/client/client.go index c2f593440fc..c3421b7a6b0 100644 --- a/pkg/control/v2/client/client.go +++ b/pkg/control/v2/client/client.go @@ -13,10 +13,11 @@ import ( "sync" "time" - "github.com/elastic/elastic-agent/pkg/control" - "github.com/elastic/elastic-agent/pkg/control/v2/cproto" + "google.golang.org/grpc" "github.com/elastic/elastic-agent/internal/pkg/agent/configuration" + "github.com/elastic/elastic-agent/pkg/control" + "github.com/elastic/elastic-agent/pkg/control/v2/cproto" ) // UnitType is the type of the unit @@ -105,6 +106,7 @@ type AgentStateInfo struct { Commit string `json:"commit" yaml:"commit"` BuildTime string `json:"build_time" yaml:"build_time"` Snapshot bool `json:"snapshot" yaml:"snapshot"` + PID int32 `json:"pid" yaml:"pid"` } // AgentState is the current state of the Elastic Agent. @@ -156,10 +158,11 @@ type DiagnosticComponentResult struct { } // Client communicates to Elastic Agent through the control protocol. -// go:generate mockery --name Client +// +//go:generate mockery --name Client type Client interface { // Connect connects to the running Elastic Agent. - Connect(ctx context.Context) error + Connect(ctx context.Context, opts ...grpc.DialOption) error // Disconnect disconnects from the running Elastic Agent. Disconnect() // Version returns the current version of the running agent. @@ -231,9 +234,9 @@ func New(opts ...Option) Client { } // Connect connects to the running Elastic Agent. -func (c *client) Connect(ctx context.Context) error { +func (c *client) Connect(ctx context.Context, opts ...grpc.DialOption) error { c.ctx, c.cancel = context.WithCancel(ctx) - conn, err := dialContext(ctx, c.address, c.maxMsgSize) + conn, err := dialContext(ctx, c.address, c.maxMsgSize, opts...) if err != nil { return err } @@ -470,6 +473,7 @@ func toState(res *cproto.StateResponse) (*AgentState, error) { Commit: res.Info.Commit, BuildTime: res.Info.BuildTime, Snapshot: res.Info.Snapshot, + PID: res.Info.Pid, }, State: res.State, Message: res.Message, diff --git a/pkg/control/v2/client/dial.go b/pkg/control/v2/client/dial.go index dde7967d360..694c2355d79 100644 --- a/pkg/control/v2/client/dial.go +++ b/pkg/control/v2/client/dial.go @@ -15,17 +15,19 @@ import ( "google.golang.org/grpc/credentials/insecure" ) -func dialContext(ctx context.Context, address string, maxMsgSize int) (*grpc.ClientConn, error) { - return grpc.DialContext( - ctx, - strings.TrimPrefix(address, "unix://"), +func dialContext(ctx context.Context, address string, maxMsgSize int, opts ...grpc.DialOption) (*grpc.ClientConn, error) { + opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithContextDialer(dialer), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxMsgSize)), ) + return grpc.DialContext(ctx, address, opts...) } func dialer(ctx context.Context, addr string) (net.Conn, error) { var d net.Dialer - return d.DialContext(ctx, "unix", addr) + if strings.HasPrefix(addr, "http://") { + return d.DialContext(ctx, "tcp", strings.TrimPrefix(addr, "http://")) + } + return d.DialContext(ctx, "unix", strings.TrimPrefix(addr, "unix://")) } diff --git a/pkg/control/v2/client/dial_windows.go b/pkg/control/v2/client/dial_windows.go index 5c890865d57..1bd2336d2bb 100644 --- a/pkg/control/v2/client/dial_windows.go +++ b/pkg/control/v2/client/dial_windows.go @@ -9,6 +9,7 @@ package client import ( "context" "net" + "strings" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -16,16 +17,19 @@ import ( "github.com/elastic/elastic-agent-libs/api/npipe" ) -func dialContext(ctx context.Context, address string, maxMsgSize int) (*grpc.ClientConn, error) { - return grpc.DialContext( - ctx, - address, +func dialContext(ctx context.Context, address string, maxMsgSize int, opts ...grpc.DialOption) (*grpc.ClientConn, error) { + opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithContextDialer(dialer), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxMsgSize)), ) + return grpc.DialContext(ctx, address, opts...) } func dialer(ctx context.Context, addr string) (net.Conn, error) { + if strings.HasPrefix(addr, "http://") { + var d net.Dialer + return d.DialContext(ctx, "tcp", strings.TrimPrefix(addr, "http://")) + } return npipe.DialContext(addr)(ctx, "", "") } diff --git a/pkg/control/v2/client/mocks/client.go b/pkg/control/v2/client/mocks/client.go index 20d4a6a9b81..1b6668798a3 100644 --- a/pkg/control/v2/client/mocks/client.go +++ b/pkg/control/v2/client/mocks/client.go @@ -2,7 +2,7 @@ // or more contributor license agreements. Licensed under the Elastic License; // you may not use this file except in compliance with the Elastic License. -// Code generated by mockery v2.24.0. DO NOT EDIT. +// Code generated by mockery v2.36.0. DO NOT EDIT. package mocks @@ -13,6 +13,8 @@ import ( cproto "github.com/elastic/elastic-agent/pkg/control/v2/cproto" + grpc "google.golang.org/grpc" + mock "github.com/stretchr/testify/mock" ) @@ -72,13 +74,20 @@ func (_c *Client_Configure_Call) RunAndReturn(run func(context.Context, string) return _c } -// Connect provides a mock function with given fields: ctx -func (_m *Client) Connect(ctx context.Context) error { - ret := _m.Called(ctx) +// Connect provides a mock function with given fields: ctx, opts +func (_m *Client) Connect(ctx context.Context, opts ...grpc.DialOption) error { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) var r0 error - if rf, ok := ret.Get(0).(func(context.Context) error); ok { - r0 = rf(ctx) + if rf, ok := ret.Get(0).(func(context.Context, ...grpc.DialOption) error); ok { + r0 = rf(ctx, opts...) } else { r0 = ret.Error(0) } @@ -93,13 +102,21 @@ type Client_Connect_Call struct { // Connect is a helper method to define mock.On call // - ctx context.Context -func (_e *Client_Expecter) Connect(ctx interface{}) *Client_Connect_Call { - return &Client_Connect_Call{Call: _e.mock.On("Connect", ctx)} +// - opts ...grpc.DialOption +func (_e *Client_Expecter) Connect(ctx interface{}, opts ...interface{}) *Client_Connect_Call { + return &Client_Connect_Call{Call: _e.mock.On("Connect", + append([]interface{}{ctx}, opts...)...)} } -func (_c *Client_Connect_Call) Run(run func(ctx context.Context)) *Client_Connect_Call { +func (_c *Client_Connect_Call) Run(run func(ctx context.Context, opts ...grpc.DialOption)) *Client_Connect_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) + variadicArgs := make([]grpc.DialOption, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(grpc.DialOption) + } + } + run(args[0].(context.Context), variadicArgs...) }) return _c } @@ -109,7 +126,7 @@ func (_c *Client_Connect_Call) Return(_a0 error) *Client_Connect_Call { return _c } -func (_c *Client_Connect_Call) RunAndReturn(run func(context.Context) error) *Client_Connect_Call { +func (_c *Client_Connect_Call) RunAndReturn(run func(context.Context, ...grpc.DialOption) error) *Client_Connect_Call { _c.Call.Return(run) return _c } @@ -613,13 +630,12 @@ func (_c *Client_Version_Call) RunAndReturn(run func(context.Context) (client.Ve return _c } -type mockConstructorTestingTNewClient interface { +// NewClient creates a new instance of Client. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewClient(t interface { mock.TestingT Cleanup(func()) -} - -// NewClient creates a new instance of Client. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewClient(t mockConstructorTestingTNewClient) *Client { +}) *Client { mock := &Client{} mock.Mock.Test(t) diff --git a/pkg/control/v2/cproto/control_v2.pb.go b/pkg/control/v2/cproto/control_v2.pb.go index d1a771ae627..143ce906ba5 100644 --- a/pkg/control/v2/cproto/control_v2.pb.go +++ b/pkg/control/v2/cproto/control_v2.pb.go @@ -4,8 +4,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.1 -// protoc v3.21.9 +// protoc-gen-go v1.31.0 +// protoc v4.23.4 // source: control_v2.proto package cproto @@ -899,6 +899,8 @@ type StateAgentInfo struct { BuildTime string `protobuf:"bytes,4,opt,name=buildTime,proto3" json:"buildTime,omitempty"` // Current running version is a snapshot. Snapshot bool `protobuf:"varint,5,opt,name=snapshot,proto3" json:"snapshot,omitempty"` + // Current running PID. + Pid int32 `protobuf:"varint,6,opt,name=pid,proto3" json:"pid,omitempty"` } func (x *StateAgentInfo) Reset() { @@ -968,6 +970,13 @@ func (x *StateAgentInfo) GetSnapshot() bool { return false } +func (x *StateAgentInfo) GetPid() int32 { + if x != nil { + return x.Pid + } + return 0 +} + // StateResponse is the current state of Elastic Agent. // Next unused id: 7 type StateResponse struct { @@ -1801,7 +1810,7 @@ var file_control_v2_proto_rawDesc = []byte{ 0x6e, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0b, 0x76, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x8c, 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x9e, 0x01, 0x0a, 0x0e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, @@ -1810,174 +1819,175 @@ var file_control_v2_proto_rawDesc = []byte{ 0x62, 0x75, 0x69, 0x6c, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x73, 0x6e, - 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x22, 0x85, 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, 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, + 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x03, 0x70, 0x69, 0x64, 0x22, 0x85, 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, + 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, 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, + 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, 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, 0x26, 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, 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, - 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, + 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, 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, + 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, 0x26, 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, + 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, 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 ( diff --git a/pkg/control/v2/cproto/control_v2_grpc.pb.go b/pkg/control/v2/cproto/control_v2_grpc.pb.go index 32f675c34a1..286fc0bb749 100644 --- a/pkg/control/v2/cproto/control_v2_grpc.pb.go +++ b/pkg/control/v2/cproto/control_v2_grpc.pb.go @@ -4,8 +4,8 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.2.0 -// - protoc v3.21.9 +// - protoc-gen-go-grpc v1.3.0 +// - protoc v4.23.4 // source: control_v2.proto package cproto @@ -23,6 +23,18 @@ import ( // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 +const ( + ElasticAgentControl_Version_FullMethodName = "/cproto.ElasticAgentControl/Version" + ElasticAgentControl_State_FullMethodName = "/cproto.ElasticAgentControl/State" + ElasticAgentControl_StateWatch_FullMethodName = "/cproto.ElasticAgentControl/StateWatch" + ElasticAgentControl_Restart_FullMethodName = "/cproto.ElasticAgentControl/Restart" + ElasticAgentControl_Upgrade_FullMethodName = "/cproto.ElasticAgentControl/Upgrade" + ElasticAgentControl_DiagnosticAgent_FullMethodName = "/cproto.ElasticAgentControl/DiagnosticAgent" + ElasticAgentControl_DiagnosticUnits_FullMethodName = "/cproto.ElasticAgentControl/DiagnosticUnits" + ElasticAgentControl_DiagnosticComponents_FullMethodName = "/cproto.ElasticAgentControl/DiagnosticComponents" + ElasticAgentControl_Configure_FullMethodName = "/cproto.ElasticAgentControl/Configure" +) + // ElasticAgentControlClient is the client API for ElasticAgentControl service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. @@ -65,7 +77,7 @@ func NewElasticAgentControlClient(cc grpc.ClientConnInterface) ElasticAgentContr func (c *elasticAgentControlClient) Version(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VersionResponse, error) { out := new(VersionResponse) - err := c.cc.Invoke(ctx, "/cproto.ElasticAgentControl/Version", in, out, opts...) + err := c.cc.Invoke(ctx, ElasticAgentControl_Version_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -74,7 +86,7 @@ func (c *elasticAgentControlClient) Version(ctx context.Context, in *Empty, opts func (c *elasticAgentControlClient) State(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*StateResponse, error) { out := new(StateResponse) - err := c.cc.Invoke(ctx, "/cproto.ElasticAgentControl/State", in, out, opts...) + err := c.cc.Invoke(ctx, ElasticAgentControl_State_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -82,7 +94,7 @@ func (c *elasticAgentControlClient) State(ctx context.Context, in *Empty, opts . } func (c *elasticAgentControlClient) StateWatch(ctx context.Context, in *Empty, opts ...grpc.CallOption) (ElasticAgentControl_StateWatchClient, error) { - stream, err := c.cc.NewStream(ctx, &ElasticAgentControl_ServiceDesc.Streams[0], "/cproto.ElasticAgentControl/StateWatch", opts...) + stream, err := c.cc.NewStream(ctx, &ElasticAgentControl_ServiceDesc.Streams[0], ElasticAgentControl_StateWatch_FullMethodName, opts...) if err != nil { return nil, err } @@ -115,7 +127,7 @@ func (x *elasticAgentControlStateWatchClient) Recv() (*StateResponse, error) { func (c *elasticAgentControlClient) Restart(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*RestartResponse, error) { out := new(RestartResponse) - err := c.cc.Invoke(ctx, "/cproto.ElasticAgentControl/Restart", in, out, opts...) + err := c.cc.Invoke(ctx, ElasticAgentControl_Restart_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -124,7 +136,7 @@ func (c *elasticAgentControlClient) Restart(ctx context.Context, in *Empty, opts func (c *elasticAgentControlClient) Upgrade(ctx context.Context, in *UpgradeRequest, opts ...grpc.CallOption) (*UpgradeResponse, error) { out := new(UpgradeResponse) - err := c.cc.Invoke(ctx, "/cproto.ElasticAgentControl/Upgrade", in, out, opts...) + err := c.cc.Invoke(ctx, ElasticAgentControl_Upgrade_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -133,7 +145,7 @@ func (c *elasticAgentControlClient) Upgrade(ctx context.Context, in *UpgradeRequ func (c *elasticAgentControlClient) DiagnosticAgent(ctx context.Context, in *DiagnosticAgentRequest, opts ...grpc.CallOption) (*DiagnosticAgentResponse, error) { out := new(DiagnosticAgentResponse) - err := c.cc.Invoke(ctx, "/cproto.ElasticAgentControl/DiagnosticAgent", in, out, opts...) + err := c.cc.Invoke(ctx, ElasticAgentControl_DiagnosticAgent_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -141,7 +153,7 @@ func (c *elasticAgentControlClient) DiagnosticAgent(ctx context.Context, in *Dia } func (c *elasticAgentControlClient) DiagnosticUnits(ctx context.Context, in *DiagnosticUnitsRequest, opts ...grpc.CallOption) (ElasticAgentControl_DiagnosticUnitsClient, error) { - stream, err := c.cc.NewStream(ctx, &ElasticAgentControl_ServiceDesc.Streams[1], "/cproto.ElasticAgentControl/DiagnosticUnits", opts...) + stream, err := c.cc.NewStream(ctx, &ElasticAgentControl_ServiceDesc.Streams[1], ElasticAgentControl_DiagnosticUnits_FullMethodName, opts...) if err != nil { return nil, err } @@ -173,7 +185,7 @@ func (x *elasticAgentControlDiagnosticUnitsClient) Recv() (*DiagnosticUnitRespon } func (c *elasticAgentControlClient) DiagnosticComponents(ctx context.Context, in *DiagnosticComponentsRequest, opts ...grpc.CallOption) (ElasticAgentControl_DiagnosticComponentsClient, error) { - stream, err := c.cc.NewStream(ctx, &ElasticAgentControl_ServiceDesc.Streams[2], "/cproto.ElasticAgentControl/DiagnosticComponents", opts...) + stream, err := c.cc.NewStream(ctx, &ElasticAgentControl_ServiceDesc.Streams[2], ElasticAgentControl_DiagnosticComponents_FullMethodName, opts...) if err != nil { return nil, err } @@ -206,7 +218,7 @@ func (x *elasticAgentControlDiagnosticComponentsClient) Recv() (*DiagnosticCompo func (c *elasticAgentControlClient) Configure(ctx context.Context, in *ConfigureRequest, opts ...grpc.CallOption) (*Empty, error) { out := new(Empty) - err := c.cc.Invoke(ctx, "/cproto.ElasticAgentControl/Configure", in, out, opts...) + err := c.cc.Invoke(ctx, ElasticAgentControl_Configure_FullMethodName, in, out, opts...) if err != nil { return nil, err } @@ -300,7 +312,7 @@ func _ElasticAgentControl_Version_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/cproto.ElasticAgentControl/Version", + FullMethod: ElasticAgentControl_Version_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ElasticAgentControlServer).Version(ctx, req.(*Empty)) @@ -318,7 +330,7 @@ func _ElasticAgentControl_State_Handler(srv interface{}, ctx context.Context, de } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/cproto.ElasticAgentControl/State", + FullMethod: ElasticAgentControl_State_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ElasticAgentControlServer).State(ctx, req.(*Empty)) @@ -357,7 +369,7 @@ func _ElasticAgentControl_Restart_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/cproto.ElasticAgentControl/Restart", + FullMethod: ElasticAgentControl_Restart_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ElasticAgentControlServer).Restart(ctx, req.(*Empty)) @@ -375,7 +387,7 @@ func _ElasticAgentControl_Upgrade_Handler(srv interface{}, ctx context.Context, } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/cproto.ElasticAgentControl/Upgrade", + FullMethod: ElasticAgentControl_Upgrade_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ElasticAgentControlServer).Upgrade(ctx, req.(*UpgradeRequest)) @@ -393,7 +405,7 @@ func _ElasticAgentControl_DiagnosticAgent_Handler(srv interface{}, ctx context.C } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/cproto.ElasticAgentControl/DiagnosticAgent", + FullMethod: ElasticAgentControl_DiagnosticAgent_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ElasticAgentControlServer).DiagnosticAgent(ctx, req.(*DiagnosticAgentRequest)) @@ -453,7 +465,7 @@ func _ElasticAgentControl_Configure_Handler(srv interface{}, ctx context.Context } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/cproto.ElasticAgentControl/Configure", + FullMethod: ElasticAgentControl_Configure_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(ElasticAgentControlServer).Configure(ctx, req.(*ConfigureRequest)) diff --git a/pkg/control/v2/server/server.go b/pkg/control/v2/server/server.go index 773468d33b1..a130e658f19 100644 --- a/pkg/control/v2/server/server.go +++ b/pkg/control/v2/server/server.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "net" + "os" "time" "github.com/elastic/elastic-agent/pkg/control" @@ -368,6 +369,7 @@ func stateToProto(state *coordinator.State, agentInfo *info.AgentInfo) (*cproto. Commit: release.Commit(), BuildTime: release.BuildTime().Format(control.TimeFormat()), Snapshot: release.Snapshot(), + Pid: int32(os.Getpid()), }, State: state.State, Message: state.Message, diff --git a/testing/integration/upgrade_rollback_test.go b/testing/integration/upgrade_rollback_test.go index 3226ae92a51..02a209e18eb 100644 --- a/testing/integration/upgrade_rollback_test.go +++ b/testing/integration/upgrade_rollback_test.go @@ -15,6 +15,8 @@ import ( "testing" "time" + "github.com/kardianos/service" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -26,6 +28,12 @@ import ( "github.com/elastic/elastic-agent/testing/upgradetest" ) +const reallyFastWatcherCfg = ` +agent.upgrade.watcher: + grace_period: 1m + error_check.interval: 5s +` + // TestStandaloneUpgradeRollback tests the scenario where upgrading to a new version // of Agent fails due to the new Agent binary reporting an unhealthy status. It checks // that the Agent is rolled back to the previous version. @@ -38,28 +46,28 @@ func TestStandaloneUpgradeRollback(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - // Start at the build version as we want to test the retry - // logic that is in the build. - startFixture, err := define.NewFixture(t, define.Version()) - require.NoError(t, err) - startVersionInfo, err := startFixture.ExecVersion(ctx) - require.NoError(t, err, "failed to get start agent build version info") - - // Upgrade to an old build. - upgradeToVersion, err := upgradetest.PreviousMinor(ctx, define.Version()) + // Upgrade from an old build because the new watcher from the new build will + // be ran. Otherwise the test will run the old watcher from the old build. + upgradeFromVersion, err := upgradetest.PreviousMinor(ctx, define.Version()) require.NoError(t, err) - endFixture, err := atesting.NewFixture( + startFixture, err := atesting.NewFixture( t, - upgradeToVersion, + upgradeFromVersion, atesting.WithFetcher(atesting.ArtifactFetcher()), ) require.NoError(t, err) + startVersionInfo, err := startFixture.ExecVersion(ctx) + require.NoError(t, err, "failed to get start agent build version info") - t.Logf("Testing Elastic Agent upgrade from %s to %s...", define.Version(), upgradeToVersion) + // Upgrade to the build under test. + endFixture, err := define.NewFixture(t, define.Version()) + require.NoError(t, err) + + t.Logf("Testing Elastic Agent upgrade from %s to %s...", upgradeFromVersion, define.Version()) // We need to use the core version in the condition below because -SNAPSHOT is // stripped from the ${agent.version.version} evaluation below. - parsedUpgradeToVersion, err := version.ParseVersion(upgradeToVersion) + endVersion, err := version.ParseVersion(define.Version()) require.NoError(t, err) // Configure Agent with fast watcher configuration and also an invalid @@ -77,7 +85,7 @@ inputs: - condition: '${agent.version.version} == "%s"' type: invalid id: invalid-input -`, parsedUpgradeToVersion.CoreVersion()) +`, endVersion.CoreVersion()) return startFixture.Configure(ctx, []byte(invalidInputPolicy)) } @@ -135,24 +143,24 @@ func TestStandaloneUpgradeRollbackOnRestarts(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - // Start at the build version as we want to test the retry - // logic that is in the build. - startFixture, err := define.NewFixture(t, define.Version()) - require.NoError(t, err) - startVersionInfo, err := startFixture.ExecVersion(ctx) - require.NoError(t, err, "failed to get start agent build version info") - - // Upgrade to an old build. - upgradeToVersion, err := upgradetest.PreviousMinor(ctx, define.Version()) + // Upgrade from an old build because the new watcher from the new build will + // be ran. Otherwise the test will run the old watcher from the old build. + upgradeFromVersion, err := upgradetest.PreviousMinor(ctx, define.Version()) require.NoError(t, err) - endFixture, err := atesting.NewFixture( + startFixture, err := atesting.NewFixture( t, - upgradeToVersion, + upgradeFromVersion, atesting.WithFetcher(atesting.ArtifactFetcher()), ) require.NoError(t, err) + startVersionInfo, err := startFixture.ExecVersion(ctx) + require.NoError(t, err, "failed to get start agent build version info") - t.Logf("Testing Elastic Agent upgrade from %s to %s...", define.Version(), upgradeToVersion) + // Upgrade to the build under test. + endFixture, err := define.NewFixture(t, define.Version()) + require.NoError(t, err) + + t.Logf("Testing Elastic Agent upgrade from %s to %s...", upgradeFromVersion, define.Version()) // Use the post-upgrade hook to bypass the remainder of the PerformUpgrade // because we want to do our own checks for the rollback. @@ -163,7 +171,8 @@ func TestStandaloneUpgradeRollbackOnRestarts(t *testing.T) { err = upgradetest.PerformUpgrade( ctx, startFixture, endFixture, t, - upgradetest.WithPostUpgradeHook(postUpgradeHook)) + upgradetest.WithPostUpgradeHook(postUpgradeHook), + upgradetest.WithCustomWatcherConfig(reallyFastWatcherCfg)) if !errors.Is(err, ErrPostExit) { require.NoError(t, err) } @@ -174,16 +183,42 @@ func TestStandaloneUpgradeRollbackOnRestarts(t *testing.T) { time.Sleep(10 * time.Second) topPath := paths.Top() - t.Logf("Restarting Agent via service to simulate crashing") - err = install.RestartService(topPath) + t.Logf("Stopping agent via service to simulate crashing") + err = install.StopService(topPath) if err != nil && runtime.GOOS == define.Windows && strings.Contains(err.Error(), "The service has not been started.") { // Due to the quick restarts every 10 seconds its possible that this is faster than Windows // can handle. Decrementing restartIdx means that the loop will occur again. t.Logf("Got an allowed error on Windows: %s", err) - restartIdx-- - continue + err = nil } require.NoError(t, err) + + // ensure that it's stopped before starting it again + var status service.Status + var statusErr error + require.Eventuallyf(t, func() bool { + status, statusErr = install.StatusService(topPath) + if statusErr != nil { + return false + } + return status != service.StatusRunning + }, 2*time.Minute, 1*time.Second, "service never fully stopped (status: %v): %s", status, statusErr) + t.Logf("Stopped agent via service to simulate crashing") + + // start it again + t.Logf("Starting agent via service to simulate crashing") + err = install.StartService(topPath) + require.NoError(t, err) + + // ensure that it's started before next loop + require.Eventuallyf(t, func() bool { + status, statusErr = install.StatusService(topPath) + if statusErr != nil { + return false + } + return status == service.StatusRunning + }, 2*time.Minute, 1*time.Second, "service never fully started (status: %v): %s", status, statusErr) + t.Logf("Started agent via service to simulate crashing") } // wait for the agent to be healthy and back at the start version diff --git a/testing/upgradetest/upgrader.go b/testing/upgradetest/upgrader.go index 64dcb2b70ac..93aeed622a5 100644 --- a/testing/upgradetest/upgrader.go +++ b/testing/upgradetest/upgrader.go @@ -27,9 +27,10 @@ type CustomPGP struct { type upgradeOpts struct { sourceURI *string - skipVerify bool - skipDefaultPgp bool - customPgp *CustomPGP + skipVerify bool + skipDefaultPgp bool + customPgp *CustomPGP + customWatcherCfg string preInstallHook func() error postInstallHook func() error @@ -98,6 +99,13 @@ func WithPostUpgradeHook(hook func() error) upgradeOpt { } } +// WithCustomWatcherConfig sets a custom watcher configuration to use. +func WithCustomWatcherConfig(cfg string) upgradeOpt { + return func(opts *upgradeOpts) { + opts.customWatcherCfg = cfg + } +} + // PerformUpgrade performs the upgrading of the Elastic Agent. func PerformUpgrade( ctx context.Context, @@ -126,7 +134,11 @@ func PerformUpgrade( } // start fixture gets the agent configured to use a faster watcher - err = ConfigureFastWatcher(ctx, startFixture) + if upgradeOpts.customWatcherCfg != "" { + err = startFixture.Configure(ctx, []byte(upgradeOpts.customWatcherCfg)) + } else { + err = ConfigureFastWatcher(ctx, startFixture) + } if err != nil { return fmt.Errorf("failed configuring the start agent with faster watcher configuration: %w", err) } diff --git a/testing/upgradetest/watcher.go b/testing/upgradetest/watcher.go index d0505f5992d..fd34dc82a7f 100644 --- a/testing/upgradetest/watcher.go +++ b/testing/upgradetest/watcher.go @@ -16,7 +16,7 @@ import ( // FastWatcherCfg is configuration that makes the watcher run faster. const FastWatcherCfg = ` -agent.upgradetest.watcher: +agent.upgrade.watcher: grace_period: 1m error_check.interval: 15s crash_check.interval: 15s