//
// Copyright (C) 2014 The Android Open Source Project
//
// 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.
//
#ifndef UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_MANAGER_INL_H_
#define UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_MANAGER_INL_H_
#include <memory>
#include <string>
#include <base/bind.h>
#include <base/location.h>
#include <brillo/message_loops/message_loop.h>
#include "update_engine/update_manager/evaluation_context.h"
namespace chromeos_update_manager {
template<typename R, typename... Args>
EvalStatus UpdateManager::EvaluatePolicy(
EvaluationContext* ec,
EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
std::string*, R*,
Args...) const,
R* result, Args... args) {
// If expiration timeout fired, dump the context and reset expiration.
// IMPORTANT: We must still proceed with evaluation of the policy in this
// case, so that the evaluation time (and corresponding reevaluation timeouts)
// are readjusted.
if (ec->is_expired()) {
LOG(WARNING) << "Request timed out, evaluation context: "
<< ec->DumpContext();
ec->ResetExpiration();
}
// Reset the evaluation context.
ec->ResetEvaluation();
const std::string policy_name = policy_->PolicyRequestName(policy_method);
LOG(INFO) << policy_name << ": START";
// First try calling the actual policy.
std::string error;
EvalStatus status = (policy_.get()->*policy_method)(ec, state_.get(), &error,
result, args...);
// If evaluating the main policy failed, defer to the default policy.
if (status == EvalStatus::kFailed) {
LOG(WARNING) << "Evaluating policy failed: " << error
<< "\nEvaluation context: " << ec->DumpContext();
error.clear();
status = (default_policy_.*policy_method)(ec, state_.get(), &error, result,
args...);
if (status == EvalStatus::kFailed) {
LOG(WARNING) << "Evaluating default policy failed: " << error;
} else if (status == EvalStatus::kAskMeAgainLater) {
LOG(ERROR)
<< "Default policy would block; this is a bug, forcing failure.";
status = EvalStatus::kFailed;
}
}
LOG(INFO) << policy_name << ": END";
return status;
}
template<typename R, typename... Args>
void UpdateManager::OnPolicyReadyToEvaluate(
scoped_refptr<EvaluationContext> ec,
base::Callback<void(EvalStatus status, const R& result)> callback,
EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
std::string*, R*,
Args...) const,
Args... args) {
// Evaluate the policy.
R result;
EvalStatus status = EvaluatePolicy(ec.get(), policy_method, &result, args...);
if (status != EvalStatus::kAskMeAgainLater) {
// AsyncPolicyRequest finished.
callback.Run(status, result);
return;
}
// Re-schedule the policy request based on used variables.
base::Closure reeval_callback = base::Bind(
&UpdateManager::OnPolicyReadyToEvaluate<R, Args...>,
base::Unretained(this), ec, callback,
policy_method, args...);
if (ec->RunOnValueChangeOrTimeout(reeval_callback))
return; // Reevaluation scheduled successfully.
// Scheduling a reevaluation can fail because policy method didn't use any
// non-const variable nor there's any time-based event that will change the
// status of evaluation. Alternatively, this may indicate an error in the use
// of the scheduling interface.
LOG(ERROR) << "Failed to schedule a reevaluation of policy "
<< policy_->PolicyRequestName(policy_method) << "; this is a bug.";
callback.Run(status, result);
}
template<typename R, typename... ActualArgs, typename... ExpectedArgs>
EvalStatus UpdateManager::PolicyRequest(
EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
std::string*, R*,
ExpectedArgs...) const,
R* result, ActualArgs... args) {
scoped_refptr<EvaluationContext> ec(
new EvaluationContext(clock_, evaluation_timeout_));
// A PolicyRequest always consists on a single evaluation on a new
// EvaluationContext.
// IMPORTANT: To ensure that ActualArgs can be converted to ExpectedArgs, we
// explicitly instantiate EvaluatePolicy with the latter in lieu of the
// former.
EvalStatus ret = EvaluatePolicy<R, ExpectedArgs...>(ec.get(), policy_method,
result, args...);
// Sync policy requests must not block, if they do then this is an error.
DCHECK(EvalStatus::kAskMeAgainLater != ret);
LOG_IF(WARNING, EvalStatus::kAskMeAgainLater == ret)
<< "Sync request used with an async policy; this is a bug";
return ret;
}
template<typename R, typename... ActualArgs, typename... ExpectedArgs>
void UpdateManager::AsyncPolicyRequest(
base::Callback<void(EvalStatus, const R& result)> callback,
EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
std::string*, R*,
ExpectedArgs...) const,
ActualArgs... args) {
scoped_refptr<EvaluationContext> ec =
new EvaluationContext(
clock_, evaluation_timeout_, expiration_timeout_,
std::unique_ptr<base::Callback<void(EvaluationContext*)>>(
new base::Callback<void(EvaluationContext*)>(
base::Bind(&UpdateManager::UnregisterEvalContext,
weak_ptr_factory_.GetWeakPtr()))));
if (!ec_repo_.insert(ec.get()).second) {
LOG(ERROR) << "Failed to register evaluation context; this is a bug.";
}
// IMPORTANT: To ensure that ActualArgs can be converted to ExpectedArgs, we
// explicitly instantiate UpdateManager::OnPolicyReadyToEvaluate with the
// latter in lieu of the former.
base::Closure eval_callback = base::Bind(
&UpdateManager::OnPolicyReadyToEvaluate<R, ExpectedArgs...>,
base::Unretained(this), ec, callback, policy_method, args...);
brillo::MessageLoop::current()->PostTask(FROM_HERE, eval_callback);
}
} // namespace chromeos_update_manager
#endif // UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_MANAGER_INL_H_