diff options
author | uzhas <uzhas@ydb.tech> | 2022-08-23 15:26:42 +0300 |
---|---|---|
committer | uzhas <uzhas@ydb.tech> | 2022-08-23 15:26:42 +0300 |
commit | 0f22555742cda02eee3aa1517b5033e98712c83d (patch) | |
tree | 8f258a81c533b8fd4da9dd0c39c22d082526372d | |
parent | 2475a1aeb474274eeb4c291af3802a9c6fafbd56 (diff) | |
download | ydb-0f22555742cda02eee3aa1517b5033e98712c83d.tar.gz |
YQ HTTP initial version
51 files changed, 1405 insertions, 208 deletions
diff --git a/ydb/core/mon/async_http_mon.cpp b/ydb/core/mon/async_http_mon.cpp index 348fea33205..953bbec3a02 100644 --- a/ydb/core/mon/async_http_mon.cpp +++ b/ydb/core/mon/async_http_mon.cpp @@ -90,6 +90,9 @@ public: if (Request->Method == "PUT") { return HTTP_METHOD_PUT; } + if (Request->Method == "DELETE") { + return HTTP_METHOD_DELETE; + } return HTTP_METHOD_UNDEFINED; } @@ -224,7 +227,7 @@ public: "Access-Control-Allow-Origin: " << origin << "\r\n" "Access-Control-Allow-Credentials: true\r\n" "Access-Control-Allow-Headers: Content-Type,Authorization,Origin,Accept\r\n" - "Access-Control-Allow-Methods: OPTIONS, GET, POST\r\n" + "Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, DELETE\r\n" "Content-Type: " + type + "\r\n" "Connection: keep-alive\r\n\r\n"; ReplyWith(request->CreateResponseString(response)); @@ -249,7 +252,7 @@ public: response << "Access-Control-Allow-Origin: " << origin << "\r\n"; response << "Access-Control-Allow-Credentials: true\r\n"; response << "Access-Control-Allow-Headers: Content-Type,Authorization,Origin,Accept\r\n"; - response << "Access-Control-Allow-Methods: OPTIONS, GET, POST\r\n"; + response << "Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, DELETE\r\n"; response << "Content-Type: text/html\r\n"; response << "Content-Length: " << body.Size() << "\r\n"; response << "\r\n"; diff --git a/ydb/core/mon/mon_impl.h b/ydb/core/mon/mon_impl.h index 1ef978750ee..ab56828c764 100644 --- a/ydb/core/mon/mon_impl.h +++ b/ydb/core/mon/mon_impl.h @@ -147,7 +147,7 @@ public: "Access-Control-Allow-Origin: " << origin << "\r\n" "Access-Control-Allow-Credentials: true\r\n" "Access-Control-Allow-Headers: Content-Type,Authorization,Origin,Accept\r\n" - "Access-Control-Allow-Methods: OPTIONS, GET, POST\r\n" + "Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, DELETE\r\n" "Content-Type: " + type + "\r\n" "Connection: Keep-Alive\r\n\r\n"; Result.SetValue(MakeHolder<NMon::TEvHttpInfoRes>(response, 0, NMon::IEvHttpInfoRes::EContentType::Custom)); @@ -170,7 +170,7 @@ public: response << "Access-Control-Allow-Origin: " << origin << "\r\n"; response << "Access-Control-Allow-Credentials: true\r\n"; response << "Access-Control-Allow-Headers: Content-Type,Authorization,Origin,Accept\r\n"; - response << "Access-Control-Allow-Methods: OPTIONS, GET, POST\r\n"; + response << "Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, DELETE\r\n"; response << "Content-Type: text/html\r\n"; response << "Content-Length: " << body.Size() << "\r\n"; response << "\r\n"; diff --git a/ydb/core/viewer/CMakeLists.txt b/ydb/core/viewer/CMakeLists.txt index aac3f90b701..354d7d5e881 100644 --- a/ydb/core/viewer/CMakeLists.txt +++ b/ydb/core/viewer/CMakeLists.txt @@ -18,11 +18,13 @@ target_link_libraries(ydb-core-viewer PUBLIC cpp-actors-core library-cpp-archive cpp-mime-types + cpp-protobuf-json ydb-core-base core-blobstorage-base blobstorage-vdisk-common core-client-server ydb-core-health_check + ydb-core-grpc_services ydb-core-node_whiteboard ydb-core-protos ydb-core-scheme @@ -30,11 +32,16 @@ target_link_libraries(ydb-core-viewer PUBLIC ydb-core-util core-viewer-json core-viewer-protos + yq-libs-result_formatter library-persqueue-topic_parser api-protos lib-deprecated-kicli + public-lib-json_value ) target_sources(ydb-core-viewer PRIVATE + ${CMAKE_SOURCE_DIR}/ydb/core/viewer/http_router.cpp + ${CMAKE_SOURCE_DIR}/ydb/core/viewer/grpc_request_context_wrapper.cpp + ${CMAKE_SOURCE_DIR}/ydb/core/viewer/json_handlers_fq.cpp ${CMAKE_SOURCE_DIR}/ydb/core/viewer/json_handlers_vdisk.cpp ${CMAKE_SOURCE_DIR}/ydb/core/viewer/json_handlers_viewer.cpp ${CMAKE_SOURCE_DIR}/ydb/core/viewer/viewer.cpp @@ -54,11 +61,13 @@ target_link_libraries(ydb-core-viewer.global PUBLIC cpp-actors-core library-cpp-archive cpp-mime-types + cpp-protobuf-json ydb-core-base core-blobstorage-base blobstorage-vdisk-common core-client-server ydb-core-health_check + ydb-core-grpc_services ydb-core-node_whiteboard ydb-core-protos ydb-core-scheme @@ -66,9 +75,11 @@ target_link_libraries(ydb-core-viewer.global PUBLIC ydb-core-util core-viewer-json core-viewer-protos + yq-libs-result_formatter library-persqueue-topic_parser api-protos lib-deprecated-kicli + public-lib-json_value ) target_sources(ydb-core-viewer.global PRIVATE ${CMAKE_BINARY_DIR}/ydb/core/viewer/1cdc663173c623f6a008fb99b02498f1.cpp diff --git a/ydb/core/viewer/grpc_request_context_wrapper.cpp b/ydb/core/viewer/grpc_request_context_wrapper.cpp new file mode 100644 index 00000000000..4d2886b365b --- /dev/null +++ b/ydb/core/viewer/grpc_request_context_wrapper.cpp @@ -0,0 +1,88 @@ +#include "grpc_request_context_wrapper.h" +#include "viewer.h" + +namespace NKikimr { +namespace NViewer { + + TGrpcRequestContextWrapper::TGrpcRequestContextWrapper(TActorSystem* actorSystem, IViewer* viewer, const NMon::TEvHttpInfo::TPtr& event, std::unique_ptr<NProtoBuf::Message> request, TReplySender replySender) + : ActorSystem(actorSystem) + , Viewer(viewer) + , Event(event) + , Request(std::move(request)) + , ReplySender(std::move(replySender)) + , AuthState(true) + , DeadlineAt(TInstant::Max()) + { + const auto& params(Event->Get()->Request.GetParams()); + JsonSettings.EnumAsNumbers = false; + JsonSettings.UI64AsString = !FromStringWithDefault<bool>(params.Get("ui64"), false); + JsonSettings.EmptyRepeated = true; + const TString& timeout = params.Get("timeout"); + if (timeout) { + DeadlineAt = TInstant::Now() + TDuration::MilliSeconds(FromStringWithDefault<ui32>(timeout, 10000)); + } + Y_UNUSED(ActorSystem); + Y_UNUSED(Viewer); + Y_VERIFY(ActorSystem); + Y_VERIFY(Event); + } + + const NProtoBuf::Message* TGrpcRequestContextWrapper::GetRequest() const { + return Request.get(); + } + + NGrpc::TAuthState& TGrpcRequestContextWrapper::GetAuthState() { + return AuthState; + } + + void TGrpcRequestContextWrapper::Reply(NProtoBuf::Message* resp, ui32 status) { + Y_UNUSED(resp); + Y_UNUSED(status); + Y_VERIFY(resp); + ReplySender(ActorSystem, Viewer, Event, JsonSettings, resp, status); + } + + void TGrpcRequestContextWrapper::Reply(grpc::ByteBuffer* resp, ui32 status) { + Y_UNUSED(resp); + Y_UNUSED(status); + Y_VERIFY(false, "TGrpcRequestContextWrapper::Reply"); + } + + void TGrpcRequestContextWrapper::ReplyUnauthenticated(const TString& in) { + Y_UNUSED(in); + ActorSystem->Send(Event->Sender, new NMon::TEvHttpInfoRes(HTTPUNAUTHORIZEDTEXT + in, 0, NMon::IEvHttpInfoRes::EContentType::Custom)); + } + + void TGrpcRequestContextWrapper::ReplyError(grpc::StatusCode code, const TString& msg, const TString& details) { + Y_UNUSED(code); + Y_UNUSED(msg); + ActorSystem->Send(Event->Sender, new NMon::TEvHttpInfoRes(TStringBuilder() << HTTPBADREQUEST_HEADERS << "code: " << (int)code << ", msg: " << msg << ", details: " << details, 0, NMon::IEvHttpInfoRes::EContentType::Custom)); + } + + TInstant TGrpcRequestContextWrapper::Deadline() const { + return DeadlineAt; + } + + TSet<TStringBuf> TGrpcRequestContextWrapper::GetPeerMetaKeys() const { + return {}; + } + + TVector<TStringBuf> TGrpcRequestContextWrapper::GetPeerMetaValues(TStringBuf key) const { + // todo: remap http public headers into internal grpc headers, e.g + // Authorization -> x-ydb-auth-ticket + // scope/project + if (key == "x-ydb-auth-ticket"sv) { + key = "authorization"sv; + } + const THttpHeaders& headers = Event->Get()->Request.GetHeaders(); + if (auto h = headers.FindHeader(key)) { + return { h->Value() }; + } + return {}; + } + + google::protobuf::Arena* TGrpcRequestContextWrapper::GetArena() { + return &Arena; + } +} +} diff --git a/ydb/core/viewer/grpc_request_context_wrapper.h b/ydb/core/viewer/grpc_request_context_wrapper.h new file mode 100644 index 00000000000..27e5cc346f7 --- /dev/null +++ b/ydb/core/viewer/grpc_request_context_wrapper.h @@ -0,0 +1,51 @@ +#include <ydb/core/grpc_services/base/base.h> +#include <ydb/core/viewer/json/json.h> +#include <ydb/public/api/protos/yq.pb.h> + +namespace NKikimr { +namespace NViewer { + +class IViewer; + +typedef std::function<void(TActorSystem* actorSystem, IViewer* viewer, const NMon::TEvHttpInfo::TPtr& event, const TJsonSettings& jsonSettings, NProtoBuf::Message* resp, ui32 status)> TReplySender; + +class TGrpcRequestContextWrapper : public NGrpc::IRequestContextBase { +private: + TActorSystem* ActorSystem; + IViewer* Viewer; + NMon::TEvHttpInfo::TPtr Event; + std::unique_ptr<NProtoBuf::Message> Request; + TReplySender ReplySender; + NGrpc::TAuthState AuthState; + google::protobuf::Arena Arena; + TJsonSettings JsonSettings; + TInstant DeadlineAt; + +public: + TGrpcRequestContextWrapper(TActorSystem* actorSystem, IViewer* viewer, const NMon::TEvHttpInfo::TPtr& event, std::unique_ptr<NProtoBuf::Message> request, TReplySender replySender); + virtual const NProtoBuf::Message* GetRequest() const; + virtual NGrpc::TAuthState& GetAuthState(); + virtual void Reply(NProtoBuf::Message* resp, ui32 status = 0); + virtual void Reply(grpc::ByteBuffer* resp, ui32 status = 0); + virtual void ReplyUnauthenticated(const TString& in); + virtual void ReplyError(grpc::StatusCode code, const TString& msg, const TString& details); + virtual TInstant Deadline() const; + virtual TSet<TStringBuf> GetPeerMetaKeys() const; + virtual TVector<TStringBuf> GetPeerMetaValues(TStringBuf key) const; + virtual grpc_compression_level GetCompressionLevel() const { return GRPC_COMPRESS_LEVEL_NONE; } + + virtual google::protobuf::Arena* GetArena(); + + virtual void AddTrailingMetadata(const TString&, const TString&) {} + + virtual void UseDatabase(const TString& ) {} + + virtual void SetNextReplyCallback(TOnNextReply&&) {} + virtual void FinishStreamingOk() {} + virtual TAsyncFinishResult GetFinishFuture() { return {}; } + virtual TString GetPeer() const { return {}; } + virtual bool SslServer() const { return false; } +}; + +} +} diff --git a/ydb/core/viewer/http_handler.h b/ydb/core/viewer/http_handler.h new file mode 100644 index 00000000000..9b879d50289 --- /dev/null +++ b/ydb/core/viewer/http_handler.h @@ -0,0 +1,35 @@ +#pragma once + +#include <library/cpp/actors/core/mon.h> + +namespace NActors { +class IActor; +} + +namespace NKikimr { +namespace NViewer { + +using namespace NActors; +class IViewer; + +struct TRequest { + NMon::TEvHttpInfo::TPtr Event; + std::map<TString, TString> PathParams; +}; + +class TJsonHandlerBase { +public: + typedef std::shared_ptr<TJsonHandlerBase> TPtr; + +public: + virtual ~TJsonHandlerBase() = default; + virtual IActor* CreateRequestActor(IViewer* viewer, const TRequest& request) = 0; + virtual TString GetResponseJsonSchema() = 0; + virtual TString GetTags() { return TString(); } + virtual TString GetRequestSummary() { return TString(); } + virtual TString GetRequestDescription() { return TString(); } + virtual TString GetRequestParameters() { return TString(); } +}; + +} +} diff --git a/ydb/core/viewer/http_router.cpp b/ydb/core/viewer/http_router.cpp new file mode 100644 index 00000000000..c40c48724d5 --- /dev/null +++ b/ydb/core/viewer/http_router.cpp @@ -0,0 +1,58 @@ +#include "http_router.h" + +namespace NKikimr { +namespace NViewer { + +namespace { + bool MatchPath(TStringBuf pathPattern, TStringBuf path, std::map<TString, TString>& pathParams) { + const char delim = '/'; + pathPattern.SkipPrefix("/"sv); + path.SkipPrefix("/"sv); + + TStringBuf pathPatternComponent = pathPattern.NextTok(delim); + TStringBuf pathComponent = path.NextTok(delim); + while (pathPatternComponent && pathComponent) { + if (pathPatternComponent.StartsWith('{') && pathPatternComponent.EndsWith('}')) { + TStringBuf paramName = pathPatternComponent.SubString(1, pathPatternComponent.Size() - 2); + pathParams.emplace(paramName, pathComponent); + } else { + if (pathPatternComponent != pathComponent) { + return false; + } + } + pathPatternComponent = pathPattern.NextTok(delim); + pathComponent = path.NextTok(delim); + } + + return !pathPattern && !path && !pathPatternComponent && !pathComponent; + } +} + +void THttpRequestRouter::RegisterHandler(HTTP_METHOD method, const TString& pathPattern, TJsonHandlerBase::TPtr handler) { + Data.emplace(std::make_pair(method, pathPattern), std::move(handler)); +} + +std::optional<THandlerWithParams> THttpRequestRouter::ResolveHandler(HTTP_METHOD method, const TStringBuf& path) const { + auto it = Data.find(std::pair<HTTP_METHOD, TString>(method, path)); + if (it != Data.end()) { + return THandlerWithParams(it->second, {}); + } + + for (const auto& [k ,v] : Data) { + if (k.first != method) { + continue; + } + std::map<TString, TString> pathParams; + if (MatchPath(k.second, path, pathParams)) { + return THandlerWithParams(v, pathParams); + } + } + return {}; +} + +size_t THttpRequestRouter::GetSize() const { + return Data.size(); +} + +} +} diff --git a/ydb/core/viewer/http_router.h b/ydb/core/viewer/http_router.h new file mode 100644 index 00000000000..6549dd24760 --- /dev/null +++ b/ydb/core/viewer/http_router.h @@ -0,0 +1,46 @@ +#pragma once + +#include "http_handler.h" + +namespace NKikimr { +namespace NViewer { + +struct THandlerWithParams { + THandlerWithParams(TJsonHandlerBase::TPtr handler, std::map<TString, TString> pathParams) + : Handler(std::move(handler)) + , PathParams(std::move(pathParams)) + { + } + + THandlerWithParams() = default; + THandlerWithParams(THandlerWithParams&&) = default; + THandlerWithParams& operator=(const THandlerWithParams&) = default; + THandlerWithParams& operator=(const THandlerWithParams&&) = default; + + TJsonHandlerBase::TPtr Handler; + std::map<TString, TString> PathParams; +}; + +class THttpRequestRouter { +public: + void RegisterHandler(HTTP_METHOD method, const TString& pathPattern, TJsonHandlerBase::TPtr handler); + void RegisterGetHandler(const TString& pathPattern, TJsonHandlerBase::TPtr handler) { + RegisterHandler(HTTP_METHOD_GET, pathPattern, handler); + } + + std::optional<THandlerWithParams> ResolveHandler(HTTP_METHOD method, const TStringBuf& path) const; + size_t GetSize() const; + + template <typename F> + void ForEach(F f) const { + for (const auto& [p, handler] : Data) { + f(p.first, p.second, handler); + } + } + +private: + std::map<std::pair<HTTP_METHOD, TString>, TJsonHandlerBase::TPtr> Data; +}; + +} +} diff --git a/ydb/core/viewer/http_router_ut.cpp b/ydb/core/viewer/http_router_ut.cpp new file mode 100644 index 00000000000..04e0a0c86e7 --- /dev/null +++ b/ydb/core/viewer/http_router_ut.cpp @@ -0,0 +1,57 @@ +#include <library/cpp/testing/unittest/registar.h> +#include "http_router.h" + +using namespace NKikimr; +using namespace NViewer; + +namespace { +class TTestHandler : public TJsonHandlerBase { + int Id; +public: + explicit TTestHandler(int id) + : Id(id) + { + } + + int GetId() const { return Id; } + virtual IActor* CreateRequestActor(IViewer* , const TRequest&) { return nullptr; } + virtual TString GetResponseJsonSchema() { return TString(); } +}; + +std::shared_ptr<TTestHandler> AsTestHandler(TJsonHandlerBase::TPtr h) { + return std::static_pointer_cast<TTestHandler>(h); +} + +} +Y_UNIT_TEST_SUITE(HttpRouter) { + Y_UNIT_TEST(Basic) { + THttpRequestRouter router; + router.RegisterHandler(HTTP_METHOD_GET, "/apix/v1/fq/query", std::make_shared<TTestHandler>(1)); + router.RegisterHandler(HTTP_METHOD_GET, "/apix/v1/fq/query/{query_id}", std::make_shared<TTestHandler>(2)); + UNIT_ASSERT_VALUES_EQUAL(router.GetSize(), 2); + + std::optional<THandlerWithParams> resolve1 = router.ResolveHandler(HTTP_METHOD_GET, "/apix/v1/fq"sv); + UNIT_ASSERT(!resolve1); + + std::optional<THandlerWithParams> resolve2 = router.ResolveHandler(HTTP_METHOD_GET, "/apix/v1/fq/query/1234/status"sv); + UNIT_ASSERT(!resolve2); + + std::optional<THandlerWithParams> resolve3 = router.ResolveHandler(HTTP_METHOD_POST, "/apix/v1/fq/query"sv); + UNIT_ASSERT(!resolve3); + + std::optional<THandlerWithParams> resolve4 = router.ResolveHandler(HTTP_METHOD_GET, "/apix/v1/fq/query"sv); + UNIT_ASSERT(resolve4); + UNIT_ASSERT(resolve4->Handler); + UNIT_ASSERT_VALUES_EQUAL(AsTestHandler(resolve4->Handler)->GetId(), 1); + UNIT_ASSERT_VALUES_EQUAL(resolve4->PathParams.size(), 0u); + + std::optional<THandlerWithParams> resolve5 = router.ResolveHandler(HTTP_METHOD_GET, "/apix/v1/fq/query/1234"sv); + UNIT_ASSERT(resolve5); + UNIT_ASSERT(resolve5->Handler); + UNIT_ASSERT_VALUES_EQUAL(AsTestHandler(resolve5->Handler)->GetId(), 2); + UNIT_ASSERT_VALUES_EQUAL(resolve5->PathParams.size(), 1u); + + UNIT_ASSERT_VALUES_EQUAL(resolve5->PathParams.begin()->first, "query_id"); + UNIT_ASSERT_VALUES_EQUAL(resolve5->PathParams.begin()->second, "1234"); + } +} diff --git a/ydb/core/viewer/json/json.cpp b/ydb/core/viewer/json/json.cpp index 51d7c9a32d0..00d969a043f 100644 --- a/ydb/core/viewer/json/json.cpp +++ b/ydb/core/viewer/json/json.cpp @@ -4,8 +4,15 @@ #include <util/string/printf.h> #include <util/charset/utf8.h> #include <util/stream/str.h> +#include <google/protobuf/duration.pb.h> +#include <google/protobuf/timestamp.pb.h> +#include <google/protobuf/util/time_util.h> #include "json.h" +#ifdef GetMessage +#undef GetMessage +#endif + void TProtoToJson::EscapeJsonString(IOutputStream& os, const TString& s) { const char* b = s.begin(); const char* e = s.end(); @@ -59,6 +66,22 @@ void TProtoToJson::ProtoToJson(IOutputStream& to, const ::google::protobuf::Enum } void TProtoToJson::ProtoToJson(IOutputStream& to, const ::google::protobuf::Message& protoFrom, const TJsonSettings& jsonSettings) { + if (protoFrom.GetTypeName() == google::protobuf::Timestamp::descriptor()->full_name()) { + auto& ts = static_cast<const google::protobuf::Timestamp&>(protoFrom); + to << '"'; + if (ts.seconds() || ts.nanos()) { + to << google::protobuf::util::TimeUtil::ToString(ts); + } + to << '"'; + return; + } + + if (protoFrom.GetTypeName() == google::protobuf::Duration::descriptor()->full_name()) { + auto& d = static_cast<const google::protobuf::Duration&>(protoFrom); + to << '"' << google::protobuf::util::TimeUtil::ToString(d) << '"'; + return; + } + to << '{'; ProtoToJsonInline(to, protoFrom, jsonSettings); to << '}'; @@ -423,54 +446,103 @@ void TProtoToJson::ProtoToJsonSchema(IOutputStream& to, const TJsonSettings& jso to << "\":"; to << "{\"type\":\"oneOf\"}"; } + char fieldSeparator = oneofFields ? ',' : ' '; for (int idx = 0; idx < fields; ++idx) { const FieldDescriptor* fieldDescriptor = descriptor->field(idx); - if (idx != 0 || oneofFields != 0) { - to << ','; - } - to << '"'; TString name; if (jsonSettings.NameGenerator) { name = jsonSettings.NameGenerator(*fieldDescriptor); } else { name = fieldDescriptor->name(); } + to << fieldSeparator; + fieldSeparator = ','; + + to << '"'; EscapeJsonString(to, name); to << "\":"; if (fieldDescriptor->is_repeated()) { to << "{\"type\":\"array\",\"items\":"; } if (fieldDescriptor->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) { - if (descriptors.insert(descriptor).second) { + if (fieldDescriptor->message_type()->full_name() == google::protobuf::Timestamp::descriptor()->full_name()) { + to << "{\"type\":\"string\",\"format\":\"date-time\"}"; + } else if (fieldDescriptor->message_type()->full_name() == google::protobuf::Duration::descriptor()->full_name()) { + to << "{\"type\":\"string\", \"example\":\"3600s\"}"; + } else if (descriptors.insert(descriptor).second) { ProtoToJsonSchema(to, jsonSettings, fieldDescriptor->message_type(), descriptors); + descriptors.erase(descriptor); } else { to << "{}"; } } else { - to << "{\"type\":\""; + to << "{"; + TString type; + TString format; switch (fieldDescriptor->cpp_type()) { case FieldDescriptor::CPPTYPE_INT32: + type = "integer"; + format = "int32"; + break; case FieldDescriptor::CPPTYPE_UINT32: - case FieldDescriptor::CPPTYPE_ENUM: - to << "integer"; + type = "integer"; + format = "uint32"; break; - case FieldDescriptor::CPPTYPE_STRING: case FieldDescriptor::CPPTYPE_INT64: + type = "string"; // because of JS compatibility (JavaScript could not handle large numbers (bigger than 2^53)) + format = "int64"; + break; case FieldDescriptor::CPPTYPE_UINT64: - to << "string"; // because of JS compatibility (JavaScript could not handle large numbers (bigger than 2^53)) + type = "string"; // because of JS compatibility (JavaScript could not handle large numbers (bigger than 2^53)) + format = "uint64"; + break; + case FieldDescriptor::CPPTYPE_STRING: + case FieldDescriptor::CPPTYPE_ENUM: + type = "string"; // because of JS compatibility (JavaScript could not handle large numbers (bigger than 2^53)) break; case FieldDescriptor::CPPTYPE_FLOAT: + type = "number"; + format = "float"; + break; case FieldDescriptor::CPPTYPE_DOUBLE: - to << "number"; + type = "number"; + format = "double"; break; case FieldDescriptor::CPPTYPE_BOOL: - to << "boolean"; + type = "boolean"; break; case FieldDescriptor::CPPTYPE_MESSAGE: - to << "object"; + type = "object"; break; - }; - to << '"'; + } + + to << "\"type\":\"" << type << "\""; + if (format) { + to << ", \"format\":\"" << format << "\""; + } + if (fieldDescriptor->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) { + to << ", \"enum\": ["; + auto enumDescriptor = fieldDescriptor->enum_type(); + auto valueCount = enumDescriptor->value_count(); + auto sep = ' '; + TString defaultValue; + for (int i = 0; i < valueCount; ++i) { + auto enumValueDescriptor = enumDescriptor->value(i); + if (jsonSettings.EnumValueFilter && !jsonSettings.EnumValueFilter(enumValueDescriptor->name())) { + continue; + } + to << sep; + sep = ','; + to << "\"" << enumValueDescriptor->name() << "\""; + if (!defaultValue) { + defaultValue = enumValueDescriptor->name(); + } + } + to << "]"; + if (defaultValue) { + to << ", \"default\": \"" << defaultValue << "\""; + } + } to << '}'; } if (fieldDescriptor->is_repeated()) { diff --git a/ydb/core/viewer/json/json.h b/ydb/core/viewer/json/json.h index 01e3b85e2d9..87231fd1cfa 100644 --- a/ydb/core/viewer/json/json.h +++ b/ydb/core/viewer/json/json.h @@ -8,12 +8,14 @@ struct TJsonSettings { using TMapperKey = const ::google::protobuf::FieldDescriptor*; using TMapperValue = std::function<void(IOutputStream&, const ::google::protobuf::Message&, const TJsonSettings&)>; using TNameGenerator = std::function<TString(const google::protobuf::FieldDescriptor&)>; + using TEnumValueFilter = std::function<bool(const TString& value)>; bool UI64AsString = true; // JavaScript could not handle large numbers (bigger than 2^53) bool EnumAsNumbers = true; bool EmptyRepeated = false; TString NaN = "null"; std::unordered_map<TMapperKey, TMapperValue> FieldRemapper; - TNameGenerator NameGenerator = {}; + TNameGenerator NameGenerator; + TEnumValueFilter EnumValueFilter; }; class TProtoToJson { diff --git a/ydb/core/viewer/json_acl.h b/ydb/core/viewer/json_acl.h index 43034830fc4..360e6c81866 100644 --- a/ydb/core/viewer/json_acl.h +++ b/ydb/core/viewer/json_acl.h @@ -27,9 +27,9 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonACL(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev) + TJsonACL(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Event(ev) + , Event(request.Event) {} void FillParams(NKikimrSchemeOp::TDescribePath* record, const TCgiParameters& params) { diff --git a/ydb/core/viewer/json_browse.h b/ydb/core/viewer/json_browse.h index 53f95f10346..6a5532ed50e 100644 --- a/ydb/core/viewer/json_browse.h +++ b/ydb/core/viewer/json_browse.h @@ -70,9 +70,9 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonBrowse(IViewer *viewer, NMon::TEvHttpInfo::TPtr &ev) + TJsonBrowse(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Event(ev) + , Event(request.Event) {} void ParsePath(const TString& path, const TActorContext& ctx) { diff --git a/ydb/core/viewer/json_bscontrollerinfo.h b/ydb/core/viewer/json_bscontrollerinfo.h index 1daafd2368e..e0360953db4 100644 --- a/ydb/core/viewer/json_bscontrollerinfo.h +++ b/ydb/core/viewer/json_bscontrollerinfo.h @@ -27,9 +27,9 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonBSControllerInfo(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev) + TJsonBSControllerInfo(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Event(ev) + , Event(request.Event) {} void Bootstrap(const TActorContext& ctx) { diff --git a/ydb/core/viewer/json_cluster.h b/ydb/core/viewer/json_cluster.h index c7d15c25dec..4aeacb4f577 100644 --- a/ydb/core/viewer/json_cluster.h +++ b/ydb/core/viewer/json_cluster.h @@ -40,12 +40,12 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonCluster(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + TJsonCluster(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Initiator(ev->Sender) + , Initiator(request.Event->Sender) , Requested(0) , Received(0) - , Event(ev) + , Event(request.Event) { const auto& params(Event->Get()->Request.GetParams()); JsonSettings.EnumAsNumbers = !FromStringWithDefault<bool>(params.Get("enums"), true); diff --git a/ydb/core/viewer/json_compute.h b/ydb/core/viewer/json_compute.h index c8e69815ca4..1cc911265db 100644 --- a/ydb/core/viewer/json_compute.h +++ b/ydb/core/viewer/json_compute.h @@ -51,9 +51,9 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonCompute(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + TJsonCompute(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Event(ev) + , Event(request.Event) {} TString GetDomainId(TPathId pathId) { diff --git a/ydb/core/viewer/json_config.h b/ydb/core/viewer/json_config.h index d9c3348a590..3efc43b53d4 100644 --- a/ydb/core/viewer/json_config.h +++ b/ydb/core/viewer/json_config.h @@ -22,9 +22,9 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonConfig(IViewer *viewer, NMon::TEvHttpInfo::TPtr &ev) + TJsonConfig(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Event(ev) + , Event(request.Event) {} void Bootstrap(const TActorContext& ctx) { diff --git a/ydb/core/viewer/json_content.h b/ydb/core/viewer/json_content.h index 79b230f1288..9ed057c7886 100644 --- a/ydb/core/viewer/json_content.h +++ b/ydb/core/viewer/json_content.h @@ -29,10 +29,10 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonContent(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + TJsonContent(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Initiator(ev->Sender) - , Event(ev) + , Initiator(request.Event->Sender) + , Event(request.Event) {} STFUNC(StateWaitingBrowse) { diff --git a/ydb/core/viewer/json_counters.h b/ydb/core/viewer/json_counters.h index c56719c77d5..7e69b505d9c 100644 --- a/ydb/core/viewer/json_counters.h +++ b/ydb/core/viewer/json_counters.h @@ -31,9 +31,9 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonCounters(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + TJsonCounters(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Event(ev) + , Event(request.Event) , Requested(0) , Received(0) {} diff --git a/ydb/core/viewer/json_describe.h b/ydb/core/viewer/json_describe.h index 3f6ec6b59c2..00abe2190a8 100644 --- a/ydb/core/viewer/json_describe.h +++ b/ydb/core/viewer/json_describe.h @@ -28,9 +28,9 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonDescribe(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev) + TJsonDescribe(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Event(ev) + , Event(request.Event) {} void FillParams(NKikimrSchemeOp::TDescribePath* record, const TCgiParameters& params) { diff --git a/ydb/core/viewer/json_handlers.h b/ydb/core/viewer/json_handlers.h index 526c671d013..84d049be195 100644 --- a/ydb/core/viewer/json_handlers.h +++ b/ydb/core/viewer/json_handlers.h @@ -1,25 +1,15 @@ #pragma once +#include "http_router.h" #include "viewer.h" -#include <ydb/core/viewer/json/json.h> namespace NKikimr::NViewer { -class TJsonHandlerBase { -public: - virtual ~TJsonHandlerBase() = default; - virtual IActor* CreateRequestActor(IViewer* viewer, NMon::TEvHttpInfo::TPtr& event) = 0; - virtual TString GetResponseJsonSchema() = 0; - virtual TString GetRequestSummary() { return TString(); } - virtual TString GetRequestDescription() { return TString(); } - virtual TString GetRequestParameters() { return TString(); } -}; - template <typename ActorRequestType> class TJsonHandler : public TJsonHandlerBase { public: - IActor* CreateRequestActor(IViewer* viewer, NMon::TEvHttpInfo::TPtr& event) override { - return new ActorRequestType(viewer, event); + IActor* CreateRequestActor(IViewer* viewer, const TRequest& request) override { + return new ActorRequestType(viewer, request); } TString GetResponseJsonSchema() override { @@ -45,63 +35,95 @@ public: template <typename TTagInfo> struct TJsonHandlers { - THashMap<TString, TAutoPtr<TJsonHandlerBase>> JsonHandlers; + THttpRequestRouter Router; void Init(); void Handle(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev, const TActorContext &ctx) { NMon::TEvHttpInfo* msg = ev->Get(); - auto itJson = JsonHandlers.find(msg->Request.GetPage()->Path + msg->Request.GetPathInfo()); - if (itJson == JsonHandlers.end()) { - itJson = JsonHandlers.find(msg->Request.GetPathInfo()); - } - if (itJson != JsonHandlers.end()) { - try { - ctx.ExecutorThread.RegisterActor(itJson->second->CreateRequestActor(viewer, ev)); - } - catch (const std::exception& e) { - ctx.Send(ev->Sender, new NMon::TEvHttpInfoRes(TString("HTTP/1.1 400 Bad Request\r\n\r\n") + e.what(), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); + auto handlerWithParamsO = Router.ResolveHandler(msg->Request.GetMethod(), msg->Request.GetPage()->Path + msg->Request.GetPathInfo()); + if (!handlerWithParamsO) { + // for legendary /viewer handlers + handlerWithParamsO = Router.ResolveHandler(msg->Request.GetMethod(), msg->Request.GetPathInfo()); + if (!handlerWithParamsO) { + ctx.Send(ev->Sender, new NMon::TEvHttpInfoRes(NMonitoring::HTTPNOTFOUND)); return; } - } else { - ctx.Send(ev->Sender, new NMon::TEvHttpInfoRes(NMonitoring::HTTPNOTFOUND)); + } + try { + TRequest request; + request.Event = ev; + request.PathParams = handlerWithParamsO->PathParams; + ctx.ExecutorThread.RegisterActor(handlerWithParamsO->Handler->CreateRequestActor(viewer, request)); + } catch (const std::exception& e) { + ctx.Send(ev->Sender, new NMon::TEvHttpInfoRes(TString("HTTP/1.1 400 Bad Request\r\n\r\n") + e.what(), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); + return; } } - void PrintForSwagger(TStringStream &json) { - for (auto itJson = JsonHandlers.begin(); itJson != JsonHandlers.end(); ++itJson) { - if (itJson != JsonHandlers.begin()) { - json << ','; - } - TString name = itJson->first; - json << "\"/" << name << '"' << ":{"; - json << "\"get\":{"; - json << "\"tags\":[\"" << TTagInfo::TagName << "\"],"; - json << "\"produces\":[\"application/json\"],"; - TString summary = itJson->second->GetRequestSummary(); - if (!summary.empty()) { - json << "\"summary\":" << summary << ','; - } - TString description = itJson->second->GetRequestDescription(); - if (!description.empty()) { - json << "\"description\":" << description << ','; - } - TString parameters = itJson->second->GetRequestParameters(); - if (!parameters.empty()) { - json << "\"parameters\":" << parameters << ','; - } - json << "\"responses\":{"; - json << "\"200\":{"; - TString schema = itJson->second->GetResponseJsonSchema(); - if (!schema.empty()) { - json << "\"schema\":" << schema; - } + void PrintForSwagger(TStringStream &json) const { + std::map<TString, std::map<HTTP_METHOD, TJsonHandlerBase*>> allHandlers; + + Router.ForEach([&](HTTP_METHOD method, const TString& pathPattern, TJsonHandlerBase::TPtr handler) { + allHandlers[pathPattern][method] = handler.get(); + }); + + char sep = ' '; + for (const auto& [path, method2handler] : allHandlers) { + json << sep; + sep = ','; + + json << '"' << path << '"' << ":{"; + char methodSep = ' '; + for (const auto& [method, handler] : method2handler) { + json << methodSep; + methodSep = ','; + + auto methodName = ResolveMethodName(method); + json << "\"" << methodName << "\":{"; + json << "\"tags\":[\"" << TTagInfo::TagName << "\"],"; + json << "\"produces\":[\"application/json\"],"; + TString summary = handler->GetRequestSummary(); + if (!summary.empty()) { + json << "\"summary\":" << summary << ','; + } + TString description = handler->GetRequestDescription(); + if (!description.empty()) { + json << "\"description\":" << description << ','; + } + TString parameters = handler->GetRequestParameters(); + if (!parameters.empty()) { + json << "\"parameters\":" << parameters << ','; + } + json << "\"responses\":{"; + json << "\"200\":{"; + TString schema = handler->GetResponseJsonSchema(); + if (!schema.empty()) { + json << "\"schema\":" << schema; + } + json << "}"; json << "}"; json << "}"; - json << "}"; + } json << "}"; } } + + static std::string_view ResolveMethodName(HTTP_METHOD method) { + switch (method) { + case HTTP_METHOD_GET: + return "get"sv; + case HTTP_METHOD_POST: + return "post"sv; + case HTTP_METHOD_PUT: + return "put"sv; + case HTTP_METHOD_DELETE: + return "delete"sv; + default: + return "unknown http method"sv; + } + } + }; struct TViewerTagInfo { @@ -110,9 +132,12 @@ struct TViewerTagInfo { struct TVDiskTagInfo { static constexpr auto TagName = "vdisk"; }; +struct FQTagInfo { + static constexpr auto TagName = "fq"; +}; using TViewerJsonHandlers = TJsonHandlers<TViewerTagInfo>; using TVDiskJsonHandlers = TJsonHandlers<TVDiskTagInfo>; - +using TFQJsonHandlers = TJsonHandlers<FQTagInfo>; } diff --git a/ydb/core/viewer/json_handlers_fq.cpp b/ydb/core/viewer/json_handlers_fq.cpp new file mode 100644 index 00000000000..01c20fb8a33 --- /dev/null +++ b/ydb/core/viewer/json_handlers_fq.cpp @@ -0,0 +1,601 @@ +#include "grpc_request_context_wrapper.h" +#include "http_router.h" +#include "json_handlers.h" +#include "viewer.h" + +#include <library/cpp/actors/core/actor_bootstrapped.h> +#include <library/cpp/actors/core/mon.h> +#include <library/cpp/protobuf/json/json2proto.h> +#include <ydb/core/protos/services.pb.h> +#include <ydb/core/grpc_services/grpc_request_proxy.h> +#include <ydb/core/grpc_services/service_yq.h> +#include <ydb/core/viewer/protos/fq.pb.h> +#include <ydb/core/yq/libs/result_formatter/result_formatter.h> + +namespace NKikimr { +namespace NViewer { + +using namespace NActors; + +using ::google::protobuf::Descriptor; +using ::google::protobuf::FieldDescriptor; +using ::google::protobuf::Reflection; +using ::google::protobuf::RepeatedField; +using ::google::protobuf::RepeatedPtrField; + +#define SIMPLE_COPY_FIELD(field) dst.set_##field(src.field()) +#define SIMPLE_COPY_RENAME_FIELD(srcField, dstField) dst.set_##dstField(src.srcField()) + +#define SIMPLE_COPY_MUTABLE_FIELD(field) *dst.mutable_##field() = src.field() +#define SIMPLE_COPY_MUTABLE_RENAME_FIELD(srcField, dstField) *dst.mutable_##dstField() = src.srcField() + +#define SIMPLE_COPY_REPEATABLE_FIELD(field) FqConvert(src.field(), *dst.mutable_##field()) +#define SIMPLE_COPY_REPEATABLE_RENAME_FIELD(srcField, dstField) FqConvert(src.srcField(), *dst.mutable_##dstField()) + +#define FQ_CONVERT_FIELD(field) FqConvert(src.field(), *dst.mutable_##field()) +#define FQ_CONVERT_RENAME_FIELD(srcField, dstField) FqConvert(src.srcField(), *dst.mutable_##dstField()) + +template <typename T> +void FqConvert(const T& src, T& dst) { + dst.CopyFrom(src); +} + +template <typename T> +void FqConvert(const T& src, ::google::protobuf::Empty& dst) { + Y_UNUSED(src); + Y_UNUSED(dst); +} + +template <typename T, typename U> +void FqConvert(const RepeatedPtrField<T>& src, RepeatedPtrField<U>& dst) { + dst.Reserve(src.size()); + for (auto& v : src) { + FqConvert(v, *dst.Add()); + } +} + +void FqConvert(const Ydb::Operations::Operation& src, FederatedQueryHttp::Error& dst) { + SIMPLE_COPY_FIELD(status); + SIMPLE_COPY_MUTABLE_FIELD(issues); +} + +#define FQ_CONVERT_QUERY_CONTENT(srcType, dstType) \ +void FqConvert(const srcType& src, dstType& dst) { \ + SIMPLE_COPY_FIELD(type); \ + SIMPLE_COPY_FIELD(name); \ + SIMPLE_COPY_FIELD(text); \ + SIMPLE_COPY_FIELD(description); \ +} + +FQ_CONVERT_QUERY_CONTENT(FederatedQueryHttp::CreateQueryRequest, YandexQuery::QueryContent); +FQ_CONVERT_QUERY_CONTENT(YandexQuery::QueryContent, FederatedQueryHttp::GetQueryResult); + +void FqConvert(const FederatedQueryHttp::CreateQueryRequest& src, YandexQuery::CreateQueryRequest& dst) { + FqConvert(src, *dst.mutable_content()); + + dst.set_execute_mode(YandexQuery::RUN); + + auto& content = *dst.mutable_content(); + if (content.type() == YandexQuery::QueryContent::QUERY_TYPE_UNSPECIFIED) { + content.set_type(YandexQuery::QueryContent::ANALYTICS); + } + + if (content.acl().visibility() == YandexQuery::Acl::VISIBILITY_UNSPECIFIED) { + content.mutable_acl()->set_visibility(YandexQuery::Acl::PRIVATE); + } + + content.set_automatic(true); +} + +void FqConvert(const YandexQuery::CreateQueryResult& src, FederatedQueryHttp::CreateQueryResult& dst) { + SIMPLE_COPY_RENAME_FIELD(query_id, id); +} + +void FqConvert(const YandexQuery::CommonMeta& src, FederatedQueryHttp::QueryMeta& dst) { + SIMPLE_COPY_MUTABLE_FIELD(created_at); +} + +void FqConvert(const YandexQuery::QueryMeta& src, FederatedQueryHttp::QueryMeta& dst) { + SIMPLE_COPY_MUTABLE_FIELD(submitted_at); + SIMPLE_COPY_MUTABLE_FIELD(finished_at); + FqConvert(src.common(), dst); +} + +FederatedQueryHttp::GetQueryResult::ComputeStatus RemapQueryStatus(YandexQuery::QueryMeta::ComputeStatus status) { + switch (status) { + case YandexQuery::QueryMeta::COMPLETED: + return FederatedQueryHttp::GetQueryResult::COMPLETED; + + case YandexQuery::QueryMeta::ABORTED_BY_USER: + [[fallthrough]]; + case YandexQuery::QueryMeta::ABORTING_BY_SYSTEM: + [[fallthrough]]; + case YandexQuery::QueryMeta::FAILED: + return FederatedQueryHttp::GetQueryResult::FAILED; + + default: + return FederatedQueryHttp::GetQueryResult::RUNNING; + } +} + +void FqConvert(const YandexQuery::ResultSetMeta& src, FederatedQueryHttp::ResultSetMeta& dst) { + SIMPLE_COPY_FIELD(rows_count); + SIMPLE_COPY_FIELD(truncated); +} + +void FqConvert(const YandexQuery::Query& src, FederatedQueryHttp::GetQueryResult& dst) { + FQ_CONVERT_FIELD(meta); + + FqConvert(src.content(), dst); + dst.set_id(src.meta().common().id()); + dst.set_status(RemapQueryStatus(src.meta().status())); + + for (const auto& result_meta : src.result_set_meta()) { + FqConvert(result_meta, *dst.mutable_result_sets()->Add()); + } + + SIMPLE_COPY_MUTABLE_RENAME_FIELD(issue, issues); + dst.mutable_issues()->MergeFrom(src.transient_issue()); +} + +void FqConvert(const FederatedQueryHttp::GetQueryRequest& src, YandexQuery::DescribeQueryRequest& dst) { + SIMPLE_COPY_FIELD(query_id); +} + +void FqConvert(const YandexQuery::DescribeQueryResult& src, FederatedQueryHttp::GetQueryResult& dst) { + FqConvert(src.query(), dst); +} + +void FqConvert(const FederatedQueryHttp::DeleteQueryRequest& src, YandexQuery::DeleteQueryRequest& dst) { + SIMPLE_COPY_FIELD(query_id); +} + +void FqConvert(const FederatedQueryHttp::GetQueryStatusRequest& src, YandexQuery::GetQueryStatusRequest& dst) { + SIMPLE_COPY_FIELD(query_id); +} + +void FqConvert(const YandexQuery::GetQueryStatusResult& src, FederatedQueryHttp::GetQueryStatusResult& dst) { + dst.set_status(RemapQueryStatus(src.status())); +} + +void FqConvert(const FederatedQueryHttp::StopQueryRequest& src, YandexQuery::ControlQueryRequest& dst) { + SIMPLE_COPY_FIELD(query_id); + dst.set_action(YandexQuery::ABORT); +} + +void FqConvert(const FederatedQueryHttp::GetResultDataRequest& src, YandexQuery::GetResultDataRequest& dst) { + SIMPLE_COPY_FIELD(query_id); + SIMPLE_COPY_FIELD(result_set_index); + SIMPLE_COPY_FIELD(offset); + SIMPLE_COPY_FIELD(limit); + + if (!dst.limit()) { + dst.set_limit(100); + } +} + +void FqConvert(const YandexQuery::GetResultDataResult& src, FederatedQueryHttp::GetResultDataResult& dst) { + SIMPLE_COPY_MUTABLE_FIELD(result_set); +} + +template <typename T> +void FqPackToJson(TStringStream& json, const T& httpResult, const TJsonSettings& jsonSettings) { + TProtoToJson::ProtoToJson(json, httpResult, jsonSettings); +} + +void FqPackToJson(TStringStream& json, const FederatedQueryHttp::GetResultDataResult& httpResult, const TJsonSettings&) { + auto resultSet = NYdb::TResultSet(httpResult.result_set()); + NJson::TJsonValue v; + NYq::FormatResultSet(v, resultSet, true); + NJson::TJsonWriterConfig jsonWriterConfig; + jsonWriterConfig.WriteNanAsString = true; + NJson::WriteJson(&json, &v, jsonWriterConfig); +} + +template <typename GrpcProtoRequestType, typename HttpProtoRequestType, typename GrpcProtoResultType, typename HttpProtoResultType, typename GrpcProtoResponseType> +class TGrpcCallWrapper : public TActorBootstrapped<TGrpcCallWrapper<GrpcProtoRequestType, HttpProtoRequestType, GrpcProtoResultType, HttpProtoResultType, GrpcProtoResponseType>> { + IViewer* const Viewer; + const TRequest Request; + + typedef std::function<std::unique_ptr<NGRpcService::TEvProxyRuntimeEvent>(TIntrusivePtr<NGrpc::IRequestContextBase> ctx)> TGrpcProxyEventFactory; + TGrpcProxyEventFactory EventFactory; + + NProtobufJson::TJson2ProtoConfig Json2ProtoConfig; + +public: + typedef GrpcProtoRequestType TGrpcProtoRequestType; + typedef HttpProtoRequestType THttpProtoRequestType; + typedef GrpcProtoResultType TGrpcProtoResultType; + typedef HttpProtoResultType THttpProtoResultType; + typedef GrpcProtoResponseType TGrpcProtoResponseType; + +public: + static constexpr NKikimrServices::TActivity::EType ActorActivityType() { + return NKikimrServices::TActivity::VIEWER_HANDLER; + } + + TGrpcCallWrapper(IViewer* viewer, const TRequest& request, TGrpcProxyEventFactory eventFactory) + : Viewer(viewer) + , Request(request) + , EventFactory(eventFactory) + { + Json2ProtoConfig = NProtobufJson::TJson2ProtoConfig() + .SetFieldNameMode(NProtobufJson::TJson2ProtoConfig::FieldNameCamelCase) + .SetMapAsObject(true); + } + + const NMon::TEvHttpInfo::TPtr& GetEvent() const { + return Request.Event; + } + + void Bootstrap(const TActorContext& ctx) { + auto grpcRequest = std::make_unique<TGrpcProtoRequestType>(); + if (Parse(ctx, *grpcRequest)) { + TIntrusivePtr<TGrpcRequestContextWrapper> requestContext = new TGrpcRequestContextWrapper(ctx.ActorSystem(), Viewer, GetEvent(), std::move(grpcRequest), &SendReply); + ctx.Send(NGRpcService::CreateGRpcRequestProxyId(), EventFactory(requestContext).release()); + } + + this->Die(ctx); + } + + bool Parse(const TActorContext& ctx, TGrpcProtoRequestType& grpcRequest) { + try { + THttpProtoRequestType request; + const auto& httpRequest = GetEvent()->Get()->Request; + // todo: check Headers to copy idempotency-key into protobuf + if (httpRequest.GetMethod() == HTTP_METHOD_POST && httpRequest.GetHeader("content-type") == "application/json"sv) { + // todo: fix Duration + Timestamp parsing + // todo: PostContent is empty for PUT in monlib + NProtobufJson::Json2Proto(httpRequest.GetPostContent(), request, Json2ProtoConfig); + } + + const auto& params = httpRequest.GetParams(); + for (const auto& [name, value] : params) { + SetProtoMessageField(request, name, value); + } + + // path params should overwrite query params in case of conflict + for (const auto& [name, value] : Request.PathParams) { + SetProtoMessageField(request, name, value); + } + FqConvert(request, grpcRequest); + return true; + } catch (const std::exception& e) { + ReplyError(ctx, TStringBuilder() << "Error in parsing: " << e.what() << ", original text: " << GetEvent()->Get()->Request.GetPostContent()); + return false; + } + } + + static void SetProtoMessageField(THttpProtoRequestType& request, const TString& name, const TString& value) { + const Reflection* reflection = request.GetReflection(); + const Descriptor* descriptor = request.GetDescriptor(); + auto field = descriptor->FindFieldByLowercaseName(name); + if (!field) { + return; + } + + switch (field->cpp_type()) { + case FieldDescriptor::CPPTYPE_INT32: + return reflection->SetInt32(&request, field, FromString<i32>(value)); + case FieldDescriptor::CPPTYPE_INT64: + return reflection->SetInt64(&request, field, FromString<i64>(value)); + case FieldDescriptor::CPPTYPE_UINT32: + return reflection->SetUInt32(&request, field, FromString<ui32>(value)); + case FieldDescriptor::CPPTYPE_UINT64: + return reflection->SetUInt64(&request, field, FromString<ui64>(value)); + case FieldDescriptor::CPPTYPE_STRING: + return reflection->SetString(&request, field, value); + default: + break; + } + } + + void ReplyError(const TActorContext& ctx, const TString& error) { + ctx.Send(GetEvent()->Sender, new NMon::TEvHttpInfoRes(TStringBuilder() << HTTPBADREQUESTJSON << error, 0, NMon::IEvHttpInfoRes::EContentType::Custom)); + } + + static void SendReply(TActorSystem* actorSystem, IViewer* viewer, const NMon::TEvHttpInfo::TPtr& event, const TJsonSettings& jsonSettings, NProtoBuf::Message* resp, ui32 status) { + Y_VERIFY(resp); + Y_VERIFY(resp->GetArena()); + Y_UNUSED(status); + auto* typedResponse = static_cast<TGrpcProtoResponseType*>(resp); + if (!typedResponse->operation().result().template Is<TGrpcProtoResultType>()) { + TStringStream json; + auto* httpResult = google::protobuf::Arena::CreateMessage<FederatedQueryHttp::Error>(resp->GetArena()); + FqConvert(typedResponse->operation(), *httpResult); + FqPackToJson(json, *httpResult, jsonSettings); + + // todo: remap ydb status to http code + actorSystem->Send(event->Sender, new NMon::TEvHttpInfoRes(TStringBuilder() << HTTPBADREQUESTJSON << json.Str(), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); + return; + } + + auto* grpcResult = google::protobuf::Arena::CreateMessage<TGrpcProtoResultType>(resp->GetArena()); + typedResponse->operation().result().UnpackTo(grpcResult); + + if (THttpProtoResultType::descriptor()->full_name() == google::protobuf::Empty::descriptor()->full_name()) { + actorSystem->Send(event->Sender, new NMon::TEvHttpInfoRes( + "HTTP/1.1 204 No Content\r\n" + "Connection: Keep-Alive\r\n\r\n", 0, NMon::IEvHttpInfoRes::EContentType::Custom)); + return; + } + + TStringStream json; + auto* httpResult = google::protobuf::Arena::CreateMessage<THttpProtoResultType>(resp->GetArena()); + FqConvert(*grpcResult, *httpResult); + FqPackToJson(json, *httpResult, jsonSettings); + + actorSystem->Send(event->Sender, new NMon::TEvHttpInfoRes(viewer->GetHTTPOKJSON(event->Get()) + json.Str(), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); + } +}; + +template <typename ProtoType> +void ProtoToPublicJsonSchema(IOutputStream& to) { + TJsonSettings settings; + settings.EnumAsNumbers = false; + settings.EmptyRepeated = false; + settings.EnumValueFilter = [](const TString& value) { + return !value.EndsWith("UNSPECIFIED"); + }; + + TProtoToJson::ProtoToJsonSchema<ProtoType>(to, settings); +} + + +#define DECLARE_YQ_GRPC_ACTOR_IMPL(action, internalAction, t1, t2, t3, t4, t5) \ +class TJson##action : public TGrpcCallWrapper<t1, t2, t3, t4, t5> { \ + typedef TGrpcCallWrapper<t1, t2, t3, t4, t5> TBase; \ +public: \ + TJson##action(IViewer* viewer, const TRequest& request) \ + : TBase(viewer, request, &NGRpcService::Create##internalAction##RequestOperationCall) {} \ +} + +#define DECLARE_YQ_GRPC_ACTOR(action, internalAction) DECLARE_YQ_GRPC_ACTOR_IMPL(action, internalAction, YandexQuery::internalAction##Request, FederatedQueryHttp::action##Request, YandexQuery::internalAction##Result, FederatedQueryHttp::action##Result, YandexQuery::internalAction##Response) +#define DECLARE_YQ_GRPC_ACTOR_WIHT_EMPTY_RESULT(action, internalAction) DECLARE_YQ_GRPC_ACTOR_IMPL(action, internalAction, YandexQuery::internalAction##Request, FederatedQueryHttp::action##Request, YandexQuery::internalAction##Result, ::google::protobuf::Empty, YandexQuery::internalAction##Response) + +//#define DECLARE_YQ_GRPC_ACTOR(action) DECLARE_YQ_GRPC_ACTOR_IMPL(action, action, YandexQuery::action##Request, YandexQuery::action##Request, YandexQuery::action##Result, YandexQuery::action##Result, YandexQuery::action##Response) +//#define DECLARE_YQ_GRPC_ACTOR_WITH_REMAPPING(action, internalAction) DECLARE_YQ_GRPC_ACTOR_IMPL(action, internalAction, YandexQuery::internalAction##Request, FederatedQueryHttp::action##Request, YandexQuery::internalAction##Result, FederatedQueryHttp::action##Result, YandexQuery::internalAction##Response) +//#define DECLARE_YQ_GRPC_ACTOR_WITH_REMAPPING_RESULT(action, internalAction, resultType) DECLARE_YQ_GRPC_ACTOR_IMPL(action, internalAction, YandexQuery::internalAction##Request, FederatedQueryHttp::action##Request, YandexQuery::internalAction##Result, resultType, YandexQuery::internalAction##Response) + +// create queries +DECLARE_YQ_GRPC_ACTOR(CreateQuery, CreateQuery); + +template <> +struct TJsonRequestSchema<TJsonCreateQuery> { + static TString GetSchema() { + TStringStream stream; + ProtoToPublicJsonSchema<FederatedQueryHttp::CreateQueryResult>(stream); + return stream.Str(); + } +}; + +template <> +struct TJsonRequestParameters<TJsonCreateQuery> { + static TString GetParameters() { + TStringStream stream; + ProtoToPublicJsonSchema<FederatedQueryHttp::CreateQueryRequest>(stream); + auto bodyScheme = stream.Str(); + + return TStringBuilder() << R"___([ + {"name":"folder_id","in":"path","description":"folder id","required":true,"type":"string"}, + {"name":"body","in":"body","required":true, "schema": )___" << bodyScheme << "}]"; + } +}; + +template <> +struct TJsonRequestSummary<TJsonCreateQuery> { + static TString GetSummary() { + return "\"Create new query\""; + } +}; + +template <> +struct TJsonRequestDescription<TJsonCreateQuery> { + static TString GetDescription() { + return "\"Create new query and optionally run it\""; + } +}; + +// describe query +DECLARE_YQ_GRPC_ACTOR(GetQuery, DescribeQuery); + +template <> +struct TJsonRequestSchema<TJsonGetQuery> { + static TString GetSchema() { + TStringStream stream; + ProtoToPublicJsonSchema<FederatedQueryHttp::GetQueryResult>(stream); + return stream.Str(); + } +}; + +template <> +struct TJsonRequestParameters<TJsonGetQuery> { + static TString GetParameters() { + return R"___([ + {"name":"folder_id","in":"path","description":"folder id","required":true,"type":"string"}, + {"name":"query_id","in":"path","description":"query id", "required": true, "type":"string"} + ])___"; + } +}; + +template <> +struct TJsonRequestSummary<TJsonGetQuery> { + static TString GetSummary() { + return "\"Get information about query\""; + } +}; + +template <> +struct TJsonRequestDescription<TJsonGetQuery> { + static TString GetDescription() { + return "\"Get detailed information about specified query\""; + } +}; + +// delete query +DECLARE_YQ_GRPC_ACTOR_WIHT_EMPTY_RESULT(DeleteQuery, DeleteQuery); + +template <> +struct TJsonRequestSchema<TJsonDeleteQuery> { + static TString GetSchema() { + TStringStream stream; + ProtoToPublicJsonSchema<FederatedQueryHttp::DeleteQueryResult>(stream); + return stream.Str(); + } +}; + +template <> +struct TJsonRequestParameters<TJsonDeleteQuery> { + static TString GetParameters() { + return R"___([ + {"name":"folder_id","in":"path","description":"folder id","required":true,"type":"string"}, + {"name":"idempotency-key","in":"header","description":"idempotency key", "required": false, "type":"string"}, + {"name":"query_id","in":"path","description":"query id", "required": true, "type":"string"} + ])___"; + } +}; + +template <> +struct TJsonRequestSummary<TJsonDeleteQuery> { + static TString GetSummary() { + return "\"Delete query\""; + } +}; + +template <> +struct TJsonRequestDescription<TJsonDeleteQuery> { + static TString GetDescription() { + return "\"Delete existing query by id\""; + } +}; + +// get query status +DECLARE_YQ_GRPC_ACTOR(GetQueryStatus, GetQueryStatus); + +template <> +struct TJsonRequestSchema<TJsonGetQueryStatus> { + static TString GetSchema() { + TStringStream stream; + ProtoToPublicJsonSchema<FederatedQueryHttp::GetQueryStatusResult>(stream); + return stream.Str(); + } +}; + +template <> +struct TJsonRequestParameters<TJsonGetQueryStatus> { + static TString GetParameters() { + return R"___([ + {"name":"folder_id","in":"path","description":"folder id","required":true,"type":"string"}, + {"name":"idempotency-key","in":"header","description":"idempotency key", "required": false, "type":"string"}, + {"name":"query_id","in":"path","description":"query id", "required": true, "type":"string"} + ])___"; + } +}; + +template <> +struct TJsonRequestSummary<TJsonGetQueryStatus> { + static TString GetSummary() { + return "\"Get query status\""; + } +}; + +template <> +struct TJsonRequestDescription<TJsonGetQueryStatus> { + static TString GetDescription() { + return "\"Get status of the query\""; + } +}; + +// stop query +DECLARE_YQ_GRPC_ACTOR_WIHT_EMPTY_RESULT(StopQuery, ControlQuery); + +template <> +struct TJsonRequestSchema<TJsonStopQuery> { + static TString GetSchema() { + TStringStream stream; + ProtoToPublicJsonSchema<FederatedQueryHttp::StopQueryResult>(stream); + return stream.Str(); + } +}; + +template <> +struct TJsonRequestParameters<TJsonStopQuery> { + static TString GetParameters() { + TStringStream stream; + ProtoToPublicJsonSchema<FederatedQueryHttp::StopQueryRequest>(stream); + auto bodyScheme = stream.Str(); + + return TStringBuilder() << R"___([ + {"name":"folder_id","in":"path","description":"folder id","required":true,"type":"string"}, + {"name":"idempotency-key","in":"header","description":"idempotency key", "required": false, "type":"string"}, + {"name":"query_id","in":"path","description":"query id", "required": true, "type":"string"}, + {"name":"body","in":"body","required":false, "schema": )___" << bodyScheme << "}]"; + } +}; + +template <> +struct TJsonRequestSummary<TJsonStopQuery> { + static TString GetSummary() { + return "\"Stop query\""; + } +}; + +template <> +struct TJsonRequestDescription<TJsonStopQuery> { + static TString GetDescription() { + return "\"Stop running query\""; + } +}; + +// get result data +DECLARE_YQ_GRPC_ACTOR(GetResultData, GetResultData); + +template <> +struct TJsonRequestSchema<TJsonGetResultData> { + static TString GetSchema() { + TStringStream stream; + ProtoToPublicJsonSchema<FederatedQueryHttp::GetResultDataResult>(stream); + return stream.Str(); + } +}; + +template <> +struct TJsonRequestParameters<TJsonGetResultData> { + static TString GetParameters() { + return R"___([ + {"name":"folder_id","in":"path","description":"folder id","required":true,"type":"string"}, + {"name":"idempotency-key","in":"header","description":"idempotency key", "required": false, "type":"string"}, + {"name":"query_id","in":"path","description":"query id", "required": true, "type":"string"}, + {"name":"result_set_index","in":"query","description":"result set index","required": true, "type":"integer"}, + {"name":"offset","in":"query","description":"row offset","default":0, "type":"integer"}, + {"name":"limit","in":"query","description":"row limit","default":100, "type":"integer"} + ])___"; + } +}; + +template <> +struct TJsonRequestSummary<TJsonGetResultData> { + static TString GetSummary() { + return "\"Get query results\""; + } +}; + +template <> +struct TJsonRequestDescription<TJsonGetResultData> { + static TString GetDescription() { + return "\"Get query results\""; + } +}; + +template <> +void TFQJsonHandlers::Init() { + Router.RegisterHandler(HTTP_METHOD_POST, "/fq/json/queries", std::make_shared<TJsonHandler<TJsonCreateQuery>>()); + Router.RegisterHandler(HTTP_METHOD_GET, "/fq/json/queries/{query_id}", std::make_shared<TJsonHandler<TJsonGetQuery>>()); + Router.RegisterHandler(HTTP_METHOD_DELETE, "/fq/json/queries/{query_id}", std::make_shared<TJsonHandler<TJsonDeleteQuery>>()); + Router.RegisterHandler(HTTP_METHOD_GET, "/fq/json/queries/{query_id}/status", std::make_shared<TJsonHandler<TJsonGetQueryStatus>>()); + Router.RegisterHandler(HTTP_METHOD_GET, "/fq/json/queries/{query_id}/results", std::make_shared<TJsonHandler<TJsonGetResultData>>()); + Router.RegisterHandler(HTTP_METHOD_POST, "/fq/json/queries/{query_id}/stop", std::make_shared<TJsonHandler<TJsonStopQuery>>()); +} + +} +} diff --git a/ydb/core/viewer/json_handlers_vdisk.cpp b/ydb/core/viewer/json_handlers_vdisk.cpp index 4684d8a2ffa..a80b01f7755 100644 --- a/ydb/core/viewer/json_handlers_vdisk.cpp +++ b/ydb/core/viewer/json_handlers_vdisk.cpp @@ -8,8 +8,8 @@ namespace NKikimr::NViewer { template <> void TVDiskJsonHandlers::Init() { - JsonHandlers["vdisk/json/vdiskstat"] = new TJsonHandler<TJsonVDiskStat>; - JsonHandlers["vdisk/json/getblob"] = new TJsonHandler<TJsonGetBlob>; + Router.RegisterGetHandler("/vdisk/json/vdiskstat", std::make_shared<TJsonHandler<TJsonVDiskStat>>()); + Router.RegisterGetHandler("/vdisk/json/getblob", std::make_shared<TJsonHandler<TJsonGetBlob>>()); } } diff --git a/ydb/core/viewer/json_handlers_viewer.cpp b/ydb/core/viewer/json_handlers_viewer.cpp index a564147bb98..57dde5c5bcf 100644 --- a/ydb/core/viewer/json_handlers_viewer.cpp +++ b/ydb/core/viewer/json_handlers_viewer.cpp @@ -39,36 +39,38 @@ namespace NKikimr::NViewer { template <> void TViewerJsonHandlers::Init() { - JsonHandlers["/json/nodelist"] = new TJsonHandler<TJsonNodeList>; - JsonHandlers["/json/nodeinfo"] = new TJsonHandler<TJsonNodeInfo>; - JsonHandlers["/json/vdiskinfo"] = new TJsonHandler<TJsonVDiskInfo>; - JsonHandlers["/json/pdiskinfo"] = new TJsonHandler<TJsonPDiskInfo>; - JsonHandlers["/json/describe"] = new TJsonHandler<TJsonDescribe>; - JsonHandlers["/json/hotkeys"] = new TJsonHandler<TJsonHotkeys>; - JsonHandlers["/json/sysinfo"] = new TJsonHandler<TJsonSysInfo>; - JsonHandlers["/json/tabletinfo"] = new TJsonHandler<TJsonTabletInfo>; - JsonHandlers["/json/hiveinfo"] = new TJsonHandler<TJsonHiveInfo>; - JsonHandlers["/json/bsgroupinfo"] = new TJsonHandler<TJsonBSGroupInfo>; - JsonHandlers["/json/bscontrollerinfo"] = new TJsonHandler<TJsonBSControllerInfo>; - JsonHandlers["/json/config"] = new TJsonHandler<TJsonConfig>; - JsonHandlers["/json/counters"] = new TJsonHandler<TJsonCounters>; - JsonHandlers["/json/topicinfo"] = new TJsonHandler<TJsonTopicInfo>; - JsonHandlers["/json/pqconsumerinfo"] = new TJsonHandler<TJsonPQConsumerInfo>(); - JsonHandlers["/json/tabletcounters"] = new TJsonHandler<TJsonTabletCounters>; - JsonHandlers["/json/storage"] = new TJsonHandler<TJsonStorage>; - JsonHandlers["/json/metainfo"] = new TJsonHandler<TJsonMetaInfo>; - JsonHandlers["/json/browse"] = new TJsonHandler<TJsonBrowse>; - JsonHandlers["/json/cluster"] = new TJsonHandler<TJsonCluster>; - JsonHandlers["/json/content"] = new TJsonHandler<TJsonContent>; - JsonHandlers["/json/labeledcounters"] = new TJsonHandler<TJsonLabeledCounters>; - JsonHandlers["/json/tenants"] = new TJsonHandler<TJsonTenants>; - JsonHandlers["/json/hivestats"] = new TJsonHandler<TJsonHiveStats>; - JsonHandlers["/json/tenantinfo"] = new TJsonHandler<TJsonTenantInfo>; - JsonHandlers["/json/whoami"] = new TJsonHandler<TJsonWhoAmI>; - JsonHandlers["/json/query"] = new TJsonHandler<TJsonQuery>; - JsonHandlers["/json/netinfo"] = new TJsonHandler<TJsonNetInfo>; - JsonHandlers["/json/compute"] = new TJsonHandler<TJsonCompute>; - JsonHandlers["/json/healthcheck"] = new TJsonHandler<TJsonHealthCheck>; - JsonHandlers["/json/nodes"] = new TJsonHandler<TJsonNodes>; - JsonHandlers["/json/acl"] = new TJsonHandler<TJsonACL>; -}} + Router.RegisterGetHandler("/json/nodelist", std::make_shared<TJsonHandler<TJsonNodeList>>()); + Router.RegisterGetHandler("/json/nodeinfo", std::make_shared<TJsonHandler<TJsonNodeInfo>>()); + Router.RegisterGetHandler("/json/vdiskinfo", std::make_shared<TJsonHandler<TJsonVDiskInfo>>()); + Router.RegisterGetHandler("/json/pdiskinfo", std::make_shared<TJsonHandler<TJsonPDiskInfo>>()); + Router.RegisterGetHandler("/json/describe", std::make_shared<TJsonHandler<TJsonDescribe>>()); + Router.RegisterGetHandler("/json/hotkeys", std::make_shared<TJsonHandler<TJsonHotkeys>>()); + Router.RegisterGetHandler("/json/sysinfo", std::make_shared<TJsonHandler<TJsonSysInfo>>()); + Router.RegisterGetHandler("/json/tabletinfo", std::make_shared<TJsonHandler<TJsonTabletInfo>>()); + Router.RegisterGetHandler("/json/hiveinfo", std::make_shared<TJsonHandler<TJsonHiveInfo>>()); + Router.RegisterGetHandler("/json/bsgroupinfo", std::make_shared<TJsonHandler<TJsonBSGroupInfo>>()); + Router.RegisterGetHandler("/json/bscontrollerinfo", std::make_shared<TJsonHandler<TJsonBSControllerInfo>>()); + Router.RegisterGetHandler("/json/config", std::make_shared<TJsonHandler<TJsonConfig>>()); + Router.RegisterGetHandler("/json/counters", std::make_shared<TJsonHandler<TJsonCounters>>()); + Router.RegisterGetHandler("/json/topicinfo", std::make_shared<TJsonHandler<TJsonTopicInfo>>()); + Router.RegisterGetHandler("/json/pqconsumerinfo", std::make_shared<TJsonHandler<TJsonPQConsumerInfo>>()); + Router.RegisterGetHandler("/json/tabletcounters", std::make_shared<TJsonHandler<TJsonTabletCounters>>()); + Router.RegisterGetHandler("/json/storage", std::make_shared<TJsonHandler<TJsonStorage>>()); + Router.RegisterGetHandler("/json/metainfo", std::make_shared<TJsonHandler<TJsonMetaInfo>>()); + Router.RegisterGetHandler("/json/browse", std::make_shared<TJsonHandler<TJsonBrowse>>()); + Router.RegisterGetHandler("/json/cluster", std::make_shared<TJsonHandler<TJsonCluster>>()); + Router.RegisterGetHandler("/json/content", std::make_shared<TJsonHandler<TJsonContent>>()); + Router.RegisterGetHandler("/json/labeledcounters", std::make_shared<TJsonHandler<TJsonLabeledCounters>>()); + Router.RegisterGetHandler("/json/tenants", std::make_shared<TJsonHandler<TJsonTenants>>()); + Router.RegisterGetHandler("/json/hivestats", std::make_shared<TJsonHandler<TJsonHiveStats>>()); + Router.RegisterGetHandler("/json/tenantinfo", std::make_shared<TJsonHandler<TJsonTenantInfo>>()); + Router.RegisterGetHandler("/json/whoami", std::make_shared<TJsonHandler<TJsonWhoAmI>>()); + Router.RegisterGetHandler("/json/query", std::make_shared<TJsonHandler<TJsonQuery>>()); + Router.RegisterGetHandler("/json/netinfo", std::make_shared<TJsonHandler<TJsonNetInfo>>()); + Router.RegisterGetHandler("/json/compute", std::make_shared<TJsonHandler<TJsonCompute>>()); + Router.RegisterGetHandler("/json/healthcheck", std::make_shared<TJsonHandler<TJsonHealthCheck>>()); + Router.RegisterGetHandler("/json/nodes", std::make_shared<TJsonHandler<TJsonNodes>>()); + Router.RegisterGetHandler("/json/acl", std::make_shared<TJsonHandler<TJsonACL>>()); +} + +} diff --git a/ydb/core/viewer/json_healthcheck.h b/ydb/core/viewer/json_healthcheck.h index 2f0e5efa736..8739f6008ba 100644 --- a/ydb/core/viewer/json_healthcheck.h +++ b/ydb/core/viewer/json_healthcheck.h @@ -28,9 +28,9 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonHealthCheck(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + TJsonHealthCheck(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Event(ev) + , Event(request.Event) {} void Bootstrap() { diff --git a/ydb/core/viewer/json_hiveinfo.h b/ydb/core/viewer/json_hiveinfo.h index 0fa58d281d2..a229cb7096e 100644 --- a/ydb/core/viewer/json_hiveinfo.h +++ b/ydb/core/viewer/json_hiveinfo.h @@ -28,9 +28,9 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonHiveInfo(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev) + TJsonHiveInfo(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Event(ev) + , Event(request.Event) {} void Bootstrap() { diff --git a/ydb/core/viewer/json_hivestats.h b/ydb/core/viewer/json_hivestats.h index 4c2e6230069..0460526b813 100644 --- a/ydb/core/viewer/json_hivestats.h +++ b/ydb/core/viewer/json_hivestats.h @@ -27,9 +27,9 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonHiveStats(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev) + TJsonHiveStats(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Event(ev) + , Event(request.Event) {} void Bootstrap() { diff --git a/ydb/core/viewer/json_hotkeys.h b/ydb/core/viewer/json_hotkeys.h index 714a9aa17c4..6ec4cc372f7 100644 --- a/ydb/core/viewer/json_hotkeys.h +++ b/ydb/core/viewer/json_hotkeys.h @@ -41,9 +41,9 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonHotkeys(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev) + TJsonHotkeys(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Event(ev) + , Event(request.Event) {} void FillParams(NKikimrSchemeOp::TDescribePath* record, const TCgiParameters& params) { diff --git a/ydb/core/viewer/json_labeledcounters.h b/ydb/core/viewer/json_labeledcounters.h index 7db53ed4e28..11eaacbad04 100644 --- a/ydb/core/viewer/json_labeledcounters.h +++ b/ydb/core/viewer/json_labeledcounters.h @@ -33,9 +33,9 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonLabeledCounters(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev) + TJsonLabeledCounters(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Event(ev) + , Event(request.Event) {} void Bootstrap(const TActorContext& ctx) { diff --git a/ydb/core/viewer/json_metainfo.h b/ydb/core/viewer/json_metainfo.h index f507909e245..97c3bb69e0e 100644 --- a/ydb/core/viewer/json_metainfo.h +++ b/ydb/core/viewer/json_metainfo.h @@ -41,9 +41,9 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonMetaInfo(IViewer *viewer, NMon::TEvHttpInfo::TPtr &ev) + TJsonMetaInfo(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Event(ev) + , Event(request.Event) {} void Bootstrap(const TActorContext& ctx) { diff --git a/ydb/core/viewer/json_netinfo.h b/ydb/core/viewer/json_netinfo.h index 8d4ab1ddee1..3c0446ef771 100644 --- a/ydb/core/viewer/json_netinfo.h +++ b/ydb/core/viewer/json_netinfo.h @@ -44,9 +44,9 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonNetInfo(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + TJsonNetInfo(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Event(ev) + , Event(request.Event) {} void Bootstrap() { diff --git a/ydb/core/viewer/json_nodelist.h b/ydb/core/viewer/json_nodelist.h index 75122941515..c9653d465c4 100644 --- a/ydb/core/viewer/json_nodelist.h +++ b/ydb/core/viewer/json_nodelist.h @@ -22,9 +22,9 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonNodeList(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev) + TJsonNodeList(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Event(ev) + , Event(request.Event) {} void Bootstrap(const TActorContext& ctx) { diff --git a/ydb/core/viewer/json_nodes.h b/ydb/core/viewer/json_nodes.h index c6359409e01..cc58aaec7a3 100644 --- a/ydb/core/viewer/json_nodes.h +++ b/ydb/core/viewer/json_nodes.h @@ -54,10 +54,10 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonNodes(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + TJsonNodes(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Initiator(ev->Sender) - , Event(std::move(ev)) + , Initiator(request.Event->Sender) + , Event(request.Event) { const auto& params(Event->Get()->Request.GetParams()); JsonSettings.EnumAsNumbers = !FromStringWithDefault<bool>(params.Get("enums"), true); diff --git a/ydb/core/viewer/json_pqconsumerinfo.h b/ydb/core/viewer/json_pqconsumerinfo.h index b7526edf7df..e312b585fac 100644 --- a/ydb/core/viewer/json_pqconsumerinfo.h +++ b/ydb/core/viewer/json_pqconsumerinfo.h @@ -34,12 +34,9 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonPQConsumerInfo( - IViewer* viewer, - NMon::TEvHttpInfo::TPtr& ev - ) + TJsonPQConsumerInfo(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Event(ev) + , Event(request.Event) {} void Bootstrap(const TActorContext& ctx) { diff --git a/ydb/core/viewer/json_query.h b/ydb/core/viewer/json_query.h index ac32ad0fc5c..1a88425b7e7 100644 --- a/ydb/core/viewer/json_query.h +++ b/ydb/core/viewer/json_query.h @@ -40,10 +40,10 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonQuery(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + TJsonQuery(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Initiator(ev->Sender) - , Event(ev) + , Initiator(request.Event->Sender) + , Event(request.Event) {} STATEFN(StateWork) { diff --git a/ydb/core/viewer/json_storage.h b/ydb/core/viewer/json_storage.h index 4644f6f710c..b48fe875d73 100644 --- a/ydb/core/viewer/json_storage.h +++ b/ydb/core/viewer/json_storage.h @@ -65,10 +65,10 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonStorage(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + TJsonStorage(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Initiator(ev->Sender) - , Event(std::move(ev)) + , Initiator(request.Event->Sender) + , Event(request.Event) { const auto& params(Event->Get()->Request.GetParams()); JsonSettings.EnumAsNumbers = !FromStringWithDefault<bool>(params.Get("enums"), true); diff --git a/ydb/core/viewer/json_tabletcounters.h b/ydb/core/viewer/json_tabletcounters.h index 19202d95d16..6ac06fac553 100644 --- a/ydb/core/viewer/json_tabletcounters.h +++ b/ydb/core/viewer/json_tabletcounters.h @@ -33,9 +33,9 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonTabletCounters(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev) + TJsonTabletCounters(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Event(ev) + , Event(request.Event) {} static NTabletPipe::TClientConfig InitPipeClientConfig() { diff --git a/ydb/core/viewer/json_tabletinfo.h b/ydb/core/viewer/json_tabletinfo.h index 3f273e8e95b..2061dfe2fe2 100644 --- a/ydb/core/viewer/json_tabletinfo.h +++ b/ydb/core/viewer/json_tabletinfo.h @@ -55,8 +55,8 @@ class TJsonTabletInfo : public TJsonWhiteboardRequest<TEvWhiteboard::TEvTabletSt using TThis = TJsonTabletInfo; TVector<ui64> Tablets; public: - TJsonTabletInfo(IViewer *viewer, NMon::TEvHttpInfo::TPtr &ev) - : TJsonWhiteboardRequest(viewer, ev) + TJsonTabletInfo(IViewer* viewer, const TRequest& request) + : TJsonWhiteboardRequest(viewer, request) {} static NTabletPipe::TClientConfig InitPipeClientConfig() { diff --git a/ydb/core/viewer/json_tenantinfo.h b/ydb/core/viewer/json_tenantinfo.h index 9a27f8e9682..515350e8e72 100644 --- a/ydb/core/viewer/json_tenantinfo.h +++ b/ydb/core/viewer/json_tenantinfo.h @@ -49,9 +49,9 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonTenantInfo(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + TJsonTenantInfo(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Event(ev) + , Event(request.Event) {} TString GetDomainId(TPathId pathId) { diff --git a/ydb/core/viewer/json_tenants.h b/ydb/core/viewer/json_tenants.h index 098d15813c4..f44f4f516be 100644 --- a/ydb/core/viewer/json_tenants.h +++ b/ydb/core/viewer/json_tenants.h @@ -30,9 +30,9 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonTenants(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev) + TJsonTenants(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Event(ev) + , Event(request.Event) {} void Bootstrap() { diff --git a/ydb/core/viewer/json_topicinfo.h b/ydb/core/viewer/json_topicinfo.h index 54f4ccde24b..27109113be4 100644 --- a/ydb/core/viewer/json_topicinfo.h +++ b/ydb/core/viewer/json_topicinfo.h @@ -29,9 +29,9 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonTopicInfo(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev) + TJsonTopicInfo(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Event(ev) + , Event(request.Event) {} void Bootstrap(const TActorContext& ctx) { diff --git a/ydb/core/viewer/json_vdisk_req.h b/ydb/core/viewer/json_vdisk_req.h index 3a6c3d08844..20d5298bac4 100644 --- a/ydb/core/viewer/json_vdisk_req.h +++ b/ydb/core/viewer/json_vdisk_req.h @@ -66,10 +66,10 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonVDiskRequest(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + TJsonVDiskRequest(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Initiator(ev->Sender) - , Event(ev) + , Initiator(request.Event->Sender) + , Event(request.Event) {} virtual void Bootstrap() { diff --git a/ydb/core/viewer/json_wb_req.h b/ydb/core/viewer/json_wb_req.h index a0f0d9cdda8..8fd19767f30 100644 --- a/ydb/core/viewer/json_wb_req.h +++ b/ydb/core/viewer/json_wb_req.h @@ -66,10 +66,10 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonWhiteboardRequest(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + TJsonWhiteboardRequest(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Initiator(ev->Sender) - , Event(ev) + , Initiator(request.Event->Sender) + , Event(request.Event) {} THolder<RequestType> BuildRequest(TNodeId nodeId) { diff --git a/ydb/core/viewer/json_whoami.h b/ydb/core/viewer/json_whoami.h index fea374b41df..5bd48a75d40 100644 --- a/ydb/core/viewer/json_whoami.h +++ b/ydb/core/viewer/json_whoami.h @@ -22,9 +22,9 @@ public: return NKikimrServices::TActivity::VIEWER_HANDLER; } - TJsonWhoAmI(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev) + TJsonWhoAmI(IViewer* viewer, const TRequest& request) : Viewer(viewer) - , Event(ev) + , Event(request.Event) {} void Bootstrap(const TActorContext& ctx) { @@ -39,7 +39,7 @@ public: Y_PROTOBUF_SUPPRESS_NODISCARD userToken.ParseFromString(Event->Get()->UserToken); TStringStream json; TProtoToJson::ProtoToJson(json, userToken, JsonSettings); - ctx.Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPOKJSON(Event->Get()) + json.Str(), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); + ctx.Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPOKJSON(Event->Get()) +json.Str(), 0, NMon::IEvHttpInfoRes::EContentType::Custom)); Die(ctx); } diff --git a/ydb/core/viewer/protos/CMakeLists.txt b/ydb/core/viewer/protos/CMakeLists.txt index 9331c1366e2..a2cdf6fc63f 100644 --- a/ydb/core/viewer/protos/CMakeLists.txt +++ b/ydb/core/viewer/protos/CMakeLists.txt @@ -15,6 +15,7 @@ target_link_libraries(core-viewer-protos PUBLIC contrib-libs-protobuf ) target_proto_messages(core-viewer-protos PRIVATE + ${CMAKE_SOURCE_DIR}/ydb/core/viewer/protos/fq.proto ${CMAKE_SOURCE_DIR}/ydb/core/viewer/protos/viewer.proto ) target_proto_addincls(core-viewer-protos diff --git a/ydb/core/viewer/protos/fq.proto b/ydb/core/viewer/protos/fq.proto new file mode 100644 index 00000000000..2c6a19f833c --- /dev/null +++ b/ydb/core/viewer/protos/fq.proto @@ -0,0 +1,105 @@ +syntax = "proto3"; +option cc_enable_arenas = true; + +package FederatedQueryHttp; + +import "ydb/public/api/protos/annotations/validation.proto"; +import "ydb/public/api/protos/ydb_issue_message.proto"; +import "ydb/public/api/protos/ydb_status_codes.proto"; +import "ydb/public/api/protos/ydb_value.proto"; +import "google/protobuf/timestamp.proto"; +import "ydb/public/api/protos/yq.proto"; + +//////////////////////////////////////////////////////////// + +message Error { + Ydb.StatusIds.StatusCode status = 1; + repeated Ydb.Issue.IssueMessage issues = 2; +} + +message QueryMeta { + google.protobuf.Timestamp created_at = 1; + google.protobuf.Timestamp submitted_at = 2; + google.protobuf.Timestamp finished_at = 3; +} + +message Column { + string name = 1; + string type = 2; +} + +message ResultSetMeta { + int64 rows_count = 1 [(Ydb.value) = ">= 0"]; + bool truncated = 2; +} + +message CreateQueryRequest { + YandexQuery.QueryContent.QueryType type = 1; + string name = 2 [(Ydb.length).le = 1024]; + string text = 3 [(Ydb.length).le = 102400]; // The text of the query itself + string description = 4 [(Ydb.length).le = 10240]; // Description of the query, there can be any text +} + +message CreateQueryResult { + string id = 1 [(Ydb.length).le = 1024]; +} + +message GetQueryRequest { + string query_id = 1 [(Ydb.length).range = {min: 1, max: 1024}]; +} + +message GetQueryResult { + enum ComputeStatus { + COMPUTE_STATUS_UNSPECIFIED = 0; + RUNNING = 1; + COMPLETED = 2; + FAILED = 3; + } + + string id = 1; + YandexQuery.QueryContent.QueryType type = 2; + string name = 3 [(Ydb.length).le = 1024]; + string description = 4 [(Ydb.length).le = 10240]; // Description of the query, there can be any text + ComputeStatus status = 5; + string text = 6 [(Ydb.length).le = 102400]; // The text of the query itself + QueryMeta meta = 7; + repeated Ydb.Issue.IssueMessage issues = 8; + repeated ResultSetMeta result_sets = 9; +} + +message GetQueryStatusRequest { + string query_id = 1 [(Ydb.length).range = {min: 1, max: 1024}]; +} + +message GetQueryStatusResult { + GetQueryResult.ComputeStatus status = 1; +} + +message DeleteQueryRequest { + string query_id = 1 [(Ydb.length).range = {min: 1, max: 1024}]; +} + +message DeleteQueryResult { +} + +message StopQueryRequest { + string query_id = 1 [(Ydb.length).range = {min: 1, max: 1024}]; +} + +message StopQueryResult { +} + +message GetResultDataRequest { + string query_id = 1 [(Ydb.length).range = {min: 1, max: 1024}]; + int32 result_set_index = 2 [(Ydb.value) = ">= 0"]; + int64 offset = 3 [(Ydb.value) = ">= 0"]; + int64 limit = 4 [(Ydb.value) = "[1; 1000]"]; +} + +message RowData { + repeated string data = 1; +} + +message GetResultDataResult { + Ydb.ResultSet result_set = 1; +} diff --git a/ydb/core/viewer/ut/CMakeLists.darwin.txt b/ydb/core/viewer/ut/CMakeLists.darwin.txt index ea1a9b5c678..b7c4189ce10 100644 --- a/ydb/core/viewer/ut/CMakeLists.darwin.txt +++ b/ydb/core/viewer/ut/CMakeLists.darwin.txt @@ -31,6 +31,7 @@ target_link_options(ydb-core-viewer-ut PRIVATE CoreFoundation ) target_sources(ydb-core-viewer-ut PRIVATE + ${CMAKE_SOURCE_DIR}/ydb/core/viewer/http_router_ut.cpp ${CMAKE_SOURCE_DIR}/ydb/core/viewer/viewer_ut.cpp ) add_test( diff --git a/ydb/core/viewer/ut/CMakeLists.linux.txt b/ydb/core/viewer/ut/CMakeLists.linux.txt index bd8010e7ddf..6ef556bbe63 100644 --- a/ydb/core/viewer/ut/CMakeLists.linux.txt +++ b/ydb/core/viewer/ut/CMakeLists.linux.txt @@ -35,6 +35,7 @@ target_link_options(ydb-core-viewer-ut PRIVATE -ldl ) target_sources(ydb-core-viewer-ut PRIVATE + ${CMAKE_SOURCE_DIR}/ydb/core/viewer/http_router_ut.cpp ${CMAKE_SOURCE_DIR}/ydb/core/viewer/viewer_ut.cpp ) add_test( diff --git a/ydb/core/viewer/viewer.cpp b/ydb/core/viewer/viewer.cpp index d171e6a684e..46a270124a7 100644 --- a/ydb/core/viewer/viewer.cpp +++ b/ydb/core/viewer/viewer.cpp @@ -25,6 +25,7 @@ #include "json_bsgroupinfo.h" #include "json_nodeinfo.h" #include "json_vdiskinfo.h" +#include "http_router.h" namespace NKikimr { @@ -32,7 +33,6 @@ namespace NViewer { using namespace NNodeWhiteboard; - void SetupPQVirtualHandlers(IViewer* viewer) { viewer->RegisterVirtualHandler( NKikimrViewer::EObjectType::Root, @@ -118,6 +118,13 @@ public: .UseAuth = true, .AllowedSIDs = allowedSIDs, }); + mon->RegisterActorPage({ + .Title = "FederatedQuery", + .RelPath = "fq", + .ActorSystem = ctx.ExecutorThread.ActorSystem, + .ActorId = ctx.SelfID, + .UseAuth = false, + }); auto whiteboardServiceId = NNodeWhiteboard::MakeNodeWhiteboardServiceId(ctx.SelfID.NodeId()); ctx.Send(whiteboardServiceId, new NNodeWhiteboard::TEvWhiteboard::TEvSystemStateAddEndpoint( "http-mon", Sprintf(":%d", KikimrRunConfig.AppConfig.GetMonitoringConfig().GetMonitoringPort()))); @@ -126,6 +133,7 @@ public: ViewerJsonHandlers.Init(); VDiskJsonHandlers.Init(); + FQJsonHandlers.Init(); TWhiteboardInfo<TEvWhiteboard::TEvNodeStateResponse>::InitMerger(); TWhiteboardInfo<TEvWhiteboard::TEvBSGroupStateResponse>::InitMerger(); @@ -170,7 +178,8 @@ public: private: TViewerJsonHandlers ViewerJsonHandlers; TVDiskJsonHandlers VDiskJsonHandlers; - THashMap<TString, TAutoPtr<TJsonHandlerBase>> JsonHandlers; + TFQJsonHandlers FQJsonHandlers; + const TKikimrRunConfig KikimrRunConfig; std::unordered_multimap<NKikimrViewer::EObjectType, TVirtualHandler> VirtualHandlersByParentType; std::unordered_map<NKikimrViewer::EObjectType, TContentHandler> ContentHandlers; @@ -206,13 +215,22 @@ private: json << "},"; json << "\"basePath\": \"/\","; json << "\"schemes\": [\"" << protocol << "\"],"; - json << R"___("consumes": ["application/json"], + json << R"___("securityDefinitions": { + "token": { + "type": "apiKey", + "in": "header", + "name": "authorization", + "description": "Authentication token" + } + }, + "consumes": ["application/json"], "produces": ["application/json"], "paths": {)___"; - ViewerJsonHandlers.PrintForSwagger(json); json << ','; VDiskJsonHandlers.PrintForSwagger(json); + json << ','; + FQJsonHandlers.PrintForSwagger(json); json << R"___(},"definitions":{)___"; json << R"___(}})___"; @@ -318,12 +336,18 @@ private: if (msg->Request.GetPathInfo().StartsWith("/json/")) { if (filename.StartsWith("viewer")) { ViewerJsonHandlers.Handle(this, ev, ctx); + return; } if (filename.StartsWith("vdisk")) { VDiskJsonHandlers.Handle(this, ev, ctx); + return; + } + if (filename.StartsWith("fq")) { + FQJsonHandlers.Handle(this, ev, ctx); + return; } - return; } + if (filename.StartsWith("counters/hosts")) { ctx.ExecutorThread.RegisterActor(new TCountersHostsList(this, ev)); return; diff --git a/ydb/core/viewer/viewer.h b/ydb/core/viewer/viewer.h index 515908ad09d..8c107c2e37d 100644 --- a/ydb/core/viewer/viewer.h +++ b/ydb/core/viewer/viewer.h @@ -145,40 +145,32 @@ static const char HTTPOKJSON[] = "HTTP/1.1 200 Ok\r\nAccess-Control-Allow-Origin static const char HTTPOKTEXT[] = "HTTP/1.1 200 Ok\r\nAccess-Control-Allow-Origin: *\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: Close\r\n\r\n"; static const char HTTPFORBIDDENJSON[] = "HTTP/1.1 403 Forbidden\r\nAccess-Control-Allow-Origin: *\r\nContent-Type: application/json; charset=utf-8\r\nConnection: Close\r\n\r\n"; static const char HTTPGATEWAYTIMEOUT[] = "HTTP/1.1 504 Gateway Time-out\r\nConnection: Close\r\n\r\nGateway Time-out\r\n"; -static const char HTTPBADREQUEST[] = "HTTP/1.1 400 Bad Request\r\nConnection: Close\r\n\r\nBad Request\r\n"; -static const char HTTPBADREQUEST_HEADERS[] = "HTTP/1.1 400 Bad Request\r\nConnection: Close\r\n\r\n"; +static const char HTTPBADREQUEST[] = "HTTP/1.1 400 Bad Request\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: Close\r\n\r\nBad Request\r\n"; +static const char HTTPBADREQUEST_HEADERS[] = "HTTP/1.1 400 Bad Request\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: Close\r\n\r\n"; +static const char HTTPBADREQUESTJSON[] = "HTTP/1.1 400 Bad Request\r\nAccess-Control-Allow-Origin: *\r\nContent-Type: application/json; charset=utf-8\r\nConnection: Close\r\n\r\n"; +static const char HTTPUNAUTHORIZEDTEXT[] = "HTTP/1.1 401 Unauthorized \r\nAccess-Control-Allow-Origin: *\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: Close\r\n\r\n"; -template <typename ValueType> -void SplitIds(TStringBuf source, char delim, TVector<ValueType>& values) { +template <typename ValueType, typename OutputIteratorType> +void GenericSplitIds(TStringBuf source, char delim, OutputIteratorType it) { for (TStringBuf value = source.NextTok(delim); !value.empty(); value = source.NextTok(delim)) { - if (value == ".") { - values.emplace_back(ValueType()); - } else { - values.emplace_back(FromStringWithDefault<ValueType>(value, ValueType())); - } + const auto newValue = (value == ".") ? ValueType() : FromStringWithDefault<ValueType>(value, ValueType()); + *(it++) = newValue; } } template <typename ValueType> +void SplitIds(TStringBuf source, char delim, TVector<ValueType>& values) { + GenericSplitIds<ValueType>(source, delim, std::back_inserter(values)); +} + +template <typename ValueType> void SplitIds(TStringBuf source, char delim, std::vector<ValueType>& values) { - for (TStringBuf value = source.NextTok(delim); !value.empty(); value = source.NextTok(delim)) { - if (value == ".") { - values.emplace_back(ValueType()); - } else { - values.emplace_back(FromStringWithDefault<ValueType>(value, ValueType())); - } - } + GenericSplitIds<ValueType>(source, delim, std::back_inserter(values)); } template <typename ValueType> void SplitIds(TStringBuf source, char delim, std::unordered_set<ValueType>& values) { - for (TStringBuf value = source.NextTok(delim); !value.empty(); value = source.NextTok(delim)) { - if (value == ".") { - values.emplace(ValueType()); - } else { - values.emplace(FromStringWithDefault<ValueType>(value, ValueType())); - } - } + GenericSplitIds<ValueType>(source, delim, std::inserter(values, values.end())); } TString GetHTTPOKJSON(); diff --git a/ydb/core/viewer/viewer_ut.cpp b/ydb/core/viewer/viewer_ut.cpp index 020239e8a5f..e9c89d42fe7 100644 --- a/ydb/core/viewer/viewer_ut.cpp +++ b/ydb/core/viewer/viewer_ut.cpp @@ -1,8 +1,10 @@ #include <library/cpp/testing/unittest/registar.h> #include <library/cpp/testing/unittest/tests_data.h> #include <library/cpp/actors/interconnect/interconnect.h> +#include <library/cpp/json/json_reader.h> #include <util/stream/null.h> #include <ydb/core/viewer/protos/viewer.pb.h> +#include "json_handlers.h" #include "json_tabletinfo.h" #include "json_vdiskinfo.h" #include "json_pdiskinfo.h" @@ -85,4 +87,27 @@ Y_UNIT_TEST_SUITE(Viewer) { UNIT_ASSERT_VALUES_EQUAL(result->Record.PDiskStateInfoSize(), 100000); Ctest << "Data has merged" << Endl; } + + template <typename T> + void TestSwagger() { + T h; + h.Init(); + + TStringStream json; + json << "{"; + h.PrintForSwagger(json); + json << "}"; + + NJson::TJsonReaderConfig jsonCfg; + jsonCfg.DontValidateUtf8 = true; + jsonCfg.AllowComments = false; + + ValidateJsonThrow(json.Str(), jsonCfg); + } + + Y_UNIT_TEST(Swagger) { + TestSwagger<TViewerJsonHandlers>(); + TestSwagger<TVDiskJsonHandlers>(); + TestSwagger<TFQJsonHandlers>(); + } } |