/*
 * Copyright 2019 Bloomberg Finance LP
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <buildboxcasd_executionproxyinstance.h>
#include <buildboxcasd_localacinstance.h>
#include <buildboxcasd_localacproxyinstance.h>
#include <buildboxcasd_localcasinstance.h>
#include <buildboxcasd_localcasproxyinstance.h>
#include <buildboxcasd_localexecutioninstance.h>
#include <buildboxcasd_localrainstance.h>
#include <buildboxcasd_localraproxyinstance.h>
#include <buildboxcasd_metricnames.h>
#include <buildboxcasd_requestcontextmanager.h>
#include <buildboxcasd_server.h>

#include <buildboxcommon_digestgenerator.h>
#include <buildboxcommon_exception.h>
#include <buildboxcommon_filestager.h>
#include <buildboxcommon_fusestager.h>
#include <buildboxcommon_grpcerror.h>
#include <buildboxcommon_hardlinkstager.h>
#include <buildboxcommon_logging.h>
#include <buildboxcommon_requestmetadata.h>
#include <buildboxcommon_stringutils.h>
#include <buildboxcommon_systemutils.h>
#include <buildboxcommonmetrics_distributionmetricutil.h>
#include <buildboxcommonmetrics_durationmetrictimer.h>
#include <buildboxcommonmetrics_metricguard.h>
#include <cstring>
#include <grpcpp/health_check_service_interface.h>
#include <memory>
#include <stdexcept>
#include <unordered_map>

#include <utility>

namespace buildboxcasd {

using namespace build::bazel::remote::execution::v2;
using namespace build::bazel::semver;
using namespace google::bytestream;
using namespace google::protobuf::util;
using namespace google::protobuf;
using namespace buildboxcommon;

using grpc::ServerContext;
using grpc::ServerReader;
using grpc::ServerWriter;
using grpc::Status;

namespace {

std::unordered_map<StageTreeRequest::StagingMode, std::shared_ptr<FileStager>>
createFileStagers(const std::shared_ptr<LocalCas> &storage,
                  const std::shared_ptr<ThreadPool> &threadPool)
{
    std::unordered_map<StageTreeRequest::StagingMode,
                       std::shared_ptr<FileStager>>
        fileStagers;
    // In the current implementation, HardlinkStager is always supported
    std::shared_ptr<FileStager> copyStager =
        std::make_shared<HardLinkStager>(storage.get(), threadPool);

    std::shared_ptr<FileStager> fuseStager = nullptr;
    // attempt to construct fuse stager
    try {
        fuseStager = std::make_shared<FuseStager>(storage.get());
    }
    catch (const FuseStager::StagerBinaryNotAvailable &e) {
        BUILDBOX_LOG_INFO("Fuse stager not available: " << e.what());
    }

    // configure the default stager based on environment variable
    const char *stagerEnv = std::getenv("BUILDBOX_STAGER");
    std::shared_ptr<FileStager> defaultStager = nullptr;
    if (stagerEnv == nullptr) {
        defaultStager = fuseStager == nullptr ? copyStager : fuseStager;
    }
    else if (strcmp(stagerEnv, "fuse") == 0) {
        if (fuseStager == nullptr) {
            BUILDBOXCOMMON_THROW_EXCEPTION(
                std::runtime_error,
                "BUILDBOX_STAGER is set to `fuse`, but the "
                "fuse stager is not available.");
        }
        defaultStager = fuseStager;
    }
    else if (strcmp(stagerEnv, "hardlink") == 0) {
        BUILDBOX_LOG_WARNING("BUILDBOX_STAGER=hardlink has been deprecated. "
                             "Use BUILDBOX_STAGER=copy-or-link instead.");
        defaultStager = copyStager;
    }
    else if (strcmp(stagerEnv, "copy-or-link") == 0) {
        defaultStager = copyStager;
    }
    else {
        BUILDBOXCOMMON_THROW_EXCEPTION(
            std::runtime_error,
            "Unsupported BUILDBOX_STAGER value `"
                << stagerEnv
                << "`. Supported values are `fuse` and `copy-or-link`.");
    }

    fileStagers.emplace(StageTreeRequest_StagingMode_COPY_OR_LINK, copyStager);
    fileStagers.emplace(StageTreeRequest_StagingMode_DEFAULT, defaultStager);
    if (fuseStager != nullptr) {
        fileStagers.emplace(StageTreeRequest_StagingMode_FUSE, fuseStager);
    }
    return fileStagers;
}

void connectionOptionsForRemote(
    const Remote &remote,
    buildboxcommon::ConnectionOptions *connection_options)
{
    connection_options->d_url = remote.url().c_str();
    connection_options->d_instanceName = remote.instance_name().c_str();
    if (!remote.server_cert().empty()) {
        connection_options->d_serverCert = remote.server_cert().c_str();
    }
    if (!remote.client_key().empty()) {
        connection_options->d_clientKey = remote.client_key().c_str();
    }
    if (!remote.client_cert().empty()) {
        connection_options->d_clientCert = remote.client_cert().c_str();
    }
    if (!remote.access_token_path().empty()) {
        connection_options->d_accessTokenPath =
            remote.access_token_path().c_str();
        if (remote.has_access_token_reload_interval()) {
            connection_options->d_tokenReloadInterval = std::to_string(
                google::protobuf::util::TimeUtil::DurationToMinutes(
                    remote.access_token_reload_interval()));
        }
    }
    if (remote.has_keepalive_time()) {
        connection_options->d_keepaliveTime =
            std::to_string(google::protobuf::util::TimeUtil::DurationToSeconds(
                remote.keepalive_time()));
    }
    if (remote.retry_limit() > 0) {
        connection_options->d_retryLimit =
            std::to_string(remote.retry_limit());
    }
    if (remote.has_retry_delay()) {
        connection_options->d_retryDelay = std::to_string(
            google::protobuf::util::TimeUtil::DurationToMilliseconds(
                remote.retry_delay()));
    }
    if (remote.has_request_timeout()) {
        connection_options->d_requestTimeout =
            std::to_string(google::protobuf::util::TimeUtil::DurationToSeconds(
                remote.request_timeout()));
    }
}

class RequestContext final {
  public:
    RequestContext(const std::string &request_name,
                   const grpc::ServerContext *ctx)
    {
        // Log the current request, the peer who initiated the request, and the
        // RequestMetadata if any
        const RequestMetadata clientMetadata =
            RequestMetadataGenerator::parse_request_metadata(
                ctx->client_metadata());
        RequestContextManager::setRequestMetadata(clientMetadata);
        RequestContextManager::setServerContext(ctx);

        std::ostringstream outputString;
        outputString << request_name << " request from [peer=\"" << ctx->peer()
                     << "\"]: [";
        if (!(clientMetadata.tool_details().tool_name()).empty()) {
            outputString << "tool_name=\""
                         << clientMetadata.tool_details().tool_name()
                         << "\", ";
        }
        if (!(clientMetadata.tool_details().tool_version()).empty()) {
            outputString << "tool_version=\""
                         << clientMetadata.tool_details().tool_version()
                         << "\", ";
        }
        if (!(clientMetadata.action_id().empty())) {
            outputString << "action_id=\"" << clientMetadata.action_id()
                         << "\", ";
        }
        if (!(clientMetadata.tool_invocation_id().empty())) {
            outputString << "tool_invocation_id=\""
                         << clientMetadata.tool_invocation_id() << "\", ";
        }
        if (!(clientMetadata.correlated_invocations_id().empty())) {
            outputString << "correlated_invocations_id=\""
                         << clientMetadata.correlated_invocations_id()
                         << "\", ";
        }
        if (!(clientMetadata.action_mnemonic().empty())) {
            outputString << "action_mnemonic=\""
                         << clientMetadata.action_mnemonic() << "\", ";
        }
        if (!(clientMetadata.target_id().empty())) {
            outputString << "target_id=\"" << clientMetadata.target_id()
                         << "\", ";
        }
        if (!(clientMetadata.configuration_id().empty())) {
            outputString << "configuration_id=\""
                         << clientMetadata.configuration_id() << "\", ";
        }
        outputString << "]";
        BUILDBOX_LOG_INFO(outputString.str());
    }
    // Delete copy and move constructors and assignment operators
    RequestContext(const RequestContext &) = delete;
    RequestContext &operator=(const RequestContext &) = delete;
    RequestContext(const RequestContext &&) = delete;
    RequestContext &operator=(RequestContext &&) = delete;

    ~RequestContext() { RequestContextManager::reset(); }
};

grpc::Status validateDigestFunction(DigestFunction_Value requested)
{
    if (requested == DigestFunction_Value_UNKNOWN) {
        return grpc::Status::OK;
    }
    if (requested != DigestGenerator::digestFunction()) {
        return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
                            "Unsupported digest function: " +
                                DigestFunction_Value_Name(requested));
    }
    return grpc::Status::OK;
}

} // namespace

Server::Server(
    const std::shared_ptr<LocalCas> &cas_storage,
    const std::shared_ptr<FsLocalAssetStorage> &asset_storage,
    const std::shared_ptr<ActionStorage> &actioncache_storage,
    const std::shared_ptr<LocalExecutionScheduler> &executionScheduler,
    const std::shared_ptr<ThreadPool> &digestThreadPool,
    const std::shared_ptr<ThreadPool> &ioThreadPool,
    const std::shared_ptr<std::unordered_set<std::string>>
        &allowedPreUnstageCommands)
    : d_cas_storage(cas_storage), d_asset_storage(asset_storage),
      d_actioncache_storage(actioncache_storage),
      d_fileStagers(createFileStagers(cas_storage, ioThreadPool)),
      d_executionScheduler(executionScheduler),
      d_instance_manager(std::make_shared<InstanceManager>()),
      d_digestThreadPool(digestThreadPool), d_ioThreadPool(ioThreadPool),
      d_allowedPreUnstageCommands(allowedPreUnstageCommands),
      d_remote_execution_cas_servicer(
          std::make_shared<CasRemoteExecutionServicer>(d_instance_manager)),
      d_cas_bytestream_servicer(
          std::make_shared<CasBytestreamServicer>(d_instance_manager)),
      d_local_cas_servicer(std::make_shared<LocalCasServicer>(
          this, d_instance_manager, d_cas_storage, d_asset_storage,
          d_actioncache_storage, d_fileStagers, d_executionScheduler,
          d_digestThreadPool, d_ioThreadPool, d_allowedPreUnstageCommands)),
      d_capabilities_servicer(
          std::make_shared<CapabilitiesServicer>(d_instance_manager)),
      d_asset_fetch_servicer(
          std::make_shared<AssetFetchServicer>(d_instance_manager)),
      d_asset_push_servicer(
          std::make_shared<AssetPushServicer>(d_instance_manager)),
      d_actioncache_servicer(
          std::make_shared<ActionCacheServicer>(d_instance_manager)),
      d_execution_servicer(
          std::make_shared<ExecutionServicer>(d_instance_manager)),
      d_operations_servicer(
          std::make_shared<OperationsServicer>(d_instance_manager))
{
}

Server::Server(
    const std::shared_ptr<LocalCas> &casStorage,
    const std::shared_ptr<FsLocalAssetStorage> &assetStorage,
    const std::shared_ptr<ActionStorage> &actioncacheStorage,
    const std::shared_ptr<ThreadPool> &digestThreadPool,
    const std::shared_ptr<ThreadPool> &ioThreadPool,
    const std::shared_ptr<std::unordered_set<std::string>>
        &allowedPreUnstageCommands,
    const buildboxcommon::ConnectionOptions &casEndpoint,
    const std::optional<buildboxcommon::ConnectionOptions> &raEndpoint,
    const std::optional<buildboxcommon::ConnectionOptions> &acEndpoint,
    const std::optional<buildboxcommon::ConnectionOptions> &executionEndpoint,
    const std::vector<std::string> &instanceNames,
    const std::unordered_map<std::string, std::string> &instanceMappings,
    const bool readOnlyRemote, const int proxyFindmissingblobsCacheTtl,
    const int proxyAcValidationCacheTtl)
    : Server(casStorage, assetStorage, actioncacheStorage, nullptr,
             digestThreadPool, ioThreadPool, allowedPreUnstageCommands)
{
    addProxyInstances(instanceNames, instanceMappings, casEndpoint, raEndpoint,
                      acEndpoint, executionEndpoint, readOnlyRemote,
                      proxyFindmissingblobsCacheTtl,
                      proxyAcValidationCacheTtl);
}

Server::Server(
    Server *base,
    const std::unordered_map<std::string, std::string> &instanceCloneMap,
    const std::string &rootPath, bool actionCacheUpdateEnabled,
    const std::shared_ptr<InodeCache> &inodeCache)
    : d_baseServer(base), d_cas_storage(base->d_cas_storage),
      d_actioncache_storage(base->d_actioncache_storage),
      d_executionScheduler(base->d_executionScheduler),
      d_instance_manager(std::make_shared<InstanceManager>()),
      d_digestThreadPool(base->d_digestThreadPool),
      d_ioThreadPool(base->d_ioThreadPool),
      d_remote_execution_cas_servicer(
          std::make_shared<CasRemoteExecutionServicer>(d_instance_manager)),
      d_cas_bytestream_servicer(
          std::make_shared<CasBytestreamServicer>(d_instance_manager)),
      d_local_cas_servicer(std::make_shared<LocalCasServicer>(
          this, d_instance_manager, d_cas_storage, d_asset_storage,
          d_actioncache_storage, d_fileStagers, d_executionScheduler,
          d_digestThreadPool, d_ioThreadPool, d_allowedPreUnstageCommands)),
      d_capabilities_servicer(
          std::make_shared<CapabilitiesServicer>(d_instance_manager)),
      d_actioncache_servicer(
          std::make_shared<ActionCacheServicer>(d_instance_manager)),
      d_execution_servicer(
          std::make_shared<ExecutionServicer>(d_instance_manager)),
      d_operations_servicer(
          std::make_shared<OperationsServicer>(d_instance_manager))
{
    for (const auto &[cloneName, baseName] : instanceCloneMap) {
        auto baseCasInstance =
            base->d_instance_manager->getCasInstance(baseName);

        if (baseCasInstance == nullptr) {
            BUILDBOXCOMMON_THROW_EXCEPTION(std::invalid_argument,
                                           "Invalid instance name\"" +
                                               baseName + "\"");
        }

        auto baseAcInstance =
            base->d_instance_manager->getAcInstance(baseName);
        auto baseExecutionInstance =
            base->d_instance_manager->getExecutionInstance(baseName);

        // Nested REAPI is not useful if only CAS is available
        if (!baseAcInstance && !baseExecutionInstance) {
            GrpcError::throwGrpcError(
                grpc::Status(grpc::FAILED_PRECONDITION,
                             "Neither the execution nor the action cache "
                             "service is enabled for instance \"" +
                                 baseName + "\""));
        }

        std::shared_ptr<CasInstance> casInstance =
            baseCasInstance->clone(cloneName, rootPath);
        const bool actionCacheReadOnly = !actionCacheUpdateEnabled;

        std::shared_ptr<AcInstance> acInstance =
            baseAcInstance
                ? baseAcInstance->clone(casInstance, actionCacheReadOnly)
                : nullptr;
        std::shared_ptr<ExecutionInstance> executionInstance =
            baseExecutionInstance ? baseExecutionInstance->clone(cloneName)
                                  : nullptr;

        auto *localCasInstance =
            dynamic_cast<LocalCasInstance *>(casInstance.get());
        if (localCasInstance) {
            localCasInstance->setInodeCache(inodeCache);
        }

        d_instance_manager->addInstance(cloneName, casInstance, nullptr,
                                        acInstance, executionInstance);
    }
}

void Server::addProxyInstances(
    const std::vector<std::string> &instanceNames,
    const std::unordered_map<std::string, std::string> &instanceMappings,
    const buildboxcommon::ConnectionOptions &casEndpoint,
    const std::optional<buildboxcommon::ConnectionOptions> &raEndpoint,
    const std::optional<buildboxcommon::ConnectionOptions> &acEndpoint,
    const std::optional<buildboxcommon::ConnectionOptions> &executionEndpoint,
    const bool readOnlyRemote, const int proxyFindmissingblobsCacheTtl,
    const int proxyAcValidationCacheTtl,
    std::optional<int> execHybridQueueLimit,
    const bool enableFallbackToLocalExecution)
{
    for (const auto &instanceName : instanceNames) {
        const std::optional<std::string> mappedInstance =
            instanceMappings.find(instanceName) == instanceMappings.end()
                ? std::optional<std::string>()
                : instanceMappings.at(instanceName);
        if (mappedInstance) {
            BUILDBOX_LOG_INFO("Proxying instance from \""
                              << instanceName << "\" to \""
                              << mappedInstance.value() << "\"");
        }

        // Create instanced connection options
        auto casInstancedEndpoint = casEndpoint;
        if (mappedInstance) {
            casInstancedEndpoint.setInstanceName(mappedInstance.value());
        }
        auto raInstancedEndpoint = raEndpoint;
        if (raInstancedEndpoint && mappedInstance) {
            raInstancedEndpoint->setInstanceName(mappedInstance.value());
        }
        auto acInstancedEndpoint = acEndpoint;
        if (acInstancedEndpoint && mappedInstance) {
            acInstancedEndpoint->setInstanceName(mappedInstance.value());
        }
        auto executionInstancedEndpoint = executionEndpoint;
        if (executionInstancedEndpoint && mappedInstance) {
            executionInstancedEndpoint->setInstanceName(
                mappedInstance.value());
        }

        addProxyInstance(
            instanceName, casInstancedEndpoint, raInstancedEndpoint,
            acInstancedEndpoint, executionInstancedEndpoint, readOnlyRemote,
            proxyFindmissingblobsCacheTtl, proxyAcValidationCacheTtl,
            execHybridQueueLimit, enableFallbackToLocalExecution);
    }
}

void Server::addProxyInstance(
    const std::string &instanceName,
    const buildboxcommon::ConnectionOptions &casEndpoint,
    const std::optional<buildboxcommon::ConnectionOptions> &raEndpoint,
    const std::optional<buildboxcommon::ConnectionOptions> &acEndpoint,
    const std::optional<buildboxcommon::ConnectionOptions> &executionEndpoint,
    const bool readOnlyRemote, const int proxyFindmissingblobsCacheTtl,
    const int proxyAcValidationCacheTtl,
    std::optional<int> execHybridQueueLimit,
    const bool enableFallbackToLocalExecution)
{
    auto casInstance = std::make_shared<LocalCasProxyInstance>(
        d_cas_storage, d_fileStagers, d_digestThreadPool, d_ioThreadPool,
        d_allowedPreUnstageCommands, casEndpoint, instanceName, readOnlyRemote,
        proxyFindmissingblobsCacheTtl);

    std::shared_ptr<LocalRaProxyInstance> raInstance;
    if (raEndpoint) {
        raInstance = std::make_shared<LocalRaProxyInstance>(d_asset_storage,
                                                            *raEndpoint);
    }

    std::shared_ptr<AcInstance> acInstance;
    if (acEndpoint) {
        acInstance = std::make_shared<LocalAcProxyInstance>(
            d_actioncache_storage, casInstance, false /* readOnly */,
            *acEndpoint, readOnlyRemote, proxyAcValidationCacheTtl);
    }

    std::shared_ptr<ExecutionInstance> executionInstance;
    if (executionEndpoint) {
        executionInstance = std::make_shared<ExecutionProxyInstance>(
            *executionEndpoint, instanceName, d_executionScheduler,
            instanceName, execHybridQueueLimit,
            enableFallbackToLocalExecution);
    }
    else if (d_executionScheduler) {
        // No remote execution server is available but local execution is
        // configured.
        executionInstance = std::make_shared<LocalExecutionInstance>(
            d_executionScheduler, instanceName);
    }

    d_instance_manager->addInstance(instanceName, casInstance, raInstance,
                                    acInstance, executionInstance);
}

