Commit 092b8500 authored by Owen Min's avatar Owen Min Committed by Commit Bot

Add report scheduler of enterprise cloud reporting.

The scheduler will
1) Check browser status to decide if cloud reporting can be enabled.
2) Listen for the policy changes.
3) Schedule next report with a timer.
4) Update lastUploadTimestamp in local_state.

Bug: 956237
Change-Id: I18dc5bd763417980053a9c63857a66dab062ea54
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1623459
Commit-Queue: Owen Min <zmin@chromium.org>
Reviewed-by: default avatarJulian Pastarmov <pastarmovj@chromium.org>
Cr-Commit-Position: refs/heads/master@{#665586}
parent 32e39dcd
......@@ -2903,6 +2903,8 @@ jumbo_split_static_library("browser") {
"download/download_shelf_controller.h",
"enterprise_reporting/prefs.cc",
"enterprise_reporting/prefs.h",
"enterprise_reporting/report_scheduler.cc",
"enterprise_reporting/report_scheduler.h",
"enterprise_reporting/report_uploader.cc",
"enterprise_reporting/report_uploader.h",
"enterprise_reporting/request_timer.cc",
......
// 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 "chrome/browser/enterprise_reporting/report_scheduler.h"
#include <utility>
#include <vector>
#include "base/task/post_task.h"
#include "base/time/time.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/enterprise_reporting/prefs.h"
#include "chrome/browser/enterprise_reporting/request_timer.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/browser/policy/browser_dm_token_storage.h"
#include "chrome/browser/policy/chrome_browser_policy_connector.h"
#include "chrome/common/pref_names.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"
#include "services/network/public/cpp/shared_url_loader_factory.h"
namespace em = enterprise_management;
namespace enterprise_reporting {
namespace {
const int kDefaultUploadIntervalHours =
24; // Default upload interval is 24 hours.
// Reads DM token and client id. Returns true if boths are non empty.
bool GetDMTokenAndDeviceId(std::string* dm_token, std::string* client_id) {
DCHECK(dm_token && client_id);
*dm_token = policy::BrowserDMTokenStorage::Get()->RetrieveDMToken();
*client_id = policy::BrowserDMTokenStorage::Get()->RetrieveClientId();
if (dm_token->empty() || client_id->empty()) {
VLOG(1)
<< "Enterprise reporting is disabled because device is not enrolled.";
return false;
}
return true;
}
// Returns true if cloud reporting is enabled.
bool IsReportingEnabled() {
return g_browser_process->local_state()->GetBoolean(
prefs::kCloudReportingEnabled);
}
} // namespace
ReportScheduler::ReportScheduler(
std::unique_ptr<policy::CloudPolicyClient> client,
std::unique_ptr<RequestTimer> request_timer)
: cloud_policy_client_(std::move(client)),
request_timer_(std::move(request_timer)) {
RegisterPerfObserver();
}
ReportScheduler::~ReportScheduler() = default;
void ReportScheduler::RegisterPerfObserver() {
pref_change_registrar_.Init(g_browser_process->local_state());
pref_change_registrar_.Add(
prefs::kCloudReportingEnabled,
base::BindRepeating(&ReportScheduler::OnReportEnabledPerfChanged,
base::Unretained(this)));
// Trigger first perf check during launch process.
OnReportEnabledPerfChanged();
}
void ReportScheduler::OnReportEnabledPerfChanged() {
std::string dm_token;
std::string client_id;
if (!IsReportingEnabled() || !GetDMTokenAndDeviceId(&dm_token, &client_id)) {
if (request_timer_)
request_timer_->Stop();
return;
}
if (!cloud_policy_client_->is_registered()) {
cloud_policy_client_->SetupRegistration(dm_token, client_id,
std::vector<std::string>());
}
Start();
}
void ReportScheduler::Start() {
base::TimeDelta upload_interval =
base::TimeDelta::FromHours(kDefaultUploadIntervalHours);
base::TimeDelta first_request_delay =
upload_interval -
(base::Time::Now() -
g_browser_process->local_state()->GetTime(kLastUploadTimestamp));
// The first report delay is based on the |lastUploadTimestamp| in the
// |local_state|, after that, it's 24 hours for each succeeded upload.
request_timer_->Start(
FROM_HERE, first_request_delay, upload_interval,
base::BindRepeating(&ReportScheduler::GenerateAndUploadReport,
base::Unretained(this)));
}
void ReportScheduler::GenerateAndUploadReport() {
VLOG(1) << "Uploading enterprise report.";
// TODO(zmin): Generates a real request.
std::unique_ptr<em::ChromeDesktopReportRequest> request =
std::make_unique<em::ChromeDesktopReportRequest>();
cloud_policy_client_->UploadChromeDesktopReport(
std::move(request),
base::BindRepeating(&ReportScheduler::OnReportUploaded,
base::Unretained(this)));
}
void ReportScheduler::OnReportUploaded(bool status) {
if (status) {
VLOG(1) << "The enterprise report has been uploaded.";
g_browser_process->local_state()->SetTime(kLastUploadTimestamp,
base::Time::Now());
if (IsReportingEnabled())
request_timer_->Reset();
return;
}
VLOG(1) << "The enterprise report has not been uploaded.";
// TODO(zmin): Implement retry logic
}
} // namespace enterprise_reporting
// 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.
#ifndef CHROME_BROWSER_ENTERPRISE_REPORTING_REPORT_SCHEDULER_H_
#define CHROME_BROWSER_ENTERPRISE_REPORTING_REPORT_SCHEDULER_H_
#include <memory>
#include <string>
#include "base/macros.h"
#include "components/prefs/pref_change_registrar.h"
namespace policy {
class CloudPolicyClient;
} // namespace policy
namespace enterprise_reporting {
class RequestTimer;
// Schedules the next report and handles retry in case of error. It also cancels
// all pending uploads if the report policy is turned off.
class ReportScheduler {
public:
ReportScheduler(std::unique_ptr<policy::CloudPolicyClient> client,
std::unique_ptr<RequestTimer> request_timer);
~ReportScheduler();
private:
// Observes CloudReportingEnabled policy.
void RegisterPerfObserver();
// Handles kCloudReportingEnabled policy value change, including the first
// policy value check during startup.
void OnReportEnabledPerfChanged();
// Schedules the first update request.
void Start();
// Generates a report and uploads it.
void GenerateAndUploadReport();
// Callback once report upload request is finished.
void OnReportUploaded(bool status);
// Policy value watcher
PrefChangeRegistrar pref_change_registrar_;
std::unique_ptr<policy::CloudPolicyClient> cloud_policy_client_;
std::unique_ptr<RequestTimer> request_timer_;
DISALLOW_COPY_AND_ASSIGN(ReportScheduler);
};
} // namespace enterprise_reporting
#endif // CHROME_BROWSER_ENTERPRISE_REPORTING_REPORT_SCHEDULER_H_
// 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 "chrome/browser/enterprise_reporting/report_scheduler.h"
#include <utility>
#include "base/test/scoped_feature_list.h"
#include "base/test/scoped_task_environment.h"
#include "chrome/browser/enterprise_reporting/prefs.h"
#include "chrome/browser/enterprise_reporting/request_timer.h"
#include "chrome/browser/policy/fake_browser_dm_token_storage.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/scoped_testing_local_state.h"
#include "chrome/test/base/testing_browser_process.h"
#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::Invoke;
using ::testing::WithArgs;
namespace em = enterprise_management;
namespace enterprise_reporting {
namespace {
constexpr char kDMToken[] = "dm_token";
constexpr char kClientId[] = "client_id";
const int kDefaultUploadInterval = 24;
} // namespace
class FakeRequestTimer : public RequestTimer {
public:
FakeRequestTimer() = default;
~FakeRequestTimer() override = default;
void Start(const base::Location& posted_from,
base::TimeDelta first_delay,
base::TimeDelta repeat_delay,
base::RepeatingClosure user_task) override {
first_delay_ = first_delay;
repeat_delay_ = repeat_delay;
user_task_ = user_task;
is_running_ = true;
}
void Stop() override { is_running_ = false; }
void Reset() override { is_running_ = true; }
void Fire() {
EXPECT_TRUE(is_running_);
user_task_.Run();
is_running_ = false;
}
bool is_running() { return is_running_; }
base::TimeDelta first_delay() { return first_delay_; }
base::TimeDelta repeat_delay() { return repeat_delay_; }
private:
base::TimeDelta first_delay_;
base::TimeDelta repeat_delay_;
base::RepeatingClosure user_task_;
bool is_running_ = false;
DISALLOW_COPY_AND_ASSIGN(FakeRequestTimer);
};
class ReportSchedulerTest : public ::testing::Test {
public:
ReportSchedulerTest()
: scoped_task_environment_(
base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME,
base::test::ScopedTaskEnvironment::NowSource::
MAIN_THREAD_MOCK_TIME),
local_state_(TestingBrowserProcess::GetGlobal()) {}
~ReportSchedulerTest() override = default;
void SetUp() override {
scoped_feature_list_.InitAndEnableFeature(
features::kEnterpriseReportingInBrowser);
RegisterPrefs(local_state_.Get()->registry());
client_ptr_ = std::make_unique<policy::MockCloudPolicyClient>();
client_ = client_ptr_.get();
timer_ptr_ = std::make_unique<FakeRequestTimer>();
timer_ = timer_ptr_.get();
Init(true, kDMToken, kClientId);
}
void Init(bool policy_enabled,
const std::string& dm_token,
const std::string& client_id) {
ToggleCloudReport(policy_enabled);
storage_.SetDMToken(dm_token);
storage_.SetClientId(client_id);
}
void CreateScheduler() {
scheduler_ = std::make_unique<ReportScheduler>(std::move(client_ptr_),
std::move(timer_ptr_));
}
void SetLastUploadInHour(int gap) {
previous_set_last_upload_timestamp_ =
base::Time::Now() - base::TimeDelta::FromHours(gap);
local_state_.Get()->SetTime(kLastUploadTimestamp,
previous_set_last_upload_timestamp_);
}
void ToggleCloudReport(bool enabled) {
local_state_.Get()->SetManagedPref(prefs::kCloudReportingEnabled,
std::make_unique<base::Value>(enabled));
}
// If lastUploadTimestamp is updated recently, it should be updated as Now().
// Otherwise, it should be same as previous set timestamp.
void ExpectLastUploadTimestampUpdated(bool is_updated) {
auto current_last_upload_timestamp =
local_state_.Get()->GetTime(kLastUploadTimestamp);
if (is_updated) {
EXPECT_EQ(base::Time::Now(), current_last_upload_timestamp);
} else {
EXPECT_EQ(previous_set_last_upload_timestamp_,
current_last_upload_timestamp);
}
}
base::test::ScopedFeatureList scoped_feature_list_;
base::test::ScopedTaskEnvironment scoped_task_environment_;
ScopedTestingLocalState local_state_;
std::unique_ptr<ReportScheduler> scheduler_;
FakeRequestTimer* timer_;
policy::MockCloudPolicyClient* client_;
policy::FakeBrowserDMTokenStorage storage_;
base::Time previous_set_last_upload_timestamp_;
private:
std::unique_ptr<policy::MockCloudPolicyClient> client_ptr_;
std::unique_ptr<FakeRequestTimer> timer_ptr_;
DISALLOW_COPY_AND_ASSIGN(ReportSchedulerTest);
};
TEST_F(ReportSchedulerTest, NoReportWithoutPolicy) {
Init(false, kDMToken, kClientId);
CreateScheduler();
EXPECT_FALSE(timer_->is_running());
}
TEST_F(ReportSchedulerTest, NoReportWithoutDMToken) {
Init(true, "", kClientId);
CreateScheduler();
EXPECT_FALSE(timer_->is_running());
}
TEST_F(ReportSchedulerTest, NoReportWithoutClientId) {
Init(true, kDMToken, "");
CreateScheduler();
EXPECT_FALSE(timer_->is_running());
}
TEST_F(ReportSchedulerTest, UploadReportSucceeded) {
EXPECT_CALL(*client_, SetupRegistration(kDMToken, kClientId, _));
EXPECT_CALL(*client_, UploadChromeDesktopReportProxy(_, _))
.WillOnce(WithArgs<1>(policy::ScheduleStatusCallback(true)));
CreateScheduler();
EXPECT_TRUE(timer_->is_running());
timer_->Fire();
// timer is paused until the report is finished.
EXPECT_FALSE(timer_->is_running());
// Run pending task.
scoped_task_environment_.FastForwardBy(base::TimeDelta());
// Next report is scheduled.
EXPECT_TRUE(timer_->is_running());
ExpectLastUploadTimestampUpdated(true);
::testing::Mock::VerifyAndClearExpectations(client_);
}
TEST_F(ReportSchedulerTest, UploadFailed) {
EXPECT_CALL(*client_, SetupRegistration(kDMToken, kClientId, _));
EXPECT_CALL(*client_, UploadChromeDesktopReportProxy(_, _))
.WillOnce(WithArgs<1>(policy::ScheduleStatusCallback(false)));
CreateScheduler();
EXPECT_TRUE(timer_->is_running());
timer_->Fire();
// timer is paused until the report is finished.
EXPECT_FALSE(timer_->is_running());
// Run pending task.
scoped_task_environment_.FastForwardBy(base::TimeDelta());
// Next report is not scheduled.
EXPECT_FALSE(timer_->is_running());
ExpectLastUploadTimestampUpdated(false);
::testing::Mock::VerifyAndClearExpectations(client_);
}
TEST_F(ReportSchedulerTest, TimerDelayWithLastUploadTimestamp) {
int gap = 10;
SetLastUploadInHour(gap);
EXPECT_CALL(*client_, SetupRegistration(kDMToken, kClientId, _));
CreateScheduler();
EXPECT_TRUE(timer_->is_running());
EXPECT_EQ(base::TimeDelta::FromHours(kDefaultUploadInterval - gap),
timer_->first_delay());
EXPECT_EQ(base::TimeDelta::FromHours(kDefaultUploadInterval),
timer_->repeat_delay());
::testing::Mock::VerifyAndClearExpectations(client_);
}
TEST_F(ReportSchedulerTest, TimerDelayWithoutLastUploadTimestamp) {
EXPECT_CALL(*client_, SetupRegistration(kDMToken, kClientId, _));
CreateScheduler();
EXPECT_TRUE(timer_->is_running());
EXPECT_GT(base::TimeDelta(), timer_->first_delay());
EXPECT_EQ(base::TimeDelta::FromHours(kDefaultUploadInterval),
timer_->repeat_delay());
::testing::Mock::VerifyAndClearExpectations(client_);
}
TEST_F(ReportSchedulerTest,
ReportingIsDisabledWhileNewReportIsScheduledButNotPosted) {
EXPECT_CALL(*client_, SetupRegistration(kDMToken, kClientId, _));
CreateScheduler();
EXPECT_TRUE(timer_->is_running());
ToggleCloudReport(false);
// Next report is not scheduled.
EXPECT_FALSE(timer_->is_running());
ExpectLastUploadTimestampUpdated(false);
::testing::Mock::VerifyAndClearExpectations(client_);
}
TEST_F(ReportSchedulerTest, ReportingIsDisabledWhileNewReportIsPosted) {
EXPECT_CALL(*client_, SetupRegistration(kDMToken, kClientId, _));
EXPECT_CALL(*client_, UploadChromeDesktopReportProxy(_, _))
.WillOnce(WithArgs<1>(policy::ScheduleStatusCallback(true)));
CreateScheduler();
EXPECT_TRUE(timer_->is_running());
timer_->Fire();
ToggleCloudReport(false);
EXPECT_FALSE(timer_->is_running());
// Run pending task.
scoped_task_environment_.FastForwardBy(base::TimeDelta());
ExpectLastUploadTimestampUpdated(true);
// Next report is not scheduled.
EXPECT_FALSE(timer_->is_running());
::testing::Mock::VerifyAndClearExpectations(client_);
}
} // namespace enterprise_reporting
......@@ -17,22 +17,22 @@ namespace enterprise_reporting {
class RequestTimer {
public:
RequestTimer();
~RequestTimer();
virtual ~RequestTimer();
// Starts the timer. The first task will be ran after |first_delay|. The
// following task will be ran with |repeat_delay|. If |first_delay| is larger
// than the |repeat_delay|, the first request will be fired after
// |repeat_delay| instead. Also, please note that the repeating task is ran
// once per Reset call.
void Start(const base::Location& posted_from,
base::TimeDelta first_delay,
base::TimeDelta repeat_delay,
base::RepeatingClosure user_task);
virtual void Start(const base::Location& posted_from,
base::TimeDelta first_delay,
base::TimeDelta repeat_delay,
base::RepeatingClosure user_task);
// Stops the timer. The running task will not be abandon.
void Stop();
virtual void Stop();
// Resets the timer, ran the task again after |repat_delay| that is set in
// Start(); This is only available after the first task is ran.
void Reset();
virtual void Reset();
bool IsRepeatTimerRunning() const;
bool IsFirstTimerRunning() const;
......
......@@ -3392,6 +3392,7 @@ test("unit_tests") {
"../browser/diagnostics/diagnostics_model_unittest.cc",
"../browser/download/download_commands_unittest.cc",
"../browser/download/download_shelf_unittest.cc",
"../browser/enterprise_reporting/report_scheduler_unittest.cc",
"../browser/enterprise_reporting/report_uploader_unittest.cc",
"../browser/enterprise_reporting/request_timer_unittest.cc",
"../browser/first_run/first_run_unittest.cc",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment