mirror of https://gitee.com/bigwinds/arangodb
1226 lines
47 KiB
C++
1226 lines
47 KiB
C++
////////////////////////////////////////////////////////////////////////////////
|
|
/// DISCLAIMER
|
|
///
|
|
/// Copyright 2014-2018 ArangoDB GmbH, Cologne, Germany
|
|
/// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany
|
|
///
|
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|
/// you may not use this file except in compliance with the License.
|
|
/// You may obtain a copy of the License at
|
|
///
|
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
|
///
|
|
/// Unless required by applicable law or agreed to in writing, software
|
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
/// See the License for the specific language governing permissions and
|
|
/// limitations under the License.
|
|
///
|
|
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
|
///
|
|
/// @author Kaveh Vahedipour
|
|
/// @author Matthew Von-Maszewski
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "Cluster/Maintenance.h"
|
|
#include "Agency/AgencyStrings.h"
|
|
#include "ApplicationFeatures/ApplicationServer.h"
|
|
#include "Basics/StringUtils.h"
|
|
#include "Basics/VelocyPackHelper.h"
|
|
#include "Cluster/ClusterFeature.h"
|
|
#include "Cluster/ClusterInfo.h"
|
|
#include "Cluster/FollowerInfo.h"
|
|
#include "Indexes/Index.h"
|
|
#include "Logger/Logger.h"
|
|
#include "Utils/DatabaseGuard.h"
|
|
#include "VocBase/LogicalCollection.h"
|
|
#include "VocBase/Methods/Databases.h"
|
|
|
|
#include <velocypack/Collection.h>
|
|
#include <velocypack/Compare.h>
|
|
#include <velocypack/Iterator.h>
|
|
#include <velocypack/Slice.h>
|
|
#include <velocypack/velocypack-aliases.h>
|
|
|
|
#include <algorithm>
|
|
#include <regex>
|
|
|
|
using namespace arangodb;
|
|
using namespace arangodb::consensus;
|
|
using namespace arangodb::basics;
|
|
using namespace arangodb::maintenance;
|
|
using namespace arangodb::methods;
|
|
using namespace arangodb::basics::StringUtils;
|
|
|
|
static std::vector<std::string> const cmp{JOURNAL_SIZE, WAIT_FOR_SYNC, DO_COMPACT, INDEX_BUCKETS};
|
|
|
|
static VPackValue const VP_DELETE("delete");
|
|
static VPackValue const VP_SET("set");
|
|
|
|
static int indexOf(VPackSlice const& slice, std::string const& val) {
|
|
if (slice.isArray()) {
|
|
int counter = 0;
|
|
for (auto const& entry : VPackArrayIterator(slice)) {
|
|
if (entry.isString()) {
|
|
if (entry.copyString() == val) {
|
|
return counter;
|
|
}
|
|
}
|
|
counter++;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static std::shared_ptr<VPackBuilder> createProps(VPackSlice const& s) {
|
|
TRI_ASSERT(s.isObject());
|
|
return std::make_shared<VPackBuilder>(
|
|
arangodb::velocypack::Collection::remove(s, std::unordered_set<std::string>({ID, NAME})));
|
|
}
|
|
|
|
static std::shared_ptr<VPackBuilder> compareRelevantProps(VPackSlice const& first,
|
|
VPackSlice const& second) {
|
|
auto result = std::make_shared<VPackBuilder>();
|
|
{
|
|
VPackObjectBuilder b(result.get());
|
|
for (auto const& property : cmp) {
|
|
auto const& planned = first.get(property);
|
|
if (!basics::VelocyPackHelper::equal(planned, second.get(property), false)) { // Register any change
|
|
result->add(property, planned);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static VPackBuilder compareIndexes(std::string const& dbname, std::string const& collname,
|
|
std::string const& shname,
|
|
VPackSlice const& plan, VPackSlice const& local,
|
|
MaintenanceFeature::errors_t const& errors,
|
|
std::unordered_set<std::string>& indis) {
|
|
VPackBuilder builder;
|
|
{
|
|
VPackArrayBuilder a(&builder);
|
|
if (plan.isArray()) {
|
|
for (auto const& pindex : VPackArrayIterator(plan)) {
|
|
// Skip primary and edge indexes
|
|
auto const& ptype = pindex.get(StaticStrings::IndexType).copyString();
|
|
if (ptype == PRIMARY || ptype == EDGE) {
|
|
continue;
|
|
}
|
|
VPackSlice planId = pindex.get(ID);
|
|
TRI_ASSERT(planId.isString());
|
|
std::string planIdS = planId.copyString();
|
|
std::string planIdWithColl = shname + "/" + planIdS;
|
|
indis.emplace(planIdWithColl);
|
|
|
|
// See, if we already have an index with the id given in the Plan:
|
|
bool found = false;
|
|
if (local.isArray()) {
|
|
for (auto const& lindex : VPackArrayIterator(local)) {
|
|
// Skip primary and edge indexes
|
|
auto const& ltype = lindex.get(StaticStrings::IndexType).copyString();
|
|
if (ltype == PRIMARY || ltype == EDGE) {
|
|
continue;
|
|
}
|
|
|
|
VPackSlice localId = lindex.get(ID);
|
|
TRI_ASSERT(localId.isString());
|
|
// The local ID has the form <collectionName>/<ID>, to compare,
|
|
// we need to extract the local ID:
|
|
std::string localIdS = localId.copyString();
|
|
auto pos = localIdS.find('/');
|
|
if (pos != std::string::npos) {
|
|
localIdS = localIdS.substr(pos + 1);
|
|
}
|
|
|
|
if (localIdS == planIdS) {
|
|
// Already have this id, so abort search:
|
|
found = true;
|
|
// We should be done now, this index already exists, and since
|
|
// one cannot legally change the properties of an index, we
|
|
// should be fine. However, for robustness sake, we compare,
|
|
// if the local index found actually has the right properties,
|
|
// if not, we schedule a dropIndex action:
|
|
if (!arangodb::Index::Compare(pindex, lindex)) {
|
|
// To achieve this, we remove the long version of the ID
|
|
// from the indis set. This way, the local index will be
|
|
// dropped further down in handleLocalShard:
|
|
indis.erase(planIdWithColl);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!found) {
|
|
// Finally check if we have an error for this index:
|
|
bool haveError = false;
|
|
std::string errorKey = dbname + "/" + collname + "/" + shname;
|
|
auto it1 = errors.indexes.find(errorKey);
|
|
if (it1 != errors.indexes.end()) {
|
|
auto it2 = it1->second.find(planIdS);
|
|
if (it2 != it1->second.end()) {
|
|
haveError = true;
|
|
}
|
|
}
|
|
if (!haveError) {
|
|
builder.add(pindex);
|
|
} else {
|
|
LOG_TOPIC("ceb3d", DEBUG, Logger::MAINTENANCE)
|
|
<< "Previous failure exists for index " << planIdS
|
|
<< " on shard " << dbname << "/" << shname << " for central "
|
|
<< dbname << "/" << collname << "- skipping";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return builder;
|
|
}
|
|
|
|
void handlePlanShard(VPackSlice const& cprops, VPackSlice const& ldb,
|
|
std::string const& dbname, std::string const& colname,
|
|
std::string const& shname, std::string const& serverId,
|
|
std::string const& leaderId, std::unordered_set<std::string>& commonShrds,
|
|
std::unordered_set<std::string>& indis,
|
|
MaintenanceFeature::errors_t& errors, MaintenanceFeature& feature,
|
|
std::vector<ActionDescription>& actions) {
|
|
bool shouldBeLeading = serverId == leaderId;
|
|
|
|
commonShrds.emplace(shname);
|
|
auto props = createProps(cprops); // Only once might need often!
|
|
|
|
if (ldb.hasKey(shname)) { // Have local collection with that name
|
|
auto const lcol = ldb.get(shname);
|
|
bool leading = lcol.get(THE_LEADER).copyString().empty();
|
|
auto const properties = compareRelevantProps(cprops, lcol);
|
|
|
|
auto fullShardLabel = dbname + "/" + colname + "/" + shname;
|
|
|
|
// Check if there is some in-sync-follower which is no longer in the Plan:
|
|
std::string followersToDropString;
|
|
if (leading && shouldBeLeading) {
|
|
VPackSlice shards = cprops.get("shards");
|
|
if (shards.isObject()) {
|
|
VPackSlice planServers = shards.get(shname);
|
|
if (planServers.isArray()) {
|
|
VPackSlice inSyncFollowers = lcol.get("servers");
|
|
if (inSyncFollowers.isArray()) {
|
|
// Now we have two server lists, we are looking for a server
|
|
// which does not occur in the plan, but is in the followers
|
|
// at an index > 0:
|
|
std::unordered_set<std::string> followersToDrop;
|
|
for (auto const& q : VPackArrayIterator(inSyncFollowers)) {
|
|
followersToDrop.insert(q.copyString());
|
|
}
|
|
for (auto const& p : VPackArrayIterator(planServers)) {
|
|
if (p.isString()) {
|
|
followersToDrop.erase(p.copyString());
|
|
}
|
|
}
|
|
// Everything remaining in followersToDrop is something we
|
|
// need to act on
|
|
for (auto const& r : followersToDrop) {
|
|
if (!followersToDropString.empty()) {
|
|
followersToDropString.push_back(',');
|
|
}
|
|
followersToDropString += r;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If comparison has brought any updates
|
|
if (!properties->slice().isObject() || properties->slice().length() > 0 ||
|
|
leading != shouldBeLeading || !followersToDropString.empty()) {
|
|
if (errors.shards.find(fullShardLabel) == errors.shards.end()) {
|
|
actions.emplace_back(ActionDescription(
|
|
std::map<std::string, std::string>{
|
|
{NAME, UPDATE_COLLECTION},
|
|
{DATABASE, dbname},
|
|
{COLLECTION, colname},
|
|
{SHARD, shname},
|
|
{THE_LEADER, shouldBeLeading ? std::string() : leaderId},
|
|
{SERVER_ID, serverId},
|
|
{LOCAL_LEADER, lcol.get(THE_LEADER).copyString()},
|
|
{FOLLOWERS_TO_DROP, followersToDropString},
|
|
{OLD_CURRENT_COUNTER, std::to_string(feature.getCurrentCounter())}},
|
|
HIGHER_PRIORITY, properties));
|
|
} else {
|
|
LOG_TOPIC("0285b", DEBUG, Logger::MAINTENANCE)
|
|
<< "Previous failure exists for local shard " << dbname << "/" << shname
|
|
<< "for central " << dbname << "/" << colname << "- skipping";
|
|
}
|
|
}
|
|
|
|
// Indexes
|
|
if (cprops.hasKey(INDEXES)) {
|
|
auto const& pindexes = cprops.get(INDEXES);
|
|
auto const& lindexes = lcol.get(INDEXES);
|
|
auto difference =
|
|
compareIndexes(dbname, colname, shname, pindexes, lindexes, errors, indis);
|
|
|
|
// Index errors are checked in `compareIndexes`. THe loop below only
|
|
// cares about those indexes that have no error.
|
|
if (difference.slice().isArray()) {
|
|
for (auto const& index : VPackArrayIterator(difference.slice())) {
|
|
actions.emplace_back(ActionDescription(
|
|
{{NAME, "EnsureIndex"},
|
|
{DATABASE, dbname},
|
|
{COLLECTION, colname},
|
|
{SHARD, shname},
|
|
{StaticStrings::IndexType, index.get(StaticStrings::IndexType).copyString()},
|
|
{FIELDS, index.get(FIELDS).toJson()},
|
|
{ID, index.get(ID).copyString()}},
|
|
INDEX_PRIORITY, std::make_shared<VPackBuilder>(index)));
|
|
}
|
|
}
|
|
}
|
|
} else { // Create the sucker, if not a previous error stops us
|
|
if (errors.shards.find(dbname + "/" + colname + "/" + shname) ==
|
|
errors.shards.end()) {
|
|
actions.emplace_back(
|
|
ActionDescription({{NAME, CREATE_COLLECTION},
|
|
{COLLECTION, colname},
|
|
{SHARD, shname},
|
|
{DATABASE, dbname},
|
|
{SERVER_ID, serverId},
|
|
{THE_LEADER, shouldBeLeading ? std::string() : leaderId}},
|
|
shouldBeLeading ? LEADER_PRIORITY : FOLLOWER_PRIORITY, props));
|
|
} else {
|
|
LOG_TOPIC("c1d8e", DEBUG, Logger::MAINTENANCE)
|
|
<< "Previous failure exists for creating local shard " << dbname << "/"
|
|
<< shname << "for central " << dbname << "/" << colname << "- skipping";
|
|
}
|
|
}
|
|
}
|
|
|
|
void handleLocalShard(std::string const& dbname, std::string const& colname,
|
|
VPackSlice const& cprops, VPackSlice const& shardMap,
|
|
std::unordered_set<std::string>& commonShrds,
|
|
std::unordered_set<std::string>& indis, std::string const& serverId,
|
|
std::vector<ActionDescription>& actions) {
|
|
std::unordered_set<std::string>::const_iterator it;
|
|
|
|
std::string plannedLeader;
|
|
if (shardMap.hasKey(colname) && shardMap.get(colname).isArray()) {
|
|
plannedLeader = shardMap.get(colname)[0].copyString();
|
|
}
|
|
bool localLeader = cprops.get(THE_LEADER).copyString().empty();
|
|
if (plannedLeader == UNDERSCORE + serverId && localLeader) {
|
|
actions.emplace_back(
|
|
ActionDescription({{NAME, "ResignShardLeadership"}, {DATABASE, dbname}, {SHARD, colname}},
|
|
RESIGN_PRIORITY));
|
|
} else {
|
|
bool drop = false;
|
|
// check if shard is in plan, if not drop it
|
|
if (commonShrds.empty()) {
|
|
drop = true;
|
|
} else {
|
|
it = std::find(commonShrds.begin(), commonShrds.end(), colname);
|
|
if (it == commonShrds.end()) {
|
|
drop = true;
|
|
}
|
|
}
|
|
|
|
if (drop) {
|
|
actions.emplace_back(
|
|
ActionDescription({{NAME, DROP_COLLECTION}, {DATABASE, dbname}, {COLLECTION, colname}},
|
|
localLeader ? LEADER_PRIORITY : FOLLOWER_PRIORITY));
|
|
} else {
|
|
// The shard exists in both Plan and Local
|
|
commonShrds.erase(it); // it not a common shard?
|
|
|
|
// We only drop indexes, when collection is not being dropped already
|
|
if (cprops.hasKey(INDEXES)) {
|
|
if (cprops.get(INDEXES).isArray()) {
|
|
for (auto const& index : VPackArrayIterator(cprops.get(INDEXES))) {
|
|
auto const& type = index.get(StaticStrings::IndexType).copyString();
|
|
if (type != PRIMARY && type != EDGE) {
|
|
std::string const id = index.get(ID).copyString();
|
|
|
|
// check if index is in plan
|
|
if (indis.find(colname + "/" + id) != indis.end() ||
|
|
indis.find(id) != indis.end()) {
|
|
indis.erase(id);
|
|
} else {
|
|
actions.emplace_back(ActionDescription({{NAME, "DropIndex"},
|
|
{DATABASE, dbname},
|
|
{COLLECTION, colname},
|
|
{"index", id}},
|
|
INDEX_PRIORITY));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// @brief Get a map shardName -> servers
|
|
VPackBuilder getShardMap(VPackSlice const& plan) {
|
|
VPackBuilder shardMap;
|
|
{
|
|
VPackObjectBuilder o(&shardMap);
|
|
for (auto database : VPackObjectIterator(plan)) {
|
|
for (auto collection : VPackObjectIterator(database.value)) {
|
|
for (auto shard : VPackObjectIterator(collection.value.get(SHARDS))) {
|
|
std::string const shName = shard.key.copyString();
|
|
shardMap.add(shName, shard.value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return shardMap;
|
|
}
|
|
|
|
struct NotEmpty {
|
|
bool operator()(const std::string& s) { return !s.empty(); }
|
|
};
|
|
|
|
/// @brief calculate difference between plan and local for for databases
|
|
arangodb::Result arangodb::maintenance::diffPlanLocal(
|
|
VPackSlice const& plan, VPackSlice const& local,
|
|
std::string const& serverId, MaintenanceFeature::errors_t& errors,
|
|
MaintenanceFeature& feature, std::vector<ActionDescription>& actions) {
|
|
arangodb::Result result;
|
|
std::unordered_set<std::string> commonShrds; // Intersection collections plan&local
|
|
std::unordered_set<std::string> indis; // Intersection indexes plan&local
|
|
|
|
// Plan to local mismatch ----------------------------------------------------
|
|
// Create or modify if local databases are affected
|
|
auto pdbs = plan.get(DATABASES);
|
|
for (auto const& pdb : VPackObjectIterator(pdbs)) {
|
|
auto const& dbname = pdb.key.copyString();
|
|
if (!local.hasKey(dbname)) {
|
|
if (errors.databases.find(dbname) == errors.databases.end()) {
|
|
actions.emplace_back(
|
|
ActionDescription({{std::string(NAME), std::string(CREATE_DATABASE)},
|
|
{std::string(DATABASE), std::string(dbname)}},
|
|
HIGHER_PRIORITY));
|
|
} else {
|
|
LOG_TOPIC("3a6a8", DEBUG, Logger::MAINTENANCE)
|
|
<< "Previous failure exists for creating database " << dbname << "skipping";
|
|
}
|
|
}
|
|
}
|
|
|
|
// Drop databases, which are no longer in plan
|
|
for (auto const& ldb : VPackObjectIterator(local)) {
|
|
auto const& dbname = ldb.key.copyString();
|
|
if (!plan.hasKey(std::vector<std::string>{DATABASES, dbname})) {
|
|
actions.emplace_back(
|
|
ActionDescription({{std::string(NAME), std::string(DROP_DATABASE)},
|
|
{std::string(DATABASE), std::string(dbname)}},
|
|
HIGHER_PRIORITY));
|
|
}
|
|
}
|
|
|
|
// Check errors for databases, which are no longer in plan and remove from
|
|
// errors
|
|
for (auto& database : errors.databases) {
|
|
if (!plan.hasKey(std::vector<std::string>{DATABASES, database.first})) {
|
|
database.second.reset();
|
|
}
|
|
}
|
|
|
|
// Create or modify if local collections are affected
|
|
pdbs = plan.get(COLLECTIONS);
|
|
for (auto const& pdb : VPackObjectIterator(pdbs)) { // for each db in Plan
|
|
auto const& dbname = pdb.key.copyString();
|
|
if (local.hasKey(dbname)) { // have database in both
|
|
auto const& ldb = local.get(dbname);
|
|
for (auto const& pcol : VPackObjectIterator(pdb.value)) { // for each plan collection
|
|
auto const& cprops = pcol.value;
|
|
for (auto const& shard : VPackObjectIterator(cprops.get(SHARDS))) { // for each shard in plan collection
|
|
if (shard.value.isArray()) {
|
|
for (auto const& dbs : VPackArrayIterator(shard.value)) { // for each db server with that shard
|
|
// We only care for shards, where we find us as "serverId" or
|
|
// "_serverId"
|
|
if (dbs.isEqualString(serverId) || dbs.isEqualString(UNDERSCORE + serverId)) {
|
|
// at this point a shard is in plan, we have the db for it
|
|
handlePlanShard(cprops, ldb, dbname, pcol.key.copyString(),
|
|
shard.key.copyString(), serverId,
|
|
shard.value[0].copyString(), commonShrds, indis,
|
|
errors, feature, actions);
|
|
break;
|
|
}
|
|
}
|
|
} // else if(!shard.value.isArray()) - intentionally do nothing
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// At this point commonShrds contains all shards that eventually reside on
|
|
// this server, are in Plan and their database is present
|
|
|
|
// Compare local to plan -----------------------------------------------------
|
|
auto const shardMap = getShardMap(pdbs); // plan shards -> servers
|
|
for (auto const& db : VPackObjectIterator(local)) { // for each local databases
|
|
auto const& dbname = db.key.copyString();
|
|
if (pdbs.hasKey(dbname)) { // if in plan
|
|
for (auto const& sh : VPackObjectIterator(db.value)) { // for each local shard
|
|
std::string shName = sh.key.copyString();
|
|
handleLocalShard(dbname, shName, sh.value, shardMap.slice(),
|
|
commonShrds, indis, serverId, actions);
|
|
}
|
|
}
|
|
}
|
|
|
|
// See if shard errors can be thrown out:
|
|
for (auto& shard : errors.shards) {
|
|
std::vector<std::string> path = split(shard.first, '/');
|
|
path.pop_back(); // Get rid of shard
|
|
if (!pdbs.hasKey(path)) { // we can drop the local error
|
|
shard.second.reset();
|
|
}
|
|
}
|
|
|
|
// See if index errors can be thrown out:
|
|
for (auto& shard : errors.indexes) {
|
|
std::vector<std::string> path = split(shard.first, '/'); // dbname, collection, shardid
|
|
path.pop_back(); // dbname, collection
|
|
path.emplace_back(INDEXES); // dbname, collection, indexes
|
|
VPackSlice indexes = pdbs.get(path);
|
|
if (!indexes.isArray()) { // collection gone, can drop errors
|
|
for (auto& index : shard.second) {
|
|
index.second.reset();
|
|
}
|
|
} else { // need to look at individual errors and indexes:
|
|
for (auto& p : shard.second) {
|
|
std::string const& id = p.first;
|
|
bool found = false;
|
|
for (auto const& ind : VPackArrayIterator(indexes)) {
|
|
if (ind.get(ID).copyString() == id) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
p.second.reset();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// @brief handle plan for local databases
|
|
arangodb::Result arangodb::maintenance::executePlan(VPackSlice const& plan,
|
|
VPackSlice const& local,
|
|
std::string const& serverId,
|
|
MaintenanceFeature& feature,
|
|
VPackBuilder& report) {
|
|
arangodb::Result result;
|
|
|
|
// Errors from maintenance feature
|
|
MaintenanceFeature::errors_t errors;
|
|
result = feature.copyAllErrors(errors);
|
|
if (!result.ok()) {
|
|
LOG_TOPIC("9039d", ERR, Logger::MAINTENANCE)
|
|
<< "phaseOne: failed to acquire copy of errors from maintenance "
|
|
"feature.";
|
|
return result;
|
|
}
|
|
|
|
// build difference between plan and local
|
|
std::vector<ActionDescription> actions;
|
|
report.add(VPackValue(AGENCY));
|
|
{
|
|
// TODO: Just putting an empty array does not make any sense here!
|
|
VPackArrayBuilder a(&report);
|
|
diffPlanLocal(plan, local, serverId, errors, feature, actions);
|
|
}
|
|
|
|
for (auto const& i : errors.databases) {
|
|
if (i.second == nullptr) {
|
|
feature.removeDBError(i.first);
|
|
}
|
|
}
|
|
for (auto const& i : errors.shards) {
|
|
if (i.second == nullptr) {
|
|
feature.removeShardError(i.first);
|
|
}
|
|
}
|
|
for (auto const& i : errors.indexes) {
|
|
std::unordered_set<std::string> tmp;
|
|
for (auto const& index : i.second) {
|
|
if (index.second == nullptr) {
|
|
tmp.emplace(index.first);
|
|
}
|
|
}
|
|
if (!tmp.empty()) {
|
|
feature.removeIndexErrors(i.first, tmp);
|
|
}
|
|
}
|
|
|
|
TRI_ASSERT(report.isOpenObject());
|
|
report.add(VPackValue(ACTIONS));
|
|
{
|
|
VPackArrayBuilder a(&report);
|
|
// enact all
|
|
for (auto const& action : actions) {
|
|
LOG_TOPIC("8513c", DEBUG, Logger::MAINTENANCE)
|
|
<< "adding action " << action << " to feature ";
|
|
{
|
|
VPackObjectBuilder b(&report);
|
|
action.toVelocyPack(report);
|
|
}
|
|
feature.addAction(std::make_shared<ActionDescription>(action), false);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// @brief add new database to current
|
|
void addDatabaseToTransactions(std::string const& name, Transactions& transactions) {
|
|
// [ {"dbPath":{}}, {"dbPath":{"oldEmpty":true}} ]
|
|
|
|
std::string dbPath = CURRENT_COLLECTIONS + name;
|
|
VPackBuilder operation; // create database in current
|
|
{
|
|
VPackObjectBuilder b(&operation);
|
|
operation.add(dbPath, VPackSlice::emptyObjectSlice());
|
|
}
|
|
VPackBuilder precondition;
|
|
{
|
|
VPackObjectBuilder b(&precondition);
|
|
precondition.add(VPackValue(dbPath));
|
|
{
|
|
VPackObjectBuilder bb(&precondition);
|
|
precondition.add("oldEmpty", VPackValue(true));
|
|
}
|
|
}
|
|
transactions.push_back({operation, precondition});
|
|
}
|
|
|
|
/// @brief report local to current
|
|
arangodb::Result arangodb::maintenance::diffLocalCurrent(VPackSlice const& local,
|
|
VPackSlice const& current,
|
|
std::string const& serverId,
|
|
Transactions& transactions) {
|
|
arangodb::Result result;
|
|
auto const& cdbs = current;
|
|
|
|
// Iterate over local databases
|
|
for (auto const& ldbo : VPackObjectIterator(local)) {
|
|
std::string dbname = ldbo.key.copyString();
|
|
// VPackSlice ldb = ldbo.value;
|
|
|
|
// Current has this database
|
|
if (cdbs.hasKey(dbname)) {
|
|
} else {
|
|
// Create new database in current
|
|
addDatabaseToTransactions(dbname, transactions);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// @brief Phase one: Compare plan and local and create descriptions
|
|
arangodb::Result arangodb::maintenance::phaseOne(VPackSlice const& plan,
|
|
VPackSlice const& local,
|
|
std::string const& serverId,
|
|
MaintenanceFeature& feature,
|
|
VPackBuilder& report) {
|
|
arangodb::Result result;
|
|
|
|
report.add(VPackValue(PHASE_ONE));
|
|
{
|
|
VPackObjectBuilder por(&report);
|
|
|
|
// Execute database changes
|
|
try {
|
|
result = executePlan(plan, local, serverId, feature, report);
|
|
} catch (std::exception const& e) {
|
|
LOG_TOPIC("55938", ERR, Logger::MAINTENANCE)
|
|
<< "Error executing plan: " << e.what() << ". " << __FILE__ << ":" << __LINE__;
|
|
}
|
|
}
|
|
|
|
report.add(VPackValue(PLAN));
|
|
{
|
|
VPackObjectBuilder p(&report);
|
|
report.add("Version", plan.get("Version"));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static VPackBuilder removeSelectivityEstimate(VPackSlice const& index) {
|
|
TRI_ASSERT(index.isObject());
|
|
return arangodb::velocypack::Collection::remove(index, std::unordered_set<std::string>(
|
|
{SELECTIVITY_ESTIMATE}));
|
|
}
|
|
|
|
static VPackBuilder assembleLocalCollectionInfo(
|
|
VPackSlice const& info, VPackSlice const& planServers,
|
|
std::string const& database, std::string const& shard,
|
|
std::string const& ourselves, MaintenanceFeature::errors_t const& allErrors) {
|
|
VPackBuilder ret;
|
|
|
|
try {
|
|
DatabaseGuard guard(database);
|
|
auto vocbase = &guard.database();
|
|
|
|
auto collection = vocbase->lookupCollection(shard);
|
|
if (collection == nullptr) {
|
|
std::string errorMsg(
|
|
"Maintenance::assembleLocalCollectionInfo: Failed to lookup "
|
|
"collection ");
|
|
errorMsg += shard;
|
|
LOG_TOPIC("33a3b", DEBUG, Logger::MAINTENANCE) << errorMsg;
|
|
{ VPackObjectBuilder o(&ret); }
|
|
return ret;
|
|
}
|
|
|
|
std::string errorKey =
|
|
database + "/" + std::to_string(collection->planId()) + "/" + shard;
|
|
{
|
|
VPackObjectBuilder r(&ret);
|
|
auto it = allErrors.shards.find(errorKey);
|
|
if (it == allErrors.shards.end()) {
|
|
ret.add(StaticStrings::Error, VPackValue(false));
|
|
ret.add(StaticStrings::ErrorMessage, VPackValue(std::string()));
|
|
ret.add(StaticStrings::ErrorNum, VPackValue(0));
|
|
} else {
|
|
VPackSlice errs(static_cast<uint8_t const*>(it->second->data()));
|
|
ret.add(StaticStrings::Error, errs.get(StaticStrings::Error));
|
|
ret.add(StaticStrings::ErrorNum, errs.get(StaticStrings::ErrorNum));
|
|
ret.add(StaticStrings::ErrorMessage, errs.get(StaticStrings::ErrorMessage));
|
|
}
|
|
ret.add(VPackValue(INDEXES));
|
|
{
|
|
VPackArrayBuilder ixs(&ret);
|
|
if (info.get(INDEXES).isArray()) {
|
|
auto it1 = allErrors.indexes.find(errorKey);
|
|
std::unordered_set<std::string> indexesDone;
|
|
// First the indexes as they are in Local, potentially replaced
|
|
// by an error:
|
|
for (auto const& index : VPackArrayIterator(info.get(INDEXES))) {
|
|
std::string id = index.get(ID).copyString();
|
|
indexesDone.insert(id);
|
|
if (it1 != allErrors.indexes.end()) {
|
|
auto it2 = it1->second.find(id);
|
|
if (it2 != it1->second.end()) {
|
|
// Add the error instead:
|
|
ret.add(VPackSlice(static_cast<uint8_t const*>(it2->second->data())));
|
|
continue;
|
|
}
|
|
}
|
|
ret.add(removeSelectivityEstimate(index).slice());
|
|
}
|
|
// Now all the errors for this shard, for which there is no index:
|
|
if (it1 != allErrors.indexes.end()) {
|
|
for (auto const& p : it1->second) {
|
|
if (indexesDone.find(p.first) == indexesDone.end()) {
|
|
ret.add(VPackSlice(static_cast<uint8_t const*>(p.second->data())));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
collection->followers()->injectFollowerInfo(ret);
|
|
}
|
|
return ret;
|
|
} catch (std::exception const& e) {
|
|
ret.clear();
|
|
std::string errorMsg(
|
|
"Maintenance::assembleLocalCollectionInfo: Failed to lookup "
|
|
"database ");
|
|
errorMsg += database;
|
|
errorMsg += ", exception: ";
|
|
errorMsg += e.what();
|
|
errorMsg += " (this is expected if the database was recently deleted).";
|
|
LOG_TOPIC("7fe5d", WARN, Logger::MAINTENANCE) << errorMsg;
|
|
{ VPackObjectBuilder o(&ret); }
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
bool equivalent(VPackSlice const& local, VPackSlice const& current) {
|
|
for (auto const& i : VPackObjectIterator(local)) {
|
|
if (!VPackNormalizedCompare::equals(i.value, current.get(i.key.copyString()))) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static VPackBuilder assembleLocalDatabaseInfo(std::string const& database,
|
|
MaintenanceFeature::errors_t const& allErrors) {
|
|
// This creates the VelocyPack that is put into
|
|
// /Current/Databases/<dbname>/<serverID> for a database.
|
|
|
|
VPackBuilder ret;
|
|
|
|
try {
|
|
DatabaseGuard guard(database);
|
|
auto vocbase = &guard.database();
|
|
|
|
{
|
|
VPackObjectBuilder o(&ret);
|
|
auto it = allErrors.databases.find(database);
|
|
if (it == allErrors.databases.end()) {
|
|
ret.add(StaticStrings::Error, VPackValue(false));
|
|
ret.add(StaticStrings::ErrorNum, VPackValue(0));
|
|
ret.add(StaticStrings::ErrorMessage, VPackValue(""));
|
|
} else {
|
|
VPackSlice errs(static_cast<uint8_t const*>(it->second->data()));
|
|
ret.add(StaticStrings::Error, errs.get(StaticStrings::Error));
|
|
ret.add(StaticStrings::ErrorNum, errs.get(StaticStrings::ErrorNum));
|
|
ret.add(StaticStrings::ErrorMessage, errs.get(StaticStrings::ErrorMessage));
|
|
}
|
|
ret.add(ID, VPackValue(std::to_string(vocbase->id())));
|
|
ret.add("name", VPackValue(vocbase->name()));
|
|
}
|
|
|
|
return ret;
|
|
} catch (std::exception const& e) {
|
|
ret.clear(); // In case the above has mid air collision.
|
|
std::string errorMsg(
|
|
"Maintenance::assembleLocalDatabaseInfo: Failed to lookup database ");
|
|
errorMsg += database;
|
|
errorMsg += ", exception: ";
|
|
errorMsg += e.what();
|
|
LOG_TOPIC("989b6", DEBUG, Logger::MAINTENANCE) << errorMsg;
|
|
{ VPackObjectBuilder o(&ret); }
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
// updateCurrentForCollections
|
|
// diff current and local and prepare agency transactions or whatever
|
|
// to update current. Will report the errors created locally to the agency
|
|
arangodb::Result arangodb::maintenance::reportInCurrent(
|
|
VPackSlice const& plan, VPackSlice const& cur, VPackSlice const& local,
|
|
MaintenanceFeature::errors_t const& allErrors, std::string const& serverId,
|
|
VPackBuilder& report) {
|
|
arangodb::Result result;
|
|
|
|
auto shardMap = getShardMap(plan.get(COLLECTIONS));
|
|
auto pdbs = plan.get(COLLECTIONS);
|
|
|
|
for (auto const& database : VPackObjectIterator(local)) {
|
|
auto const dbName = database.key.copyString();
|
|
|
|
std::vector<std::string> const cdbpath{DATABASES, dbName, serverId};
|
|
|
|
if (!cur.hasKey(cdbpath)) {
|
|
auto const localDatabaseInfo = assembleLocalDatabaseInfo(dbName, allErrors);
|
|
TRI_ASSERT(!localDatabaseInfo.slice().isNone());
|
|
if (!localDatabaseInfo.slice().isEmptyObject() &&
|
|
!localDatabaseInfo.slice().isNone()) {
|
|
report.add(VPackValue(CURRENT_DATABASES + dbName + "/" + serverId));
|
|
{
|
|
VPackObjectBuilder o(&report);
|
|
report.add(OP, VP_SET);
|
|
report.add("payload", localDatabaseInfo.slice());
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto const& shard : VPackObjectIterator(database.value)) {
|
|
auto const shName = shard.key.copyString();
|
|
auto const shSlice = shard.value;
|
|
auto const colName = shSlice.get(StaticStrings::DataSourcePlanId).copyString();
|
|
|
|
VPackBuilder error;
|
|
if (shSlice.get(THE_LEADER).copyString().empty()) { // Leader
|
|
|
|
// Check that we are the leader of this shard in the Plan, together
|
|
// with the precondition below that the Plan is unchanged, this ensures
|
|
// that we only ever modify Current if we are the leader in the Plan:
|
|
auto const planPath = std::vector<std::string>{dbName, colName, "shards", shName};
|
|
if (!pdbs.hasKey(planPath)) {
|
|
LOG_TOPIC("43242", DEBUG, Logger::MAINTENANCE)
|
|
<< "Ooops, we have a shard for which we believe to be the "
|
|
"leader,"
|
|
" but the Plan does not have it any more, we do not report "
|
|
"in "
|
|
"Current about this, database: "
|
|
<< dbName << ", shard: " << shName;
|
|
continue;
|
|
}
|
|
|
|
VPackSlice thePlanList = pdbs.get(planPath);
|
|
if (!thePlanList.isArray() || thePlanList.length() == 0 ||
|
|
!thePlanList[0].isString() || !thePlanList[0].isEqualStringUnchecked(serverId)) {
|
|
LOG_TOPIC("87776", DEBUG, Logger::MAINTENANCE)
|
|
<< "Ooops, we have a shard for which we believe to be the "
|
|
"leader,"
|
|
" but the Plan says otherwise, we do not report in Current "
|
|
"about this, database: "
|
|
<< dbName << ", shard: " << shName;
|
|
continue;
|
|
}
|
|
|
|
auto const localCollectionInfo =
|
|
assembleLocalCollectionInfo(shSlice, shardMap.slice().get(shName),
|
|
dbName, shName, serverId, allErrors);
|
|
// Collection no longer exists
|
|
TRI_ASSERT(!localCollectionInfo.slice().isNone());
|
|
if (localCollectionInfo.slice().isEmptyObject() ||
|
|
localCollectionInfo.slice().isNone()) {
|
|
continue;
|
|
}
|
|
|
|
auto cp = std::vector<std::string>{COLLECTIONS, dbName, colName, shName};
|
|
auto inCurrent = cur.hasKey(cp);
|
|
|
|
if (!inCurrent || !equivalent(localCollectionInfo.slice(), cur.get(cp))) {
|
|
report.add(VPackValue(CURRENT_COLLECTIONS + dbName + "/" + colName + "/" + shName));
|
|
|
|
{
|
|
VPackObjectBuilder o(&report);
|
|
report.add(OP, VP_SET);
|
|
// Report new current entry ...
|
|
report.add("payload", localCollectionInfo.slice());
|
|
// ... if and only if plan for this shard has changed in the
|
|
// meantime Add a precondition:
|
|
report.add(VPackValue("precondition"));
|
|
{
|
|
VPackObjectBuilder p(&report);
|
|
report.add(PLAN_COLLECTIONS + dbName + "/" + colName + "/shards/" + shName,
|
|
thePlanList);
|
|
}
|
|
}
|
|
}
|
|
} else { // Follower
|
|
|
|
auto servers =
|
|
std::vector<std::string>{COLLECTIONS, dbName, colName, shName, SERVERS};
|
|
if (cur.hasKey(servers)) {
|
|
auto s = cur.get(servers);
|
|
if (s.isArray() && cur.get(servers)[0].copyString() == serverId) {
|
|
// We are in the situation after a restart, that we do not know
|
|
// who the leader is because FollowerInfo is not updated yet.
|
|
// Hence, in the case we are the Leader in Plan but do not
|
|
// know it yet, do nothing here.
|
|
if (shSlice.get("theLeaderTouched").isTrue()) {
|
|
// we were previously leader and we are done resigning.
|
|
// update current and let supervision handle the rest, however
|
|
// check that we are in the Plan a leader which is supposed to
|
|
// resign and add a precondition that this is still the case:
|
|
|
|
auto const planPath =
|
|
std::vector<std::string>{dbName, colName, "shards", shName};
|
|
if (!pdbs.hasKey(planPath)) {
|
|
LOG_TOPIC("65432", DEBUG, Logger::MAINTENANCE)
|
|
<< "Ooops, we have a shard for which we believe that we "
|
|
"just resigned, but the Plan does not have it any "
|
|
"more,"
|
|
" we do not report in Current about this, database: "
|
|
<< dbName << ", shard: " << shName;
|
|
continue;
|
|
}
|
|
|
|
VPackSlice thePlanList = pdbs.get(planPath);
|
|
if (!thePlanList.isArray() || thePlanList.length() == 0 ||
|
|
!thePlanList[0].isString() ||
|
|
!thePlanList[0].isEqualStringUnchecked(UNDERSCORE + serverId)) {
|
|
LOG_TOPIC("99987", DEBUG, Logger::MAINTENANCE)
|
|
<< "Ooops, we have a shard for which we believe that we "
|
|
"have just resigned, but the Plan says otherwise, we "
|
|
"do not report in Current about this, database: "
|
|
<< dbName << ", shard: " << shName;
|
|
continue;
|
|
}
|
|
VPackBuilder ns;
|
|
{
|
|
VPackArrayBuilder a(&ns);
|
|
if (s.isArray()) {
|
|
bool front = true;
|
|
for (auto const& i : VPackArrayIterator(s)) {
|
|
ns.add(VPackValue((!front) ? i.copyString()
|
|
: UNDERSCORE + i.copyString()));
|
|
front = false;
|
|
}
|
|
}
|
|
}
|
|
report.add(VPackValue(CURRENT_COLLECTIONS + dbName + "/" +
|
|
colName + "/" + shName + "/" + SERVERS));
|
|
|
|
{
|
|
VPackObjectBuilder o(&report);
|
|
report.add(OP, VP_SET);
|
|
report.add("payload", ns.slice());
|
|
{
|
|
VPackObjectBuilder p(&report, "precondition");
|
|
report.add(PLAN_COLLECTIONS + dbName + "/" + colName +
|
|
"/shards/" + shName,
|
|
thePlanList);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// UpdateCurrentForDatabases
|
|
auto cdbs = cur.get(DATABASES);
|
|
for (auto const& database : VPackObjectIterator(cdbs)) {
|
|
auto const dbName = database.key.copyString();
|
|
if (!database.value.isObject()) {
|
|
continue;
|
|
}
|
|
VPackSlice myEntry = database.value.get(serverId);
|
|
if (!myEntry.isNone()) {
|
|
// Database no longer in Plan and local
|
|
if (!local.hasKey(dbName) && !pdbs.hasKey(dbName)) {
|
|
// This covers the case that the database is neither in Local nor in
|
|
// Plan. It remains to make sure an error is reported to Current if
|
|
// there is a database in the Plan but not in Local
|
|
report.add(VPackValue(CURRENT_DATABASES + dbName + "/" + serverId));
|
|
{
|
|
VPackObjectBuilder o(&report);
|
|
report.add(OP, VP_DELETE);
|
|
}
|
|
// We delete all under /Current/Collections/<dbName>, it does not
|
|
// hurt if every DBserver does this, since it is an idempotent
|
|
// operation.
|
|
report.add(VPackValue(CURRENT_COLLECTIONS + dbName));
|
|
{
|
|
VPackObjectBuilder o(&report);
|
|
report.add(OP, VP_DELETE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// UpdateCurrentForCollections
|
|
auto curcolls = cur.get(COLLECTIONS);
|
|
for (auto const& database : VPackObjectIterator(curcolls)) {
|
|
auto const dbName = database.key.copyString();
|
|
|
|
// UpdateCurrentForCollections (Current/Collections/Collection)
|
|
for (auto const& collection : VPackObjectIterator(database.value)) {
|
|
auto const colName = collection.key.copyString();
|
|
|
|
for (auto const& shard : VPackObjectIterator(collection.value)) {
|
|
auto const shName = shard.key.copyString();
|
|
|
|
// Shard in current and has servers
|
|
if (shard.value.hasKey(SERVERS)) {
|
|
auto servers = shard.value.get(SERVERS);
|
|
|
|
if (servers.isArray() && servers.length() > 0 // servers in current
|
|
&& servers[0].copyString() == serverId // we are leading
|
|
&& !local.hasKey(std::vector<std::string>{dbName, shName}) // no local collection
|
|
&& !shardMap.slice().hasKey(shName)) { // no such shard in plan
|
|
report.add(VPackValue(CURRENT_COLLECTIONS + dbName + "/" + colName + "/" + shName));
|
|
{
|
|
VPackObjectBuilder o(&report);
|
|
report.add(OP, VP_DELETE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Let's find database errors for databases which do not occur in Local
|
|
// but in Plan:
|
|
VPackSlice planDatabases = plan.get(DATABASES);
|
|
VPackSlice curDatabases = cur.get(DATABASES);
|
|
if (planDatabases.isObject() && curDatabases.isObject()) {
|
|
for (auto const& p : allErrors.databases) {
|
|
VPackSlice planDbEntry = planDatabases.get(p.first);
|
|
VPackSlice curDbEntry = curDatabases.get(p.first);
|
|
if (planDbEntry.isObject() && curDbEntry.isNone()) {
|
|
// Need to create an error entry:
|
|
report.add(VPackValue(CURRENT_DATABASES + p.first + "/" + serverId));
|
|
{
|
|
VPackObjectBuilder o(&report);
|
|
report.add(OP, VP_SET);
|
|
report.add(VPackValue("payload"));
|
|
{
|
|
VPackObjectBuilder pp(&report);
|
|
VPackSlice errs(static_cast<uint8_t const*>(p.second->data()));
|
|
report.add(StaticStrings::Error, errs.get(StaticStrings::Error));
|
|
report.add(StaticStrings::ErrorNum, errs.get(StaticStrings::ErrorNum));
|
|
report.add(StaticStrings::ErrorMessage, errs.get(StaticStrings::ErrorMessage));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Finally, let's find shard errors for shards which do not occur in
|
|
// Local but in Plan, we need to make sure that these errors are reported
|
|
// in Current:
|
|
for (auto const& p : allErrors.shards) {
|
|
// First split the key:
|
|
std::string const& key = p.first;
|
|
auto pos = key.find('/');
|
|
TRI_ASSERT(pos != std::string::npos);
|
|
std::string d = key.substr(0, pos); // database
|
|
auto pos2 = key.find('/', pos + 1); // collection
|
|
TRI_ASSERT(pos2 != std::string::npos);
|
|
std::string c = key.substr(pos + 1, pos2);
|
|
std::string s = key.substr(pos2 + 1); // shard name
|
|
|
|
// Now find out if the shard appears in the Plan but not in Local:
|
|
VPackSlice inPlan = pdbs.get(std::vector<std::string>({d, c, "shards", s}));
|
|
VPackSlice inLoc = local.get(std::vector<std::string>({d, s}));
|
|
if (inPlan.isObject() && inLoc.isNone()) {
|
|
VPackSlice inCur = curcolls.get(std::vector<std::string>({d, c, s}));
|
|
VPackSlice theErr(static_cast<uint8_t const*>(p.second->data()));
|
|
if (inCur.isNone() || !equivalent(theErr, inCur)) {
|
|
report.add(VPackValue(CURRENT_COLLECTIONS + d + "/" + c + "/" + s));
|
|
{
|
|
VPackObjectBuilder o(&report);
|
|
report.add(OP, VP_SET);
|
|
report.add("payload", theErr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
arangodb::Result arangodb::maintenance::syncReplicatedShardsWithLeaders(
|
|
VPackSlice const& plan, VPackSlice const& current, VPackSlice const& local,
|
|
std::string const& serverId, MaintenanceFeature& feature,
|
|
std::vector<ActionDescription>& actions) {
|
|
auto pdbs = plan.get(COLLECTIONS);
|
|
auto cdbs = current.get(COLLECTIONS);
|
|
|
|
for (auto const& pdb : VPackObjectIterator(pdbs)) {
|
|
auto const& dbname = pdb.key.copyString();
|
|
if (local.hasKey(dbname) && cdbs.hasKey(dbname)) {
|
|
for (auto const& pcol : VPackObjectIterator(pdb.value)) {
|
|
auto const& colname = pcol.key.copyString();
|
|
if (cdbs.get(dbname).hasKey(colname)) {
|
|
for (auto const& pshrd : VPackObjectIterator(pcol.value.get(SHARDS))) {
|
|
auto const& shname = pshrd.key.copyString();
|
|
|
|
// shard does not exist locally so nothing we can do at this point
|
|
if (!local.hasKey(std::vector<std::string>{dbname, shname})) {
|
|
continue;
|
|
}
|
|
|
|
// current stuff is created by the leader this one here will just
|
|
// bring followers in sync so just continue here
|
|
auto cpath = std::vector<std::string>{dbname, colname, shname};
|
|
if (!cdbs.hasKey(cpath)) {
|
|
LOG_TOPIC("402a4", DEBUG, Logger::MAINTENANCE)
|
|
<< "Shard " << shname
|
|
<< " not in current yet. Rescheduling maintenance.";
|
|
continue;
|
|
}
|
|
|
|
// Plan's servers
|
|
auto ppath = std::vector<std::string>{dbname, colname, SHARDS, shname};
|
|
if (!pdbs.hasKey(ppath)) {
|
|
LOG_TOPIC("e1136", ERR, Logger::MAINTENANCE)
|
|
<< "Shard " << shname << " does not have servers substructure in 'Plan'";
|
|
continue;
|
|
}
|
|
auto const& pservers = pdbs.get(ppath);
|
|
|
|
// Current's servers
|
|
cpath.push_back(SERVERS);
|
|
if (!cdbs.hasKey(cpath)) {
|
|
LOG_TOPIC("1d596", ERR, Logger::MAINTENANCE)
|
|
<< "Shard " << shname
|
|
<< " does not have servers substructure in 'Current'";
|
|
continue;
|
|
}
|
|
auto const& cservers = cdbs.get(cpath);
|
|
|
|
// we are not planned to be a follower
|
|
if (indexOf(pservers, serverId) <= 0) {
|
|
continue;
|
|
}
|
|
// if we are considered to be in sync there is nothing to do
|
|
if (indexOf(cservers, serverId) > 0) {
|
|
continue;
|
|
}
|
|
|
|
auto const leader = pservers[0].copyString();
|
|
actions.emplace_back(ActionDescription(
|
|
{{NAME, SYNCHRONIZE_SHARD},
|
|
{DATABASE, dbname},
|
|
{COLLECTION, colname},
|
|
{SHARD, shname},
|
|
{THE_LEADER, leader},
|
|
{SHARD_VERSION, std::to_string(feature.shardVersion(shname))}},
|
|
SYNCHRONIZE_PRIORITY));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return Result();
|
|
}
|
|
|
|
/// @brief Phase two: See, what we can report to the agency
|
|
arangodb::Result arangodb::maintenance::phaseTwo(VPackSlice const& plan,
|
|
VPackSlice const& cur,
|
|
VPackSlice const& local,
|
|
std::string const& serverId,
|
|
MaintenanceFeature& feature,
|
|
VPackBuilder& report) {
|
|
MaintenanceFeature::errors_t allErrors;
|
|
feature.copyAllErrors(allErrors);
|
|
|
|
arangodb::Result result;
|
|
|
|
report.add(VPackValue(PHASE_TWO));
|
|
{
|
|
VPackObjectBuilder p2(&report);
|
|
|
|
// agency transactions
|
|
report.add(VPackValue("agency"));
|
|
{
|
|
VPackObjectBuilder agency(&report);
|
|
// Update Current
|
|
try {
|
|
result = reportInCurrent(plan, cur, local, allErrors, serverId, report);
|
|
} catch (std::exception const& e) {
|
|
LOG_TOPIC("c9a75", ERR, Logger::MAINTENANCE)
|
|
<< "Error reporting in current: " << e.what() << ". " << __FILE__
|
|
<< ":" << __LINE__;
|
|
}
|
|
}
|
|
|
|
// maintenace actions
|
|
report.add(VPackValue("actions"));
|
|
{
|
|
VPackObjectBuilder agency(&report);
|
|
try {
|
|
std::vector<ActionDescription> actions;
|
|
result = syncReplicatedShardsWithLeaders(plan, cur, local, serverId, feature, actions);
|
|
|
|
for (auto const& action : actions) {
|
|
feature.addAction(std::make_shared<ActionDescription>(action), false);
|
|
}
|
|
} catch (std::exception const& e) {
|
|
LOG_TOPIC("7e286", ERR, Logger::MAINTENANCE)
|
|
<< "Error scheduling shards: " << e.what() << ". " << __FILE__
|
|
<< ":" << __LINE__;
|
|
}
|
|
}
|
|
}
|
|
|
|
report.add(VPackValue("Current"));
|
|
{
|
|
VPackObjectBuilder p(&report);
|
|
report.add("Version", cur.get("Version"));
|
|
}
|
|
|
|
return result;
|
|
}
|