void Server::addListeningPort(const std::string &addr)
{
    if (d_server != nullptr) {
        BUILDBOXCOMMON_THROW_EXCEPTION(
            std::logic_error,
            "Cannot add listening port after starting server");
    }

    d_server_builder.AddListeningPort(addr, grpc::InsecureServerCredentials());
}

void Server::permitKeepaliveTime(int seconds)
{
    if (d_server != nullptr) {
        BUILDBOXCOMMON_THROW_EXCEPTION(
            std::logic_error,
            "Cannot configure keepalive after starting server");
    }

    const int MS_PER_SECOND = 1000;
    d_server_builder.AddChannelArgument(
        GRPC_ARG_HTTP2_MIN_RECV_PING_INTERVAL_WITHOUT_DATA_MS,
        seconds * MS_PER_SECOND);
}

void Server::start()
{
    if (d_server != nullptr) {
        BUILDBOXCOMMON_THROW_EXCEPTION(std::logic_error,
                                       "Cannot start server twice");
    }

    grpc::EnableDefaultHealthCheckService(true);

    d_server_builder.RegisterService(d_remote_execution_cas_servicer.get());
    d_server_builder.RegisterService(d_cas_bytestream_servicer.get());
    d_server_builder.RegisterService(d_local_cas_servicer.get());
    d_server_builder.RegisterService(d_capabilities_servicer.get());
    if (d_asset_fetch_servicer) {
        d_server_builder.RegisterService(d_asset_fetch_servicer.get());
    }
    if (d_asset_push_servicer) {
        d_server_builder.RegisterService(d_asset_push_servicer.get());
    }
    d_server_builder.RegisterService(d_actioncache_servicer.get());
    d_server_builder.RegisterService(d_execution_servicer.get());
    d_server_builder.RegisterService(d_operations_servicer.get());

    d_server = d_server_builder.BuildAndStart();
    if (d_server == nullptr) {
        BUILDBOXCOMMON_THROW_EXCEPTION(std::runtime_error,
                                       "Failed to start server");
    }

    d_server->GetHealthCheckService()->SetServingStatus(true);
}

