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,127 +12,176 @@ 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-
17+ bool ChangedOnly = false ;
2218 TRequestResponse<NConsole::TEvConsole::TEvListTenantsResponse> TenantsResponse;
23- THashMap<TString, TRequestResponse<NConsole::TEvConsole::TEvGetNodeConfigResponse>> NodeConfigResponses;
19+ TRequestResponse<NConsole::TEvConsole::TEvGetAllConfigsResponse> AllConfigsResponse;
20+ std::unordered_map<TString, TRequestResponse<TEvTxProxySchemeCache::TEvNavigateKeySetResult>> PathNameNavigateKeySetResults;
21+ std::unordered_map<TPathId, TRequestResponse<TEvTxProxySchemeCache::TEvNavigateKeySetResult>> PathIdNavigateKeySetResults;
2422
2523public:
2624 TJsonFeatureFlags (IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev)
2725 : TViewerPipeClient(viewer, ev)
2826 {}
2927
30- void MakeNodeConfigRequest (const TString& database) {
31- NodeConfigResponses[database] = MakeRequestConsoleNodeConfigByTenant (database, Cookie);
32- DatabaseByCookie[Cookie++] = database;
33- }
34-
3528 void Bootstrap () override {
3629 const auto & params (Event->Get ()->Request .GetParams ());
37-
3830 JsonSettings.EnumAsNumbers = !FromStringWithDefault<bool >(params.Get (" enums" ), true );
3931 JsonSettings.UI64AsString = !FromStringWithDefault<bool >(params.Get (" ui64" ), false );
4032 FilterDatabase = params.Get (" database" );
4133 StringSplitter (params.Get (" features" )).Split (' ,' ).SkipEmpty ().Collect (&FilterFeatures);
42- Direct = FromStringWithDefault<bool >(params.Get (" direct" ), Direct );
34+ bool direct = FromStringWithDefault<bool >(params.Get (" direct" ), false );
4335 Timeout = FromStringWithDefault<ui32>(params.Get (" timeout" ), 10000 );
36+ ChangedOnly = FromStringWithDefault<bool >(params.Get (" changed" ), ChangedOnly);
4437
45- TIntrusivePtr<TDomainsInfo> domains = AppData ()->DomainsInfo ;
46- auto * domain = domains->GetDomain ();
47- DomainPath = " /" + domain->Name ;
48-
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) {
38+ direct |= !TBase::Event->Get ()->Request .GetHeader (" X-Forwarded-From-Node" ).empty (); // we're already forwarding
39+ direct |= (FilterDatabase == AppData ()->TenantName ); // we're already on the right node
40+ if (FilterDatabase && !direct) {
5241 return RedirectToDatabase (FilterDatabase); // to find some dynamic node and redirect query there
53- } else if (!FilterDatabase) {
54- MakeNodeConfigRequest (DomainPath);
55- TenantsResponse = MakeRequestConsoleListTenants ();
42+ }
43+
44+ if (FilterDatabase) {
45+ PathNameNavigateKeySetResults[FilterDatabase] = MakeRequestSchemeCacheNavigate (FilterDatabase);
5646 } else {
57- MakeNodeConfigRequest (FilterDatabase );
47+ TenantsResponse = MakeRequestConsoleListTenants ( );
5848 }
49+ AllConfigsResponse = MakeRequestConsoleGetAllConfigs ();
5950
6051 Become (&TThis::StateWork, TDuration::MilliSeconds (Timeout), new TEvents::TEvWakeup ());
6152 }
6253
6354 STATEFN (StateWork) {
6455 switch (ev->GetTypeRewrite ()) {
6556 hFunc (NConsole::TEvConsole::TEvListTenantsResponse, Handle);
66- hFunc (NConsole::TEvConsole::TEvGetNodeConfigResponse, Handle);
57+ hFunc (NConsole::TEvConsole::TEvGetAllConfigsResponse, Handle);
58+ hFunc (TEvTxProxySchemeCache::TEvNavigateKeySetResult, Handle);
6759 hFunc (TEvTabletPipe::TEvClientConnected, TBase::Handle);
6860 cFunc (TEvents::TSystem::Wakeup, HandleTimeout);
6961 }
7062 }
7163
7264 void Handle (NConsole::TEvConsole::TEvListTenantsResponse::TPtr& ev) {
7365 TenantsResponse.Set (std::move (ev));
74- Ydb::Cms::ListDatabasesResult listDatabasesResult;
75- TenantsResponse->Record .GetResponse ().operation ().result ().UnpackTo (&listDatabasesResult);
76- for (const TString& path : listDatabasesResult.paths ()) {
77- MakeNodeConfigRequest (path);
66+ if (TenantsResponse.IsOk ()) {
67+ Ydb::Cms::ListDatabasesResult listDatabasesResult;
68+ TenantsResponse->Record .GetResponse ().operation ().result ().UnpackTo (&listDatabasesResult);
69+ for (const TString& database : listDatabasesResult.paths ()) {
70+ if (PathNameNavigateKeySetResults.count (database) == 0 ) {
71+ PathNameNavigateKeySetResults[database] = MakeRequestSchemeCacheNavigate (database);
72+ }
73+ }
74+ }
75+ if (PathNameNavigateKeySetResults.empty ()) {
76+ if (AppData ()->DomainsInfo && AppData ()->DomainsInfo ->Domain ) {
77+ TString domain = " /" + AppData ()->DomainsInfo ->Domain ->Name ;
78+ PathNameNavigateKeySetResults[domain] = MakeRequestSchemeCacheNavigate (domain);
79+ }
7880 }
7981 RequestDone ();
8082 }
8183
82- void Handle (NConsole::TEvConsole::TEvGetNodeConfigResponse::TPtr& ev) {
83- TString database = DatabaseByCookie[ev.Get ()->Cookie ];
84- NodeConfigResponses[database].Set (std::move (ev));
84+ void Handle (NConsole::TEvConsole::TEvGetAllConfigsResponse::TPtr& ev) {
85+ AllConfigsResponse.Set (std::move (ev));
8586 RequestDone ();
8687 }
8788
88- THashMap<TString, NKikimrViewer::TFeatureFlagsConfig::TFeatureFlag> ParseFeatureFlags (const NKikimrConfig::TFeatureFlags& featureFlags) {
89- THashMap<TString, NKikimrViewer::TFeatureFlagsConfig::TFeatureFlag> features;
89+ void Handle (TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev) {
90+ TString path = GetPath (ev);
91+ if (path) {
92+ auto it = PathNameNavigateKeySetResults.find (path);
93+ if (it != PathNameNavigateKeySetResults.end () && !it->second .IsDone ()) {
94+ it->second .Set (std::move (ev));
95+ if (it->second .IsOk ()) {
96+ TSchemeCacheNavigate::TEntry& entry (it->second ->Request ->ResultSet .front ());
97+ if (entry.DomainInfo ) {
98+ if (entry.DomainInfo ->ResourcesDomainKey && entry.DomainInfo ->DomainKey != entry.DomainInfo ->ResourcesDomainKey ) {
99+ TPathId resourceDomainKey (entry.DomainInfo ->ResourcesDomainKey );
100+ if (PathIdNavigateKeySetResults.count (resourceDomainKey) == 0 ) {
101+ PathIdNavigateKeySetResults[resourceDomainKey] = MakeRequestSchemeCacheNavigate (resourceDomainKey);
102+ }
103+ }
104+ }
105+ }
106+ RequestDone ();
107+ return ;
108+ }
109+ }
110+ TPathId pathId = GetPathId (ev);
111+ if (pathId) {
112+ auto it = PathIdNavigateKeySetResults.find (pathId);
113+ if (it != PathIdNavigateKeySetResults.end () && !it->second .IsDone ()) {
114+ it->second .Set (std::move (ev));
115+ RequestDone ();
116+ return ;
117+ }
118+ }
119+ }
120+
121+ void ParseFeatureFlags (const NKikimrConfig::TFeatureFlags& featureFlags, NKikimrViewer::TFeatureFlagsConfig::TDatabase& result) {
90122 const google::protobuf::Reflection* reflection = featureFlags.GetReflection ();
91123 const google::protobuf::Descriptor* descriptor = featureFlags.GetDescriptor ();
92-
93124 for (int i = 0 ; i < descriptor->field_count (); ++i) {
94125 const google::protobuf::FieldDescriptor* field = descriptor->field (i);
95126 if (field->cpp_type () == google::protobuf::FieldDescriptor::CPPTYPE_BOOL) {
96- auto & feat = features[field->name ()];
97- feat.SetName (field->name ());
98- if (reflection->HasField (featureFlags, field)) {
99- feat.SetCurrent (reflection->GetBool (featureFlags, field));
100- }
101- if (field->has_default_value ()) {
102- feat.SetDefault (field->default_value_bool ());
127+ if (FilterFeatures.empty () || FilterFeatures.count (field->name ())) {
128+ bool hasField = reflection->HasField (featureFlags, field);
129+ if (ChangedOnly && !hasField) {
130+ continue ;
131+ }
132+ auto flag = result.AddFeatureFlags ();
133+ flag->SetName (field->name ());
134+ if (hasField) {
135+ flag->SetCurrent (reflection->GetBool (featureFlags, field));
136+ }
137+ if (field->has_default_value ()) {
138+ flag->SetDefault (field->default_value_bool ());
139+ }
103140 }
104141 }
105142 }
106- return features;
107143 }
108144
109- void ReplyAndPassAway () override {
110- THashMap<TString, THashMap<TString, NKikimrViewer::TFeatureFlagsConfig::TFeatureFlag>> FeatureFlagsByDatabase;
111- for (const auto & [database, response] : NodeConfigResponses) {
112- NKikimrConsole::TGetNodeConfigResponse rec = response->Record ;
113- FeatureFlagsByDatabase[database] = ParseFeatureFlags (rec.GetConfig ().GetFeatureFlags ());
114- }
115-
116- auto domainFeaturesIt = FeatureFlagsByDatabase.find (DomainPath);
117- if (domainFeaturesIt == FeatureFlagsByDatabase.end ()) {
118- return TBase::ReplyAndPassAway (GetHTTPINTERNALERROR (" text/plain" , " No domain info from Console" ));
145+ void ParseConfig (const TString& database,
146+ const TRequestResponse<TEvTxProxySchemeCache::TEvNavigateKeySetResult>& navigate,
147+ NKikimrViewer::TFeatureFlagsConfig& result) {
148+ if (AllConfigsResponse.IsOk ()) {
149+ TString realDatabase = database;
150+ auto databaseProto = result.AddDatabases ();
151+ databaseProto->SetName (database);
152+ TSchemeCacheNavigate::TEntry& entry (navigate->Request ->ResultSet .front ());
153+ if (entry.DomainInfo ) {
154+ if (entry.DomainInfo ->ResourcesDomainKey && entry.DomainInfo ->DomainKey != entry.DomainInfo ->ResourcesDomainKey ) {
155+ TPathId resourceDomainKey (entry.DomainInfo ->ResourcesDomainKey );
156+ auto it = PathIdNavigateKeySetResults.find (resourceDomainKey);
157+ if (it != PathIdNavigateKeySetResults.end () && it->second .IsOk () && it->second ->Request ->ResultSet .size () == 1 ) {
158+ realDatabase = CanonizePath (it->second ->Request ->ResultSet .begin ()->Path );
159+ }
160+ }
161+ }
162+ NKikimrConfig::TAppConfig appConfig;
163+ if (AllConfigsResponse->Record .GetResponse ().config ()) {
164+ try {
165+ NYamlConfig::ResolveAndParseYamlConfig (AllConfigsResponse->Record .GetResponse ().config (), {}, {{" tenant" , realDatabase}}, appConfig);
166+ } catch (const std::exception& e) {
167+ BLOG_ERROR (" Failed to parse config for tenant " << realDatabase << " : " << e.what ());
168+ }
169+ ParseFeatureFlags (appConfig.GetFeatureFlags (), *databaseProto);
170+ } else {
171+ ParseFeatureFlags (AppData ()->FeatureFlags , *databaseProto);
172+ }
119173 }
174+ }
120175
176+ void ReplyAndPassAway () override {
121177 // prepare response
122178 NKikimrViewer::TFeatureFlagsConfig Result;
123179 Result.SetVersion (Viewer->GetCapabilityVersion (" /viewer/feature_flags" ));
124- for (const auto & [database, features] : FeatureFlagsByDatabase) {
125- auto databaseProto = Result.AddDatabases ();
126- databaseProto->SetName (database);
127- for (const auto & [name, featProto] : features) {
128- if (FilterFeatures.empty () || FilterFeatures.find (name) != FilterFeatures.end ()) {
129- auto flag = databaseProto->AddFeatureFlags ();
130- flag->CopyFrom (featProto);
131- }
180+ for (const auto & [database, navigate] : PathNameNavigateKeySetResults) {
181+ if (navigate.IsOk ()) {
182+ ParseConfig (database, navigate, Result);
132183 }
133184 }
134-
135185 TStringStream json;
136186 TProtoToJson::ProtoToJson (json, Result, JsonSettings);
137187 TBase::ReplyAndPassAway (GetHTTPOKJSON (json.Str ()));
@@ -142,12 +192,13 @@ class TJsonFeatureFlags : public TViewerPipeClient {
142192 .Method = " get" ,
143193 .Tag = " viewer" ,
144194 .Summary = " Feature flags" ,
145- .Description = " Returns feature flags of each database"
195+ .Description = " Returns feature flags of a database"
146196 });
147197 yaml.AddParameter ({
148198 .Name = " database" ,
149199 .Description = " database name" ,
150200 .Type = " string" ,
201+ .Required = true ,
151202 });
152203 yaml.AddParameter ({
153204 .Name = " features" ,
@@ -164,16 +215,6 @@ class TJsonFeatureFlags : public TViewerPipeClient {
164215 .Description = " timeout in ms" ,
165216 .Type = " integer" ,
166217 });
167- yaml.AddParameter ({
168- .Name = " enums" ,
169- .Description = " convert enums to strings" ,
170- .Type = " boolean" ,
171- });
172- yaml.AddParameter ({
173- .Name = " ui64" ,
174- .Description = " return ui64 as number" ,
175- .Type = " boolean" ,
176- });
177218 yaml.SetResponseSchema (TProtoToYaml::ProtoToYamlSchema<NKikimrViewer::TFeatureFlagsConfig>());
178219 return yaml;
179220 }
0 commit comments