From 93e2bcb884cbbcfab39cb7a8e7e31d72a91db437 Mon Sep 17 00:00:00 2001 From: Vladislav Stepanyuk <114143252+vladstepanyuk@users.noreply.github.com> Date: Thu, 16 Jan 2025 12:07:59 +0700 Subject: [PATCH] [NBS] Issue 2732: add change device/agent state buttons (#2760) * issue-2732: add change device state button * issue-2732: add change agent state button and tests * issue-2732: correct issues * issue-2732: correct issues * issue-2732: fmt constructor * issue-2732: correct linter issues * issue-2732: add config flag for buttons * issue-2732: rename conf param and refactor * issue-2732: rename allowlist change log msgs and reson * issue-2732: rename list * issue-2732: correct allowlist and enums * issue-2732: SUCCEDED -> !HasError * issue-2732: correct build issue --- cloud/blockstore/config/storage.proto | 6 + cloud/blockstore/libs/storage/core/config.cpp | 2 + cloud/blockstore/libs/storage/core/config.h | 3 + .../disk_registry/disk_registry_actor.h | 10 + ...disk_registry_actor_change_agent_state.cpp | 251 ++++++++++++++++++ ...isk_registry_actor_change_device_state.cpp | 250 +++++++++++++++++ .../disk_registry_actor_monitoring.cpp | 87 +++++- .../libs/storage/disk_registry/ya.make | 2 + cloud/blockstore/tests/monitoring/test.py | 95 +++++++ cloud/blockstore/tests/python/lib/client.py | 14 + 10 files changed, 717 insertions(+), 3 deletions(-) create mode 100644 cloud/blockstore/libs/storage/disk_registry/disk_registry_actor_change_agent_state.cpp create mode 100644 cloud/blockstore/libs/storage/disk_registry/disk_registry_actor_change_device_state.cpp diff --git a/cloud/blockstore/config/storage.proto b/cloud/blockstore/config/storage.proto index c7fd81f92d1..e032170f956 100644 --- a/cloud/blockstore/config/storage.proto +++ b/cloud/blockstore/config/storage.proto @@ -1083,4 +1083,10 @@ message TStorageServiceConfig // percentage, then the rejection of such agents does not occur - we assume // a connectivity failure in the cluster. optional double DiskRegistryInitialAgentRejectionThreshold = 396; + + // Enable buttons for agent/device state changing. + optional bool EnableToChangeStatesFromDiskRegistryMonpage = 397; + + // Enable buttons for device state changing, when they in error state. + optional bool EnableToChangeErrorStatesFromDiskRegistryMonpage = 398; } diff --git a/cloud/blockstore/libs/storage/core/config.cpp b/cloud/blockstore/libs/storage/core/config.cpp index 827ad3a8a01..fddceb9b504 100644 --- a/cloud/blockstore/libs/storage/core/config.cpp +++ b/cloud/blockstore/libs/storage/core/config.cpp @@ -521,6 +521,8 @@ TDuration MSeconds(ui32 value) xxx(EncryptionAtRestForDiskRegistryBasedDisksEnabled, bool, false )\ xxx(DisableFullPlacementGroupCountCalculation, bool, false )\ xxx(DiskRegistryInitialAgentRejectionThreshold, double, 50 )\ + xxx(EnableToChangeStatesFromDiskRegistryMonpage, bool, false )\ + xxx(EnableToChangeErrorStatesFromDiskRegistryMonpage, bool, false )\ // BLOCKSTORE_STORAGE_CONFIG_RW #define BLOCKSTORE_STORAGE_CONFIG(xxx) \ diff --git a/cloud/blockstore/libs/storage/core/config.h b/cloud/blockstore/libs/storage/core/config.h index 96ed8b1968b..56e10d2a106 100644 --- a/cloud/blockstore/libs/storage/core/config.h +++ b/cloud/blockstore/libs/storage/core/config.h @@ -622,6 +622,9 @@ class TStorageConfig [[nodiscard]] bool GetDisableFullPlacementGroupCountCalculation() const; [[nodiscard]] double GetDiskRegistryInitialAgentRejectionThreshold() const; + [[nodiscard]] bool GetEnableToChangeStatesFromDiskRegistryMonpage() const; + [[nodiscard]] bool + GetEnableToChangeErrorStatesFromDiskRegistryMonpage() const; }; ui64 GetAllocationUnit( diff --git a/cloud/blockstore/libs/storage/disk_registry/disk_registry_actor.h b/cloud/blockstore/libs/storage/disk_registry/disk_registry_actor.h index a295b3b1342..951be732909 100644 --- a/cloud/blockstore/libs/storage/disk_registry/disk_registry_actor.h +++ b/cloud/blockstore/libs/storage/disk_registry/disk_registry_actor.h @@ -346,6 +346,16 @@ class TDiskRegistryActor final const TCgiParameters& params, TRequestInfoPtr requestInfo); + void HandleHttpInfo_ChangeDeviseState( + const NActors::TActorContext& ctx, + const TCgiParameters& params, + TRequestInfoPtr requestInfo); + + void HandleHttpInfo_ChangeAgentState( + const NActors::TActorContext& ctx, + const TCgiParameters& params, + TRequestInfoPtr requestInfo); + void HandleHttpInfo_RenderDisks( const NActors::TActorContext& ctx, const TCgiParameters& params, diff --git a/cloud/blockstore/libs/storage/disk_registry/disk_registry_actor_change_agent_state.cpp b/cloud/blockstore/libs/storage/disk_registry/disk_registry_actor_change_agent_state.cpp new file mode 100644 index 00000000000..72c9472fcb4 --- /dev/null +++ b/cloud/blockstore/libs/storage/disk_registry/disk_registry_actor_change_agent_state.cpp @@ -0,0 +1,251 @@ +#include "disk_registry_actor.h" + +#include + +namespace NCloud::NBlockStore::NStorage { + +using namespace NActors; + +using namespace NKikimr; + +using namespace NMonitoringUtils; + +namespace { + +//////////////////////////////////////////////////////////////////////////////// + +class TChangeAgentStateActor final + : public TActorBootstrapped +{ +private: + const TActorId Owner; + const ui64 TabletID; + const TRequestInfoPtr RequestInfo; + const TString AgentID; + const NProto::EAgentState NewState; + const NProto::EAgentState OldState; + +public: + TChangeAgentStateActor( + const TActorId& owner, + ui64 tabletID, + TRequestInfoPtr requestInfo, + TString agentId, + NProto::EAgentState newState, + NProto::EAgentState oldState); + + void Bootstrap(const TActorContext& ctx); + +private: + void Notify( + const TActorContext& ctx, + TString message, + const EAlertLevel alertLevel); + + void ReplyAndDie(const TActorContext& ctx, NProto::TError error); + +private: + STFUNC(StateWork); + + void HandleChangeAgentStateResponse( + const TEvDiskRegistry::TEvChangeAgentStateResponse::TPtr& ev, + const TActorContext& ctx); + + void HandlePoisonPill( + const TEvents::TEvPoisonPill::TPtr& ev, + const TActorContext& ctx); +}; + +//////////////////////////////////////////////////////////////////////////////// + +TChangeAgentStateActor::TChangeAgentStateActor( + const TActorId& owner, + ui64 tabletID, + TRequestInfoPtr requestInfo, + TString agentId, + NProto::EAgentState newState, + NProto::EAgentState oldState) + : Owner(owner) + , TabletID(tabletID) + , RequestInfo(std::move(requestInfo)) + , AgentID(std::move(agentId)) + , NewState(newState) + , OldState(oldState) +{} + +void TChangeAgentStateActor::Bootstrap(const TActorContext& ctx) +{ + auto request = + std::make_unique(); + + request->Record.SetAgentId(AgentID); + request->Record.SetAgentState(NewState); + request->Record.SetReason("monpage action"); + + NCloud::Send(ctx, Owner, std::move(request)); + + Become(&TThis::StateWork); +} + +void TChangeAgentStateActor::Notify( + const TActorContext& ctx, + TString message, + const EAlertLevel alertLevel) +{ + TStringStream out; + + BuildNotifyPageWithRedirect( + out, + std::move(message), + TStringBuilder() << "./app?action=agent&TabletId=" << TabletID + << "&AgentID=" << AgentID, + alertLevel); + + auto response = std::make_unique(out.Str()); + NCloud::Reply(ctx, *RequestInfo, std::move(response)); +} + +void TChangeAgentStateActor::ReplyAndDie( + const TActorContext& ctx, + NProto::TError error) +{ + if (!HasError(error)) { + Notify(ctx, "Operation successfully completed", EAlertLevel::SUCCESS); + } else { + Notify( + ctx, + TStringBuilder() + << "failed to change agent[" << AgentID.Quote() + << "] state from " << EAgentState_Name(OldState) << " to " + << EAgentState_Name(NewState) << ": " << FormatError(error), + EAlertLevel::DANGER); + } + + NCloud::Send( + ctx, + Owner, + std::make_unique()); + + Die(ctx); +} + +//////////////////////////////////////////////////////////////////////////////// + +void TChangeAgentStateActor::HandlePoisonPill( + const TEvents::TEvPoisonPill::TPtr& ev, + const TActorContext& ctx) +{ + Y_UNUSED(ev); + ReplyAndDie(ctx, MakeTabletIsDeadError(E_REJECTED, __LOCATION__)); +} + +void TChangeAgentStateActor::HandleChangeAgentStateResponse( + const TEvDiskRegistry::TEvChangeAgentStateResponse::TPtr& ev, + const TActorContext& ctx) +{ + const auto* response = ev->Get(); + + ReplyAndDie(ctx, response->GetError()); +} + +//////////////////////////////////////////////////////////////////////////////// + +STFUNC(TChangeAgentStateActor::StateWork) +{ + switch (ev->GetTypeRewrite()) { + HFunc(TEvents::TEvPoisonPill, HandlePoisonPill); + + HFunc( + TEvDiskRegistry::TEvChangeAgentStateResponse, + HandleChangeAgentStateResponse); + + default: + HandleUnexpectedEvent( + ev, + TBlockStoreComponents::DISK_REGISTRY_WORKER); + break; + } +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// + +void TDiskRegistryActor::HandleHttpInfo_ChangeAgentState( + const TActorContext& ctx, + const TCgiParameters& params, + TRequestInfoPtr requestInfo) +{ + if (!Config->GetEnableToChangeStatesFromDiskRegistryMonpage()) { + RejectHttpRequest(ctx, *requestInfo, "Can't change state from monpage"); + return; + } + + const auto& newStateRaw = params.Get("NewState"); + const auto& agentId = params.Get("AgentID"); + + if (!newStateRaw) { + RejectHttpRequest(ctx, *requestInfo, "No new state is given"); + return; + } + + if (!agentId) { + RejectHttpRequest(ctx, *requestInfo, "No agent id is given"); + return; + } + + NProto::EAgentState newState; + if (!EAgentState_Parse(newStateRaw, &newState)) { + RejectHttpRequest(ctx, *requestInfo, "Invalid new state"); + return; + } + + switch (newState) { + case NProto::AGENT_STATE_ONLINE: + case NProto::AGENT_STATE_WARNING: + break; + default: + RejectHttpRequest(ctx, *requestInfo, "Invalid new state"); + return; + } + + const auto agentState = State->GetAgentState(agentId); + if (agentState.Empty()) { + RejectHttpRequest(ctx, *requestInfo, "Unknown agent"); + return; + } + + switch (newState) { + case NProto::AGENT_STATE_ONLINE: + case NProto::AGENT_STATE_WARNING: + break; + default: + RejectHttpRequest( + ctx, + *requestInfo, + "Can't change agent state from " + + EAgentState_Name(*agentState.Get())); + return; + } + + LOG_INFO( + ctx, + TBlockStoreComponents::DISK_REGISTRY, + "Change state of agent[%s] on monitoring page from %s to %s", + agentId.Quote().c_str(), + EAgentState_Name(*agentState.Get()).c_str(), + EAgentState_Name(newState).c_str()); + + auto actor = NCloud::Register( + ctx, + SelfId(), + TabletID(), + std::move(requestInfo), + agentId, + newState, + *agentState.Get()); + + Actors.insert(actor); +} + +} // namespace NCloud::NBlockStore::NStorage diff --git a/cloud/blockstore/libs/storage/disk_registry/disk_registry_actor_change_device_state.cpp b/cloud/blockstore/libs/storage/disk_registry/disk_registry_actor_change_device_state.cpp new file mode 100644 index 00000000000..192bd2c20b8 --- /dev/null +++ b/cloud/blockstore/libs/storage/disk_registry/disk_registry_actor_change_device_state.cpp @@ -0,0 +1,250 @@ +#include "disk_registry_actor.h" +#include "util/string/join.h" + +#include + +namespace NCloud::NBlockStore::NStorage { + +using namespace NActors; + +using namespace NKikimr; + +using namespace NMonitoringUtils; + +namespace { + +//////////////////////////////////////////////////////////////////////////////// + +class TChangeDeviceStateActor final + : public TActorBootstrapped +{ +private: + const TActorId Owner; + const ui64 TabletID; + const TRequestInfoPtr RequestInfo; + const TString DeviceUUID; + const NProto::EDeviceState NewState; + const NProto::EDeviceState OldState; + +public: + TChangeDeviceStateActor( + const TActorId& owner, + ui64 tabletID, + TRequestInfoPtr requestInfo, + TString deviceId, + NProto::EDeviceState newState, + NProto::EDeviceState oldState); + + void Bootstrap(const TActorContext& ctx); + +private: + void Notify( + const TActorContext& ctx, + TString message, + const EAlertLevel alertLevel); + + void ReplyAndDie(const TActorContext& ctx, NProto::TError error); + +private: + STFUNC(StateWork); + + void HandleChangeDeviceStateResponse( + const TEvDiskRegistry::TEvChangeDeviceStateResponse::TPtr& ev, + const TActorContext& ctx); + + void HandlePoisonPill( + const TEvents::TEvPoisonPill::TPtr& ev, + const TActorContext& ctx); +}; + +//////////////////////////////////////////////////////////////////////////////// + +TChangeDeviceStateActor::TChangeDeviceStateActor( + const TActorId& owner, + ui64 tabletID, + TRequestInfoPtr requestInfo, + TString deviceId, + NProto::EDeviceState newState, + NProto::EDeviceState oldState) + : Owner(owner) + , TabletID(tabletID) + , RequestInfo(std::move(requestInfo)) + , DeviceUUID(std::move(deviceId)) + , NewState(newState) + , OldState(oldState) +{} + +void TChangeDeviceStateActor::Bootstrap(const TActorContext& ctx) +{ + auto request = + std::make_unique(); + + request->Record.SetDeviceUUID(DeviceUUID); + request->Record.SetDeviceState(NewState); + request->Record.SetReason("monpage action"); + + NCloud::Send(ctx, Owner, std::move(request)); + + Become(&TThis::StateWork); +} + +void TChangeDeviceStateActor::Notify( + const TActorContext& ctx, + TString message, + const EAlertLevel alertLevel) +{ + TStringStream out; + + BuildNotifyPageWithRedirect( + out, + std::move(message), + TStringBuilder() << "./app?action=dev&TabletId=" << TabletID + << "&DeviceUUID=" << DeviceUUID, + alertLevel); + + auto response = std::make_unique(out.Str()); + NCloud::Reply(ctx, *RequestInfo, std::move(response)); +} + +void TChangeDeviceStateActor::ReplyAndDie( + const TActorContext& ctx, + NProto::TError error) +{ + if (!HasError(error)) { + Notify(ctx, "Operation successfully completed", EAlertLevel::SUCCESS); + } else { + Notify( + ctx, + TStringBuilder() + << "failed to change device " << DeviceUUID.Quote() + << " state from: " << EDeviceState_Name(OldState) << "to " + << EDeviceState_Name(NewState) << ": " << FormatError(error), + EAlertLevel::DANGER); + } + + NCloud::Send( + ctx, + Owner, + std::make_unique()); + + Die(ctx); +} + +//////////////////////////////////////////////////////////////////////////////// + +void TChangeDeviceStateActor::HandlePoisonPill( + const TEvents::TEvPoisonPill::TPtr& ev, + const TActorContext& ctx) +{ + Y_UNUSED(ev); + ReplyAndDie(ctx, MakeTabletIsDeadError(E_REJECTED, __LOCATION__)); +} + +void TChangeDeviceStateActor::HandleChangeDeviceStateResponse( + const TEvDiskRegistry::TEvChangeDeviceStateResponse::TPtr& ev, + const TActorContext& ctx) +{ + const auto* response = ev->Get(); + + ReplyAndDie(ctx, response->GetError()); +} + +//////////////////////////////////////////////////////////////////////////////// + +STFUNC(TChangeDeviceStateActor::StateWork) +{ + switch (ev->GetTypeRewrite()) { + HFunc(TEvents::TEvPoisonPill, HandlePoisonPill); + + HFunc( + TEvDiskRegistry::TEvChangeDeviceStateResponse, + HandleChangeDeviceStateResponse); + + default: + HandleUnexpectedEvent( + ev, + TBlockStoreComponents::DISK_REGISTRY_WORKER); + break; + } +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// + +void TDiskRegistryActor::HandleHttpInfo_ChangeDeviseState( + const TActorContext& ctx, + const TCgiParameters& params, + TRequestInfoPtr requestInfo) +{ + if (!Config->GetEnableToChangeStatesFromDiskRegistryMonpage()) { + RejectHttpRequest(ctx, *requestInfo, "Can't change state from monpage"); + return; + } + const auto& newStateRaw = params.Get("NewState"); + const auto& deviceUUID = params.Get("DeviceUUID"); + + if (!newStateRaw) { + RejectHttpRequest(ctx, *requestInfo, "No new state is given"); + return; + } + if (!deviceUUID) { + RejectHttpRequest(ctx, *requestInfo, "No device id is given"); + return; + } + + NProto::EDeviceState newState; + if (!EDeviceState_Parse(newStateRaw, &newState)) { + RejectHttpRequest(ctx, *requestInfo, "Invalid new state"); + return; + } + + switch (newState) { + case NProto::DEVICE_STATE_ONLINE: + case NProto::DEVICE_STATE_WARNING: + break; + default: + RejectHttpRequest(ctx, *requestInfo, "Invalid new state"); + return; + } + + const auto& device = State->GetDevice(deviceUUID); + switch (device.GetState()) { + case NProto::DEVICE_STATE_ONLINE: + case NProto::DEVICE_STATE_WARNING: + break; + case NProto::DEVICE_STATE_ERROR: + if (Config->GetEnableToChangeErrorStatesFromDiskRegistryMonpage()) { + break; + } + [[fallthrough]]; + default: + RejectHttpRequest( + ctx, + *requestInfo, + "Can't change device state from " + + EDeviceState_Name(device.GetState())); + return; + } + + LOG_INFO( + ctx, + TBlockStoreComponents::DISK_REGISTRY, + "Change state of device[%s] on monitoring page from %s to %s", + deviceUUID.Quote().c_str(), + EDeviceState_Name(device.GetState()).c_str(), + EDeviceState_Name(newState).c_str()); + + auto actor = NCloud::Register( + ctx, + SelfId(), + TabletID(), + std::move(requestInfo), + deviceUUID, + newState, + device.GetState()); + + Actors.insert(actor); +} + +} // namespace NCloud::NBlockStore::NStorage diff --git a/cloud/blockstore/libs/storage/disk_registry/disk_registry_actor_monitoring.cpp b/cloud/blockstore/libs/storage/disk_registry/disk_registry_actor_monitoring.cpp index fee2825330e..1cb6486f6e8 100644 --- a/cloud/blockstore/libs/storage/disk_registry/disk_registry_actor_monitoring.cpp +++ b/cloud/blockstore/libs/storage/disk_registry/disk_registry_actor_monitoring.cpp @@ -103,6 +103,56 @@ void BuildDeviceReplaceButton( ); } +void BuildChangeDeviceStateButton( + IOutputStream& out, + ui64 tabletId, + const TString& deviceUUID) +{ + out << Sprintf( + R"(
+
+ + + + + + +
)", + deviceUUID.c_str(), + EDeviceState_Name(NProto::DEVICE_STATE_ONLINE).c_str(), + EDeviceState_Name(NProto::DEVICE_STATE_WARNING).c_str(), + deviceUUID.c_str(), + tabletId); +} + +void BuildChangeAgentStateButton( + IOutputStream& out, + ui64 tabletId, + const TString& agentId) +{ + out << Sprintf( + R"(
+
+ + + + + + +
)", + agentId.c_str(), + EAgentState_Name(NProto::AGENT_STATE_ONLINE).c_str(), + EAgentState_Name(NProto::AGENT_STATE_WARNING).c_str(), + agentId.c_str(), + tabletId); +} + void GenerateDiskRegistryActionsJS(IOutputStream& out) { out << R"html( @@ -462,6 +512,20 @@ void TDiskRegistryActor::RenderDeviceHtmlInfo( } DIV() { out << "State Message: " << device.GetStateMessage(); } + if (Config->GetEnableToChangeStatesFromDiskRegistryMonpage()) { + if (device.GetState() != NProto::EDeviceState::DEVICE_STATE_ERROR || + Config->GetEnableToChangeErrorStatesFromDiskRegistryMonpage()) + { + DIV() + { + BuildChangeDeviceStateButton( + out, + TabletID(), + device.GetDeviceUUID()); + } + } + } + if (auto diskId = State->FindDisk(id)) { DIV() { out << "Disk: "; @@ -532,6 +596,19 @@ void TDiskRegistryActor::RenderAgentHtmlInfo( out << "State Timestamp: " << TInstant::MicroSeconds(agent->GetStateTs()); } + DIV() { + if (Config->GetEnableToChangeStatesFromDiskRegistryMonpage()) { + if (agent->GetState() != + NProto::EAgentState::AGENT_STATE_UNAVAILABLE) + { + BuildChangeAgentStateButton( + out, + TabletID(), + agent->GetAgentId()); + } + } + + } DIV() { out << "State Message: " << agent->GetStateMessage(); } DIV() { out << "CMS Timestamp: " @@ -2253,9 +2330,13 @@ void TDiskRegistryActor::HandleHttpInfo( using THttpHandlers = THashMap; - static const THttpHandlers postActions {{ - {"volumeRealloc", &TDiskRegistryActor::HandleHttpInfo_VolumeRealloc }, - {"replaceDevice", &TDiskRegistryActor::HandleHttpInfo_ReplaceDevice }, + static const THttpHandlers postActions{{ + {"volumeRealloc", &TDiskRegistryActor::HandleHttpInfo_VolumeRealloc}, + {"replaceDevice", &TDiskRegistryActor::HandleHttpInfo_ReplaceDevice}, + {"changeDeviceState", + &TDiskRegistryActor::HandleHttpInfo_ChangeDeviseState}, + {"changeAgentState", + &TDiskRegistryActor::HandleHttpInfo_ChangeAgentState}, }}; static const THttpHandlers getActions {{ diff --git a/cloud/blockstore/libs/storage/disk_registry/ya.make b/cloud/blockstore/libs/storage/disk_registry/ya.make index 2428d312d23..2a0546479e2 100644 --- a/cloud/blockstore/libs/storage/disk_registry/ya.make +++ b/cloud/blockstore/libs/storage/disk_registry/ya.make @@ -4,6 +4,8 @@ SRCS( disk_registry_actor_acquire.cpp disk_registry_actor_allocate.cpp disk_registry_actor_backup_state.cpp + disk_registry_actor_change_agent_state.cpp + disk_registry_actor_change_device_state.cpp disk_registry_actor_change_disk_device.cpp disk_registry_actor_checkpoint.cpp disk_registry_actor_remove_orphan_devices.cpp diff --git a/cloud/blockstore/tests/monitoring/test.py b/cloud/blockstore/tests/monitoring/test.py index 2b4ee7a1371..ed1d2fb4758 100644 --- a/cloud/blockstore/tests/monitoring/test.py +++ b/cloud/blockstore/tests/monitoring/test.py @@ -13,6 +13,7 @@ TServerAppConfig, TKikimrServiceConfig from cloud.blockstore.config.storage_pb2 import TStorageServiceConfig from cloud.blockstore.tests.python.lib.nbs_runner import LocalNbs +from cloud.blockstore.tests.python.lib.client import NbsClient from cloud.blockstore.tests.python.lib.nonreplicated_setup import \ setup_nonreplicated, create_file_devices, \ setup_disk_registry_config_simple, enable_writable_state @@ -409,6 +410,93 @@ def check_replacedevice(self): self.session, self.base_url, self.dr_id, params, "

Replace device is not allowed

") + def check_change_device_state(self): + params = {"action": "changeDeviceState"} + check_tablet_get_redirect( + self.session, self.base_url, + self.dr_id, params, "

Wrong HTTP method

") + check_tablet_post_redirect( + self.session, self.base_url, + self.dr_id, params, "

No new state is given

") + + params = { + "action": "changeDeviceState", + "NewState" : "DEVICE_STATE_ONLINE", + } + check_tablet_post_redirect( + self.session, self.base_url, + self.dr_id, params, "

No device id is given

") + + params = { + "action": "changeDeviceState", + "NewState" : "not a state", + "DeviceUUID": "FileDevice-1" + } + check_tablet_post_redirect( + self.session, self.base_url, + self.dr_id, params, "

Invalid new state

") + + params = { + "action": "changeDeviceState", + "NewState" : "DEVICE_STATE_WARNING", + "DeviceUUID": "FileDevice-1" + } + + check_tablet_post_redirect( + self.session, self.base_url, + self.dr_id, params, "

Operation successfully completed

") + + state = self.client.backup_disk_registry_state() + + for agent in state["Backup"]["Agents"]: + for device in agent["Devices"]: + if device["DeviceUUID"] == "FileDevice-1": + assert device["State"] == "DEVICE_STATE_WARNING" + + def check_change_agent_state(self): + params = {"action": "changeAgentState"} + check_tablet_get_redirect( + self.session, self.base_url, + self.dr_id, params, "

Wrong HTTP method

") + check_tablet_post_redirect( + self.session, self.base_url, + self.dr_id, params, "

No new state is given

") + + params = { + "action": "changeAgentState", + "NewState" : "AGENT_STATE_ONLINE", + } + check_tablet_post_redirect( + self.session, self.base_url, + self.dr_id, params, "

No agent id is given

") + + params = { + "action": "changeAgentState", + "NewState" : "not a state", + "AgentID": "localhost" + } + check_tablet_post_redirect( + self.session, self.base_url, + self.dr_id, params, "

Invalid new state

") + + params = { + "action": "changeAgentState", + "NewState" : "AGENT_STATE_WARNING", + "AgentID": "localhost" + } + + check_tablet_post_redirect( + self.session, self.base_url, + self.dr_id, params, "

Operation successfully completed

") + + state = self.client.backup_disk_registry_state() + + for agent in state["Backup"]["Agents"]: + if agent["AgentId"] == "localhost": + assert agent["State"] == "AGENT_STATE_WARNING" + + self.client.change_agent_state("localhost", "0") + def check_showdisk(self): params = {"action": "disk"} check_tablet_get_redirect( @@ -447,8 +535,12 @@ def run(self, nbs, nbs_http_port): self.dr_id = nbs.get_dr_tablet_id() logging.info("disk registry tablet %s" % self.dr_id) + self.client = NbsClient(nbs.nbs_port) + self.check_mainpage() self.check_replacedevice() + self.check_change_device_state() + self.check_change_agent_state() self.check_reallocate() self.check_showdisk() self.check_showdevice() @@ -773,6 +865,9 @@ def __run_test(test_case): storage.NonReplicatedAgentMaxTimeout = 3000 storage.NonReplicatedDiskRecyclingPeriod = 5000 + storage.EnableToChangeStatesFromDiskRegistryMonpage = True + storage.EnableToChangeErrorStatesFromDiskRegistryMonpage = True + nbs = Nbs( kikimr_port, configurator.domains_txt, diff --git a/cloud/blockstore/tests/python/lib/client.py b/cloud/blockstore/tests/python/lib/client.py index e3844bd4a10..783a1a8b983 100644 --- a/cloud/blockstore/tests/python/lib/client.py +++ b/cloud/blockstore/tests/python/lib/client.py @@ -173,3 +173,17 @@ def get_storage_service_config(self, disk_id=None, timeout=300): resp = self.__execute_action('getstorageconfig', req, timeout) return json.loads(resp) + + def change_device_state(self, device_uuid, state, timeout=300): + req = {"ChangeDeviceState": {"DeviceUUID": device_uuid, "State": state}, "Message": "XXX"} + + resp = self.__execute_action("diskregistrychangestate", req, timeout) + + return json.loads(resp) + + def change_agent_state(self, agent_id, state, timeout=300): + req = {"ChangeAgentState": {"AgentId": agent_id, "State": state}, "Message": "XXX"} + + resp = self.__execute_action("diskregistrychangestate", req, timeout) + + return json.loads(resp)