// Copyright (c) 2012 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 "base/callback.h" #include "base/compiler_specific.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "base/test/test_simple_task_runner.h" #include "chrome/browser/policy/cloud/cloud_policy_constants.h" #include "chrome/browser/policy/cloud/cloud_policy_refresh_scheduler.h" #include "chrome/browser/policy/cloud/mock_cloud_policy_client.h" #include "chrome/browser/policy/cloud/mock_cloud_policy_store.h" #include "policy/policy_constants.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace em = enterprise_management; using testing::Mock; namespace policy { namespace { const int64 kPolicyRefreshRate = 4 * 60 * 60 * 1000; const int64 kInitialCacheAgeMinutes = 1; } // namespace class CloudPolicyRefreshSchedulerTest : public testing::Test { protected: CloudPolicyRefreshSchedulerTest() : task_runner_(new base::TestSimpleTaskRunner()), network_change_notifier_(net::NetworkChangeNotifier::CreateMock()) {} virtual void SetUp() OVERRIDE { client_.SetDMToken("token"); // Set up the protobuf timestamp to be one minute in the past. Since the // protobuf field only has millisecond precision, we convert the actual // value back to get a millisecond-clamped time stamp for the checks below. store_.policy_.reset(new em::PolicyData()); base::Time now = base::Time::NowFromSystemTime(); base::TimeDelta initial_age = base::TimeDelta::FromMinutes(kInitialCacheAgeMinutes); store_.policy_->set_timestamp( ((now - initial_age) - base::Time::UnixEpoch()).InMilliseconds()); last_update_ = base::Time::UnixEpoch() + base::TimeDelta::FromMilliseconds(store_.policy_->timestamp()); } CloudPolicyRefreshScheduler* CreateRefreshScheduler() { EXPECT_EQ(0u, task_runner_->GetPendingTasks().size()); CloudPolicyRefreshScheduler* scheduler = new CloudPolicyRefreshScheduler(&client_, &store_, task_runner_); scheduler->SetRefreshDelay(kPolicyRefreshRate); // If the store has policy, run the wait-for-invalidations timeout task. if (store_.has_policy()) { EXPECT_EQ(1u, task_runner_->GetPendingTasks().size()); task_runner_->RunPendingTasks(); } return scheduler; } void NotifyIPAddressChanged() { net::NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests(); loop_.RunUntilIdle(); } base::TimeDelta GetLastDelay() const { const std::deque& pending_tasks = task_runner_->GetPendingTasks(); return pending_tasks.empty() ? base::TimeDelta() : pending_tasks.back().delay; } void CheckTiming(int64 expected_delay_ms) const { CheckTimingWithAge(base::TimeDelta::FromMilliseconds(expected_delay_ms), base::TimeDelta()); } // Checks that the latest refresh scheduled used an offset of // |offset_from_last_refresh| from the time of the previous refresh. // |cache_age| is how old the cache was when the refresh was issued. void CheckTimingWithAge(const base::TimeDelta& offset_from_last_refresh, const base::TimeDelta& cache_age) const { EXPECT_FALSE(task_runner_->GetPendingTasks().empty()); base::Time now(base::Time::NowFromSystemTime()); // |last_update_| was updated and then a refresh was scheduled at time S, // so |last_update_| is a bit before that. // Now is a bit later, N. // GetLastDelay() + S is the time when the refresh will run, T. // |cache_age| is the age of the cache at time S. It was thus created at // S - cache_age. // // Schematically: // // . S . N . . . . . . . T . . . . // | | | // set "last_refresh_" and then scheduled the next refresh; the cache // was "cache_age" old at this point. // | | // some time elapsed on the test execution since then; // this is the current time, "now" // | // the refresh will execute at this time // // So the exact delay is T - S - |cache_age|, but we don't have S here. // // |last_update_| was a bit before S, so if // elapsed = now - |last_update_| then the delay is more than // |offset_from_last_refresh| - elapsed. // // The delay is also less than offset_from_last_refresh, because some time // already elapsed. Additionally, if the cache was already considered old // when the schedule was performed then its age at that time has been // discounted from the delay. So the delay is a bit less than // |offset_from_last_refresh - cache_age|. EXPECT_GE(GetLastDelay(), offset_from_last_refresh - (now - last_update_)); EXPECT_LE(GetLastDelay(), offset_from_last_refresh - cache_age); } void CheckInitialRefresh(bool with_invalidations) const { #if defined(OS_ANDROID) // Android takes the cache age into account for the initial fetch. // Usually the cache age is ignored for the initial refresh, but Android // uses it to restrain from refreshing on every startup. base::TimeDelta rate = base::TimeDelta::FromMilliseconds( with_invalidations ? CloudPolicyRefreshScheduler::kWithInvalidationsRefreshDelayMs : kPolicyRefreshRate); CheckTimingWithAge(rate, base::TimeDelta::FromMinutes(kInitialCacheAgeMinutes)); #else // Other platforms refresh immediately. EXPECT_EQ(base::TimeDelta(), GetLastDelay()); #endif } base::MessageLoop loop_; MockCloudPolicyClient client_; MockCloudPolicyStore store_; scoped_refptr task_runner_; scoped_ptr network_change_notifier_; // Base time for the refresh that the scheduler should be using. base::Time last_update_; }; TEST_F(CloudPolicyRefreshSchedulerTest, InitialRefreshNoPolicy) { store_.policy_.reset(); scoped_ptr scheduler(CreateRefreshScheduler()); EXPECT_FALSE(task_runner_->GetPendingTasks().empty()); EXPECT_EQ(GetLastDelay(), base::TimeDelta()); EXPECT_CALL(client_, FetchPolicy()).Times(1); task_runner_->RunUntilIdle(); } TEST_F(CloudPolicyRefreshSchedulerTest, InitialRefreshUnmanaged) { store_.policy_->set_state(em::PolicyData::UNMANAGED); scoped_ptr scheduler(CreateRefreshScheduler()); CheckTiming(CloudPolicyRefreshScheduler::kUnmanagedRefreshDelayMs); EXPECT_CALL(client_, FetchPolicy()).Times(1); task_runner_->RunUntilIdle(); } TEST_F(CloudPolicyRefreshSchedulerTest, InitialRefreshManagedNotYetFetched) { scoped_ptr scheduler(CreateRefreshScheduler()); EXPECT_FALSE(task_runner_->GetPendingTasks().empty()); CheckInitialRefresh(false); EXPECT_CALL(client_, FetchPolicy()).Times(1); task_runner_->RunUntilIdle(); } TEST_F(CloudPolicyRefreshSchedulerTest, InitialRefreshManagedAlreadyFetched) { last_update_ = base::Time::NowFromSystemTime(); client_.SetPolicy(PolicyNamespaceKey(dm_protocol::kChromeUserPolicyType, std::string()), em::PolicyFetchResponse()); scoped_ptr scheduler(CreateRefreshScheduler()); CheckTiming(kPolicyRefreshRate); EXPECT_CALL(client_, FetchPolicy()).Times(1); task_runner_->RunUntilIdle(); } TEST_F(CloudPolicyRefreshSchedulerTest, Unregistered) { client_.SetDMToken(std::string()); scoped_ptr scheduler(CreateRefreshScheduler()); client_.NotifyPolicyFetched(); client_.NotifyRegistrationStateChanged(); client_.NotifyClientError(); scheduler->SetRefreshDelay(12 * 60 * 60 * 1000); store_.NotifyStoreLoaded(); store_.NotifyStoreError(); EXPECT_TRUE(task_runner_->GetPendingTasks().empty()); } TEST_F(CloudPolicyRefreshSchedulerTest, RefreshSoonRateLimit) { scoped_ptr scheduler(CreateRefreshScheduler()); // Max out the request rate. for (int i = 0; i < 5; ++i) { EXPECT_CALL(client_, FetchPolicy()).Times(1); scheduler->RefreshSoon(); task_runner_->RunUntilIdle(); Mock::VerifyAndClearExpectations(&client_); } // The next refresh is throttled. EXPECT_CALL(client_, FetchPolicy()).Times(0); scheduler->RefreshSoon(); task_runner_->RunPendingTasks(); Mock::VerifyAndClearExpectations(&client_); } TEST_F(CloudPolicyRefreshSchedulerTest, InvalidationsAvailable) { scoped_ptr scheduler( new CloudPolicyRefreshScheduler(&client_, &store_, task_runner_)); scheduler->SetRefreshDelay(kPolicyRefreshRate); // The scheduler is currently waiting for the invalidations service to // initialize. EXPECT_EQ(1u, task_runner_->GetPendingTasks().size()); // Signal that invalidations are available. The scheduler is currently // waiting for any pending invalidations to be received. scheduler->SetInvalidationServiceAvailability(true); EXPECT_EQ(2u, task_runner_->GetPendingTasks().size()); // Run the invalidation service timeout task. EXPECT_CALL(client_, FetchPolicy()).Times(0); task_runner_->RunPendingTasks(); Mock::VerifyAndClearExpectations(&client_); // The initial refresh is scheduled. EXPECT_EQ(1u, task_runner_->GetPendingTasks().size()); CheckInitialRefresh(true); EXPECT_CALL(client_, FetchPolicy()).Times(1); task_runner_->RunPendingTasks(); Mock::VerifyAndClearExpectations(&client_); // Complete that fetch. last_update_ = base::Time::NowFromSystemTime(); client_.NotifyPolicyFetched(); // The next refresh has been scheduled using a lower refresh rate. EXPECT_EQ(1u, task_runner_->GetPendingTasks().size()); CheckTiming(CloudPolicyRefreshScheduler::kWithInvalidationsRefreshDelayMs); } TEST_F(CloudPolicyRefreshSchedulerTest, InvalidationsNotAvailable) { scoped_ptr scheduler( new CloudPolicyRefreshScheduler(&client_, &store_, task_runner_)); scheduler->SetRefreshDelay(kPolicyRefreshRate); // The scheduler is currently waiting for the invalidations service to // initialize. EXPECT_EQ(1u, task_runner_->GetPendingTasks().size()); // Signal that invalidations are not available. The scheduler will keep // waiting for us. for (int i = 0; i < 10; ++i) { scheduler->SetInvalidationServiceAvailability(false); EXPECT_EQ(1u, task_runner_->GetPendingTasks().size()); } // Run the timeout task. EXPECT_CALL(client_, FetchPolicy()).Times(0); task_runner_->RunPendingTasks(); Mock::VerifyAndClearExpectations(&client_); // This scheduled the initial refresh. CheckInitialRefresh(false); // Perform that fetch now. EXPECT_CALL(client_, FetchPolicy()).Times(1); task_runner_->RunPendingTasks(); Mock::VerifyAndClearExpectations(&client_); // Complete that fetch. last_update_ = base::Time::NowFromSystemTime(); client_.NotifyPolicyFetched(); // The next refresh has been scheduled at the normal rate. EXPECT_EQ(1u, task_runner_->GetPendingTasks().size()); CheckTiming(kPolicyRefreshRate); } TEST_F(CloudPolicyRefreshSchedulerTest, InvalidationsOffAndOn) { scoped_ptr scheduler( new CloudPolicyRefreshScheduler(&client_, &store_, task_runner_)); scheduler->SetRefreshDelay(kPolicyRefreshRate); scheduler->SetInvalidationServiceAvailability(true); // Initial fetch. EXPECT_CALL(client_, FetchPolicy()).Times(1); task_runner_->RunUntilIdle(); Mock::VerifyAndClearExpectations(&client_); last_update_ = base::Time::NowFromSystemTime(); client_.NotifyPolicyFetched(); // The next refresh has been scheduled using a lower refresh rate. // Flush that task. CheckTiming(CloudPolicyRefreshScheduler::kWithInvalidationsRefreshDelayMs); EXPECT_CALL(client_, FetchPolicy()).Times(1); task_runner_->RunPendingTasks(); Mock::VerifyAndClearExpectations(&client_); // If the service goes down and comes back up before the timeout then a // refresh is rescheduled at the lower rate again; after executing all // pending tasks only 1 fetch is performed. EXPECT_CALL(client_, FetchPolicy()).Times(0); scheduler->SetInvalidationServiceAvailability(false); scheduler->SetInvalidationServiceAvailability(true); // Run the invalidation service timeout task. task_runner_->RunPendingTasks(); Mock::VerifyAndClearExpectations(&client_); // The next refresh has been scheduled using a lower refresh rate. EXPECT_CALL(client_, FetchPolicy()).Times(1); CheckTiming(CloudPolicyRefreshScheduler::kWithInvalidationsRefreshDelayMs); task_runner_->RunPendingTasks(); Mock::VerifyAndClearExpectations(&client_); } TEST_F(CloudPolicyRefreshSchedulerTest, InvalidationsDisconnected) { scoped_ptr scheduler( new CloudPolicyRefreshScheduler(&client_, &store_, task_runner_)); scheduler->SetRefreshDelay(kPolicyRefreshRate); scheduler->SetInvalidationServiceAvailability(true); // Initial fetch. EXPECT_CALL(client_, FetchPolicy()).Times(1); task_runner_->RunUntilIdle(); Mock::VerifyAndClearExpectations(&client_); last_update_ = base::Time::NowFromSystemTime(); client_.NotifyPolicyFetched(); // The next refresh has been scheduled using a lower refresh rate. // Flush that task. CheckTiming(CloudPolicyRefreshScheduler::kWithInvalidationsRefreshDelayMs); EXPECT_CALL(client_, FetchPolicy()).Times(1); task_runner_->RunPendingTasks(); Mock::VerifyAndClearExpectations(&client_); // If the service goes down then the refresh scheduler falls back on the // default polling rate after a timeout. EXPECT_CALL(client_, FetchPolicy()).Times(0); scheduler->SetInvalidationServiceAvailability(false); task_runner_->RunPendingTasks(); Mock::VerifyAndClearExpectations(&client_); // The next refresh has been scheduled at the normal rate. CheckTiming(kPolicyRefreshRate); } class CloudPolicyRefreshSchedulerSteadyStateTest : public CloudPolicyRefreshSchedulerTest { protected: CloudPolicyRefreshSchedulerSteadyStateTest() {} virtual void SetUp() OVERRIDE { refresh_scheduler_.reset(CreateRefreshScheduler()); refresh_scheduler_->SetRefreshDelay(kPolicyRefreshRate); CloudPolicyRefreshSchedulerTest::SetUp(); last_update_ = base::Time::NowFromSystemTime(); client_.NotifyPolicyFetched(); CheckTiming(kPolicyRefreshRate); } scoped_ptr refresh_scheduler_; }; TEST_F(CloudPolicyRefreshSchedulerSteadyStateTest, OnPolicyFetched) { client_.NotifyPolicyFetched(); CheckTiming(kPolicyRefreshRate); } TEST_F(CloudPolicyRefreshSchedulerSteadyStateTest, OnRegistrationStateChanged) { client_.SetDMToken("new_token"); client_.NotifyRegistrationStateChanged(); EXPECT_EQ(GetLastDelay(), base::TimeDelta()); task_runner_->ClearPendingTasks(); client_.SetDMToken(std::string()); client_.NotifyRegistrationStateChanged(); EXPECT_TRUE(task_runner_->GetPendingTasks().empty()); } TEST_F(CloudPolicyRefreshSchedulerSteadyStateTest, OnStoreLoaded) { store_.NotifyStoreLoaded(); CheckTiming(kPolicyRefreshRate); } TEST_F(CloudPolicyRefreshSchedulerSteadyStateTest, OnStoreError) { task_runner_->ClearPendingTasks(); store_.NotifyStoreError(); EXPECT_TRUE(task_runner_->GetPendingTasks().empty()); } TEST_F(CloudPolicyRefreshSchedulerSteadyStateTest, RefreshDelayChange) { const int delay_short_ms = 5 * 60 * 1000; refresh_scheduler_->SetRefreshDelay(delay_short_ms); CheckTiming(CloudPolicyRefreshScheduler::kRefreshDelayMinMs); const int delay_ms = 12 * 60 * 60 * 1000; refresh_scheduler_->SetRefreshDelay(delay_ms); CheckTiming(delay_ms); const int delay_long_ms = 20 * 24 * 60 * 60 * 1000; refresh_scheduler_->SetRefreshDelay(delay_long_ms); CheckTiming(CloudPolicyRefreshScheduler::kRefreshDelayMaxMs); } TEST_F(CloudPolicyRefreshSchedulerSteadyStateTest, OnIPAddressChanged) { NotifyIPAddressChanged(); CheckTiming(kPolicyRefreshRate); client_.SetStatus(DM_STATUS_REQUEST_FAILED); NotifyIPAddressChanged(); EXPECT_EQ(GetLastDelay(), base::TimeDelta()); } struct ClientErrorTestParam { DeviceManagementStatus client_error; int64 expected_delay_ms; int backoff_factor; }; static const ClientErrorTestParam kClientErrorTestCases[] = { { DM_STATUS_REQUEST_INVALID, CloudPolicyRefreshScheduler::kUnmanagedRefreshDelayMs, 1 }, { DM_STATUS_REQUEST_FAILED, CloudPolicyRefreshScheduler::kInitialErrorRetryDelayMs, 2 }, { DM_STATUS_TEMPORARY_UNAVAILABLE, CloudPolicyRefreshScheduler::kInitialErrorRetryDelayMs, 2 }, { DM_STATUS_HTTP_STATUS_ERROR, CloudPolicyRefreshScheduler::kUnmanagedRefreshDelayMs, 1 }, { DM_STATUS_RESPONSE_DECODING_ERROR, CloudPolicyRefreshScheduler::kUnmanagedRefreshDelayMs, 1 }, { DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED, CloudPolicyRefreshScheduler::kUnmanagedRefreshDelayMs, 1 }, { DM_STATUS_SERVICE_DEVICE_NOT_FOUND, -1, 1 }, { DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID, -1, 1 }, { DM_STATUS_SERVICE_ACTIVATION_PENDING, kPolicyRefreshRate, 1 }, { DM_STATUS_SERVICE_INVALID_SERIAL_NUMBER, -1, 1 }, { DM_STATUS_SERVICE_MISSING_LICENSES, -1, 1 }, { DM_STATUS_SERVICE_DEVICE_ID_CONFLICT, -1, 1 }, { DM_STATUS_SERVICE_POLICY_NOT_FOUND, kPolicyRefreshRate, 1 }, }; class CloudPolicyRefreshSchedulerClientErrorTest : public CloudPolicyRefreshSchedulerSteadyStateTest, public testing::WithParamInterface { }; TEST_P(CloudPolicyRefreshSchedulerClientErrorTest, OnClientError) { client_.SetStatus(GetParam().client_error); task_runner_->ClearPendingTasks(); // See whether the error triggers the right refresh delay. int64 expected_delay_ms = GetParam().expected_delay_ms; client_.NotifyClientError(); if (expected_delay_ms >= 0) { CheckTiming(expected_delay_ms); // Check whether exponential backoff is working as expected and capped at // the regular refresh rate (if applicable). do { expected_delay_ms *= GetParam().backoff_factor; last_update_ = base::Time::NowFromSystemTime(); client_.NotifyClientError(); CheckTiming(std::max(std::min(expected_delay_ms, kPolicyRefreshRate), GetParam().expected_delay_ms)); } while (GetParam().backoff_factor > 1 && expected_delay_ms <= kPolicyRefreshRate); } else { EXPECT_EQ(base::TimeDelta(), GetLastDelay()); EXPECT_TRUE(task_runner_->GetPendingTasks().empty()); } } INSTANTIATE_TEST_CASE_P(CloudPolicyRefreshSchedulerClientErrorTest, CloudPolicyRefreshSchedulerClientErrorTest, testing::ValuesIn(kClientErrorTestCases)); } // namespace policy