summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormnissler@chromium.org <mnissler@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-08-20 09:31:27 +0000
committermnissler@chromium.org <mnissler@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-08-20 09:31:27 +0000
commitad2f0d5290fa56d582387622ba80ec7467430d96 (patch)
tree1562b79f5779aa56df569c806fdbca3e0aa47fda
parent3644f774ee2579756b3e76d2582fa88784765f83 (diff)
downloadchromium_src-ad2f0d5290fa56d582387622ba80ec7467430d96.zip
chromium_src-ad2f0d5290fa56d582387622ba80ec7467430d96.tar.gz
chromium_src-ad2f0d5290fa56d582387622ba80ec7467430d96.tar.bz2
Support change detection and reloading in ConfigDirPolicyProvider.
BUG=52418 TEST=unit tests Review URL: http://codereview.chromium.org/3124025 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@56832 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/policy/config_dir_policy_provider.cc203
-rw-r--r--chrome/browser/policy/config_dir_policy_provider.h118
-rw-r--r--chrome/browser/policy/config_dir_policy_provider_unittest.cc114
3 files changed, 414 insertions, 21 deletions
diff --git a/chrome/browser/policy/config_dir_policy_provider.cc b/chrome/browser/policy/config_dir_policy_provider.cc
index 59509fc..8c64efa 100644
--- a/chrome/browser/policy/config_dir_policy_provider.cc
+++ b/chrome/browser/policy/config_dir_policy_provider.cc
@@ -8,22 +8,109 @@
#include "base/file_util.h"
#include "base/logging.h"
-#include "base/scoped_ptr.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/common/json_value_serializer.h"
-ConfigDirPolicyProvider::ConfigDirPolicyProvider(const FilePath& config_dir)
- : config_dir_(config_dir) {
+namespace {
+
+// Amount of time we wait for the files in the policy directory to settle before
+// trying to load it. This alleviates the problem of reading partially written
+// files and allows to batch quasi-simultaneous changes.
+const int kSettleIntervalSeconds = 5;
+
+// The time interval for rechecking policy. This is our fallback in case the
+// directory watch fails or doesn't report a change.
+const int kReloadIntervalMinutes = 15;
+
+} // namespace
+
+// PolicyDirLoader implementation:
+
+PolicyDirLoader::PolicyDirLoader(
+ base::WeakPtr<ConfigDirPolicyProvider> provider,
+ const FilePath& config_dir,
+ int settle_interval_seconds,
+ int reload_interval_minutes)
+ : provider_(provider),
+ origin_loop_(MessageLoop::current()),
+ config_dir_(config_dir),
+ reload_task_(NULL),
+ settle_interval_seconds_(settle_interval_seconds),
+ reload_interval_minutes_(reload_interval_minutes) {
+ // Force an initial load, so GetPolicy() works.
+ policy_.reset(Load());
+ DCHECK(policy_.get());
}
-bool ConfigDirPolicyProvider::Provide(ConfigurationPolicyStore* store) {
- scoped_ptr<DictionaryValue> policy(ReadPolicies());
- DecodePolicyValueTree(policy.get(), store);
- return true;
+void PolicyDirLoader::Stop() {
+ if (!ChromeThread::CurrentlyOn(ChromeThread::FILE)) {
+ ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE,
+ NewRunnableMethod(this, &PolicyDirLoader::Stop));
+ return;
+ }
+
+ if (reload_task_) {
+ reload_task_->Cancel();
+ reload_task_ = NULL;
+ }
}
-DictionaryValue* ConfigDirPolicyProvider::ReadPolicies() {
+void PolicyDirLoader::Reload() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
+
+ // Check the directory time in order to see whether a reload is required.
+ base::Time now = base::Time::Now();
+ base::TimeDelta delay;
+ if (!IsSafeToReloadPolicy(now, &delay)) {
+ ScheduleReloadTask(delay);
+ return;
+ }
+
+ // Load the policy definitions.
+ scoped_ptr<DictionaryValue> new_policy(Load());
+
+ // Check again in case the directory has changed while reading it.
+ if (!IsSafeToReloadPolicy(now, &delay)) {
+ ScheduleReloadTask(delay);
+ return;
+ }
+
+ // Replace policy definition.
+ bool changed = false;
+ {
+ AutoLock lock(lock_);
+ changed = !policy_->Equals(new_policy.get());
+ policy_.reset(new_policy.release());
+ }
+
+ // There's a change, report it!
+ if (changed) {
+ LOG(INFO) << "Policy reload from " << config_dir_.value() << " succeeded.";
+ origin_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &PolicyDirLoader::NotifyPolicyChanged));
+ }
+
+ // As a safeguard in case the file watcher fails, schedule a reload task
+ // that'll make us recheck after a reasonable interval.
+ ScheduleReloadTask(base::TimeDelta::FromMinutes(reload_interval_minutes_));
+}
+
+DictionaryValue* PolicyDirLoader::GetPolicy() {
+ AutoLock lock(lock_);
+ return static_cast<DictionaryValue*>(policy_->DeepCopy());
+}
+
+void PolicyDirLoader::OnFilePathChanged(const FilePath& path) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
+ Reload();
+}
+
+void PolicyDirLoader::OnError() {
+ LOG(ERROR) << "FileWatcher on " << config_dir_.value() << " failed.";
+}
+
+DictionaryValue* PolicyDirLoader::Load() {
// Enumerate the files and sort them lexicographically.
std::set<FilePath> files;
file_util::FileEnumerator file_enumerator(config_dir_, false,
@@ -56,6 +143,105 @@ DictionaryValue* ConfigDirPolicyProvider::ReadPolicies() {
return policy;
}
+bool PolicyDirLoader::IsSafeToReloadPolicy(const base::Time& now,
+ base::TimeDelta* delay) {
+ DCHECK(delay);
+ file_util::FileInfo dir_info;
+
+ // Reading an empty directory or a file is always safe.
+ if (!file_util::GetFileInfo(config_dir_, &dir_info) ||
+ !dir_info.is_directory) {
+ return true;
+ }
+
+ // Check for too recent changes.
+ base::TimeDelta settleInterval(
+ base::TimeDelta::FromSeconds(settle_interval_seconds_));
+ base::TimeDelta age = now - dir_info.last_modified;
+ if (age < settleInterval) {
+ *delay = settleInterval - age;
+ return false;
+ }
+
+ return true;
+}
+
+void PolicyDirLoader::ScheduleReloadTask(const base::TimeDelta& delay) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
+
+ if (reload_task_)
+ reload_task_->Cancel();
+
+ reload_task_ = NewRunnableMethod(this, &PolicyDirLoader::ReloadFromTask);
+ ChromeThread::PostDelayedTask(ChromeThread::FILE, FROM_HERE, reload_task_,
+ delay.InMilliseconds());
+}
+
+void PolicyDirLoader::NotifyPolicyChanged() {
+ DCHECK_EQ(origin_loop_, MessageLoop::current());
+ if (provider_)
+ provider_->NotifyStoreOfPolicyChange();
+}
+
+void PolicyDirLoader::ReloadFromTask() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
+
+ // Drop the reference to the reload task, since the task might be the only
+ // referer that keeps us alive, so we should not Cancel() it.
+ reload_task_ = NULL;
+
+ Reload();
+}
+
+// PolicyDirWatcher implementation:
+
+void PolicyDirWatcher::Init(PolicyDirLoader* loader) {
+ // Initialization can happen early when the file thread is not yet available.
+ // So post a task to ourselves on th UI thread which will run after threading
+ // is up and schedule watch initialization on the file thread.
+ ChromeThread::PostTask(ChromeThread::UI, FROM_HERE,
+ NewRunnableMethod(this,
+ &PolicyDirWatcher::InitWatcher,
+ scoped_refptr<PolicyDirLoader>(loader)));
+}
+
+void PolicyDirWatcher::InitWatcher(
+ const scoped_refptr<PolicyDirLoader>& loader) {
+ if (!ChromeThread::CurrentlyOn(ChromeThread::FILE)) {
+ ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE,
+ NewRunnableMethod(this, &PolicyDirWatcher::InitWatcher, loader));
+ return;
+ }
+
+ if (!Watch(loader->config_dir(), loader.get()))
+ loader->OnError();
+
+ // There might have been changes to the directory in the time between
+ // construction of the loader and initialization of the watcher. Call reload
+ // to detect if that is the case.
+ loader->Reload();
+}
+
+// ConfigDirPolicyProvider implementation:
+
+ConfigDirPolicyProvider::ConfigDirPolicyProvider(const FilePath& config_dir) {
+ loader_ = new PolicyDirLoader(AsWeakPtr(), config_dir, kSettleIntervalSeconds,
+ kReloadIntervalMinutes);
+ watcher_ = new PolicyDirWatcher;
+ watcher_->Init(loader_.get());
+}
+
+ConfigDirPolicyProvider::~ConfigDirPolicyProvider() {
+ loader_->Stop();
+}
+
+bool ConfigDirPolicyProvider::Provide(ConfigurationPolicyStore* store) {
+ scoped_ptr<DictionaryValue> policy(loader_->GetPolicy());
+ DCHECK(policy.get());
+ DecodePolicyValueTree(policy.get(), store);
+ return true;
+}
+
void ConfigDirPolicyProvider::DecodePolicyValueTree(
DictionaryValue* policies,
ConfigurationPolicyStore* store) {
@@ -71,4 +257,3 @@ void ConfigDirPolicyProvider::DecodePolicyValueTree(
// TODO(mnissler): Handle preference overrides once |ConfigurationPolicyStore|
// supports it.
}
-
diff --git a/chrome/browser/policy/config_dir_policy_provider.h b/chrome/browser/policy/config_dir_policy_provider.h
index 9412ff2..d299423 100644
--- a/chrome/browser/policy/config_dir_policy_provider.h
+++ b/chrome/browser/policy/config_dir_policy_provider.h
@@ -8,9 +8,111 @@
#include "base/basictypes.h"
#include "base/file_path.h"
+#include "base/lock.h"
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "base/weak_ptr.h"
+#include "chrome/browser/file_path_watcher.h"
#include "chrome/browser/policy/configuration_policy_provider.h"
+class ConfigDirPolicyProvider;
class DictionaryValue;
+class MessageLoop;
+
+// FilePathWatcher delegate implementation that handles change notifications for
+// the configuration directory. It keeps the authorative version of the
+// currently effective policy dictionary and updates it as appropriate.
+class PolicyDirLoader : public FilePathWatcher::Delegate {
+ public:
+ // Create a new loader that'll load its data from |config_dir|.
+ PolicyDirLoader(base::WeakPtr<ConfigDirPolicyProvider> provider,
+ const FilePath& config_dir,
+ int settle_interval_second,
+ int reload_interval_minutes);
+
+ // Stops any pending reload tasks.
+ void Stop();
+
+ // Reloads the policies and sends out a notification, if appropriate. Must be
+ // called on the file thread.
+ void Reload();
+
+ // Get the current dictionary value object. Ownership of the returned value is
+ // transferred to the caller.
+ DictionaryValue* GetPolicy();
+
+ const FilePath& config_dir() { return config_dir_; }
+
+ // FilePathWatcher::Delegate implementation:
+ void OnFilePathChanged(const FilePath& path);
+ void OnError();
+
+ private:
+ // Load the policy information. Ownership of the return value is transferred
+ // to the caller.
+ DictionaryValue* Load();
+
+ // Check the directory modification time to see whether reading the
+ // configuration directory is safe. If not, returns false and the delay until
+ // it is considered safe to reload in |delay|.
+ bool IsSafeToReloadPolicy(const base::Time& now, base::TimeDelta* delay);
+
+ // Post a reload task. Must be called on the file thread.
+ void ScheduleReloadTask(const base::TimeDelta& delay);
+
+ // Notifies the policy provider to send out a policy changed notification.
+ // Must be called on |origin_loop_|.
+ void NotifyPolicyChanged();
+
+ // Invoked from the reload task on the file thread.
+ void ReloadFromTask();
+
+ // The provider this loader is associated with. Access only on the thread that
+ // called the constructor. See |origin_loop_| below.
+ base::WeakPtr<ConfigDirPolicyProvider> provider_;
+
+ // The message loop on which this object was constructed and |provider_|
+ // received on. Recorded so we can call back into the non thread safe provider
+ // to fire the notification.
+ MessageLoop* origin_loop_;
+
+ // The directory in which we look for configuration files.
+ const FilePath config_dir_;
+
+ // Protects |policy_|.
+ Lock lock_;
+
+ // The current policy definition.
+ scoped_ptr<DictionaryValue> policy_;
+
+ // The reload task. Access only on the file thread. Holds a reference to the
+ // currently posted task, so we can cancel and repost it if necessary.
+ CancelableTask* reload_task_;
+
+ // Settle and reload intervals.
+ const int settle_interval_seconds_;
+ const int reload_interval_minutes_;
+
+ DISALLOW_COPY_AND_ASSIGN(PolicyDirLoader);
+};
+
+// Wraps a FilePathWatcher for the configuration directory and takes care of
+// initializing the watcher object on the file thread.
+class PolicyDirWatcher : public FilePathWatcher,
+ public base::RefCountedThreadSafe<PolicyDirWatcher> {
+ public:
+ PolicyDirWatcher() {}
+
+ // Run initialization. This is in a separate method since we need to post a
+ // task (which cannot be done from the constructor).
+ void Init(PolicyDirLoader* loader);
+
+ private:
+ // Actually sets up the watch with the FilePathWatcher code.
+ void InitWatcher(const scoped_refptr<PolicyDirLoader>& loader);
+
+ DISALLOW_COPY_AND_ASSIGN(PolicyDirWatcher);
+};
// A policy provider implementation backed by a set of files in a given
// directory. The files should contain JSON-formatted policy settings. They are
@@ -18,24 +120,26 @@ class DictionaryValue;
// ConfigurationPolicyProvider interface. The files are consulted in
// lexicographic file name order, so the last value read takes precedence in
// case of preference key collisions.
-class ConfigDirPolicyProvider : public ConfigurationPolicyProvider {
+class ConfigDirPolicyProvider
+ : public ConfigurationPolicyProvider,
+ public base::SupportsWeakPtr<ConfigDirPolicyProvider> {
public:
explicit ConfigDirPolicyProvider(const FilePath& config_dir);
- virtual ~ConfigDirPolicyProvider() { }
+ virtual ~ConfigDirPolicyProvider();
// ConfigurationPolicyProvider implementation.
virtual bool Provide(ConfigurationPolicyStore* store);
private:
- // Read and merge the files from the configuration directory.
- DictionaryValue* ReadPolicies();
-
// Decodes the value tree and writes the configuration to the given |store|.
void DecodePolicyValueTree(DictionaryValue* policies,
ConfigurationPolicyStore* store);
- // The directory in which we look for configuration files.
- const FilePath config_dir_;
+ // Watches for changes to the configuration directory.
+ scoped_refptr<PolicyDirWatcher> watcher_;
+
+ // The loader object we use internally.
+ scoped_refptr<PolicyDirLoader> loader_;
DISALLOW_COPY_AND_ASSIGN(ConfigDirPolicyProvider);
};
diff --git a/chrome/browser/policy/config_dir_policy_provider_unittest.cc b/chrome/browser/policy/config_dir_policy_provider_unittest.cc
index 9e3d25b..c7a2e94 100644
--- a/chrome/browser/policy/config_dir_policy_provider_unittest.cc
+++ b/chrome/browser/policy/config_dir_policy_provider_unittest.cc
@@ -9,9 +9,24 @@
#include "chrome/browser/policy/mock_configuration_policy_store.h"
#include "chrome/common/json_value_serializer.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/gmock/include/gmock/gmock.h"
-class ConfigDirPolicyProviderTest : public testing::Test {
+using testing::Mock;
+
+namespace {
+
+// Shorter reload intervals for testing PolicyDirWatcher.
+const int kSettleIntervalSecondsForTesting = 0;
+const int kReloadIntervalMinutesForTesting = 1;
+
+} // namespace
+
+class ConfigDirPolicyProviderTestBase : public testing::Test {
protected:
+ ConfigDirPolicyProviderTestBase()
+ : ui_thread_(ChromeThread::UI, &loop_),
+ file_thread_(ChromeThread::FILE, &loop_) {}
+
virtual void SetUp() {
// Determine the directory to use for testing.
ASSERT_TRUE(PathService::Get(base::DIR_TEMP, &test_dir_));
@@ -22,12 +37,10 @@ class ConfigDirPolicyProviderTest : public testing::Test {
file_util::Delete(test_dir_, true);
file_util::CreateDirectory(test_dir_);
ASSERT_TRUE(file_util::DirectoryExists(test_dir_));
-
- // Create a fresh policy store mock.
- policy_store_.reset(new MockConfigurationPolicyStore());
}
virtual void TearDown() {
+ loop_.RunAllPending();
// Clean up test directory.
ASSERT_TRUE(file_util::Delete(test_dir_, true));
ASSERT_FALSE(file_util::PathExists(test_dir_));
@@ -40,10 +53,101 @@ class ConfigDirPolicyProviderTest : public testing::Test {
JSONStringValueSerializer serializer(&data);
serializer.Serialize(dict);
FilePath file_path(test_dir_.AppendASCII(file_name));
- file_util::WriteFile(file_path, data.c_str(), data.size());
+ ASSERT_TRUE(file_util::WriteFile(file_path, data.c_str(), data.size()));
}
FilePath test_dir_;
+ MessageLoop loop_;
+ ChromeThread ui_thread_;
+ ChromeThread file_thread_;
+};
+
+// A mock provider that allows us to capture to reload notifications.
+class MockConfigDirPolicyProvider : public ConfigDirPolicyProvider {
+ public:
+ explicit MockConfigDirPolicyProvider(const FilePath& config_dir_)
+ : ConfigDirPolicyProvider(config_dir_) {}
+
+ MOCK_METHOD0(NotifyStoreOfPolicyChange, void());
+};
+
+class PolicyDirLoaderTest : public ConfigDirPolicyProviderTestBase {
+ protected:
+ PolicyDirLoaderTest() {}
+
+ virtual void SetUp() {
+ ConfigDirPolicyProviderTestBase::SetUp();
+ provider_.reset(new MockConfigDirPolicyProvider(test_dir_));
+ }
+
+ virtual void TearDown() {
+ provider_.reset(NULL);
+ ConfigDirPolicyProviderTestBase::TearDown();
+ }
+
+ scoped_ptr<MockConfigDirPolicyProvider> provider_;
+};
+
+TEST_F(PolicyDirLoaderTest, BasicLoad) {
+ DictionaryValue test_dict;
+ test_dict.SetString("HomepageLocation", "http://www.google.com");
+ WriteConfigFile(test_dict, "config_file");
+
+ scoped_refptr<PolicyDirLoader> loader_(
+ new PolicyDirLoader(provider_->AsWeakPtr(), test_dir_,
+ kSettleIntervalSecondsForTesting,
+ kReloadIntervalMinutesForTesting));
+ scoped_ptr<DictionaryValue> policy(loader_->GetPolicy());
+ EXPECT_TRUE(policy.get());
+ EXPECT_EQ(1U, policy->size());
+
+ std::string str_value;
+ EXPECT_TRUE(policy->GetString("HomepageLocation", &str_value));
+ EXPECT_EQ("http://www.google.com", str_value);
+
+ loader_->Stop();
+}
+
+TEST_F(PolicyDirLoaderTest, TestRefresh) {
+ scoped_refptr<PolicyDirLoader> loader_(
+ new PolicyDirLoader(provider_->AsWeakPtr(), test_dir_,
+ kSettleIntervalSecondsForTesting,
+ kReloadIntervalMinutesForTesting));
+ scoped_ptr<DictionaryValue> policy(loader_->GetPolicy());
+ EXPECT_TRUE(policy.get());
+ EXPECT_EQ(0U, policy->size());
+
+ DictionaryValue test_dict;
+ test_dict.SetString("HomepageLocation", "http://www.google.com");
+ WriteConfigFile(test_dict, "config_file");
+
+ EXPECT_CALL(*provider_, NotifyStoreOfPolicyChange()).Times(1);
+ loader_->OnFilePathChanged(test_dir_.AppendASCII("config_file"));
+
+ // Run the loop. The refresh should be handled immediately since the settle
+ // interval has been disabled.
+ loop_.RunAllPending();
+ Mock::VerifyAndClearExpectations(provider_.get());
+
+ policy.reset(loader_->GetPolicy());
+ EXPECT_TRUE(policy.get());
+ EXPECT_EQ(1U, policy->size());
+
+ std::string str_value;
+ EXPECT_TRUE(policy->GetString("HomepageLocation", &str_value));
+ EXPECT_EQ("http://www.google.com", str_value);
+
+ loader_->Stop();
+}
+
+class ConfigDirPolicyProviderTest : public ConfigDirPolicyProviderTestBase {
+ protected:
+ virtual void SetUp() {
+ ConfigDirPolicyProviderTestBase::SetUp();
+ // Create a fresh policy store mock.
+ policy_store_.reset(new MockConfigurationPolicyStore());
+ }
+
scoped_ptr<MockConfigurationPolicyStore> policy_store_;
};