Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vdisk evict #5093

Merged
merged 1 commit into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion ydb/core/viewer/json_handlers_vdisk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@
#include "json_vdiskstat.h"
#include "json_getblob.h"
#include "json_blobindexstat.h"

#include "json_vdisk_evict.h"

namespace NKikimr::NViewer {

void InitVDiskJsonHandlers(TJsonHandlers& jsonHandlers) {
jsonHandlers.AddHandler("/vdisk/vdiskstat", new TJsonHandler<TJsonVDiskStat>);
jsonHandlers.AddHandler("/vdisk/getblob", new TJsonHandler<TJsonGetBlob>);
jsonHandlers.AddHandler("/vdisk/blobindexstat", new TJsonHandler<TJsonBlobIndexStat>);
jsonHandlers.AddHandler("/vdisk/evict", new TJsonHandler<TJsonVDiskEvict>);
}

}
15 changes: 15 additions & 0 deletions ydb/core/viewer/json_pipe_req.h
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,21 @@ class TViewerPipeClient : public TActorBootstrapped<TDerived> {
SendRequestToPipe(pipeClient, request.Release());
}

void RequestBSControllerVDiskEvict(ui32 groupId, ui32 groupGeneration, ui32 failRealmIdx, ui32 failDomainIdx, ui32 vdiskIdx, bool force = false) {
TActorId pipeClient = ConnectTabletPipe(GetBSControllerId());
THolder<TEvBlobStorage::TEvControllerConfigRequest> request = MakeHolder<TEvBlobStorage::TEvControllerConfigRequest>();
auto* evictVDisk = request->Record.MutableRequest()->AddCommand()->MutableReassignGroupDisk();
evictVDisk->SetGroupId(groupId);
evictVDisk->SetGroupGeneration(groupGeneration);
evictVDisk->SetFailRealmIdx(failRealmIdx);
evictVDisk->SetFailDomainIdx(failDomainIdx);
evictVDisk->SetVDiskIdx(vdiskIdx);
if (force) {
request->Record.MutableRequest()->SetIgnoreDegradedGroupsChecks(true);
}
SendRequestToPipe(pipeClient, request.Release());
}

