| // Copyright 2024 Google LLC |
| // |
| // 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 |
| // |
| // https://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. |
| |
| package manager |
| |
| import ( |
| "context" |
| "fmt" |
| "time" |
| |
| "github.com/GoogleCloudPlatform/galog" |
| "github.com/GoogleCloudPlatform/google-guest-agent/internal/events" |
| "github.com/GoogleCloudPlatform/google-guest-agent/internal/retry" |
| pcpb "github.com/GoogleCloudPlatform/google-guest-agent/pkg/proto/plugin_comm" |
| ) |
| |
| const ( |
| // WatcherID is the core plugin watcher's ID. |
| WatcherID = "plugin-status-watcher" |
| // EventID is the core plugin event type ID. |
| EventID = "plugin-watcher,status" |
| ) |
| |
| // InitWatcher initializes and registers the watcher to monitor plugin status |
| // for a specific request. Runner also removes the watcher as soon as the |
| // condition is met. |
| func InitWatcher(ctx context.Context, name string, code int32, req string) (*Watcher, error) { |
| w := &Watcher{name: name, statusCode: code, request: req} |
| return w, events.FetchManager().AddWatcher(ctx, w) |
| } |
| |
| // Watcher is the plugin event watcher implementation. |
| type Watcher struct { |
| // name is name of the plugin watcher is watching. |
| name string |
| // statusCode is the status code that should generate successful event. |
| statusCode int32 |
| // request is the context to get status for. |
| request string |
| } |
| |
| // ID returns the plugin watcher ID. |
| func (w *Watcher) ID() string { |
| return WatcherID |
| } |
| |
| // Events returns an slice with all implemented events. |
| func (w *Watcher) Events() []string { |
| return []string{EventID} |
| } |
| |
| // Run implements the plugin event watcher that does status check |
| // on plugin and notifies when plugin returns the required [status] code. |
| // Watcher stops watching the plugin once the event is detected. |
| // Non-nil error is sent only when the required event is encountered. |
| func (w *Watcher) Run(ctx context.Context, event string) (bool, any, error) { |
| galog.Debugf("Running watcher for plugin: %q, request: %q, status: %d", w.name, w.request, w.statusCode) |
| |
| p, err := Instance().Fetch(w.name) |
| if err != nil { |
| return false, nil, fmt.Errorf("unable to fetch plugin %q: %w", w.name, err) |
| } |
| |
| // Returning will cause event manager to run this watcher immediately. |
| // Use retry policy to have backoff and avoid overloading or any contention |
| // on the plugin. |
| policy := retry.Policy{MaxAttempts: 3, BackoffFactor: 2, Jitter: time.Second * 2} |
| |
| // This function returns non-nil error only when status code condition is |
| // matched. |
| f := func() (*pcpb.Status, error) { |
| resp, status := p.GetStatus(ctx, w.request) |
| if status.Err() != nil { |
| return nil, fmt.Errorf("unable to get %q plugin status: %+v", w.name, status) |
| } |
| if resp.GetCode() == w.statusCode { |
| return resp, nil |
| } |
| |
| return nil, fmt.Errorf("plugin %q returned [%d] status code, last status: [%+v]", w.name, w.statusCode, resp) |
| } |
| |
| status, err := retry.RunWithResponse(ctx, policy, f) |
| if err == nil { |
| // Return false, no need to watch as event has already occurred and |
| // subscribers will be notified. |
| return false, status, nil |
| } |
| |
| return true, nil, err |
| } |