blob: 57330f49f9e4aac561984790ba006123d3d3ade5 [file] [log] [blame]
Alex Vakulenko07216fe2014-09-19 15:31:09 -07001// Copyright 2014 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "buffet/states/state_manager.h"
6
7#include <base/files/file_enumerator.h>
8#include <base/files/file_path.h>
9#include <base/logging.h>
10#include <base/values.h>
11#include <chromeos/errors/error_codes.h>
Christopher Wileyf4d8d7b2015-02-06 13:59:20 -080012#include <chromeos/key_value_store.h>
Alex Vakulenko07216fe2014-09-19 15:31:09 -070013#include <chromeos/strings/string_utils.h>
14
15#include "buffet/states/error_codes.h"
Alex Vakulenko57123b22014-10-28 13:50:16 -070016#include "buffet/states/state_change_queue_interface.h"
Alex Vakulenko07216fe2014-09-19 15:31:09 -070017#include "buffet/utils.h"
18
19namespace buffet {
20
Christopher Wileyf4d8d7b2015-02-06 13:59:20 -080021namespace {
22
23const char kBaseStateFirmwareVersion[] = "base.firmwareVersion";
24
25} // namespace
26
27
Alex Vakulenko57123b22014-10-28 13:50:16 -070028StateManager::StateManager(StateChangeQueueInterface* state_change_queue)
29 : state_change_queue_(state_change_queue) {
30 CHECK(state_change_queue_) << "State change queue not specified";
31}
32
Vitaly Buka247620b2015-05-26 15:42:20 -070033void StateManager::AddOnChangedCallback(const base::Closure& callback) {
34 on_changed_.push_back(callback);
35 callback.Run(); // Force to read current state.
36}
37
Alex Vakulenko07216fe2014-09-19 15:31:09 -070038void StateManager::Startup() {
39 LOG(INFO) << "Initializing StateManager.";
40
41 // Load standard device state definition.
42 base::FilePath base_state_file("/etc/buffet/base_state.schema.json");
43 LOG(INFO) << "Loading standard state definition from "
44 << base_state_file.value();
45 CHECK(LoadBaseStateDefinition(base_state_file, nullptr))
46 << "Failed to load the standard state definition file.";
47
48 // Load component-specific device state definitions.
49 base::FilePath device_state_dir("/etc/buffet/states");
50 base::FileEnumerator enumerator(device_state_dir, false,
51 base::FileEnumerator::FILES,
52 FILE_PATH_LITERAL("*.schema.json"));
53 base::FilePath json_file_path = enumerator.Next();
54 while (!json_file_path.empty()) {
55 LOG(INFO) << "Loading state definition from " << json_file_path.value();
56 CHECK(LoadStateDefinition(json_file_path, nullptr))
57 << "Failed to load the state definition file.";
58 json_file_path = enumerator.Next();
59 }
60
61 // Load standard device state defaults.
62 base::FilePath base_state_defaults("/etc/buffet/base_state.defaults.json");
63 LOG(INFO) << "Loading base state defaults from "
64 << base_state_defaults.value();
65 CHECK(LoadStateDefaults(base_state_defaults, nullptr))
66 << "Failed to load the base state defaults.";
67
68 // Load component-specific device state defaults.
69 base::FileEnumerator enumerator2(device_state_dir, false,
70 base::FileEnumerator::FILES,
71 FILE_PATH_LITERAL("*.defaults.json"));
72 json_file_path = enumerator2.Next();
73 while (!json_file_path.empty()) {
74 LOG(INFO) << "Loading state defaults from " << json_file_path.value();
75 CHECK(LoadStateDefaults(json_file_path, nullptr))
76 << "Failed to load the state defaults.";
77 json_file_path = enumerator2.Next();
78 }
Christopher Wileyf4d8d7b2015-02-06 13:59:20 -080079
80 // Populate state fields that belong to the system.
81 base::FilePath lsb_release_path("/etc/lsb-release");
82 chromeos::KeyValueStore lsb_release_store;
83 std::string firmware_version;
84 if (lsb_release_store.Load(lsb_release_path)) {
85 if (!lsb_release_store.GetString("CHROMEOS_RELEASE_VERSION",
86 &firmware_version))
87 LOG(ERROR) << "Missing key for firmware version in version file.";
88
89 } else {
90 LOG(ERROR) << "Failed to read file for firmwareVersion.";
91 }
92 CHECK(SetPropertyValue(kBaseStateFirmwareVersion,
93 firmware_version,
94 base::Time::Now(),
95 nullptr));
Vitaly Buka247620b2015-05-26 15:42:20 -070096
97 for (const auto& cb : on_changed_)
98 cb.Run();
Alex Vakulenko07216fe2014-09-19 15:31:09 -070099}
100
101std::unique_ptr<base::DictionaryValue> StateManager::GetStateValuesAsJson(
102 chromeos::ErrorPtr* error) const {
103 std::unique_ptr<base::DictionaryValue> dict{new base::DictionaryValue};
104 for (const auto& pair : packages_) {
105 auto pkg_value = pair.second->GetValuesAsJson(error);
106 if (!pkg_value) {
107 dict.reset();
108 break;
109 }
110 dict->SetWithoutPathExpansion(pair.first, pkg_value.release());
111 }
112 return dict;
113}
114
Vitaly Buka247620b2015-05-26 15:42:20 -0700115bool StateManager::SetProperties(
116 const chromeos::VariantDictionary& property_set,
117 chromeos::ErrorPtr* error) {
118 base::Time timestamp = base::Time::Now();
119 bool all_success = true;
120 for (const auto& pair : property_set) {
121 if (!SetPropertyValue(pair.first, pair.second, timestamp, error)) {
122 // Remember that an error occurred but keep going and update the rest of
123 // the properties if possible.
124 all_success = false;
125 }
126 }
127 for (const auto& cb : on_changed_)
128 cb.Run();
129 return all_success;
130}
131
Alex Vakulenkoff73cf22014-10-29 09:53:52 -0700132bool StateManager::SetPropertyValue(const std::string& full_property_name,
133 const chromeos::Any& value,
134 const base::Time& timestamp,
135 chromeos::ErrorPtr* error) {
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700136 std::string package_name;
137 std::string property_name;
138 bool split = chromeos::string_utils::SplitAtFirst(
Vitaly Bukadb770e72015-03-10 19:33:33 -0700139 full_property_name, ".", &package_name, &property_name);
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700140 if (full_property_name.empty() || (split && property_name.empty())) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800141 chromeos::Error::AddTo(error, FROM_HERE, errors::state::kDomain,
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700142 errors::state::kPropertyNameMissing,
143 "Property name is missing");
144 return false;
145 }
146 if (!split || package_name.empty()) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800147 chromeos::Error::AddTo(error, FROM_HERE, errors::state::kDomain,
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700148 errors::state::kPackageNameMissing,
149 "Package name is missing in the property name");
150 return false;
151 }
152 StatePackage* package = FindPackage(package_name);
153 if (package == nullptr) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800154 chromeos::Error::AddToPrintf(error, FROM_HERE, errors::state::kDomain,
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700155 errors::state::kPropertyNotDefined,
156 "Unknown state property package '%s'",
157 package_name.c_str());
158 return false;
159 }
Alex Vakulenkoff73cf22014-10-29 09:53:52 -0700160 if (!package->SetPropertyValue(property_name, value, error))
Alex Vakulenko57123b22014-10-28 13:50:16 -0700161 return false;
162
Anton Muhin01829452014-11-21 02:16:04 +0400163 native_types::Object prop_set{{full_property_name,
164 package->GetProperty(property_name)}};
Alex Vakulenkoff73cf22014-10-29 09:53:52 -0700165 state_change_queue_->NotifyPropertiesUpdated(timestamp, prop_set);
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700166 return true;
167}
168
Alex Vakulenko57123b22014-10-28 13:50:16 -0700169std::vector<StateChange> StateManager::GetAndClearRecordedStateChanges() {
170 return state_change_queue_->GetAndClearRecordedStateChanges();
171}
172
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700173bool StateManager::LoadStateDefinition(const base::DictionaryValue& json,
174 const std::string& category,
175 chromeos::ErrorPtr* error) {
176 base::DictionaryValue::Iterator iter(json);
177 while (!iter.IsAtEnd()) {
178 std::string package_name = iter.key();
179 if (package_name.empty()) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800180 chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainBuffet,
181 kInvalidPackageError,
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700182 "State package name is empty");
183 return false;
184 }
185 const base::DictionaryValue* package_dict = nullptr;
186 if (!iter.value().GetAsDictionary(&package_dict)) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800187 chromeos::Error::AddToPrintf(error, FROM_HERE,
188 chromeos::errors::json::kDomain,
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700189 chromeos::errors::json::kObjectExpected,
190 "State package '%s' must be an object",
191 package_name.c_str());
192 return false;
193 }
194 StatePackage* package = FindOrCreatePackage(package_name);
195 CHECK(package) << "Unable to create state package " << package_name;
196 if (!package->AddSchemaFromJson(package_dict, error))
197 return false;
198 iter.Advance();
199 }
200 if (category != kDefaultCategory)
201 categories_.insert(category);
202
203 return true;
204}
205
206bool StateManager::LoadStateDefinition(const base::FilePath& json_file_path,
207 chromeos::ErrorPtr* error) {
208 std::unique_ptr<const base::DictionaryValue> json =
209 LoadJsonDict(json_file_path, error);
210 if (!json)
211 return false;
212 std::string category = json_file_path.BaseName().RemoveExtension().value();
213 if (category == kDefaultCategory) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800214 chromeos::Error::AddToPrintf(error, FROM_HERE, kErrorDomainBuffet,
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700215 kInvalidCategoryError,
216 "Invalid state category specified in '%s'",
217 json_file_path.value().c_str());
218 return false;
219 }
220
221 if (!LoadStateDefinition(*json, category, error)) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800222 chromeos::Error::AddToPrintf(error, FROM_HERE, kErrorDomainBuffet,
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700223 kFileReadError,
224 "Failed to load file '%s'",
225 json_file_path.value().c_str());
226 return false;
227 }
228 return true;
229}
230
231bool StateManager::LoadBaseStateDefinition(const base::FilePath& json_file_path,
232 chromeos::ErrorPtr* error) {
233 std::unique_ptr<const base::DictionaryValue> json =
234 LoadJsonDict(json_file_path, error);
235 if (!json)
236 return false;
237 if (!LoadStateDefinition(*json, kDefaultCategory, error)) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800238 chromeos::Error::AddToPrintf(error, FROM_HERE, kErrorDomainBuffet,
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700239 kFileReadError,
240 "Failed to load file '%s'",
241 json_file_path.value().c_str());
242 return false;
243 }
244 return true;
245}
246
247bool StateManager::LoadStateDefaults(const base::DictionaryValue& json,
248 chromeos::ErrorPtr* error) {
249 base::DictionaryValue::Iterator iter(json);
250 while (!iter.IsAtEnd()) {
251 std::string package_name = iter.key();
252 if (package_name.empty()) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800253 chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainBuffet,
254 kInvalidPackageError,
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700255 "State package name is empty");
256 return false;
257 }
258 const base::DictionaryValue* package_dict = nullptr;
259 if (!iter.value().GetAsDictionary(&package_dict)) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800260 chromeos::Error::AddToPrintf(error, FROM_HERE,
261 chromeos::errors::json::kDomain,
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700262 chromeos::errors::json::kObjectExpected,
263 "State package '%s' must be an object",
264 package_name.c_str());
265 return false;
266 }
267 StatePackage* package = FindPackage(package_name);
268 if (package == nullptr) {
269 chromeos::Error::AddToPrintf(
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800270 error, FROM_HERE, chromeos::errors::json::kDomain,
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700271 chromeos::errors::json::kObjectExpected,
272 "Providing values for undefined state package '%s'",
273 package_name.c_str());
274 return false;
275 }
276 if (!package->AddValuesFromJson(package_dict, error))
277 return false;
278 iter.Advance();
279 }
280 return true;
281}
282
283bool StateManager::LoadStateDefaults(const base::FilePath& json_file_path,
284 chromeos::ErrorPtr* error) {
285 std::unique_ptr<const base::DictionaryValue> json =
286 LoadJsonDict(json_file_path, error);
287 if (!json)
288 return false;
289 if (!LoadStateDefaults(*json, error)) {
Alex Vakulenkoac8037d2014-11-11 11:42:05 -0800290 chromeos::Error::AddToPrintf(error, FROM_HERE, kErrorDomainBuffet,
Alex Vakulenko07216fe2014-09-19 15:31:09 -0700291 kFileReadError,
292 "Failed to load file '%s'",
293 json_file_path.value().c_str());
294 return false;
295 }
296 return true;
297}
298
299StatePackage* StateManager::FindPackage(const std::string& package_name) {
300 auto it = packages_.find(package_name);
301 return (it != packages_.end()) ? it->second.get() : nullptr;
302}
303
304StatePackage* StateManager::FindOrCreatePackage(
305 const std::string& package_name) {
306 StatePackage* package = FindPackage(package_name);
307 if (package == nullptr) {
308 std::unique_ptr<StatePackage> new_package{new StatePackage(package_name)};
309 package = packages_.emplace(package_name,
310 std::move(new_package)).first->second.get();
311 }
312 return package;
313}
314
315} // namespace buffet