Skip to content

Commit e90fcb5

Browse files
StekPerepolnenadameat
authored andcommitted
add http-handle returning configs (ydb-platform#7830)
1 parent 2e7864c commit e90fcb5

File tree

7 files changed

+246
-0
lines changed

7 files changed

+246
-0
lines changed

ydb/core/viewer/json_handlers_viewer.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "viewer_describe_consumer.h"
1212
#include "viewer_describe.h"
1313
#include "viewer_describe_topic.h"
14+
#include "viewer_feature_flags.h"
1415
#include "viewer_graph.h"
1516
#include "viewer_healthcheck.h"
1617
#include "viewer_hiveinfo.h"
@@ -265,6 +266,10 @@ void InitViewerCheckAccessJsonHandler(TJsonHandlers& jsonHandlers) {
265266
jsonHandlers.AddHandler("/viewer/check_access", new TJsonHandler<TCheckAccess>(TCheckAccess::GetSwagger()));
266267
}
267268

269+
void InitViewerFeatureFlagsJsonHandler(TJsonHandlers& handlers) {
270+
handlers.AddHandler("/viewer/feature_flags", new TJsonHandler<TJsonFeatureFlags>(TJsonFeatureFlags::GetSwagger()));
271+
}
272+
268273
void InitViewerJsonHandlers(TJsonHandlers& jsonHandlers) {
269274
InitViewerCapabilitiesJsonHandler(jsonHandlers);
270275
InitViewerNodelistJsonHandler(jsonHandlers);
@@ -303,6 +308,7 @@ void InitViewerJsonHandlers(TJsonHandlers& jsonHandlers) {
303308
InitViewerRenderJsonHandler(jsonHandlers);
304309
InitViewerAutocompleteJsonHandler(jsonHandlers);
305310
InitViewerCheckAccessJsonHandler(jsonHandlers);
311+
InitViewerFeatureFlagsJsonHandler(jsonHandlers);
306312
}
307313

308314
}

ydb/core/viewer/json_pipe_req.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,14 @@ TViewerPipeClient::TRequestResponse<NConsole::TEvConsole::TEvListTenantsResponse
199199
return MakeRequestToPipe<NConsole::TEvConsole::TEvListTenantsResponse>(pipeClient, request.Release());
200200
}
201201

