blob: e132b204be9956e74e2e5f9c66047195bd00d15f [file] [log] [blame]
// Copyright 2019 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 "diagnostics/wilco_dtc_supportd/routine_service.h"
#include <iterator>
#include <string>
#include <utility>
#include <vector>
#include "diagnostics/common/mojo_utils.h"
#include "mojo/cros_healthd.mojom.h"
#include "mojo/cros_healthd_diagnostics.mojom.h"
#include "mojo/nullable_primitives.mojom.h"
namespace diagnostics {
namespace mojo_ipc = ::chromeos::cros_healthd::mojom;
namespace {
// Converts from mojo's DiagnosticRoutineStatusEnum to gRPC's
// DiagnosticRoutineStatus.
bool GetGrpcStatusFromMojoStatus(
chromeos::cros_healthd::mojom::DiagnosticRoutineStatusEnum mojo_status,
grpc_api::DiagnosticRoutineStatus* grpc_status_out) {
DCHECK(grpc_status_out);
switch (mojo_status) {
case chromeos::cros_healthd::mojom::DiagnosticRoutineStatusEnum::kReady:
*grpc_status_out = grpc_api::ROUTINE_STATUS_READY;
return true;
case chromeos::cros_healthd::mojom::DiagnosticRoutineStatusEnum::kRunning:
*grpc_status_out = grpc_api::ROUTINE_STATUS_RUNNING;
return true;
case chromeos::cros_healthd::mojom::DiagnosticRoutineStatusEnum::kWaiting:
*grpc_status_out = grpc_api::ROUTINE_STATUS_WAITING;
return true;
case chromeos::cros_healthd::mojom::DiagnosticRoutineStatusEnum::kPassed:
*grpc_status_out = grpc_api::ROUTINE_STATUS_PASSED;
return true;
case chromeos::cros_healthd::mojom::DiagnosticRoutineStatusEnum::kFailed:
*grpc_status_out = grpc_api::ROUTINE_STATUS_FAILED;
return true;
case chromeos::cros_healthd::mojom::DiagnosticRoutineStatusEnum::kError:
*grpc_status_out = grpc_api::ROUTINE_STATUS_ERROR;
return true;
case chromeos::cros_healthd::mojom::DiagnosticRoutineStatusEnum::kCancelled:
*grpc_status_out = grpc_api::ROUTINE_STATUS_CANCELLED;
return true;
case chromeos::cros_healthd::mojom::DiagnosticRoutineStatusEnum::
kFailedToStart:
*grpc_status_out = grpc_api::ROUTINE_STATUS_FAILED_TO_START;
return true;
case chromeos::cros_healthd::mojom::DiagnosticRoutineStatusEnum::kRemoved:
*grpc_status_out = grpc_api::ROUTINE_STATUS_REMOVED;
return true;
case chromeos::cros_healthd::mojom::DiagnosticRoutineStatusEnum::
kCancelling:
*grpc_status_out = grpc_api::ROUTINE_STATUS_CANCELLING;
return true;
case chromeos::cros_healthd::mojom::DiagnosticRoutineStatusEnum::
kUnsupported:
*grpc_status_out = grpc_api::ROUTINE_STATUS_ERROR;
return true;
case chromeos::cros_healthd::mojom::DiagnosticRoutineStatusEnum::kNotRun:
*grpc_status_out = grpc_api::ROUTINE_STATUS_FAILED_TO_START;
return true;
}
LOG(ERROR) << "Unknown mojo routine status: "
<< static_cast<int>(mojo_status);
return false;
}
// Converts from mojo's DiagnosticRoutineUserMessageEnum to gRPC's
// DiagnosticRoutineUserMessage.
bool GetUserMessageFromMojoEnum(
chromeos::cros_healthd::mojom::DiagnosticRoutineUserMessageEnum
mojo_message,
grpc_api::DiagnosticRoutineUserMessage* grpc_message_out) {
DCHECK(grpc_message_out);
switch (mojo_message) {
case chromeos::cros_healthd::mojom::DiagnosticRoutineUserMessageEnum::
kUnplugACPower:
*grpc_message_out = grpc_api::ROUTINE_USER_MESSAGE_UNPLUG_AC_POWER;
return true;
default:
LOG(ERROR) << "Unknown mojo user message: "
<< static_cast<int>(mojo_message);
return false;
}
}
// Converts from mojo's DiagnosticRoutineEnum to gRPC's DiagnosticRoutine.
bool GetGrpcRoutineEnumFromMojoRoutineEnum(
chromeos::cros_healthd::mojom::DiagnosticRoutineEnum mojo_enum,
std::vector<grpc_api::DiagnosticRoutine>* grpc_enum_out) {
DCHECK(grpc_enum_out);
switch (mojo_enum) {
case chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kBatteryCapacity:
grpc_enum_out->push_back(grpc_api::ROUTINE_BATTERY);
return true;
case chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kBatteryHealth:
grpc_enum_out->push_back(grpc_api::ROUTINE_BATTERY_SYSFS);
return true;
case chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kUrandom:
grpc_enum_out->push_back(grpc_api::ROUTINE_URANDOM);
return true;
case chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kSmartctlCheck:
grpc_enum_out->push_back(grpc_api::ROUTINE_SMARTCTL_CHECK);
return true;
case chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kCpuCache:
grpc_enum_out->push_back(grpc_api::ROUTINE_CPU_CACHE);
return true;
case chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kCpuStress:
grpc_enum_out->push_back(grpc_api::ROUTINE_CPU_STRESS);
return true;
case chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::
kFloatingPointAccuracy:
grpc_enum_out->push_back(grpc_api::ROUTINE_FLOATING_POINT_ACCURACY);
return true;
case chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kNvmeWearLevel:
grpc_enum_out->push_back(grpc_api::ROUTINE_NVME_WEAR_LEVEL);
return true;
// There is only one mojo enum for self_test(short & extended share same
// class), but there're 2 gRPC enum for self_test according to requirement.
case chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kNvmeSelfTest:
grpc_enum_out->push_back(grpc_api::ROUTINE_NVME_SHORT_SELF_TEST);
grpc_enum_out->push_back(grpc_api::ROUTINE_NVME_LONG_SELF_TEST);
return true;
case chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kDiskRead:
grpc_enum_out->push_back(grpc_api::ROUTINE_DISK_LINEAR_READ);
grpc_enum_out->push_back(grpc_api::ROUTINE_DISK_RANDOM_READ);
return true;
case chromeos::cros_healthd::mojom::DiagnosticRoutineEnum::kPrimeSearch:
grpc_enum_out->push_back(grpc_api::ROUTINE_PRIME_SEARCH);
return true;
default:
LOG(ERROR) << "Unknown mojo routine: " << static_cast<int>(mojo_enum);
return false;
}
}
// Converts from mojo's RoutineUpdate to gRPC's GetRoutineUpdateResponse.
void SetGrpcUpdateFromMojoUpdate(
chromeos::cros_healthd::mojom::RoutineUpdatePtr mojo_update,
grpc_api::GetRoutineUpdateResponse* grpc_update) {
DCHECK(grpc_update);
grpc_update->set_progress_percent(mojo_update->progress_percent);
const auto& update_union = mojo_update->routine_update_union;
if (update_union->is_interactive_update()) {
grpc_api::DiagnosticRoutineUserMessage grpc_message;
chromeos::cros_healthd::mojom::DiagnosticRoutineUserMessageEnum
mojo_message = update_union->get_interactive_update()->user_message;
if (!GetUserMessageFromMojoEnum(mojo_message, &grpc_message)) {
grpc_update->set_status(grpc_api::ROUTINE_STATUS_ERROR);
} else {
grpc_update->set_user_message(grpc_message);
}
} else {
grpc_update->set_status_message(
update_union->get_noninteractive_update()->status_message);
grpc_api::DiagnosticRoutineStatus grpc_status;
auto mojo_status = update_union->get_noninteractive_update()->status;
if (!GetGrpcStatusFromMojoStatus(mojo_status, &grpc_status)) {
grpc_update->set_status(grpc_api::ROUTINE_STATUS_ERROR);
} else {
grpc_update->set_status(grpc_status);
}
}
if (!mojo_update->output.is_valid()) {
// This isn't necessarily an error, since some requests may not have
// specified that they wanted output returned, and some routines may never
// return any extra input. We'll log the event in the case that it was an
// error.
VLOG(1) << "No output in mojo update.";
return;
}
auto shm_mapping = GetReadOnlySharedMemoryMappingFromMojoHandle(
std::move(mojo_update->output));
if (!shm_mapping.IsValid()) {
PLOG(ERROR) << "Failed to read data from mojo handle";
return;
}
grpc_update->set_output(std::string(shm_mapping.GetMemoryAs<const char>(),
shm_mapping.mapped_size()));
}
// Converts from gRPC's GetRoutineUpdateRequest::Command to mojo's
// DiagnosticRoutineCommandEnum.
bool GetMojoCommandFromGrpcCommand(
grpc_api::GetRoutineUpdateRequest::Command grpc_command,
chromeos::cros_healthd::mojom::DiagnosticRoutineCommandEnum*
mojo_command_out) {
DCHECK(mojo_command_out);
switch (grpc_command) {
case grpc_api::GetRoutineUpdateRequest::RESUME:
*mojo_command_out = chromeos::cros_healthd::mojom::
DiagnosticRoutineCommandEnum::kContinue;
return true;
case grpc_api::GetRoutineUpdateRequest::CANCEL:
*mojo_command_out =
chromeos::cros_healthd::mojom::DiagnosticRoutineCommandEnum::kCancel;
return true;
case grpc_api::GetRoutineUpdateRequest::GET_STATUS:
*mojo_command_out = chromeos::cros_healthd::mojom::
DiagnosticRoutineCommandEnum::kGetStatus;
return true;
case grpc_api::GetRoutineUpdateRequest::REMOVE:
*mojo_command_out =
chromeos::cros_healthd::mojom::DiagnosticRoutineCommandEnum::kRemove;
return true;
default:
LOG(ERROR) << "Unknown gRPC command: " << static_cast<int>(grpc_command);
return false;
}
}
} // namespace
RoutineService::RoutineService(Delegate* delegate) : delegate_(delegate) {
DCHECK(delegate_);
}
RoutineService::~RoutineService() {
RunInFlightCallbacks();
}
void RoutineService::GetAvailableRoutines(
const GetAvailableRoutinesToServiceCallback& callback) {
if (!BindCrosHealthdDiagnosticsServiceIfNeeded()) {
LOG(WARNING) << "GetAvailableRoutines called before mojo was bootstrapped.";
callback.Run(std::vector<grpc_api::DiagnosticRoutine>{},
grpc_api::ROUTINE_SERVICE_STATUS_UNAVAILABLE);
return;
}
const size_t callback_key = next_get_available_routines_key_;
next_get_available_routines_key_++;
DCHECK_EQ(get_available_routines_callbacks_.count(callback_key), 0);
get_available_routines_callbacks_.insert({callback_key, std::move(callback)});
service_ptr_->GetAvailableRoutines(
base::Bind(&RoutineService::ForwardGetAvailableRoutinesResponse,
weak_ptr_factory_.GetWeakPtr(), callback_key));
}
void RoutineService::RunRoutine(const grpc_api::RunRoutineRequest& request,
const RunRoutineToServiceCallback& callback) {
if (!BindCrosHealthdDiagnosticsServiceIfNeeded()) {
LOG(WARNING) << "RunRoutine called before mojo was bootstrapped.";
callback.Run(0 /* uuid */, grpc_api::ROUTINE_STATUS_FAILED_TO_START,
grpc_api::ROUTINE_SERVICE_STATUS_UNAVAILABLE);
return;
}
const size_t callback_key = next_run_routine_key_;
next_run_routine_key_++;
DCHECK_EQ(run_routine_callbacks_.count(callback_key), 0);
auto it = run_routine_callbacks_.insert({callback_key, std::move(callback)});
switch (request.routine()) {
case grpc_api::ROUTINE_BATTERY:
DCHECK_EQ(request.parameters_case(),
grpc_api::RunRoutineRequest::kBatteryParams);
service_ptr_->RunBatteryCapacityRoutine(
base::Bind(&RoutineService::ForwardRunRoutineResponse,
weak_ptr_factory_.GetWeakPtr(), callback_key));
break;
case grpc_api::ROUTINE_BATTERY_SYSFS:
DCHECK_EQ(request.parameters_case(),
grpc_api::RunRoutineRequest::kBatterySysfsParams);
service_ptr_->RunBatteryHealthRoutine(
base::Bind(&RoutineService::ForwardRunRoutineResponse,
weak_ptr_factory_.GetWeakPtr(), callback_key));
break;
case grpc_api::ROUTINE_URANDOM:
DCHECK_EQ(request.parameters_case(),
grpc_api::RunRoutineRequest::kUrandomParams);
service_ptr_->RunUrandomRoutine(
chromeos::cros_healthd::mojom::NullableUint32::New(
request.urandom_params().length_seconds()),
base::Bind(&RoutineService::ForwardRunRoutineResponse,
weak_ptr_factory_.GetWeakPtr(), callback_key));
break;
case grpc_api::ROUTINE_SMARTCTL_CHECK:
DCHECK_EQ(request.parameters_case(),
grpc_api::RunRoutineRequest::kSmartctlCheckParams);
service_ptr_->RunSmartctlCheckRoutine(
base::Bind(&RoutineService::ForwardRunRoutineResponse,
weak_ptr_factory_.GetWeakPtr(), callback_key));
break;
case grpc_api::ROUTINE_CPU_CACHE:
DCHECK_EQ(request.parameters_case(),
grpc_api::RunRoutineRequest::kCpuParams);
service_ptr_->RunCpuCacheRoutine(
chromeos::cros_healthd::mojom::NullableUint32::New(
request.cpu_params().length_seconds()),
base::Bind(&RoutineService::ForwardRunRoutineResponse,
weak_ptr_factory_.GetWeakPtr(), callback_key));
break;
case grpc_api::ROUTINE_CPU_STRESS:
DCHECK_EQ(request.parameters_case(),
grpc_api::RunRoutineRequest::kCpuParams);
service_ptr_->RunCpuStressRoutine(
chromeos::cros_healthd::mojom::NullableUint32::New(
request.cpu_params().length_seconds()),
base::Bind(&RoutineService::ForwardRunRoutineResponse,
weak_ptr_factory_.GetWeakPtr(), callback_key));
break;
case grpc_api::ROUTINE_FLOATING_POINT_ACCURACY:
DCHECK_EQ(request.parameters_case(),
grpc_api::RunRoutineRequest::kFloatingPointAccuracyParams);
service_ptr_->RunFloatingPointAccuracyRoutine(
chromeos::cros_healthd::mojom::NullableUint32::New(
request.floating_point_accuracy_params().length_seconds()),
base::Bind(&RoutineService::ForwardRunRoutineResponse,
weak_ptr_factory_.GetWeakPtr(), callback_key));
break;
case grpc_api::ROUTINE_NVME_WEAR_LEVEL:
DCHECK_EQ(request.parameters_case(),
grpc_api::RunRoutineRequest::kNvmeWearLevelParams);
service_ptr_->RunNvmeWearLevelRoutine(
request.nvme_wear_level_params().wear_level_threshold(),
base::Bind(&RoutineService::ForwardRunRoutineResponse,
weak_ptr_factory_.GetWeakPtr(), callback_key));
break;
case grpc_api::ROUTINE_NVME_SHORT_SELF_TEST:
DCHECK_EQ(request.parameters_case(),
grpc_api::RunRoutineRequest::kNvmeShortSelfTestParams);
service_ptr_->RunNvmeSelfTestRoutine(
chromeos::cros_healthd::mojom::NvmeSelfTestTypeEnum::kShortSelfTest,
base::Bind(&RoutineService::ForwardRunRoutineResponse,
weak_ptr_factory_.GetWeakPtr(), callback_key));
break;
case grpc_api::ROUTINE_NVME_LONG_SELF_TEST:
DCHECK_EQ(request.parameters_case(),
grpc_api::RunRoutineRequest::kNvmeLongSelfTestParams);
service_ptr_->RunNvmeSelfTestRoutine(
chromeos::cros_healthd::mojom::NvmeSelfTestTypeEnum::kLongSelfTest,
base::Bind(&RoutineService::ForwardRunRoutineResponse,
weak_ptr_factory_.GetWeakPtr(), callback_key));
break;
case grpc_api::ROUTINE_DISK_LINEAR_READ:
DCHECK_EQ(request.parameters_case(),
grpc_api::RunRoutineRequest::kDiskLinearReadParams);
service_ptr_->RunDiskReadRoutine(
mojo_ipc::DiskReadRoutineTypeEnum::kLinearRead,
request.disk_linear_read_params().length_seconds(),
request.disk_linear_read_params().file_size_mb(),
base::Bind(&RoutineService::ForwardRunRoutineResponse,
weak_ptr_factory_.GetWeakPtr(), callback_key));
break;
case grpc_api::ROUTINE_DISK_RANDOM_READ:
DCHECK_EQ(request.parameters_case(),
grpc_api::RunRoutineRequest::kDiskRandomReadParams);
service_ptr_->RunDiskReadRoutine(
mojo_ipc::DiskReadRoutineTypeEnum::kRandomRead,
request.disk_random_read_params().length_seconds(),
request.disk_random_read_params().file_size_mb(),
base::Bind(&RoutineService::ForwardRunRoutineResponse,
weak_ptr_factory_.GetWeakPtr(), callback_key));
break;
case grpc_api::ROUTINE_PRIME_SEARCH:
DCHECK_EQ(request.parameters_case(),
grpc_api::RunRoutineRequest::kPrimeSearchParams);
service_ptr_->RunPrimeSearchRoutine(
chromeos::cros_healthd::mojom::NullableUint32::New(
request.prime_search_params().length_seconds()),
base::Bind(&RoutineService::ForwardRunRoutineResponse,
weak_ptr_factory_.GetWeakPtr(), callback_key));
break;
default:
LOG(ERROR) << "RunRoutineRequest routine not set or unrecognized.";
it.first->second.Run(0 /* uuid */, grpc_api::ROUTINE_STATUS_INVALID_FIELD,
grpc_api::ROUTINE_SERVICE_STATUS_OK);
run_routine_callbacks_.erase(it.first);
break;
}
}
void RoutineService::GetRoutineUpdate(
int uuid,
grpc_api::GetRoutineUpdateRequest::Command command,
bool include_output,
const GetRoutineUpdateRequestToServiceCallback& callback) {
if (!BindCrosHealthdDiagnosticsServiceIfNeeded()) {
LOG(WARNING) << "GetRoutineUpdate called before mojo was bootstrapped.";
callback.Run(uuid, grpc_api::ROUTINE_STATUS_ERROR, 0 /* progress_percent */,
grpc_api::ROUTINE_USER_MESSAGE_UNSET, "" /* output */,
"" /* status_message */,
grpc_api::ROUTINE_SERVICE_STATUS_UNAVAILABLE);
return;
}
chromeos::cros_healthd::mojom::DiagnosticRoutineCommandEnum mojo_command;
if (!GetMojoCommandFromGrpcCommand(command, &mojo_command)) {
callback.Run(uuid, grpc_api::ROUTINE_STATUS_INVALID_FIELD,
0 /* progress_percent */, grpc_api::ROUTINE_USER_MESSAGE_UNSET,
"" /* output */, "" /* status_message */,
grpc_api::ROUTINE_SERVICE_STATUS_OK);
return;
}
const size_t callback_key = next_get_routine_update_key_;
next_get_routine_update_key_++;
DCHECK_EQ(get_routine_update_callbacks_.count(callback_key), 0);
get_routine_update_callbacks_.insert(
{callback_key, {uuid, std::move(callback)}});
service_ptr_->GetRoutineUpdate(
uuid, mojo_command, include_output,
base::Bind(&RoutineService::ForwardGetRoutineUpdateResponse,
weak_ptr_factory_.GetWeakPtr(), callback_key));
}
void RoutineService::ForwardGetAvailableRoutinesResponse(
size_t callback_key,
const std::vector<chromeos::cros_healthd::mojom::DiagnosticRoutineEnum>&
mojo_routines) {
auto it = get_available_routines_callbacks_.find(callback_key);
if (it == get_available_routines_callbacks_.end()) {
LOG(ERROR) << "Unknown callback_key for received mojo GetAvailableRoutines "
"response: "
<< callback_key;
return;
}
std::vector<grpc_api::DiagnosticRoutine> grpc_routines;
for (auto mojo_routine : mojo_routines) {
std::vector<grpc_api::DiagnosticRoutine> grpc_mojo_routines;
if (GetGrpcRoutineEnumFromMojoRoutineEnum(mojo_routine,
&grpc_mojo_routines))
for (auto grpc_routine : grpc_mojo_routines)
grpc_routines.push_back(grpc_routine);
}
it->second.Run(std::move(grpc_routines), grpc_api::ROUTINE_SERVICE_STATUS_OK);
get_available_routines_callbacks_.erase(it);
}
void RoutineService::ForwardRunRoutineResponse(
size_t callback_key,
chromeos::cros_healthd::mojom::RunRoutineResponsePtr response) {
auto it = run_routine_callbacks_.find(callback_key);
if (it == run_routine_callbacks_.end()) {
LOG(ERROR) << "Unknown callback_key for received mojo GetAvailableRoutines "
"response: "
<< callback_key;
return;
}
grpc_api::DiagnosticRoutineStatus grpc_status;
chromeos::cros_healthd::mojom::DiagnosticRoutineStatusEnum mojo_status =
response->status;
if (!GetGrpcStatusFromMojoStatus(mojo_status, &grpc_status)) {
it->second.Run(0 /* uuid */, grpc_api::ROUTINE_STATUS_ERROR,
grpc_api::ROUTINE_SERVICE_STATUS_OK);
} else {
it->second.Run(response->id, grpc_status,
grpc_api::ROUTINE_SERVICE_STATUS_OK);
}
run_routine_callbacks_.erase(it);
}
void RoutineService::ForwardGetRoutineUpdateResponse(
size_t callback_key,
chromeos::cros_healthd::mojom::RoutineUpdatePtr response) {
auto it = get_routine_update_callbacks_.find(callback_key);
if (it == get_routine_update_callbacks_.end()) {
LOG(ERROR) << "Unknown callback_key for received mojo GetAvailableRoutines "
"response: "
<< callback_key;
return;
}
grpc_api::GetRoutineUpdateResponse grpc_response;
SetGrpcUpdateFromMojoUpdate(std::move(response), &grpc_response);
it->second.second.Run(it->second.first /* uuid */, grpc_response.status(),
grpc_response.progress_percent(),
grpc_response.user_message(), grpc_response.output(),
grpc_response.status_message(),
grpc_api::ROUTINE_SERVICE_STATUS_OK);
get_routine_update_callbacks_.erase(it);
}
bool RoutineService::BindCrosHealthdDiagnosticsServiceIfNeeded() {
if (service_ptr_.is_bound())
return true;
auto request = mojo::MakeRequest(&service_ptr_);
service_ptr_.set_connection_error_handler(base::Bind(
&RoutineService::OnDisconnect, weak_ptr_factory_.GetWeakPtr()));
if (!delegate_->GetCrosHealthdDiagnosticsService(std::move(request)))
return false;
return true;
}
void RoutineService::OnDisconnect() {
VLOG(1) << "cros_healthd Mojo connection closed.";
RunInFlightCallbacks();
service_ptr_.reset();
}
void RoutineService::RunInFlightCallbacks() {
for (auto& it : get_available_routines_callbacks_) {
it.second.Run(std::vector<grpc_api::DiagnosticRoutine>{},
grpc_api::ROUTINE_SERVICE_STATUS_UNAVAILABLE);
}
get_available_routines_callbacks_.clear();
for (auto& it : run_routine_callbacks_) {
it.second.Run(0 /* uuid */, grpc_api::ROUTINE_STATUS_FAILED_TO_START,
grpc_api::ROUTINE_SERVICE_STATUS_UNAVAILABLE);
}
run_routine_callbacks_.clear();
for (auto& it : get_routine_update_callbacks_) {
it.second.second.Run(
it.second.first /* uuid */, grpc_api::ROUTINE_STATUS_ERROR,
0 /* progress_percent */, grpc_api::ROUTINE_USER_MESSAGE_UNSET,
"" /* output */, "" /* status_message */,
grpc_api::ROUTINE_SERVICE_STATUS_UNAVAILABLE);
}
get_routine_update_callbacks_.clear();
}
} // namespace diagnostics