Skip to content

Commit 8d830b4

Browse files
XaBbl4dyemanov
authored andcommitted
Do not connect to the database if the replication configuration is incorrect (#8381)
* Do not connect to the database if the replication configuration is incorrect Configuration errors can lead to loss of synchronization between master and replica. * Send config errors to the client app only if report_errors is enabled * Report a replication error if it occurs during the initial connection to the replica and the report_errors is enabled * Add error "all configured replicas skipped"
1 parent e7a69e7 commit 8d830b4

2 files changed

Lines changed: 125 additions & 28 deletions

File tree

src/jrd/replication/Config.cpp

Lines changed: 121 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ namespace
6262
const ULONG DEFAULT_GROUP_FLUSH_DELAY = 0;
6363
const ULONG DEFAULT_APPLY_IDLE_TIMEOUT = 10; // seconds
6464
const ULONG DEFAULT_APPLY_ERROR_TIMEOUT = 60; // seconds
65+
const bool DEFAULT_REPORT_ERRORS = false;
6566

6667
void parseLong(const string& input, ULONG& output)
6768
{
@@ -79,36 +80,53 @@ namespace
7980
output = false;
8081
}
8182

82-
void configError(const string& type, const string& key, const string& value)
83+
void configError(CheckStatusWrapper* status, const string& type, const string& key, const string& value)
8384
{
84-
raiseError("%s specifies %s: %s", key.c_str(), type.c_str(), value.c_str());
85+
string msg;
86+
if (!(status->getState() & IStatus::STATE_ERRORS))
87+
{
88+
msg.printf("Incorrect entry in %s", REPLICATION_CFGFILE);
89+
(Arg::Gds(isc_random) << Arg::Str(msg)).appendTo(status);
90+
}
91+
92+
msg.printf("%s specifies %s: %s", key.c_str(), type.c_str(), value.c_str());
93+
(Arg::Gds(isc_random) << Arg::Str(msg)).appendTo(status);
8594
}
8695

87-
void checkAccess(const PathName& path, const string& key)
96+
bool checkAccess(CheckStatusWrapper* status, const PathName& path, const string& key)
8897
{
8998
if (path.hasData() && !PathUtils::canAccess(path, 6))
90-
configError("missing or inaccessible directory", key, path.c_str());
99+
{
100+
configError(status, "missing or inaccessible directory", key, path.c_str());
101+
return false;
102+
}
103+
return true;
91104
}
92105

93106
void composeError(CheckStatusWrapper* status, const Exception& ex)
94107
{
95-
string prefix;
96-
prefix.printf("Incorrect entry in %s", REPLICATION_CFGFILE);
97-
98108
Arg::StatusVector sv;
99-
sv << Arg::Gds(isc_random) << Arg::Str(prefix);
109+
110+
if (!(status->getState() & IStatus::STATE_ERRORS))
111+
{
112+
string prefix;
113+
prefix.printf("Incorrect entry in %s", REPLICATION_CFGFILE);
114+
sv << Arg::Gds(isc_random) << Arg::Str(prefix);
115+
}
116+
117+
sv << Arg::StatusVector(status);
100118
sv << Arg::StatusVector(ex);
101119

102120
status->setErrors(sv.value());
103121
}
104122

105-
void parseExternalValue(const string& key, const string& value, string& output)
123+
bool parseExternalValue(CheckStatusWrapper* status, const string& key, const string& value, string& output)
106124
{
107125
const auto pos = key.rfind('_');
108126
if (pos == string::npos)
109127
{
110128
output = value.c_str();
111-
return;
129+
return true;
112130
}
113131

114132
string temp;
@@ -118,7 +136,10 @@ namespace
118136
{
119137
fb_utils::readenv(value.c_str(), temp);
120138
if (temp.isEmpty())
121-
configError("missing environment variable", key, value);
139+
{
140+
configError(status, "missing environment variable", key, value);
141+
return false;
142+
}
122143
}
123144
else if (key_source.equals(KEY_SUFFIX_FILE))
124145
{
@@ -129,44 +150,65 @@ namespace
129150

130151
AutoPtr<FILE> file(os_utils::fopen(filename.c_str(), "rt"));
131152
if (!file)
132-
configError("missing or inaccessible file", key, filename.c_str());
153+
{
154+
configError(status, "missing or inaccessible file", key, filename.c_str());
155+
return false;
156+
}
133157

134158
if (temp.LoadFromFile(file))
135159
temp.alltrim("\r");
136160

137161
if (temp.isEmpty())
138-
configError("first empty line of file", key, filename.c_str());
162+
{
163+
configError(status, "first empty line of file", key, filename.c_str());
164+
return false;
165+
}
139166
}
140167

141168
output = temp.c_str();
169+
return true;
142170
}
143171

144-
void parseSyncReplica(const ConfigFile::Parameters& params, SyncReplica& output)
172+
bool parseSyncReplica(CheckStatusWrapper* status, const ConfigFile::Parameters& params, SyncReplica& output)
145173
{
174+
bool result = true;
146175
for (const auto& el : params)
147176
{
148177
const string key(el.name.c_str());
149178
const string value(el.value);
150179

151180
if (value.isEmpty())
152-
continue;
181+
{
182+
configError(status, "empty value", output.database, key);
183+
result = false;
184+
}
153185

154186
if (key.find("username") == 0)
155187
{
156188
if (output.username.hasData())
157-
configError("multiple values", output.database, "username");
158-
parseExternalValue(key, value, output.username);
189+
{
190+
configError(status, "multiple values", output.database, "username");
191+
result = false;
192+
}
193+
result &= parseExternalValue(status, key, value, output.username);
159194
output.username.rtrim(" ");
160195
}
161196
else if (key.find("password") == 0)
162197
{
163198
if (output.password.hasData())
164-
configError("multiple values", output.database, "password");
165-
parseExternalValue(key, value, output.password);
199+
{
200+
configError(status, "multiple values", output.database, "password");
201+
result = false;
202+
}
203+
result &= parseExternalValue(status, key, value, output.password);
166204
}
167205
else
168-
configError("unknown parameter", output.database, key);
206+
{
207+
configError(status, "unknown key", output.database, key);
208+
result = false;
209+
}
169210
}
211+
return result;
170212
}
171213
}
172214

