Skip to content

Commit

Permalink
vdisk evict (#5093)
Browse files Browse the repository at this point in the history
  • Loading branch information
StekPerepolnen authored Jun 5, 2024
1 parent c3a716e commit 7fc48b8
Show file tree
Hide file tree
Showing 4 changed files with 308 additions and 1 deletion.
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

0 comments on commit 7fc48b8

Please sign in to comment.