void RequestSchemeCacheNavigate(const TString& path) {
THolder<NSchemeCache::TSchemeCacheNavigate> request = MakeHolder<NSchemeCache::TSchemeCacheNavigate>();
NSchemeCache::TSchemeCacheNavigate::TEntry entry;
Expand Down
290 changes: 290 additions & 0 deletions ydb/core/viewer/json_vdisk_evict.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
#pragma once
#include <ydb/library/actors/core/actor_bootstrapped.h>
#include <ydb/library/actors/core/interconnect.h>
#include <ydb/library/actors/core/mon.h>
#include <ydb/library/services/services.pb.h>
#include <ydb/core/viewer/json/json.h>
#include <ydb/core/util/wildcard.h>
#include <library/cpp/json/json_writer.h>
#include "viewer.h"
#include "json_pipe_req.h"

namespace NKikimr {
namespace NViewer {

using namespace NActors;

class TJsonVDiskEvict : public TViewerPipeClient<TJsonVDiskEvict> {
enum EEv {
EvRetryNodeRequest = EventSpaceBegin(NActors::TEvents::ES_PRIVATE),
EvEnd
};

static_assert(EvEnd < EventSpaceEnd(NActors::TEvents::ES_PRIVATE), "expect EvEnd < EventSpaceEnd(TEvents::ES_PRIVATE)");

struct TEvRetryNodeRequest : NActors::TEventLocal<TEvRetryNodeRequest, EvRetryNodeRequest> {
TEvRetryNodeRequest()
{}
};

protected:
using TThis = TJsonVDiskEvict;
using TBase = TViewerPipeClient<TThis>;
IViewer* Viewer;
NMon::TEvHttpInfo::TPtr Event;
TJsonSettings JsonSettings;
ui32 Timeout = 0;
ui32 ActualRetries = 0;
ui32 Retries = 0;
TDuration RetryPeriod = TDuration::MilliSeconds(500);

std::unique_ptr<TEvBlobStorage::TEvControllerConfigResponse> Response;

ui32 GroupId = 0;
ui32 GroupGeneration = 0;
ui32 FailRealmIdx = 0;
ui32 FailDomainIdx = 0;
ui32 VdiskIdx = 0;
bool Force = false;

public:
static constexpr NKikimrServices::TActivity::EType ActorActivityType() {
return NKikimrServices::TActivity::VIEWER_HANDLER;
}

TJsonVDiskEvict(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev)
: Viewer(viewer)
, Event(ev)
{}

inline ui32 GetRequiredParam(const TCgiParameters& params, const std::string& name, ui32& obj) {
if (!TryFromString<ui32>(params.Get(name), obj)) {
TBase::Send(Event->Sender, new NMon::TEvHttpInfoRes(
Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", TStringBuilder() << "field '" << name << "' or 'vdisk_id' are required"),
0, NMon::IEvHttpInfoRes::EContentType::Custom));
return false;
}
return true;
}

void Bootstrap() {
const auto& params(Event->Get()->Request.GetParams());
TString vdisk_id = params.Get("vdisk_id");
if (vdisk_id) {
TVector<TString> parts = StringSplitter(vdisk_id).Split('-').SkipEmpty();
if (parts.size() == 5) {
GroupId = FromStringWithDefault<ui32>(parts[0], Max<ui32>());
GroupGeneration = FromStringWithDefault<ui32>(parts[1], Max<ui32>());
FailRealmIdx = FromStringWithDefault<ui32>(parts[2], Max<ui32>());
FailDomainIdx = FromStringWithDefault<ui32>(parts[3], Max<ui32>());
VdiskIdx = FromStringWithDefault<ui32>(parts[4], Max<ui32>());
}
if (parts.size() != 5 || GroupId == Max<ui32>()
|| GroupGeneration == Max<ui32>() || FailRealmIdx == Max<ui32>()
|| FailDomainIdx == Max<ui32>() || VdiskIdx == Max<ui32>()) {
TBase::Send(Event->Sender, new NMon::TEvHttpInfoRes(
Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", TStringBuilder() << "Unable to parse the 'vdisk_id' parameter"),
0, NMon::IEvHttpInfoRes::EContentType::Custom));
return PassAway();
}
} else if (!GetRequiredParam(params, "group_id", GroupId)
|| !GetRequiredParam(params, "group_generation_id", GroupGeneration)
|| !GetRequiredParam(params, "fail_realm_idx", FailRealmIdx)
|| !GetRequiredParam(params, "fail_domain_idx", FailDomainIdx)
|| !GetRequiredParam(params, "vdisk_idx", VdiskIdx)) {
return PassAway();
}

if (Event->Get()->Request.GetMethod() != HTTP_METHOD_POST) {
TBase::Send(Event->Sender, new NMon::TEvHttpInfoRes(
Viewer->GetHTTPBADREQUEST(Event->Get(), "text/plain", "Only POST method is allowed"),
0, NMon::IEvHttpInfoRes::EContentType::Custom));
return PassAway();
}
TBase::InitConfig(params);

Force = FromStringWithDefault<bool>(params.Get("force"), false);
Timeout = FromStringWithDefault<ui32>(params.Get("timeout"), 10000);
Retries = FromStringWithDefault<ui32>(params.Get("retries"), 0);
RetryPeriod = TDuration::MilliSeconds(FromStringWithDefault<ui32>(params.Get("retry_period"), RetryPeriod.MilliSeconds()));

SendRequest();

TBase::Become(&TThis::StateWork, TDuration::MilliSeconds(Timeout), new TEvents::TEvWakeup());
}

STATEFN(StateWork) {
switch (ev->GetTypeRewrite()) {
hFunc(TEvBlobStorage::TEvControllerConfigResponse, Handle);
cFunc(TEvRetryNodeRequest::EventType, HandleRetry);
cFunc(TEvents::TEvUndelivered::EventType, Undelivered);
cFunc(TEvents::TSystem::Wakeup, HandleTimeout);
}
}

void SendRequest() {
RequestBSControllerVDiskEvict(GroupId, GroupGeneration, FailRealmIdx, FailDomainIdx, VdiskIdx, Force);
}

bool RetryRequest() {
if (Retries) {
if (++ActualRetries <= Retries) {
TBase::Schedule(RetryPeriod, new TEvRetryNodeRequest());
return true;
}
}
return false;
}

void Undelivered() {
if (!RetryRequest()) {
TBase::RequestDone();
}
}

void Handle(TEvBlobStorage::TEvControllerConfigResponse::TPtr& ev) {
Response.reset(ev->Release().Release());
ReplyAndPassAway();
}

void HandleRetry() {
SendRequest();
}

void HandleTimeout() {
Send(Event->Sender, new NMon::TEvHttpInfoRes(
Viewer->GetHTTPGATEWAYTIMEOUT(Event->Get(), "text/plain", "Timeout receiving response from NodeWarden"),
0, NMon::IEvHttpInfoRes::EContentType::Custom));
}

void PassAway() override {
TBase::PassAway();
}

void TryToTranslateFromBSC2Human(const NKikimrBlobStorage::TConfigResponse& response, TString& bscError, bool& forceRetryPossible) {
if (response.GroupsGetDisintegratedByExpectedStatusSize()) {
bscError = TStringBuilder() << "Calling this operation will cause at least groups [" << JoinStrings(response.GetGroupsGetDisintegratedByExpectedStatus().begin(), response.GetGroupsGetDisintegratedByExpectedStatus().end(), ", ") << "] to go into a dead state";
} else if (response.GroupsGetDisintegratedSize()) {
bscError = TStringBuilder() << "Calling this operation will cause at least groups [" << JoinStrings(response.GetGroupsGetDisintegrated().begin(), response.GetGroupsGetDisintegrated().end(), ", ") << "] to go into a dead state";
} else if (response.GroupsGetDegradedSize()) {
bscError = TStringBuilder() << "Calling this operation will cause at least groups [" << JoinStrings(response.GetGroupsGetDegraded().begin(), response.GetGroupsGetDegraded().end(), ", ") << "] to go into a degraded state";
forceRetryPossible = true;
} else if (response.StatusSize()) {
const auto& lastStatus = response.GetStatus(response.StatusSize() - 1);
TVector<ui32> groups;
for (auto& failParam: lastStatus.GetFailParam()) {
if (failParam.HasGroupId()) {
groups.emplace_back(failParam.GetGroupId());
}
}
if (lastStatus.GetFailReason() == NKikimrBlobStorage::TConfigResponse::TStatus::kMayGetDegraded) {
bscError = TStringBuilder() << "Calling this operation will cause at least groups [" << JoinVectorIntoString(groups, ", ") << "] to go into a degraded state";
forceRetryPossible = true;
} else if (lastStatus.GetFailReason() == NKikimrBlobStorage::TConfigResponse::TStatus::kMayLoseData) {
bscError = TStringBuilder() << "Calling this operation may result in data loss for at least groups [" << JoinVectorIntoString(groups, ", ") << "]";
}
}
}

void ReplyAndPassAway() {
NJson::TJsonValue json;
if (Response != nullptr) {
if (Response->Record.GetResponse().GetSuccess()) {
json["result"] = true;
} else {
json["result"] = false;
TString error = Response->Record.GetResponse().GetErrorDescription();
bool forceRetryPossible = false;
TryToTranslateFromBSC2Human(Response->Record.GetResponse(), error, forceRetryPossible);
json["error"] = error;
if (forceRetryPossible) {
json["forceRetryPossible"] = true;
}
}
json["debugMessage"] = Response->Record.ShortDebugString();
} else {
json["result"] = false;
json["error"] = "No response was received from BSC";
}
TBase::Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPOKJSON(Event->Get(), NJson::WriteJson(json)), 0, NMon::IEvHttpInfoRes::EContentType::Custom));
PassAway();
}
};

template <>
YAML::Node TJsonRequestSwagger<TJsonVDiskEvict>::GetSwagger() {
return YAML::Load(R"___(
post:
tags:
- vdisk
summary: VDisk evict
description: VDisk evict
parameters:
- name: vdisk_id
in: query
description: vdisk identifier
required: false
type: string
- name: group_id
in: query
description: group identifier
required: false
type: integer
- name: group_generation_id
in: query
description: group generation identifier
required: false
type: integer
- name: fail_realm_idx
in: query
description: fail realm identifier
required: false
type: integer
- name: fail_domain_ids
in: query
description: fail domain identifier
required: false
type: integer
- name: vdisk_idx
in: query
description: vdisk idx identifier
required: false
type: integer
- name: timeout
in: query
description: timeout in ms
required: false
type: integer
- name: force
in: query
description: attempt forced operation, ignore warnings
required: false
type: boolean
responses:
200:
description: OK
content:
application/json:
schema:
type: object
properties:
result:
type: boolean
description: was operation successful or not
error:
type: string
description: details about failed operation
forceRetryPossible:
type: boolean
description: if true, operation can be retried with force flag
400:
description: Bad Request
403:
description: Forbidden
504:
description: Gateway Timeout
)___");
}

}
}
1 change: 1 addition & 0 deletions ydb/core/viewer/ya.make
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ SRCS(
json_topicinfo.h
json_pqconsumerinfo.h
json_vdisk_req.h
json_vdisk_evict.h
json_vdiskinfo.h
json_vdiskstat.h
json_wb_req.h
Expand Down
Loading