blob: 77de7b6ba2cc76375320444254a2049ddd749e14 [file] [log] [blame]
// 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 "psyche/psyched/registrar.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <base/logging.h>
#include <base/macros.h>
#include <germ/constants.h>
#include <gtest/gtest.h>
#include <protobinder/binder_manager_stub.h>
#include <protobinder/binder_proxy.h>
#include <protobinder/iinterface.h>
#include <protobinder/proto_util.h>
#include <soma/constants.h>
#include "psyche/common/binder_test_base.h"
#include "psyche/proto_bindings/psyche.pb.h"
#include "psyche/proto_bindings/psyche.pb.rpc.h"
#include "psyche/proto_bindings/soma.pb.h"
#include "psyche/proto_bindings/soma.pb.rpc.h"
#include "psyche/proto_bindings/soma_container_spec.pb.h"
#include "psyche/psyched/cell_stub.h"
#include "psyche/psyched/client_stub.h"
#include "psyche/psyched/service_stub.h"
#include "psyche/psyched/stub_factory.h"
using protobinder::BinderProxy;
using soma::ContainerSpec;
namespace psyche {
namespace {
// Stub implementation of the Soma interface that returns canned ContainerSpecs.
class SomaInterfaceStub : public soma::ISoma {
public:
SomaInterfaceStub() : return_value_(0) {}
~SomaInterfaceStub() override = default;
void set_return_value(int value) { return_value_ = value; }
// Sets the ContainerSpec to return in response to a request for
// |service_name|.
void AddEphemeralContainerSpec(const ContainerSpec& spec,
const std::string& service_name) {
service_specs_[service_name] = spec;
}
// Adds a ContainerSpec to be returned by GetPersistentContainerSpecs().
void AddPersistentContainerSpec(const ContainerSpec& spec) {
persistent_specs_.push_back(spec);
}
// ISoma:
int GetContainerSpec(soma::GetContainerSpecRequest* in,
soma::GetContainerSpecResponse* out) override {
const auto& it = service_specs_.find(in->service_name());
if (it != service_specs_.end())
out->mutable_container_spec()->CopyFrom(it->second);
return return_value_;
}
int GetPersistentContainerSpecs(
soma::GetPersistentContainerSpecsRequest* in,
soma::GetPersistentContainerSpecsResponse* out) override {
for (const auto& spec : persistent_specs_)
out->add_container_specs()->CopyFrom(spec);
return return_value_;
}
private:
// Keyed by service name for which the spec should be returned.
std::map<std::string, ContainerSpec> service_specs_;
std::vector<ContainerSpec> persistent_specs_;
// binder result returned by handlers.
int return_value_;
DISALLOW_COPY_AND_ASSIGN(SomaInterfaceStub);
};
class RegistrarTest : public BinderTestBase {
public:
RegistrarTest()
: factory_(new StubFactory),
registrar_(new Registrar),
soma_proxy_(nullptr),
// Create an interface immediately so that tests can add persistent
// cells to it before calling Init().
soma_(new SomaInterfaceStub) {
registrar_->SetFactoryForTesting(
std::unique_ptr<FactoryInterface>(factory_));
}
~RegistrarTest() override {
// Disgusting hack: manually delete the interface if Init() never got called
// to transfer ownership to |binder_manager_|.
// TODO(derat): Remove this after http://brbug.com/648 is fixed.
if (soma_ && !soma_proxy_)
delete soma_;
}
protected:
// Performs initialization. Should be called at the beginning of each test;
// separated from the c'tor so that persistent services can be created first.
void Init() {
registrar_->Init();
// Pass ownership of |soma_| to InitSoma(), which will pass it to
// |binder_manager_|.
CHECK(InitSoma(std::unique_ptr<SomaInterfaceStub>(soma_)));
}
// Initializes |soma_proxy_| and |soma_| and registers them with |registrar_|
// and |binder_manager_|. May be called from within a test to simulate somad
// restarting and reregistering itself with psyched.
bool InitSoma(
std::unique_ptr<SomaInterfaceStub> interface) WARN_UNUSED_RESULT {
soma_proxy_ = CreateBinderProxy().release();
soma_ = interface.get();
binder_manager_->SetTestInterface(
soma_proxy_, std::unique_ptr<IInterface>(interface.release()));
return RegisterService(soma::kSomaServiceName,
std::unique_ptr<BinderProxy>(soma_proxy_));
}
// Returns the client that |factory_| created for |client_proxy|, crashing if
// it doesn't exist.
ClientStub* GetClientOrDie(const BinderProxy& client_proxy) {
ClientStub* client = factory_->GetClient(client_proxy);
CHECK(client) << "No client for proxy " << client_proxy.handle();
return client;
}
// Returns the service that |factory_| created for |service_name|, crashing if
// it doesn't exist.
ServiceStub* GetServiceOrDie(const std::string& service_name) {
ServiceStub* service = factory_->GetService(service_name);
CHECK(service) << "No service named \"" << service_name << "\"";
return service;
}
// Calls |registrar_|'s RegisterService method, returning true on success.
bool RegisterService(
const std::string& service_name,
std::unique_ptr<BinderProxy> service_proxy) WARN_UNUSED_RESULT {
RegisterServiceRequest request;
request.set_name(service_name);
// |service_proxy| will be extracted from the protocol buffer into another
// std::unique_ptr, so release it from the original one here.
protobinder::StoreBinderInProto(*(service_proxy.release()),
request.mutable_binder());
RegisterServiceResponse response;
CHECK_EQ(registrar_->RegisterService(&request, &response), 0)
<< "RegisterService call for " << service_name << " failed";
return response.success();
}
// Calls |registrar_|'s RequestService method, returning true if a failure
// wasn't immediately reported back to the client.
bool RequestService(const std::string& service_name,
std::unique_ptr<BinderProxy> client_proxy) {
// Hang on to the proxy so we can use it to find the client later.
BinderProxy* client_proxy_ptr = client_proxy.get();
ClientStub* client = factory_->GetClient(*client_proxy_ptr);
const int initial_failures =
client ? client->GetServiceRequestFailures(service_name) : 0;
RequestServiceRequest request;
request.set_name(service_name);
// |service_proxy| will be extracted from the protocol buffer into another
// std::unique_ptr, so release it from the original one here.
protobinder::StoreBinderInProto(*(client_proxy.release()),
request.mutable_client_binder());
CHECK_EQ(registrar_->RequestService(&request), 0)
<< "RequestService call for " << service_name << " failed";
const int new_failures = GetClientOrDie(*client_proxy_ptr)
->GetServiceRequestFailures(service_name);
CHECK_GE(new_failures, initial_failures)
<< "Client " << client_proxy_ptr->handle() << "'s request failures "
<< "for \"" << service_name << " decreased from " << initial_failures
<< " to " << new_failures;
return new_failures == initial_failures;
}
// Creates a CellStub object named |cell_name| and registers it in
// |soma_| and |factory_| so it'll be returned for a request for
// |service_name|. The caller is responsible for calling the stub's
// AddService() method to make it claim to provide services.
//
// The returned object is owned by |registrar_| (and may not
// persist beyond the request if |registrar_| decides not to keep it).
CellStub* AddEphemeralCell(const std::string& cell_name,
const std::string& service_name) {
ContainerSpec spec;
spec.set_name(cell_name);
soma_->AddEphemeralContainerSpec(spec, service_name);
CellStub* cell = new CellStub(cell_name);
factory_->SetCell(cell_name, std::unique_ptr<CellStub>(cell));
return cell;
}
// Creates a CellStub object named |cell_name| and registers it in |soma_| and
// |factory_| so it'll be returned as a persistent cell.
//
// The returned object is owned by |registrar_| (and may not
// persist beyond the request if |registrar_| decides not to keep it).
CellStub* AddPersistentCell(const std::string& cell_name) {
ContainerSpec spec;
spec.set_name(cell_name);
spec.set_is_persistent(true);
soma_->AddPersistentContainerSpec(spec);
CellStub* cell = new CellStub(cell_name);
factory_->SetCell(cell_name, std::unique_ptr<CellStub>(cell));
return cell;
}
StubFactory* factory_; // Owned by |registrar_|.
std::unique_ptr<Registrar> registrar_;
BinderProxy* soma_proxy_; // Owned by |registrar_|.
SomaInterfaceStub* soma_; // Owned by |binder_manager_|.
private:
DISALLOW_COPY_AND_ASSIGN(RegistrarTest);
};
TEST_F(RegistrarTest, RegisterAndRequestService) {
Init();
// Register a service.
const std::string kServiceName("service");
BinderProxy* service_proxy = CreateBinderProxy().release();
EXPECT_TRUE(RegisterService(kServiceName,
std::unique_ptr<BinderProxy>(service_proxy)));
// Request the service.
BinderProxy* client_proxy = CreateBinderProxy().release();
EXPECT_TRUE(RequestService(kServiceName,
std::unique_ptr<BinderProxy>(client_proxy)));
// Check that the service was added to the client.
ClientStub* client = GetClientOrDie(*client_proxy);
ServiceStub* service = GetServiceOrDie(kServiceName);
ASSERT_EQ(1U, client->GetServices().count(service));
}
TEST_F(RegistrarTest, ReregisterService) {
Init();
// Register a service.
const std::string kServiceName("service");
BinderProxy* service_proxy = CreateBinderProxy().release();
EXPECT_TRUE(RegisterService(kServiceName,
std::unique_ptr<BinderProxy>(service_proxy)));
// The service should hold the correct proxy.
ServiceStub* service = GetServiceOrDie(kServiceName);
EXPECT_EQ(service_proxy, service->GetProxy());
// Trying to register the same service again while it's still running should
// fail.
service_proxy = CreateBinderProxy().release();
EXPECT_FALSE(RegisterService(kServiceName,
std::unique_ptr<BinderProxy>(service_proxy)));
// After clearing the service's proxy, it should be possible to register the
// service again.
service->SetProxyForTesting(std::unique_ptr<BinderProxy>());
service_proxy = CreateBinderProxy().release();
EXPECT_TRUE(RegisterService(kServiceName,
std::unique_ptr<BinderProxy>(service_proxy)));
EXPECT_EQ(service_proxy, service->GetProxy());
}
TEST_F(RegistrarTest, QuerySomaForServices) {
Init();
const std::string kCellName("/foo/org.example.cell.json");
const std::string kService1Name("org.example.cell.service1");
const std::string kService2Name("org.example.cell.service2");
CellStub* cell = AddEphemeralCell(kCellName, kService1Name);
ServiceStub* service1 = cell->AddService(kService1Name);
ServiceStub* service2 = cell->AddService(kService2Name);
// When a client requests the first service, check that the cell is launched
// and that the client is added to the service (so it can be notified after
// the service is registered).
BinderProxy* client1_proxy = CreateBinderProxy().release();
EXPECT_TRUE(RequestService(kService1Name,
std::unique_ptr<BinderProxy>(client1_proxy)));
EXPECT_EQ(1, cell->launch_count());
ClientStub* client1 = GetClientOrDie(*client1_proxy);
EXPECT_TRUE(service1->HasClient(client1));
EXPECT_FALSE(service2->HasClient(client1));
// Check that a second client is also added to the first service.
BinderProxy* client2_proxy = CreateBinderProxy().release();
EXPECT_TRUE(RequestService(kService1Name,
std::unique_ptr<BinderProxy>(client2_proxy)));
EXPECT_EQ(1, cell->launch_count());
ClientStub* client2 = GetClientOrDie(*client2_proxy);
EXPECT_TRUE(service1->HasClient(client2));
EXPECT_FALSE(service2->HasClient(client2));
// Now make a third client request the second service.
BinderProxy* client3_proxy = CreateBinderProxy().release();
EXPECT_TRUE(RequestService(kService2Name,
std::unique_ptr<BinderProxy>(client3_proxy)));
EXPECT_EQ(1, cell->launch_count());
ClientStub* client3 = GetClientOrDie(*client3_proxy);
EXPECT_FALSE(service1->HasClient(client3));
EXPECT_TRUE(service2->HasClient(client3));
}
// Tests that failure is reported when a ContainerSpec is returned in response
// to a request for a service that it doesn't actually provide.
TEST_F(RegistrarTest, UnknownService) {
Init();
// Create a ContainerSpec that'll get returned for a given service, but don't
// make it claim to provide that service.
const std::string kCellName("/foo/org.example.cell.json");
const std::string kServiceName("org.example.cell.service");
AddEphemeralCell(kCellName, kServiceName);
// A request for the service should fail.
EXPECT_FALSE(RequestService(kServiceName, CreateBinderProxy()));
// TODO(derat): Once germd communication is present, check that no request was
// made to launch the cell. We can't check the CellStub since it ought to have
// been deleted by this point.
}
// Tests that a second ContainerSpec claiming to provide a service that's
// already provided by an earlier ContainerSpec is ignored.
TEST_F(RegistrarTest, DuplicateService) {
Init();
const std::string kCell1Name("/foo/org.example.cell1.json");
const std::string kService1Name("org.example.cell1.service");
CellStub* cell1 = AddEphemeralCell(kCell1Name, kService1Name);
cell1->AddService(kService1Name);
// Create a second spec, returned for a second service, that also claims
// ownership of the first cell's service.
const std::string kCell2Name("/foo/org.example.cell2.json");
const std::string kService2Name("org.example.cell2.service");
CellStub* cell2 = AddEphemeralCell(kCell2Name, kService2Name);
cell2->AddService(kService1Name);
cell2->AddService(kService2Name);
// Requesting the first service should succeed, but requesting the second
// service should fail due to the second cell claiming that it also provides
// the first service.
EXPECT_TRUE(RequestService(kService1Name, CreateBinderProxy()));
EXPECT_FALSE(RequestService(kService2Name, CreateBinderProxy()));
}
// Tests that a duplicate ContainerSpec (i.e. one that was previously received
// from somad, but that now claims to provide a service that it didn't provide
// earlier) gets ignored.
TEST_F(RegistrarTest, ServiceListChanged) {
Init();
const std::string kCellName("/foo/org.example.cell.json");
const std::string kService1Name("org.example.cell.service1");
CellStub* cell1 = AddEphemeralCell(kCellName, kService1Name);
cell1->AddService(kService1Name);
EXPECT_TRUE(RequestService(kService1Name, CreateBinderProxy()));
// A request for a second service that returns the already-created spec (which
// didn't previously claim to provide the second service) should fail.
const std::string kService2Name("org.example.cell.service2");
CellStub* cell2 = AddEphemeralCell(kCellName, kService2Name);
cell2->AddService(kService1Name);
cell2->AddService(kService2Name);
EXPECT_FALSE(RequestService(kService2Name, CreateBinderProxy()));
}
// Tests that persistent ContainerSpecs are fetched from soma during
// initialization and launched.
TEST_F(RegistrarTest, PersistentCells) {
// Create two persistent cells with one service each.
const std::string kCell1Name("/foo/org.example.cell1.json");
const std::string kService1Name("org.example.cell1.service");
CellStub* cell1 = AddPersistentCell(kCell1Name);
ServiceStub* service1 = cell1->AddService(kService1Name);
const std::string kCell2Name("/foo/org.example.cell2.json");
const std::string kService2Name("org.example.cell2.service");
CellStub* cell2 = AddPersistentCell(kCell2Name);
ServiceStub* service2 = cell2->AddService(kService2Name);
// After initialization, both cells should be launched.
Init();
EXPECT_EQ(1, cell1->launch_count());
EXPECT_EQ(1, cell2->launch_count());
// Their services should also be available to clients.
EXPECT_TRUE(RequestService(kService1Name, CreateBinderProxy()));
EXPECT_EQ(1U, service1->num_clients());
EXPECT_TRUE(RequestService(kService2Name, CreateBinderProxy()));
EXPECT_EQ(1U, service2->num_clients());
}
// Tests that Registrar doesn't hand out its connection to somad.
TEST_F(RegistrarTest, DontProvideSomaService) {
Init();
EXPECT_FALSE(RequestService(soma::kSomaServiceName, CreateBinderProxy()));
}
// Tests various failures when communicating with somad.
TEST_F(RegistrarTest, SomaFailures) {
Init();
const std::string kCellName("/foo/org.example.cell.json");
const std::string kServiceName("org.example.cell.service");
CellStub* cell = AddEphemeralCell(kCellName, kServiceName);
cell->AddService(kServiceName);
// Failure should be reported for RPC errors.
soma_->set_return_value(-1);
EXPECT_FALSE(RequestService(kServiceName, CreateBinderProxy()));
// Now report that the somad binder proxy died.
soma_->set_return_value(0);
binder_manager_->ReportBinderDeath(soma_proxy_);
EXPECT_FALSE(RequestService(kServiceName, CreateBinderProxy()));
// Register a new proxy for somad and check that the next service request is
// successful.
EXPECT_TRUE(
InitSoma(std::unique_ptr<SomaInterfaceStub>(new SomaInterfaceStub)));
cell = AddEphemeralCell(kCellName, kServiceName);
cell->AddService(kServiceName);
EXPECT_TRUE(RequestService(kServiceName, CreateBinderProxy()));
}
// Tests that Registrar doesn't hand out its connection to germd.
TEST_F(RegistrarTest, DontProvideGermService) {
Init();
EXPECT_FALSE(RequestService(germ::kGermServiceName, CreateBinderProxy()));
}
// Tests that Registrar reports cell launch failures to clients.
TEST_F(RegistrarTest, CellLaunchFailure) {
Init();
const std::string kCellName("/foo/org.example.cell.json");
const std::string kServiceName("org.example.cell.service");
CellStub* cell = AddEphemeralCell(kCellName, kServiceName);
cell->AddService(kServiceName);
cell->set_launch_return_value(false);
EXPECT_FALSE(RequestService(kServiceName, CreateBinderProxy()));
}
} // namespace
} // namespace psyche