void Server::shutdown()
{
    d_server->GetHealthCheckService()->Shutdown();
    if (d_executionScheduler && d_baseServer == nullptr) {
        d_executionScheduler->stop();
    }
    d_instance_manager->stopExecutions();
    d_server->Shutdown();
}

void Server::shutdown(gpr_timespec timeout)
{
    d_server->GetHealthCheckService()->Shutdown();
    if (d_executionScheduler && d_baseServer == nullptr) {
        d_executionScheduler->stop();
    }
    d_instance_manager->stopExecutions();
    d_server->Shutdown(timeout);
}

void Server::wait() { d_server->Wait(); }

void Server::addLocalServerInstance(const std::string &instanceName)
{
    auto cas_instance = std::make_shared<LocalCasInstance>(
        d_cas_storage, d_fileStagers, d_digestThreadPool,
        d_allowedPreUnstageCommands, instanceName);

    auto ra_instance = std::make_shared<LocalRaInstance>(d_asset_storage);

    auto ac_instance = std::make_shared<LocalAcInstance>(
        d_actioncache_storage, cas_instance, false /* readOnly */);

    std::shared_ptr<ExecutionInstance> execution_instance;
    if (d_executionScheduler) {
        execution_instance = std::make_shared<LocalExecutionInstance>(
            d_executionScheduler, instanceName);
    }

    d_instance_manager->addInstance(instanceName, cas_instance, ra_instance,
                                    ac_instance, execution_instance);
}

