Skip to content

Commit 376e237

Browse files
authored
another fix for feature flags (#8690)
1 parent 5f731ba commit 376e237

File tree

7 files changed

+171
-86
lines changed

7 files changed

+171
-86
lines changed

ydb/core/viewer/json_pipe_req.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,40 @@ TString TViewerPipeClient::GetPath(TEvTxProxySchemeCache::TEvNavigateKeySetResul
113113
return {};
114114
}
115115

116+
bool TViewerPipeClient::IsSuccess(const std::unique_ptr<TEvTxProxySchemeCache::TEvNavigateKeySetResult>& ev) {
117+
return (ev->Request->ResultSet.size() == 1) && (ev->Request->ResultSet.begin()->Status == NSchemeCache::TSchemeCacheNavigate::EStatus::Ok);
118+
}
119+
120+
TString TViewerPipeClient::GetError(const std::unique_ptr<TEvTxProxySchemeCache::TEvNavigateKeySetResult>& ev) {
121+
if (ev->Request->ResultSet.size() == 0) {
122+
return "empty response";
123+
}
124+
switch (ev->Request->ResultSet.begin()->Status) {
125+
case NSchemeCache::TSchemeCacheNavigate::EStatus::Ok:
126+
return "Ok";
127+
case NSchemeCache::TSchemeCacheNavigate::EStatus::Unknown:
128+
return "Unknown";
129+
case NSchemeCache::TSchemeCacheNavigate::EStatus::RootUnknown:
130+
return "RootUnknown";
131+
case NSchemeCache::TSchemeCacheNavigate::EStatus::PathErrorUnknown:
132+
return "PathErrorUnknown";
133+
case NSchemeCache::TSchemeCacheNavigate::EStatus::PathNotTable:
134+
return "PathNotTable";
135+
case NSchemeCache::TSchemeCacheNavigate::EStatus::PathNotPath:
136+
return "PathNotPath";
137+
case NSchemeCache::TSchemeCacheNavigate::EStatus::TableCreationNotComplete:
138+
return "TableCreationNotComplete";
139+
case NSchemeCache::TSchemeCacheNavigate::EStatus::LookupError:
140+
return "LookupError";
141+
case NSchemeCache::TSchemeCacheNavigate::EStatus::RedirectLookupError:
142+
return "RedirectLookupError";
143+
case NSchemeCache::TSchemeCacheNavigate::EStatus::AccessDenied:
144+
return "AccessDenied";
145+
default:
146+
return ::ToString(static_cast<int>(ev->Request->ResultSet.begin()->Status));
147+
}
148+
}
149+
116150
void TViewerPipeClient::RequestHiveDomainStats(NNodeWhiteboard::TTabletId hiveId) {
117151
TActorId pipeClient = ConnectTabletPipe(hiveId);
118152
THolder<TEvHive::TEvRequestHiveDomainStats> request = MakeHolder<TEvHive::TEvRequestHiveDomainStats>();
@@ -266,6 +300,11 @@ TViewerPipeClient::TRequestResponse<NConsole::TEvConsole::TEvGetNodeConfigRespon
266300
return MakeRequestToPipe<NConsole::TEvConsole::TEvGetNodeConfigResponse>(pipeClient, request.Release(), cookie);
267301
}
268302

303+
TViewerPipeClient::TRequestResponse<NConsole::TEvConsole::TEvGetAllConfigsResponse> TViewerPipeClient::MakeRequestConsoleGetAllConfigs() {
304+
TActorId pipeClient = ConnectTabletPipe(GetConsoleId());
305+
return MakeRequestToPipe<NConsole::TEvConsole::TEvGetAllConfigsResponse>(pipeClient, new NConsole::TEvConsole::TEvGetAllConfigsRequest());
306+
}
307+
269308
void TViewerPipeClient::RequestConsoleGetTenantStatus(const TString& path) {
270309
TActorId pipeClient = ConnectTabletPipe(GetConsoleId());
271310
THolder<NConsole::TEvConsole::TEvGetTenantStatusRequest> request = MakeHolder<NConsole::TEvConsole::TEvGetTenantStatusRequest>();

ydb/core/viewer/json_pipe_req.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@ class TViewerPipeClient : public TActorBootstrapped<TViewerPipeClient> {
7373
TRequestResponse& operator =(TRequestResponse&&) = default;
7474

7575
void Set(std::unique_ptr<T>&& response) {
76+
constexpr bool hasErrorCheck = requires(const std::unique_ptr<T>& r) {TViewerPipeClient::IsSuccess(r);};
77+
if constexpr (hasErrorCheck) {
78+
if (!TViewerPipeClient::IsSuccess(response)) {
79+
Error(TViewerPipeClient::GetError(response));
80+
return;
81+
}
82+
}
7683
if (!IsDone()) {
7784
Span.EndOk();
7885
}
@@ -200,12 +207,17 @@ class TViewerPipeClient : public TActorBootstrapped<TViewerPipeClient> {
200207

201208
static TPathId GetPathId(TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev);
202209
static TString GetPath(TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev);
210+
211+
static bool IsSuccess(const std::unique_ptr<TEvTxProxySchemeCache::TEvNavigateKeySetResult>& ev);
212+
static TString GetError(const std::unique_ptr<TEvTxProxySchemeCache::TEvNavigateKeySetResult>& ev);
213+
203214
TRequestResponse<TEvHive::TEvResponseHiveDomainStats> MakeRequestHiveDomainStats(TTabletId hiveId);
204215
TRequestResponse<TEvHive::TEvResponseHiveStorageStats> MakeRequestHiveStorageStats(TTabletId hiveId);
205216
TRequestResponse<TEvHive::TEvResponseHiveNodeStats> MakeRequestHiveNodeStats(TTabletId hiveId, TEvHive::TEvRequestHiveNodeStats* request);
206217
void RequestConsoleListTenants();
207218
TRequestResponse<NConsole::TEvConsole::TEvListTenantsResponse> MakeRequestConsoleListTenants();
208219
TRequestResponse<NConsole::TEvConsole::TEvGetNodeConfigResponse> MakeRequestConsoleNodeConfigByTenant(TString tenant, ui64 cookie = 0);
220+
TRequestResponse<NConsole::TEvConsole::TEvGetAllConfigsResponse> MakeRequestConsoleGetAllConfigs();
209221
void RequestConsoleGetTenantStatus(const TString& path);
210222
TRequestResponse<NConsole::TEvConsole::TEvGetTenantStatusResponse> MakeRequestConsoleGetTenantStatus(const TString& path);
211223
void RequestBSControllerConfig();

ydb/core/viewer/storage_groups.h

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,15 +1112,7 @@ class TStorageGroups : public TViewerPipeClient {
11121112
return RequestDone();
11131113
}
11141114
auto& navigateResult(itNavigateKeySetResult->second);
1115-
if (ev->Get()->Request->ResultSet.size() == 1) {
1116-
if (ev->Get()->Request->ResultSet.begin()->Status == NSchemeCache::TSchemeCacheNavigate::EStatus::Ok) {
1117-
navigateResult.Set(std::move(ev));
1118-
} else {
1119-
navigateResult.Error(TStringBuilder() << "Error " << ev->Get()->Request->ResultSet.begin()->Status);
1120-
}
1121-
} else {
1122-
navigateResult.Error(TStringBuilder() << "Invalid number of results: " << ev->Get()->Request->ResultSet.size());
1123-
}
1115+
navigateResult.Set(std::move(ev));
11241116
if (navigateResult.IsOk()) {
11251117
TString path = CanonizePath(navigateResult->Request->ResultSet.begin()->Path);
11261118
TIntrusiveConstPtr<TSchemeCacheNavigate::TDomainDescription> domainDescription = navigateResult->Request->ResultSet.begin()->DomainDescription;

ydb/core/viewer/viewer_feature_flags.h

Lines changed: 115 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22
#include "json_pipe_req.h"
33
#include "viewer.h"
4+
#include <ydb/library/yaml_config/yaml_config.h>
45

56
namespace 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

2523
public:
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

Comments
 (0)