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 6b50bb12c03..9fc073260f9 100644 --- a/cloud/blockstore/libs/storage/disk_registry/disk_registry_actor.h +++ b/cloud/blockstore/libs/storage/disk_registry/disk_registry_actor.h @@ -90,7 +90,7 @@ class TDiskRegistryActor final bool UsersNotificationInProgress = false; bool DiskStatesPublicationInProgress = false; bool AutomaticallyReplacedDevicesDeletionInProgress = false; - bool SecureEraseInProgress = false; + THashSet SecureEraseInProgressPerPool; bool StartMigrationInProgress = false; TVector DisksBeingDestroyed; diff --git a/cloud/blockstore/libs/storage/disk_registry/disk_registry_actor_secure_erase.cpp b/cloud/blockstore/libs/storage/disk_registry/disk_registry_actor_secure_erase.cpp index 251df41693c..48beadfc001 100644 --- a/cloud/blockstore/libs/storage/disk_registry/disk_registry_actor_secure_erase.cpp +++ b/cloud/blockstore/libs/storage/disk_registry/disk_registry_actor_secure_erase.cpp @@ -24,6 +24,7 @@ class TSecureEraseActor final const TRequestInfoPtr Request; const TDuration RequestTimeout; + const TString PoolName; TVector Devices; TVector CleanDevices; @@ -34,6 +35,7 @@ class TSecureEraseActor final const TActorId& owner, TRequestInfoPtr request, TDuration requestTimeout, + TString poolName, TVector devicesToClean); void Bootstrap(const TActorContext& ctx); @@ -73,10 +75,12 @@ TSecureEraseActor::TSecureEraseActor( const TActorId& owner, TRequestInfoPtr request, TDuration requestTimeout, + TString poolName, TVector devicesToClean) : Owner(owner) , Request(std::move(request)) , RequestTimeout(requestTimeout) + , PoolName(std::move(poolName)) , Devices(std::move(devicesToClean)) {} @@ -113,6 +117,7 @@ void TSecureEraseActor::ReplyAndDie(const TActorContext& ctx, NProto::TError err { auto response = std::make_unique( std::move(error), + PoolName, CleanDevices.size()); NCloud::Reply(ctx, *Request, std::move(response)); @@ -273,16 +278,9 @@ void TDiskRegistryActor::ExecuteCleanupDevices( TTransactionContext& tx, TTxDiskRegistry::TCleanupDevices& args) { - Y_UNUSED(ctx); - TDiskRegistryDatabase db(tx.DB); - - for (const auto& uuid: args.Devices) { - auto diskId = State->MarkDeviceAsClean(ctx.Now(), db, uuid); - if (diskId) { - args.SyncDeallocatedDisks.push_back(std::move(diskId)); - } - } + args.SyncDeallocatedDisks = + State->MarkDevicesAsClean(ctx.Now(), db, args.Devices); } void TDiskRegistryActor::CompleteCleanupDevices( @@ -301,10 +299,6 @@ void TDiskRegistryActor::CompleteCleanupDevices( void TDiskRegistryActor::SecureErase(const TActorContext& ctx) { - if (SecureEraseInProgress) { - return; - } - auto dirtyDevices = State->GetDirtyDevices(); EraseIf(dirtyDevices, [&] (auto& d) { if (d.GetState() == NProto::DEVICE_STATE_ERROR) { @@ -374,30 +368,55 @@ void TDiskRegistryActor::SecureErase(const TActorContext& ctx) countBeforeFiltration, dirtyDevices.size()); - SecureEraseInProgress = true; - - auto request = std::make_unique( - std::move(dirtyDevices), - Config->GetNonReplicatedSecureEraseTimeout()); - - auto deadline = Min(SecureEraseStartTs, ctx.Now()) + TDuration::Seconds(5); - if (deadline > ctx.Now()) { - LOG_INFO(ctx, TBlockStoreComponents::DISK_REGISTRY, - "[%lu] Scheduled secure erase, now: %lu, deadline: %lu", - TabletID(), - ctx.Now().MicroSeconds(), - deadline.MicroSeconds()); - - ctx.ExecutorThread.Schedule( - deadline, - new IEventHandle(ctx.SelfID, ctx.SelfID, request.get())); - request.release(); - } else { - LOG_INFO(ctx, TBlockStoreComponents::DISK_REGISTRY, - "[%lu] Sending secure erase request", - TabletID()); + auto it = dirtyDevices.begin(); + while (it != dirtyDevices.end()) { + auto first = it; + const auto poolName = first->GetPoolName(); + it = std::partition( + first, + dirtyDevices.end(), + [&poolName](const auto& device) + { return poolName == device.GetPoolName(); }); + + auto [_, alreadyInProgress] = + SecureEraseInProgressPerPool.insert(poolName); + if (!alreadyInProgress) { + continue; + } - NCloud::Send(ctx, ctx.SelfID, std::move(request)); + auto request = + std::make_unique( + poolName, + TVector( + std::make_move_iterator(first), + std::make_move_iterator(it)), + Config->GetNonReplicatedSecureEraseTimeout()); + + auto deadline = + Min(SecureEraseStartTs, ctx.Now()) + TDuration::Seconds(5); + if (deadline > ctx.Now()) { + LOG_INFO( + ctx, + TBlockStoreComponents::DISK_REGISTRY, + "[%lu] Scheduled secure erase for pool: %s, now: %lu, " + "deadline: %lu", + TabletID(), + poolName.c_str(), + ctx.Now().MicroSeconds(), + deadline.MicroSeconds()); + + ctx.ExecutorThread.Schedule( + deadline, + new IEventHandle(ctx.SelfID, ctx.SelfID, request.release())); + } else { + LOG_INFO( + ctx, + TBlockStoreComponents::DISK_REGISTRY, + "[%lu] Sending secure erase request", + TabletID()); + + NCloud::Send(ctx, ctx.SelfID, std::move(request)); + } } } @@ -425,6 +444,7 @@ void TDiskRegistryActor::HandleSecureErase( msg->CallContext ), msg->RequestTimeout, + msg->PoolName, std::move(msg->DirtyDevices)); Actors.insert(actor); } @@ -440,7 +460,7 @@ void TDiskRegistryActor::HandleSecureEraseResponse( TabletID(), msg->CleanDevices); - SecureEraseInProgress = false; + SecureEraseInProgressPerPool.erase(msg->PoolName); SecureErase(ctx); } diff --git a/cloud/blockstore/libs/storage/disk_registry/disk_registry_actor_writable_state.cpp b/cloud/blockstore/libs/storage/disk_registry/disk_registry_actor_writable_state.cpp index df481c96364..bbe30e89bf1 100644 --- a/cloud/blockstore/libs/storage/disk_registry/disk_registry_actor_writable_state.cpp +++ b/cloud/blockstore/libs/storage/disk_registry/disk_registry_actor_writable_state.cpp @@ -96,7 +96,7 @@ void TDiskRegistryActor::CompleteWritableState( DisksNotificationInProgress = false; UsersNotificationInProgress = false; DiskStatesPublicationInProgress = false; - SecureEraseInProgress = false; + SecureEraseInProgressPerPool.clear(); StartMigrationInProgress = false; } diff --git a/cloud/blockstore/libs/storage/disk_registry/disk_registry_private.h b/cloud/blockstore/libs/storage/disk_registry/disk_registry_private.h index 47bcc1dfe2f..366acfdddfd 100644 --- a/cloud/blockstore/libs/storage/disk_registry/disk_registry_private.h +++ b/cloud/blockstore/libs/storage/disk_registry/disk_registry_private.h @@ -267,25 +267,32 @@ struct TEvDiskRegistryPrivate struct TSecureEraseRequest { + TString PoolName; TVector DirtyDevices; TDuration RequestTimeout; - explicit TSecureEraseRequest( + TSecureEraseRequest( + TString poolName, TVector dirtyDevices, TDuration requestTimeout) - : DirtyDevices(std::move(dirtyDevices)) + : PoolName(std::move(poolName)) + , DirtyDevices(std::move(dirtyDevices)) , RequestTimeout(requestTimeout) {} }; struct TSecureEraseResponse { + TString PoolName; size_t CleanDevices = 0; TSecureEraseResponse() = default; - explicit TSecureEraseResponse(size_t cleanDevices) - : CleanDevices(cleanDevices) + TSecureEraseResponse( + TString poolName, + size_t cleanDevices) + : PoolName(std::move(poolName)) + , CleanDevices(cleanDevices) {} }; diff --git a/cloud/blockstore/libs/storage/disk_registry/disk_registry_state.cpp b/cloud/blockstore/libs/storage/disk_registry/disk_registry_state.cpp index ffa56cae87b..386db8383a6 100644 --- a/cloud/blockstore/libs/storage/disk_registry/disk_registry_state.cpp +++ b/cloud/blockstore/libs/storage/disk_registry/disk_registry_state.cpp @@ -3601,21 +3601,37 @@ bool TDiskRegistryState::MarkDeviceAsDirty( return true; } -TString TDiskRegistryState::MarkDeviceAsClean( +TDiskRegistryState::TDiskId TDiskRegistryState::MarkDeviceAsClean( TInstant now, TDiskRegistryDatabase& db, const TDeviceId& uuid) { - DeviceList.MarkDeviceAsClean(uuid); - db.DeleteDirtyDevice(uuid); + auto ret = MarkDevicesAsClean(now, db, TVector{uuid}); + return ret.empty() ? "" : ret[0]; +} - if (!DeviceList.IsSuspendedDevice(uuid)) { - db.DeleteSuspendedDevice(uuid); +TVector TDiskRegistryState::MarkDevicesAsClean( + TInstant now, + TDiskRegistryDatabase& db, + const TVector& uuids) +{ + for (const auto& uuid: uuids) { + DeviceList.MarkDeviceAsClean(uuid); + db.DeleteDirtyDevice(uuid); + + if (!DeviceList.IsSuspendedDevice(uuid)) { + db.DeleteSuspendedDevice(uuid); + } } - TryUpdateDevice(now, db, uuid); + TVector ret; + for (const auto& uuid: TryUpdateDevices(now, db, uuids)) { + if (auto diskId = PendingCleanup.EraseDevice(uuid); !diskId.empty()) { + ret.push_back(std::move(diskId)); + } + } - return PendingCleanup.EraseDevice(uuid); + return ret; } bool TDiskRegistryState::TryUpdateDevice( @@ -3623,19 +3639,38 @@ bool TDiskRegistryState::TryUpdateDevice( TDiskRegistryDatabase& db, const TDeviceId& uuid) { - Y_UNUSED(now); + return !TryUpdateDevices(now, db, {uuid}).empty(); +} - auto [agent, device] = FindDeviceLocation(uuid); - if (!agent || !device) { - return false; - } +TVector TDiskRegistryState::TryUpdateDevices( + TInstant now, + TDiskRegistryDatabase& db, + const TVector& uuids) +{ + TVector ret; + ret.reserve(uuids.size()); - AdjustDeviceIfNeeded(*device, {}); + TSet agentsSet; + for (const auto& uuid: uuids) { + auto [agent, device] = FindDeviceLocation(uuid); + if (!agent || !device) { + continue; + } + ret.push_back(uuid); + agentsSet.emplace(agent->GetAgentId()); + AdjustDeviceIfNeeded(*device, now); + } - UpdateAgent(db, *agent); - DeviceList.UpdateDevices(*agent, DevicePoolConfigs); + for (const auto& agentId: agentsSet) { + auto* agent = AgentList.FindAgent(agentId); + if (!agent) { + continue; + } + UpdateAgent(db, *agent); + DeviceList.UpdateDevices(*agent, DevicePoolConfigs); + } - return true; + return ret; } TVector TDiskRegistryState::CollectBrokenDevices( diff --git a/cloud/blockstore/libs/storage/disk_registry/disk_registry_state.h b/cloud/blockstore/libs/storage/disk_registry/disk_registry_state.h index 88f9b4a951f..8386f958d70 100644 --- a/cloud/blockstore/libs/storage/disk_registry/disk_registry_state.h +++ b/cloud/blockstore/libs/storage/disk_registry/disk_registry_state.h @@ -493,10 +493,22 @@ class TDiskRegistryState TVector GetBrokenDevices() const; TVector GetDirtyDevices() const; - TString MarkDeviceAsClean( + + /// Mark selected device as clean and remove it + /// from lists of suspended/dirty/pending cleanup devices + /// @return disk id where selected device was allocated + TDiskId MarkDeviceAsClean( TInstant now, TDiskRegistryDatabase& db, const TDeviceId& uuid); + + /// Mark selected devices as clean and remove them + /// from lists of suspended/dirty/pending cleanup devices + /// @return vector of disk ids where selected devices were allocated + TVector MarkDevicesAsClean( + TInstant now, + TDiskRegistryDatabase& db, + const TVector& uuids); bool MarkDeviceAsDirty(TDiskRegistryDatabase& db, const TDeviceId& uuid); NProto::TError CreatePlacementGroup( @@ -1123,11 +1135,22 @@ class TDiskRegistryState TDiskRegistryDatabase& db, const TString& diskId); + /// Try to update configuration of selected device and its agent + /// in the disk registry database + /// @return true if the device updates successfully; otherwise, return false bool TryUpdateDevice( TInstant now, TDiskRegistryDatabase& db, const TDeviceId& uuid); + /// Try to update configuration of selected devices and their agents + /// in the disk registry database + /// @return List of updated devices + TVector TryUpdateDevices( + TInstant now, + TDiskRegistryDatabase& db, + const TVector& uuids); + TDeviceList::TAllocationQuery MakeMigrationQuery( const TDiskId& sourceDiskId, const NProto::TDeviceConfig& sourceDevice); diff --git a/cloud/blockstore/libs/storage/disk_registry/disk_registry_state_ut.cpp b/cloud/blockstore/libs/storage/disk_registry/disk_registry_state_ut.cpp index a1265d03510..536a498389c 100644 --- a/cloud/blockstore/libs/storage/disk_registry/disk_registry_state_ut.cpp +++ b/cloud/blockstore/libs/storage/disk_registry/disk_registry_state_ut.cpp @@ -1271,11 +1271,13 @@ Y_UNIT_TEST_SUITE(TDiskRegistryStateTest) Device("dev-3", "uuid-7", "rack-1") }); - TDiskRegistryState state = TDiskRegistryStateBuilder() - .WithKnownAgents({ agent1, agent2 }) - .WithDisks({ Disk("disk-1", {"uuid-1"}) }) - .WithDirtyDevices({ "uuid-4", "uuid-7" }) - .Build(); + TDiskRegistryState state = + TDiskRegistryStateBuilder() + .WithKnownAgents({agent1, agent2}) + .WithDisks({Disk("disk-1", {"uuid-1"})}) + .WithDirtyDevices( + {TDirtyDevice{"uuid-4", {}}, TDirtyDevice{"uuid-7", {}}}) + .Build(); executor.WriteTx([&] (TDiskRegistryDatabase db) { TVector devices; @@ -8292,18 +8294,29 @@ Y_UNIT_TEST_SUITE(TDiskRegistryStateTest) }), }; - TDiskRegistryState state = TDiskRegistryStateBuilder() - .WithKnownAgents(agents) - .WithDisks({ - Disk("disk-1", {"uuid-1.1"}), - Disk("disk-2", {"uuid-2.2"}), - Disk("disk-3", {"uuid-3.4", "uuid-3.5"}), - Disk("disk-4", {"uuid-4.1"}), - }) - .WithDirtyDevices({"uuid-2.1", "uuid-3.3", "uuid-3.6", "uuid-5.2"}) - .AddDevicePoolConfig("local-ssd", 10_GB, NProto::DEVICE_POOL_KIND_LOCAL) - .AddDevicePoolConfig("rot", 10_GB, NProto::DEVICE_POOL_KIND_GLOBAL) - .Build(); + TDiskRegistryState state = + TDiskRegistryStateBuilder() + .WithKnownAgents(agents) + .WithDisks({ + Disk("disk-1", {"uuid-1.1"}), + Disk("disk-2", {"uuid-2.2"}), + Disk("disk-3", {"uuid-3.4", "uuid-3.5"}), + Disk("disk-4", {"uuid-4.1"}), + }) + .WithDirtyDevices( + {TDirtyDevice{"uuid-2.1", {}}, + TDirtyDevice{"uuid-3.3", {}}, + TDirtyDevice{"uuid-3.6", {}}, + TDirtyDevice{"uuid-5.2", {}}}) + .AddDevicePoolConfig( + "local-ssd", + 10_GB, + NProto::DEVICE_POOL_KIND_LOCAL) + .AddDevicePoolConfig( + "rot", + 10_GB, + NProto::DEVICE_POOL_KIND_GLOBAL) + .Build(); const auto poolNames = state.GetPoolNames(); ASSERT_VECTORS_EQUAL( @@ -11535,6 +11548,84 @@ Y_UNIT_TEST_SUITE(TDiskRegistryStateTest) UNIT_ASSERT_VALUES_EQUAL(errorMessage, d.GetStateMessage()); }); } + + Y_UNIT_TEST(ShouldCleanMultipleDevicesFromOneDisk) + { + TTestExecutor executor; + executor.WriteTx([&](TDiskRegistryDatabase db) { db.InitSchema(); }); + + const auto agent1 = AgentConfig( + 1, + {Device("dev-1", "uuid-1", "rack-1")}); + + const auto agent2 = AgentConfig( + 2, + {Device("dev-2", "uuid-2", "rack-1")}); + + TDiskRegistryState state = + TDiskRegistryStateBuilder() + .WithKnownAgents({agent1, agent2}) + .WithDisks({Disk("disk-1", {"uuid-1", "uuid-2"})}) + .WithDirtyDevices( + {TDirtyDevice{"uuid-1", "disk-1"}, + TDirtyDevice{"uuid-2", "disk-1"}}) + .Build(); + + UNIT_ASSERT_EQUAL(state.GetDirtyDevices().size(), 2); + + executor.WriteTx( + [&](TDiskRegistryDatabase db) + { + const auto& cleanDisks = state.MarkDevicesAsClean( + Now(), + db, + TVector{"uuid-1", "uuid-2"}); + UNIT_ASSERT_EQUAL(cleanDisks.size(), 1); + UNIT_ASSERT_EQUAL(cleanDisks[0], "disk-1"); + }); + + UNIT_ASSERT(state.GetDirtyDevices().empty()); + } + + Y_UNIT_TEST(ShouldCleanMultipleDevicesFromDifferentDisks) + { + TTestExecutor executor; + executor.WriteTx([&](TDiskRegistryDatabase db) { db.InitSchema(); }); + + const auto agent1 = AgentConfig( + 1, + {Device("dev-1", "uuid-1", "rack-1")}); + + const auto agent2 = AgentConfig( + 2, + {Device("dev-2", "uuid-2", "rack-1")}); + + TDiskRegistryState state = + TDiskRegistryStateBuilder() + .WithKnownAgents({agent1, agent2}) + .WithDisks( + {Disk("disk-1", {"uuid-1"}), Disk("disk-1", {"uuid-2"})}) + .WithDirtyDevices( + {TDirtyDevice{"uuid-1", "disk-1"}, + TDirtyDevice{"uuid-2", "disk-2"}}) + .Build(); + + UNIT_ASSERT_EQUAL(state.GetDirtyDevices().size(), 2); + + executor.WriteTx( + [&](TDiskRegistryDatabase db) + { + const auto& cleanDisks = state.MarkDevicesAsClean( + Now(), + db, + TVector{"uuid-1", "uuid-2"}); + UNIT_ASSERT_EQUAL(cleanDisks.size(), 2); + UNIT_ASSERT_EQUAL(cleanDisks[0], "disk-1"); + UNIT_ASSERT_EQUAL(cleanDisks[1], "disk-2"); + }); + + UNIT_ASSERT(state.GetDirtyDevices().empty()); + } } } // namespace NCloud::NBlockStore::NStorage diff --git a/cloud/blockstore/libs/storage/disk_registry/disk_registry_state_ut_agents_info.cpp b/cloud/blockstore/libs/storage/disk_registry/disk_registry_state_ut_agents_info.cpp index 0c0a7b7db57..e0651ab5464 100644 --- a/cloud/blockstore/libs/storage/disk_registry/disk_registry_state_ut_agents_info.cpp +++ b/cloud/blockstore/libs/storage/disk_registry/disk_registry_state_ut_agents_info.cpp @@ -110,7 +110,7 @@ Y_UNIT_TEST_SUITE(TDiskRegistryStateQueryAgentsInfoTest) return config; }()) .WithAgents(agents) - .WithDirtyDevices({"uuid-1.1"}) + .WithDirtyDevices({TDirtyDevice{"uuid-1.1", {}}}) .Build(); auto agentsInfo = state.QueryAgentsInfo(); diff --git a/cloud/blockstore/libs/storage/disk_registry/disk_registry_state_ut_create.cpp b/cloud/blockstore/libs/storage/disk_registry/disk_registry_state_ut_create.cpp index dece6807d36..87db2992bae 100644 --- a/cloud/blockstore/libs/storage/disk_registry/disk_registry_state_ut_create.cpp +++ b/cloud/blockstore/libs/storage/disk_registry/disk_registry_state_ut_create.cpp @@ -59,11 +59,12 @@ Y_UNIT_TEST_SUITE(TDiskRegistryStateCreateTest) }) }; - TDiskRegistryState state = TDiskRegistryStateBuilder() - .WithKnownAgents(agents) - .WithDisks({ Disk("disk-1", {"uuid-1.1"}) }) - .WithDirtyDevices({"uuid-2.1"}) - .Build(); + TDiskRegistryState state = + TDiskRegistryStateBuilder() + .WithKnownAgents(agents) + .WithDisks({Disk("disk-1", {"uuid-1.1"})}) + .WithDirtyDevices({TDirtyDevice{"uuid-2.1", {}}}) + .Build(); auto deviceByName = [] (auto agentId, auto name) { NProto::TDeviceConfig config; diff --git a/cloud/blockstore/libs/storage/disk_registry/disk_registry_state_ut_migration.cpp b/cloud/blockstore/libs/storage/disk_registry/disk_registry_state_ut_migration.cpp index b5ac3c6c9af..1939f31d670 100644 --- a/cloud/blockstore/libs/storage/disk_registry/disk_registry_state_ut_migration.cpp +++ b/cloud/blockstore/libs/storage/disk_registry/disk_registry_state_ut_migration.cpp @@ -66,14 +66,18 @@ Y_UNIT_TEST_SUITE(TDiskRegistryStateMigrationTest) }) }; - TDiskRegistryState state = TDiskRegistryStateBuilder() - .WithKnownAgents(agents) - .WithDisks({ - Disk("foo", { "uuid-1.1", "uuid-1.2" }), // rack-1 - Disk("bar", { "uuid-2.1" }) // rack-2 - }) - .WithDirtyDevices({"uuid-4.1", "uuid-4.2", "uuid-4.3"}) - .Build(); + TDiskRegistryState state = + TDiskRegistryStateBuilder() + .WithKnownAgents(agents) + .WithDisks({ + Disk("foo", {"uuid-1.1", "uuid-1.2"}), // rack-1 + Disk("bar", {"uuid-2.1"}) // rack-2 + }) + .WithDirtyDevices( + {TDirtyDevice{"uuid-4.1", {}}, + TDirtyDevice{"uuid-4.2", {}}, + TDirtyDevice{"uuid-4.3", {}}}) + .Build(); UNIT_ASSERT(state.IsMigrationListEmpty()); diff --git a/cloud/blockstore/libs/storage/disk_registry/disk_registry_ut_lifecycle.cpp b/cloud/blockstore/libs/storage/disk_registry/disk_registry_ut_lifecycle.cpp index c553d8f0bac..d0cc57ef582 100644 --- a/cloud/blockstore/libs/storage/disk_registry/disk_registry_ut_lifecycle.cpp +++ b/cloud/blockstore/libs/storage/disk_registry/disk_registry_ut_lifecycle.cpp @@ -2219,6 +2219,263 @@ Y_UNIT_TEST_SUITE(TDiskRegistryTest) UNIT_ASSERT_EQUAL(E_NOT_FOUND, response->Record.GetError().GetCode()); } } + + Y_UNIT_TEST(ShouldSecureEraseDevicesFromDifferentPools) + { + // 1 .Create devices from two different pools with pool kind + // DEVICE_POOL_KIND_LOCAL. Devices are created in the suspended state. + // 2. Set observer for EvCleanupDevicesRequest which check that + // all devices from one message belong to the same pool. + // 3. Call ResumeDevice for all devices. Internally ResumeDevice + // triggers SecureErase. + + const TString poolName1 = "pool-1"; + const auto withPool1 = + WithPool(poolName1, NProto::DEVICE_POOL_KIND_GLOBAL); + auto agent1 = CreateAgentConfig( + "agent-1", + {Device("dev-1", "uuid-1", "rack-1", 10_GB) | withPool1, + Device("dev-2", "uuid-2", "rack-1", 10_GB) | withPool1, + Device("dev-3", "uuid-3", "rack-1", 10_GB) | withPool1}); + + const TString poolName2 = "pool-2"; + const auto withPool2 = + WithPool(poolName2, NProto::DEVICE_POOL_KIND_GLOBAL); + auto agent2 = CreateAgentConfig( + "agent-2", + {Device("dev-4", "uuid-4", "rack-1", 10_GB) | withPool2, + Device("dev-5", "uuid-5", "rack-1", 10_GB) | withPool2, + Device("dev-6", "uuid-6", "rack-1", 10_GB) | withPool2}); + + auto runtime = + TTestRuntimeBuilder().WithAgents({agent1, agent2}).Build(); + + TDiskRegistryClient diskRegistry(*runtime); + diskRegistry.WaitReady(); + diskRegistry.SetWritableState(true); + + diskRegistry.UpdateConfig( + [&] + { + auto config = CreateRegistryConfig(0, {agent1, agent2}); + + auto* pool1 = config.AddDevicePoolConfigs(); + pool1->SetName(poolName1); + pool1->SetKind(NProto::DEVICE_POOL_KIND_LOCAL); + pool1->SetAllocationUnit(10_GB); + + auto* pool2 = config.AddDevicePoolConfigs(); + pool2->SetName(poolName2); + pool2->SetKind(NProto::DEVICE_POOL_KIND_LOCAL); + pool2->SetAllocationUnit(10_GB); + + return config; + }()); + + RegisterAgents(*runtime, 2); + WaitForAgents(*runtime, 2); + + THashMap deviceUuidToPoolName = { + {"uuid-1", poolName1}, + {"uuid-2", poolName1}, + {"uuid-3", poolName1}, + {"uuid-4", poolName2}, + {"uuid-5", poolName2}, + {"uuid-6", poolName2}, + }; + + runtime->SetObserverFunc( + [&](TTestActorRuntimeBase& runtime, TAutoPtr& event) + { + switch (event->GetTypeRewrite()) { + case TEvDiskRegistryPrivate::EvCleanupDevicesRequest: { + auto& msg = *event->Get< + TEvDiskRegistryPrivate::TEvCleanupDevicesRequest>(); + TSet poolNameSet; + for (auto& device: msg.Devices) { + poolNameSet.insert(deviceUuidToPoolName[device]); + } + // each SecureErase actor handles devices only from one + // pool + UNIT_ASSERT_EQUAL(1, poolNameSet.size()); + break; + } + } + + return TTestActorRuntime::DefaultObserverFunc(runtime, event); + }); + + diskRegistry.ResumeDevice("agent-1", "dev-1", /*dryRun=*/false); + diskRegistry.ResumeDevice("agent-2", "dev-4", /*dryRun=*/false); + diskRegistry.ResumeDevice("agent-1", "dev-2", /*dryRun=*/false); + diskRegistry.ResumeDevice("agent-2", "dev-5", /*dryRun=*/false); + diskRegistry.ResumeDevice("agent-1", "dev-3", /*dryRun=*/false); + diskRegistry.ResumeDevice("agent-2", "dev-6", /*dryRun=*/false); + WaitForSecureErase(*runtime, 6); + } + + Y_UNIT_TEST(ShouldSecureEraseDevicesFromDifferentPoolsIndependently) + { + const TString defaultPool = ""; + const TString rotPool = "rot"; + const auto withRotPool = + WithPool(rotPool, NProto::DEVICE_POOL_KIND_GLOBAL); + + TVector agents{ + CreateAgentConfig( + "agent-1", + {Device("path", "uuid-1", "rack-1", 10_GB) | withRotPool, + Device("path", "uuid-2", "rack-1", 10_GB) | withRotPool, + Device("path", "uuid-3", "rack-1", 10_GB) | withRotPool}), + CreateAgentConfig( + "agent-2", + {Device("path", "uuid-4", "rack-1", 10_GB), + Device("path", "uuid-5", "rack-1", 10_GB), + Device("path", "uuid-6", "rack-1", 10_GB)})}; + + THashMap deviceUuidToPoolName = { + {"uuid-1", rotPool}, + {"uuid-2", rotPool}, + {"uuid-3", rotPool}, + {"uuid-4", defaultPool}, + {"uuid-5", defaultPool}, + {"uuid-6", defaultPool}, + }; + + auto runtime = + TTestRuntimeBuilder() + .With( + [] + { + auto config = CreateDefaultStorageConfig(); + + config + .SetMaxDevicesToErasePerDeviceNameForDefaultPoolKind( + 1); + config + .SetMaxDevicesToErasePerDeviceNameForGlobalPoolKind( + 1); + + return config; + }()) + .WithAgents(agents) + .Build(); + + TDiskRegistryClient diskRegistry(*runtime); + diskRegistry.WaitReady(); + diskRegistry.SetWritableState(true); + + diskRegistry.UpdateConfig( + [&] + { + auto config = CreateRegistryConfig(0, agents); + + auto* rot = config.AddDevicePoolConfigs(); + rot->SetName(rotPool); + rot->SetKind(NProto::DEVICE_POOL_KIND_GLOBAL); + rot->SetAllocationUnit(10_GB); + + return config; + }()); + + TVector> secureEraseRequests; + + runtime->SetObserverFunc( + [&](TTestActorRuntimeBase& runtime, TAutoPtr& event) + { + switch (event->GetTypeRewrite()) { + case TEvDiskAgent::EvSecureEraseDeviceRequest: { + secureEraseRequests.emplace_back(std::move(event)); + return TTestActorRuntime::EEventAction::DROP; + } + } + + return TTestActorRuntime::DefaultObserverFunc(runtime, event); + }); + + RegisterAgents(*runtime, 2); + WaitForAgents(*runtime, 2); + + runtime->DispatchEvents( + {.FinalEvents = {{TEvDiskAgent::EvSecureEraseDeviceRequest, 2}}}, + 15s); + UNIT_ASSERT_VALUES_EQUAL(2, secureEraseRequests.size()); + + auto getPoolName = [&](const auto& event) + { + const auto& msg = *event->template Get< + TEvDiskAgent::TEvSecureEraseDeviceRequest>(); + + return deviceUuidToPoolName[msg.Record.GetDeviceUUID()]; + }; + + SortBy(secureEraseRequests, getPoolName); + + UNIT_ASSERT_VALUES_EQUAL( + defaultPool, + getPoolName(secureEraseRequests[0])); + UNIT_ASSERT_VALUES_EQUAL(rotPool, getPoolName(secureEraseRequests[1])); + + runtime->DispatchEvents( + {.FinalEvents = {{TEvDiskAgent::EvSecureEraseDeviceRequest}}}, + 15s); + UNIT_ASSERT_VALUES_EQUAL(2, secureEraseRequests.size()); + + auto sendResponse = [&](auto event) + { + runtime->Send(new IEventHandle( + event->Sender, + event->Recipient, + new TEvDiskAgent::TEvSecureEraseDeviceResponse(), + 0, // flags + event->Cookie)); + }; + + // send the first response for the device from the default pool + sendResponse(std::move(secureEraseRequests[0])); + + runtime->DispatchEvents( + {.FinalEvents = {{TEvDiskAgent::EvSecureEraseDeviceRequest}}}, + 15s); + UNIT_ASSERT_VALUES_EQUAL(3, secureEraseRequests.size()); + UNIT_ASSERT_VALUES_EQUAL( + defaultPool, + getPoolName(secureEraseRequests[2])); + + runtime->DispatchEvents( + {.FinalEvents = {{TEvDiskAgent::EvSecureEraseDeviceRequest}}}, + 15s); + UNIT_ASSERT_VALUES_EQUAL(3, secureEraseRequests.size()); + + // send the second response for the device from the default pool + sendResponse(std::move(secureEraseRequests[2])); + + runtime->DispatchEvents( + {.FinalEvents = {{TEvDiskAgent::EvSecureEraseDeviceRequest}}}, + 15s); + UNIT_ASSERT_VALUES_EQUAL(4, secureEraseRequests.size()); + UNIT_ASSERT_VALUES_EQUAL( + defaultPool, + getPoolName(secureEraseRequests[3])); + + // send the third response for the device from the default pool + sendResponse(std::move(secureEraseRequests[3])); + runtime->DispatchEvents( + {.FinalEvents = {{TEvDiskAgent::EvSecureEraseDeviceRequest}}}, + 15s); + + // there are no new requests - all devices from the default pool are + // clean + UNIT_ASSERT_VALUES_EQUAL(4, secureEraseRequests.size()); + + // send the response for the device from the rot pool + sendResponse(std::move(secureEraseRequests[1])); + runtime->DispatchEvents( + {.FinalEvents = {{TEvDiskAgent::EvSecureEraseDeviceRequest}}}, + 15s); + UNIT_ASSERT_VALUES_EQUAL(5, secureEraseRequests.size()); + UNIT_ASSERT_VALUES_EQUAL(rotPool, getPoolName(secureEraseRequests[4])); + } } } // namespace NCloud::NBlockStore::NStorage diff --git a/cloud/blockstore/libs/storage/disk_registry/testlib/test_env.h b/cloud/blockstore/libs/storage/disk_registry/testlib/test_env.h index 0174a9e4a12..089130b5094 100644 --- a/cloud/blockstore/libs/storage/disk_registry/testlib/test_env.h +++ b/cloud/blockstore/libs/storage/disk_registry/testlib/test_env.h @@ -744,6 +744,7 @@ class TDiskRegistryClient TDuration requestTimeout = {}) { return std::make_unique( + TString{}, std::move(devices), requestTimeout); } diff --git a/cloud/blockstore/libs/storage/disk_registry/testlib/test_state.cpp b/cloud/blockstore/libs/storage/disk_registry/testlib/test_state.cpp index 65a37d4ab42..ff8215b53c6 100644 --- a/cloud/blockstore/libs/storage/disk_registry/testlib/test_state.cpp +++ b/cloud/blockstore/libs/storage/disk_registry/testlib/test_state.cpp @@ -561,16 +561,9 @@ TDiskRegistryStateBuilder& TDiskRegistryStateBuilder::WithDisks( } TDiskRegistryStateBuilder& TDiskRegistryStateBuilder::WithDirtyDevices( - TVector dirtyDevices) -{ - DirtyDevices.clear(); - DirtyDevices.reserve(dirtyDevices.size()); - for (auto& s: dirtyDevices) { - DirtyDevices.emplace_back(TDirtyDevice { - .Id = std::move(s) - }); - } - + TVector dirtyDevices) +{ + DirtyDevices = std::move(dirtyDevices); return *this; } diff --git a/cloud/blockstore/libs/storage/disk_registry/testlib/test_state.h b/cloud/blockstore/libs/storage/disk_registry/testlib/test_state.h index dceef794121..df8391064b7 100644 --- a/cloud/blockstore/libs/storage/disk_registry/testlib/test_state.h +++ b/cloud/blockstore/libs/storage/disk_registry/testlib/test_state.h @@ -276,7 +276,7 @@ struct TDiskRegistryStateBuilder TDiskRegistryStateBuilder& WithDisks(TVector disks); - TDiskRegistryStateBuilder& WithDirtyDevices(TVector dirtyDevices); + TDiskRegistryStateBuilder& WithDirtyDevices(TVector dirtyDevices); TDiskRegistryStateBuilder& WithSuspendedDevices( TVector suspendedDevices); diff --git a/cloud/filestore/apps/client/lib/CMakeLists.darwin-x86_64.txt b/cloud/filestore/apps/client/lib/CMakeLists.darwin-x86_64.txt index ffd094d7d35..ee45ed2066f 100644 --- a/cloud/filestore/apps/client/lib/CMakeLists.darwin-x86_64.txt +++ b/cloud/filestore/apps/client/lib/CMakeLists.darwin-x86_64.txt @@ -46,6 +46,7 @@ target_sources(filestore-apps-client-lib PRIVATE ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/reset_session.cpp ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/resize.cpp ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/rm.cpp + ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/set_node_attr.cpp ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/start_endpoint.cpp ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/stat.cpp ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/stop_endpoint.cpp diff --git a/cloud/filestore/apps/client/lib/CMakeLists.linux-aarch64.txt b/cloud/filestore/apps/client/lib/CMakeLists.linux-aarch64.txt index 37fa656ab35..9a990af5e93 100644 --- a/cloud/filestore/apps/client/lib/CMakeLists.linux-aarch64.txt +++ b/cloud/filestore/apps/client/lib/CMakeLists.linux-aarch64.txt @@ -47,6 +47,7 @@ target_sources(filestore-apps-client-lib PRIVATE ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/reset_session.cpp ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/resize.cpp ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/rm.cpp + ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/set_node_attr.cpp ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/start_endpoint.cpp ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/stat.cpp ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/stop_endpoint.cpp diff --git a/cloud/filestore/apps/client/lib/CMakeLists.linux-x86_64.txt b/cloud/filestore/apps/client/lib/CMakeLists.linux-x86_64.txt index 37fa656ab35..9a990af5e93 100644 --- a/cloud/filestore/apps/client/lib/CMakeLists.linux-x86_64.txt +++ b/cloud/filestore/apps/client/lib/CMakeLists.linux-x86_64.txt @@ -47,6 +47,7 @@ target_sources(filestore-apps-client-lib PRIVATE ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/reset_session.cpp ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/resize.cpp ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/rm.cpp + ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/set_node_attr.cpp ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/start_endpoint.cpp ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/stat.cpp ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/stop_endpoint.cpp diff --git a/cloud/filestore/apps/client/lib/CMakeLists.windows-x86_64.txt b/cloud/filestore/apps/client/lib/CMakeLists.windows-x86_64.txt index ffd094d7d35..ee45ed2066f 100644 --- a/cloud/filestore/apps/client/lib/CMakeLists.windows-x86_64.txt +++ b/cloud/filestore/apps/client/lib/CMakeLists.windows-x86_64.txt @@ -46,6 +46,7 @@ target_sources(filestore-apps-client-lib PRIVATE ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/reset_session.cpp ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/resize.cpp ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/rm.cpp + ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/set_node_attr.cpp ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/start_endpoint.cpp ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/stat.cpp ${CMAKE_SOURCE_DIR}/cloud/filestore/apps/client/lib/stop_endpoint.cpp diff --git a/cloud/filestore/apps/client/lib/create_session.cpp b/cloud/filestore/apps/client/lib/create_session.cpp index 575b6c8f4b3..6e6bd95dc40 100644 --- a/cloud/filestore/apps/client/lib/create_session.cpp +++ b/cloud/filestore/apps/client/lib/create_session.cpp @@ -8,6 +8,17 @@ namespace { //////////////////////////////////////////////////////////////////////////////// +void Print(const NProto::TCreateSessionResponse& response, bool jsonOutput) +{ + if (jsonOutput) { + Cout << response.AsJSON() << Endl; + } else { + Cout << response.DebugString() << Endl; + } +} + +//////////////////////////////////////////////////////////////////////////////// + class TCreateSessionCommand final : public TFileStoreCommand { @@ -42,6 +53,7 @@ class TCreateSessionCommand final TCallContextPtr ctx = MakeIntrusive(); auto response = WaitFor(Client->CreateSession(ctx, std::move(request))); CheckResponse(response); + Print(response, JsonOutput); return true; } diff --git a/cloud/filestore/apps/client/lib/factory.cpp b/cloud/filestore/apps/client/lib/factory.cpp index 84558562b43..1ee548bb5f5 100644 --- a/cloud/filestore/apps/client/lib/factory.cpp +++ b/cloud/filestore/apps/client/lib/factory.cpp @@ -32,6 +32,7 @@ TCommandPtr NewExecuteActionCommand(); TCommandPtr NewCreateSessionCommand(); TCommandPtr NewResetSessionCommand(); TCommandPtr NewStatCommand(); +TCommandPtr NewSetNodeAttrCommand(); //////////////////////////////////////////////////////////////////////////////// @@ -41,8 +42,10 @@ using TFactoryMap = TMap; static const TMap Commands = { { "addclusternode", NewAddClusterNodeCommand }, { "create", NewCreateCommand }, + { "createsession", NewCreateSessionCommand }, { "describe", NewDescribeCommand }, { "destroy", NewDestroyCommand }, + { "executeaction", NewExecuteActionCommand }, { "kickendpoint", NewKickEndpointCommand }, { "listclusternodes", NewListClusterNodesCommand }, { "listendpoints", NewListEndpointsCommand }, @@ -52,16 +55,15 @@ static const TMap Commands = { { "mount", NewMountCommand }, { "read", NewReadCommand }, { "removeclusternode", NewRemoveClusterNodeCommand }, + { "resetsession", NewResetSessionCommand }, { "resize", NewResizeCommand }, { "rm", NewRmCommand }, + { "setnodeattr", NewSetNodeAttrCommand }, { "startendpoint", NewStartEndpointCommand }, + { "stat", NewStatCommand }, { "stopendpoint", NewStopEndpointCommand }, { "touch", NewTouchCommand }, { "write", NewWriteCommand }, - { "executeaction", NewExecuteActionCommand }, - { "createsession", NewCreateSessionCommand }, - { "resetsession", NewResetSessionCommand }, - { "stat", NewStatCommand }, }; //////////////////////////////////////////////////////////////////////////////// diff --git a/cloud/filestore/apps/client/lib/set_node_attr.cpp b/cloud/filestore/apps/client/lib/set_node_attr.cpp new file mode 100644 index 00000000000..212f9ec579c --- /dev/null +++ b/cloud/filestore/apps/client/lib/set_node_attr.cpp @@ -0,0 +1,131 @@ +#include "command.h" + +#include + +#include + +#include +#include +#include + +namespace NCloud::NFileStore::NClient { + +namespace { + +//////////////////////////////////////////////////////////////////////////////// + +class TSetNodeAttrCommand final: public TFileStoreCommand +{ +private: + ui32 NodeId = 0; + std::optional ModeAttr; + std::optional UidAttr; + std::optional GidAttr; + std::optional SizeAttr; + std::optional ATimeAttr; + std::optional MTimeAttr; + std::optional CTimeAttr; + +public: + TSetNodeAttrCommand() + { + Opts.AddLongOption("node-id") + .Required() + .StoreResult(&NodeId); + + Opts.AddLongOption("mode") + .Optional() + .StoreResult(&ModeAttr); + + Opts.AddLongOption("uid") + .Optional() + .StoreResult(&UidAttr); + + Opts.AddLongOption("gid") + .Optional() + .StoreResult(&GidAttr); + + Opts.AddLongOption("size") + .Optional() + .StoreResult(&SizeAttr); + + Opts.AddLongOption("atime") + .Optional() + .StoreResult(&ATimeAttr); + + Opts.AddLongOption("mtime") + .Optional() + .StoreResult(&MTimeAttr); + + Opts.AddLongOption("ctime") + .Optional() + .StoreResult(&CTimeAttr); + } + + bool Execute() override + { + auto sessionGuard = CreateSession(); + + auto request = CreateRequest(); + request->SetNodeId(NodeId); + + auto addFlag = [&request](NProto::TSetNodeAttrRequest::EFlags flag) + { + request->SetFlags( + request->GetFlags() | NCloud::NFileStore::ProtoFlag(flag)); + }; + + if (ModeAttr) + { + addFlag(NProto::TSetNodeAttrRequest::F_SET_ATTR_MODE); + request->MutableUpdate()->SetMode(*ModeAttr); + } + + if (UidAttr) { + addFlag(NProto::TSetNodeAttrRequest::F_SET_ATTR_UID); + request->MutableUpdate()->SetUid(*UidAttr); + } + + if (GidAttr) { + addFlag(NProto::TSetNodeAttrRequest::F_SET_ATTR_GID); + request->MutableUpdate()->SetGid(*GidAttr); + } + + if (SizeAttr) { + addFlag(NProto::TSetNodeAttrRequest::F_SET_ATTR_SIZE); + request->MutableUpdate()->SetSize(*SizeAttr); + } + + if (ATimeAttr) { + addFlag(NProto::TSetNodeAttrRequest::F_SET_ATTR_ATIME); + request->MutableUpdate()->SetATime(*ATimeAttr); + } + + if (MTimeAttr) { + addFlag(NProto::TSetNodeAttrRequest::F_SET_ATTR_MTIME); + request->MutableUpdate()->SetMTime(*MTimeAttr); + } + + if (CTimeAttr) { + addFlag(NProto::TSetNodeAttrRequest::F_SET_ATTR_CTIME); + request->MutableUpdate()->SetCTime(*CTimeAttr); + } + + auto response = WaitFor( + Client->SetNodeAttr(PrepareCallContext(), std::move(request))); + + CheckResponse(response); + return true; + } +}; + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// + +TCommandPtr NewSetNodeAttrCommand() +{ + return std::make_shared(); +} + +} // namespace NCloud::NFileStore::NClient diff --git a/cloud/filestore/apps/client/lib/ya.make b/cloud/filestore/apps/client/lib/ya.make index db36c4e1d34..217d6b36679 100644 --- a/cloud/filestore/apps/client/lib/ya.make +++ b/cloud/filestore/apps/client/lib/ya.make @@ -22,6 +22,7 @@ SRCS( reset_session.cpp resize.cpp rm.cpp + set_node_attr.cpp start_endpoint.cpp stat.cpp stop_endpoint.cpp diff --git a/cloud/filestore/tests/client/test.py b/cloud/filestore/tests/client/test.py index 4880a54dc13..8af5f8149bb 100644 --- a/cloud/filestore/tests/client/test.py +++ b/cloud/filestore/tests/client/test.py @@ -171,3 +171,76 @@ def test_write_ls_rm_ls(): ret = common.canonical_file(results_path, local=True) return ret + + +def test_set_node_attr(): + client, results_path = __init_test() + client.create("fs0", "test_cloud", "test_folder", BLOCK_SIZE, BLOCKS_COUNT) + client.mkdir("fs0", "/aaa") + + out = client.stat("fs0", "/aaa") + stat = json.loads(out) + node_id = stat["Id"] + uid = 1 + gid = 1 + size = 123 + mode = 221 + atime = stat["ATime"] - 1 + mtime = stat["MTime"] - 1 + ctime = stat["CTime"] - 1 + + client.set_node_attr( + "fs0", node_id, + "--uid", uid, + "--gid", gid, + "--size", size, + "--mode", mode, + "--atime", atime, + "--mtime", mtime, + "--ctime", ctime) + + out = client.stat("fs0", "/aaa") + stat = json.loads(out) + + assert uid == stat["Uid"] + assert gid == stat["Gid"] + assert size == stat["Size"] + assert mode == stat["Mode"] + assert atime == stat["ATime"] + assert mtime == stat["MTime"] + assert ctime == stat["CTime"] + + +def test_partial_set_node_attr(): + client, results_path = __init_test() + client.create("fs0", "test_cloud", "test_folder", BLOCK_SIZE, BLOCKS_COUNT) + client.mkdir("fs0", "/aaa") + client.touch("fs0", "/aaa/bbb") + + out = client.stat("fs0", "/aaa/bbb") + stat = json.loads(out) + node_id = stat["Id"] + uid = 1 + gid = 1 + + client.set_node_attr( + "fs0", node_id, + "--uid", uid, + "--gid", gid, + "--size", 123) + + out = client.stat("fs0", "/aaa/bbb") + stat = json.loads(out) + + assert uid == stat["Uid"] + assert gid == stat["Gid"] + gid = 2 + client.set_node_attr( + "fs0", node_id, + "--gid", gid) + out = client.stat("fs0", "/aaa/bbb") + new_stat = json.loads(out) + assert gid == new_stat["Gid"] + assert stat["Uid"] == new_stat["Uid"] + assert stat["Size"] == new_stat["Size"] + assert stat["Mode"] == new_stat["Mode"] diff --git a/cloud/filestore/tests/python/lib/client.py b/cloud/filestore/tests/python/lib/client.py index 89389b45c47..4b32829b942 100644 --- a/cloud/filestore/tests/python/lib/client.py +++ b/cloud/filestore/tests/python/lib/client.py @@ -157,6 +157,16 @@ def stat(self, fs, path): return common.execute(cmd).stdout + def set_node_attr(self, fs, node_id, *argv): + list_args = [str(x) for x in argv] + cmd = [ + self.__binary_path, "setnodeattr", + "--filesystem", fs, + "--node-id", str(node_id), + ] + list_args + self.__cmd_opts() + + return common.execute(cmd).stdout + def execute_action(self, action, request): request_file = tempfile.NamedTemporaryFile(mode="w", delete=False) json.dump(request, request_file) diff --git a/library/cpp/getopt/small/last_getopt_opt.h b/library/cpp/getopt/small/last_getopt_opt.h index 5210b6e7565..4ffdfc394a1 100644 --- a/library/cpp/getopt/small/last_getopt_opt.h +++ b/library/cpp/getopt/small/last_getopt_opt.h @@ -10,6 +10,7 @@ #include #include +#include #include namespace NLastGetopt { @@ -628,6 +629,11 @@ namespace NLastGetopt { return StoreResultT(target); } + template + TOpt& StoreResult(std::optional* target) { + return StoreResultT(target); + } + template TOpt& StoreResultT(T* target, const TpDef& def) { return Handler1T(def, NPrivate::TStoreResultFunctor(target));