std::shared_ptr<InstanceManager> Server::getInstanceManager()
{
    return this->d_instance_manager;
}

/*
 * Remote Execution API: Content Addressable Storage service
 *
 */
CasRemoteExecutionServicer::CasRemoteExecutionServicer(
    const std::shared_ptr<InstanceManager> &instance_manager)
    : d_instance_manager(instance_manager)
{
}

// NOLINTBEGIN (cppcoreguidelines-macro-usage)
#define CALL_INSTANCE(service, servicer_function, instance_getter,            \
                      instance_name)                                          \
    {                                                                         \
        auto _instance =                                                      \
            this->d_instance_manager->instance_getter(instance_name);         \
        if (_instance != nullptr) {                                           \
            try {                                                             \
                auto r = _instance->servicer_function;                        \
                return r;                                                     \
            }                                                                 \
            catch (const GrpcError &e) {                                      \
                return grpc::Status(e.status.error_code(),                    \
                                    std::string(__func__) + ": " + e.what()); \
            }                                                                 \
            catch (const std::runtime_error &e) {                             \
                return grpc::Status(grpc::StatusCode::INTERNAL,               \
                                    std::string(__func__) + ": " + e.what()); \
            }                                                                 \
        }                                                                     \
        else if (this->d_instance_manager->contains(instance_name)) {         \
            return grpc::Status(                                              \
                grpc::StatusCode::UNIMPLEMENTED,                              \
                service " service is not enabled for instance \"" +           \
                    instance_name + "\"");                                    \
        }                                                                     \
        else {                                                                \
            return INVALID_INSTANCE_NAME_ERROR(request, instance_name)        \
        }                                                                     \
    }

#define CALL_CAS_INSTANCE(servicer_function)                                  \
    CALL_INSTANCE("CAS", servicer_function, getCasInstance,                   \
                  request->instance_name())

#define CALL_RA_INSTANCE(servicer_function)                                   \
    CALL_INSTANCE("Remote Asset", servicer_function, getRaInstance,           \
                  request->instance_name())

#define CALL_AC_INSTANCE(servicer_function)                                   \
    CALL_INSTANCE("Action Cache", servicer_function, getAcInstance,           \
                  request->instance_name())

#define CALL_EXECUTION_INSTANCE(servicer_function, instance_name)             \
    CALL_INSTANCE("Execution", servicer_function, getExecutionInstance,       \
                  instance_name)

#define INVALID_INSTANCE_NAME_ERROR(request, instance_name)                   \
    grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,                          \
                 "Invalid instance name \"" + instance_name + "\"");

// NOLINTEND (cppcoreguidelines-macro-usage)

Status CasRemoteExecutionServicer::FindMissingBlobs(
    ServerContext *ctx, const FindMissingBlobsRequest *request,
    FindMissingBlobsResponse *response)
{
    RequestContext reqCtx("FindMissingBlobs", ctx);

    auto dfStatus = validateDigestFunction(request->digest_function());
    if (!dfStatus.ok()) {
        return dfStatus;
    }

    buildboxcommon::buildboxcommonmetrics::MetricGuard<
        buildboxcommon::buildboxcommonmetrics::DurationMetricTimer>
        mt(MetricNames::TIMER_NAME_CAS_FIND_MISSING_BLOBS);

    for (const auto &digest : request->blob_digests()) {
        buildboxcommon::buildboxcommonmetrics::DistributionMetricUtil::
            recordDistributionMetric(
                MetricNames::DISTRIBUTION_NAME_CAS_FIND_MISSING_BLOBS_SIZES,
                digest.size_bytes());
    }

    CALL_CAS_INSTANCE(FindMissingBlobs(*request, response));
}

Status CasRemoteExecutionServicer::BatchUpdateBlobs(
    ServerContext *ctx, const BatchUpdateBlobsRequest *request,
    BatchUpdateBlobsResponse *response)
{
    RequestContext reqCtx("BatchUpdateBlobs", ctx);

    auto dfStatus = validateDigestFunction(request->digest_function());
    if (!dfStatus.ok()) {
        return dfStatus;
    }

    buildboxcommon::buildboxcommonmetrics::MetricGuard<
        buildboxcommon::buildboxcommonmetrics::DurationMetricTimer>
        mt(MetricNames::TIMER_NAME_CAS_BATCH_UPDATE_BLOBS);

    for (const auto &entry : request->requests()) {
        buildboxcommon::buildboxcommonmetrics::DistributionMetricUtil::
            recordDistributionMetric(
                MetricNames::DISTRIBUTION_NAME_CAS_BATCH_UPDATE_BLOB_SIZES,
                entry.digest().size_bytes());
    }

    CALL_CAS_INSTANCE(BatchUpdateBlobs(*request, response));
}

Status CasRemoteExecutionServicer::BatchReadBlobs(
    ServerContext *ctx, const BatchReadBlobsRequest *request,
    BatchReadBlobsResponse *response)
{
    RequestContext reqCtx("BatchReadBlobs", ctx);

    auto dfStatus = validateDigestFunction(request->digest_function());
    if (!dfStatus.ok()) {
        return dfStatus;
    }

    // Checking that the blobs returned in the response do not exceed the gRPC
    // message size limit.
    // We use the maximum gRPC message size allowed to send/receive minus a
    // somewhat arbitrary value used as an estimate for the space consumed by
    // the digests and status codes embedded in the response:
    static const google::protobuf::int64 response_size_limit =
        GRPC_DEFAULT_MAX_RECV_MESSAGE_LENGTH - (1 << 16);

    google::protobuf::int64 bytes_requested = 0;
    for (const Digest &digest : request->digests()) {
        bytes_requested += digest.size_bytes();

        buildboxcommon::buildboxcommonmetrics::DistributionMetricUtil::
            recordDistributionMetric(
                MetricNames::DISTRIBUTION_NAME_CAS_BATCH_READ_BLOBS_SIZES,
                digest.size_bytes());
    }

    if (bytes_requested > response_size_limit) {
        return grpc::Status(
            grpc::StatusCode::INVALID_ARGUMENT,
            "Sum of bytes requested exceeds the gRPC message size limit: " +
                std::to_string(bytes_requested) + " > " +
                std::to_string(response_size_limit) + " bytes");
    }

    buildboxcommon::buildboxcommonmetrics::MetricGuard<
        buildboxcommon::buildboxcommonmetrics::DurationMetricTimer>
        mt(MetricNames::TIMER_NAME_CAS_BATCH_READ_BLOBS);

    CALL_CAS_INSTANCE(BatchReadBlobs(*request, response));
}

