Commit 564101d8 authored by Leonid Baraz's avatar Leonid Baraz Committed by Commit Bot

Adding MeetDeviceTelemetryReportHandler - ChromeOS only!

This is a temporary solution for Hotrod to wrap AppInstallReportHandler
and use old endpoint, until such time that the new endpoint is
ready to be used.

Bug: b:169427520
Change-Id: Ie1f68989e526a19ba3725debf531a52ea3d9a2b4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2432140
Commit-Queue: Leonid Baraz <lbaraz@chromium.org>
Reviewed-by: default avatarZach Trudo <zatrudo@google.com>
Cr-Commit-Position: refs/heads/master@{#812521}
parent fe8ffb1c
......@@ -1190,6 +1190,8 @@ static_library("browser") {
"policy/messaging_layer/upload/app_install_report_handler.h",
"policy/messaging_layer/upload/dm_server_upload_service.cc",
"policy/messaging_layer/upload/dm_server_upload_service.h",
"policy/messaging_layer/upload/meet_device_telemetry_report_handler.cc",
"policy/messaging_layer/upload/meet_device_telemetry_report_handler.h",
"policy/messaging_layer/upload/upload_client.cc",
"policy/messaging_layer/upload/upload_client.h",
"policy/messaging_layer/util/backoff_settings.cc",
......
......@@ -145,7 +145,8 @@ AppInstallReportHandler::AppInstallReportHandler(
AppInstallReportHandler::~AppInstallReportHandler() = default;
Status AppInstallReportHandler::HandleRecord(Record record) {
ASSIGN_OR_RETURN(base::Value report, ValidateRecord(record));
RETURN_IF_ERROR(ValidateRecord(record));
ASSIGN_OR_RETURN(base::Value report, ConvertRecord(record));
ClientCallback client_cb = base::BindOnce([](bool finished_running) {
VLOG(1) << "Finished Running AppInstallReportUploader";
......@@ -160,14 +161,25 @@ Status AppInstallReportHandler::HandleRecord(Record record) {
return Status::StatusOK();
}
StatusOr<base::Value> AppInstallReportHandler::ValidateRecord(
const Record& record) const {
if (record.destination() != Destination::APP_INSTALL_EVENT) {
return Status(error::INVALID_ARGUMENT,
base::StrCat({"Record destination mismatch, encountered=",
Destination_Name(record.destination())}));
Status AppInstallReportHandler::ValidateRecord(const Record& record) const {
return ValidateDestination(record, Destination::APP_INSTALL_EVENT);
}
Status AppInstallReportHandler::ValidateDestination(
const Record& record,
Destination expected_destination) const {
if (record.destination() != expected_destination) {
return Status(
error::INVALID_ARGUMENT,
base::StrCat({"Record destination mismatch, expected=",
Destination_Name(expected_destination), ", encountered=",
Destination_Name(record.destination())}));
}
return Status::StatusOK();
}
StatusOr<base::Value> AppInstallReportHandler::ConvertRecord(
const Record& record) const {
base::Optional<base::Value> report_result =
base::JSONReader::Read(record.data());
if (!report_result.has_value()) {
......@@ -177,7 +189,7 @@ StatusOr<base::Value> AppInstallReportHandler::ValidateRecord(
return std::move(report_result.value());
}
Status AppInstallReportHandler::ValidateClientState() {
Status AppInstallReportHandler::ValidateClientState() const {
if (!GetClient()->is_registered()) {
return Status(error::UNAVAILABLE, "DmServer is currently unavailable");
}
......
......@@ -135,11 +135,23 @@ class AppInstallReportHandler : public DmServerUploadService::RecordHandler {
explicit AppInstallReportHandler(policy::CloudPolicyClient* client);
~AppInstallReportHandler() override;
// Base class RecordHandler method implementation.
Status HandleRecord(Record record) override;
protected:
// Helper method for |ValidateRecord|. Validates destination.
Status ValidateDestination(const Record& record,
Destination expected_destination) const;
private:
StatusOr<base::Value> ValidateRecord(const Record& record) const;
Status ValidateClientState();
// Validate record (override for subclass).
virtual Status ValidateRecord(const Record& record) const;
// Convert record into base::Value for upload (override for subclass).
virtual StatusOr<base::Value> ConvertRecord(const Record& record) const;
// Helper method. Validates CloudPolicyClient state.
Status ValidateClientState() const;
scoped_refptr<SharedQueue<base::Value>> report_queue_;
scoped_refptr<UploaderLeaderTracker> leader_tracker_;
......
......@@ -4,7 +4,6 @@
#include "chrome/browser/policy/messaging_layer/upload/app_install_report_handler.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/optional.h"
#include "base/synchronization/waitable_event.h"
......@@ -25,14 +24,14 @@
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::WithArgs;
namespace reporting {
namespace {
using testing::_;
using testing::Invoke;
using testing::Return;
using testing::WithArgs;
MATCHER_P(MatchValue, expected, "matches base::Value") {
std::string arg_string;
if (!base::JSONWriter::Write(arg, &arg_string)) {
......
......@@ -11,12 +11,14 @@
#include "base/task/post_task.h"
#include "base/task_runner.h"
#include "chrome/browser/policy/messaging_layer/upload/app_install_report_handler.h"
#include "chrome/browser/policy/messaging_layer/upload/meet_device_telemetry_report_handler.h"
#include "chrome/browser/policy/messaging_layer/util/backoff_settings.h"
#include "chrome/browser/policy/messaging_layer/util/shared_vector.h"
#include "chrome/browser/policy/messaging_layer/util/status.h"
#include "chrome/browser/policy/messaging_layer/util/status_macros.h"
#include "chrome/browser/policy/messaging_layer/util/statusor.h"
#include "chrome/browser/policy/messaging_layer/util/task_runner_context.h"
#include "chrome/browser/profiles/profile.h"
#include "components/policy/core/common/cloud/cloud_policy_client.h"
#include "components/policy/core/common/cloud/user_cloud_policy_manager.h"
#include "components/policy/proto/record.pb.h"
......@@ -26,6 +28,24 @@
#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/policy/user_cloud_policy_manager_chromeos.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
namespace reporting {
namespace {
StatusOr<Profile*> GetPrimaryProfile() {
if (!user_manager::UserManager::IsInitialized()) {
return Status(error::FAILED_PRECONDITION, "User manager not initialized");
}
const auto* primary_user = user_manager::UserManager::Get()->GetPrimaryUser();
if (!primary_user) {
return Status(error::FAILED_PRECONDITION, "Primary user not found");
}
return chromeos::ProfileHelper::Get()->GetProfileByUser(primary_user);
}
} // namespace
} // namespace reporting
#endif
namespace reporting {
......@@ -284,6 +304,14 @@ Status DmServerUploadService::InitRecordHandlers() {
record_handlers_->PushBack(std::make_unique<AppInstallReportHandler>(client),
base::DoNothing());
// Temporary wrapper for MeetDeviceTelementry
#ifdef OS_CHROMEOS
ASSIGN_OR_RETURN(Profile* const primary_profile, GetPrimaryProfile());
record_handlers_->PushBack(std::make_unique<MeetDeviceTelemetryReportHandler>(
primary_profile, client),
base::DoNothing());
#endif // OS_CHROMEOS
return Status::StatusOK();
}
......
......@@ -53,10 +53,10 @@ class DmServerUploadService {
virtual Status HandleRecord(Record record) = 0;
protected:
policy::CloudPolicyClient* GetClient() { return client_; }
policy::CloudPolicyClient* GetClient() const { return client_; }
private:
policy::CloudPolicyClient* client_;
policy::CloudPolicyClient* const client_;
};
// Context runner for handling the upload of events passed to the
......
// Copyright 2020 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/policy/messaging_layer/upload/meet_device_telemetry_report_handler.h"
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/containers/queue.h"
#include "base/json/json_reader.h"
#include "base/json/json_string_value_serializer.h"
#include "base/optional.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/strcat.h"
#include "base/task/post_task.h"
#include "base/task_runner.h"
#include "base/values.h"
#include "chrome/browser/chromeos/file_manager/path_util.h"
#include "chrome/browser/policy/messaging_layer/upload/app_install_report_handler.h"
#include "chrome/browser/policy/messaging_layer/util/status.h"
#include "chrome/browser/policy/messaging_layer/util/status_macros.h"
#include "chrome/browser/policy/messaging_layer/util/statusor.h"
#include "chrome/browser/profiles/reporting_util.h"
#include "components/policy/core/common/cloud/cloud_policy_client.h"
#include "components/policy/core/common/cloud/realtime_reporting_job_configuration.h"
#include "components/policy/proto/record.pb.h"
#include "components/policy/proto/record_constants.pb.h"
namespace reporting {
namespace {
// Common Key names used when building the dictionary to pass to the Chrome
// Reporting API.
constexpr char kDestination[] = "destination";
constexpr char kDmToken[] = "dmToken";
constexpr char kTimestampUs[] = "timestampUs";
constexpr char kData[] = "data";
base::Value ConvertRecordProtoToValue(const Record& record,
const base::Value& context) {
base::Value record_fields{base::Value::Type::DICTIONARY};
if (record.has_destination()) {
record_fields.SetIntKey(kDestination, record.destination());
}
if (!record.dm_token().empty()) {
record_fields.SetStringKey(kDmToken, record.dm_token());
}
if (record.has_timestamp_us()) {
// Do not convert into RFC3339 format - we need to keep microseconds.
// 64-bit ints aren't supported by JSON - must be stored as strings
std::ostringstream str;
str << record.timestamp_us();
record_fields.SetStringKey(kTimestampUs, str.str());
}
if (record.has_data()) { // No data indicates gap, empty data is still data.
record_fields.SetStringKey(kData, record.data());
}
base::Value records_list{base::Value::Type::LIST};
records_list.Append(std::move(record_fields));
return records_list;
}
} // namespace
MeetDeviceTelemetryReportHandler::MeetDeviceTelemetryReportHandler(
Profile* profile,
policy::CloudPolicyClient* client)
: AppInstallReportHandler(client), profile_(profile) {}
MeetDeviceTelemetryReportHandler::~MeetDeviceTelemetryReportHandler() = default;
Status MeetDeviceTelemetryReportHandler::ValidateRecord(
const Record& record) const {
RETURN_IF_ERROR(
ValidateDestination(record, Destination::MEET_DEVICE_TELEMETRY));
if (!record.has_data()) {
return Status(error::INVALID_ARGUMENT, "No 'data' in the Record");
}
return Status::StatusOK();
}
StatusOr<base::Value> MeetDeviceTelemetryReportHandler::ConvertRecord(
const Record& record) const {
base::Value context = reporting::GetContext(profile_);
base::Value event_list = ConvertRecordProtoToValue(record, context);
return policy::RealtimeReportingJobConfiguration::BuildReport(
std::move(event_list), std::move(context));
}
} // namespace reporting
// Copyright 2020 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_POLICY_MESSAGING_LAYER_UPLOAD_MEET_DEVICE_TELEMETRY_REPORT_HANDLER_H_
#define CHROME_BROWSER_POLICY_MESSAGING_LAYER_UPLOAD_MEET_DEVICE_TELEMETRY_REPORT_HANDLER_H_
#include <string>
#include <utility>
#include "base/callback.h"
#include "base/memory/ref_counted.h"
#include "base/sequenced_task_runner.h"
#include "base/task/post_task.h"
#include "base/task_runner.h"
#include "base/values.h"
#include "chrome/browser/policy/messaging_layer/upload/app_install_report_handler.h"
#include "chrome/browser/policy/messaging_layer/util/status.h"
#include "chrome/browser/profiles/profile.h"
#include "components/policy/core/common/cloud/cloud_policy_client.h"
#include "components/policy/proto/record.pb.h"
namespace reporting {
// |MeetDeviceTelemetryReportHandler| wraps |AppInstallReportHandler| to send
// MeetDeviceTelemetry data to DM server with MEET_DEVICE_TELEMETRY destination
// using |CloudPolicyClient|. Since |CloudPolicyClient| will cancel any in
// progress reports if a new report is added, |AppInstallReportHandler| ensures
// that only one report is ever processed at one time by forming a queue.
// Exists only on ChromeOS.
class MeetDeviceTelemetryReportHandler : public AppInstallReportHandler {
public:
// The client uses a boolean value for status, where true indicates success
// and false indicates failure.
using ClientCallback = AppInstallReportHandler::ClientCallback;
MeetDeviceTelemetryReportHandler(Profile* profile,
policy::CloudPolicyClient* client);
~MeetDeviceTelemetryReportHandler() override;
private:
Status ValidateRecord(const Record& record) const override;
StatusOr<base::Value> ConvertRecord(const Record& record) const override;
Profile* const profile_;
};
} // namespace reporting
#endif // CHROME_BROWSER_POLICY_MESSAGING_LAYER_UPLOAD_MEET_DEVICE_TELEMETRY_REPORT_HANDLER_H_
// Copyright 2020 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/policy/messaging_layer/upload/meet_device_telemetry_report_handler.h"
#include "base/json/json_writer.h"
#include "base/optional.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/post_task.h"
#include "base/task_runner.h"
#include "base/test/task_environment.h"
#include "base/values.h"
#include "chrome/browser/policy/messaging_layer/upload/dm_server_upload_service.h"
#include "chrome/browser/policy/messaging_layer/util/status.h"
#include "chrome/browser/policy/messaging_layer/util/status_macros.h"
#include "chrome/browser/policy/messaging_layer/util/statusor.h"
#include "chrome/browser/policy/messaging_layer/util/task_runner_context.h"
#include "components/policy/core/common/cloud/cloud_policy_client.h"
#include "components/policy/core/common/cloud/dm_token.h"
#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
#include "components/policy/proto/record.pb.h"
#include "components/policy/proto/record_constants.pb.h"
#include "components/user_manager/scoped_user_manager.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::WithArgs;
namespace reporting {
namespace {
MATCHER_P(MatchValue, expected, "matches base::Value") {
base::Value* const events = arg.FindListKey("events");
if (!events) {
LOG(ERROR) << "Arg does not have 'events' or 'events' is not a list";
return false;
}
base::Value::ListView events_list = events->GetList();
if (events_list.size() != 1) {
LOG(ERROR) << "'events' is empty or has more than one element in the list";
return false;
}
const base::Value& event = *events_list.begin();
const auto destination = event.FindIntKey("destination");
if (!destination.has_value() ||
destination.value() != Destination::MEET_DEVICE_TELEMETRY) {
LOG(ERROR) << "'destination' is wrong or missing";
return false;
}
const std::string* const data = event.FindStringKey("data");
if (!data) {
LOG(ERROR) << "'data' is missing";
return false;
}
DCHECK(expected);
std::string expected_string;
if (!base::JSONWriter::Write(*expected, &expected_string)) {
LOG(INFO) << "Unable to serialize the expected";
return false;
}
return *data == expected_string;
}
class TestCallbackWaiter {
public:
TestCallbackWaiter() : run_loop_(std::make_unique<base::RunLoop>()) {}
virtual void Signal() { run_loop_->Quit(); }
void Wait() { run_loop_->Run(); }
protected:
std::unique_ptr<base::RunLoop> run_loop_;
};
class MeetDeviceTelemetryReportHandlerTest : public testing::Test {
public:
MeetDeviceTelemetryReportHandlerTest() = default;
void SetUp() override {
// Set up client.
client_.SetDMToken(
policy::DMToken::CreateValidTokenForTesting("FAKE_DM_TOKEN").value());
}
protected:
content::BrowserTaskEnvironment task_envrionment_;
policy::MockCloudPolicyClient client_;
};
class TestRecord : public Record {
public:
explicit TestRecord(base::StringPiece key = "TEST_KEY",
base::StringPiece value = "TEST_VALUE") {
data_.SetKey(key, base::Value(value));
std::string json_data;
base::JSONWriter::Write(data_, &json_data);
set_data(json_data);
set_destination(Destination::MEET_DEVICE_TELEMETRY);
}
const base::Value* data() const { return &data_; }
private:
base::Value data_{base::Value::Type::DICTIONARY};
};
TEST_F(MeetDeviceTelemetryReportHandlerTest, AcceptsValidRecord) {
TestCallbackWaiter waiter;
TestRecord test_record;
EXPECT_CALL(client_,
UploadExtensionInstallReport_(MatchValue(test_record.data()), _))
.WillOnce(WithArgs<1>(Invoke(
[&waiter](
MeetDeviceTelemetryReportHandler::ClientCallback& callback) {
std::move(callback).Run(true);
waiter.Signal();
})));
MeetDeviceTelemetryReportHandler handler(/*profile=*/nullptr, &client_);
Status handle_status = handler.HandleRecord(test_record);
EXPECT_OK(handle_status);
waiter.Wait();
}
TEST_F(MeetDeviceTelemetryReportHandlerTest, DeniesInvalidDestination) {
EXPECT_CALL(client_, UploadExtensionInstallReport_(_, _)).Times(0);
MeetDeviceTelemetryReportHandler handler(/*profile=*/nullptr, &client_);
TestRecord test_record;
test_record.set_destination(Destination::UPLOAD_EVENTS);
Status handle_status = handler.HandleRecord(test_record);
EXPECT_FALSE(handle_status.ok());
EXPECT_EQ(handle_status.error_code(), error::INVALID_ARGUMENT);
}
TEST_F(MeetDeviceTelemetryReportHandlerTest, DeniesInvalidData) {
EXPECT_CALL(client_, UploadExtensionInstallReport_(_, _)).Times(0);
MeetDeviceTelemetryReportHandler handler(/*profile=*/nullptr, &client_);
TestRecord test_record;
test_record.clear_data();
Status handle_status = handler.HandleRecord(test_record);
EXPECT_FALSE(handle_status.ok());
EXPECT_EQ(handle_status.error_code(), error::INVALID_ARGUMENT);
}
TEST_F(MeetDeviceTelemetryReportHandlerTest, ReportsUnsuccessfulCall) {
TestCallbackWaiter waiter;
TestRecord test_record;
EXPECT_CALL(client_,
UploadExtensionInstallReport_(MatchValue(test_record.data()), _))
.WillOnce(WithArgs<1>(Invoke(
[&waiter](
MeetDeviceTelemetryReportHandler::ClientCallback& callback) {
std::move(callback).Run(false);
waiter.Signal();
})));
MeetDeviceTelemetryReportHandler handler(/*profile=*/nullptr, &client_);
Status handle_status = handler.HandleRecord(test_record);
EXPECT_OK(handle_status);
waiter.Wait();
}
class TestCallbackWaiterWithCounter : public TestCallbackWaiter {
public:
explicit TestCallbackWaiterWithCounter(int counter_limit)
: counter_limit_(counter_limit) {}
void Signal() override {
DCHECK_GT(counter_limit_, 0);
if (--counter_limit_ == 0) {
run_loop_->Quit();
}
}
private:
std::atomic<int> counter_limit_;
};
TEST_F(MeetDeviceTelemetryReportHandlerTest, AcceptsMultipleValidRecords) {
const int kExpectedCallTimes = 10;
TestCallbackWaiterWithCounter waiter{kExpectedCallTimes};
TestRecord test_record;
EXPECT_CALL(client_,
UploadExtensionInstallReport_(MatchValue(test_record.data()), _))
.WillRepeatedly(WithArgs<1>(Invoke(
[&waiter](
MeetDeviceTelemetryReportHandler::ClientCallback& callback) {
std::move(callback).Run(true);
waiter.Signal();
})));
MeetDeviceTelemetryReportHandler handler(/*profile=*/nullptr, &client_);
for (int i = 0; i < kExpectedCallTimes; i++) {
Status handle_status = handler.HandleRecord(test_record);
EXPECT_OK(handle_status);
}
waiter.Wait();
}
} // namespace
} // namespace reporting
......@@ -19,6 +19,12 @@
#include "content/public/test/browser_task_environment.h"
#include "services/network/test/test_network_connection_tracker.h"
#ifdef OS_CHROMEOS
#include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h"
#include "chrome/test/base/testing_profile.h"
#include "components/user_manager/scoped_user_manager.h"
#endif // OS_CHROMEOS
namespace reporting {
namespace {
......@@ -59,6 +65,23 @@ class TestCallbackWaiterWithCounter : public TestCallbackWaiter {
TEST(UploadClientTest, CreateUploadClient) {
content::BrowserTaskEnvironment task_envrionment_;
#ifdef OS_CHROMEOS
// Set up fake primary profile.
auto mock_user_manager =
std::make_unique<testing::NiceMock<chromeos::FakeChromeUserManager>>();
auto profile = std::make_unique<TestingProfile>(
base::FilePath(FILE_PATH_LITERAL("/home/chronos/u-0123456789abcdef")));
const AccountId account_id(
AccountId::FromUserEmailGaiaId(profile->GetProfileUserName(), "12345"));
const user_manager::User* user =
mock_user_manager->AddPublicAccountUser(account_id);
mock_user_manager->UserLoggedIn(account_id, user->username_hash(),
/*browser_restart=*/false,
/*is_child=*/false);
auto user_manager = std::make_unique<user_manager::ScopedUserManager>(
std::move(mock_user_manager));
#endif // OS_CHROMEOS
const int kExpectedCallTimes = 10;
const uint64_t kGenerationId = 1234;
......@@ -78,7 +101,7 @@ TEST(UploadClientTest, CreateUploadClient) {
auto upload_client_result =
UploadClient::Create(std::move(client), base::DoNothing());
ASSERT_TRUE(upload_client_result.ok());
ASSERT_OK(upload_client_result) << upload_client_result.status();
base::Value data{base::Value::Type::DICTIONARY};
data.SetKey("TEST_KEY", base::Value("TEST_VALUE"));
......
......@@ -3587,6 +3587,7 @@ test("unit_tests") {
"../browser/policy/messaging_layer/storage/test_storage_module.h",
"../browser/policy/messaging_layer/upload/app_install_report_handler_unittest.cc",
"../browser/policy/messaging_layer/upload/dm_server_upload_service_unittest.cc",
"../browser/policy/messaging_layer/upload/meet_device_telemetry_report_handler_unittest.cc",
"../browser/policy/messaging_layer/upload/upload_client_unittest.cc",
"../browser/policy/messaging_layer/util/shared_queue_unittest.cc",
"../browser/policy/messaging_layer/util/shared_vector_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