11#pragma once
22#include " json_pipe_req.h"
33#include " viewer.h"
4+ #include < ydb/library/yaml_config/yaml_config.h>
45
56namespace NKikimr ::NViewer {
67
@@ -11,132 +12,163 @@ class TJsonFeatureFlags : public TViewerPipeClient {
1112 using TBase = TViewerPipeClient;
1213 TJsonSettings JsonSettings;
1314 ui32 Timeout = 0 ;
14-
1515 TString FilterDatabase;
1616 THashSet<TString> FilterFeatures;
17- THashMap<ui64, TString> DatabaseByCookie;
18- ui64 Cookie = 0 ;
19- TString DomainPath;
20- bool Direct = false ;
21-
2217 TRequestResponse<NConsole::TEvConsole::TEvListTenantsResponse> TenantsResponse;
23- THashMap<TString, TRequestResponse<NConsole::TEvConsole::TEvGetNodeConfigResponse>> NodeConfigResponses;
18+ TRequestResponse<NConsole::TEvConsole::TEvGetAllConfigsResponse> AllConfigsResponse;
19+ std::unordered_map<TString, TRequestResponse<TEvTxProxySchemeCache::TEvNavigateKeySetResult>> PathNameNavigateKeySetResults;
20+ std::unordered_map<TPathId, TRequestResponse<TEvTxProxySchemeCache::TEvNavigateKeySetResult>> PathIdNavigateKeySetResults;
2421
2522public:
2623 TJsonFeatureFlags (IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev)
2724 : TViewerPipeClient(viewer, ev)
2825 {}
2926
30- void MakeNodeConfigRequest (const TString& database) {
31- NodeConfigResponses[database] = MakeRequestConsoleNodeConfigByTenant (database, Cookie);
32- DatabaseByCookie[Cookie++] = database;
33- }
34-
3527 void Bootstrap () override {
3628 const auto & params (Event->Get ()->Request .GetParams ());
37-
3829 JsonSettings.EnumAsNumbers = !FromStringWithDefault<bool >(params.Get (" enums" ), true );
3930 JsonSettings.UI64AsString = !FromStringWithDefault<bool >(params.Get (" ui64" ), false );
4031 FilterDatabase = params.Get (" database" );
4132 StringSplitter (params.Get (" features" )).Split (' ,' ).SkipEmpty ().Collect (&FilterFeatures);
42- Direct = FromStringWithDefault<bool >(params.Get (" direct" ), Direct );
33+ bool direct = FromStringWithDefault<bool >(params.Get (" direct" ), false );
4334 Timeout = FromStringWithDefault<ui32>(params.Get (" timeout" ), 10000 );
4435
45- TIntrusivePtr<TDomainsInfo> domains = AppData ()->DomainsInfo ;
46- auto * domain = domains->GetDomain ();
47- DomainPath = " /" + domain->Name ;
36+ direct |= !TBase::Event->Get ()->Request .GetHeader (" X-Forwarded-From-Node" ).empty (); // we're already forwarding
37+ direct |= (FilterDatabase == AppData ()->TenantName ); // we're already on the right node
38+ if (FilterDatabase && !direct) {
39+ return RedirectToDatabase (FilterDatabase); // to find some dynamic node and redirect query there
40+ }
4841
49- Direct |= !TBase::Event->Get ()->Request .GetHeader (" X-Forwarded-From-Node" ).empty (); // 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 ();
42+ if (FilterDatabase) {
43+ PathNameNavigateKeySetResults[FilterDatabase] = MakeRequestSchemeCacheNavigate (FilterDatabase);
5644 } else {
57- MakeNodeConfigRequest (FilterDatabase );
45+ TenantsResponse = MakeRequestConsoleListTenants ( );
5846 }
47+ AllConfigsResponse = MakeRequestConsoleGetAllConfigs ();
5948
6049 Become (&TThis::StateWork, TDuration::MilliSeconds (Timeout), new TEvents::TEvWakeup ());
6150 }
6251
63- void HandleReply (TEvStateStorage::TEvBoardInfo::TPtr& ev) {
64- TBase::ReplyAndPassAway (MakeForward (GetNodesFromBoardReply (ev)));
65- }
66-
6752 STATEFN (StateWork) {
6853 switch (ev->GetTypeRewrite ()) {
69- hFunc (TEvStateStorage::TEvBoardInfo, HandleReply);
7054 hFunc (NConsole::TEvConsole::TEvListTenantsResponse, Handle);
71- hFunc (NConsole::TEvConsole::TEvGetNodeConfigResponse, Handle);
55+ hFunc (NConsole::TEvConsole::TEvGetAllConfigsResponse, Handle);
56+ hFunc (TEvTxProxySchemeCache::TEvNavigateKeySetResult, Handle);
7257 hFunc (TEvTabletPipe::TEvClientConnected, TBase::Handle);
7358 cFunc (TEvents::TSystem::Wakeup, HandleTimeout);
7459 }
7560 }
7661
7762 void Handle (NConsole::TEvConsole::TEvListTenantsResponse::TPtr& ev) {
7863 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- }
8464 RequestDone ();
8565 }
8666
87- void Handle (NConsole::TEvConsole::TEvGetNodeConfigResponse::TPtr& ev) {
88- TString database = DatabaseByCookie[ev.Get ()->Cookie ];
89- NodeConfigResponses[database].Set (std::move (ev));
67+ void Handle (NConsole::TEvConsole::TEvGetAllConfigsResponse::TPtr& ev) {
68+ AllConfigsResponse.Set (std::move (ev));
9069 RequestDone ();
9170 }
9271
93- THashMap<TString, NKikimrViewer::TFeatureFlagsConfig::TFeatureFlag> ParseFeatureFlags (const NKikimrConfig::TFeatureFlags& featureFlags) {
94- THashMap<TString, NKikimrViewer::TFeatureFlagsConfig::TFeatureFlag> features;
72+ void Handle (TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev) {
73+ TString path = GetPath (ev);
74+ if (path) {
75+ auto it = PathNameNavigateKeySetResults.find (path);
76+ if (it != PathNameNavigateKeySetResults.end () && !it->second .IsDone ()) {
77+ it->second .Set (std::move (ev));
78+ if (it->second .IsOk ()) {
79+ TSchemeCacheNavigate::TEntry& entry (it->second ->Request ->ResultSet .front ());
80+ if (entry.DomainInfo ) {
81+ if (entry.DomainInfo ->ResourcesDomainKey && entry.DomainInfo ->DomainKey != entry.DomainInfo ->ResourcesDomainKey ) {
82+ TPathId resourceDomainKey (entry.DomainInfo ->ResourcesDomainKey );
83+ PathIdNavigateKeySetResults[resourceDomainKey] = MakeRequestSchemeCacheNavigate (resourceDomainKey);
84+ }
85+ }
86+ }
87+ RequestDone ();
88+ return ;
89+ }
90+ }
91+ TPathId pathId = GetPathId (ev);
92+ if (pathId) {
93+ auto it = PathIdNavigateKeySetResults.find (pathId);
94+ if (it != PathIdNavigateKeySetResults.end () && !it->second .IsDone ()) {
95+ it->second .Set (std::move (ev));
96+ RequestDone ();
97+ return ;
98+ }
99+ }
100+ }
101+
102+ void ParseFeatureFlags (const NKikimrConfig::TFeatureFlags& featureFlags, NKikimrViewer::TFeatureFlagsConfig::TDatabase& result) {
95103 const google::protobuf::Reflection* reflection = featureFlags.GetReflection ();
96104 const google::protobuf::Descriptor* descriptor = featureFlags.GetDescriptor ();
97-
98105 for (int i = 0 ; i < descriptor->field_count (); ++i) {
99106 const google::protobuf::FieldDescriptor* field = descriptor->field (i);
100107 if (field->cpp_type () == google::protobuf::FieldDescriptor::CPPTYPE_BOOL) {
101- auto & feat = features[field->name ()];
102- feat.SetName (field->name ());
103- if (reflection->HasField (featureFlags, field)) {
104- feat.SetCurrent (reflection->GetBool (featureFlags, field));
105- }
106- if (field->has_default_value ()) {
107- feat.SetDefault (field->default_value_bool ());
108+ if (FilterFeatures.empty () || FilterFeatures.count (field->name ())) {
109+ auto flag = result.AddFeatureFlags ();
110+ flag->SetName (field->name ());
111+ if (reflection->HasField (featureFlags, field)) {
112+ flag->SetCurrent (reflection->GetBool (featureFlags, field));
113+ }
114+ if (field->has_default_value ()) {
115+ flag->SetDefault (field->default_value_bool ());
116+ }
108117 }
109118 }
110119 }
111- return features;
112120 }
113121
114- void ReplyAndPassAway () override {
115- THashMap<TString, THashMap<TString, NKikimrViewer::TFeatureFlagsConfig::TFeatureFlag>> FeatureFlagsByDatabase;
116- for (const auto & [database, response] : NodeConfigResponses) {
117- NKikimrConsole::TGetNodeConfigResponse rec = response->Record ;
118- FeatureFlagsByDatabase[database] = ParseFeatureFlags (rec.GetConfig ().GetFeatureFlags ());
119- }
120-
121- auto domainFeaturesIt = FeatureFlagsByDatabase.find (DomainPath);
122- if (domainFeaturesIt == FeatureFlagsByDatabase.end ()) {
123- return TBase::ReplyAndPassAway (GetHTTPINTERNALERROR (" text/plain" , " No domain info from Console" ));
122+ void ParseConfig (const TString& database, NKikimrViewer::TFeatureFlagsConfig& result) {
123+ if (AllConfigsResponse.IsOk ()) {
124+ TString realDatabase = database;
125+ NKikimrViewer::TFeatureFlagsConfig_TDatabase* databaseProto = nullptr ;
126+ auto itNavigate = PathNameNavigateKeySetResults.find (database);
127+ if (itNavigate != PathNameNavigateKeySetResults.end () && itNavigate->second .IsOk ()) {
128+ databaseProto = result.AddDatabases ();
129+ databaseProto->SetName (database);
130+ TSchemeCacheNavigate::TEntry& entry (itNavigate->second ->Request ->ResultSet .front ());
131+ if (entry.DomainInfo ) {
132+ if (entry.DomainInfo ->ResourcesDomainKey && entry.DomainInfo ->DomainKey != entry.DomainInfo ->ResourcesDomainKey ) {
133+ TPathId resourceDomainKey (entry.DomainInfo ->ResourcesDomainKey );
134+ auto it = PathIdNavigateKeySetResults.find (resourceDomainKey);
135+ if (it != PathIdNavigateKeySetResults.end () && it->second .IsOk () && it->second ->Request ->ResultSet .size () == 1 ) {
136+ realDatabase = CanonizePath (it->second ->Request ->ResultSet .begin ()->Path );
137+ }
138+ }
139+ }
140+ } else {
141+ return ;
142+ }
143+ NKikimrConfig::TAppConfig appConfig;
144+ if (AllConfigsResponse->Record .GetResponse ().config ()) {
145+ try {
146+ NYamlConfig::ResolveAndParseYamlConfig (AllConfigsResponse->Record .GetResponse ().config (), {}, {{" tenant" , realDatabase}}, appConfig);
147+ } catch (const std::exception& e) {
148+ BLOG_ERROR (" Failed to parse config for tenant " << realDatabase << " : " << e.what ());
149+ }
150+ ParseFeatureFlags (appConfig.GetFeatureFlags (), *databaseProto);
151+ } else {
152+ ParseFeatureFlags (AppData ()->FeatureFlags , *databaseProto);
153+ }
124154 }
155+ }
125156
157+ void ReplyAndPassAway () override {
126158 // prepare response
127159 NKikimrViewer::TFeatureFlagsConfig Result;
128160 Result.SetVersion (Viewer->GetCapabilityVersion (" /viewer/feature_flags" ));
129- for (const auto & [database, features] : FeatureFlagsByDatabase) {
130- auto databaseProto = Result.AddDatabases ();
131- databaseProto->SetName (database);
132- for (const auto & [name, featProto] : features) {
133- if (FilterFeatures.empty () || FilterFeatures.find (name) != FilterFeatures.end ()) {
134- auto flag = databaseProto->AddFeatureFlags ();
135- flag->CopyFrom (featProto);
161+ if (FilterDatabase) {
162+ ParseConfig (FilterDatabase, Result);
163+ } else {
164+ if (TenantsResponse.IsOk ()) {
165+ Ydb::Cms::ListDatabasesResult listDatabasesResult;
166+ TenantsResponse->Record .GetResponse ().operation ().result ().UnpackTo (&listDatabasesResult);
167+ for (const TString& database : listDatabasesResult.paths ()) {
168+ ParseConfig (database, Result);
136169 }
137170 }
138171 }
139-
140172 TStringStream json;
141173 TProtoToJson::ProtoToJson (json, Result, JsonSettings);
142174 TBase::ReplyAndPassAway (GetHTTPOKJSON (json.Str ()));
@@ -147,12 +179,13 @@ class TJsonFeatureFlags : public TViewerPipeClient {
147179 .Method = " get" ,
148180 .Tag = " viewer" ,
149181 .Summary = " Feature flags" ,
150- .Description = " Returns feature flags of each database"
182+ .Description = " Returns feature flags of a database"
151183 });
152184 yaml.AddParameter ({
153185 .Name = " database" ,
154186 .Description = " database name" ,
155187 .Type = " string" ,
188+ .Required = true ,
156189 });
157190 yaml.AddParameter ({
158191 .Name = " features" ,
@@ -169,16 +202,6 @@ class TJsonFeatureFlags : public TViewerPipeClient {
169202 .Description = " timeout in ms" ,
170203 .Type = " integer" ,
171204 });
172- yaml.AddParameter ({
173- .Name = " enums" ,
174- .Description = " convert enums to strings" ,
175- .Type = " boolean" ,
176- });
177- yaml.AddParameter ({
178- .Name = " ui64" ,
179- .Description = " return ui64 as number" ,
180- .Type = " boolean" ,
181- });
182205 yaml.SetResponseSchema (TProtoToYaml::ProtoToYamlSchema<NKikimrViewer::TFeatureFlagsConfig>());
183206 return yaml;
184207 }
0 commit comments