Fix get_status api in COS policy manager GetStatus was deprecated in Update Engine. This broke the get_status api in COS Policy Manager. Changing the implementation to use GetStatusAdvanced api instead. BUG=b/219970525 TEST=presubmit and manually tested on VM RELEASE_NOTES=Fix get_status api in COS Policy Manager Change-Id: Ia0d0db2f5552cf528fb0863da1011aa1365a5a34
diff --git a/src/policy_manager/dbus/update_engine_client.go b/src/policy_manager/dbus/update_engine_client.go index 5b84086..bc0da24 100644 --- a/src/policy_manager/dbus/update_engine_client.go +++ b/src/policy_manager/dbus/update_engine_client.go
@@ -14,6 +14,8 @@ package dbus +import "policy_manager/policymanagerproto" + // UpdateEngineClient is the generic interface for interacting with update-engine. type UpdateEngineClient interface { // SetChannel Sets the target channel for update. @@ -23,7 +25,7 @@ AttemptUpdate() error // GetStatus returns the update status. - GetStatus() (response UEGetStatusResponse, err error) + GetStatus() (*policymanagerproto.StatusResult, error) // SubscribeStatusUpdate subscribes the status update of update-engine. // It returns a channel so that any udpate status change of udpate-engine
diff --git a/src/policy_manager/dbus/update_engine_client_impl.go b/src/policy_manager/dbus/update_engine_client_impl.go index 420a3aa..35e6742 100644 --- a/src/policy_manager/dbus/update_engine_client_impl.go +++ b/src/policy_manager/dbus/update_engine_client_impl.go
@@ -16,21 +16,23 @@ import ( "fmt" + "policy_manager/policymanagerproto" "strings" godbus "github.com/godbus/dbus" + "github.com/golang/protobuf/proto" ) // Implementation of the UpdateEngineClient interface that interacts with update-engine. const ( - updateEngineObjectName = "org.chromium.UpdateEngine" - updateEngineObjectPath = "/org/chromium/UpdateEngine" - setChannelMethod = "org.chromium.UpdateEngineInterface.SetChannel" - attemptUpdateMethod = "org.chromium.UpdateEngineInterface.AttemptUpdate" - getStatusMethod = "org.chromium.UpdateEngineInterface.GetStatus" - statusUpdateSignal = "org.chromium.UpdateEngineInterface.StatusUpdate" - dBusAddMatchMethod = "org.freedesktop.DBus.AddMatch" + updateEngineObjectName = "org.chromium.UpdateEngine" + updateEngineObjectPath = "/org/chromium/UpdateEngine" + setChannelMethod = "org.chromium.UpdateEngineInterface.SetChannel" + attemptUpdateMethod = "org.chromium.UpdateEngineInterface.AttemptUpdate" + getStatusAdvancedMethod = "org.chromium.UpdateEngineInterface.GetStatusAdvanced" + statusUpdateSignal = "org.chromium.UpdateEngineInterface.StatusUpdate" + dBusAddMatchMethod = "org.freedesktop.DBus.AddMatch" ) type updateEngineClientImpl struct { @@ -72,15 +74,18 @@ // GetStatus returns the update status func (client *updateEngineClientImpl) GetStatus() ( - response UEGetStatusResponse, err error) { - err = client.conn.Object(updateEngineObjectName, updateEngineObjectPath).Call( - getStatusMethod, 0).Store( - &response.LastCheckedTime, - &response.Progress, - &response.UpdateStatus, - &response.NewVersion, - &response.NewSize) - return + *policymanagerproto.StatusResult, error) { + var updateStatusBytes []byte + updateStatus := new(policymanagerproto.StatusResult) + if err := client.conn.Object(updateEngineObjectName, updateEngineObjectPath).Call( + getStatusAdvancedMethod, 0).Store(&updateStatusBytes); err != nil { + return nil, fmt.Errorf("failed to fetch update status: %s", err) + } + if err := proto.Unmarshal(updateStatusBytes, updateStatus); err != nil { + return nil, fmt.Errorf("failed to unmarshal UpdateStatus %s: %s", + updateStatusBytes, updateStatus) + } + return updateStatus, nil } // SubscribeStatusUpdate subscribes the status update of update-engine.
diff --git a/src/policy_manager/dbus/update_engine_client_impl_test.go b/src/policy_manager/dbus/update_engine_client_impl_test.go index 074fe46..0e8b3c5 100644 --- a/src/policy_manager/dbus/update_engine_client_impl_test.go +++ b/src/policy_manager/dbus/update_engine_client_impl_test.go
@@ -17,6 +17,8 @@ import ( "errors" + "github.com/golang/protobuf/proto" + "policy_manager/policymanagerproto" "reflect" "testing" @@ -122,25 +124,28 @@ // TestGetStatus tests GetStatus returns correct values. func TestGetStatus(t *testing.T) { + rebootStatus := policymanagerproto.Operation_UPDATED_NEED_REBOOT tests := []struct { name string - expectedReturn UEGetStatusResponse + expectedReturn *policymanagerproto.StatusResult expectError bool }{ { "NormalCase", - UEGetStatusResponse{ - 1433971203, - 0.000000, - "UPDATE_STATUS_UPDATED_NEED_REBOOT", - "7150.0.0", - 66542575, + &policymanagerproto.StatusResult{ + NewVersion: proto.String("16550.0.0"), + UpdateStatus: &rebootStatus, + LastCheckedTime: proto.Int64(1433971203), }, false, }, { "ErrorCase", - UEGetStatusResponse{}, + &policymanagerproto.StatusResult{ + NewVersion: proto.String("16550.0.0"), + UpdateStatus: nil, + LastCheckedTime: proto.Int64(1433971203), + }, true, }, } @@ -149,21 +154,21 @@ // Mock dbus connection and object. fakeConn := NewFakeConn() fakeObj := NewFakeBusObject() + updateStatusBytes, err := proto.Marshal(test.expectedReturn) + if err != nil { + t.Errorf("Failed to marshal UpdateStatusBytes %v", updateStatusBytes) + } fakeCall := func(args ...interface{}) ([]interface{}, error) { if !test.expectError { return []interface{}{ - test.expectedReturn.LastCheckedTime, - test.expectedReturn.Progress, - test.expectedReturn.UpdateStatus, - test.expectedReturn.NewVersion, - test.expectedReturn.NewSize, + updateStatusBytes, }, nil } else { return nil, errors.New("Expected error.") } } fakeConn.SetObject(updateEngineObjectName, updateEngineObjectPath, fakeObj) - fakeObj.SetCall(getStatusMethod, fakeCall) + fakeObj.SetCall(getStatusAdvancedMethod, fakeCall) ueClient := newFakeUpdateEngineClient(fakeConn) ret, err := ueClient.GetStatus() @@ -171,7 +176,7 @@ if err == nil { if test.expectError { t.Errorf("Test %s didn't get expected error", test.name) - } else if !reflect.DeepEqual(test.expectedReturn, ret) { + } else if !proto.Equal(test.expectedReturn, ret) { t.Errorf("Test %s got unexpected return %v, want %v", test.name, ret, test.expectedReturn) } } else if !test.expectError {
diff --git a/src/policy_manager/imgstatus/reporter_impl.go b/src/policy_manager/imgstatus/reporter_impl.go index db4104d..5bf09cf 100644 --- a/src/policy_manager/imgstatus/reporter_impl.go +++ b/src/policy_manager/imgstatus/reporter_impl.go
@@ -134,7 +134,7 @@ // Parse lsb-release file as key value pairs. pairs, err := parseKeyValuePairs(bytes.NewReader(content)) if err != nil { - return nil, fmt.Errorf("Unable to parse '%s': %s", lsbFile, err) + return nil, fmt.Errorf("unable to parse '%s': %s", lsbFile, err) } osVersion := new(pmpb.OSVersion) @@ -171,19 +171,18 @@ status := new(pmpb.InstanceStatus) - // Set update status string. - statVal, ok := pmpb.InstanceStatus_UpdateStatus_value[response.UpdateStatus] - if ok { - statEnumVal := pmpb.InstanceStatus_UpdateStatus(statVal) - status.UpdateStatus = &statEnumVal + // Set update status. + status.UpdateStatus = response.UpdateStatus + if status.UpdateStatus == nil { + return nil, fmt.Errorf("failed to fetch update status: It is possible that update_engine has not fetched the status yet.") } // Set new OS version. status.NewOsVersion = new(pmpb.OSVersion) - status.NewOsVersion.VersionString = proto.String(response.NewVersion) + status.NewOsVersion.VersionString = proto.String(*response.NewVersion) // Set last update check timestamp. - status.UpdateCheckTimestamp = proto.Int64(response.LastCheckedTime) + status.UpdateCheckTimestamp = proto.Int64(*response.LastCheckedTime) return status, nil } @@ -224,7 +223,7 @@ // Get update status from update-engine via dbus. updateStatus, err := getStatusFromUpdateEngine(rep.ueClient) if err != nil { - status.UpdateStatus = pmpb.InstanceStatus_UPDATE_STATUS_UNKNOWN.Enum() + status.UpdateStatus = nil status.Error = proto.String(err.Error()) return status, err }
diff --git a/src/policy_manager/imgstatus/reporter_impl_test.go b/src/policy_manager/imgstatus/reporter_impl_test.go index ea2f5c6..72839ce 100644 --- a/src/policy_manager/imgstatus/reporter_impl_test.go +++ b/src/policy_manager/imgstatus/reporter_impl_test.go
@@ -252,21 +252,19 @@ // TestGetStatusFromUpdateEngine tests that we can correctly get the update // status from update-engine via dbus. func TestGetStatusFromUpdateEngine(t *testing.T) { - rebootStatus := policymanagerproto.InstanceStatus_UPDATE_STATUS_UPDATED_NEED_REBOOT + rebootStatus := policymanagerproto.Operation_UPDATED_NEED_REBOOT tests := []struct { name string - getStatusResponse dbus.UEGetStatusResponse + getStatusResponse *policymanagerproto.StatusResult expectedRet *policymanagerproto.InstanceStatus expectedError error }{ { "GoodUpdateEngineOutput", - dbus.UEGetStatusResponse{ - int64(1433971203), - 0.000000, - "UPDATE_STATUS_UPDATED_NEED_REBOOT", - "16550.0.0", - int64(66542575), + &policymanagerproto.StatusResult{ + NewVersion: proto.String("16550.0.0"), + UpdateStatus: &rebootStatus, + LastCheckedTime: proto.Int64(1433971203), }, &policymanagerproto.InstanceStatus{ NewOsVersion: &policymanagerproto.OSVersion{ @@ -279,12 +277,10 @@ }, { "BadUpdateEngineOutput", - dbus.UEGetStatusResponse{ - 0, - 0, - "", - "", - 0, + &policymanagerproto.StatusResult{ + NewVersion: proto.String(""), + UpdateStatus: nil, + LastCheckedTime: proto.Int64(0), }, nil, errors.New("GetStatus error"), @@ -330,13 +326,13 @@ mockAPI.EXPECT().ReadFile("/etc/lsb-release").Return([]byte(testLSB), nil) mockClient := mockdbus.NewMockUpdateEngineClient(mockCtrl) + rebootStatus := policymanagerproto.Operation_UPDATED_NEED_REBOOT + mockClient.EXPECT().GetStatus().Return( - dbus.UEGetStatusResponse{ - int64(1433971203), - 0.000000, - "UPDATE_STATUS_UPDATED_NEED_REBOOT", - "16550.0.0", - int64(66542575), + &policymanagerproto.StatusResult{ + NewVersion: proto.String("16550.0.0"), + UpdateStatus: &rebootStatus, + LastCheckedTime: proto.Int64(1433971203), }, nil) mockPolicyEnforcer := mockpolicyenforcer.NewMockPolicyEnforcer(mockCtrl) @@ -359,13 +355,11 @@ VersionString: proto.String("16550.0.0"), } - updateStatus := policymanagerproto.InstanceStatus_UPDATE_STATUS_UPDATED_NEED_REBOOT - want := &policymanagerproto.InstanceStatus{ InstanceId: proto.Uint64(testInstanceID), OsVersion: ¤tVersion, NewOsVersion: &newVersion, - UpdateStatus: &updateStatus, + UpdateStatus: &rebootStatus, UpdateCheckTimestamp: proto.Int64(1433971203), HealthMonitorStatus: &policymanagerproto.HealthMonitorStatus{ Logging: proto.Bool(true), @@ -422,11 +416,16 @@ Return([]byte{}, test.lsbErr).AnyTimes() mockClient := mockdbus.NewMockUpdateEngineClient(mockCtrl) + errorStatus := policymanagerproto.Operation_ERROR + // Will only call GetStatus() if we didn't get error from ReadFile(). if test.lsbErr == nil { mockClient.EXPECT().GetStatus().Return( - dbus.UEGetStatusResponse{ - int64(0), float64(0), "", "", int64(0)}, + &policymanagerproto.StatusResult{ + NewVersion: proto.String(""), + UpdateStatus: &errorStatus, + LastCheckedTime: proto.Int64(0), + }, test.updateEngineErr) }
diff --git a/src/policy_manager/main.go b/src/policy_manager/main.go index 85de4dd..362ea15 100644 --- a/src/policy_manager/main.go +++ b/src/policy_manager/main.go
@@ -278,18 +278,18 @@ resolveEnforcementConfig(userConfig, manager) if err = resolveLocalUpdateStrategy(userConfig, status.GetOsVersion()); err != nil { - return fmt.Errorf("Error resolving update strategy (%s). Instance config will not be applied.", err) + return fmt.Errorf("error resolving update strategy (%s). Instance config will not be applied.", err) } if isUpdateDisabled(userConfig) { glog.Info("Detected updates disabled: stopping update engine...") if err := stopUpdateEngine(false); err != nil { - glog.Errorf("Failed to stop update engine: %v", err) + glog.Errorf("failed to stop update engine: %v", err) } } if err = switchChannelIfNeeded(ueClient, status, userConfig); err != nil { - return fmt.Errorf("Error switching channel (%s). Instance config will not be applied.", err) + return fmt.Errorf("error switching channel (%s). Instance config will not be applied.", err) } return manager.SetInstanceConfig(userConfig) @@ -313,11 +313,11 @@ channelConfig := fmt.Sprintf("%s-channel", targetChannel) if err := ueClient.SetChannel(channelConfig); err != nil { - glog.Errorf("Channel switch from %s to %s failed: %s.", + glog.Errorf("channel switch from %s to %s failed: %s.", currentChannel, targetChannel, err) return err } else { - glog.Infof("Channel switch from %s to %s succeeded.", + glog.Infof("channel switch from %s to %s succeeded.", currentChannel, targetChannel) } } @@ -332,7 +332,7 @@ match := imageNamePattern.FindStringSubmatch(strategy) if match == nil { - return nil, fmt.Errorf("Error parsing update_strategy: %s", strategy) + return nil, fmt.Errorf("error parsing update_strategy: %s", strategy) } channelValue := policymanagerproto.ReleaseChannel_value[strings.ToUpper(match[2])] @@ -355,7 +355,7 @@ if ueStatus.UpdateStatus == "UPDATE_STATUS_UPDATED_NEED_REBOOT" { config, err := manager.GetInstanceConfig() if err != nil { - return fmt.Errorf("Failed to get Instance config: %v, "+ + return fmt.Errorf("failed to get Instance config: %v, "+ "ignore UPDATED_NEED_REBOOT", err) } @@ -368,11 +368,11 @@ glog.Infof("New COS version %v has been installed. Rebooting...", ueStatus.NewVersion) _, _, err := apiHandler.RunCommand("/sbin/shutdown", "-r", "now") if err != nil { - return fmt.Errorf("Reboot attempt failed: %v", err) + return fmt.Errorf("reboot attempt failed: %v", err) } } else { return fmt.Errorf( - "Installed new version %v doesn't match "+ + "installed new version %v doesn't match "+ "instance config target_version_prefix %v, "+ "ignore UPDATED_NEED_REBOOT", ueStatus.NewVersion, config.GetTargetVersionPrefix()) @@ -395,7 +395,7 @@ glog.Info("Detected updates disabled: stopping update engine...") // Use --no-block because update-engine depends on init-device-policy if err := stopUpdateEngine(true); err != nil { - glog.Errorf("Failed to stop update-engine: %v", err) + glog.Errorf("failed to stop update-engine: %v", err) } } @@ -606,7 +606,7 @@ case showConfigCommand: handleShowConfigCmd(args) default: - glog.Errorf("Unrecognized command: %s\n", command) + glog.Errorf("unrecognized command: %s\n", command) flag.Usage() }
diff --git a/src/policy_manager/policymanagerproto/instance_status.proto b/src/policy_manager/policymanagerproto/instance_status.proto index ac0f511..1c30681 100644 --- a/src/policy_manager/policymanagerproto/instance_status.proto +++ b/src/policy_manager/policymanagerproto/instance_status.proto
@@ -15,6 +15,8 @@ // Protobuf definitions for reporting instance status. syntax = "proto2"; +import "policy_manager/policymanagerproto/update_status.proto"; + package policymanagerproto; // ReleaseChannel specifies the Chrome OS release channel that the image is on. @@ -124,7 +126,7 @@ } // The current update status as reported by the update_engine. - optional UpdateStatus update_status = 3 [default = UPDATE_STATUS_UNKNOWN]; + optional Operation update_status = 3 [default = ERROR]; // New version delivered by the update. // This fields is only useful if the update engine status is one of the
diff --git a/src/policy_manager/policymanagerproto/update_status.proto b/src/policy_manager/policymanagerproto/update_status.proto new file mode 100644 index 0000000..3e06a8f --- /dev/null +++ b/src/policy_manager/policymanagerproto/update_status.proto
@@ -0,0 +1,98 @@ +// Copyright 2022 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 +// +// 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. + +// Protobuf definitions for Update Status of COS instance. Keep this file +// synced with +// chromiumos/src/platform2/system_api/dbus/update_engine/update_engine.proto. +syntax = "proto2"; + +package policymanagerproto; + +// Keep in sync with: +// update_engine/client_library/include/update_engine/update_status.h +enum Operation { + IDLE = 0; + CHECKING_FOR_UPDATE = 1; + UPDATE_AVAILABLE = 2; + DOWNLOADING = 3; + VERIFYING = 4; + FINALIZING = 5; + UPDATED_NEED_REBOOT = 6; + REPORTING_ERROR_EVENT = 7; + ATTEMPTING_ROLLBACK = 8; + DISABLED = 9; + NEED_PERMISSION_TO_UPDATE = 10; + + // ERROR is only used by Chrome and update_engine doesn't really use/set this + // value as it is not the proper way to propagate errors. DO NOT use this + // anywhere other than Chrome. + // TODO(crbug.com/977320): Remove this from Chrome. + ERROR = -1; +} + +// Keep in sync with: +// update_engine/client_library/include/update_engine/update_status.h +enum UpdateUrgency { + REGULAR = 0; + CRITICAL = 1; +} + +// This is the message transferred between update_engine and other processes +// about the current status of the update_engine. It is used either in +// |GetUpdateStatusAdvanced| DBus method or |StatusUpdateAdvanced| signal. +// +// NOTE: Keep this updated with: +// update_engine/client_library/include/update_engine/update_status.h +message StatusResult { + // When the update_engine last checked for updates (time_t: seconds from Unix + // epoch). + optional int64 last_checked_time = 1; + + // The current progress (0.0f-1.0f). + optional double progress = 2; + + // The current status/operation of the update_engine. + optional Operation update_status = 3; + + // The new product version. + optional string new_version = 4; + + // The size of the update payload (bytes). + optional int64 new_size = 5; + + // Whether the update is actually an enterprise rollback. The value is valid + // only if the current_operation is passed |CHECKING_FOR_UPDATE|. + optional bool is_enterprise_rollback = 6; + + // Indication of install for DLC(s). + optional bool is_install = 7; + + // The end-of-life date of the device in the number of days since Unix Epoch. + optional int64 eol_date = 8; + + // If true, the system will powerwash once the update is applied and the + // system is rebooted. This value is reliable on |UPDATE_NEED_REBOOT|. + optional bool will_powerwash_after_reboot = 9; + + // The last update/install attempt error. + // Reference update_engine/dbus-constants.h for the errors that update_engine + // will propagate into this field. + optional int32 last_attempt_error = 10; + + // If true, the update is critical. DEPRECATED. + // bool critical_update = 11; + + // Indicates how important an update is. + optional UpdateUrgency update_urgency = 12; +}
diff --git a/src/policy_manager/policymanagerutil/config_fetcher.go b/src/policy_manager/policymanagerutil/config_fetcher.go index 2c4d03d..33422a7 100644 --- a/src/policy_manager/policymanagerutil/config_fetcher.go +++ b/src/policy_manager/policymanagerutil/config_fetcher.go
@@ -114,16 +114,16 @@ var err error if rawMetadata, etag, err = retriever.FetchMetadata(lastEtag); err != nil { - glog.Errorf("Failed to fetch metadata from metadata server: %s", err) + glog.Errorf("failed to fetch metadata from metadata server: %s", err) return nil, "", err } if metadata, err = parseRawMetadata(rawMetadata); err != nil { - glog.Errorf("Failed to parse raw metadata: %s", err) + glog.Errorf("failed to parse raw metadata: %s", err) return nil, "", err } - glog.V(4).Infof("Current value of instance custom metadata directory: %v", metadata) + glog.V(4).Infof("current value of instance custom metadata directory: %v", metadata) userConfig := getUserConfigFromMetadata(metadata) return userConfig, etag, nil } @@ -165,7 +165,7 @@ if configStr, ok = metadata[gciLegacyConfigKey]; ok { if configStr != "" { if err = jsonpb.UnmarshalString(configStr, userConfig); err != nil { - glog.Errorf("Failed to unmarshal InstanceConfig %s: %s", configStr, err) + glog.Errorf("failed to unmarshal InstanceConfig %s: %s", configStr, err) } } return userConfig