Status
CasRemoteExecutionServicer::GetTree(ServerContext *ctx,
                                    const GetTreeRequest *request,
                                    ServerWriter<GetTreeResponse> *stream)
{
    RequestContext reqCtx("GetTree", ctx);

    auto dfStatus = validateDigestFunction(request->digest_function());
    if (!dfStatus.ok()) {
        return dfStatus;
    }

    buildboxcommon::buildboxcommonmetrics::MetricGuard<
        buildboxcommon::buildboxcommonmetrics::DurationMetricTimer>
        mt(MetricNames::TIMER_NAME_CAS_GET_TREE);

    CALL_CAS_INSTANCE(GetTree(*request, stream));
}

CasBytestreamServicer::CasBytestreamServicer(
    const std::shared_ptr<InstanceManager> &instance_manager)
    : d_instance_manager(instance_manager)
{
}

Status CasBytestreamServicer::Read(ServerContext *ctx,
                                   const ReadRequest *request,
                                   ServerWriter<ReadResponse> *writer)
{
    RequestContext reqCtx("Read", ctx);
    const std::string &resource_name = request->resource_name();
    std::string instance_name = readInstanceName(resource_name);

    auto instance = this->d_instance_manager->getCasInstance(instance_name);
    if (instance == nullptr) {
        return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
                            "Invalid instance name.");
    }

    buildboxcommon::buildboxcommonmetrics::MetricGuard<
        buildboxcommon::buildboxcommonmetrics::DurationMetricTimer>
        mt(MetricNames::TIMER_NAME_CAS_BYTESTREAM_READ);

    Digest requested_digest;
    Status read_status = instance->Read(*request, writer, &requested_digest);

    if (requested_digest != Digest()) {
        buildboxcommon::buildboxcommonmetrics::DistributionMetricUtil::
            recordDistributionMetric(
                MetricNames::DISTRIBUTION_NAME_CAS_BYTESTREAM_READ_BLOB_SIZES,
                requested_digest.size_bytes());
    }

    return read_status;
}

Status CasBytestreamServicer::Write(ServerContext *ctx,
                                    ServerReader<WriteRequest> *request,
                                    WriteResponse *response)
{
    RequestContext reqCtx("Write", ctx);
    WriteRequest request_message;

    if (!request->Read(&request_message)) {
        return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
                            "No request received.");
    }

    const std::string &resource_name = request_message.resource_name();
    std::string instance_name = writeInstanceName(resource_name);

    auto instance = this->d_instance_manager->getCasInstance(instance_name);
    if (instance == nullptr) {
        return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
                            "Invalid instance name.");
    }

    buildboxcommon::buildboxcommonmetrics::MetricGuard<
        buildboxcommon::buildboxcommonmetrics::DurationMetricTimer>
        mt(MetricNames::TIMER_NAME_CAS_BYTESTREAM_WRITE);

    Digest requested_digest;
    Status writeStatus = instance->Write(&request_message, *request, response,
                                         &requested_digest);

    if (requested_digest != Digest()) {
        buildboxcommon::buildboxcommonmetrics::DistributionMetricUtil::
            recordDistributionMetric(
                MetricNames::DISTRIBUTION_NAME_CAS_BYTESTREAM_WRITE_BLOB_SIZES,
                requested_digest.size_bytes());
    }

    return writeStatus;
}

std::string
CasBytestreamServicer::readInstanceName(const std::string &resource_name) const
{
    return instanceNameFromResourceName(resource_name, "blobs");
}

std::string CasBytestreamServicer::writeInstanceName(
    const std::string &resource_name) const
{
    return instanceNameFromResourceName(resource_name, "uploads");
}

std::string CasBytestreamServicer::instanceNameFromResourceName(
    const std::string &resource_name, const std::string &expected_root) const
{

    // "expected_root/..."
    if (resource_name.substr(0, expected_root.size()) == expected_root) {
        return ""; // No instance name specified.
    }

    // "{instance_name}/expected_root/..."
    const auto instance_name_end = resource_name.find("/" + expected_root);
    // (According to the RE specification: "[...] the `instance_name`
    // is an identifier, possibly containing multiple path segments [...]".)
    return resource_name.substr(0, instance_name_end);
}

CapabilitiesServicer::CapabilitiesServicer(
    const std::shared_ptr<InstanceManager> &instance_manager)
    : d_instance_manager(instance_manager)
{
    CacheCapabilities *const cache_capabilities =
        d_server_capabilities.mutable_cache_capabilities();

    cache_capabilities->add_digest_functions(
        DigestGenerator::digestFunction());

    cache_capabilities->set_symlink_absolute_path_strategy(
        SymlinkAbsolutePathStrategy_Value_ALLOWED);

    // Since we are running locally, we do not limit the size of
    // batches:
    cache_capabilities->set_max_batch_total_size_bytes(0);

    ActionCacheUpdateCapabilities *const action_cache_update_capabilities =
        cache_capabilities->mutable_action_cache_update_capabilities();
    action_cache_update_capabilities->set_update_enabled(true);

    constexpr int HIGH_API_MAJOR_VERSION = 2;
    constexpr int HIGH_API_MINOR_VERSION = 12;
    constexpr int LOW_API_MAJOR_VERSION = 2;
    constexpr int LOW_API_MINOR_VERSION = 1;

    build::bazel::semver::SemVer *const high_api =
        d_server_capabilities.mutable_high_api_version();
    build::bazel::semver::SemVer *const low_api =
        d_server_capabilities.mutable_low_api_version();
    high_api->set_major(HIGH_API_MAJOR_VERSION);
    high_api->set_minor(HIGH_API_MINOR_VERSION);
    low_api->set_major(LOW_API_MAJOR_VERSION);
    low_api->set_minor(LOW_API_MINOR_VERSION);
}

Status
CapabilitiesServicer::GetCapabilities(grpc::ServerContext *ctx,
                                      const GetCapabilitiesRequest *request,
                                      ServerCapabilities *response)
{
    RequestContext reqCtx("GetCapabilities", ctx);
    buildboxcommon::buildboxcommonmetrics::MetricGuard<
        buildboxcommon::buildboxcommonmetrics::DurationMetricTimer>
        mt(MetricNames::TIMER_NAME_CAS_GET_CAPABILITIES);

    if (this->d_instance_manager->getCasInstance(request->instance_name()) !=
        nullptr) {
        response->CopyFrom(d_server_capabilities);
        if (this->d_instance_manager->getExecutionInstance(
                request->instance_name()) != nullptr) {
            ExecutionCapabilities *const exec_capabilities =
                response->mutable_execution_capabilities();
            exec_capabilities->set_exec_enabled(true);
            exec_capabilities->add_digest_functions(
                DigestGenerator::digestFunction());
            exec_capabilities->set_digest_function(
                DigestGenerator::digestFunction());
        }
        return grpc::Status::OK;
    }

    return INVALID_INSTANCE_NAME_ERROR(request, request->instance_name());
}

