blob: 869cac95eef5e1020353caf0440c5c00863a7872 [file] [log] [blame]
// Copyright 2022 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "modemfwd/dlc_manager.h"
#include <string>
#include <utility>
#include <base/logging.h>
#include <base/memory/weak_ptr.h>
#include <base/strings/stringprintf.h>
#include <base/threading/thread_task_runner_handle.h>
#include <brillo/errors/error.h>
#include "dlcservice/dbus-proxies.h"
#include "dlcservice/proto_bindings/dlcservice.pb.h"
#include "modemfwd/error.h"
namespace modemfwd {
namespace dlcmanager {
const base::TimeDelta kInstallTimeout = base::Minutes(2);
const base::TimeDelta kGetDlcStatePollPeriod = base::Seconds(1);
const base::TimeDelta kInitialInstallRetryPeriod = base::Minutes(5);
const base::TimeDelta kInstallRetryMaxPeriod = base::Hours(2);
} // namespace dlcmanager
DlcManager::DlcManager(scoped_refptr<dbus::Bus> bus,
Metrics* metrics,
std::map<std::string, std::string> dlc_per_variant,
std::string variant)
: metrics_(metrics),
variant_(variant),
install_retry_period_(base::Minutes(5)),
weak_ptr_factory_(this) {
DCHECK(!variant_.empty());
Init(dlc_per_variant);
dlc_service_proxy_ =
std::make_unique<org::chromium::DlcServiceInterfaceProxy>(bus);
}
// Constructor for testing
DlcManager::DlcManager(
Metrics* metrics,
std::map<std::string, std::string> dlc_per_variant,
std::string variant,
std::unique_ptr<org::chromium::DlcServiceInterfaceProxyInterface> proxy)
: metrics_(metrics),
variant_(variant),
install_retry_period_(dlcmanager::kInitialInstallRetryPeriod),
weak_ptr_factory_(this) {
Init(dlc_per_variant);
dlc_service_proxy_ = std::move(proxy);
}
void DlcManager::Init(std::map<std::string, std::string> dlc_per_variant) {
for (const auto& it : dlc_per_variant) {
if (it.first != variant_) {
dlcs_to_remove_.emplace(it.second);
} else {
dlc_id_ = it.second;
}
}
}
void DlcManager::RemoveUnecessaryModemDlcs() {
if (variant_.empty()) {
LOG(ERROR) << "Cannot remove modem DLCs without knowing the current "
<< "variant";
auto err = Error::Create(FROM_HERE, error::kUnexpectedEmptyVariant,
"Empty variant value");
metrics_->SendDlcUninstallResultFailure(err.get());
return;
}
dlc_service_proxy_->GetExistingDlcsAsync(
base::BindOnce(&DlcManager::OnGetExistingDlcsSuccess,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&DlcManager::OnGetExistingDlcsError,
weak_ptr_factory_.GetWeakPtr()));
}
void DlcManager::OnGetExistingDlcsSuccess(
const dlcservice::DlcsWithContent& dlc_list) {
std::set<std::string> dlcs_to_remove_join;
for (const auto& dlc_info : dlc_list.dlc_infos()) {
if (dlcs_to_remove_.count(dlc_info.id()))
dlcs_to_remove_join.emplace(dlc_info.id());
}
dlcs_to_remove_ = std::move(dlcs_to_remove_join);
RemoveNextDlc();
}
void DlcManager::OnGetExistingDlcsError(brillo::Error* dbus_error) {
brillo::ErrorPtr err = Error::CreateFromDbusError(dbus_error);
brillo::Error::AddTo(&err, FROM_HERE, kModemfwdErrorDomain,
error::kDlcServiceReturnedErrorOnGetExistingDlcs,
"Failed to get existing DLCs.");
metrics_->SendDlcUninstallResultFailure(err.get());
// Nothing else to do without the list of existing DLCs.
}
void DlcManager::RemoveNextDlc() {
if (dlcs_to_remove_.empty()) {
LOG(INFO) << "No more DLCs to remove";
return;
}
auto it = dlcs_to_remove_.begin();
LOG(INFO) << "Removing DLC: " << *it;
dlc_service_proxy_->PurgeAsync(
*it,
base::BindOnce(&DlcManager::OnPurgeSuccess,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&DlcManager::OnPurgeError,
weak_ptr_factory_.GetWeakPtr()));
dlcs_to_remove_.erase(it);
}
void DlcManager::OnPurgeSuccess() {
metrics_->SendDlcUninstallResultSuccess();
RemoveNextDlc();
}
void DlcManager::OnPurgeError(brillo::Error* dbus_error) {
// If purging the DLC fails, log the error and continue purging the rest.
brillo::ErrorPtr err = Error::CreateFromDbusError(dbus_error);
brillo::Error::AddTo(&err, FROM_HERE, kModemfwdErrorDomain,
error::kDlcServiceReturnedErrorOnPurge,
"Failed to purge DLC.");
metrics_->SendDlcUninstallResultFailure(err.get());
RemoveNextDlc();
}
void DlcManager::InstallModemDlc(InstallModemDlcOnceCallback cb) {
LOG(INFO) << "Installing DLC:" << dlc_id_;
CHECK(install_callback_.is_null());
if (!install_callback_.is_null())
return;
install_callback_ = std::move(cb);
install_step_ = InstallStep::WAITING_FOR_SERVICE;
install_timeout_callback_.Reset(base::BindOnce(
&DlcManager::InstallDlcTimedout, weak_ptr_factory_.GetWeakPtr()));
// Add a timeout in case dlcservice is offline, or the Install call never
// returns. This will allow modemfwd to continue with other tasks.
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, install_timeout_callback_.callback(),
dlcmanager::kInstallTimeout);
// modemfwd might start before dlcservice, so it needs to wait until
// dlcservice shows up in the Dbus before calling |Install|.
dlc_service_proxy_->GetObjectProxy()->WaitForServiceToBeAvailable(
base::BindOnce(&DlcManager::OnServiceAvailable,
weak_ptr_factory_.GetWeakPtr()));
}
void DlcManager::OnServiceAvailable(bool available) {
if (!available)
LOG(WARNING) << "dlcservice not available";
TryInstall();
}
void DlcManager::PostRetryInstallTask() {
LOG(INFO) << "Posting DLC install retry task";
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&DlcManager::TryInstall, weak_ptr_factory_.GetWeakPtr()),
install_retry_period_);
// Increase the period exponentially until it reaches
// |kInstallRetryMaxPeriod|.
if (install_retry_period_ < dlcmanager::kInstallRetryMaxPeriod)
install_retry_period_ = install_retry_period_ * 2;
}
void DlcManager::TryInstall() {
install_step_ = InstallStep::INSTALLING;
dlcservice::InstallRequest install_request;
install_request.set_id(dlc_id_);
// set_reserve instructs dlcservice to reserve the space in the stateful
// partition even when it fails to install the DLC. This ensures that the
// stateful partition always has room to install the DLC.
install_request.set_reserve(true);
dlc_service_proxy_->InstallAsync(
install_request,
base::BindOnce(&DlcManager::OnInstallSuccess,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&DlcManager::OnInstallError,
weak_ptr_factory_.GetWeakPtr()));
}
// When the Install times out, modemfwd should still continue the install flow,
// since the DLC might need to be downloaded from the internet, and this
// process might take a very long time. If the Install flow succeeds later on,
// the DLC will be available the next time modemfwd starts.
void DlcManager::InstallDlcTimedout() {
brillo::ErrorPtr err;
switch (install_step_) {
case InstallStep::WAITING_FOR_SERVICE:
err = Error::Create(FROM_HERE, error::kTimeoutWaitingForDlcService,
"Timeout waiting for dlcservice");
break;
case InstallStep::INSTALLING:
err = Error::Create(FROM_HERE, error::kTimeoutWaitingForDlcInstall,
"Timeout installing DLC");
break;
case InstallStep::GET_DLC_STATE:
err = Error::Create(FROM_HERE, error::kTimeoutWaitingForInstalledState,
"Timeout while waiting for INSTALLED state.");
break;
}
if (!install_callback_.is_null())
std::move(install_callback_).Run("", err.get());
metrics_->SendDlcInstallResultFailure(err.get());
}
void DlcManager::OnInstallSuccess() {
LOG(INFO) << "DLC install call returned successfully, checking DLC state.";
install_step_ = InstallStep::GET_DLC_STATE;
// Because |InstallAsync| only initializes the installation process, we still
// need to verify that the DLC was actually installed without failures.
// Also, when the DLC doesn't exist on the device, and has to be downloaded,
// |Install| returns true and starts the download in the background.
// When that happens, the state will be |INSTALLING| for some time, and we
// need to wait until the state changes to |INSTALLED| or |NOT_INSTALLED|.
CallGetDlcStateAsync();
}
void DlcManager::CallGetDlcStateAsync() {
dlc_service_proxy_->GetDlcStateAsync(
dlc_id_,
base::BindOnce(&DlcManager::OnInstallGetDlcStateSuccess,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&DlcManager::OnInstallGetDlcStateError,
weak_ptr_factory_.GetWeakPtr()));
}
void DlcManager::OnInstallError(brillo::Error* dbus_error) {
brillo::ErrorPtr err = Error::CreateFromDbusError(dbus_error);
brillo::Error::AddTo(&err, FROM_HERE, kModemfwdErrorDomain,
error::kDlcServiceReturnedErrorOnInstall,
"Failed to install DLC.");
ProcessInstallError(std::move(err));
}
void DlcManager::OnInstallGetDlcStateSuccess(
const dlcservice::DlcState& state) {
if (state.state() == dlcservice::DlcState::INSTALLING) {
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&DlcManager::CallGetDlcStateAsync,
base::Unretained(this)),
dlcmanager::kGetDlcStatePollPeriod);
return;
}
if (state.state() != dlcservice::DlcState::INSTALLED) {
const std::string& state_name =
dlcservice::DlcState::State_Name(state.state());
LOG(INFO) << "DLC not installed correctly. Current state is:" << state_name;
brillo::ErrorPtr err = Error::Create(
FROM_HERE, error::kUnexpectedDlcState,
base::StringPrintf("Unexpected DLC state:%s", state_name.c_str()));
ProcessInstallError(std::move(err));
return;
}
// Cancel the timeout callback.
install_timeout_callback_.Cancel();
if (!install_callback_.is_null())
std::move(install_callback_).Run(state.root_path(), nullptr);
metrics_->SendDlcInstallResultSuccess();
}
void DlcManager::OnInstallGetDlcStateError(brillo::Error* dbus_error) {
brillo::ErrorPtr err = Error::CreateFromDbusError(dbus_error);
brillo::Error::AddTo(&err, FROM_HERE, kModemfwdErrorDomain,
error::kDlcServiceReturnedErrorOnGetDlcState,
"Failed to get the state of the DLC.");
ProcessInstallError(std::move(err));
}
void DlcManager::ProcessInstallError(brillo::ErrorPtr err) {
// TODO(b/229148265): Retry the install until it times out before returning
// an error.
// Cancel the timeout callback
install_timeout_callback_.Cancel();
if (!install_callback_.is_null())
std::move(install_callback_).Run("", err.get());
metrics_->SendDlcInstallResultFailure(err.get());
PostRetryInstallTask();
}
} // namespace modemfwd