buffet: Expose RegistrationStatus over DBus This new property lets applications monitor Buffet's connection to cloud services. BUG=brillo:16 TEST=Unittests, buffet_Registration has been expanded appropriately. CQ-DEPEND=CL:*199337 Change-Id: I30253e8199cb65068a74dd8b780a8ab0954bf9fa Reviewed-on: https://chromium-review.googlesource.com/250011 Reviewed-by: Alex Vakulenko <avakulenko@chromium.org> Tested-by: Christopher Wiley <wiley@chromium.org> Commit-Queue: Christopher Wiley <wiley@chromium.org>
diff --git a/buffet/buffet.gyp b/buffet/buffet.gyp index 6628f4d..51312e5 100644 --- a/buffet/buffet.gyp +++ b/buffet/buffet.gyp
@@ -37,6 +37,7 @@ 'dbus_bindings/org.chromium.Buffet.Manager.xml', 'dbus_constants.cc', 'manager.cc', + 'registration_status.cc', 'storage_impls.cc', 'states/error_codes.cc', 'states/state_change_queue.cc',
diff --git a/buffet/buffet_client.cc b/buffet/buffet_client.cc index 10efa6e..5a8a72a 100644 --- a/buffet/buffet_client.cc +++ b/buffet/buffet_client.cc
@@ -146,7 +146,12 @@ return return_code; object_manager_.reset(new org::chromium::Buffet::ObjectManagerProxy{bus_}); - manager_proxy_.reset(new org::chromium::Buffet::ManagerProxy{bus_}); + auto manager_instances = object_manager_->GetManagerInstances(); + if (manager_instances.empty()) { + fprintf(stderr, "Buffet daemon was offline."); + return EX_UNAVAILABLE; + } + manager_proxy_ = manager_instances.front(); auto args = CommandLine::ForCurrentProcess()->GetArgs(); @@ -361,8 +366,8 @@ } std::unique_ptr<org::chromium::Buffet::ObjectManagerProxy> object_manager_; - std::unique_ptr<org::chromium::Buffet::ManagerProxy> manager_proxy_; - int exit_code_ = EX_OK; + org::chromium::Buffet::ManagerProxy* manager_proxy_{nullptr}; + int exit_code_{EX_OK}; DISALLOW_COPY_AND_ASSIGN(Daemon); };
diff --git a/buffet/dbus_bindings/org.chromium.Buffet.Manager.xml b/buffet/dbus_bindings/org.chromium.Buffet.Manager.xml index daf1458..ea5e574 100644 --- a/buffet/dbus_bindings/org.chromium.Buffet.Manager.xml +++ b/buffet/dbus_bindings/org.chromium.Buffet.Manager.xml
@@ -41,5 +41,26 @@ <arg name="echoed_message" type="s" direction="out"/> <annotation name="org.chromium.DBus.Method.Kind" value="simple"/> </method> + + <property name="Status" type="s" access="read"> + <tp:docstring> + State of Buffet's cloud registration. + Possible values include: + + "offline": Buffet has credentials, but no connectivity to the Internet. + "cloud_error": Buffet has credentials, but cannot contact cloud services + to verify their validity. This could indicate that cloud + services are down, or that DNS is not resolving. + "invalid_credentials": Buffet has credentials, but they are no longer + valid. + "unregistered": Buffet has no credentials, either from an out of + box state, or because those credentials have been + rejected by the cloud service. Note that we + can unregistered with or without Internet connectivity. + "registering": Buffet has been provided with credentials, and is + registering with the cloud. + "registered": Buffet is online and registered with cloud services. + </tp:docstring> + </property> </interface> </node>
diff --git a/buffet/device_registration_info.cc b/buffet/device_registration_info.cc index 3e8fd00..16f576a 100644 --- a/buffet/device_registration_info.cc +++ b/buffet/device_registration_info.cc
@@ -146,12 +146,14 @@ const std::shared_ptr<StateManager>& state_manager, std::unique_ptr<chromeos::KeyValueStore> config_store, const std::shared_ptr<chromeos::http::Transport>& transport, - const std::shared_ptr<StorageInterface>& state_store) + const std::shared_ptr<StorageInterface>& state_store, + const StatusHandler& status_handler) : transport_{transport}, storage_{state_store}, command_manager_{command_manager}, state_manager_{state_manager}, - config_store_{std::move(config_store)} { + config_store_{std::move(config_store)}, + registration_status_handler_{status_handler} { } DeviceRegistrationInfo::~DeviceRegistrationInfo() = default; @@ -251,6 +253,9 @@ description_ = description; location_ = location; + if (HaveRegistrationCredentials(nullptr)) + SetRegistrationStatus(RegistrationStatus::kOffline); + return true; } @@ -286,19 +291,23 @@ } bool DeviceRegistrationInfo::CheckRegistration(chromeos::ErrorPtr* error) { - LOG(INFO) << "Checking device registration record."; - if (refresh_token_.empty() || - device_id_.empty() || - device_robot_account_.empty()) { - LOG(INFO) << "No valid device registration record found."; + return HaveRegistrationCredentials(error) && + ValidateAndRefreshAccessToken(error); +} + +bool DeviceRegistrationInfo::HaveRegistrationCredentials( + chromeos::ErrorPtr* error) { + const bool have_credentials = !refresh_token_.empty() && + !device_id_.empty() && + !device_robot_account_.empty(); + + VLOG(1) << "Device registration record " + << ((have_credentials) ? "found" : "not found."); + if (!have_credentials) chromeos::Error::AddTo(error, FROM_HERE, kErrorDomainGCD, "device_not_registered", "No valid device registration record found"); - return false; - } - - LOG(INFO) << "Device registration record found."; - return ValidateAndRefreshAccessToken(error); + return have_credentials; } bool DeviceRegistrationInfo::ValidateAndRefreshAccessToken( @@ -543,6 +552,7 @@ // We're going to respond with our success immediately and we'll StartDevice // shortly after. ScheduleStartDevice(base::TimeDelta::FromSeconds(0)); + SetRegistrationStatus(RegistrationStatus::kRegistered); return device_id_; } @@ -704,7 +714,6 @@ void DeviceRegistrationInfo::StartDevice(chromeos::ErrorPtr* error) { if (!CheckRegistration(error)) return; - base::Bind( &DeviceRegistrationInfo::UpdateDeviceResource, base::Unretained(this), @@ -820,6 +829,10 @@ base::Bind(&DeviceRegistrationInfo::PublishStateUpdates, base::Unretained(this)), base::TimeDelta::FromSeconds(7)); + // TODO(wiley): This is the very bare minimum of state to report to local + // services interested in our GCD state. Build a more + // robust model of our state with respect to the server. + SetRegistrationStatus(RegistrationStatus::kRegistered); } void DeviceRegistrationInfo::PublishCommands(const base::ListValue& commands) { @@ -915,4 +928,14 @@ return false; } +void DeviceRegistrationInfo::SetRegistrationStatus( + RegistrationStatus new_status) { + if (new_status == registration_status_) + return; + VLOG(1) << "Changing registration status to " << StatusToString(new_status); + registration_status_ = new_status; + if (!registration_status_handler_.is_null()) + registration_status_handler_.Run(registration_status_); +} + } // namespace buffet
diff --git a/buffet/device_registration_info.h b/buffet/device_registration_info.h index 4409d25..95c8b0d 100644 --- a/buffet/device_registration_info.h +++ b/buffet/device_registration_info.h
@@ -19,6 +19,7 @@ #include <chromeos/errors/error.h> #include <chromeos/http/http_transport.h> +#include "buffet/registration_status.h" #include "buffet/storage_interface.h" #include "buffet/xmpp/xmpp_client.h" @@ -44,13 +45,15 @@ public: // This is a helper class for unit testing. class TestHelper; + using StatusHandler = base::Callback<void(RegistrationStatus)>; DeviceRegistrationInfo( const std::shared_ptr<CommandManager>& command_manager, const std::shared_ptr<StateManager>& state_manager, std::unique_ptr<chromeos::KeyValueStore> config_store, const std::shared_ptr<chromeos::http::Transport>& transport, - const std::shared_ptr<StorageInterface>& state_store); + const std::shared_ptr<StorageInterface>& state_store, + const StatusHandler& status_handler); ~DeviceRegistrationInfo() override; @@ -60,6 +63,11 @@ LOG(FATAL) << "No write watcher is configured"; } + // Returns our current best known registration status. + RegistrationStatus GetRegistrationStatus() const { + return registration_status_; + } + // Returns the authorization HTTP header that can be used to talk // to GCD server for authenticated device communication. // Make sure ValidateAndRefreshAccessToken() is called before this call. @@ -136,6 +144,9 @@ // Saves the device registration to cache. bool Save() const; + // Checks whether we have credentials generated during registration. + bool HaveRegistrationCredentials(chromeos::ErrorPtr* error); + // Makes sure the access token is available and up-to-date. bool ValidateAndRefreshAccessToken(chromeos::ErrorPtr* error); @@ -191,6 +202,8 @@ std::unique_ptr<base::DictionaryValue> BuildDeviceResource( chromeos::ErrorPtr* error); + void SetRegistrationStatus(RegistrationStatus new_status); + std::unique_ptr<XmppClient> xmpp_client_; base::MessageLoopForIO::FileDescriptorWatcher fd_watcher_; @@ -225,6 +238,10 @@ // Buffet configuration. std::unique_ptr<chromeos::KeyValueStore> config_store_; + // Tracks our current registration status. + RegistrationStatus registration_status_{RegistrationStatus::kUnregistered}; + StatusHandler registration_status_handler_; + friend class TestHelper; base::WeakPtrFactory<DeviceRegistrationInfo> weak_factory_{this}; DISALLOW_COPY_AND_ASSIGN(DeviceRegistrationInfo);
diff --git a/buffet/device_registration_info_unittest.cc b/buffet/device_registration_info_unittest.cc index ee49b0a..dee5614 100644 --- a/buffet/device_registration_info_unittest.cc +++ b/buffet/device_registration_info_unittest.cc
@@ -189,12 +189,18 @@ config_store->SetString("model_id", "AAA"); config_store->SetString("oauth_url", test_data::kOAuthURL); config_store->SetString("service_url", test_data::kServiceURL); + auto mock_callback = base::Bind( + &DeviceRegistrationInfoTest::OnRegistrationStatusChange, + base::Unretained(this)); dev_reg_ = std::unique_ptr<DeviceRegistrationInfo>( new DeviceRegistrationInfo(command_manager_, state_manager_, std::move(config_store), - transport_, storage_)); + transport_, storage_, + mock_callback)); } + MOCK_METHOD1(OnRegistrationStatusChange, void(RegistrationStatus)); + base::DictionaryValue data_; std::shared_ptr<MemStorage> storage_; std::shared_ptr<chromeos::http::fake::Transport> transport_; @@ -425,6 +431,7 @@ EXPECT_EQ(1, storage_->save_count()); // The device info must have been saved. EXPECT_EQ(3, transport_->GetRequestCount()); + EXPECT_EQ(RegistrationStatus::kRegistered, dev_reg_->GetRegistrationStatus()); // Validate the device info saved to storage... auto storage_data = storage_->Load(); @@ -449,4 +456,17 @@ EXPECT_EQ(test_data::kServiceURL, value); } +TEST_F(DeviceRegistrationInfoTest, OOBRegistrationStatus) { + // After we've been initialized, we should be either offline or unregistered, + // depending on whether or not we've found credentials. + EXPECT_TRUE(dev_reg_->Load()); + EXPECT_EQ(RegistrationStatus::kUnregistered, + dev_reg_->GetRegistrationStatus()); + // Put some credentials into our state, make sure we call that offline. + SetDefaultDeviceRegistration(&data_); + storage_->Save(&data_); + EXPECT_TRUE(dev_reg_->Load()); + EXPECT_EQ(RegistrationStatus::kOffline, dev_reg_->GetRegistrationStatus()); +} + } // namespace buffet
diff --git a/buffet/manager.cc b/buffet/manager.cc index a1457c4..2e96fbc 100644 --- a/buffet/manager.cc +++ b/buffet/manager.cc
@@ -63,12 +63,16 @@ // TODO(avakulenko): Figure out security implications of storing // device info state data unencrypted. device_info_ = std::unique_ptr<DeviceRegistrationInfo>( - new DeviceRegistrationInfo(command_manager_, - state_manager_, - std::move(config_store), - chromeos::http::Transport::CreateDefault(), - std::move(state_store))); + new DeviceRegistrationInfo( + command_manager_, + state_manager_, + std::move(config_store), + chromeos::http::Transport::CreateDefault(), + std::move(state_store), + base::Bind(&Manager::OnRegistrationStatusChange, + base::Unretained(this)))); device_info_->Load(); + OnRegistrationStatusChange(device_info_->GetRegistrationStatus()); // Wait a significant amount of time for local daemons to publish their // state to Buffet before publishing it to the cloud. // TODO(wiley) We could do a lot of things here to either expose this @@ -218,4 +222,8 @@ return message; } +void Manager::OnRegistrationStatusChange(RegistrationStatus status) { + dbus_adaptor_.SetStatus(StatusToString(status)); +} + } // namespace buffet
diff --git a/buffet/manager.h b/buffet/manager.h index d7d71bf..1c891ec 100644 --- a/buffet/manager.h +++ b/buffet/manager.h
@@ -75,6 +75,8 @@ // Handles calls to org.chromium.Buffet.Manager.Test() std::string TestMethod(const std::string& message) override; + void OnRegistrationStatusChange(RegistrationStatus status); + org::chromium::Buffet::ManagerAdaptor dbus_adaptor_{this}; chromeos::dbus_utils::DBusObject dbus_object_;
diff --git a/buffet/registration_status.cc b/buffet/registration_status.cc new file mode 100644 index 0000000..4fb1721 --- /dev/null +++ b/buffet/registration_status.cc
@@ -0,0 +1,25 @@ +// Copyright 2015 The Chromium OS 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 "buffet/registration_status.h" + +namespace buffet { + +std::string StatusToString(RegistrationStatus status) { + switch (status) { + case RegistrationStatus::kOffline: + return "offline"; + case RegistrationStatus::kCloudError: + return "cloud_error"; + case RegistrationStatus::kUnregistered: + return "unregistered"; + case RegistrationStatus::kRegistering: + return "registering"; + case RegistrationStatus::kRegistered: + return "registered"; + } + return "unknown"; +} + +} // namespace buffet
diff --git a/buffet/registration_status.h b/buffet/registration_status.h new file mode 100644 index 0000000..fa2e2cb --- /dev/null +++ b/buffet/registration_status.h
@@ -0,0 +1,25 @@ +// Copyright 2015 The Chromium OS 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 BUFFET_REGISTRATION_STATUS_H_ +#define BUFFET_REGISTRATION_STATUS_H_ + +#include <string> + +namespace buffet { + +// See the DBus interface XML file for complete descriptions of these states. +enum class RegistrationStatus { + kOffline, // We have credentials but are offline. + kCloudError, // We're online, but can't talk to cloud services. + kUnregistered, // We have no credentials. + kRegistering, // We've just been given credentials. + kRegistered, // We're registered and online. +}; + +std::string StatusToString(RegistrationStatus status); + +} // namespace buffet + +#endif // BUFFET_REGISTRATION_STATUS_H_