// 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 "chrome/browser/policy/cloud/cloud_policy_refresh_scheduler.h" #include #include "base/bind.h" #include "base/bind_helpers.h" #include "base/command_line.h" #include "base/memory/scoped_ptr.h" #include "base/metrics/histogram.h" #include "base/sequenced_task_runner.h" #include "base/time/default_tick_clock.h" #include "base/time/tick_clock.h" #include "chrome/browser/policy/cloud/cloud_policy_constants.h" #include "components/policy/core/common/policy_switches.h" namespace policy { namespace { // The maximum rate at which to refresh policies. const size_t kMaxRefreshesPerHour = 5; // The maximum time to wait for the invalidations service to become available // before starting to issue requests. const int kWaitForInvalidationsTimeoutSeconds = 5; } // namespace #if defined(OS_ANDROID) const int64 CloudPolicyRefreshScheduler::kDefaultRefreshDelayMs = 24 * 60 * 60 * 1000; // 1 day. const int64 CloudPolicyRefreshScheduler::kUnmanagedRefreshDelayMs = 24 * 60 * 60 * 1000; // 1 day. // Delay for periodic refreshes when the invalidations service is available, // in milliseconds. // TODO(joaodasilva): increase this value once we're confident that the // invalidations channel works as expected. const int64 CloudPolicyRefreshScheduler::kWithInvalidationsRefreshDelayMs = 24 * 60 * 60 * 1000; // 1 day. const int64 CloudPolicyRefreshScheduler::kInitialErrorRetryDelayMs = 5 * 60 * 1000; // 5 minutes. const int64 CloudPolicyRefreshScheduler::kRefreshDelayMinMs = 30 * 60 * 1000; // 30 minutes. const int64 CloudPolicyRefreshScheduler::kRefreshDelayMaxMs = 7 * 24 * 60 * 60 * 1000; // 1 week. #else const int64 CloudPolicyRefreshScheduler::kDefaultRefreshDelayMs = 3 * 60 * 60 * 1000; // 3 hours. const int64 CloudPolicyRefreshScheduler::kUnmanagedRefreshDelayMs = 24 * 60 * 60 * 1000; // 1 day. // Delay for periodic refreshes when the invalidations service is available, // in milliseconds. // TODO(joaodasilva): increase this value once we're confident that the // invalidations channel works as expected. const int64 CloudPolicyRefreshScheduler::kWithInvalidationsRefreshDelayMs = 3 * 60 * 60 * 1000; // 3 hours. const int64 CloudPolicyRefreshScheduler::kInitialErrorRetryDelayMs = 5 * 60 * 1000; // 5 minutes. const int64 CloudPolicyRefreshScheduler::kRefreshDelayMinMs = 30 * 60 * 1000; // 30 minutes. const int64 CloudPolicyRefreshScheduler::kRefreshDelayMaxMs = 24 * 60 * 60 * 1000; // 1 day. #endif CloudPolicyRefreshScheduler::CloudPolicyRefreshScheduler( CloudPolicyClient* client, CloudPolicyStore* store, const scoped_refptr& task_runner) : client_(client), store_(store), task_runner_(task_runner), error_retry_delay_ms_(kInitialErrorRetryDelayMs), refresh_delay_ms_(kDefaultRefreshDelayMs), rate_limiter_(kMaxRefreshesPerHour, base::TimeDelta::FromHours(1), base::Bind(&CloudPolicyRefreshScheduler::RefreshNow, base::Unretained(this)), task_runner_, scoped_ptr(new base::DefaultTickClock())), invalidations_available_(false), creation_time_(base::Time::NowFromSystemTime()) { client_->AddObserver(this); store_->AddObserver(this); net::NetworkChangeNotifier::AddIPAddressObserver(this); UpdateLastRefreshFromPolicy(); // Give some time for the invalidation service to become available before the // first refresh if there is already policy present. if (store->has_policy()) WaitForInvalidationService(); else ScheduleRefresh(); } CloudPolicyRefreshScheduler::~CloudPolicyRefreshScheduler() { store_->RemoveObserver(this); client_->RemoveObserver(this); net::NetworkChangeNotifier::RemoveIPAddressObserver(this); } void CloudPolicyRefreshScheduler::SetRefreshDelay(int64 refresh_delay) { refresh_delay_ms_ = std::min(std::max(refresh_delay, kRefreshDelayMinMs), kRefreshDelayMaxMs); ScheduleRefresh(); } void CloudPolicyRefreshScheduler::RefreshSoon() { // An external consumer needs a policy update now (e.g. a new extension, or // the InvalidationService received a policy invalidation), so don't wait // before fetching anymore. wait_for_invalidations_timeout_callback_.Cancel(); rate_limiter_.PostRequest(); } void CloudPolicyRefreshScheduler::SetInvalidationServiceAvailability( bool is_available) { if (!creation_time_.is_null()) { base::TimeDelta elapsed = base::Time::NowFromSystemTime() - creation_time_; UMA_HISTOGRAM_MEDIUM_TIMES("Enterprise.PolicyInvalidationsStartupTime", elapsed); creation_time_ = base::Time(); } if (is_available == invalidations_available_) { // No change in state. If we're currently WaitingForInvalidationService // then the timeout task will eventually execute and trigger a reschedule; // let the InvalidationService keep retrying until that happens. return; } wait_for_invalidations_timeout_callback_.Cancel(); invalidations_available_ = is_available; // Schedule a refresh since the refresh delay has been updated; however, allow // some time for the invalidation service to update. If it is now online, the // wait allows pending invalidations to be delivered. If it is now offline, // then the wait allows for the service to recover from transient failure // before falling back on the polling behavior. WaitForInvalidationService(); } void CloudPolicyRefreshScheduler::OnPolicyFetched(CloudPolicyClient* client) { error_retry_delay_ms_ = kInitialErrorRetryDelayMs; // Schedule the next refresh. last_refresh_ = base::Time::NowFromSystemTime(); ScheduleRefresh(); } void CloudPolicyRefreshScheduler::OnRegistrationStateChanged( CloudPolicyClient* client) { error_retry_delay_ms_ = kInitialErrorRetryDelayMs; // The client might have registered, so trigger an immediate refresh. RefreshNow(); } void CloudPolicyRefreshScheduler::OnClientError(CloudPolicyClient* client) { // Save the status for below. DeviceManagementStatus status = client_->status(); // Schedule an error retry if applicable. last_refresh_ = base::Time::NowFromSystemTime(); ScheduleRefresh(); // Update the retry delay. if (client->is_registered() && (status == DM_STATUS_REQUEST_FAILED || status == DM_STATUS_TEMPORARY_UNAVAILABLE)) { error_retry_delay_ms_ = std::min(error_retry_delay_ms_ * 2, refresh_delay_ms_); } else { error_retry_delay_ms_ = kInitialErrorRetryDelayMs; } } void CloudPolicyRefreshScheduler::OnStoreLoaded(CloudPolicyStore* store) { UpdateLastRefreshFromPolicy(); // Re-schedule the next refresh in case the is_managed bit changed. ScheduleRefresh(); } void CloudPolicyRefreshScheduler::OnStoreError(CloudPolicyStore* store) { // If |store_| fails, the is_managed bit that it provides may become stale. // The best guess in that situation is to assume is_managed didn't change and // continue using the stale information. Thus, no specific response to a store // error is required. NB: Changes to is_managed fire OnStoreLoaded(). } void CloudPolicyRefreshScheduler::OnIPAddressChanged() { if (client_->status() == DM_STATUS_REQUEST_FAILED) RefreshSoon(); } void CloudPolicyRefreshScheduler::UpdateLastRefreshFromPolicy() { if (!last_refresh_.is_null()) return; // If the client has already fetched policy, assume that happened recently. If // that assumption ever breaks, the proper thing to do probably is to move the // |last_refresh_| bookkeeping to CloudPolicyClient. if (!client_->responses().empty()) { last_refresh_ = base::Time::NowFromSystemTime(); return; } #if defined(OS_ANDROID) // Refreshing on Android: // - if no user is signed-in then the |client_| is never registered and // nothing happens here. // - if the user is signed-in but isn't enterprise then the |client_| is // never registered and nothing happens here. // - if the user is signed-in but isn't registered for policy yet then the // |client_| isn't registered either; the UserPolicySigninService will try // to register, and OnRegistrationStateChanged() will be invoked later. // - if the client is signed-in and has policy then its timestamp is used to // determine when to perform the next fetch, which will be once the cached // version is considered "old enough". // // If there is an old policy cache then a fetch will be performed "soon"; if // that fetch fails then a retry is attempted after a delay, with exponential // backoff. If those fetches keep failing then the cached timestamp is *not* // updated, and another fetch (and subsequent retries) will be attempted // again on the next startup. // // But if the cached policy is considered fresh enough then we try to avoid // fetching again on startup; the Android logic differs from the desktop in // this aspect. if (store_->has_policy() && store_->policy()->has_timestamp()) { last_refresh_ = base::Time::UnixEpoch() + base::TimeDelta::FromMilliseconds(store_->policy()->timestamp()); } #else // If there is a cached non-managed response, make sure to only re-query the // server after kUnmanagedRefreshDelayMs. NB: For existing policy, an // immediate refresh is intentional. if (store_->has_policy() && store_->policy()->has_timestamp() && !store_->is_managed()) { last_refresh_ = base::Time::UnixEpoch() + base::TimeDelta::FromMilliseconds(store_->policy()->timestamp()); } #endif } void CloudPolicyRefreshScheduler::RefreshNow() { last_refresh_ = base::Time(); ScheduleRefresh(); } void CloudPolicyRefreshScheduler::ScheduleRefresh() { // If the client isn't registered, there is nothing to do. if (!client_->is_registered()) { refresh_callback_.Cancel(); return; } // Don't schedule anything yet if we're still waiting for the invalidations // service. if (WaitingForInvalidationService()) return; // If policy invalidations are available then periodic updates are done at // a much lower rate; otherwise use the |refresh_delay_ms_| value. int64 refresh_delay_ms = invalidations_available_ ? kWithInvalidationsRefreshDelayMs : refresh_delay_ms_; // If there is a registration, go by the client's status. That will tell us // what the appropriate refresh delay should be. switch (client_->status()) { case DM_STATUS_SUCCESS: if (store_->is_managed()) RefreshAfter(refresh_delay_ms); else RefreshAfter(kUnmanagedRefreshDelayMs); return; case DM_STATUS_SERVICE_ACTIVATION_PENDING: case DM_STATUS_SERVICE_POLICY_NOT_FOUND: RefreshAfter(refresh_delay_ms); return; case DM_STATUS_REQUEST_FAILED: case DM_STATUS_TEMPORARY_UNAVAILABLE: RefreshAfter(error_retry_delay_ms_); return; case DM_STATUS_REQUEST_INVALID: case DM_STATUS_HTTP_STATUS_ERROR: case DM_STATUS_RESPONSE_DECODING_ERROR: case DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED: RefreshAfter(kUnmanagedRefreshDelayMs); return; case DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID: case DM_STATUS_SERVICE_DEVICE_NOT_FOUND: case DM_STATUS_SERVICE_INVALID_SERIAL_NUMBER: case DM_STATUS_SERVICE_DEVICE_ID_CONFLICT: case DM_STATUS_SERVICE_MISSING_LICENSES: // Need a re-registration, no use in retrying. refresh_callback_.Cancel(); return; } NOTREACHED() << "Invalid client status " << client_->status(); RefreshAfter(kUnmanagedRefreshDelayMs); } void CloudPolicyRefreshScheduler::PerformRefresh() { if (client_->is_registered()) { // Update |last_refresh_| so another fetch isn't triggered inadvertently. last_refresh_ = base::Time::NowFromSystemTime(); // The result of this operation will be reported through a callback, at // which point the next refresh will be scheduled. client_->FetchPolicy(); return; } // This should never happen, as the registration change should have been // handled via OnRegistrationStateChanged(). NOTREACHED(); } void CloudPolicyRefreshScheduler::RefreshAfter(int delta_ms) { base::TimeDelta delta(base::TimeDelta::FromMilliseconds(delta_ms)); refresh_callback_.Cancel(); // Schedule the callback. base::TimeDelta delay = std::max((last_refresh_ + delta) - base::Time::NowFromSystemTime(), base::TimeDelta()); refresh_callback_.Reset( base::Bind(&CloudPolicyRefreshScheduler::PerformRefresh, base::Unretained(this))); task_runner_->PostDelayedTask(FROM_HERE, refresh_callback_.callback(), delay); } void CloudPolicyRefreshScheduler::WaitForInvalidationService() { DCHECK(!WaitingForInvalidationService()); wait_for_invalidations_timeout_callback_.Reset( base::Bind( &CloudPolicyRefreshScheduler::OnWaitForInvalidationServiceTimeout, base::Unretained(this))); base::TimeDelta delay = base::TimeDelta::FromSeconds(kWaitForInvalidationsTimeoutSeconds); // Do not wait for the invalidation service if the feature is disabled. if (CommandLine::ForCurrentProcess()->HasSwitch( switches::kDisableCloudPolicyPush)) { delay = base::TimeDelta(); } task_runner_->PostDelayedTask( FROM_HERE, wait_for_invalidations_timeout_callback_.callback(), delay); } void CloudPolicyRefreshScheduler::OnWaitForInvalidationServiceTimeout() { wait_for_invalidations_timeout_callback_.Cancel(); ScheduleRefresh(); } bool CloudPolicyRefreshScheduler::WaitingForInvalidationService() const { return !wait_for_invalidations_timeout_callback_.IsCancelled(); } } // namespace policy