202+
TViewerPipeClient::TRequestResponse<NConsole::TEvConsole::TEvGetNodeConfigResponse> TViewerPipeClient::MakeRequestConsoleNodeConfigByTenant(TString tenant, ui64 cookie) {
203+
TActorId pipeClient = ConnectTabletPipe(GetConsoleId());
204+
auto request = MakeHolder<NConsole::TEvConsole::TEvGetNodeConfigRequest>();
205+
request->Record.MutableNode()->SetTenant(tenant);
206+
request->Record.AddItemKinds(static_cast<ui32>(NKikimrConsole::TConfigItem::FeatureFlagsItem));
207+
return MakeRequestToPipe<NConsole::TEvConsole::TEvGetNodeConfigResponse>(pipeClient, request.Release(), cookie);
208+
}
209+
202210
void TViewerPipeClient::RequestConsoleGetTenantStatus(const TString& path) {
203211
TActorId pipeClient = ConnectTabletPipe(GetConsoleId());
204212
THolder<NConsole::TEvConsole::TEvGetTenantStatusRequest> request = MakeHolder<NConsole::TEvConsole::TEvGetTenantStatusRequest>();

ydb/core/viewer/json_pipe_req.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ class TViewerPipeClient : public TActorBootstrapped<TViewerPipeClient> {
190190
TRequestResponse<TEvHive::TEvResponseHiveStorageStats> MakeRequestHiveStorageStats(TTabletId hiveId);
191191
void RequestConsoleListTenants();
192192
TRequestResponse<NConsole::TEvConsole::TEvListTenantsResponse> MakeRequestConsoleListTenants();
193+
TRequestResponse<NConsole::TEvConsole::TEvGetNodeConfigResponse> MakeRequestConsoleNodeConfigByTenant(TString tenant, ui64 cookie = 0);
193194
void RequestConsoleGetTenantStatus(const TString& path);
194195
TRequestResponse<NConsole::TEvConsole::TEvGetTenantStatusResponse> MakeRequestConsoleGetTenantStatus(const TString& path);
195196
void RequestBSControllerConfig();

ydb/core/viewer/protos/viewer.proto

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,3 +719,19 @@ message TPDiskInfo {
719719
TPDiskInfoWhiteboard Whiteboard = 1;
720720
TPDiskInfoBSC BSC = 2;
721721
}
722+
723+
message TFeatureFlagsConfig {
724+
message TFeatureFlag {
725+
string Name = 1;
726+
bool Enabled = 2;
727+
bool IsDefault = 3;
728+
}
729+
730+
message TDatabase {
731+
string Name = 1;
732+
repeated TFeatureFlag FeatureFlags = 2;
733+
}
734+
735+
uint32 Version = 1;
736+
repeated TDatabase Databases = 2;
737+
}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
#pragma once
2+
#include "json_pipe_req.h"
3+
#include "viewer.h"
4+
5+
namespace NKikimr::NViewer {
6+
7+
using namespace NActors;
8+
9+
class TJsonFeatureFlags : public TViewerPipeClient {
10+
using TThis = TJsonFeatureFlags;
11+
using TBase = TViewerPipeClient;
12+
TJsonSettings JsonSettings;
13+
ui32 Timeout = 0;
14+
15+
TString FilterDatabase;
16+
THashSet<TString> FilterFeatures;
17+
THashMap<ui64, TString> DatabaseByCookie;
18+
ui64 Cookie = 0;
19+
TString DomainPath;
20+
bool Direct = false;
21+
22+
TRequestResponse<NConsole::TEvConsole::TEvListTenantsResponse> TenantsResponse;
23+
THashMap<TString, TRequestResponse<NConsole::TEvConsole::TEvGetNodeConfigResponse>> NodeConfigResponses;
24+
25+
public:
26+
TJsonFeatureFlags(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev)
27+
: TViewerPipeClient(viewer, ev)
28+
{}
29+
30+
void MakeNodeConfigRequest(const TString& database) {
31+
NodeConfigResponses[database] = MakeRequestConsoleNodeConfigByTenant(database, Cookie);
32+
DatabaseByCookie[Cookie++] = database;
33+
}
34+
35+
void Bootstrap() override {
36+
const auto& params(Event->Get()->Request.GetParams());
37+
38+
JsonSettings.EnumAsNumbers = !FromStringWithDefault<bool>(params.Get("enums"), true);
39+
JsonSettings.UI64AsString = !FromStringWithDefault<bool>(params.Get("ui64"), false);
40+
FilterDatabase = params.Get("database");
41+
StringSplitter(params.Get("features")).Split(',').SkipEmpty().Collect(&FilterFeatures);
42+
Direct = FromStringWithDefault<bool>(params.Get("direct"), Direct);
43+
Timeout = FromStringWithDefault<ui32>(params.Get("timeout"), 10000);
44+
45+
TIntrusivePtr<TDomainsInfo> domains = AppData()->DomainsInfo;
46+
auto* domain = domains->GetDomain();
47+
DomainPath = "/" + domain->Name;
48+
49+
Direct |= Event->Get()->Request.GetUri().StartsWith("/node/"); // we're already forwarding
50+
Direct |= (FilterDatabase == AppData()->TenantName); // we're already on the right node
51+
if (FilterDatabase && !Direct) {
52+
RequestStateStorageEndpointsLookup(FilterDatabase); // to find some dynamic node and redirect there
53+
} else if (!FilterDatabase) {
54+
MakeNodeConfigRequest(DomainPath);
55+
TenantsResponse = MakeRequestConsoleListTenants();
56+
} else {
57+
MakeNodeConfigRequest(FilterDatabase);
58+
}
59+
60+
Become(&TThis::StateWork, TDuration::MilliSeconds(Timeout), new TEvents::TEvWakeup());
61+
}
62+
63+
void HandleReply(TEvStateStorage::TEvBoardInfo::TPtr& ev) {
64+
TBase::ReplyAndPassAway(MakeForward(GetNodesFromBoardReply(ev)));
65+
}
66+
67+
STATEFN(StateWork) {
68+
switch (ev->GetTypeRewrite()) {
69+
hFunc(TEvStateStorage::TEvBoardInfo, HandleReply);
70+
hFunc(NConsole::TEvConsole::TEvListTenantsResponse, Handle);
71+
hFunc(NConsole::TEvConsole::TEvGetNodeConfigResponse, Handle);
72+
hFunc(TEvTabletPipe::TEvClientConnected, TBase::Handle);
73+
cFunc(TEvents::TSystem::Wakeup, HandleTimeout);
74+
}
75+
}
76+
77+
void Handle(NConsole::TEvConsole::TEvListTenantsResponse::TPtr& ev) {
78+
TenantsResponse.Set(std::move(ev));
79+
Ydb::Cms::ListDatabasesResult listDatabasesResult;
80+
TenantsResponse->Record.GetResponse().operation().result().UnpackTo(&listDatabasesResult);
81+
for (const TString& path : listDatabasesResult.paths()) {
82+
MakeNodeConfigRequest(path);
83+
}
84+
RequestDone();
85+
}
86+
87+
void Handle(NConsole::TEvConsole::TEvGetNodeConfigResponse::TPtr& ev) {
88+
TString database = DatabaseByCookie[ev.Get()->Cookie];
89+
NodeConfigResponses[database].Set(std::move(ev));
90+
RequestDone();
91+
}
92+
93+
THashMap<TString, NKikimrViewer::TFeatureFlagsConfig::TFeatureFlag> ParseFeatureFlags(const NKikimrConfig::TFeatureFlags& featureFlags) {
94+
THashMap<TString, NKikimrViewer::TFeatureFlagsConfig::TFeatureFlag> features;
95+
const google::protobuf::Reflection* reflection = featureFlags.GetReflection();
96+
const google::protobuf::Descriptor* descriptor = featureFlags.GetDescriptor();
97+
98+
for (int i = 0; i < descriptor->field_count(); ++i) {
99+
const google::protobuf::FieldDescriptor* field = descriptor->field(i);
100+
if (field->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_BOOL) {
101+
auto& feat = features[field->name()];
102+
feat.SetName(field->name());
103+
feat.SetEnabled(reflection->GetBool(featureFlags, field));
104+
feat.SetIsDefault(field->default_value_bool());
105+
}
106+
}
107+
return features;
108+
}
109+
110+
void ReplyAndPassAway() override {
111+
THashMap<TString, THashMap<TString, NKikimrViewer::TFeatureFlagsConfig::TFeatureFlag>> FeatureFlagsByDatabase;
112+
for (const auto& [database, response] : NodeConfigResponses) {
113+
NKikimrConsole::TGetNodeConfigResponse rec = response->Record;
114+
FeatureFlagsByDatabase[database] = ParseFeatureFlags(rec.GetConfig().GetFeatureFlags());
115+
}
116+
117+
auto domainFeaturesIt = FeatureFlagsByDatabase.find(DomainPath);
118+
if (domainFeaturesIt == FeatureFlagsByDatabase.end()) {
119+
return TBase::ReplyAndPassAway(GetHTTPINTERNALERROR("text/plain", "No domain info from Console"));
120+
}
121+
122+
// prepare response
123+
NKikimrViewer::TFeatureFlagsConfig Result;
124+
Result.SetVersion(1);
125+
for (const auto& [database, features] : FeatureFlagsByDatabase) {
126+
auto databaseProto = Result.AddDatabases();
127+
databaseProto->SetName(database);
128+
for (const auto& [name, featProto] : features) {
129+
if (FilterFeatures.empty() || FilterFeatures.find(name) != FilterFeatures.end()) {
130+
auto flag = databaseProto->AddFeatureFlags();
131+
flag->CopyFrom(featProto);
132+
}
133+
}
134+
}
135+
136+
TStringStream json;
137+
TProtoToJson::ProtoToJson(json, Result, JsonSettings);
138+
TBase::ReplyAndPassAway(GetHTTPOKJSON(json.Str()));
139+
}
140+
141+
static YAML::Node GetSwagger() {
142+
TSimpleYamlBuilder yaml({
143+
.Method = "get",
144+
.Tag = "viewer",
145+
.Summary = "Feature flags",
146+
.Description = "Returns feature flags of each database"
147+
});
148+
yaml.AddParameter({
149+
.Name = "database",
150+
.Description = "database name",
151+
.Type = "string",
152+
});
153+
yaml.AddParameter({
154+
.Name = "features",
155+
.Description = "comma separated list of features",
156+
.Type = "string",
157+
});
158+
yaml.AddParameter({
159+
.Name = "direct",
160+
.Description = "direct request to the node",
161+
.Type = "boolean",
162+
});
163+
yaml.AddParameter({
164+
.Name = "timeout",
165+
.Description = "timeout in ms",
166+
.Type = "integer",
167+
});
168+
yaml.AddParameter({
169+
.Name = "enums",
170+
.Description = "convert enums to strings",
171+
.Type = "boolean",
172+
});
173+
yaml.AddParameter({
174+
.Name = "ui64",
175+
.Description = "return ui64 as number",
176+
.Type = "boolean",
177+
});
178+
yaml.SetResponseSchema(TProtoToYaml::ProtoToYamlSchema<NKikimrViewer::TFeatureFlagsConfig>());
179+
return yaml;
180+
}
181+
};
182+
183+
}

ydb/core/viewer/viewer_ut.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1780,4 +1780,35 @@ Y_UNIT_TEST_SUITE(Viewer) {
17801780
UNIT_ASSERT_VALUES_EQUAL_C(ticketParser->AuthorizeTicketRequests, 1, response);
17811781
UNIT_ASSERT_VALUES_EQUAL_C(ticketParser->AuthorizeTicketSuccesses, 1, response);
17821782
}
1783+
1784+
Y_UNIT_TEST(SimpleFeatureFlags) {
1785+
TPortManager tp;
1786+
ui16 port = tp.GetPort(2134);
1787+
ui16 grpcPort = tp.GetPort(2135);
1788+
ui16 monPort = tp.GetPort(8765);
1789+
auto settings = TServerSettings(port);
1790+
1791+
settings.InitKikimrRunConfig()
1792+
.SetNodeCount(1)
1793+
.SetUseRealThreads(true)
1794+
.SetDomainName("Root")
1795+
.SetMonitoringPortOffset(monPort, true);
1796+
1797+
TServer server(settings);
1798+
server.EnableGRpc(grpcPort);
1799+
TClient client(settings);
1800+
1801+
TKeepAliveHttpClient httpClient("localhost", monPort);
1802+
TStringStream responseStream;
1803+
TKeepAliveHttpClient::THeaders headers;
1804+
headers["Content-Type"] = "application/json";
1805+
const TKeepAliveHttpClient::THttpCode statusCode = httpClient.DoGet("/viewer/feature_flags?timeout=600000&base64=false", &responseStream, headers);
1806+
const TString response = responseStream.ReadAll();
1807+
UNIT_ASSERT_EQUAL_C(statusCode, HTTP_OK, statusCode << ": " << response);
1808+
NJson::TJsonReaderConfig jsonCfg;
1809+
NJson::TJsonValue json;
1810+
NJson::ReadJsonTree(response, &jsonCfg, &json, /* throwOnError = */ true);
1811+
auto resultSets = json["Databases"].GetArray();
1812+
UNIT_ASSERT_EQUAL_C(1, resultSets.size(), response);
1813+
}
17831814
}

ydb/core/viewer/ya.make

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ SRCS(
6464
viewer_describe_consumer.h
6565
viewer_describe.h
6666
viewer_describe_topic.h
67+
viewer_feature_flags.h
6768
viewer_graph.h
6869
viewer_healthcheck.h
6970
viewer_helper.h

0 commit comments

Comments
 (0)