ActionCacheServicer::ActionCacheServicer(
    const std::shared_ptr<InstanceManager> &instance_manager)
    : d_instance_manager(instance_manager)
{
}

Status
ActionCacheServicer::GetActionResult(ServerContext *ctx,
                                     const GetActionResultRequest *request,
                                     ActionResult *result)
{
    RequestContext reqCtx("GetActionResult", ctx);

    auto dfStatus = validateDigestFunction(request->digest_function());
    if (!dfStatus.ok()) {
        return dfStatus;
    }

    CALL_AC_INSTANCE(GetActionResult(*request, result));
}

Status ActionCacheServicer::UpdateActionResult(
    ServerContext *ctx, const UpdateActionResultRequest *request,
    ActionResult *result)
{
    RequestContext reqCtx("UpdateActionResult", ctx);

    auto dfStatus = validateDigestFunction(request->digest_function());
    if (!dfStatus.ok()) {
        return dfStatus;
    }

    CALL_AC_INSTANCE(UpdateActionResult(*request, result));
}

LocalCasServicer::LocalCasServicer(
    Server *server, const std::shared_ptr<InstanceManager> &instance_manager,
    const std::shared_ptr<LocalCas> &storage,
    const std::shared_ptr<FsLocalAssetStorage> &asset_storage,
    const std::shared_ptr<ActionStorage> &actioncache_storage,
    std::unordered_map<buildboxcommon::StageTreeRequest::StagingMode,
                       std::shared_ptr<buildboxcommon::FileStager>>
        fileStagers,
    const std::shared_ptr<LocalExecutionScheduler> &executionScheduler,
    const std::shared_ptr<ThreadPool> &digestThreadPool,
    const std::shared_ptr<ThreadPool> &ioThreadPool,
    const std::shared_ptr<std::unordered_set<std::string>>
        &allowedPreUnstageCommands)
    : d_server(server), d_instance_manager(instance_manager),
      d_storage(storage), d_asset_storage(asset_storage),
      d_actioncache_storage(actioncache_storage),
      d_fileStagers(std::move(fileStagers)),
      d_executionScheduler(executionScheduler),
      d_digestThreadPool(digestThreadPool), d_ioThreadPool(ioThreadPool),
      d_allowedPreUnstageCommands(allowedPreUnstageCommands)
{
}

Status LocalCasServicer::GetLocalServerDetails(
    ServerContext *ctx, const GetLocalServerDetailsRequest *request,
    LocalServerDetails *response)
{
    RequestContext reqCtx("GetLocalServerDetails", ctx);

    buildboxcommon::buildboxcommonmetrics::MetricGuard<
        buildboxcommon::buildboxcommonmetrics::DurationMetricTimer>
        mt(MetricNames::TIMER_NAME_LOCAL_CAS_GET_SERVER_DETAILS);

    CALL_CAS_INSTANCE(GetLocalServerDetails(*request, response));
}

Status
LocalCasServicer::FetchMissingBlobs(ServerContext *ctx,
                                    const FetchMissingBlobsRequest *request,
                                    FetchMissingBlobsResponse *response)
{
    RequestContext reqCtx("FetchMissingBlobs", ctx);

    buildboxcommon::buildboxcommonmetrics::MetricGuard<
        buildboxcommon::buildboxcommonmetrics::DurationMetricTimer>
        mt(MetricNames::TIMER_NAME_LOCAL_CAS_FETCH_MISSING_BLOB);

    for (const auto &digest : request->blob_digests()) {
        buildboxcommon::buildboxcommonmetrics::DistributionMetricUtil::
            recordDistributionMetric(
                MetricNames::DISTRIBUTION_NAME_CAS_FETCH_MISSING_BLOBS_SIZES,
                digest.size_bytes());
    }

    CALL_CAS_INSTANCE(FetchMissingBlobs(*request, response));
}

Status
LocalCasServicer::UploadMissingBlobs(ServerContext *ctx,
                                     const UploadMissingBlobsRequest *request,
                                     UploadMissingBlobsResponse *response)
{
    RequestContext reqCtx("UploadMissingBlobs", ctx);
    buildboxcommon::buildboxcommonmetrics::MetricGuard<
        buildboxcommon::buildboxcommonmetrics::DurationMetricTimer>
        mt(MetricNames::TIMER_NAME_LOCAL_CAS_UPLOAD_MISSING_BLOB);

    for (const auto &digest : request->blob_digests()) {
        buildboxcommon::buildboxcommonmetrics::DistributionMetricUtil::
            recordDistributionMetric(
                MetricNames::DISTRIBUTION_NAME_CAS_UPLOAD_MISSING_BLOBS_SIZES,
                digest.size_bytes());
    }

    CALL_CAS_INSTANCE(UploadMissingBlobs(*request, response));
}

Status LocalCasServicer::FetchTree(ServerContext *ctx,
                                   const FetchTreeRequest *request,
                                   FetchTreeResponse *response)
{
    RequestContext reqCtx("FetchTree", ctx);
    buildboxcommon::buildboxcommonmetrics::MetricGuard<
        buildboxcommon::buildboxcommonmetrics::DurationMetricTimer>
        mt(MetricNames::TIMER_NAME_LOCAL_CAS_FETCH_TREE);

    CALL_CAS_INSTANCE(FetchTree(*request, response));
}

Status LocalCasServicer::UploadTree(ServerContext *ctx,
                                    const UploadTreeRequest *request,
                                    UploadTreeResponse *response)
{
    RequestContext reqCtx("UploadTree", ctx);
    buildboxcommon::buildboxcommonmetrics::MetricGuard<
        buildboxcommon::buildboxcommonmetrics::DurationMetricTimer>
        mt(MetricNames::TIMER_NAME_LOCAL_CAS_UPLOAD_TREE);

    CALL_CAS_INSTANCE(UploadTree(*request, response));
}

Status LocalCasServicer::StageTree(
    ServerContext *ctx,
    ServerReaderWriter<StageTreeResponse, StageTreeRequest> *stream)
{
    RequestContext reqCtx("StageTree", ctx);
    StageTreeRequest stage_request;
    if (!stream->Read(&stage_request)) {
        return Status(grpc::StatusCode::INTERNAL, "Could not read request.");
    }

    StageTreeRequest *request = &stage_request;

    buildboxcommon::buildboxcommonmetrics::MetricGuard<
        buildboxcommon::buildboxcommonmetrics::DurationMetricTimer>
        mt(MetricNames::TIMER_NAME_LOCAL_CAS_STAGE_TREE_TOTAL);

    CALL_CAS_INSTANCE(StageTree(stage_request, stream, d_server));
}

Status LocalCasServicer::NestedServer(
    ServerContext *ctx,
    ServerReaderWriter<NestedServerResponse, NestedServerRequest> *stream)
{
    RequestContext reqCtx("NestedServer", ctx);
    NestedServerRequest nestedServerRequest;
    if (!stream->Read(&nestedServerRequest)) {
        return Status(grpc::StatusCode::INTERNAL, "Could not read request.");
    }

    NestedServerRequest *request = &nestedServerRequest;

    CALL_CAS_INSTANCE(NestedServer(nestedServerRequest, stream, d_server));
}

