blob: 77cf5e9d92145a80a7d2ce16f2e6ab5d166e8e36 [file] [log] [blame]
// Copyright 2019 The Chromium 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 "components/enterprise/browser/reporting/report_scheduler.h"
#include <string>
#include <utility>
#include <vector>
#include "base/metrics/histogram_functions.h"
#include "base/syslog_logging.h"
#include "base/task/post_task.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "components/enterprise/browser/controller/browser_dm_token_storage.h"
#include "components/enterprise/browser/reporting/common_pref_names.h"
#include "components/enterprise/browser/reporting/real_time_report_generator.h"
#include "components/enterprise/browser/reporting/real_time_uploader.h"
#include "components/enterprise/browser/reporting/reporting_delegate_factory.h"
#include "components/policy/core/common/cloud/cloud_policy_client.h"
#include "components/policy/core/common/cloud/device_management_service.h"
#include "components/prefs/pref_service.h"
namespace em = enterprise_management;
namespace enterprise_reporting {
namespace {
constexpr base::TimeDelta kDefaultUploadInterval =
base::Hours(24); // Default upload interval is 24 hours.
const int kMaximumRetry = 10; // Retry 10 times takes about 15 to 19 hours.
bool IsBrowserVersionUploaded(ReportScheduler::ReportTrigger trigger) {
switch (trigger) {
case ReportScheduler::kTriggerTimer:
case ReportScheduler::kTriggerUpdate:
case ReportScheduler::kTriggerNewVersion:
return true;
case ReportScheduler::kTriggerNone:
case ReportScheduler::kTriggerExtensionRequestRealTime:
return false;
}
}
bool IsExtensionRequestUploaded(ReportScheduler::ReportTrigger trigger) {
switch (trigger) {
case ReportScheduler::kTriggerTimer:
case ReportScheduler::kTriggerExtensionRequestRealTime:
return true;
case ReportScheduler::kTriggerNone:
case ReportScheduler::kTriggerUpdate:
case ReportScheduler::kTriggerNewVersion:
return false;
}
}
void OnExtensionRequestEnqueued(bool success) {
// So far, there is nothing handle the enqueue failure as the CBCM status
// report will cover all failed requests. However, we may need a retry logic
// here if Extension workflow is decoupled from the status report.
if (!success)
LOG(ERROR) << "Extension request failed to be added to the pipeline.";
}
ReportType TriggerToReportType(ReportScheduler::ReportTrigger trigger) {
switch (trigger) {
case ReportScheduler::kTriggerNone:
case ReportScheduler::kTriggerExtensionRequestRealTime:
NOTREACHED();
[[fallthrough]];
case ReportScheduler::kTriggerTimer:
return ReportType::kFull;
case ReportScheduler::kTriggerUpdate:
return ReportType::kBrowserVersion;
case ReportScheduler::kTriggerNewVersion:
return ReportType::kBrowserVersion;
}
}
} // namespace
ReportScheduler::Delegate::Delegate() = default;
ReportScheduler::Delegate::~Delegate() = default;
void ReportScheduler::Delegate::SetReportTriggerCallback(
ReportScheduler::ReportTriggerCallback callback) {
DCHECK(trigger_report_callback_.is_null());
trigger_report_callback_ = std::move(callback);
}
void ReportScheduler::Delegate::SetRealtimeReportTriggerCallback(
ReportScheduler::RealtimeReportTriggerCallback callback) {
DCHECK(trigger_realtime_report_callback_.is_null());
trigger_realtime_report_callback_ = std::move(callback);
}
ReportScheduler::ReportScheduler(
policy::CloudPolicyClient* client,
std::unique_ptr<ReportGenerator> report_generator,
std::unique_ptr<RealTimeReportGenerator> real_time_report_generator,
ReportingDelegateFactory* delegate_factory)
: ReportScheduler(std::move(client),
std::move(report_generator),
std::move(real_time_report_generator),
delegate_factory->GetReportSchedulerDelegate()) {}
ReportScheduler::ReportScheduler(
policy::CloudPolicyClient* client,
std::unique_ptr<ReportGenerator> report_generator,
std::unique_ptr<RealTimeReportGenerator> real_time_report_generator,
std::unique_ptr<ReportScheduler::Delegate> delegate)
: delegate_(std::move(delegate)),
cloud_policy_client_(std::move(client)),
report_generator_(std::move(report_generator)),
real_time_report_generator_(std::move(real_time_report_generator)) {
delegate_->SetReportTriggerCallback(
base::BindRepeating(&ReportScheduler::GenerateAndUploadReport,
weak_ptr_factory_.GetWeakPtr()));
delegate_->SetRealtimeReportTriggerCallback(
base::BindRepeating(&ReportScheduler::GenerateAndUploadRealtimeReport,
weak_ptr_factory_.GetWeakPtr()));
RegisterPrefObserver();
}
ReportScheduler::~ReportScheduler() = default;
bool ReportScheduler::IsReportingEnabled() const {
return delegate_->GetLocalState()->GetBoolean(kCloudReportingEnabled);
}
bool ReportScheduler::IsNextReportScheduledForTesting() const {
return request_timer_.IsRunning();
}
void ReportScheduler::SetReportUploaderForTesting(
std::unique_ptr<ReportUploader> uploader) {
report_uploader_ = std::move(uploader);
}
void ReportScheduler::SetExtensionRequestUploaderForTesting(
std::unique_ptr<RealTimeUploader> uploader) {
extension_request_uploader_ = std::move(uploader);
}
ReportScheduler::Delegate* ReportScheduler::GetDelegateForTesting() {
return delegate_.get();
}
void ReportScheduler::OnDMTokenUpdated() {
OnReportEnabledPrefChanged();
}
void ReportScheduler::RegisterPrefObserver() {
pref_change_registrar_.Init(delegate_->GetLocalState());
pref_change_registrar_.Add(
kCloudReportingEnabled,
base::BindRepeating(&ReportScheduler::OnReportEnabledPrefChanged,
base::Unretained(this)));
// Trigger first pref check during launch process.
OnReportEnabledPrefChanged();
}
void ReportScheduler::OnReportEnabledPrefChanged() {
if (!IsReportingEnabled()) {
Stop();
return;
}
// For Chrome OS, it needn't register the cloud policy client here. The
// |dm_token| and |client_id| should have already existed after the client is
// initialized, and will keep valid during whole life-cycle.
#if !BUILDFLAG(IS_CHROMEOS_ASH)
if (!SetupBrowserPolicyClientRegistration()) {
Stop();
return;
}
#endif
// Start the periodic report timer.
const base::Time last_upload_timestamp =
delegate_->GetLocalState()->GetTime(kLastUploadTimestamp);
Start(last_upload_timestamp);
delegate_->StartWatchingUpdatesIfNeeded(last_upload_timestamp,
kDefaultUploadInterval);
delegate_->StartWatchingExtensionRequestIfNeeded();
}
void ReportScheduler::Stop() {
request_timer_.Stop();
delegate_->StopWatchingUpdates();
delegate_->StopWatchingExtensionRequest();
extension_request_uploader_.reset();
}
bool ReportScheduler::SetupBrowserPolicyClientRegistration() {
if (cloud_policy_client_->is_registered())
return true;
#if !BUILDFLAG(IS_CHROMEOS_ASH)
policy::DMToken browser_dm_token =
policy::BrowserDMTokenStorage::Get()->RetrieveDMToken();
std::string client_id =
policy::BrowserDMTokenStorage::Get()->RetrieveClientId();
if (!browser_dm_token.is_valid() || client_id.empty()) {
VLOG(1)
<< "Enterprise reporting is disabled because device is not enrolled.";
return false;
}
cloud_policy_client_->SetupRegistration(browser_dm_token.value(), client_id,
std::vector<std::string>());
return true;
#else
NOTREACHED();
return true;
#endif
}
void ReportScheduler::Start(base::Time last_upload_time) {
// The next report is triggered 24h after the previous was uploaded.
const base::Time next_upload_time = last_upload_time + kDefaultUploadInterval;
if (VLOG_IS_ON(1)) {
base::TimeDelta first_request_delay = next_upload_time - base::Time::Now();
VLOG(1) << "Schedule the first report in about "
<< first_request_delay.InHours() << " hour(s) and "
<< first_request_delay.InMinutes() % 60 << " minute(s).";
}
request_timer_.Start(FROM_HERE, next_upload_time,
base::BindOnce(&ReportScheduler::GenerateAndUploadReport,
base::Unretained(this), kTriggerTimer));
}
void ReportScheduler::GenerateAndUploadReport(ReportTrigger trigger) {
if (active_trigger_ != kTriggerNone) {
// A report is already being generated. Remember this trigger to be handled
// once the current report completes.
pending_triggers_ |= trigger;
return;
}
active_trigger_ = trigger;
report_generator_->Generate(
TriggerToReportType(trigger),
base::BindOnce(&ReportScheduler::OnReportGenerated,
base::Unretained(this)));
}
void ReportScheduler::GenerateAndUploadRealtimeReport(
ReportTrigger trigger,
const RealTimeReportGenerator::Data& data) {
if (trigger == kTriggerExtensionRequestRealTime) {
UploadExtensionRequests(data);
return;
}
}
void ReportScheduler::OnReportGenerated(ReportRequestQueue requests) {
DCHECK_NE(active_trigger_, kTriggerNone);
if (requests.empty()) {
SYSLOG(ERROR)
<< "No cloud report can be generated. Likely the report is too large.";
// Do not restart the periodic report timer, as it's likely that subsequent
// attempts to generate full reports would also fail.
active_trigger_ = kTriggerNone;
RunPendingTriggers();
return;
}
VLOG(1) << "Uploading enterprise report.";
if (!report_uploader_) {
report_uploader_ =
std::make_unique<ReportUploader>(cloud_policy_client_, kMaximumRetry);
}
RecordUploadTrigger(active_trigger_);
report_uploader_->SetRequestAndUpload(
TriggerToReportType(active_trigger_), std::move(requests),
base::BindOnce(&ReportScheduler::OnReportUploaded,
base::Unretained(this)));
}
void ReportScheduler::OnReportUploaded(ReportUploader::ReportStatus status) {
DCHECK_NE(active_trigger_, kTriggerNone);
VLOG(1) << "The enterprise report upload result " << status << ".";
switch (status) {
case ReportUploader::kSuccess:
// Schedule the next report for success. Reset uploader to reset failure
// count.
report_uploader_.reset();
if (IsBrowserVersionUploaded(active_trigger_))
delegate_->OnBrowserVersionUploaded();
if (IsExtensionRequestUploaded(active_trigger_))
delegate_->OnExtensionRequestUploaded();
delegate_->GetLocalState()->SetTime(kLastUploadSucceededTimestamp,
base::Time::Now());
[[fallthrough]];
case ReportUploader::kTransientError:
// Stop retrying and schedule the next report to avoid stale report.
// Failure count is not reset so retry delay remains.
if (active_trigger_ == kTriggerTimer) {
const base::Time now = base::Time::Now();
delegate_->GetLocalState()->SetTime(kLastUploadTimestamp, now);
if (IsReportingEnabled())
Start(now);
}
break;
case ReportUploader::kPersistentError:
Stop();
// No future upload until Chrome relaunch or pref change event.
break;
}
active_trigger_ = kTriggerNone;
RunPendingTriggers();
}
void ReportScheduler::RunPendingTriggers() {
DCHECK_EQ(active_trigger_, kTriggerNone);
if (!pending_triggers_)
return;
// Timer-triggered reports are a superset of those triggered by an update or a
// new version, so favor them and consider that they serve all purposes.
ReportTrigger trigger;
if ((pending_triggers_ & kTriggerTimer) != 0) {
// Timer-triggered reports contain data of all other report types.
trigger = kTriggerTimer;
pending_triggers_ = 0;
} else {
trigger = (pending_triggers_ & kTriggerUpdate) != 0 ? kTriggerUpdate
: kTriggerNewVersion;
pending_triggers_ = 0;
}
GenerateAndUploadReport(trigger);
}
void ReportScheduler::UploadExtensionRequests(
const RealTimeReportGenerator::Data& data) {
RecordUploadTrigger(kTriggerExtensionRequestRealTime);
DCHECK(real_time_report_generator_);
VLOG(1) << "Create extension request and add it to the pipeline.";
if (!extension_request_uploader_) {
extension_request_uploader_ =
RealTimeUploader::Create(cloud_policy_client_->dm_token(),
reporting::Destination::EXTENSIONS_WORKFLOW,
reporting::Priority::FAST_BATCH);
}
auto reports = real_time_report_generator_->Generate(
RealTimeReportGenerator::ReportType::kExtensionRequest, data);
for (auto& report : reports) {
extension_request_uploader_->Upload(
std::move(report), base::BindOnce(&OnExtensionRequestEnqueued));
}
delegate_->OnExtensionRequestUploaded();
}
// static
void ReportScheduler::RecordUploadTrigger(ReportTrigger trigger) {
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class Sample {
kNone = 0,
kTimer = 1,
kUpdate = 2,
kNewVersion = 3,
kExtensionRequest = 4, // Deprecated.
kExtensionRequestRealTime = 5,
kMaxValue = kExtensionRequestRealTime
} sample = Sample::kNone;
switch (trigger) {
case kTriggerNone:
break;
case kTriggerTimer:
sample = Sample::kTimer;
break;
case kTriggerUpdate:
sample = Sample::kUpdate;
break;
case kTriggerNewVersion:
sample = Sample::kNewVersion;
break;
case kTriggerExtensionRequestRealTime:
sample = Sample::kExtensionRequestRealTime;
break;
}
base::UmaHistogramEnumeration("Enterprise.CloudReportingUploadTrigger",
sample);
}
} // namespace enterprise_reporting