@@ -194,7 +236,7 @@ Config::Config()
194236
applyErrorTimeout(DEFAULT_APPLY_ERROR_TIMEOUT),
195237
pluginName(getPool()),
196238
logErrors(true),
197-
reportErrors(false),
239+
reportErrors(DEFAULT_REPORT_ERRORS),
198240
disableOnError(true),
199241
cascadeReplication(false)
200242
{
@@ -234,6 +276,7 @@ Config* Config::get(const PathName& lookupName)
234276
{
235277
fb_assert(lookupName.hasData());
236278

279+
bool reportErrors = DEFAULT_REPORT_ERRORS;
237280
try
238281
{
239282
const PathName filename =
@@ -245,7 +288,8 @@ Config* Config::get(const PathName& lookupName)
245288

246289
AutoPtr<Config> config(FB_NEW Config);
247290

248-
bool defaultFound = false, exactMatch = false;
291+
FbLocalStatus localStatus;
292+
bool defaultFound = false, exactMatch = false, replicaSkip = false;
249293

250294
for (const auto& section : cfgFile.getParameters())
251295
{
@@ -283,15 +327,24 @@ Config* Config::get(const PathName& lookupName)
283327
string value(el.value);
284328

285329
if (value.isEmpty())
330+
{
331+
configError(&localStatus, "empty value of key",
332+
exactMatch ? lookupName.c_str() : section.name.c_str(),
333+
key);
286334
continue;
335+
}
287336

288337
if (key == "sync_replica")
289338
{
290339
SyncReplica syncReplica(config->getPool());
291340
if (el.sub)
292341
{
293342
syncReplica.database = value;
294-
parseSyncReplica(el.sub->getParameters(), syncReplica);
343+
if (!parseSyncReplica(&localStatus, el.sub->getParameters(), syncReplica))
344+
{
345+
replicaSkip = true;
346+
continue;
347+
}
295348
}
296349
else
297350
splitConnectionString(value, syncReplica.database, syncReplica.username, syncReplica.password);
@@ -324,7 +377,12 @@ Config* Config::get(const PathName& lookupName)
324377
{
325378
config->journalDirectory = value.c_str();
326379
PathUtils::ensureSeparator(config->journalDirectory);
327-
checkAccess(config->journalDirectory, key);
380+
if (!checkAccess(&localStatus, config->journalDirectory, key))
381+
{
382+
config->journalDirectory.erase();
383+
replicaSkip = true;
384+
continue;
385+
}
328386
}
329387
else if (key == "journal_file_prefix")
330388
{
@@ -338,7 +396,11 @@ Config* Config::get(const PathName& lookupName)
338396
{
339397
config->archiveDirectory = value.c_str();
340398
PathUtils::ensureSeparator(config->archiveDirectory);
341-
checkAccess(config->archiveDirectory, key);
399+
if (!checkAccess(&localStatus, config->archiveDirectory, key))
400+
{
401+
config->archiveDirectory.erase();
402+
continue;
403+
}
342404
}
343405
else if (key == "journal_archive_command")
344406
{
@@ -359,6 +421,7 @@ Config* Config::get(const PathName& lookupName)
359421
else if (key == "report_errors")
360422
{
361423
parseBoolean(value, config->reportErrors);
424+
reportErrors = config->reportErrors;
362425
}
363426
else if (key == "disable_on_error")
364427
{
@@ -368,12 +431,28 @@ Config* Config::get(const PathName& lookupName)
368431
{
369432
parseBoolean(value, config->cascadeReplication);
370433
}
434+
else if ((key != "journal_source_directory") &&
435+
(key != "source_guid") &&
436+
(key != "verbose_logging") &&
437+
(key != "apply_idle_timeout") &&
438+
(key != "apply_error_timeout"))
439+
{
440+
configError(&localStatus, "unknown key",
441+
exactMatch ? lookupName.c_str() : section.name.c_str(),
442+
key);
443+
}
371444
}
372445

373446
if (exactMatch)
374447
break;
375448
}
376449

450+
if (localStatus->getState() & IStatus::STATE_ERRORS)
451+
logPrimaryStatus(lookupName, &localStatus);
452+
453+
if (replicaSkip && !config->disableOnError)
454+
raiseError("One or more replicas configured with errors");
455+
377456
// TODO: As soon as plugin name is moved into RDB$PUBLICATIONS,
378457
// delay config parse until real replication start
379458
if (config->pluginName.hasData())
@@ -396,13 +475,18 @@ Config* Config::get(const PathName& lookupName)
396475

397476
return config.release();
398477
}
478+
479+
if (replicaSkip)
480+
raiseError("All configured replicas skipped");
399481
}
400482
catch (const Exception& ex)
401483
{
402484
FbLocalStatus localStatus;
403485
composeError(&localStatus, ex);
404486

405487
logPrimaryStatus(lookupName, &localStatus);
488+
if (reportErrors)
489+
localStatus.raise();
406490
}
407491

408492
return nullptr;
@@ -414,6 +498,7 @@ Config* Config::get(const PathName& lookupName)
414498
void Config::enumerate(ReplicaList& replicas)
415499
{
416500
PathName dbName;
501+
FbLocalStatus localStatus;
417502

418503
try
419504
{
@@ -466,11 +551,19 @@ void Config::enumerate(ReplicaList& replicas)
466551
{
467552
config->sourceDirectory = value.c_str();
468553
PathUtils::ensureSeparator(config->sourceDirectory);
554+
if (!checkAccess(&localStatus, config->sourceDirectory, key))
555+
{
556+
config->sourceDirectory.erase();
557+
continue;
558+
}
469559
}
470560
else if (key == "source_guid")
471561
{
472562
if (!StringToGuid(&config->sourceGuid, value.c_str()))
473-
configError("invalid (misformatted) value", key, value);
563+
{
564+
configError(&localStatus, "invalid (misformatted) value", key, value);
565+
continue;
566+
}
474567
}
475568
else if (key == "verbose_logging")
476569
{
@@ -500,11 +593,11 @@ void Config::enumerate(ReplicaList& replicas)
500593
}
501594
catch (const Exception& ex)
502595
{
503-
FbLocalStatus localStatus;
504596
composeError(&localStatus, ex);
597+
}
505598

599+
if (localStatus->getState() & IStatus::STATE_ERRORS)
506600
logReplicaStatus(dbName, &localStatus);
507-
}
508601
}
509602

510603
// This routine is used for split input connection string to parts

src/jrd/replication/Manager.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ Manager::Manager(const string& dbId,
161161
if (localStatus->getState() & IStatus::STATE_ERRORS)
162162
{
163163
logPrimaryStatus(m_config->dbName, &localStatus);
164+
if (m_config->reportErrors)
165+
(Arg::Gds(isc_repl_error) << Arg::StatusVector(&localStatus)).raise();
164166
continue;
165167
}
166168

@@ -169,6 +171,8 @@ Manager::Manager(const string& dbId,
169171
{
170172
logPrimaryStatus(m_config->dbName, &localStatus);
171173
attachment->detach(&localStatus);
174+
if (m_config->reportErrors)
175+
(Arg::Gds(isc_repl_error) << Arg::StatusVector(&localStatus)).raise();
172176
continue;
173177
}
174178

0 commit comments

Comments
 (0)