diff --git a/configs/static_config.yaml b/configs/static_config.yaml index 099e0da..f3bddc3 100644 --- a/configs/static_config.yaml +++ b/configs/static_config.yaml @@ -1,3 +1,4 @@ +config_vars: config_vars.yaml components_manager: task_processors: # Task processor is an executor for coroutine tasks @@ -36,6 +37,7 @@ components_manager: testsuite-support: {} congestion-control: + load-enabled: false http-client: http-client-core: @@ -94,6 +96,9 @@ components_manager: leader-service: load-enabled: true + admin-service: + load-enabled: true + handler-ping: load-enabled: true path: /ping @@ -102,8 +107,8 @@ components_manager: throttling_enabled: false url_trailing_slash: strict-match - handler-hello: # Finally! Our handler. + handler-get-context: load-enabled: true - path: /hello # Registering handler by URL '/hello'. - method: GET,POST # It will only reply to GET (HEAD) and POST requests. - task_processor: main-task-processor # Run it on CPU bound task processor \ No newline at end of file + path: /admin/context + method: GET + task_processor: main-task-processor diff --git a/src/api/http/exceptions/handler_exceptions.cpp b/src/api/http/exceptions/handler_exceptions.cpp new file mode 100644 index 0000000..16788cf --- /dev/null +++ b/src/api/http/exceptions/handler_exceptions.cpp @@ -0,0 +1,27 @@ +#include "handler_exceptions.hpp" + +namespace NCoordinator::NInfra::NHandlers { + +//////////////////////////////////////////////////////////////////////////////// + +userver::formats::json::Value MakeError(std::string_view field_name, std::string_view message) +{ + userver::formats::json::ValueBuilder error_builder; + error_builder["errors"][std::string{field_name}].PushBack(message); + return error_builder.ExtractValue(); +} + +userver::formats::json::Value MakeServerError(std::optional message) +{ + userver::formats::json::ValueBuilder error_builder; + error_builder["errors"].PushBack(message.value_or("Internal Server Error")); + return error_builder.ExtractValue(); +} + +TServerException::TServerException(std::string_view msg) + : BaseType(MakeServerError(msg)) +{ } + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NCoordinator::NInfra::NHandlers diff --git a/src/api/http/exceptions/handler_exceptions.hpp b/src/api/http/exceptions/handler_exceptions.hpp new file mode 100644 index 0000000..acd4196 --- /dev/null +++ b/src/api/http/exceptions/handler_exceptions.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include + +namespace NCoordinator::NInfra::NHandlers { + +//////////////////////////////////////////////////////////////////////////////// + +userver::formats::json::Value MakeError(std::string_view error); + +userver::formats::json::Value MakeServerError(std::optional message = std::nullopt); + +// HTTP 500 +class TServerException + : public userver::server::handlers::ExceptionWithCode +{ +public: + TServerException(std::string_view msg); +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NCoordinator::NInfra::NHandlers diff --git a/src/api/http/v1/admin/get_context.cpp b/src/api/http/v1/admin/get_context.cpp new file mode 100644 index 0000000..e4aa126 --- /dev/null +++ b/src/api/http/v1/admin/get_context.cpp @@ -0,0 +1,45 @@ +#include "get_context.hpp" + +#include + +#include +#include + +#include + +#include + +namespace NCoordinator::NInfra::NHandlers { + +//////////////////////////////////////////////////////////////////////////////// + +TGetContextHandler::TGetContextHandler( + const userver::components::ComponentConfig& config, + const userver::components::ComponentContext& context) + : HttpHandlerJsonBase(config, context) + , AdminService_(context.FindComponent().GetService()) +{ } + +userver::formats::json::Value TGetContextHandler::HandleRequestJsonThrow( + const userver::server::http::HttpRequest& /*request*/, + const userver::formats::json::Value& /*request_json*/, + userver::server::request::RequestContext& /*request_context*/) const +{ + NApp::NDto::TGetContextResponse result; + + try { + result = AdminService_.GetCoordinationContext(); + } catch (const NApp::NUseCase::TGetContextTemporaryUnavailable& ex) { + LOG_ERROR() << "Get context unavailable: " << ex.what(); + throw TServerException("Get context temporary unavailable"); + } + + userver::formats::json::ValueBuilder builder; + builder["context"] = SerializeCoordinationContext(result.Context); + + return builder.ExtractValue(); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NChat::NInfra::NHandlers diff --git a/src/api/http/v1/admin/get_context.hpp b/src/api/http/v1/admin/get_context.hpp new file mode 100644 index 0000000..9cdd0c5 --- /dev/null +++ b/src/api/http/v1/admin/get_context.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include +#include +#include + +namespace NCoordinator::NInfra::NHandlers { + +//////////////////////////////////////////////////////////////////////////////// + +class TGetContextHandler final + : public userver::server::handlers::HttpHandlerJsonBase +{ +public: + static constexpr std::string_view kName = "handler-get-context"; + + TGetContextHandler( + const userver::components::ComponentConfig&, + const userver::components::ComponentContext&); + + userver::formats::json::Value HandleRequestJsonThrow( + const userver::server::http::HttpRequest& request, + const userver::formats::json::Value& request_json, + userver::server::request::RequestContext& context) const override; + +private: + NApp::NService::TAdminService& AdminService_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NChat::NInfra::NHandlers diff --git a/src/app/dto/admin/get_context.hpp b/src/app/dto/admin/get_context.hpp new file mode 100644 index 0000000..9c344cd --- /dev/null +++ b/src/app/dto/admin/get_context.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace NCoordinator::NApp::NDto { + +//////////////////////////////////////////////////////////////////////////////// + +struct TGetContextResponse { + NCore::NDomain::TCoordinationContext Context; +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NCoordinator::NApp::NDto diff --git a/src/app/exceptions.hpp b/src/app/exceptions.hpp new file mode 100644 index 0000000..b0690fe --- /dev/null +++ b/src/app/exceptions.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace NCoordinator::NApp { + +//////////////////////////////////////////////////////////////////////////////// + +class TApplicationException + : public std::runtime_error +{ +public: + using std::runtime_error::runtime_error; +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NCoordinator::NApp diff --git a/src/app/services/admin/admin_service.cpp b/src/app/services/admin/admin_service.cpp new file mode 100644 index 0000000..988ab26 --- /dev/null +++ b/src/app/services/admin/admin_service.cpp @@ -0,0 +1,18 @@ +#include "admin_service.hpp" + +namespace NCoordinator::NApp::NService { + +//////////////////////////////////////////////////////////////////////////////// + +TAdminService::TAdminService(NCore::NDomain::ICoordinationRepository& coordinationRepository) + : GetContextUseCase_(coordinationRepository) +{ } + +NDto::TGetContextResponse TAdminService::GetCoordinationContext() const +{ + return GetContextUseCase_.Execute(); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NCoordinator::NApp::NService diff --git a/src/app/services/admin/admin_service.hpp b/src/app/services/admin/admin_service.hpp new file mode 100644 index 0000000..9e86a80 --- /dev/null +++ b/src/app/services/admin/admin_service.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace NCoordinator::NApp::NService { + +//////////////////////////////////////////////////////////////////////////////// + +class TAdminService final +{ +public: + TAdminService(NCore::NDomain::ICoordinationRepository& coordinationRepository); + + NDto::TGetContextResponse GetCoordinationContext() const; + +private: + NUseCase::TGetContextUseCase GetContextUseCase_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NCoordinator::NApp::NService diff --git a/src/app/services/leader/leader_service.cpp b/src/app/services/leader/leader_service.cpp index b8ad600..6b628af 100644 --- a/src/app/services/leader/leader_service.cpp +++ b/src/app/services/leader/leader_service.cpp @@ -7,11 +7,11 @@ namespace NCoordinator::NApp::NService { //////////////////////////////////////////////////////////////////////////////// TLeaderService::TLeaderService( - NCore::NDomain::ICoordinationGateway& coordinationGateway_, - NCore::NDomain::ICoordinationRepository& coordinationRepository_, + NCore::NDomain::ICoordinationGateway& coordinationGateway, + NCore::NDomain::ICoordinationRepository& coordinationRepository, NCore::NDomain::IHubGateway& hubGateway, NCore::ILoadFactorPredictor& loadFactorPredictor) - : CoordinationUseCase_(coordinationGateway_, coordinationRepository_, hubGateway, loadFactorPredictor) + : CoordinationUseCase_(coordinationGateway, coordinationRepository, hubGateway, loadFactorPredictor) { } void TLeaderService::Coordinate(const NDto::TCoordinationRequest& request) const diff --git a/src/app/services/leader/leader_service.hpp b/src/app/services/leader/leader_service.hpp index c21c55e..dcc2e80 100644 --- a/src/app/services/leader/leader_service.hpp +++ b/src/app/services/leader/leader_service.hpp @@ -14,8 +14,8 @@ class TLeaderService final { public: TLeaderService( - NCore::NDomain::ICoordinationGateway& coordinationGateway_, - NCore::NDomain::ICoordinationRepository& coordinationRepository_, + NCore::NDomain::ICoordinationGateway& coordinationGateway, + NCore::NDomain::ICoordinationRepository& coordinationRepository, NCore::NDomain::IHubGateway& hubGateway, NCore::ILoadFactorPredictor& loadFactorPredictor); diff --git a/src/app/use_cases/admin/get_context/get_context.cpp b/src/app/use_cases/admin/get_context/get_context.cpp new file mode 100644 index 0000000..7731c47 --- /dev/null +++ b/src/app/use_cases/admin/get_context/get_context.cpp @@ -0,0 +1,37 @@ +#include "get_context.hpp" + +#include + +#include + +#include + +namespace NCoordinator::NApp::NUseCase { + +//////////////////////////////////////////////////////////////////////////////// + +TGetContextUseCase::TGetContextUseCase(NCore::NDomain::ICoordinationRepository& coordinationRepository) + : CoordinationRepository_(coordinationRepository) +{ } + +NDto::TGetContextResponse TGetContextUseCase::Execute() const +{ + + NCore::NDomain::TCoordinationContext context; + + try { + context = CoordinationRepository_.GetCoordinationContext(); + } catch (std::exception& ex) { + throw TGetContextTemporaryUnavailable(fmt::format("Failed to get coordination context: {}", ex.what())); + } + + NDto::TGetContextResponse response{ + .Context = std::move(context), + }; + + return response; +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NCoordinator::NApp::NUseCase diff --git a/src/app/use_cases/admin/get_context/get_context.hpp b/src/app/use_cases/admin/get_context/get_context.hpp new file mode 100644 index 0000000..5379a54 --- /dev/null +++ b/src/app/use_cases/admin/get_context/get_context.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace NCoordinator::NApp::NUseCase { + +//////////////////////////////////////////////////////////////////////////////// + +class TGetContextTemporaryUnavailable + : public TApplicationException +{ + using TApplicationException::TApplicationException; +}; + +class TGetContextUseCase final +{ +public: + TGetContextUseCase(NCore::NDomain::ICoordinationRepository& coordinationRepository); + + NDto::TGetContextResponse Execute() const; + +private: + NCore::NDomain::ICoordinationRepository& CoordinationRepository_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NCoordinator::NApp::NUseCase diff --git a/src/app/use_cases/leader/coordination/coordination.cpp b/src/app/use_cases/leader/coordination/coordination.cpp index 5aca6d1..309a5c8 100644 --- a/src/app/use_cases/leader/coordination/coordination.cpp +++ b/src/app/use_cases/leader/coordination/coordination.cpp @@ -9,12 +9,12 @@ namespace NCoordinator::NApp::NUseCase { //////////////////////////////////////////////////////////////////////////////// TCoordinationUseCase::TCoordinationUseCase( - NCore::NDomain::ICoordinationGateway& coordinationGateway_, - NCore::NDomain::ICoordinationRepository& coordinationRepository_, + NCore::NDomain::ICoordinationGateway& coordinationGateway, + NCore::NDomain::ICoordinationRepository& coordinationRepository, NCore::NDomain::IHubGateway& hubGateway, NCore::ILoadFactorPredictor& loadFactorPredictor) - : CoordinationGateway_(coordinationGateway_) - , CoordinationRepository_(coordinationRepository_) + : CoordinationGateway_(coordinationGateway) + , CoordinationRepository_(coordinationRepository) , HubGateway_(hubGateway) , Balancer_(loadFactorPredictor) { } diff --git a/src/app/use_cases/leader/coordination/coordination.hpp b/src/app/use_cases/leader/coordination/coordination.hpp index 63b02d7..ba12551 100644 --- a/src/app/use_cases/leader/coordination/coordination.hpp +++ b/src/app/use_cases/leader/coordination/coordination.hpp @@ -15,8 +15,8 @@ class TCoordinationUseCase final { public: TCoordinationUseCase( - NCore::NDomain::ICoordinationGateway& coordinationGateway_, - NCore::NDomain::ICoordinationRepository& coordinationRepository_, + NCore::NDomain::ICoordinationGateway& coordinationGateway, + NCore::NDomain::ICoordinationRepository& coordinationRepository, NCore::NDomain::IHubGateway& hubGateway, NCore::ILoadFactorPredictor& loadFactorPredictor); diff --git a/src/greeting.cpp b/src/greeting.cpp deleted file mode 100644 index 1a3b8aa..0000000 --- a/src/greeting.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include - -#include - -#include - -namespace coordinator { - -std::string SayHelloTo(std::string_view name, UserType type) { - if (name.empty()) { - name = "unknown user"; - } - - switch (type) { - case UserType::kFirstTime: - return fmt::format("Hello, {}!\n", name); - case UserType::kKnown: - return fmt::format("Hi again, {}!\n", name); - } - - UASSERT(false); -} - -} // namespace coordinator \ No newline at end of file diff --git a/src/greeting.hpp b/src/greeting.hpp deleted file mode 100644 index 4364b42..0000000 --- a/src/greeting.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include -#include - -namespace coordinator { - -enum class UserType { kFirstTime, kKnown }; - -std::string SayHelloTo(std::string_view name, UserType type); - -} // namespace coordinator \ No newline at end of file diff --git a/src/hello.cpp b/src/hello.cpp deleted file mode 100644 index 3071cf7..0000000 --- a/src/hello.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include - -#include - -#include - -#include -#include - -#include - -namespace coordinator { - -Hello::Hello(const userver::components::ComponentConfig& config, const userver::components::ComponentContext& context) - : HttpHandlerJsonBase(config, context) - , gateway(context.FindComponent().GetGateway()) -{ } - -userver::formats::json::Value Hello::HandleRequestJsonThrow( - const userver::server::http::HttpRequest& , - const userver::formats::json::Value& , - userver::server::request::RequestContext&) const -{ - auto result = gateway.GetPartitionMap(); - - return NCoordinator::NInfra::SerializePartitionMap(result.value()); -} - -} // namespace coordinator \ No newline at end of file diff --git a/src/hello.hpp b/src/hello.hpp deleted file mode 100644 index 3b23267..0000000 --- a/src/hello.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include -#include - - -namespace coordinator { - -class Hello final : public userver::server::handlers::HttpHandlerJsonBase { -public: - static constexpr std::string_view kName = "handler-hello"; - - Hello(const userver::components::ComponentConfig& config, const userver::components::ComponentContext& context); - - userver::formats::json::Value HandleRequestJsonThrow( - const userver::server::http::HttpRequest& request, - const userver::formats::json::Value& request_json, - userver::server::request::RequestContext& context - ) const override; - -private: - NYdb::NCoordination::TSessionSettings settings; - NCoordinator::NCore::NDomain::ICoordinationGateway& gateway; -}; - -} // namespace coordinator \ No newline at end of file diff --git a/src/infra/components/admin/admin_service_component.cpp b/src/infra/components/admin/admin_service_component.cpp new file mode 100644 index 0000000..1195430 --- /dev/null +++ b/src/infra/components/admin/admin_service_component.cpp @@ -0,0 +1,30 @@ +#include "admin_service_component.hpp" + +#include + +#include +#include + +namespace NCoordinator::NInfra::NComponents { + +//////////////////////////////////////////////////////////////////////////////// + +TAdminServiceComponent::TAdminServiceComponent( + const userver::components::ComponentConfig& config, + const userver::components::ComponentContext& context) + : LoggableComponentBase(config, context) +{ + auto& coordinationRepository = + context.FindComponent().GetRepository(); + + Service_ = std::make_unique(coordinationRepository); +} + +NApp::NService::TAdminService& TAdminServiceComponent::GetService() +{ + return *Service_; +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NCoordinator::NInfra::NComponents diff --git a/src/infra/components/admin/admin_service_component.hpp b/src/infra/components/admin/admin_service_component.hpp new file mode 100644 index 0000000..916759d --- /dev/null +++ b/src/infra/components/admin/admin_service_component.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include + +#include + +namespace NCoordinator::NInfra::NComponents { + +//////////////////////////////////////////////////////////////////////////////// + +class TAdminServiceComponent + : public userver::components::LoggableComponentBase +{ +public: + static constexpr std::string_view kName = "admin-service"; + + TAdminServiceComponent( + const userver::components::ComponentConfig& config, + const userver::components::ComponentContext& context); + + NApp::NService::TAdminService& GetService(); + +private: + std::unique_ptr Service_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NCoordinator::NInfra::NComponents diff --git a/src/infra/components/components.cpp b/src/infra/components/components.cpp index c2c1636..72d2aa8 100644 --- a/src/infra/components/components.cpp +++ b/src/infra/components/components.cpp @@ -1,5 +1,7 @@ #include "components.hpp" +#include +#include #include #include #include @@ -7,8 +9,6 @@ #include #include -#include - #include #include #include @@ -56,13 +56,14 @@ void RegisterInfraComponents(userver::components::ComponentList& list) // Services void RegisterServices(userver::components::ComponentList& list) { - list.Append(); + list.Append() + .Append(); } // Handlers void RegisterHandlers(userver::components::ComponentList& list) { - list.Append(); + list.Append(); } } // namespace NCoordinator::NInfra::NComponents diff --git a/src/infra/serializer/serializer.cpp b/src/infra/serializer/serializer.cpp index eb4920b..fe663ab 100644 --- a/src/infra/serializer/serializer.cpp +++ b/src/infra/serializer/serializer.cpp @@ -6,6 +6,8 @@ #include +#include + namespace { //////////////////////////////////////////////////////////////////////////////// @@ -13,7 +15,10 @@ namespace { constexpr inline std::string_view EPOCH_KEY = "epoch"; constexpr inline std::string_view PARTITIONS_KEY = "partitions"; constexpr inline std::string_view PARTITION_ID_KEY = "id"; +constexpr inline std::string_view PARTITION_WEIGHT_KEY = "partition_weight"; +constexpr inline std::string_view PARTITIONS_CONTEXT_KEY = "partitions_context"; constexpr inline std::string_view HUB_ENDPOINT_KEY = "hub"; +constexpr inline std::string_view COOLDOWN_EPOCH_KEY = "cooldown_epoch"; //////////////////////////////////////////////////////////////////////////////// @@ -41,6 +46,37 @@ userver::formats::json::Value SerializePartitionMap( return result.ExtractValue(); } +userver::formats::json::Value SerializeCoordinationContext(const NCore::NDomain::TCoordinationContext& context) +{ + userver::formats::json::ValueBuilder result; + result[PARTITIONS_CONTEXT_KEY.data()] = userver::formats::common::Type::kArray; + + std::unordered_set allIds; + for (const auto& [id, _] : context.PartitionCooldowns) { + allIds.insert(id); + } + for (const auto& [id, _] : context.PartitionWeights) { + allIds.insert(id); + } + + for (const auto& partitionId : allIds) { + userver::formats::json::ValueBuilder item; + item[PARTITION_ID_KEY.data()] = partitionId.GetUnderlying(); + + if (auto it = context.PartitionCooldowns.find(partitionId); it != context.PartitionCooldowns.end()) { + item[COOLDOWN_EPOCH_KEY.data()] = it->second.GetUnderlying(); + } + + if (auto it = context.PartitionWeights.find(partitionId); it != context.PartitionWeights.end()) { + item[PARTITION_WEIGHT_KEY.data()] = it->second.GetUnderlying(); + } + + result[PARTITIONS_CONTEXT_KEY.data()].PushBack(item.ExtractValue()); + } + + return result.ExtractValue(); +} + NCore::NDomain::TPartitionMap DeserializePartitionMap(const userver::formats::json::Value& jsonValue) { NCore::NDomain::TPartitionMap result; @@ -62,6 +98,27 @@ NCore::NDomain::TPartitionMap DeserializePartitionMap(const userver::formats::js return result; } +NCore::NDomain::TCoordinationContext DeserializeCoordinationContext(const userver::formats::json::Value& json) +{ + NCore::NDomain::TCoordinationContext context; + + for (const auto& item : json[PARTITIONS_CONTEXT_KEY.data()]) { + auto partitionId = NCore::NDomain::TPartitionId{item[PARTITION_ID_KEY.data()].As()}; + + if (item.HasMember(COOLDOWN_EPOCH_KEY.data())) { + context.PartitionCooldowns[partitionId] = + NCore::NDomain::TEpoch{item[COOLDOWN_EPOCH_KEY.data()].As()}; + } + + if (item.HasMember(PARTITION_WEIGHT_KEY.data())) { + context.PartitionWeights[partitionId] = + NCore::NDomain::TPartitionWeight{item[PARTITION_WEIGHT_KEY.data()].As()}; + } + } + + return context; +} + NCore::NDomain::THubReport DeserializeHubReport(const userver::formats::json::Value& jsonValue) { NCore::NDomain::THubReport report; diff --git a/src/infra/serializer/serializer.hpp b/src/infra/serializer/serializer.hpp index bbfd94f..1977474 100644 --- a/src/infra/serializer/serializer.hpp +++ b/src/infra/serializer/serializer.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -14,8 +15,12 @@ namespace NCoordinator::NInfra { userver::formats::json::Value SerializePartitionMap(const NCore::NDomain::TPartitionMap& partitionMap); +userver::formats::json::Value SerializeCoordinationContext(const NCore::NDomain::TCoordinationContext& context); + NCore::NDomain::TPartitionMap DeserializePartitionMap(const userver::formats::json::Value& jsonValue); +NCore::NDomain::TCoordinationContext DeserializeCoordinationContext(const userver::formats::json::Value& json); + NCore::NDomain::THubReport DeserializeHubReport(const userver::formats::json::Value& jsonValue); //////////////////////////////////////////////////////////////////////////////// diff --git a/src/infra/serializer/serializer_ut.cpp b/src/infra/serializer/serializer_ut.cpp index c242792..014d7d9 100644 --- a/src/infra/serializer/serializer_ut.cpp +++ b/src/infra/serializer/serializer_ut.cpp @@ -29,6 +29,22 @@ TPartitionMap MakePartitionMap() return map; } +TCoordinationContext MakeTestContext() { + TCoordinationContext context; + + auto idBoth = TPartitionId{10}; + context.PartitionCooldowns[idBoth] = TEpoch{100}; + context.PartitionWeights[idBoth] = TPartitionWeight{50}; + + auto idOnlyCooldown = TPartitionId{20}; + context.PartitionCooldowns[idOnlyCooldown] = TEpoch{200}; + + auto idOnlyWeight = TPartitionId{30}; + context.PartitionWeights[idOnlyWeight] = TPartitionWeight{300}; + + return context; +} + //////////////////////////////////////////////////////////////////////////////// } // anonymous namespace @@ -86,6 +102,45 @@ TEST(PartitionMapSerializer, HandlesEmptyPartitions) EXPECT_TRUE(restored.Partitions.empty()); } +TEST(CoordinationContextSerializer, RoundTripRestoresAllCombinations) { + const auto original = MakeTestContext(); + + const auto json = SerializeCoordinationContext(original); + const auto restored = DeserializeCoordinationContext(json); + + ASSERT_EQ(restored.PartitionCooldowns.size(), original.PartitionCooldowns.size()); + for (const auto& [id, epoch] : original.PartitionCooldowns) { + ASSERT_TRUE(restored.PartitionCooldowns.count(id)) << "Missing ID in cooldowns: " << id.GetUnderlying(); + EXPECT_EQ(restored.PartitionCooldowns.at(id), epoch); + } + + ASSERT_EQ(restored.PartitionWeights.size(), original.PartitionWeights.size()); + for (const auto& [id, weight] : original.PartitionWeights) { + ASSERT_TRUE(restored.PartitionWeights.count(id)) << "Missing ID in weights: " << id.GetUnderlying(); + EXPECT_EQ(restored.PartitionWeights.at(id), weight); + } +} + +TEST(CoordinationContextSerializer, JsonStructureCheck) { + const auto context = MakeTestContext(); + const auto json = SerializeCoordinationContext(context); + + ASSERT_TRUE(json["partitions_context"].IsArray()); + EXPECT_EQ(json["partitions_context"].GetSize(), 3); + + for (const auto& item : json["partitions_context"]) { + uint64_t id = item["id"].As(); + + if (id == 20) { + EXPECT_TRUE(item.HasMember("cooldown_epoch")); + EXPECT_FALSE(item.HasMember("partition_weight")); + } else if (id == 30) { + EXPECT_FALSE(item.HasMember("cooldown_epoch")); + EXPECT_TRUE(item.HasMember("partition_weight")); + } + } +} + TEST(HubReportDeserializer, ParsesValidReport) { userver::formats::json::ValueBuilder json; diff --git a/tests/test_basic.py b/tests/test_basic.py deleted file mode 100644 index 1f4a9fa..0000000 --- a/tests/test_basic.py +++ /dev/null @@ -1,8 +0,0 @@ -# Start via `make test-debug` or `make test-release` - - -async def test_basic(service_client): - response = await service_client.post('/hello', params={'name': 'Tester'}) - assert response.status == 200 - assert 'application/json' in response.headers['Content-Type'] - assert response.text == '{"partitions":[],"epoch":0}' \ No newline at end of file diff --git a/tests/test_get_context.py b/tests/test_get_context.py new file mode 100644 index 0000000..913c41b --- /dev/null +++ b/tests/test_get_context.py @@ -0,0 +1,8 @@ +# Start via `make test-debug` or `make test-release` + + +async def test_get_context(service_client, mocked_time): + response = await service_client.get('/admin/context') + assert response.status == 200 + assert 'application/json' in response.headers['Content-Type'] + assert response.text == '{"context":{"partitions_context":[]}}' diff --git a/ydb/migrations/coordination_context.yql b/ydb/migrations/001_coordination_context.sql similarity index 75% rename from ydb/migrations/coordination_context.yql rename to ydb/migrations/001_coordination_context.sql index 0be5e86..8cdd541 100644 --- a/ydb/migrations/coordination_context.yql +++ b/ydb/migrations/001_coordination_context.sql @@ -4,4 +4,7 @@ CREATE TABLE coordination_context ( cooldown_epoch Uint64, weight Uint64, PRIMARY KEY (partition_id) -); \ No newline at end of file +) + +-- +goose Down +DROP TABLE coordination_context; \ No newline at end of file