Status LocalCasServicer::ExportFiles(ServerContext *ctx,
                                     const ExportFilesRequest *request,
                                     ExportFilesResponse *response)
{
    RequestContext reqCtx("ExportFiles", ctx);
    buildboxcommon::buildboxcommonmetrics::MetricGuard<
        buildboxcommon::buildboxcommonmetrics::DurationMetricTimer>
        mt(MetricNames::TIMER_NAME_LOCAL_CAS_EXPORT_FILES);

    CALL_CAS_INSTANCE(ExportFiles(*request, response));
}

Status LocalCasServicer::CaptureTree(ServerContext *ctx,
                                     const CaptureTreeRequest *request,
                                     CaptureTreeResponse *response)
{
    RequestContext reqCtx("CaptureTree", ctx);
    buildboxcommon::buildboxcommonmetrics::MetricGuard<
        buildboxcommon::buildboxcommonmetrics::DurationMetricTimer>
        mt(MetricNames::TIMER_NAME_LOCAL_CAS_CAPTURE_TREE);

    CALL_CAS_INSTANCE(CaptureTree(*request, response));
}

Status LocalCasServicer::CaptureFiles(ServerContext *ctx,
                                      const CaptureFilesRequest *request,
                                      CaptureFilesResponse *response)
{
    RequestContext reqCtx("CaptureFiles", ctx);
    buildboxcommon::buildboxcommonmetrics::MetricGuard<
        buildboxcommon::buildboxcommonmetrics::DurationMetricTimer>
        mt(MetricNames::TIMER_NAME_LOCAL_CAS_CAPTURE_FILES);

    CALL_CAS_INSTANCE(CaptureFiles(*request, response));
}

Status LocalCasServicer::HashFiles(ServerContext *ctx,
                                   const HashFilesRequest *request,
                                   HashFilesResponse *response)
{
    RequestContext reqCtx("HashFiles", ctx);
    CALL_CAS_INSTANCE(HashFiles(*request, response));
}

Status LocalCasServicer::GetInstanceNameForRemote(
    ServerContext *ctx, const GetInstanceNameForRemoteRequest *request,
    GetInstanceNameForRemoteResponse *response)
{
    RequestContext reqCtx("GetInstanceNameForRemote", ctx);
    GetInstanceNameForRemotesRequest new_request;
    GetInstanceNameForRemotesResponse new_response;

    // Transform the deprecated GetInstanceNameForRemoteRequest message into
    // the new GetInstanceNameForRemotesRequest message.
    const std::string request_str = request->SerializeAsString();
    new_request.mutable_content_addressable_storage()->ParseFromString(
        request_str);

    auto status = GetInstanceNameForRemotes(ctx, &new_request, &new_response);

    response->set_instance_name(new_response.instance_name());

    return status;
}

Status LocalCasServicer::GetInstanceNameForRemotes(
    ServerContext *ctx, const GetInstanceNameForRemotesRequest *request,
    GetInstanceNameForRemotesResponse *response)
{
    RequestContext reqCtx("GetInstanceNameForRemotes", ctx);
    buildboxcommon::buildboxcommonmetrics::MetricGuard<
        buildboxcommon::buildboxcommonmetrics::DurationMetricTimer>
        mt(MetricNames::TIMER_NAME_LOCAL_CAS_GET_INSTANCE_FROM_REMOTE);

    /*
     * In regular operation there is always at least one instance, however,
     * during test setup, it may be empty.
     */
    if (!this->d_instance_manager->empty()) {
        auto base_instance =
            this->d_instance_manager->getCasInstance(request->instance_name());
        if (base_instance == nullptr) {
            return INVALID_INSTANCE_NAME_ERROR(request,
                                               request->instance_name());
        }

        if (base_instance->chrooted()) {
            return grpc::Status(grpc::StatusCode::PERMISSION_DENIED,
                                "Not allowed for namespaced instances");
        }
    }

    /* Use hash of request message as instance name */
    const std::string request_str = request->SerializeAsString();
    const Digest request_digest = DigestGenerator::hash(request_str);
    const std::string &instance_name = request_digest.hash();

    if (!d_instance_manager->contains(instance_name)) {
        /* First request for this remote, instantiate client. */

        std::shared_ptr<LocalCasProxyInstance> cas_instance;
        if (request->has_content_addressable_storage()) {
            buildboxcommon::ConnectionOptions cas_endpoint;
            connectionOptionsForRemote(request->content_addressable_storage(),
                                       &cas_endpoint);

            try {
                cas_instance = std::make_shared<LocalCasProxyInstance>(
                    d_storage, d_fileStagers, d_digestThreadPool,
                    d_ioThreadPool, d_allowedPreUnstageCommands, cas_endpoint,
                    instance_name,
                    request->content_addressable_storage().read_only());
            }
            catch (const GrpcError &e) {
                /* Failed to initialize connection to server. */
                BUILDBOX_LOG_WARNING("Failed to connect to CAS server "
                                     << cas_endpoint.d_url << ": "
                                     << e.what());
                return e.status;
            }
            catch (const std::runtime_error &e) {
                BUILDBOX_LOG_WARNING("Failed to connect to CAS server "
                                     << cas_endpoint.d_url << ": "
                                     << e.what());
                return grpc::Status(grpc::StatusCode::INTERNAL,
                                    std::string(__func__) + ": " + e.what());
            }
        }

        std::shared_ptr<LocalRaProxyInstance> ra_instance;
        if (request->has_remote_asset()) {
            buildboxcommon::ConnectionOptions ra_endpoint;
            connectionOptionsForRemote(request->remote_asset(), &ra_endpoint);

            try {
                ra_instance = std::make_shared<LocalRaProxyInstance>(
                    nullptr, ra_endpoint);
            }
            catch (const GrpcError &e) {
                /* Failed to initialize connection to server. */
                BUILDBOX_LOG_WARNING("Failed to connect to RA server "
                                     << ra_endpoint.d_url << ": " << e.what());
                return e.status;
            }
            catch (const std::runtime_error &e) {
                BUILDBOX_LOG_WARNING("Failed to connect to RA server "
                                     << ra_endpoint.d_url << ": " << e.what());
                return grpc::Status(grpc::StatusCode::INTERNAL,
                                    std::string(__func__) + ": " + e.what());
            }
        }

        std::shared_ptr<LocalAcProxyInstance> ac_instance;
        if (request->has_action_cache()) {
            buildboxcommon::ConnectionOptions ac_endpoint;
            connectionOptionsForRemote(request->action_cache(), &ac_endpoint);

            try {
                ac_instance = std::make_shared<LocalAcProxyInstance>(
                    d_actioncache_storage, cas_instance, false /* readOnly */,
                    ac_endpoint, request->action_cache().read_only());
            }
            catch (const GrpcError &e) {
                /* Failed to initialize connection to server. */
                BUILDBOX_LOG_WARNING("Failed to connect to AC server "
                                     << ac_endpoint.d_url << ": " << e.what());
                return e.status;
            }
            catch (const std::runtime_error &e) {
                BUILDBOX_LOG_WARNING("Failed to connect to AC server "
                                     << ac_endpoint.d_url << ": " << e.what());
                return grpc::Status(grpc::StatusCode::INTERNAL,
                                    std::string(__func__) + ": " + e.what());
            }
        }

        std::shared_ptr<ExecutionInstance> exec_instance;
        if (request->has_execution()) {
            buildboxcommon::ConnectionOptions exec_endpoint;
            connectionOptionsForRemote(request->execution(), &exec_endpoint);

            try {
                exec_instance = std::make_shared<ExecutionProxyInstance>(
                    exec_endpoint, instance_name);
            }
            catch (const GrpcError &e) {
                /* Failed to initialize connection to server. */
                BUILDBOX_LOG_WARNING("Failed to connect to execution server "
                                     << exec_endpoint.d_url << ": "
                                     << e.what());
                return e.status;
            }
            catch (const std::runtime_error &e) {
                BUILDBOX_LOG_WARNING("Failed to connect to execution server "
                                     << exec_endpoint.d_url << ": "
                                     << e.what());
                return grpc::Status(grpc::StatusCode::INTERNAL,
                                    std::string(__func__) + ": " + e.what());
            }
        }
        else if (d_executionScheduler) {
            // No remote execution server is available but local execution is
            // configured.
            exec_instance = std::make_shared<LocalExecutionInstance>(
                d_executionScheduler, instance_name);
        }

        d_instance_manager->addInstance(instance_name, cas_instance,
                                        ra_instance, ac_instance,
                                        exec_instance);
    }

    response->set_instance_name(instance_name);

    return grpc::Status::OK;
}

Status LocalCasServicer::GetInstanceNameForNamespace(
    ServerContext *ctx, const GetInstanceNameForNamespaceRequest *request,
    GetInstanceNameForNamespaceResponse *response)
{
    RequestContext reqCtx("GetInstanceNameForNamespace", ctx);
    if (this->d_instance_manager->getCasInstance("") != nullptr) {
        // Deny request if there is a default instance as that would allow
        // clients to bypass the sandboxing restriction.
        return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION,
                            "Namespacing not supported with default instance");
    }

    auto base_instance =
        this->d_instance_manager->getCasInstance(request->instance_name());
    if (base_instance == nullptr) {
        return INVALID_INSTANCE_NAME_ERROR(request, request->instance_name());
    }

    if (request->root().empty()) {
        return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
                            "Invalid root path");
    }

    // Use a random UUID as new instance name
    std::string instance_name = StringUtils::getUUIDString();

    std::shared_ptr<CasInstance> cas_instance;
    try {
        // Restrict cloned instance to specified root
        cas_instance = base_instance->clone(instance_name, request->root());
    }
    catch (const std::runtime_error &e) {
        return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION,
                            std::string(__func__) + ": " + e.what());
    }

    d_instance_manager->addInstance(instance_name, cas_instance, nullptr,
                                    nullptr, nullptr);

    response->set_instance_name(instance_name);

    return grpc::Status::OK;
}

Status LocalCasServicer::GetLocalDiskUsage(ServerContext *ctx,
                                           const GetLocalDiskUsageRequest *,
                                           GetLocalDiskUsageResponse *response)
{
    RequestContext reqCtx("GetLocalDiskUsage", ctx);
    buildboxcommon::buildboxcommonmetrics::MetricGuard<
        buildboxcommon::buildboxcommonmetrics::DurationMetricTimer>
        mt(MetricNames::TIMER_NAME_LOCAL_CAS_GET_LOCAL_DISK_USAGE);

    response->set_size_bytes(d_storage->getDiskUsage());
    // using 0 to represent no quota
    response->set_quota_bytes(d_storage->getDiskQuota().value_or(0L));

    return grpc::Status::OK;
}

AssetFetchServicer::AssetFetchServicer(
    const std::shared_ptr<InstanceManager> &instance_manager)
    : d_instance_manager(instance_manager)
{
}

Status AssetFetchServicer::FetchBlob(ServerContext *ctx,
                                     const FetchBlobRequest *request,
                                     FetchBlobResponse *response)
{
    RequestContext reqCtx("FetchBlob", ctx);
    CALL_RA_INSTANCE(FetchBlob(*request, response));
}

Status AssetFetchServicer::FetchDirectory(ServerContext *ctx,
                                          const FetchDirectoryRequest *request,
                                          FetchDirectoryResponse *response)
{
    RequestContext reqCtx("FetchDirectory", ctx);
    CALL_RA_INSTANCE(FetchDirectory(*request, response));
}

AssetPushServicer::AssetPushServicer(
    const std::shared_ptr<InstanceManager> &instance_manager)
    : d_instance_manager(instance_manager)
{
}

Status AssetPushServicer::PushBlob(ServerContext *ctx,
                                   const PushBlobRequest *request,
                                   PushBlobResponse *response)
{
    RequestContext reqCtx("PushBlob", ctx);
    CALL_RA_INSTANCE(PushBlob(*request, response));
}

Status AssetPushServicer::PushDirectory(ServerContext *ctx,
                                        const PushDirectoryRequest *request,
                                        PushDirectoryResponse *response)
{
    RequestContext reqCtx("PushDirectory", ctx);
    CALL_RA_INSTANCE(PushDirectory(*request, response));
}

ExecutionServicer::ExecutionServicer(
    const std::shared_ptr<InstanceManager> &instance_manager)
    : d_instance_manager(instance_manager)
{
}

Status ExecutionServicer::Execute(ServerContext *ctx,
                                  const ExecuteRequest *request,
                                  ServerWriter<Operation> *writer)
{
    RequestContext reqCtx("Execute", ctx);

    auto dfStatus = validateDigestFunction(request->digest_function());
    if (!dfStatus.ok()) {
        return dfStatus;
    }

    CALL_EXECUTION_INSTANCE(Execute(ctx, *request, writer),
                            request->instance_name());
}

Status ExecutionServicer::WaitExecution(ServerContext *ctx,
                                        const WaitExecutionRequest *request,
                                        ServerWriter<Operation> *writer)
{
    RequestContext reqCtx("WaitExecution", ctx);
    size_t idx = request->name().find_first_of('#');
    std::string instance_name = "";
    if (idx != std::string::npos) {
        instance_name = request->name().substr(0, idx);
    }
    CALL_EXECUTION_INSTANCE(WaitExecution(ctx, *request, writer),
                            instance_name);
}

OperationsServicer::OperationsServicer(
    const std::shared_ptr<InstanceManager> &instance_manager)
    : d_instance_manager(instance_manager)
{
}

Status OperationsServicer::GetOperation(ServerContext *ctx,
                                        const GetOperationRequest *request,
                                        Operation *response)
{
    RequestContext reqCtx("GetOperation", ctx);
    size_t idx = request->name().find_first_of('#');
    std::string instance_name = "";
    if (idx != std::string::npos) {
        instance_name = request->name().substr(0, idx);
    }
    CALL_EXECUTION_INSTANCE(GetOperation(*request, response), instance_name);
}

Status OperationsServicer::ListOperations(ServerContext *ctx,
                                          const ListOperationsRequest *request,
                                          ListOperationsResponse *response)
{
    RequestContext reqCtx("ListOperations", ctx);
    size_t idx = request->name().find_first_of('#');
    std::string instance_name = "";
    if (idx != std::string::npos) {
        instance_name = request->name().substr(0, idx);
    }
    CALL_EXECUTION_INSTANCE(ListOperations(*request, response), instance_name);
}

Status
OperationsServicer::CancelOperation(ServerContext *ctx,
                                    const CancelOperationRequest *request,
                                    google::protobuf::Empty *response)
{
    RequestContext reqCtx("CancelOperation", ctx);
    size_t idx = request->name().find_first_of('#');
    std::string instance_name = "";
    if (idx != std::string::npos) {
        instance_name = request->name().substr(0, idx);
    }
    CALL_EXECUTION_INSTANCE(CancelOperation(*request, response),
                            instance_name);
}

} // namespace buildboxcasd
